diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html index 6bd0736..b28d625 100644 --- a/zebra-report/zebra-spaces.html +++ b/zebra-report/zebra-spaces.html @@ -2911,15 +2911,23 @@ async function sfuSubscribe(){ * a hard refresh handles the WS side; this handles the SFU side. */ pc.onconnectionstatechange = () => { /* 'failed' is terminal ICE failure (we should rebuild). - * 'closed' is OUR OWN sfuUnsubscribe() — never rebuild on that - * (Chrome fires the state change synchronously before sfuSubPC is - * null'd, which used to trigger a subscribe→close→subscribe loop - * every time anyone deliberately tore down the sub). + * 'closed' coming from our OWN sfuUnsubscribe() must NOT rebuild + * (subscribe→close→subscribe loop). sfuUnsubscribe nulls + * sfuSubPC BEFORE pc.close(), so the `sfuSubPC === pc` guard + * catches self-teardown — the guard fails and we early-return. + * 'closed' coming from the SFU (server-side close — e.g. our + * own wedge-recovery in renegotiateLocked) DOES need a rebuild + * because sfuSubPC === pc is still true (we didn't tear down). + * Without this branch the host's sub stays at sub=none after + * any server-driven close, every subsequent track add goes + * nowhere. Fox 2026-06-04: "lost cohost camera / screen + * share" was downstream of this missing rebuild. * 'disconnected' is transient — let WebRTC try to recover before - * we yank the rug. */ - if (pc.connectionState !== 'failed') return; + * we yank the rug. */ + const s = pc.connectionState; + if (s !== 'failed' && s !== 'closed') return; if (sfuSubPC !== pc) return; - logLine('err', 'sfu sub PC failed — rebuilding'); + logLine('err', 'sfu sub PC '+s+' (remote-driven) — rebuilding'); sfuUnsubscribe().then(() => { if (wantConnected && roomID) sfuSubscribe().catch(e => logLine('err','sfu re-subscribe: '+e.message)); }); @@ -5568,8 +5576,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');