diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html index f692ee6..becfdce 100644 --- a/zebra-report/zebra-spaces.html +++ b/zebra-report/zebra-spaces.html @@ -3763,8 +3763,12 @@ async function onRoleEntered(){ /* Listener landing — auto-enrol into DJ mode for every speaker so * playback rides the deep-buffered HTTP Ogg path. Slight delay so * the SFU has time to wire its Ogg writers for the existing - * publishers (lazy-inits on first /stream listener). */ + * publishers (lazy-inits on first /stream listener). Plus start + * the periodic retry loop so streams come up when the host's mic + * publish lands AFTER we already auto-enrolled (race the 600ms + * delay can't cover). */ setTimeout(autoEnableDjModeForListener, 600); + startAutoEnrolRetryLoop(); } } let inRoleTransition = false; @@ -3785,7 +3789,9 @@ async function onRoleChanged(prev, next){ muted = true; try { sessionStorage.setItem(MUTE_STATE_KEY, '1'); } catch(_){} /* coming out of listener — tear down every HTTP DJ-mode tap so - * the WebRTC audio path takes over (low-latency for conversation). */ + * the WebRTC audio path takes over (low-latency for conversation). + * Also stop the auto-enrol retry loop — speakers don't need it. */ + stopAutoEnrolRetryLoop(); autoDisableDjModeForAll(); await ensureMicAndUI(); for (const [uuid, mm] of members){ @@ -4195,6 +4201,26 @@ function autoEnableDjModeForListener(){ startStream(uuid, pubHex); } } +/* Periodic retry — covers the timing race where the host's mic publish + * hasn't been registered at the SFU yet when the listener first + * auto-enrols. onFail removes the pubHex from streamMode so the next + * sweep will re-attempt; without this poll the only retry trigger was + * peer-joined / role-change events, which never fire when a speaker + * who was already in the room simply starts publishing later. Cheap: + * idempotent on already-streaming pubHexes, no-op if not a listener. */ +let autoEnrolRetryTimer = null; +function startAutoEnrolRetryLoop(){ + if (autoEnrolRetryTimer) return; + autoEnrolRetryTimer = setInterval(() => { + if (myRole !== 'listener'){ stopAutoEnrolRetryLoop(); return; } + autoEnableDjModeForListener(); + }, 4000); +} +function stopAutoEnrolRetryLoop(){ + if (!autoEnrolRetryTimer) return; + clearInterval(autoEnrolRetryTimer); + autoEnrolRetryTimer = null; +} /* Tear down all DJ-mode HTTP streams — used when we transition out * of listener into speaker/cohost/host. Speakers need the low- @@ -4861,8 +4887,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');