220 lines
6.7 KiB
JavaScript
220 lines
6.7 KiB
JavaScript
let localStream = null;
|
|
const remoteStreams = {};
|
|
|
|
const Media = {
|
|
async loadDevices() {
|
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
|
|
const cameras = devices.filter(d => d.kind === "videoinput");
|
|
const mics = devices.filter(d => d.kind === "audioinput");
|
|
|
|
const cameraSelect = document.getElementById("cameraSelect");
|
|
const micSelect = document.getElementById("micSelect");
|
|
|
|
cameraSelect.innerHTML = "";
|
|
micSelect.innerHTML = "";
|
|
|
|
for (const camera of cameras) {
|
|
const option = document.createElement("option");
|
|
option.value = camera.deviceId;
|
|
option.textContent = camera.label || `Camera ${cameraSelect.length + 1}`;
|
|
cameraSelect.appendChild(option);
|
|
}
|
|
|
|
for (const mic of mics) {
|
|
const option = document.createElement("option");
|
|
option.value = mic.deviceId;
|
|
option.textContent = mic.label || `Microphone ${micSelect.length + 1}`;
|
|
micSelect.appendChild(option);
|
|
}
|
|
|
|
LogMessage(`Loaded devices: ${cameras.length} cameras, ${mics.length} mics`);
|
|
},
|
|
|
|
async ensureLocalMedia() {
|
|
const cameraSelect = document.getElementById("cameraSelect");
|
|
const micSelect = document.getElementById("micSelect");
|
|
|
|
if (localStream) {
|
|
return localStream;
|
|
}
|
|
|
|
const audioDeviceId = micSelect?.value;
|
|
const videoDeviceId = cameraSelect?.value;
|
|
|
|
const constraints = {
|
|
audio: audioDeviceId
|
|
? { deviceId: { exact: audioDeviceId } }
|
|
: true,
|
|
video: videoDeviceId
|
|
? { deviceId: { exact: videoDeviceId } }
|
|
: true
|
|
};
|
|
|
|
try {
|
|
localStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
} catch {
|
|
localStream = await navigator.mediaDevices.getUserMedia({
|
|
audio: audioDeviceId
|
|
? { deviceId: { exact: audioDeviceId } }
|
|
: true,
|
|
video: false
|
|
});
|
|
|
|
LogMessage("No camera available, continuing without video");
|
|
}
|
|
|
|
this.attachLocalStream(localStream);
|
|
LogMessage("Local media initialized");
|
|
|
|
return localStream;
|
|
},
|
|
|
|
attachLocalStream(stream) {
|
|
const localVideo = document.getElementById("localVideo");
|
|
const localMediaStatus = document.getElementById("localMediaStatus");
|
|
const localVideoStatus = document.getElementById("localVideoStatus");
|
|
|
|
if (localVideo) {
|
|
localVideo.srcObject = stream;
|
|
}
|
|
|
|
const audioTracks = stream.getAudioTracks();
|
|
const videoTracks = stream.getVideoTracks();
|
|
|
|
if (localMediaStatus) {
|
|
localMediaStatus.textContent =
|
|
audioTracks.length > 0
|
|
? "Microphone active"
|
|
: "No microphone";
|
|
}
|
|
|
|
if (localVideoStatus) {
|
|
localVideoStatus.textContent =
|
|
videoTracks.length > 0
|
|
? "Local video active"
|
|
: "Local video unavailable";
|
|
}
|
|
},
|
|
|
|
async refreshDevicesAndPreview() {
|
|
if (localStream) {
|
|
localStream.getTracks().forEach(track => track.stop());
|
|
localStream = null;
|
|
}
|
|
|
|
await this.loadDevices();
|
|
await this.ensureLocalMedia();
|
|
|
|
if (window.RelayRtc?.applyLocalStreamToAllPeerConnections) {
|
|
await window.RelayRtc.applyLocalStreamToAllPeerConnections();
|
|
}
|
|
},
|
|
|
|
async applyLocalStreamToPeerConnection(pc, username) {
|
|
const stream = await this.ensureLocalMedia();
|
|
|
|
const existingSenders = pc.getSenders();
|
|
|
|
for (const track of stream.getTracks()) {
|
|
const existingSender = existingSenders.find(sender =>
|
|
sender.track && sender.track.kind === track.kind
|
|
);
|
|
|
|
if (existingSender) {
|
|
await existingSender.replaceTrack(track);
|
|
} else {
|
|
pc.addTrack(track, stream);
|
|
}
|
|
|
|
LogMessage(`Added local ${track.kind} track for ${username}`);
|
|
}
|
|
},
|
|
|
|
async applyLocalStreamToAllPeerConnections() {
|
|
if (!window.RelayRtc?.peerConnections) return;
|
|
|
|
for (const [username, pc] of Object.entries(window.RelayRtc.peerConnections)) {
|
|
await this.applyLocalStreamToPeerConnection(pc, username);
|
|
}
|
|
},
|
|
|
|
attachRemoteStream(username, stream) {
|
|
remoteStreams[username] = stream;
|
|
|
|
const tile = this.ensureRemoteTile(username);
|
|
const video = tile.querySelector("video");
|
|
const status = tile.querySelector(".remote-media-status");
|
|
|
|
video.srcObject = stream;
|
|
|
|
const audioTracks = stream.getAudioTracks();
|
|
const videoTracks = stream.getVideoTracks();
|
|
|
|
status.textContent =
|
|
`${audioTracks.length > 0 ? "Audio" : "No audio"} / ` +
|
|
`${videoTracks.length > 0 ? "Video" : "No video"}`;
|
|
},
|
|
|
|
ensureRemoteTile(username) {
|
|
const container = document.getElementById("remoteMediaContainer");
|
|
|
|
let tile = document.getElementById(`remote-tile-${username}`);
|
|
|
|
if (tile) return tile;
|
|
|
|
tile = document.createElement("div");
|
|
tile.id = `remote-tile-${username}`;
|
|
tile.className = "remote-media-tile";
|
|
|
|
const title = document.createElement("div");
|
|
title.className = "remote-media-title";
|
|
title.textContent = username;
|
|
|
|
const video = document.createElement("video");
|
|
video.autoplay = true;
|
|
video.playsInline = true;
|
|
|
|
const status = document.createElement("div");
|
|
status.className = "remote-media-status";
|
|
status.textContent = "Remote media: waiting...";
|
|
|
|
tile.appendChild(title);
|
|
tile.appendChild(video);
|
|
tile.appendChild(status);
|
|
|
|
container.appendChild(tile);
|
|
|
|
return tile;
|
|
},
|
|
|
|
removeRemoteStream(username) {
|
|
delete remoteStreams[username];
|
|
|
|
const tile = document.getElementById(`remote-tile-${username}`);
|
|
if (tile) {
|
|
tile.remove();
|
|
}
|
|
},
|
|
|
|
wireDeviceSelectors() {
|
|
const cameraSelect = document.getElementById("cameraSelect");
|
|
const micSelect = document.getElementById("micSelect");
|
|
|
|
if (cameraSelect) {
|
|
cameraSelect.addEventListener("change", async () => {
|
|
LogMessage("Camera changed");
|
|
await this.refreshDevicesAndPreview();
|
|
});
|
|
}
|
|
|
|
if (micSelect) {
|
|
micSelect.addEventListener("change", async () => {
|
|
LogMessage("Microphone changed");
|
|
await this.refreshDevicesAndPreview();
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
window.Media = Media; |