* first attempt at stream sidebar, tasks, and events * fix folder from root * cleanup folders and events, and condense changes * cleanup and add toggle for sidebar * cleanup * fix nits
112 lines
2.9 KiB
TypeScript
112 lines
2.9 KiB
TypeScript
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<MediaParticleHandle | null>;
|
|
}
|
|
|
|
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 };
|
|
}
|