diff --git a/web/src/components/player/DynamicVideoPlayer.tsx b/web/src/components/player/DynamicVideoPlayer.tsx index 0a71ca28d..ff6c49667 100644 --- a/web/src/components/player/DynamicVideoPlayer.tsx +++ b/web/src/components/player/DynamicVideoPlayer.tsx @@ -1,4 +1,11 @@ -import { MutableRefObject, useEffect, useMemo, useRef, useState } from "react"; +import { + MutableRefObject, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import VideoPlayer from "./VideoPlayer"; import Player from "video.js/dist/types/player"; import TimelineEventOverlay from "../overlay/TimelineDataOverlay"; @@ -6,6 +13,7 @@ import { useApiHost } from "@/api"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import ActivityIndicator from "../ui/activity-indicator"; +import useKeyboardListener from "@/hooks/use-keyboard-listener"; /** * Dynamically switches between video playback and scrubbing preview player. @@ -55,6 +63,51 @@ export default function DynamicVideoPlayer({ ); }, [config]); + // keyboard control + const onKeyboardShortcut = useCallback( + (key: string, down: boolean, repeat: boolean) => { + switch (key) { + case "ArrowLeft": + if (down) { + const currentTime = playerRef.current?.currentTime(); + + if (currentTime) { + playerRef.current?.currentTime(Math.max(0, currentTime - 5)); + } + } + break; + case "ArrowRight": + if (down) { + const currentTime = playerRef.current?.currentTime(); + + if (currentTime) { + playerRef.current?.currentTime(currentTime + 5); + } + } + break; + case "m": + if (down && !repeat && playerRef.current) { + playerRef.current.muted(!playerRef.current.muted()); + } + break; + case " ": + if (down && playerRef.current) { + if (playerRef.current.paused()) { + playerRef.current.play(); + } else { + playerRef.current.pause(); + } + } + break; + } + }, + [playerRef] + ); + useKeyboardListener( + ["ArrowLeft", "ArrowRight", "m", " "], + onKeyboardShortcut + ); + // initial state const initialPlaybackSource = useMemo(() => { diff --git a/web/src/hooks/use-keyboard-listener.tsx b/web/src/hooks/use-keyboard-listener.tsx new file mode 100644 index 000000000..a68cb3a7e --- /dev/null +++ b/web/src/hooks/use-keyboard-listener.tsx @@ -0,0 +1,43 @@ +import { useCallback, useEffect } from "react"; + +export default function useKeyboardListener( + keys: string[], + listener: (key: string, down: boolean, repeat: boolean) => void +) { + const keyDownListener = useCallback( + (e: KeyboardEvent) => { + if (!e) { + return; + } + + if (keys.includes(e.key)) { + e.preventDefault(); + listener(e.key, true, e.repeat); + } + }, + [listener] + ); + + const keyUpListener = useCallback( + (e: KeyboardEvent) => { + if (!e) { + return; + } + + if (keys.includes(e.key)) { + e.preventDefault(); + listener(e.key, false, false); + } + }, + [listener] + ); + + useEffect(() => { + document.addEventListener("keydown", keyDownListener); + document.addEventListener("keyup", keyUpListener); + return () => { + document.removeEventListener("keydown", keyDownListener); + document.removeEventListener("keyup", keyUpListener); + }; + }, [listener]); +}