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');