From 837d62c3354412034cfd16fb3d41e056285035af Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Fri, 22 May 2026 13:14:21 +0000 Subject: [PATCH] fix: add missing _ensureAudioUnlocked() definition; use shared AudioContext in _playCookingTimerSound --- assets/js/app.js | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index ce7ad30..004757a 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -13657,11 +13657,31 @@ let _cookingSuggestedSeconds = 0; let _cookingSuggestedLabel = ''; let _sharedAudioCtx = null; // pre-unlocked AudioContext (created on user gesture) +/** + * Pre-unlock the shared AudioContext during a user gesture. + * Call this from any click/touch handler so that the context is already + * in 'running' state when the timer fires (potentially outside a gesture). + */ +function _ensureAudioUnlocked() { + try { + const Ctx = window.AudioContext || window.webkitAudioContext; + if (!Ctx) return; + if (!_sharedAudioCtx || _sharedAudioCtx.state === 'closed') { + _sharedAudioCtx = new Ctx(); + } + if (_sharedAudioCtx.state === 'suspended') { + _sharedAudioCtx.resume().catch(() => {}); + } + } catch (_) { /* ignore */ } +} + function _playCookingTimerSound(type = 'done') { try { const Ctx = window.AudioContext || window.webkitAudioContext; if (!Ctx) return; - const ctx = new Ctx(); + // Use the pre-unlocked shared context; fall back to a new one if closed + let ctx = (_sharedAudioCtx && _sharedAudioCtx.state !== 'closed') ? _sharedAudioCtx : null; + if (!ctx) { ctx = new Ctx(); } const pattern = type === 'warning' ? [{ f: 880, d: 0.08, o: 0.00 }, { f: 1046, d: 0.10, o: 0.14 }] : [ @@ -13685,12 +13705,8 @@ function _playCookingTimerSound(type = 'done') { osc.start(now + p.o); osc.stop(now + p.o + p.d + 0.02); } - const endAt = now + Math.max(...pattern.map(p => p.o + p.d)) + 0.08; - setTimeout(() => { try { ctx.close(); } catch (_) { /* ignore */ } }, Math.max(120, Math.round((endAt - now) * 1000))); }; - // AudioContext starts suspended on mobile/Android after autoplay policy — - // must call resume() before scheduling nodes, even outside a user gesture. if (ctx.state === 'suspended') { ctx.resume().then(doPlay).catch(() => {}); } else {