Compare commits

..

2 Commits

Author SHA1 Message Date
8fb9126072 Merge remote-tracking branch 'origin/main' 2026-04-08 18:53:18 -04:00
e18e61710e Added Hotswapping to Settings. 2026-04-08 18:53:07 -04:00
2 changed files with 91 additions and 21 deletions

View File

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

View File

@@ -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);
});