From fb9cfbbd55febaddbb7f6faf21d8b3e038b31db0 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Wed, 3 Jun 2026 13:21:16 -0400 Subject: [PATCH] zebra-spaces: chimes ring in background tabs too (doorman use case) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Was: tones skipped when document.hidden. That's wrong for the doorman cohost use case fox flagged — the whole point is to know a guest just arrived without watching the tab. Remove the document.hidden gate. Also defensively resume() the AudioContext if it slipped to 'suspended' (some mobile browsers suspend on tab-hide) — keeps the timeline alive so the scheduled tone actually fires. --- web/zebra-spaces.html | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/web/zebra-spaces.html b/web/zebra-spaces.html index 6a64d39..c33b982 100644 --- a/web/zebra-spaces.html +++ b/web/zebra-spaces.html @@ -2902,9 +2902,11 @@ function stopMeter(uuid){ meterCtl.delete(uuid); } * autoplay policy is satisfied. Each tone uses a short exponential * fade-out to avoid click artifacts. * - * Tones are skipped if the page is hidden (background tab) — the - * idea is to notify the LIVE user, not interrupt them when they - * stepped away. + * Tones play even when the tab is in the background — the doorman / + * mod use case wants to know a guest just arrived without having to + * watch the tab. Browsers throttle some background work but Web + * Audio scheduling is preserved as long as the AudioContext was + * created after a user gesture (the 'enter' click counts). * ================================================================== */ let _toneCtx = null; function _toneCtxGet(){ @@ -2913,8 +2915,12 @@ function _toneCtxGet(){ return _toneCtx; } function playToneBlip(freq, durationMs, type){ - if (typeof document !== 'undefined' && document.hidden) return; const ctx = _toneCtxGet(); if (!ctx) return; + /* if the user gesture was throttled (some mobile browsers suspend + * the context on tab hide), nudge it back to running before + * scheduling — resume() on a suspended context restarts the + * timeline so the tone actually fires. */ + if (ctx.state === 'suspended'){ try { ctx.resume(); } catch(_){} } try { const t0 = ctx.currentTime; const osc = ctx.createOscillator(); @@ -4091,8 +4097,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');