|
|
|
|
@@ -96,19 +96,25 @@ async function ensurePeerConnection() {
|
|
|
|
|
LogMessage("ICE gathering state: " + peerConnection.iceGatheringState);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
async function ensureLocalMedia() {
|
|
|
|
|
if (localStream) return;
|
|
|
|
|
|
|
|
|
|
async function ensureLocalMedia(forceReload = false) {
|
|
|
|
|
const localMediaStatus = document.getElementById("localMediaStatus");
|
|
|
|
|
const localVideoStatus = document.getElementById("localVideoStatus");
|
|
|
|
|
const localVideo = document.getElementById("localVideo");
|
|
|
|
|
const cameraSelect = document.getElementById("cameraSelect");
|
|
|
|
|
const micSelect = document.getElementById("micSelect");
|
|
|
|
|
|
|
|
|
|
if (localStream && !forceReload) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (localStream) {
|
|
|
|
|
localStream.getTracks().forEach(track => track.stop());
|
|
|
|
|
localStream = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let selectedCameraId = cameraSelect ? cameraSelect.value : "";
|
|
|
|
|
let selectedMicId = micSelect ? micSelect.value : "";
|
|
|
|
|
|
|
|
|
|
let mediaError = null;
|
|
|
|
|
|
|
|
|
|
const videoConstraint = selectedCameraId
|
|
|
|
|
? { deviceId: { exact: selectedCameraId } }
|
|
|
|
|
: false;
|
|
|
|
|
@@ -125,7 +131,6 @@ async function ensureLocalMedia() {
|
|
|
|
|
|
|
|
|
|
LogMessage("Local media initialized");
|
|
|
|
|
} catch (err) {
|
|
|
|
|
mediaError = err;
|
|
|
|
|
LogMessage("selected media failed: " + err);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
@@ -137,28 +142,69 @@ async function ensureLocalMedia() {
|
|
|
|
|
LogMessage("Local media initialized with audio only fallback");
|
|
|
|
|
} catch (audioErr) {
|
|
|
|
|
LogMessage("audio-only failed: " + audioErr);
|
|
|
|
|
|
|
|
|
|
if (localMediaStatus) localMediaStatus.textContent = "Local media failed";
|
|
|
|
|
if (localVideoStatus) localVideoStatus.textContent = "Local video: unavailable";
|
|
|
|
|
throw mediaError;
|
|
|
|
|
if (localVideo) localVideo.srcObject = null;
|
|
|
|
|
|
|
|
|
|
throw audioErr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const localVideo = document.getElementById("localVideo");
|
|
|
|
|
const hasVideo = localStream.getVideoTracks().length > 0;
|
|
|
|
|
const hasAudio = localStream.getAudioTracks().length > 0;
|
|
|
|
|
|
|
|
|
|
if (localStream.getVideoTracks().length > 0) {
|
|
|
|
|
localVideo.srcObject = localStream;
|
|
|
|
|
if (localVideoStatus) localVideoStatus.textContent = "Local video: active";
|
|
|
|
|
if (localMediaStatus) localMediaStatus.textContent = "Local media: audio + video";
|
|
|
|
|
} else {
|
|
|
|
|
localVideo.srcObject = null;
|
|
|
|
|
if (localVideoStatus) localVideoStatus.textContent = "Local video: unavailable";
|
|
|
|
|
if (localMediaStatus) localMediaStatus.textContent = "Local media: audio only";
|
|
|
|
|
LogMessage("No camera available, continuing without video");
|
|
|
|
|
localVideo.srcObject = hasVideo ? localStream : null;
|
|
|
|
|
|
|
|
|
|
if (localVideoStatus) {
|
|
|
|
|
localVideoStatus.textContent = hasVideo
|
|
|
|
|
? "Local video: active"
|
|
|
|
|
: "Local video: unavailable";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const track of localStream.getTracks()) {
|
|
|
|
|
peerConnection.addTrack(track, localStream);
|
|
|
|
|
LogMessage(`Added local track: ${track.kind}`);
|
|
|
|
|
if (localMediaStatus) {
|
|
|
|
|
localMediaStatus.textContent = `Local media: audio=${hasAudio} video=${hasVideo}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!hasVideo) {
|
|
|
|
|
LogMessage("No camera available, continuing without video");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function applyLocalStreamToPeerConnection() {
|
|
|
|
|
if (!peerConnection || !localStream) return;
|
|
|
|
|
|
|
|
|
|
const senders = peerConnection.getSenders();
|
|
|
|
|
|
|
|
|
|
const audioTrack = localStream.getAudioTracks()[0] || null;
|
|
|
|
|
const videoTrack = localStream.getVideoTracks()[0] || null;
|
|
|
|
|
|
|
|
|
|
const audioSender = senders.find(s => s.track && s.track.kind === "audio");
|
|
|
|
|
const videoSender = senders.find(s => s.track && s.track.kind === "video");
|
|
|
|
|
|
|
|
|
|
if (audioSender) {
|
|
|
|
|
await audioSender.replaceTrack(audioTrack);
|
|
|
|
|
LogMessage("Replaced audio track on peer connection");
|
|
|
|
|
} else if (audioTrack) {
|
|
|
|
|
peerConnection.addTrack(audioTrack, localStream);
|
|
|
|
|
LogMessage("Added audio track to peer connection");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (videoSender) {
|
|
|
|
|
await videoSender.replaceTrack(videoTrack);
|
|
|
|
|
LogMessage("Replaced video track on peer connection");
|
|
|
|
|
} else if (videoTrack) {
|
|
|
|
|
peerConnection.addTrack(videoTrack, localStream);
|
|
|
|
|
LogMessage("Added video track to peer connection");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function refreshDevicesAndPreview() {
|
|
|
|
|
await loadDevices();
|
|
|
|
|
await ensureLocalMedia(true);
|
|
|
|
|
|
|
|
|
|
if (peerConnection) {
|
|
|
|
|
await applyLocalStreamToPeerConnection();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -251,6 +297,7 @@ async function channelCallJoin(activeCall)
|
|
|
|
|
// LogMessage("Active call: " + activeCall);
|
|
|
|
|
await ensurePeerConnection2();
|
|
|
|
|
await ensureLocalMedia();
|
|
|
|
|
await applyLocalStreamToPeerConnection();
|
|
|
|
|
|
|
|
|
|
if (activeCall)
|
|
|
|
|
{
|
|
|
|
|
@@ -432,6 +479,27 @@ async function loadDevices() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wireDeviceSelectors() {
|
|
|
|
|
const cameraSelect = document.getElementById("cameraSelect");
|
|
|
|
|
const micSelect = document.getElementById("micSelect");
|
|
|
|
|
|
|
|
|
|
if (cameraSelect) {
|
|
|
|
|
cameraSelect.onchange = async () => {
|
|
|
|
|
LogMessage("Camera changed");
|
|
|
|
|
await ensureLocalMedia(true);
|
|
|
|
|
await applyLocalStreamToPeerConnection();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (micSelect) {
|
|
|
|
|
micSelect.onchange = async () => {
|
|
|
|
|
LogMessage("Microphone changed");
|
|
|
|
|
await ensureLocalMedia(true);
|
|
|
|
|
await applyLocalStreamToPeerConnection();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function waitForIceGatheringComplete(pc) {
|
|
|
|
|
if (pc.iceGatheringState === "complete") return;
|
|
|
|
|
|
|
|
|
|
@@ -457,4 +525,6 @@ window.addEventListener("load", async () => {
|
|
|
|
|
LogMessage("RTC page loaded");
|
|
|
|
|
window.HybridWebView.SendRawMessage("rtc_page_ready");
|
|
|
|
|
await loadDevices();
|
|
|
|
|
wireDeviceSelectors();
|
|
|
|
|
await ensureLocalMedia(true);
|
|
|
|
|
});
|