zebra-report/test
Russell Ballestrini 467146a14a
zebra-spaces: route audioCtx to user-picked sink (kill fedora-chrome silence)
Fox 2026-06-06 telemetry from blanka-chrome speaker session:

  · role=speaker sub=connected/connected pub=connected/connected
    mesh=2 ... aud.recv pkt=308→556→800 level=0.000
  · jitter-buffer started uuid=2d4a target=0.5s
  · audio via AudioContext 2d4a target=0.5s ctxState=running

RTP arriving at 50pps (active speaker), worklet started, ctxState
running — every audio invariant green except level=0 and "no audio
from any speakers".

Root cause: the speaker-output picker in the UI only called setSinkId
on <audio> elements. The worklet path (every listener AND every
speaker monitor since c3ff58c routed mesh through the worklet too)
runs through audioCtx.destination, which always emits to the SYSTEM
DEFAULT sink. Users who picked a non-default speaker in the dropdown
heard nothing on their chosen device because the worklet bypassed
the sink-routing entirely.

Firefox where blanka started hearing music again works by accident:
its default output happens to be the speaker fox wants. Chrome's
default is something else (PulseAudio's per-app routing, presumably).
The Jun 5 cascade fixes + Jun 6 flushSfuStreams prefix-match fixed
the FSM; this fixes the actual sink the FSM was emitting to.

Fix: applySinkToAudioCtx() — best-effort audioCtx.setSinkId(deviceId)
mirroring the existing applySinkTo(<audio>) helper. Called on
audioCtx creation (primeAudioOnGesture and attachAudioStreamViaWorklet)
and in applySinkToAll fan-out when the user changes the dropdown.

Browser support: Chrome ≥110, Firefox ≥116. Older versions silently
fall through to system default — same as today.

Diagnostic: log line now includes sink=... so the next telemetry
session shows which sink is routed. 'n/a' when the API doesn't
exist; 'default' when sinkId is the empty string; otherwise the
device id.

Tests:
  - audioCtx.setSinkId is wired and callable in the sandbox
  - attach does not throw when setSinkId is missing (older browser
    fallback contract)
2026-06-06 19:27:35 -04:00
..
functional.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
integration.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
jitter-buffer-worklet.test.js zebra-spaces: clean shrink-retarget (drop to target, skip-couple video) 2026-06-06 14:55:03 -04:00
listener-audio-attach.test.js zebra-spaces: route audioCtx to user-picked sink (kill fedora-chrome silence) 2026-06-06 19:27:35 -04:00
mod-action-serializer.test.js test: pin the mod-action serializer race fix 2026-06-04 10:46:55 -04:00
multi-peer-mesh.test.js zebra-spaces: rebuild <video> on MSID supplant — autoplay grant resets 2026-06-03 16:08:01 -04:00
self-listener-fsm.test.js zebra-spaces: formalize self-listener as FSM — pure spec + observer-driven side effects + 12 unit tests 2026-06-04 13:10:29 -04:00
sendbye-fsm.test.js zebra-spaces: send 'bye' on pagehide for listeners even on bfcache (kill closed-firefox roster zombies) 2026-06-06 14:45:39 -04:00
test.h phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
unit.c phase 1: unfirehose reconstruction from session JSONL ingest 2026-05-27 13:51:14 -04:00
video-track-removal.test.js zebra-spaces: 120s mute window for screen + game (static content), 15s stays for camera 2026-06-03 10:33:28 -04:00
web-protocol.test.js test: node web protocol suite (no devices) + exponential retransmit backoff 2026-05-28 12:44:20 -04:00
zebra-fsm.test.js test/zebra-fsm: MuteFSM transition tests — 14 new cases (102 total) 2026-06-04 14:14:52 -04:00
zebra-spaces.test.js zebra-spaces: JS↔Go protocol parity + crypto tests 2026-05-31 11:06:59 -04:00