Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-04-08 18:56:36 -04:00
2 changed files with 91 additions and 21 deletions

View File

@@ -16,7 +16,7 @@
</div> </div>
<div> <div>
<button onclick="loadDevices()">Refresh Devices</button> <button onclick="refreshDevicesAndPreview()">Refresh Devices</button>
<button onclick="joinChannelCall()">Join Call</button> <button onclick="joinChannelCall()">Join Call</button>
</div> </div>

View File

@@ -96,19 +96,25 @@ async function ensurePeerConnection() {
LogMessage("ICE gathering state: " + peerConnection.iceGatheringState); LogMessage("ICE gathering state: " + peerConnection.iceGatheringState);
}; };
} }
async function ensureLocalMedia() { async function ensureLocalMedia(forceReload = false) {
if (localStream) return;
const localMediaStatus = document.getElementById("localMediaStatus"); const localMediaStatus = document.getElementById("localMediaStatus");
const localVideoStatus = document.getElementById("localVideoStatus"); const localVideoStatus = document.getElementById("localVideoStatus");
const localVideo = document.getElementById("localVideo");
const cameraSelect = document.getElementById("cameraSelect"); const cameraSelect = document.getElementById("cameraSelect");
const micSelect = document.getElementById("micSelect"); 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 selectedCameraId = cameraSelect ? cameraSelect.value : "";
let selectedMicId = micSelect ? micSelect.value : ""; let selectedMicId = micSelect ? micSelect.value : "";
let mediaError = null;
const videoConstraint = selectedCameraId const videoConstraint = selectedCameraId
? { deviceId: { exact: selectedCameraId } } ? { deviceId: { exact: selectedCameraId } }
: false; : false;
@@ -125,7 +131,6 @@ async function ensureLocalMedia() {
LogMessage("Local media initialized"); LogMessage("Local media initialized");
} catch (err) { } catch (err) {
mediaError = err;
LogMessage("selected media failed: " + err); LogMessage("selected media failed: " + err);
try { try {
@@ -137,28 +142,69 @@ async function ensureLocalMedia() {
LogMessage("Local media initialized with audio only fallback"); LogMessage("Local media initialized with audio only fallback");
} catch (audioErr) { } catch (audioErr) {
LogMessage("audio-only failed: " + audioErr); LogMessage("audio-only failed: " + audioErr);
if (localMediaStatus) localMediaStatus.textContent = "Local media failed"; if (localMediaStatus) localMediaStatus.textContent = "Local media failed";
if (localVideoStatus) localVideoStatus.textContent = "Local video: unavailable"; 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 = hasVideo ? localStream : null;
localVideo.srcObject = localStream;
if (localVideoStatus) localVideoStatus.textContent = "Local video: active"; if (localVideoStatus) {
if (localMediaStatus) localMediaStatus.textContent = "Local media: audio + video"; localVideoStatus.textContent = hasVideo
} else { ? "Local video: active"
localVideo.srcObject = null; : "Local video: unavailable";
if (localVideoStatus) localVideoStatus.textContent = "Local video: unavailable";
if (localMediaStatus) localMediaStatus.textContent = "Local media: audio only";
LogMessage("No camera available, continuing without video");
} }
for (const track of localStream.getTracks()) { if (localMediaStatus) {
peerConnection.addTrack(track, localStream); localMediaStatus.textContent = `Local media: audio=${hasAudio} video=${hasVideo}`;
LogMessage(`Added local track: ${track.kind}`); }
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); // LogMessage("Active call: " + activeCall);
await ensurePeerConnection2(); await ensurePeerConnection2();
await ensureLocalMedia(); await ensureLocalMedia();
await applyLocalStreamToPeerConnection();
if (activeCall) 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) { async function waitForIceGatheringComplete(pc) {
if (pc.iceGatheringState === "complete") return; if (pc.iceGatheringState === "complete") return;
@@ -457,4 +525,6 @@ window.addEventListener("load", async () => {
LogMessage("RTC page loaded"); LogMessage("RTC page loaded");
window.HybridWebView.SendRawMessage("rtc_page_ready"); window.HybridWebView.SendRawMessage("rtc_page_ready");
await loadDevices(); await loadDevices();
wireDeviceSelectors();
await ensureLocalMedia(true);
}); });