zebra-report: deploy audioCtx sinkId routing (fixes fedora chrome silence)
Pulls in zebra-report 467146a: every audioCtx now routes to the user-picked output sink via applySinkToAudioCtx(). Fixes the fedora chrome silent-speaker telemetry where RTP arrived, worklet started, ctxState=running, but no audio reached the user — because audioCtx.destination was always emitting to system default while the speaker dropdown set <audio> sinks only. fox 2026-06-06. Audio path now respects the picker on Chrome ≥110 and Firefox ≥116. Older browsers continue to fall through to default.
This commit is contained in:
parent
e45db971aa
commit
0f36e02e42
1 changed files with 50 additions and 5 deletions
|
|
@ -3272,7 +3272,14 @@ function attachAudioStreamViaWorklet(uuid, stream, targetSeconds){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!audioCtx){
|
if (!audioCtx){
|
||||||
try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); }
|
try {
|
||||||
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
/* Route to picked output sink on first creation — the prime-on-
|
||||||
|
* gesture path already does this, but attach can also be the
|
||||||
|
* first user of audioCtx on auto-rejoin / desktop-resume paths
|
||||||
|
* where no gesture fired. fox 2026-06-06 blanka-chrome silence. */
|
||||||
|
applySinkToAudioCtx();
|
||||||
|
}
|
||||||
catch(e){ logLine('err','audioCtx create: '+e.message); return false; }
|
catch(e){ logLine('err','audioCtx create: '+e.message); return false; }
|
||||||
}
|
}
|
||||||
if (audioCtx.state === 'suspended'){
|
if (audioCtx.state === 'suspended'){
|
||||||
|
|
@ -3357,7 +3364,8 @@ function attachAudioStreamViaWorklet(uuid, stream, targetSeconds){
|
||||||
if (transcribeEnabled){
|
if (transcribeEnabled){
|
||||||
startCaptureForUuid(uuid).catch(e => logLine('err', 'transcribe '+uuid.slice(0,4)+': '+e.message));
|
startCaptureForUuid(uuid).catch(e => logLine('err', 'transcribe '+uuid.slice(0,4)+': '+e.message));
|
||||||
}
|
}
|
||||||
logLine('', 'audio via AudioContext '+uuid.slice(0,4)+' target='+targetSeconds+'s ctxState='+audioCtx.state);
|
logLine('', 'audio via AudioContext '+uuid.slice(0,4)+' target='+targetSeconds+'s ctxState='+audioCtx.state
|
||||||
|
+ ' sink=' + (audioCtx.sinkId === undefined ? 'n/a' : (audioCtx.sinkId || 'default')));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5109,10 +5117,36 @@ async function applySinkTo(el){
|
||||||
try { await el.setSinkId(speakerDeviceId || ''); }
|
try { await el.setSinkId(speakerDeviceId || ''); }
|
||||||
catch(e){ logLine('err','setSinkId rejected: '+e.message); }
|
catch(e){ logLine('err','setSinkId rejected: '+e.message); }
|
||||||
}
|
}
|
||||||
/* Re-route every live remote-audio element to the newly picked sink. */
|
/* AudioContext-level sink routing (Chrome ≥110, Firefox ≥116). Every
|
||||||
|
* listener / speaker audio path now runs through the worklet feeding
|
||||||
|
* audioCtx.destination — if the user picked a non-default speaker in
|
||||||
|
* the UI but we only apply it to <audio> elements, the worklet still
|
||||||
|
* routes to the system default sink and the user hears nothing on
|
||||||
|
* their chosen device.
|
||||||
|
*
|
||||||
|
* Fox 2026-06-06 (telemetry from blanka-chrome speaker session): RTP
|
||||||
|
* arriving at 50pps, "jitter-buffer started", ctxState=running, yet
|
||||||
|
* "fedora chrome no audio from any speakers". The audio was being
|
||||||
|
* decoded and emitted to audioCtx.destination — the wrong sink.
|
||||||
|
*
|
||||||
|
* Best-effort: setSinkId on AudioContext is unsupported on some
|
||||||
|
* browsers / older versions; swallow errors so the worklet still
|
||||||
|
* delivers to default in that case. */
|
||||||
|
async function applySinkToAudioCtx(){
|
||||||
|
if (!audioCtx || typeof audioCtx.setSinkId !== 'function') return;
|
||||||
|
try {
|
||||||
|
await audioCtx.setSinkId(speakerDeviceId || '');
|
||||||
|
logLine('', 'audioCtx sink → '+(speakerDeviceId || 'default'));
|
||||||
|
} catch(e){
|
||||||
|
logLine('err', 'audioCtx setSinkId rejected: '+e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Re-route every live remote-audio element AND the AudioContext to
|
||||||
|
* the newly picked sink. */
|
||||||
async function applySinkToAll(){
|
async function applySinkToAll(){
|
||||||
if (!spkSupported) return;
|
if (!spkSupported) return;
|
||||||
for (const [, a] of remoteAudio){ await applySinkTo(a); }
|
for (const [, a] of remoteAudio){ await applySinkTo(a); }
|
||||||
|
await applySinkToAudioCtx();
|
||||||
}
|
}
|
||||||
function tagTrack(t){ if (t) t.contentHint = musicMode ? 'music' : 'speech'; }
|
function tagTrack(t){ if (t) t.contentHint = musicMode ? 'music' : 'speech'; }
|
||||||
/* Firefox sometimes ignores the EC/NS/AGC constraints at getUserMedia time
|
/* Firefox sometimes ignores the EC/NS/AGC constraints at getUserMedia time
|
||||||
|
|
@ -7753,6 +7787,17 @@ function primeAudioOnGesture(){
|
||||||
osc.stop(audioCtx.currentTime + 0.05);
|
osc.stop(audioCtx.currentTime + 0.05);
|
||||||
osc.onended = () => { try { osc.disconnect(); g.disconnect(); } catch(_){} };
|
osc.onended = () => { try { osc.disconnect(); g.disconnect(); } catch(_){} };
|
||||||
logLine('', 'audioCtx primed state='+audioCtx.state);
|
logLine('', 'audioCtx primed state='+audioCtx.state);
|
||||||
|
/* Route audioCtx.destination to the user-picked output sink. The
|
||||||
|
* <audio>-element setSinkId path already runs in the fan-out
|
||||||
|
* below, but the worklet/listener path runs through this
|
||||||
|
* AudioContext — without this call every speaker-monitor + every
|
||||||
|
* listener heard audio on the SYSTEM DEFAULT sink even though
|
||||||
|
* they'd picked a specific device in the dropdown. fox 2026-06-06:
|
||||||
|
* blanka-chrome speaker telemetry showed RTP arriving, worklet
|
||||||
|
* started, ctxState=running, but "no audio from any speakers"
|
||||||
|
* — audio was being routed to default instead of her selected
|
||||||
|
* monitor. */
|
||||||
|
applySinkToAudioCtx();
|
||||||
} catch(e){ logLine('err', 'audioCtx prime: '+e.message); }
|
} catch(e){ logLine('err', 'audioCtx prime: '+e.message); }
|
||||||
/* pre-bless a pool of audio elements for unmuted autoplay. Each one
|
/* pre-bless a pool of audio elements for unmuted autoplay. Each one
|
||||||
* gets play() called inside this click handler — the gesture grants
|
* gets play() called inside this click handler — the gesture grants
|
||||||
|
|
@ -7964,8 +8009,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">
|
<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-06</span><br>
|
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> · built <span class="stamp-date">2026-06-06</span><br>
|
||||||
md5 <span class="stamp-md5">bd769bd46730955722b172d51f93e673</span><br>
|
md5 <span class="stamp-md5">43f585be4e478d110d4250eff68b9d68</span><br>
|
||||||
sha256 <span class="stamp-sha">a8f6964a1f1e0c87131b1df57a635659fdbbb215a29c7b0032997eb8f6bc5cfc</span><br>
|
sha256 <span class="stamp-sha">6677c5b69c23976a7c9d35104fdc324fe69fa80d24120e1f32aaab23ebdd0606</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">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>
|
<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>
|
</footer>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue