From 4061be602dc335cb3b3de625608fbf53602687da Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 26 Feb 2024 09:04:56 -0700 Subject: [PATCH] Automatically skip to next preview / clip when watching full recordings (#10055) * Fix useEffect and try to load next clip for preview * Get scrubbing to next preview working * Handle skipping to next preview --- .../components/player/DynamicVideoPlayer.tsx | 58 ++++++++++++++----- web/src/views/events/DesktopRecordingView.tsx | 23 +++++--- 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/web/src/components/player/DynamicVideoPlayer.tsx b/web/src/components/player/DynamicVideoPlayer.tsx index 306f10392..df89af0e0 100644 --- a/web/src/components/player/DynamicVideoPlayer.tsx +++ b/web/src/components/player/DynamicVideoPlayer.tsx @@ -199,8 +199,6 @@ export default function DynamicVideoPlayer({ return ; } - //console.log(`${config.detect.width / config.detect.height < 1.7 ? "16:9" : undefined}`) - return (
{ controller.updateProgress(player.currentTime() || 0); }); - player.on("ended", () => controller.fireClipEndEvent()); + player.on("ended", () => controller.fireClipChangeEvent("forward")); if (onControllerReady) { onControllerReady(controller); @@ -264,6 +262,7 @@ export default function DynamicVideoPlayer({ previewRef.current = player; player.pause(); player.on("seeked", () => controller.finishedSeeking()); + player.on("loadeddata", () => controller.previewReady()); }} onDispose={() => { previewRef.current = undefined; @@ -285,7 +284,8 @@ export class DynamicVideoController { // playback private recordings: Recording[] = []; private onPlaybackTimestamp: ((time: number) => void) | undefined = undefined; - private onClipEnded: (() => void) | undefined = undefined; + private onClipChange: ((dir: "forward" | "backward") => void) | undefined = + undefined; private annotationOffset: number; private timeToStart: number | undefined = undefined; @@ -293,6 +293,7 @@ export class DynamicVideoController { private preview: Preview | undefined = undefined; private timeToSeek: number | undefined = undefined; private seeking = false; + private readyToScrub = true; constructor( playerRef: MutableRefObject, @@ -395,30 +396,50 @@ export class DynamicVideoController { this.onPlaybackTimestamp = listener; } - onClipEndedEvent(listener: () => void) { - this.onClipEnded = listener; + onClipChangedEvent(listener: (dir: "forward" | "backward") => void) { + this.onClipChange = listener; } - fireClipEndEvent() { - if (this.onClipEnded) { - this.onClipEnded(); + fireClipChangeEvent(dir: "forward" | "backward") { + if (this.onClipChange) { + this.onClipChange(dir); } } scrubToTimestamp(time: number) { + if (!this.preview) { + return; + } + + if (!this.readyToScrub) { + return; + } + + if (time > this.preview.end) { + if (this.playerMode == "scrubbing") { + this.playerMode = "playback"; + this.setScrubbing(false); + this.timeToSeek = undefined; + this.seeking = false; + this.readyToScrub = false; + this.fireClipChangeEvent("forward"); + } + return; + } + if (this.playerMode != "scrubbing") { this.playerMode = "scrubbing"; this.playerRef.current?.pause(); this.setScrubbing(true); } - if (this.preview) { - if (this.seeking) { - this.timeToSeek = time; - } else { - this.previewRef.current?.currentTime(time - this.preview.start); - this.seeking = true; - } + if (this.seeking) { + this.timeToSeek = time; + } else { + this.previewRef.current?.currentTime( + Math.max(0, time - this.preview.start) + ); + this.seeking = true; } } @@ -438,4 +459,9 @@ export class DynamicVideoController { this.seeking = false; } } + + previewReady() { + this.previewRef.current?.pause(); + this.readyToScrub = true; + } } diff --git a/web/src/views/events/DesktopRecordingView.tsx b/web/src/views/events/DesktopRecordingView.tsx index 4b73e6491..cd908fd22 100644 --- a/web/src/views/events/DesktopRecordingView.tsx +++ b/web/src/views/events/DesktopRecordingView.tsx @@ -20,9 +20,13 @@ export default function DesktopRecordingView({ relevantPreviews, }: DesktopRecordingViewProps) { const navigate = useNavigate(); - const controllerRef = useRef(undefined); const contentRef = useRef(null); + // controller state + + const [playerReady, setPlayerReady] = useState(false); + const controllerRef = useRef(undefined); + // timeline time const timeRange = useMemo( @@ -44,12 +48,14 @@ export default function DesktopRecordingView({ return; } - if (selectedRangeIdx < timeRange.ranges.length - 1) { - controllerRef.current.onClipEndedEvent(() => { + controllerRef.current.onClipChangedEvent((dir) => { + if (dir == "forward" && selectedRangeIdx < timeRange.ranges.length - 1) { setSelectedRangeIdx(selectedRangeIdx + 1); - }); - } - }, [controllerRef, selectedRangeIdx]); + } else if (selectedRangeIdx > 0) { + setSelectedRangeIdx(selectedRangeIdx - 1); + } + }); + }, [playerReady, selectedRangeIdx]); // scrubbing and timeline state @@ -62,13 +68,13 @@ export default function DesktopRecordingView({ if (scrubbing) { controllerRef.current?.scrubToTimestamp(currentTime); } - }, [controllerRef, currentTime, scrubbing]); + }, [currentTime, scrubbing]); useEffect(() => { if (!scrubbing) { controllerRef.current?.seekToTimestamp(currentTime, true); } - }, [controllerRef, scrubbing]); + }, [scrubbing]); return (
@@ -87,6 +93,7 @@ export default function DesktopRecordingView({ cameraPreviews={relevantPreviews || []} onControllerReady={(controller) => { controllerRef.current = controller; + setPlayerReady(true); controllerRef.current.onPlayerTimeUpdate((timestamp: number) => { setCurrentTime(timestamp); });