From 349ab4baae17d9e83e1d3010c91c62e4a86b0072 Mon Sep 17 00:00:00 2001 From: itsRevela Date: Fri, 17 Apr 2026 14:56:21 -0500 Subject: [PATCH] fix(audio): advance tracks on end and avoid consecutive repeats The audio creation effect had [showIntro, audioElement, playingTrack, musicVol] as deps. Any of those changing (volume slider, track change) triggered the cleanup which removed the ended listener and paused the audio. The if (audioElement) return guard then prevented recreating it, leaving a paused audio with no ended handler so tracks never advanced. - Narrow creation effect deps to [showIntro] so it runs once and does not tear down on volume or track changes. - Move the ended listener into its own effect with [audioElement, currentTrack] deps. The handler is re-attached on currentTrack changes and always reads the latest mode; the cleanup removes only the listener, not the audio. - Update the randomize-on-currentTrack path to use a prev-aware picker so enabling randomize never replays the currently-playing track. --- src/hooks/useAudioController.ts | 48 +++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/hooks/useAudioController.ts b/src/hooks/useAudioController.ts index 2d9529a..65d30ab 100644 --- a/src/hooks/useAudioController.ts +++ b/src/hooks/useAudioController.ts @@ -109,20 +109,6 @@ export function useAudioController({ musicVol, sfxVol, showIntro, isGameRunning, const audio = new Audio(TRACKS[playingTrack]); audio.volume = musicVol / 100; - const handleEnded = () => { - if (currentTrack === -1) { - // Randomize: pick a different track - setPlayingTrack((prev) => { - let next; - do { next = Math.floor(Math.random() * TRACKS.length); } while (next === prev && TRACKS.length > 1); - return next; - }); - } else { - setPlayingTrack((prev) => (prev + 1) % TRACKS.length); - setCurrentTrack((prev) => (prev + 1) % TRACKS.length); - } - }; - audio.addEventListener("ended", handleEnded); const tryPlay = () => { audio.play().catch((err) => { @@ -137,19 +123,41 @@ export function useAudioController({ musicVol, sfxVol, showIntro, isGameRunning, tryPlay(); setAudioElement(audio); - return () => { - audio.removeEventListener("ended", handleEnded); - audio.pause(); + }, [showIntro]); + + // Attach/re-attach `ended` listener when audio or currentTrack changes, so the + // handler always reads the latest randomize-vs-specific mode. + useEffect(() => { + if (!audioElement) return; + const handleEnded = () => { + if (currentTrack === -1) { + setPlayingTrack((prev) => { + if (TRACKS.length <= 1) return prev; + let next; + do { next = Math.floor(Math.random() * TRACKS.length); } while (next === prev); + return next; + }); + } else { + setPlayingTrack((prev) => (prev + 1) % TRACKS.length); + setCurrentTrack((prev) => (prev + 1) % TRACKS.length); + } }; - }, [showIntro, audioElement, playingTrack, musicVol]); + audioElement.addEventListener("ended", handleEnded); + return () => audioElement.removeEventListener("ended", handleEnded); + }, [audioElement, currentTrack]); // When user selects a specific track, sync playingTrack useEffect(() => { if (currentTrack >= 0) { setPlayingTrack(currentTrack); } else { - // Randomize: pick a new random track - setPlayingTrack(Math.floor(Math.random() * TRACKS.length)); + // Randomize: pick a new random track, avoiding the current one + setPlayingTrack((prev) => { + if (TRACKS.length <= 1) return prev; + let next; + do { next = Math.floor(Math.random() * TRACKS.length); } while (next === prev); + return next; + }); } }, [currentTrack]);