zebra-report: kill audio chops (FEC+DTX+jitter buffer) + clear tiles on unshare

Mirror source 353037b. Needs SFU proxy.unturf.com#60a620f deployed for
the NACK + transport-cc feedback that browsers use to retransmit lost
audio packets and react to congestion.
This commit is contained in:
russell@unturf.com 2026-06-01 21:05:37 -04:00
parent 851d2e688f
commit 4a336a2702
No known key found for this signature in database

View file

@ -928,19 +928,29 @@ async function sfuSubscribe(){
const kind = sid.slice(colon + 1);
/* skip echo of our own publish — we already render a local preview */
if (myKeys && pubHex === myKeys.pubHex) return;
/* video kinds: when the publisher unshares, the SFU stops the
* transceiver and the corresponding remote track fires 'ended'.
* Wire that to the tile remover so the listener's UI matches the
* publisher's state instead of holding a frozen last frame. */
if (kind === 'screen'){
screenStreams.set(pubHex, ev.streams[0]);
renderScreenTile(pubHex, ev.streams[0]);
ev.track.addEventListener('ended', () => removeScreenTile(pubHex));
return;
}
if (kind === 'camera'){
cameraStreams.set(pubHex, ev.streams[0]);
renderCameraTile(pubHex, ev.streams[0]);
ev.track.addEventListener('ended', () => removeCameraTile(pubHex));
return;
}
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.1; } catch(_){}
const pubHex = sid;
/* cache stream by publisher pubkey so it survives the member's session
* uuid changing across leave/rejoin — see flushSfuStreams */
@ -1113,7 +1123,15 @@ async function setSenderMaxBitrate(sender, bps){
* has to be raised explicitly for music to actually use the headroom. */
function preferStereoOpus(sdp, maxAvgBps){
return sdp.replace(/a=fmtp:(\d+) ([^\r\n]*minptime=10[^\r\n]*)/g, (m, pt, fmtp) => {
const want = { 'stereo': '1', 'sprop-stereo': '1', 'maxaveragebitrate': String(maxAvgBps) };
/* useinbandfec=1: forward error correction so a single dropped
* packet doesn't audibly chop — Opus reconstructs from FEC.
* usedtx=1: discontinuous transmission elides silence so the
* bandwidth budget goes to actual audio (cuts congestion). */
const want = {
'stereo': '1', 'sprop-stereo': '1',
'maxaveragebitrate': String(maxAvgBps),
'useinbandfec': '1', 'usedtx': '1',
};
const parts = fmtp.split(';').map(s => s.trim()).filter(Boolean);
const seen = new Set();
for (let i = 0; i < parts.length; i++){
@ -1526,6 +1544,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.1; } catch(_){}
stopMeter(uuid); startMeter(uuid, a.srcObject);
};
pc.onicecandidate = (ev) => { /* using waitForIceGathering pattern, candidates ignored */ };
@ -1878,8 +1899,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">d91602a3c30333c3c88c8fb9d1ce83d6</span><br>
sha256 <span class="stamp-sha">d57f2da4b2958764af64527fdbc7f41a8420c216975f1ec9d55f259d5832c6ed</span><br>
md5 <span class="stamp-md5">39992d2506da348cfff78c04a999e409</span><br>
sha256 <span class="stamp-sha">0208f19ac63acfd243a6e745aa045fd1d56a206bbff9ca3f058e6c2466aed525</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>