zebra-report: 4s auto-enrol retry loop covers post-join publish race

If the host's mic publish lands after the listener's initial DJ auto-
enrol attempt, the 404 from the SFU was permanent — no event re-
triggered enrol. 4s interval re-checks every speaker and starts streams
that are still missing. Stops on transition out of listener.
This commit is contained in:
russell@unturf.com 2026-06-03 21:02:29 -04:00
parent fb1fabc810
commit e8bb5aadbd
No known key found for this signature in database

View file

@ -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');
<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-04</span><br>
md5 <span class="stamp-md5">86588d00e3993870a95504ac16973b26</span><br>
sha256 <span class="stamp-sha">1e8dc752014189b0ba6e45bad1981950079700e3cf8da33f15560c006f4a314e</span><br>
md5 <span class="stamp-md5">63bf820f7f0bc11d4726b3f1550380f6</span><br>
sha256 <span class="stamp-sha">6a259843e45682d391e7485fd11b670eea32b8622846529b3a319f2a375420a8</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>
</footer>