import { useCallback, useEffect, useRef, useState, type RefObject, } from 'react'; import type { MediaParticleHandle } from '@/features/particles/media-particle-view'; import { useSuspendPlayback } from '@/hooks/use-suspend-playback'; import { selectIsPaused, usePlaybackPauseStore, } from '@/stores/playback-pause-store'; import { isTypingTarget } from '@/lib/keyboard'; const SPACE_TAP_THRESHOLD_MS = 250; interface UsePlaybackKeysOptions { mediaRef: RefObject; } interface UsePlaybackKeysResult { fastPlayback: boolean; /** True while playback is paused by the user's space toggle/hold. */ spacePaused: boolean; /** Clear the user's space pause (e.g. clicking the on-screen play button). */ resume: () => void; } /** * Hold-space to pause, hold-shift for 1.5x. Space is always handled (it * manages its own suspender via useSuspendPlayback); other keys bail when * playback is already paused for an external reason. */ export function usePlaybackKeys({ mediaRef, }: UsePlaybackKeysOptions): UsePlaybackKeysResult { const [spaceHeld, setSpaceHeld] = useState(false); const [fastPlayback, setFastPlayback] = useState(false); const spaceStartRef = useRef(0); useSuspendPlayback(spaceHeld, 'hold-space'); const resume = useCallback(() => { setSpaceHeld(false); spaceStartRef.current = 0; }, []); useEffect(() => { const isExternallyPaused = () => selectIsPaused(usePlaybackPauseStore.getState()) && !spaceHeld; const onKeyDown = (e: KeyboardEvent) => { if (isTypingTarget(e)) return; if (e.key === ' ') { e.preventDefault(); if (!e.repeat) { if (spaceHeld) { setSpaceHeld(false); spaceStartRef.current = 0; } else { setSpaceHeld(true); spaceStartRef.current = Date.now(); } } return; } if (isExternallyPaused()) return; if (e.key === 'Shift' && !e.repeat) { mediaRef.current?.setPlaybackRate(1.5); setFastPlayback(true); } }; const onKeyUp = (e: KeyboardEvent) => { if (isTypingTarget(e)) return; if (e.key === ' ') { e.preventDefault(); // keep space-held if it was a quick tap, to allow for space-to-toggle behavior if (Date.now() - spaceStartRef.current < SPACE_TAP_THRESHOLD_MS) { return; } setSpaceHeld(false); spaceStartRef.current = 0; return; } if (isExternallyPaused()) return; if (e.key === 'Shift') { mediaRef.current?.setPlaybackRate(1); setFastPlayback(false); } }; window.addEventListener('keydown', onKeyDown); window.addEventListener('keyup', onKeyUp); return () => { window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); }; }, [mediaRef, spaceHeld]); return { fastPlayback, spacePaused: spaceHeld, resume }; }