From 1e94fa68129cffc7ae9f34a497d781aa0f8265d7 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Wed, 3 Jun 2026 12:38:55 -0400 Subject: [PATCH] zebra-spaces: drop tiles of demoted-to-listener peers immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was: when a speaker was bounced to listener their tiles froze on every other peer until either the SFU's removePublisher path finally fired 'ended' on the subscriber's tracks or the 60s/120s mute window reaped them. Pion's OnConnectionStateChange can lag and SSE renegotiation can race, leaving visibly-frozen tiles for tens of seconds. Authoritative removal: when role-change strips speak rights from peer X (canSpeak(prev) && !canSpeak(next)), drop every kind of X's video tile on the receiver side immediately. The demoted user can't publish anymore by definition, so the tiles are guaranteed-stale — no need to wait for the data-plane path to catch up. Mirrors the existing peer-left handler which removes by pubkey. --- web/zebra-spaces.html | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 83bd378..4da0f36 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -3191,6 +3191,21 @@ async function handleSignal(raw){ if (canSpeak(myRole) && canSpeak(m.role) && !peers.has(m.uuid)){ connectToPeer(m.uuid, myUUID < m.uuid); } + /* If the role transition stripped them of the ability to + * publish (anything -> listener), their camera/screen/game + * tiles can't get fresh RTP anymore. The demoted page calls + * sfuUnpublish* on its own side which should propagate via + * SFU 'ended', but that path is unreliable (Pion may not + * fire OnConnectionStateChange immediately and SSE renego + * can race). Authoritative removal: drop tiles by pubkey + * here, the moment the role change broadcast lands. */ + if (canSpeak(prev) && !canSpeak(m.role) && subjPub){ + try { + removeScreenTile(subjPub); + removeCameraTile(subjPub); + removeVideoTile('gameshare', subjPub); + } catch(_){} + } } renderRoom(); reorderTiles(); @@ -3974,8 +3989,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');