zebra-report: tap-anywhere-to-resume on mobile autoplay block

When mobile Firefox/Safari blocks audio.play() on auto-enrolled DJ
streams, queue the elements and retry on the first pointerdown/
touchstart anywhere on the page. WebRTC stays unmuted as fallback
so the listener isn't silent in the meantime.
This commit is contained in:
russell@unturf.com 2026-06-03 20:16:20 -04:00
parent e3363d9cf1
commit 393b81fb23
No known key found for this signature in database

View file

@ -4050,6 +4050,33 @@ const streamAudio = new Map(); // uuid -> <audio> 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 <audio>'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');
<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">a0d7d8646e0dcec5b7563cc20ee69ade</span><br>
sha256 <span class="stamp-sha">712548a89b0e0ebfa299b91a360ef0a75128e5f35d17b2ffd7519bf0ff4423fa</span><br>
md5 <span class="stamp-md5">988fb9812616b4cbd5c7f1073f3e4d01</span><br>
sha256 <span class="stamp-sha">7b9cc2c6cdab3bba55beb73b6afa413b784743b986294b3ad4e043f31c553e3f</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>