zebra-spaces: bump playoutDelayHint to 400ms — Wi-Fi peak jitter eats 200ms buffer

Listener reported clean network (43ms RTT, 0% loss) but 53ms smoothed
jitter, which on Wi-Fi typically peaks at 150-250ms inter-arrival. A
200ms playout buffer overflows on those peaks; 400ms absorbs them.

Tradeoff: extra ~quarter-second of latency. For music broadcast that
is invisible; for conversation it remains well below noticeable
turn-taking thresholds.

Applied to both SFU subscribe (mic audio path) and mesh peer audio.
This commit is contained in:
Russell Ballestrini 2026-06-01 21:40:14 -04:00
parent 383ed9af1e
commit 872d3a4df1
No known key found for this signature in database

View file

@ -973,10 +973,12 @@ async function sfuSubscribe(){
logLine('', 'sfu: unknown kind '+kind+' from '+shortHex(pubHex));
return;
}
/* mic audio — set a small jitter-buffer hint so brief network jitter
* doesn't cause audible drops. 100ms is enough to absorb typical
* Wi-Fi micro-bursts without making conversation feel laggy. */
try { ev.receiver.playoutDelayHint = 0.2; } catch(_){}
/* mic audio — 400ms jitter-buffer target. Mobile/Wi-Fi peak inter-
* arrival is 3-5x the smoothed jitter reported by getStats, so a
* smoothed 50ms means 150-250ms peaks. 400ms absorbs that and feels
* fine for music broadcasts; conversation gains ~quarter-second of
* delay which is well below noticeable. */
try { ev.receiver.playoutDelayHint = 0.4; } catch(_){}
const pubHex = sid;
/* cache stream by publisher pubkey so it survives the member's session
* uuid changing across leave/rejoin — see flushSfuStreams */
@ -1572,9 +1574,9 @@ async function connectToPeer(uuid, weOffer){
let a = remoteAudio.get(uuid);
if (!a){ a = document.createElement('audio'); a.autoplay = true; document.body.appendChild(a); remoteAudio.set(uuid, a); }
a.srcObject = ev.streams[0] || new MediaStream([ev.track]);
/* 100ms jitter buffer absorbs typical Wi-Fi micro-bursts without
* adding perceptible conversation lag */
try { ev.receiver.playoutDelayHint = 0.2; } catch(_){}
/* 400ms jitter buffer — Wi-Fi peak inter-arrival is multiples of
* the smoothed jitter, so a generous buffer absorbs the bursts */
try { ev.receiver.playoutDelayHint = 0.4; } catch(_){}
stopMeter(uuid); startMeter(uuid, a.srcObject);
};
pc.onicecandidate = (ev) => { /* using waitForIceGathering pattern, candidates ignored */ };
@ -2071,8 +2073,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">
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">b3dc62bb089cc56ac842287fa3ce2c7b</span><br>
sha256 <span class="stamp-sha">e164446240db3632db870b3f35efaf8dd5b8cbdb10b56177746bec9ba4c9fd36</span><br>
md5 <span class="stamp-md5">b968332fc24066fa99f88860638ec179</span><br>
sha256 <span class="stamp-sha">ba484cf6851555630d7fd6c5525d6fda834793f5e146582b8f48dff91300dac6</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>
</footer>