diff --git a/docs/tickets/0001-fedora-chrome-cannot-hear-speakers.md b/docs/tickets/0001-fedora-chrome-cannot-hear-speakers.md index 6d4a660..843a4f7 100644 --- a/docs/tickets/0001-fedora-chrome-cannot-hear-speakers.md +++ b/docs/tickets/0001-fedora-chrome-cannot-hear-speakers.md @@ -88,13 +88,91 @@ telemetry session shows which sink the worklet is routed to. - `audioCtx without setSinkId support (older browser) does not throw — silent fallback to default` +## 2026-06-07 telemetry — sink hypothesis ruled out + +Fresh `/var/log/zebra-spaces-signal.log` capture (00:20:01 — fxhp host +publishing real music, blanka-chrome speaker): + +``` +audio via AudioContext 085b target=0.5s ctxState=running sink=default +mesh stream swapped into worklet for 085b — buffer cushion now applies to mesh too +... +aud.recv pkt=14622 lost=1 bytes=9117575 jitter=0.0100 level=0.000 jbuf=? lp=0.1s +``` + +Comparison — fxhp-phone in the same room, same window, same publisher: + +``` +aud.recv pkt=11161 lost=1 bytes=7154201 jitter=0.0090 level=0.001 jbuf=0.49s lp=0.0s +``` + +- sink IS routed (deployed fix `467146a` confirmed live — later capture + shows `sink=c38572ec…` = the user-picked deviceId, NOT default). +- 50 pps RTP, 9.1 MB of stereo Opus arriving on blanka-chrome's SFU sub PC. +- `jbuf=?` = `jitterBufferEmittedCount === 0` = receiver **decodes + nothing**. There is no consumer wired to the SFU audio receiver. +- fxhp-phone on the same SFU stream has `jbuf=0.49s` + `level=0.001` + (native decoder running) — so the SFU is fine, the problem is per- + client wiring. + +So: **sink routing was not the bug, or was only part of it.** With the +sink fix applied, fedora chrome still hears silence. + +## Real root cause + +Two coupled defects, both visible in `handleRemoteSfuTrack` (line 4700) +and the mesh `pc.ontrack` (line 6693): + +**(A) `tracks=0` race at mic ontrack.** Chrome can fire `ontrack` where +`ev.streams[0]` exists but has 0 tracks at handler-time (the track is +added a microtask later, or MSID-supplant merges new + dead tracks). +Screen / camera / game already work around this by constructing a fresh +`new MediaStream([ev.track])` and passing that. **Mic does not.** It +caches `ev.streams[0]` directly into `sfuStreamsByPubHex` and hands the +same reference to `attachSfuTrack`. When that fires before the track +shows up on the stream, `attachAudioStreamViaWorklet` rejects with +`liveAudio.length === 0` → falls through to the silent `