From 92116dcc6af8461fc7077e5dad3aede83e34e9bc Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Sun, 3 Mar 2024 14:25:02 -0700 Subject: [PATCH] Implement backward preview jump and jump lockout --- .../components/player/DynamicVideoPlayer.tsx | 40 +++++++++++++++++-- web/src/components/player/VideoPlayer.tsx | 2 +- web/src/views/events/EventView.tsx | 28 ++++++++++--- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/web/src/components/player/DynamicVideoPlayer.tsx b/web/src/components/player/DynamicVideoPlayer.tsx index e4dccedda..d019abd24 100644 --- a/web/src/components/player/DynamicVideoPlayer.tsx +++ b/web/src/components/player/DynamicVideoPlayer.tsx @@ -18,6 +18,8 @@ import { Recording } from "@/types/record"; import { Preview } from "@/types/preview"; import { DynamicPlayback } from "@/types/playback"; +type PlayerMode = "playback" | "scrubbing"; + /** * Dynamically switches between video playback and scrubbing preview player. */ @@ -26,6 +28,7 @@ type DynamicVideoPlayerProps = { camera: string; timeRange: { start: number; end: number }; cameraPreviews: Preview[]; + defaultMode?: PlayerMode; onControllerReady?: (controller: DynamicVideoController) => void; }; export default function DynamicVideoPlayer({ @@ -33,6 +36,7 @@ export default function DynamicVideoPlayer({ camera, timeRange, cameraPreviews, + defaultMode = "playback", onControllerReady, }: DynamicVideoPlayerProps) { const apiHost = useApiHost(); @@ -60,7 +64,7 @@ export default function DynamicVideoPlayer({ const playerRef = useRef(undefined); const previewRef = useRef(undefined); - const [isScrubbing, setIsScrubbing] = useState(false); + const [isScrubbing, setIsScrubbing] = useState(defaultMode == "scrubbing"); const [hasPreview, setHasPreview] = useState(false); const [focusedItem, setFocusedItem] = useState( undefined, @@ -74,10 +78,11 @@ export default function DynamicVideoPlayer({ playerRef, previewRef, (config.cameras[camera]?.detect?.annotation_offset || 0) / 1000, + defaultMode, setIsScrubbing, setFocusedItem, ); - }, [camera, config]); + }, [camera, config, defaultMode]); // keyboard control @@ -178,7 +183,7 @@ export default function DynamicVideoPlayer({ ); useEffect(() => { - if (!controller || !recordings || recordings.length == 0) { + if (!controller || !recordings) { return; } @@ -292,7 +297,7 @@ export class DynamicVideoController { private previewRef: MutableRefObject; private setScrubbing: (isScrubbing: boolean) => void; private setFocusedItem: (timeline: Timeline) => void; - private playerMode: "playback" | "scrubbing" = "playback"; + private playerMode: PlayerMode = "playback"; // playback private recordings: Recording[] = []; @@ -301,6 +306,7 @@ export class DynamicVideoController { undefined; private annotationOffset: number; private timeToStart: number | undefined = undefined; + private clipChangeLockout: boolean = false; // preview private preview: Preview | undefined = undefined; @@ -312,12 +318,14 @@ export class DynamicVideoController { playerRef: MutableRefObject, previewRef: MutableRefObject, annotationOffset: number, + defaultMode: PlayerMode, setScrubbing: (isScrubbing: boolean) => void, setFocusedItem: (timeline: Timeline) => void, ) { this.playerRef = playerRef; this.previewRef = previewRef; this.annotationOffset = annotationOffset; + this.playerMode = defaultMode; this.setScrubbing = setScrubbing; this.setFocusedItem = setFocusedItem; } @@ -429,17 +437,39 @@ export class DynamicVideoController { } if (time > this.preview.end) { + if (this.clipChangeLockout) { + return; + } + if (this.playerMode == "scrubbing") { this.playerMode = "playback"; this.setScrubbing(false); this.timeToSeek = undefined; this.seeking = false; this.readyToScrub = false; + this.clipChangeLockout = true; this.fireClipChangeEvent("forward"); } return; } + if (time < this.preview.start) { + if (this.clipChangeLockout) { + return; + } + + if (this.playerMode == "scrubbing") { + this.playerMode = "playback"; + this.setScrubbing(false); + this.timeToSeek = undefined; + this.seeking = false; + this.readyToScrub = false; + this.clipChangeLockout = true; + this.fireClipChangeEvent("backward"); + } + return; + } + if (this.playerMode != "scrubbing") { this.playerMode = "scrubbing"; this.playerRef.current?.pause(); @@ -461,6 +491,8 @@ export class DynamicVideoController { return; } + this.clipChangeLockout = false; + if ( this.timeToSeek && this.timeToSeek != this.previewRef.current?.currentTime() diff --git a/web/src/components/player/VideoPlayer.tsx b/web/src/components/player/VideoPlayer.tsx index d1b8a22a2..881238bec 100644 --- a/web/src/components/player/VideoPlayer.tsx +++ b/web/src/components/player/VideoPlayer.tsx @@ -88,7 +88,7 @@ export default function VideoPlayer({ return (
-
+
{children}
); diff --git a/web/src/views/events/EventView.tsx b/web/src/views/events/EventView.tsx index ebd0176cc..50b941c76 100644 --- a/web/src/views/events/EventView.tsx +++ b/web/src/views/events/EventView.tsx @@ -533,9 +533,7 @@ function MotionReview({ let cameras; if (!filter || !filter.cameras) { - cameras = Object.values(config.cameras).filter( - (cam) => cam.name == "front_cam", - ); + cameras = Object.values(config.cameras); } else { const filteredCams = filter.cameras; @@ -569,6 +567,26 @@ function MotionReview({ timeRangeSegments.ranges[selectedRangeIdx].start, ); + // move to next clip + useEffect(() => { + if (!videoPlayersRef.current) { + return; + } + + Object.values(videoPlayersRef.current).forEach((controller) => { + controller.onClipChangedEvent((dir) => { + if ( + dir == "forward" && + selectedRangeIdx < timeRangeSegments.ranges.length - 1 + ) { + setSelectedRangeIdx(selectedRangeIdx + 1); + } else if (selectedRangeIdx > 0) { + setSelectedRangeIdx(selectedRangeIdx - 1); + } + }); + }); + }, [selectedRangeIdx, timeRangeSegments]); + useEffect(() => { Object.values(videoPlayersRef.current).forEach((controller) => { controller.scrubToTimestamp(currentTime); @@ -579,7 +597,7 @@ function MotionReview({ <>
{reviewCameras.map((camera) => { let grow; @@ -597,9 +615,9 @@ function MotionReview({ camera={camera.name} timeRange={timeRangeSegments.ranges[selectedRangeIdx]} cameraPreviews={relevantPreviews || []} + defaultMode="scrubbing" onControllerReady={(controller) => { videoPlayersRef.current[camera.name] = controller; - //setPlayerReady(true); }} />