From 6302e9978af0c95fbe897665e8940ffb10e95ef0 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Thu, 4 Jun 2026 15:26:07 -0400 Subject: [PATCH] zebra-spaces: AudioWorklet manual jitter buffer for listener role (4s) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Browser-native jitterBufferTarget didn't help on the music stream — Firefox Android holds it at 0.06–0.21s on a high-bitrate stereo Opus receiver while honoring 4s on voice and video receivers on the same PC. Per-codec implementation gap in the receiver-side jitter buffer. This adds a userland buffer in Web Audio. The listener path already ran through AudioContext (source → gain → destination); now an AudioWorkletNode sits between source and gain, queues incoming 128-sample blocks until targetSamples (4 × sampleRate) have arrived, then emits with a constant delay. Bounded at maxSamples (6 × sampleRate) so clock drift can't grow the queue unbounded. If the queue fully drains, the buffer re-arms — a hiccup doesn't lock us silent. Worklet code lives inline as a Blob URL (single-file app: no separate JS file shipped). loadJitterWorklet is fire-and-forget on first attach; existing direct-connected streams get swapped through the buffer the moment the worklet module finishes loading. Fallback on worklet creation failure: existing source → gain path stays live. Speaker / cohost / host paths untouched — they need conversational latency, can't sit on a 4s cushion. Listener role test plan: rejoin, watch the new "jitter-buffer installed" log line, observe that the listener is now 4s behind the host's speech. Wiggle the host (X11) — listeners should hear no disruption while the buffer is full. --- web/zebra-spaces.html | 130 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 6 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index c6bd0fd..c61aaf8 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -1749,7 +1749,116 @@ const sfuStreamsByPubHex = new Map(); // pubHex -> MediaStream * setSinkId requirements that still want