diff --git a/web/src/components/player/DynamicVideoPlayer.tsx b/web/src/components/player/DynamicVideoPlayer.tsx index a4309c6dd..02e4acdca 100644 --- a/web/src/components/player/DynamicVideoPlayer.tsx +++ b/web/src/components/player/DynamicVideoPlayer.tsx @@ -30,6 +30,7 @@ type DynamicVideoPlayerProps = { cameraPreviews: Preview[]; previewOnly?: boolean; onControllerReady?: (controller: DynamicVideoController) => void; + onClick?: () => void; }; export default function DynamicVideoPlayer({ className, @@ -38,6 +39,7 @@ export default function DynamicVideoPlayer({ cameraPreviews, previewOnly = false, onControllerReady, + onClick, }: DynamicVideoPlayerProps) { const apiHost = useApiHost(); const { data: config } = useSWR("config"); @@ -83,6 +85,8 @@ export default function DynamicVideoPlayer({ ); }, [camera, config, previewOnly]); + const [hasRecordingAtTime, setHasRecordingAtTime] = useState(true); + // keyboard control const onKeyboardShortcut = useCallback( @@ -145,28 +149,35 @@ export default function DynamicVideoPlayer({ // we only want to calculate this once // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const initialPreviewSource = useMemo(() => { - const preview = cameraPreviews.find( + const initialPreview = useMemo(() => { + return cameraPreviews.find( (preview) => preview.camera == camera && Math.round(preview.start) >= timeRange.start && Math.floor(preview.end) <= timeRange.end, ); - if (preview) { - return { - src: preview.src, - type: preview.type, - }; - } else { - return undefined; - } - // we only want to calculate this once // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const [currentPreview, setCurrentPreview] = useState(initialPreviewSource); + const [currentPreview, setCurrentPreview] = useState(initialPreview); + + const onPreviewSeeked = useCallback(() => { + if (!controller) { + return; + } + + controller.finishedSeeking(); + + if (currentPreview && previewOnly && previewRef.current && onClick) { + setHasRecordingAtTime( + controller.hasRecordingAtTime( + currentPreview.start + previewRef.current.currentTime, + ), + ); + } + }, [controller, currentPreview, onClick, previewOnly]); // state of playback player @@ -177,7 +188,9 @@ export default function DynamicVideoPlayer({ }; }, [timeRange]); const { data: recordings } = useSWR( - previewOnly ? null : [`${camera}/recordings`, recordingParams], + previewOnly && onClick == undefined + ? null + : [`${camera}/recordings`, recordingParams], { revalidateOnFocus: false }, ); @@ -217,7 +230,9 @@ export default function DynamicVideoPlayer({ } return ( -
+
{!previewOnly && (
controller.finishedSeeking()} + onSeeked={onPreviewSeeked} onLoadedData={() => controller.previewReady()} onLoadStart={ previewOnly && onControllerReady @@ -286,6 +301,9 @@ export default function DynamicVideoPlayer({ )} + {onClick && !hasRecordingAtTime && ( +
+ )}
); } @@ -508,4 +526,16 @@ export class DynamicVideoController { this.previewRef.current?.pause(); this.readyToScrub = true; } + + hasRecordingAtTime(time: number): boolean { + if (!this.recordings || this.recordings.length == 0) { + return false; + } + + return ( + this.recordings.find( + (segment) => segment.start_time <= time && segment.end_time >= time, + ) != undefined + ); + } } diff --git a/web/src/views/events/RecordingView.tsx b/web/src/views/events/RecordingView.tsx index b959ce1a7..21bf884ff 100644 --- a/web/src/views/events/RecordingView.tsx +++ b/web/src/views/events/RecordingView.tsx @@ -98,9 +98,22 @@ export function DesktopRecordingView({ videoPlayersRef.current[mainCamera].onPlayerTimeUpdate(undefined); videoPlayersRef.current[mainCamera].scrubToTimestamp(currentTime); videoPlayersRef.current[newCam].seekToTimestamp(currentTime, true); + videoPlayersRef.current[newCam].onPlayerTimeUpdate( + (timestamp: number) => { + setCurrentTime(timestamp); + + allCameras.forEach((cam) => { + if (cam != newCam) { + videoPlayersRef.current[cam]?.scrubToTimestamp( + Math.floor(timestamp), + ); + } + }); + }, + ); setMainCamera(newCam); }, - [currentTime, mainCamera], + [allCameras, currentTime, mainCamera], ); return ( @@ -130,6 +143,14 @@ export function DesktopRecordingView({ setPlayerReady(true); controller.onPlayerTimeUpdate((timestamp: number) => { setCurrentTime(timestamp); + + allCameras.forEach((otherCam) => { + if (cam != otherCam) { + videoPlayersRef.current[otherCam]?.scrubToTimestamp( + Math.floor(timestamp), + ); + } + }); }); controller.seekToTimestamp(startTime, true); @@ -140,11 +161,7 @@ export function DesktopRecordingView({ } return ( -
onSelectCamera(cam)} - > +
onSelectCamera(cam)} />
); })}
-
+