93 lines
2.6 KiB
TypeScript
93 lines
2.6 KiB
TypeScript
import { 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";
|
|
import { set } from "zod";
|
|
|
|
const SPACE_TAP_THRESHOLD_MS = 250;
|
|
|
|
interface UsePlaybackKeysOptions {
|
|
mediaRef: RefObject<MediaParticleHandle | null>;
|
|
}
|
|
|
|
interface UsePlaybackKeysResult {
|
|
fastPlayback: boolean;
|
|
}
|
|
|
|
/**
|
|
* 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");
|
|
|
|
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 };
|
|
}
|