mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-10 05:05:26 +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 };
|
timeRange: { start: number; end: number };
|
||||||
cameraPreviews: Preview[];
|
cameraPreviews: Preview[];
|
||||||
previewOnly?: boolean;
|
previewOnly?: boolean;
|
||||||
|
preloadRecordings: boolean;
|
||||||
onControllerReady: (controller: DynamicVideoController) => void;
|
onControllerReady: (controller: DynamicVideoController) => void;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
@ -37,6 +38,7 @@ export default function DynamicVideoPlayer({
|
|||||||
timeRange,
|
timeRange,
|
||||||
cameraPreviews,
|
cameraPreviews,
|
||||||
previewOnly = false,
|
previewOnly = false,
|
||||||
|
preloadRecordings = true,
|
||||||
onControllerReady,
|
onControllerReady,
|
||||||
onClick,
|
onClick,
|
||||||
}: DynamicVideoPlayerProps) {
|
}: DynamicVideoPlayerProps) {
|
||||||
@ -100,18 +102,26 @@ export default function DynamicVideoPlayer({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [controller]);
|
}, [controller]);
|
||||||
|
|
||||||
|
const [initPreviewOnly, setInitPreviewOnly] = useState(previewOnly);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!controller || !playerRef) {
|
if (!controller || !playerRef) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (previewOnly == initPreviewOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (previewOnly) {
|
if (previewOnly) {
|
||||||
playerRef.autoplay(false);
|
playerRef.autoplay(false);
|
||||||
controller.removePlayerListeners();
|
controller.removePlayerListeners();
|
||||||
} else {
|
} else {
|
||||||
controller.setPlayerListeners();
|
controller.setPlayerListeners();
|
||||||
playerRef.play();
|
controller.seekToTimestamp(playerRef.currentTime() || 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInitPreviewOnly(previewOnly);
|
||||||
// we only want to fire once when players are ready
|
// we only want to fire once when players are ready
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [controller, previewOnly]);
|
}, [controller, previewOnly]);
|
||||||
@ -266,6 +276,7 @@ export default function DynamicVideoPlayer({
|
|||||||
className={`relative ${className ?? ""} ${onClick ? (hasRecordingAtTime ? "cursor-pointer" : "") : ""}`}
|
className={`relative ${className ?? ""} ${onClick ? (hasRecordingAtTime ? "cursor-pointer" : "") : ""}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
|
{preloadRecordings && (
|
||||||
<div
|
<div
|
||||||
className={`w-full relative ${
|
className={`w-full relative ${
|
||||||
previewOnly || (currentPreview != undefined && isScrubbing)
|
previewOnly || (currentPreview != undefined && isScrubbing)
|
||||||
@ -302,6 +313,7 @@ export default function DynamicVideoPlayer({
|
|||||||
)}
|
)}
|
||||||
</VideoPlayer>
|
</VideoPlayer>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<video
|
<video
|
||||||
ref={previewRef}
|
ref={previewRef}
|
||||||
className={`size-full rounded-2xl ${currentPreview != undefined && (previewOnly || isScrubbing) ? "visible" : "hidden"} ${tallVideo ? "aspect-tall" : ""} bg-black`}
|
className={`size-full rounded-2xl ${currentPreview != undefined && (previewOnly || isScrubbing) ? "visible" : "hidden"} ${tallVideo ? "aspect-tall" : ""} bg-black`}
|
||||||
|
|||||||
@ -659,6 +659,7 @@ function MotionReview({
|
|||||||
timeRange={currentTimeRange}
|
timeRange={currentTimeRange}
|
||||||
cameraPreviews={relevantPreviews || []}
|
cameraPreviews={relevantPreviews || []}
|
||||||
previewOnly
|
previewOnly
|
||||||
|
preloadRecordings={false}
|
||||||
onControllerReady={(controller) => {
|
onControllerReady={(controller) => {
|
||||||
videoPlayersRef.current[camera.name] = controller;
|
videoPlayersRef.current[camera.name] = controller;
|
||||||
setPlayerReady(true);
|
setPlayerReady(true);
|
||||||
|
|||||||
@ -49,6 +49,10 @@ export function DesktopRecordingView({
|
|||||||
return chunk.start <= startTime && chunk.end >= startTime;
|
return chunk.start <= startTime && chunk.end >= startTime;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const currentTimeRange = useMemo(
|
||||||
|
() => timeRange.ranges[selectedRangeIdx],
|
||||||
|
[selectedRangeIdx, timeRange],
|
||||||
|
);
|
||||||
|
|
||||||
// move to next clip
|
// move to next clip
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -59,21 +63,18 @@ export function DesktopRecordingView({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstController = Object.values(videoPlayersRef.current)[0];
|
const mainController = videoPlayersRef.current[mainCamera];
|
||||||
|
|
||||||
if (firstController) {
|
if (mainController) {
|
||||||
firstController.onClipChangedEvent((dir) => {
|
mainController.onClipChangedEvent((dir) => {
|
||||||
if (
|
if (dir == "forward") {
|
||||||
dir == "forward" &&
|
if (selectedRangeIdx < timeRange.ranges.length - 1) {
|
||||||
selectedRangeIdx < timeRange.ranges.length - 1
|
|
||||||
) {
|
|
||||||
setSelectedRangeIdx(selectedRangeIdx + 1);
|
setSelectedRangeIdx(selectedRangeIdx + 1);
|
||||||
} else if (selectedRangeIdx > 0) {
|
}
|
||||||
setSelectedRangeIdx(selectedRangeIdx - 1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [selectedRangeIdx, timeRange, videoPlayersRef, playerReady]);
|
}, [selectedRangeIdx, timeRange, videoPlayersRef, playerReady, mainCamera]);
|
||||||
|
|
||||||
// scrubbing and timeline state
|
// scrubbing and timeline state
|
||||||
|
|
||||||
@ -82,11 +83,25 @@ export function DesktopRecordingView({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrubbing) {
|
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) => {
|
Object.values(videoPlayersRef.current).forEach((controller) => {
|
||||||
controller.scrubToTimestamp(currentTime);
|
controller.scrubToTimestamp(currentTime);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [currentTime, scrubbing]);
|
}, [currentTime, scrubbing, timeRange, currentTimeRange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!scrubbing) {
|
if (!scrubbing) {
|
||||||
@ -99,11 +114,15 @@ export function DesktopRecordingView({
|
|||||||
|
|
||||||
const onSelectCamera = useCallback(
|
const onSelectCamera = useCallback(
|
||||||
(newCam: string) => {
|
(newCam: string) => {
|
||||||
videoPlayersRef.current[mainCamera].onPlayerTimeUpdate(undefined);
|
const lastController = videoPlayersRef.current[mainCamera];
|
||||||
videoPlayersRef.current[mainCamera].scrubToTimestamp(currentTime);
|
const newController = videoPlayersRef.current[newCam];
|
||||||
videoPlayersRef.current[newCam].seekToTimestamp(currentTime, true);
|
lastController.onPlayerTimeUpdate(undefined);
|
||||||
videoPlayersRef.current[newCam].onPlayerTimeUpdate(
|
lastController.scrubToTimestamp(currentTime);
|
||||||
(timestamp: number) => {
|
newController.onCanPlay(() => {
|
||||||
|
newController.seekToTimestamp(currentTime, true);
|
||||||
|
newController.onCanPlay(null);
|
||||||
|
});
|
||||||
|
newController.onPlayerTimeUpdate((timestamp: number) => {
|
||||||
setCurrentTime(timestamp);
|
setCurrentTime(timestamp);
|
||||||
|
|
||||||
allCameras.forEach((cam) => {
|
allCameras.forEach((cam) => {
|
||||||
@ -113,8 +132,7 @@ export function DesktopRecordingView({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
});
|
||||||
);
|
|
||||||
setMainCamera(newCam);
|
setMainCamera(newCam);
|
||||||
},
|
},
|
||||||
[allCameras, currentTime, mainCamera],
|
[allCameras, currentTime, mainCamera],
|
||||||
@ -155,8 +173,9 @@ export function DesktopRecordingView({
|
|||||||
>
|
>
|
||||||
<DynamicVideoPlayer
|
<DynamicVideoPlayer
|
||||||
camera={cam}
|
camera={cam}
|
||||||
timeRange={timeRange.ranges[selectedRangeIdx]}
|
timeRange={currentTimeRange}
|
||||||
cameraPreviews={allPreviews ?? []}
|
cameraPreviews={allPreviews ?? []}
|
||||||
|
preloadRecordings
|
||||||
onControllerReady={(controller) => {
|
onControllerReady={(controller) => {
|
||||||
videoPlayersRef.current[cam] = controller;
|
videoPlayersRef.current[cam] = controller;
|
||||||
setPlayerReady(true);
|
setPlayerReady(true);
|
||||||
@ -172,7 +191,10 @@ export function DesktopRecordingView({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
controller.onCanPlay(() => {
|
||||||
controller.seekToTimestamp(startTime, true);
|
controller.seekToTimestamp(startTime, true);
|
||||||
|
controller.onCanPlay(null);
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -184,9 +206,10 @@ export function DesktopRecordingView({
|
|||||||
<DynamicVideoPlayer
|
<DynamicVideoPlayer
|
||||||
className="size-full"
|
className="size-full"
|
||||||
camera={cam}
|
camera={cam}
|
||||||
timeRange={timeRange.ranges[selectedRangeIdx]}
|
timeRange={currentTimeRange}
|
||||||
cameraPreviews={allPreviews ?? []}
|
cameraPreviews={allPreviews ?? []}
|
||||||
previewOnly
|
previewOnly
|
||||||
|
preloadRecordings
|
||||||
onControllerReady={(controller) => {
|
onControllerReady={(controller) => {
|
||||||
videoPlayersRef.current[cam] = controller;
|
videoPlayersRef.current[cam] = controller;
|
||||||
setPlayerReady(true);
|
setPlayerReady(true);
|
||||||
@ -265,6 +288,10 @@ export function MobileRecordingView({
|
|||||||
return chunk.start <= startTime && chunk.end >= startTime;
|
return chunk.start <= startTime && chunk.end >= startTime;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
const currentTimeRange = useMemo(
|
||||||
|
() => timeRange.ranges[selectedRangeIdx],
|
||||||
|
[selectedRangeIdx, timeRange],
|
||||||
|
);
|
||||||
|
|
||||||
// move to next clip
|
// move to next clip
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -273,10 +300,10 @@ export function MobileRecordingView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
controllerRef.current.onClipChangedEvent((dir) => {
|
controllerRef.current.onClipChangedEvent((dir) => {
|
||||||
if (dir == "forward" && selectedRangeIdx < timeRange.ranges.length - 1) {
|
if (dir == "forward") {
|
||||||
|
if (selectedRangeIdx < timeRange.ranges.length - 1) {
|
||||||
setSelectedRangeIdx(selectedRangeIdx + 1);
|
setSelectedRangeIdx(selectedRangeIdx + 1);
|
||||||
} else if (selectedRangeIdx > 0) {
|
}
|
||||||
setSelectedRangeIdx(selectedRangeIdx - 1);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [playerReady, selectedRangeIdx, timeRange]);
|
}, [playerReady, selectedRangeIdx, timeRange]);
|
||||||
@ -290,9 +317,23 @@ export function MobileRecordingView({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (scrubbing) {
|
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);
|
controllerRef.current?.scrubToTimestamp(currentTime);
|
||||||
}
|
}
|
||||||
}, [currentTime, scrubbing]);
|
}, [currentTime, scrubbing, currentTimeRange, timeRange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!scrubbing) {
|
if (!scrubbing) {
|
||||||
@ -328,8 +369,9 @@ export function MobileRecordingView({
|
|||||||
<div>
|
<div>
|
||||||
<DynamicVideoPlayer
|
<DynamicVideoPlayer
|
||||||
camera={startCamera}
|
camera={startCamera}
|
||||||
timeRange={timeRange.ranges[selectedRangeIdx]}
|
timeRange={currentTimeRange}
|
||||||
cameraPreviews={relevantPreviews || []}
|
cameraPreviews={relevantPreviews || []}
|
||||||
|
preloadRecordings
|
||||||
onControllerReady={(controller) => {
|
onControllerReady={(controller) => {
|
||||||
controllerRef.current = controller;
|
controllerRef.current = controller;
|
||||||
setPlayerReady(true);
|
setPlayerReady(true);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user