From 8f17355417d42cdfbe87f7b400738b6b7e016214 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 23 Apr 2026 11:21:52 -0500 Subject: [PATCH] Fix gameday playback regression --- frontend/src/hooks/useClipPlayback.ts | 94 +++++++++++++-------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/frontend/src/hooks/useClipPlayback.ts b/frontend/src/hooks/useClipPlayback.ts index 5361b9f..b86ee32 100644 --- a/frontend/src/hooks/useClipPlayback.ts +++ b/frontend/src/hooks/useClipPlayback.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { API_BASE } from "../api/client"; @@ -31,7 +31,47 @@ export function useClipPlayback() { useEffect(() => stopClip(), []); - function ensureAudio() { + const setPlaybackGain = useCallback((value: number) => { + const gainNode = gainNodeRef.current; + if (!gainNode) { + return; + } + + gainNode.gain.cancelScheduledValues(gainNode.context.currentTime); + gainNode.gain.setValueAtTime(value, gainNode.context.currentTime); + }, []); + + const clearFadeOutTimer = useCallback(() => { + if (fadeOutTimerRef.current !== null) { + window.clearTimeout(fadeOutTimerRef.current); + fadeOutTimerRef.current = null; + } + }, []); + + const stopClip = useCallback((resetGain = true) => { + clearFadeOutTimer(); + + const audio = audioRef.current; + if (audio) { + audio.pause(); + audio.currentTime = 0; + audio.removeAttribute("src"); + audio.load(); + } + + if (resetGain) { + setPlaybackGain(1); + } + + activeKeyRef.current = null; + playbackRangeRef.current = null; + setActiveKey(null); + setActiveDetails(null); + setIsPlaying(false); + setCurrentTimeMs(null); + }, [clearFadeOutTimer, setPlaybackGain]); + + const ensureAudio = useCallback(() => { const audio = audioRef.current ?? new Audio(); if (!audioRef.current) { audio.onplay = () => { @@ -70,49 +110,9 @@ export function useClipPlayback() { audioRef.current = audio; return audio; - } + }, [stopClip]); - function setPlaybackGain(value: number) { - const gainNode = gainNodeRef.current; - if (!gainNode) { - return; - } - - gainNode.gain.cancelScheduledValues(gainNode.context.currentTime); - gainNode.gain.setValueAtTime(value, gainNode.context.currentTime); - } - - function clearFadeOutTimer() { - if (fadeOutTimerRef.current !== null) { - window.clearTimeout(fadeOutTimerRef.current); - fadeOutTimerRef.current = null; - } - } - - function stopClip(resetGain = true) { - clearFadeOutTimer(); - - const audio = audioRef.current; - if (audio) { - audio.pause(); - audio.currentTime = 0; - audio.removeAttribute("src"); - audio.load(); - } - - if (resetGain) { - setPlaybackGain(1); - } - - activeKeyRef.current = null; - playbackRangeRef.current = null; - setActiveKey(null); - setActiveDetails(null); - setIsPlaying(false); - setCurrentTimeMs(null); - } - - function fadeOutClip(durationMs = 1000) { + const fadeOutClip = useCallback((durationMs = 1000) => { const audio = audioRef.current; if (!audio || audio.paused) { stopClip(); @@ -137,9 +137,9 @@ export function useClipPlayback() { fadeOutTimerRef.current = window.setTimeout(() => { stopClip(false); }, safeDuration); - } + }, [clearFadeOutTimer, stopClip]); - async function playClip({ key, url, startMs, endMs, title, subtitle }: PlayClipArgs) { + const playClip = useCallback(async ({ key, url, startMs, endMs, title, subtitle }: PlayClipArgs) => { if (!url) { return; } @@ -192,7 +192,7 @@ export function useClipPlayback() { stopClip(); throw error; } - } + }, [ensureAudio, setPlaybackGain, stopClip]); return { activeKey,