diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html index 441b6a1..937069e 100644 --- a/zebra-report/zebra-spaces.html +++ b/zebra-report/zebra-spaces.html @@ -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');