diff --git a/src/pages/PlayerPage.tsx b/src/pages/PlayerPage.tsx index e164416..0bd391d 100644 --- a/src/pages/PlayerPage.tsx +++ b/src/pages/PlayerPage.tsx @@ -78,11 +78,18 @@ export default function PlayerPage() { [setVolumePref], ) const [isFullscreen, setIsFullscreen] = useState(false) + // High-frequency playback state. currentTime and buffered update on every + // vidstack tick (4-60 Hz) - we store the raw value in a ref for imperative + // code (progress reporting, A-B loop, seek calculations) and only push to + // React state at ~4 Hz so the UI doesn't re-render on every video frame. + const currentTimeRef = useRef(0) + const bufferedRef = useRef(0) const [currentTime, setCurrentTime] = useState(0) const [duration, setDuration] = useState(0) const [buffered, setBuffered] = useState(0) const [scrubPercent, setScrubPercent] = useState(null) const [seeking, setSeeking] = useState(false) + const lastUiUpdateRef = useRef(0) /* Track selection (audio / subtitle). * `audioIndex` is the UI selection; `streamAudioIndex` is what we send @@ -216,25 +223,28 @@ export default function PlayerPage() { const [playbackRate, setPlaybackRate] = useState(defaultPlaybackRate || 1) /* Sleep timer - counts down only while playing */ + const sleepRemainingRef = useRef(sleepTimerMinutes * 60) const [sleepRemainingSec, setSleepRemainingSec] = useState(sleepTimerMinutes * 60) useEffect(() => { - setSleepRemainingSec(sleepTimerMinutes * 60) + const sec = sleepTimerMinutes * 60 + sleepRemainingRef.current = sec + setSleepRemainingSec(sec) }, [sleepTimerMinutes, id]) useEffect(() => { - if (sleepTimerMinutes <= 0 || sleepRemainingSec <= 0) return + if (sleepTimerMinutes <= 0) return if (!isPaused) { const id = window.setInterval(() => { - setSleepRemainingSec(prev => { - if (prev <= 1) { - playerRef.current?.pause() - return 0 - } - return prev - 1 - }) + const next = sleepRemainingRef.current - 1 + sleepRemainingRef.current = next + setSleepRemainingSec(next) + if (next <= 0) { + playerRef.current?.pause() + window.clearInterval(id) + } }, 1000) return () => window.clearInterval(id) } - }, [isPaused, sleepTimerMinutes, sleepRemainingSec]) + }, [isPaused, sleepTimerMinutes]) const reducedMotion = useReducedMotion() const { @@ -455,16 +465,25 @@ export default function PlayerPage() { * * Wrapped in try/catch since vidstack may have already cleaned the * element by the time this runs. */ + // Capture the underlying