diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html index f5b802f..2b158d5 100644 --- a/zebra-report/zebra-spaces.html +++ b/zebra-report/zebra-spaces.html @@ -3272,7 +3272,14 @@ function attachAudioStreamViaWorklet(uuid, stream, targetSeconds){ return false; } 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; } } if (audioCtx.state === 'suspended'){ @@ -3357,7 +3364,8 @@ function attachAudioStreamViaWorklet(uuid, stream, targetSeconds){ if (transcribeEnabled){ 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; } @@ -5109,10 +5117,36 @@ async function applySinkTo(el){ try { await el.setSinkId(speakerDeviceId || ''); } 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