Implement backward preview jump and jump lockout

This commit is contained in:
Nicolas Mowen 2024-03-03 14:25:02 -07:00
parent 5864e6791b
commit 92116dcc6a
3 changed files with 60 additions and 10 deletions

View File

@ -18,6 +18,8 @@ import { Recording } from "@/types/record";
import { Preview } from "@/types/preview";
import { DynamicPlayback } from "@/types/playback";
type PlayerMode = "playback" | "scrubbing";
/**
* Dynamically switches between video playback and scrubbing preview player.
*/
@ -26,6 +28,7 @@ type DynamicVideoPlayerProps = {
camera: string;
timeRange: { start: number; end: number };
cameraPreviews: Preview[];
defaultMode?: PlayerMode;
onControllerReady?: (controller: DynamicVideoController) => void;
};
export default function DynamicVideoPlayer({
@ -33,6 +36,7 @@ export default function DynamicVideoPlayer({
camera,
timeRange,
cameraPreviews,
defaultMode = "playback",
onControllerReady,
}: DynamicVideoPlayerProps) {
const apiHost = useApiHost();
@ -60,7 +64,7 @@ export default function DynamicVideoPlayer({
const playerRef = useRef<Player | undefined>(undefined);
const previewRef = useRef<Player | undefined>(undefined);
const [isScrubbing, setIsScrubbing] = useState(false);
const [isScrubbing, setIsScrubbing] = useState(defaultMode == "scrubbing");
const [hasPreview, setHasPreview] = useState(false);
const [focusedItem, setFocusedItem] = useState<Timeline | undefined>(
undefined,
@ -74,10 +78,11 @@ export default function DynamicVideoPlayer({
playerRef,
previewRef,
(config.cameras[camera]?.detect?.annotation_offset || 0) / 1000,
defaultMode,
setIsScrubbing,
setFocusedItem,
);
}, [camera, config]);
}, [camera, config, defaultMode]);
// keyboard control
@ -178,7 +183,7 @@ export default function DynamicVideoPlayer({
);
useEffect(() => {
if (!controller || !recordings || recordings.length == 0) {
if (!controller || !recordings) {
return;
}
@ -292,7 +297,7 @@ export class DynamicVideoController {
private previewRef: MutableRefObject<Player | undefined>;
private setScrubbing: (isScrubbing: boolean) => void;
private setFocusedItem: (timeline: Timeline) => void;
private playerMode: "playback" | "scrubbing" = "playback";
private playerMode: PlayerMode = "playback";
// playback
private recordings: Recording[] = [];
@ -301,6 +306,7 @@ export class DynamicVideoController {
undefined;
private annotationOffset: number;
private timeToStart: number | undefined = undefined;
private clipChangeLockout: boolean = false;
// preview
private preview: Preview | undefined = undefined;
@ -312,12 +318,14 @@ export class DynamicVideoController {
playerRef: MutableRefObject<Player | undefined>,
previewRef: MutableRefObject<Player | undefined>,
annotationOffset: number,
defaultMode: PlayerMode,
setScrubbing: (isScrubbing: boolean) => void,
setFocusedItem: (timeline: Timeline) => void,
) {
this.playerRef = playerRef;
this.previewRef = previewRef;
this.annotationOffset = annotationOffset;
this.playerMode = defaultMode;
this.setScrubbing = setScrubbing;
this.setFocusedItem = setFocusedItem;
}
@ -429,17 +437,39 @@ export class DynamicVideoController {
}
if (time > this.preview.end) {
if (this.clipChangeLockout) {
return;
}
if (this.playerMode == "scrubbing") {
this.playerMode = "playback";
this.setScrubbing(false);
this.timeToSeek = undefined;
this.seeking = false;
this.readyToScrub = false;
this.clipChangeLockout = true;
this.fireClipChangeEvent("forward");
}
return;
}
if (time < this.preview.start) {
if (this.clipChangeLockout) {
return;
}
if (this.playerMode == "scrubbing") {
this.playerMode = "playback";
this.setScrubbing(false);
this.timeToSeek = undefined;
this.seeking = false;
this.readyToScrub = false;
this.clipChangeLockout = true;
this.fireClipChangeEvent("backward");
}
return;
}
if (this.playerMode != "scrubbing") {
this.playerMode = "scrubbing";
this.playerRef.current?.pause();
@ -461,6 +491,8 @@ export class DynamicVideoController {
return;
}
this.clipChangeLockout = false;
if (
this.timeToSeek &&
this.timeToSeek != this.previewRef.current?.currentTime()

View File

@ -88,7 +88,7 @@ export default function VideoPlayer({
return (
<div data-vjs-player>
<div ref={videoRef} />
<div className="rounded-2xl overflow-hidden" ref={videoRef} />
{children}
</div>
);

View File

@ -533,9 +533,7 @@ function MotionReview({
let cameras;
if (!filter || !filter.cameras) {
cameras = Object.values(config.cameras).filter(
(cam) => cam.name == "front_cam",
);
cameras = Object.values(config.cameras);
} else {
const filteredCams = filter.cameras;
@ -569,6 +567,26 @@ function MotionReview({
timeRangeSegments.ranges[selectedRangeIdx].start,
);
// move to next clip
useEffect(() => {
if (!videoPlayersRef.current) {
return;
}
Object.values(videoPlayersRef.current).forEach((controller) => {
controller.onClipChangedEvent((dir) => {
if (
dir == "forward" &&
selectedRangeIdx < timeRangeSegments.ranges.length - 1
) {
setSelectedRangeIdx(selectedRangeIdx + 1);
} else if (selectedRangeIdx > 0) {
setSelectedRangeIdx(selectedRangeIdx - 1);
}
});
});
}, [selectedRangeIdx, timeRangeSegments]);
useEffect(() => {
Object.values(videoPlayersRef.current).forEach((controller) => {
controller.scrubToTimestamp(currentTime);
@ -579,7 +597,7 @@ function MotionReview({
<>
<div
ref={contentRef}
className={`size-full mt-4 grid sm:grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4 gap-2 md:gap-4`}
className={`size-full m-2 grid sm:grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4 gap-2 overflow-auto`}
>
{reviewCameras.map((camera) => {
let grow;
@ -597,9 +615,9 @@ function MotionReview({
camera={camera.name}
timeRange={timeRangeSegments.ranges[selectedRangeIdx]}
cameraPreviews={relevantPreviews || []}
defaultMode="scrubbing"
onControllerReady={(controller) => {
videoPlayersRef.current[camera.name] = controller;
//setPlayerReady(true);
}}
/>
</div>