mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-09 04:35:25 +03:00
Simplify switching between cameras
This commit is contained in:
parent
509211b440
commit
929a1b4b23
@ -28,6 +28,7 @@ type DynamicVideoPlayerProps = {
|
||||
timeRange: { start: number; end: number };
|
||||
cameraPreviews: Preview[];
|
||||
previewOnly?: boolean;
|
||||
preloadRecordings: boolean;
|
||||
onControllerReady: (controller: DynamicVideoController) => void;
|
||||
onClick?: () => void;
|
||||
};
|
||||
@ -37,6 +38,7 @@ export default function DynamicVideoPlayer({
|
||||
timeRange,
|
||||
cameraPreviews,
|
||||
previewOnly = false,
|
||||
preloadRecordings = true,
|
||||
onControllerReady,
|
||||
onClick,
|
||||
}: DynamicVideoPlayerProps) {
|
||||
@ -100,18 +102,26 @@ export default function DynamicVideoPlayer({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [controller]);
|
||||
|
||||
const [initPreviewOnly, setInitPreviewOnly] = useState(previewOnly);
|
||||
|
||||
useEffect(() => {
|
||||
if (!controller || !playerRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previewOnly == initPreviewOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (previewOnly) {
|
||||
playerRef.autoplay(false);
|
||||
controller.removePlayerListeners();
|
||||
} else {
|
||||
controller.setPlayerListeners();
|
||||
playerRef.play();
|
||||
controller.seekToTimestamp(playerRef.currentTime() || 0, true);
|
||||
}
|
||||
|
||||
setInitPreviewOnly(previewOnly);
|
||||
// we only want to fire once when players are ready
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [controller, previewOnly]);
|
||||
@ -266,42 +276,44 @@ export default function DynamicVideoPlayer({
|
||||
className={`relative ${className ?? ""} ${onClick ? (hasRecordingAtTime ? "cursor-pointer" : "") : ""}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div
|
||||
className={`w-full relative ${
|
||||
previewOnly || (currentPreview != undefined && isScrubbing)
|
||||
? "hidden"
|
||||
: "visible"
|
||||
}`}
|
||||
>
|
||||
<VideoPlayer
|
||||
options={{
|
||||
preload: "auto",
|
||||
autoplay: !previewOnly,
|
||||
sources: [initialPlaybackSource],
|
||||
aspectRatio: tallVideo ? "16:9" : undefined,
|
||||
controlBar: {
|
||||
remainingTimeDisplay: false,
|
||||
progressControl: {
|
||||
seekBar: false,
|
||||
},
|
||||
},
|
||||
}}
|
||||
seekOptions={{ forward: 10, backward: 5 }}
|
||||
onReady={(player) => {
|
||||
setPlayerRef(player);
|
||||
}}
|
||||
onDispose={() => {
|
||||
setPlayerRef(undefined);
|
||||
}}
|
||||
{preloadRecordings && (
|
||||
<div
|
||||
className={`w-full relative ${
|
||||
previewOnly || (currentPreview != undefined && isScrubbing)
|
||||
? "hidden"
|
||||
: "visible"
|
||||
}`}
|
||||
>
|
||||
{config && focusedItem && (
|
||||
<TimelineEventOverlay
|
||||
timeline={focusedItem}
|
||||
cameraConfig={config.cameras[camera]}
|
||||
/>
|
||||
)}
|
||||
</VideoPlayer>
|
||||
</div>
|
||||
<VideoPlayer
|
||||
options={{
|
||||
preload: "auto",
|
||||
autoplay: !previewOnly,
|
||||
sources: [initialPlaybackSource],
|
||||
aspectRatio: tallVideo ? "16:9" : undefined,
|
||||
controlBar: {
|
||||
remainingTimeDisplay: false,
|
||||
progressControl: {
|
||||
seekBar: false,
|
||||
},
|
||||
},
|
||||
}}
|
||||
seekOptions={{ forward: 10, backward: 5 }}
|
||||
onReady={(player) => {
|
||||
setPlayerRef(player);
|
||||
}}
|
||||
onDispose={() => {
|
||||
setPlayerRef(undefined);
|
||||
}}
|
||||
>
|
||||
{config && focusedItem && (
|
||||
<TimelineEventOverlay
|
||||
timeline={focusedItem}
|
||||
cameraConfig={config.cameras[camera]}
|
||||
/>
|
||||
)}
|
||||
</VideoPlayer>
|
||||
</div>
|
||||
)}
|
||||
<video
|
||||
ref={previewRef}
|
||||
className={`size-full rounded-2xl ${currentPreview != undefined && (previewOnly || isScrubbing) ? "visible" : "hidden"} ${tallVideo ? "aspect-tall" : ""} bg-black`}
|
||||
|
||||
@ -659,6 +659,7 @@ function MotionReview({
|
||||
timeRange={currentTimeRange}
|
||||
cameraPreviews={relevantPreviews || []}
|
||||
previewOnly
|
||||
preloadRecordings={false}
|
||||
onControllerReady={(controller) => {
|
||||
videoPlayersRef.current[camera.name] = controller;
|
||||
setPlayerReady(true);
|
||||
|
||||
@ -49,6 +49,10 @@ export function DesktopRecordingView({
|
||||
return chunk.start <= startTime && chunk.end >= startTime;
|
||||
}),
|
||||
);
|
||||
const currentTimeRange = useMemo(
|
||||
() => timeRange.ranges[selectedRangeIdx],
|
||||
[selectedRangeIdx, timeRange],
|
||||
);
|
||||
|
||||
// move to next clip
|
||||
useEffect(() => {
|
||||
@ -59,21 +63,18 @@ export function DesktopRecordingView({
|
||||
return;
|
||||
}
|
||||
|
||||
const firstController = Object.values(videoPlayersRef.current)[0];
|
||||
const mainController = videoPlayersRef.current[mainCamera];
|
||||
|
||||
if (firstController) {
|
||||
firstController.onClipChangedEvent((dir) => {
|
||||
if (
|
||||
dir == "forward" &&
|
||||
selectedRangeIdx < timeRange.ranges.length - 1
|
||||
) {
|
||||
setSelectedRangeIdx(selectedRangeIdx + 1);
|
||||
} else if (selectedRangeIdx > 0) {
|
||||
setSelectedRangeIdx(selectedRangeIdx - 1);
|
||||
if (mainController) {
|
||||
mainController.onClipChangedEvent((dir) => {
|
||||
if (dir == "forward") {
|
||||
if (selectedRangeIdx < timeRange.ranges.length - 1) {
|
||||
setSelectedRangeIdx(selectedRangeIdx + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [selectedRangeIdx, timeRange, videoPlayersRef, playerReady]);
|
||||
}, [selectedRangeIdx, timeRange, videoPlayersRef, playerReady, mainCamera]);
|
||||
|
||||
// scrubbing and timeline state
|
||||
|
||||
@ -82,11 +83,25 @@ export function DesktopRecordingView({
|
||||
|
||||
useEffect(() => {
|
||||
if (scrubbing) {
|
||||
if (
|
||||
currentTime > currentTimeRange.end + 60 ||
|
||||
currentTime < currentTimeRange.start - 60
|
||||
) {
|
||||
const index = timeRange.ranges.findIndex(
|
||||
(seg) => seg.start <= currentTime && seg.end >= currentTime,
|
||||
);
|
||||
|
||||
if (index != -1) {
|
||||
setSelectedRangeIdx(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Object.values(videoPlayersRef.current).forEach((controller) => {
|
||||
controller.scrubToTimestamp(currentTime);
|
||||
});
|
||||
}
|
||||
}, [currentTime, scrubbing]);
|
||||
}, [currentTime, scrubbing, timeRange, currentTimeRange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!scrubbing) {
|
||||
@ -99,22 +114,25 @@ export function DesktopRecordingView({
|
||||
|
||||
const onSelectCamera = useCallback(
|
||||
(newCam: string) => {
|
||||
videoPlayersRef.current[mainCamera].onPlayerTimeUpdate(undefined);
|
||||
videoPlayersRef.current[mainCamera].scrubToTimestamp(currentTime);
|
||||
videoPlayersRef.current[newCam].seekToTimestamp(currentTime, true);
|
||||
videoPlayersRef.current[newCam].onPlayerTimeUpdate(
|
||||
(timestamp: number) => {
|
||||
setCurrentTime(timestamp);
|
||||
const lastController = videoPlayersRef.current[mainCamera];
|
||||
const newController = videoPlayersRef.current[newCam];
|
||||
lastController.onPlayerTimeUpdate(undefined);
|
||||
lastController.scrubToTimestamp(currentTime);
|
||||
newController.onCanPlay(() => {
|
||||
newController.seekToTimestamp(currentTime, true);
|
||||
newController.onCanPlay(null);
|
||||
});
|
||||
newController.onPlayerTimeUpdate((timestamp: number) => {
|
||||
setCurrentTime(timestamp);
|
||||
|
||||
allCameras.forEach((cam) => {
|
||||
if (cam != newCam) {
|
||||
videoPlayersRef.current[cam]?.scrubToTimestamp(
|
||||
Math.floor(timestamp),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
allCameras.forEach((cam) => {
|
||||
if (cam != newCam) {
|
||||
videoPlayersRef.current[cam]?.scrubToTimestamp(
|
||||
Math.floor(timestamp),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
setMainCamera(newCam);
|
||||
},
|
||||
[allCameras, currentTime, mainCamera],
|
||||
@ -155,8 +173,9 @@ export function DesktopRecordingView({
|
||||
>
|
||||
<DynamicVideoPlayer
|
||||
camera={cam}
|
||||
timeRange={timeRange.ranges[selectedRangeIdx]}
|
||||
timeRange={currentTimeRange}
|
||||
cameraPreviews={allPreviews ?? []}
|
||||
preloadRecordings
|
||||
onControllerReady={(controller) => {
|
||||
videoPlayersRef.current[cam] = controller;
|
||||
setPlayerReady(true);
|
||||
@ -172,7 +191,10 @@ export function DesktopRecordingView({
|
||||
});
|
||||
});
|
||||
|
||||
controller.seekToTimestamp(startTime, true);
|
||||
controller.onCanPlay(() => {
|
||||
controller.seekToTimestamp(startTime, true);
|
||||
controller.onCanPlay(null);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@ -184,9 +206,10 @@ export function DesktopRecordingView({
|
||||
<DynamicVideoPlayer
|
||||
className="size-full"
|
||||
camera={cam}
|
||||
timeRange={timeRange.ranges[selectedRangeIdx]}
|
||||
timeRange={currentTimeRange}
|
||||
cameraPreviews={allPreviews ?? []}
|
||||
previewOnly
|
||||
preloadRecordings
|
||||
onControllerReady={(controller) => {
|
||||
videoPlayersRef.current[cam] = controller;
|
||||
setPlayerReady(true);
|
||||
@ -265,6 +288,10 @@ export function MobileRecordingView({
|
||||
return chunk.start <= startTime && chunk.end >= startTime;
|
||||
}),
|
||||
);
|
||||
const currentTimeRange = useMemo(
|
||||
() => timeRange.ranges[selectedRangeIdx],
|
||||
[selectedRangeIdx, timeRange],
|
||||
);
|
||||
|
||||
// move to next clip
|
||||
useEffect(() => {
|
||||
@ -273,10 +300,10 @@ export function MobileRecordingView({
|
||||
}
|
||||
|
||||
controllerRef.current.onClipChangedEvent((dir) => {
|
||||
if (dir == "forward" && selectedRangeIdx < timeRange.ranges.length - 1) {
|
||||
setSelectedRangeIdx(selectedRangeIdx + 1);
|
||||
} else if (selectedRangeIdx > 0) {
|
||||
setSelectedRangeIdx(selectedRangeIdx - 1);
|
||||
if (dir == "forward") {
|
||||
if (selectedRangeIdx < timeRange.ranges.length - 1) {
|
||||
setSelectedRangeIdx(selectedRangeIdx + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [playerReady, selectedRangeIdx, timeRange]);
|
||||
@ -290,9 +317,23 @@ export function MobileRecordingView({
|
||||
|
||||
useEffect(() => {
|
||||
if (scrubbing) {
|
||||
if (
|
||||
currentTime > currentTimeRange.end + 60 ||
|
||||
currentTime < currentTimeRange.start - 60
|
||||
) {
|
||||
const index = timeRange.ranges.findIndex(
|
||||
(seg) => seg.start <= currentTime && seg.end >= currentTime,
|
||||
);
|
||||
|
||||
if (index != -1) {
|
||||
setSelectedRangeIdx(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
controllerRef.current?.scrubToTimestamp(currentTime);
|
||||
}
|
||||
}, [currentTime, scrubbing]);
|
||||
}, [currentTime, scrubbing, currentTimeRange, timeRange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!scrubbing) {
|
||||
@ -328,8 +369,9 @@ export function MobileRecordingView({
|
||||
<div>
|
||||
<DynamicVideoPlayer
|
||||
camera={startCamera}
|
||||
timeRange={timeRange.ranges[selectedRangeIdx]}
|
||||
timeRange={currentTimeRange}
|
||||
cameraPreviews={relevantPreviews || []}
|
||||
preloadRecordings
|
||||
onControllerReady={(controller) => {
|
||||
controllerRef.current = controller;
|
||||
setPlayerReady(true);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user