Simplify switching between cameras

This commit is contained in:
Nicolas Mowen 2024-03-07 14:26:20 -07:00
parent 509211b440
commit 929a1b4b23
3 changed files with 127 additions and 72 deletions

View File

@ -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`}

View File

@ -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);

View File

@ -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);