Add preview only mode to make loading more efficeint

This commit is contained in:
Nicolas Mowen 2024-03-03 16:43:06 -07:00
parent e6d5cdedfd
commit 2172ac43ff
4 changed files with 70 additions and 57 deletions

View File

@ -28,7 +28,7 @@ type DynamicVideoPlayerProps = {
camera: string; camera: string;
timeRange: { start: number; end: number }; timeRange: { start: number; end: number };
cameraPreviews: Preview[]; cameraPreviews: Preview[];
defaultMode?: PlayerMode; previewOnly?: boolean;
onControllerReady?: (controller: DynamicVideoController) => void; onControllerReady?: (controller: DynamicVideoController) => void;
}; };
export default function DynamicVideoPlayer({ export default function DynamicVideoPlayer({
@ -36,7 +36,7 @@ export default function DynamicVideoPlayer({
camera, camera,
timeRange, timeRange,
cameraPreviews, cameraPreviews,
defaultMode = "playback", previewOnly = false,
onControllerReady, onControllerReady,
}: DynamicVideoPlayerProps) { }: DynamicVideoPlayerProps) {
const apiHost = useApiHost(); const apiHost = useApiHost();
@ -64,7 +64,7 @@ export default function DynamicVideoPlayer({
const playerRef = useRef<Player | undefined>(undefined); const playerRef = useRef<Player | undefined>(undefined);
const previewRef = useRef<Player | undefined>(undefined); const previewRef = useRef<Player | undefined>(undefined);
const [isScrubbing, setIsScrubbing] = useState(defaultMode == "scrubbing"); const [isScrubbing, setIsScrubbing] = useState(previewOnly);
const [hasPreview, setHasPreview] = useState(false); const [hasPreview, setHasPreview] = useState(false);
const [focusedItem, setFocusedItem] = useState<Timeline | undefined>( const [focusedItem, setFocusedItem] = useState<Timeline | undefined>(
undefined, undefined,
@ -78,11 +78,11 @@ export default function DynamicVideoPlayer({
playerRef, playerRef,
previewRef, previewRef,
(config.cameras[camera]?.detect?.annotation_offset || 0) / 1000, (config.cameras[camera]?.detect?.annotation_offset || 0) / 1000,
defaultMode, previewOnly ? "scrubbing" : "playback",
setIsScrubbing, setIsScrubbing,
setFocusedItem, setFocusedItem,
); );
}, [camera, config, defaultMode]); }, [camera, config, previewOnly]);
// keyboard control // keyboard control
@ -178,12 +178,12 @@ export default function DynamicVideoPlayer({
}; };
}, [timeRange]); }, [timeRange]);
const { data: recordings } = useSWR<Recording[]>( const { data: recordings } = useSWR<Recording[]>(
[`${camera}/recordings`, recordingParams], previewOnly ? null : [`${camera}/recordings`, recordingParams],
{ revalidateOnFocus: false }, { revalidateOnFocus: false },
); );
useEffect(() => { useEffect(() => {
if (!controller || !recordings) { if (!controller || (!previewOnly && !recordings)) {
return; return;
} }
@ -204,7 +204,7 @@ export default function DynamicVideoPlayer({
setHasPreview(preview != undefined); setHasPreview(preview != undefined);
controller.newPlayback({ controller.newPlayback({
recordings, recordings: recordings ?? [],
playbackUri, playbackUri,
preview, preview,
}); });
@ -219,49 +219,53 @@ export default function DynamicVideoPlayer({
return ( return (
<div className={className}> <div className={className}>
<div {!previewOnly && (
className={`w-full relative ${ <div
hasPreview && isScrubbing ? "hidden" : "visible" className={`w-full relative ${
}`} hasPreview && isScrubbing ? "hidden" : "visible"
> }`}
<VideoPlayer
options={{
preload: "auto",
autoplay: true,
sources: [initialPlaybackSource],
aspectRatio: tallVideo ? "16:9" : undefined,
controlBar: {
remainingTimeDisplay: false,
progressControl: {
seekBar: false,
},
},
}}
seekOptions={{ forward: 10, backward: 5 }}
onReady={(player) => {
playerRef.current = player;
player.on("playing", () => setFocusedItem(undefined));
player.on("timeupdate", () => {
controller.updateProgress(player.currentTime() || 0);
});
player.on("ended", () => controller.fireClipChangeEvent("forward"));
if (onControllerReady) {
onControllerReady(controller);
}
}}
onDispose={() => {
playerRef.current = undefined;
}}
> >
{config && focusedItem && ( <VideoPlayer
<TimelineEventOverlay options={{
timeline={focusedItem} preload: "auto",
cameraConfig={config.cameras[camera]} autoplay: true,
/> sources: [initialPlaybackSource],
)} aspectRatio: tallVideo ? "16:9" : undefined,
</VideoPlayer> controlBar: {
</div> remainingTimeDisplay: false,
progressControl: {
seekBar: false,
},
},
}}
seekOptions={{ forward: 10, backward: 5 }}
onReady={(player) => {
playerRef.current = player;
player.on("playing", () => setFocusedItem(undefined));
player.on("timeupdate", () => {
controller.updateProgress(player.currentTime() || 0);
});
player.on("ended", () =>
controller.fireClipChangeEvent("forward"),
);
if (onControllerReady) {
onControllerReady(controller);
}
}}
onDispose={() => {
playerRef.current = undefined;
}}
>
{config && focusedItem && (
<TimelineEventOverlay
timeline={focusedItem}
cameraConfig={config.cameras[camera]}
/>
)}
</VideoPlayer>
</div>
)}
<div <div
className={`w-full ${hasPreview && isScrubbing ? "visible" : "hidden"}`} className={`w-full ${hasPreview && isScrubbing ? "visible" : "hidden"}`}
> >
@ -281,6 +285,10 @@ export default function DynamicVideoPlayer({
player.pause(); player.pause();
player.on("seeked", () => controller.finishedSeeking()); player.on("seeked", () => controller.finishedSeeking());
player.on("loadeddata", () => controller.previewReady()); player.on("loadeddata", () => controller.previewReady());
if (previewOnly && onControllerReady) {
onControllerReady(controller);
}
}} }}
onDispose={() => { onDispose={() => {
previewRef.current = undefined; previewRef.current = undefined;

View File

@ -5,8 +5,8 @@ import useOverlayState from "@/hooks/use-overlay-state";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { Preview } from "@/types/preview"; import { Preview } from "@/types/preview";
import { ReviewFilter, ReviewSegment, ReviewSeverity } from "@/types/review"; import { ReviewFilter, ReviewSegment, ReviewSeverity } from "@/types/review";
import DesktopRecordingView from "@/views/events/DesktopRecordingView";
import EventView from "@/views/events/EventView"; import EventView from "@/views/events/EventView";
import RecordingView from "@/views/events/RecordingView";
import axios from "axios"; import axios from "axios";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import useSWR from "swr"; import useSWR from "swr";
@ -220,7 +220,7 @@ export default function Events() {
if (selectedData) { if (selectedData) {
return ( return (
<DesktopRecordingView <RecordingView
reviewItems={selectedData.cameraSegments} reviewItems={selectedData.cameraSegments}
selectedReview={selectedData.selected} selectedReview={selectedData.selected}
relevantPreviews={selectedData.cameraPreviews} relevantPreviews={selectedData.cameraPreviews}

View File

@ -13,7 +13,12 @@ import { useEventUtils } from "@/hooks/use-event-utils";
import { useScrollLockout } from "@/hooks/use-mouse-listener"; import { useScrollLockout } from "@/hooks/use-mouse-listener";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { Preview } from "@/types/preview"; import { Preview } from "@/types/preview";
import { ReviewFilter, ReviewSegment, ReviewSeverity } from "@/types/review"; import {
ReviewFilter,
ReviewSegment,
ReviewSeverity,
ReviewSummary,
} from "@/types/review";
import { getChunkedTimeRange } from "@/utils/timelineUtil"; import { getChunkedTimeRange } from "@/utils/timelineUtil";
import axios from "axios"; import axios from "axios";
import { import {
@ -621,7 +626,7 @@ function MotionReview({
camera={camera.name} camera={camera.name}
timeRange={timeRangeSegments.ranges[selectedRangeIdx]} timeRange={timeRangeSegments.ranges[selectedRangeIdx]}
cameraPreviews={relevantPreviews || []} cameraPreviews={relevantPreviews || []}
defaultMode="scrubbing" previewOnly
onControllerReady={(controller) => { onControllerReady={(controller) => {
videoPlayersRef.current[camera.name] = controller; videoPlayersRef.current[camera.name] = controller;
setPlayerReady(true); setPlayerReady(true);

View File

@ -10,16 +10,16 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { IoMdArrowRoundBack } from "react-icons/io"; import { IoMdArrowRoundBack } from "react-icons/io";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
type DesktopRecordingViewProps = { type RecordingViewProps = {
selectedReview: ReviewSegment; selectedReview: ReviewSegment;
reviewItems: ReviewSegment[]; reviewItems: ReviewSegment[];
relevantPreviews?: Preview[]; relevantPreviews?: Preview[];
}; };
export default function DesktopRecordingView({ export default function RecordingView({
selectedReview, selectedReview,
reviewItems, reviewItems,
relevantPreviews, relevantPreviews,
}: DesktopRecordingViewProps) { }: RecordingViewProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);