diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 3a6389a..b8e2aca 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -4050,6 +4050,33 @@ const streamAudio = new Map(); // uuid -> pulling stream function streamUrlFor(pubHex){ return SFU_BASE + '/stream?room=' + encodeURIComponent(roomID) + '&pub=' + pubHex; } +/* Tap-to-resume queue: when mobile Firefox/Safari blocks autoplay + * (auto-enrolment runs too far away from the entry-button gesture + * for the activation window to still be alive), we stash the audio + * element here and retry its play() on the first user touch anywhere + * on the page. One global listener handles every queued stream at + * once. Idempotent install. */ +const pendingAutoplay = new Set(); +let tapResumeInstalled = false; +function installTapResume(){ + if (tapResumeInstalled) return; + tapResumeInstalled = true; + const retry = () => { + if (!pendingAutoplay.size) return; + for (const a of [...pendingAutoplay]){ + const p = a.play(); + if (p && p.then) p.then(() => pendingAutoplay.delete(a)).catch(()=>{}); + else pendingAutoplay.delete(a); + } + if (!pendingAutoplay.size){ + document.removeEventListener('pointerdown', retry); + document.removeEventListener('touchstart', retry); + tapResumeInstalled = false; + } + }; + document.addEventListener('pointerdown', retry, { passive: true }); + document.addEventListener('touchstart', retry, { passive: true }); +} async function startStream(uuid, pubHex){ /* DON'T mute the WebRTC audio yet — if the fresh 's autoplay * is blocked (common on mobile after the entry gesture's grace has @@ -4106,7 +4133,19 @@ async function startStream(uuid, pubHex){ const p = a.play(); if (p && p.then){ p.then(() => logLine('', 'stream on for '+pubHex.slice(0,12)+' — DJ mode (~2s delay, glitch-free)')) - .catch(e => unmuteWebRtcOnFail('autoplay blocked: '+e.message)); + .catch(e => { + /* mobile Firefox / Safari block autoplay when the gesture + * activation window has expired between entry-click and + * auto-enrolment. Don't permanently fall back to WebRTC — + * queue this element for tap-to-resume so the next tap + * anywhere on the page retries play(). */ + pendingAutoplay.add(a); + installTapResume(); + logLine('', 'stream for '+pubHex.slice(0,12)+' — tap anywhere to start ('+e.message+')'); + /* Keep WebRTC audible as the fallback while waiting for a tap. */ + const w = remoteAudio.get(uuid); + if (w) try { w.muted = false; } catch(_){} + }); } else { logLine('', 'stream on for '+pubHex.slice(0,12)+' — DJ mode (~2s delay, glitch-free)'); } @@ -4780,8 +4819,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');