zebra-spaces: drop tiles of demoted-to-listener peers immediately

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.
This commit is contained in:
Russell Ballestrini 2026-06-03 12:38:55 -04:00
parent c76cb397ba
commit 1e94fa6812
No known key found for this signature in database

View file

@ -3191,6 +3191,21 @@ async function handleSignal(raw){
if (canSpeak(myRole) && canSpeak(m.role) && !peers.has(m.uuid)){ if (canSpeak(myRole) && canSpeak(m.role) && !peers.has(m.uuid)){
connectToPeer(m.uuid, myUUID < 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(); renderRoom();
reorderTiles(); reorderTiles();
@ -3974,8 +3989,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"> <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> <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">fc29c8bf692b0c2c0621d23df9f46a75</span><br> md5 <span class="stamp-md5">f116a2320937cb5069015b6b85fdbab7</span><br>
sha256 <span class="stamp-sha">c05107efe2e7971407d4bfc3971016a2bd49cc4dc34cd4fbe9d7e33090853156</span><br> sha256 <span class="stamp-sha">d011be2528f20ab980d0e2d5364b270f69c57b9e09d4a95f88fb0dc1aec40a32</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">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> <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> </footer>