zebra-spaces: bump video-track mute-removal window 3s -> 15s

Repros from fox:
- host shares screen, all peers see it, then non-phone speaker loses
  it after a few seconds
- hard refresh: tiles appear then disappear

Root cause: watchVideoTrackForRemoval declared the publisher gone
after 3s of mute. That's way too aggressive — a transient network
blip, NACK retransmission gap, brief CPU pressure on the publisher,
or a mobile network handoff can pause RTP for 3s without the publisher
actually unsharing. 3s mute fired remove(), tile disappeared even
though the SFU was still forwarding.

15s is enough to weather Wi-Fi stalls and mobile network handoffs
while still removing the tile within a reasonable window when the
publisher genuinely unshares.

Added logLine on both the timeout fire ('video track muted >15s —
removing tile') and the rescue ('RTP resumed before timeout — keeping
tile') so we can see what's actually happening on the receiver side
the next time something looks wrong.
This commit is contained in:
Russell Ballestrini 2026-06-03 10:11:23 -04:00
parent 415e6e27c9
commit bb1bf56527
No known key found for this signature in database

View file

@ -1601,9 +1601,19 @@ function flushSfuStreams(){
* subscriber's pc and renegotiates. Browsers don't reliably fire
* 'ended' on remote tracks under this path (Chromium half-fires,
* Firefox stays silent). They DO fire 'mute' when RTP stops arriving.
* Watch both: a sustained mute for >1.5s = the publisher is gone and
* we should remove the tile. If 'unmute' fires within the window
* (transient network blip) we cancel the removal. */
* Watch both: a sustained mute for >MUTE_WINDOW ms = the publisher is
* (probably) gone and we remove the tile. If 'unmute' fires within
* the window — transient network blip, NACK retransmission gap, brief
* CPU pressure on publisher, mobile network handoff — we cancel the
* removal.
*
* Window history:
* - 3s: too aggressive — transient blips killed live tiles, the
* 'host shares screen, other speaker loses it' regression.
* - 15s: enough to weather Wi-Fi stalls and mobile network handoffs
* without leaving frozen tiles around for ages. A genuine unshare
* still removes the tile within 15s. */
const VIDEO_REMOVE_MUTE_WINDOW_MS = 15000;
function watchVideoTrackForRemoval(track, removeFn){
if (!track) return;
let timer = null, removed = false, hasFlowed = false;
@ -1617,7 +1627,10 @@ function watchVideoTrackForRemoval(track, removeFn){
/* RTP arrived; future mute events are meaningful (a flow that
* existed then stopped — publisher unshared or net dropped) */
hasFlowed = true;
if (timer){ clearTimeout(timer); timer = null; }
if (timer){
clearTimeout(timer); timer = null;
logLine('', 'video track: RTP resumed before timeout — keeping tile');
}
};
const onMute = () => {
if (removed || timer) return;
@ -1627,8 +1640,11 @@ function watchVideoTrackForRemoval(track, removeFn){
if (!hasFlowed) return;
timer = setTimeout(() => {
timer = null;
if (!removed && track.muted) remove();
}, 3000);
if (!removed && track.muted){
logLine('', 'video track muted >'+(VIDEO_REMOVE_MUTE_WINDOW_MS/1000)+'s — removing tile');
remove();
}
}, VIDEO_REMOVE_MUTE_WINDOW_MS);
};
track.addEventListener('ended', remove);
track.addEventListener('mute', onMute);
@ -3813,8 +3829,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-03</span><br>
md5 <span class="stamp-md5">39ee0b8db4e04da59859e19bee0ea41a</span><br>
sha256 <span class="stamp-sha">ba28e894d869cb11ccea4cdbbeedf22cf67a25f11d29450c4de27a1ecffa9396</span><br>
md5 <span class="stamp-md5">202400eb71552609f46039301779f8ed</span><br>
sha256 <span class="stamp-sha">684fb8eb60e8a4a30f7a9781f51a3dba088401b874262a84f735f332269f5758</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>