diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html
index 688f280..110a54d 100644
--- a/web/zebra-spaces.html
+++ b/web/zebra-spaces.html
@@ -1459,30 +1459,33 @@ function flushSfuStreams(){
* (transient network blip) we cancel the removal. */
function watchVideoTrackForRemoval(track, removeFn){
if (!track) return;
- let timer = null, removed = false;
+ let timer = null, removed = false, hasFlowed = false;
const remove = () => {
if (removed) return;
removed = true;
if (timer) { clearTimeout(timer); timer = null; }
try { removeFn(); } catch(_){}
};
+ const onUnmute = () => {
+ /* 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; }
+ };
const onMute = () => {
if (removed || timer) return;
+ /* fresh remote tracks ALWAYS start muted until the first RTP packet
+ * arrives. If we've never seen data flow on this track, the mute is
+ * its initial state, not a publisher unshare — don't prune. */
+ if (!hasFlowed) return;
timer = setTimeout(() => {
timer = null;
if (!removed && track.muted) remove();
- }, 1500);
- };
- const onUnmute = () => {
- if (timer){ clearTimeout(timer); timer = null; }
+ }, 3000);
};
track.addEventListener('ended', remove);
track.addEventListener('mute', onMute);
track.addEventListener('unmute', onUnmute);
- /* defensive: if the track is already muted at attach time (the
- * 'mute' event may have fired before we got here), schedule the
- * pruning right away */
- if (track.muted) onMute();
}
/* Spotlight model — every tile keeps its thumbnail permanently in the
@@ -3360,8 +3363,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');