From 54fda1914c1d465a9db81edc72deafd856b67af6 Mon Sep 17 00:00:00 2001 From: yuanzonghao Date: Tue, 2 Jun 2026 01:29:33 +0800 Subject: [PATCH] fix(web): gate play-page un-mute prefetch on actual mute transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On mount the mute effect fired alongside the scene effect (both call prefetchSceneAudio), so the initial /api/beat-audio batch was dispatched twice — the first set aborted mid-flight. Track the previous muted value in a ref and only re-prefetch on a real transition, leaving the mount-time synthesis to the scene effect. Addresses Copilot review on PR #9. Co-Authored-By: Claude Opus 4.7 --- apps/web/app/play/page.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/web/app/play/page.tsx b/apps/web/app/play/page.tsx index 6be09c6..2efd99b 100644 --- a/apps/web/app/play/page.tsx +++ b/apps/web/app/play/page.tsx @@ -414,7 +414,16 @@ function PlayInner() { // skips synthesis while muted, so a scene entered muted has no audio to play // back otherwise. (Clearing the map re-synthesizes already-fetched beats on a // mid-scene un-mute, but that's bounded to one scene and a rare toggle.) + // + // Gate on actual mute *transitions*: on mount this effect would otherwise + // fire alongside the scene effect above (both call prefetchSceneAudio), + // doubling the initial /api/beat-audio batch — the first set is dispatched + // only to be aborted mid-flight, burning TTS quota. + const prevMutedRef = useRef(muted); useEffect(() => { + const prev = prevMutedRef.current; + prevMutedRef.current = muted; + if (prev === muted) return; cancelBeatAudioFetches(); if (muted) return; setBeatAudioMap({});