zebra-report: graceful mic re-acquire on BT/USB device swap — mirror
This commit is contained in:
parent
9a3bb7f64b
commit
d34eb61205
1 changed files with 55 additions and 3 deletions
|
|
@ -1622,8 +1622,47 @@ async function getMic(){
|
|||
micStream = await navigator.mediaDevices.getUserMedia({ audio: micConstraints(), video:false });
|
||||
const t = micStream.getAudioTracks()[0];
|
||||
tagTrack(t); await enforceMicConstraints(t);
|
||||
watchMicTrack(t);
|
||||
return micStream;
|
||||
}
|
||||
/* If the underlying mic device disappears (BT disconnect, USB unplug, OS
|
||||
* audio swap), the track fires 'ended' or stops emitting samples but the
|
||||
* MediaStream object stays alive — so existing RTP senders keep pointing
|
||||
* at a dead track and the host goes silent until they leave + rejoin.
|
||||
* Watch the track and re-acquire on the fly. */
|
||||
let micReacquireInFlight = false;
|
||||
function watchMicTrack(track){
|
||||
if (!track) return;
|
||||
track.addEventListener('ended', () => {
|
||||
logLine('err', 'mic track ended — re-acquiring');
|
||||
reacquireMic().catch(e => logLine('err','mic re-acquire failed: '+e.message));
|
||||
});
|
||||
}
|
||||
async function reacquireMic(){
|
||||
if (micReacquireInFlight) return;
|
||||
micReacquireInFlight = true;
|
||||
try {
|
||||
const ns = await navigator.mediaDevices.getUserMedia({ audio: micConstraints(), video:false });
|
||||
const nt = ns.getAudioTracks()[0];
|
||||
tagTrack(nt); nt.enabled = !muted;
|
||||
await enforceMicConstraints(nt);
|
||||
watchMicTrack(nt);
|
||||
/* hot-swap onto every existing sender — no renegotiation needed
|
||||
* since the codec / SDP didn't change, just the track behind it */
|
||||
async function swap(pc){
|
||||
const sender = pc.getSenders().find(s=>s.track && s.track.kind==='audio') || pc.getSenders()[0];
|
||||
if (sender){ try { await sender.replaceTrack(nt); } catch(_){} }
|
||||
}
|
||||
for (const [, pc] of peers) await swap(pc);
|
||||
if (sfuPubPC) await swap(sfuPubPC);
|
||||
if (micStream) micStream.getTracks().forEach(t=>t.stop());
|
||||
micStream = ns;
|
||||
if (myUUID){ stopMeter(myUUID); startMeter(myUUID, micStream); }
|
||||
logLine('', 'mic re-acquired');
|
||||
} finally {
|
||||
micReacquireInFlight = false;
|
||||
}
|
||||
}
|
||||
async function setSenderBitrate(sender){
|
||||
if (!sender) return;
|
||||
try {
|
||||
|
|
@ -1681,6 +1720,7 @@ async function applyMicMode(){
|
|||
const nt = ns.getAudioTracks()[0];
|
||||
tagTrack(nt); nt.enabled = !muted;
|
||||
await enforceMicConstraints(nt);
|
||||
watchMicTrack(nt);
|
||||
async function swap(pc){
|
||||
const sender = pc.getSenders().find(s=>s.track && s.track.kind==='audio') || pc.getSenders()[0];
|
||||
if (sender){ try { await sender.replaceTrack(nt); } catch(_){} setSenderBitrate(sender); }
|
||||
|
|
@ -2552,7 +2592,19 @@ $('music-mode').addEventListener('change', async (e) => {
|
|||
if (micStream){ try { await applyMicMode(); } catch(err){ logLine('err','mic mode switch failed: '+err.message); } }
|
||||
});
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.addEventListener){
|
||||
navigator.mediaDevices.addEventListener('devicechange', refreshMicList);
|
||||
navigator.mediaDevices.addEventListener('devicechange', () => {
|
||||
refreshMicList();
|
||||
/* belt-and-suspenders for browsers that don't fire track.onended on
|
||||
* device disappearance (Firefox/BT swap can leave the track in
|
||||
* 'live' state but emitting silence). If our current track has
|
||||
* gone dead since the last devicechange, re-acquire. */
|
||||
if (micStream){
|
||||
const t = micStream.getAudioTracks()[0];
|
||||
if (t && (t.readyState === 'ended' || t.muted)){
|
||||
reacquireMic().catch(e => logLine('err','mic re-acquire on devicechange: '+e.message));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/* initial population so the dropdown lists the user's inputs before they
|
||||
* enter a space (matches what the camera-select does). Labels are blank
|
||||
|
|
@ -2656,8 +2708,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
|
|||
|
||||
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
|
||||
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">ad6a80d1c54036ff0d7782df2b98c4db</span><br>
|
||||
sha256 <span class="stamp-sha">77b28cfbf2690bdf0b12697c141ab460d0c95301344ce0245936a1c498aa35a9</span><br>
|
||||
md5 <span class="stamp-md5">181a0e0d4e4da7c4a40e2e5e576954fd</span><br>
|
||||
sha256 <span class="stamp-sha">29024b972f5dede7d5fd2b924170aaf9aa097afc9bac4402e86b01f9d543a4ed</span><br>
|
||||
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
|
||||
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
|
||||
</footer>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue