diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 0eb70ac..9dc788f 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -4785,16 +4785,22 @@ document.addEventListener('visibilitychange', () => { * Tiny WAV header + 1 sample of silence — the smallest valid PCM * audio resource a browser will accept. Base64 ~80 bytes. */ const SILENCE_WAV = 'data:audio/wav;base64,UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAAAAA=='; -let audioPrimed = false; +/* audioPrimed gate REMOVED — every entry click should re-prime even + * after a successful first prime. Mobile browsers can suspend the + * audio session on tab background / leave; the next entry click needs + * a fresh play() to re-bless audio for the new session. */ function primeAudioOnGesture(){ - if (audioPrimed) return; const a = document.createElement('audio'); a.src = SILENCE_WAV; a.muted = false; a.volume = 0; a.style.display = 'none'; document.body.appendChild(a); const p = a.play(); - if (p && p.then) p.then(() => { audioPrimed = true; }) - .catch(()=>{ /* still try the rest of join */ }); + if (p && p.then) p.then(() => { + /* keep the element around briefly so the play() actually emits a + * frame; then yank it from the DOM to avoid a growing pile of + * primer elements after many entry clicks. */ + setTimeout(() => { try { a.remove(); } catch(_){} }, 500); + }).catch(()=>{ try { a.remove(); } catch(_){} }); /* Also try resuming any existing AudioContext (meter / chime). * resume() on an already-running context is a no-op, and on a * suspended one it transitions to running — granting Web Audio @@ -4860,6 +4866,19 @@ $('btn-leave').addEventListener('click', async () => { try { sessionStorage.removeItem(ACTIVE_CALL_KEY); } catch(_){} try { sessionStorage.removeItem(ACTIVE_CAM_KEY); } catch(_){} if (sigReconnect){ clearTimeout(sigReconnect); sigReconnect = null; } + /* tear down ALL DJ-mode HTTP streams + their