mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-08 20:25:26 +03:00
Implement backward preview jump and jump lockout
This commit is contained in:
parent
5864e6791b
commit
92116dcc6a
@ -18,6 +18,8 @@ import { Recording } from "@/types/record";
|
|||||||
import { Preview } from "@/types/preview";
|
import { Preview } from "@/types/preview";
|
||||||
import { DynamicPlayback } from "@/types/playback";
|
import { DynamicPlayback } from "@/types/playback";
|
||||||
|
|
||||||
|
type PlayerMode = "playback" | "scrubbing";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically switches between video playback and scrubbing preview player.
|
* Dynamically switches between video playback and scrubbing preview player.
|
||||||
*/
|
*/
|
||||||
@ -26,6 +28,7 @@ type DynamicVideoPlayerProps = {
|
|||||||
camera: string;
|
camera: string;
|
||||||
timeRange: { start: number; end: number };
|
timeRange: { start: number; end: number };
|
||||||
cameraPreviews: Preview[];
|
cameraPreviews: Preview[];
|
||||||
|
defaultMode?: PlayerMode;
|
||||||
onControllerReady?: (controller: DynamicVideoController) => void;
|
onControllerReady?: (controller: DynamicVideoController) => void;
|
||||||
};
|
};
|
||||||
export default function DynamicVideoPlayer({
|
export default function DynamicVideoPlayer({
|
||||||
@ -33,6 +36,7 @@ export default function DynamicVideoPlayer({
|
|||||||
camera,
|
camera,
|
||||||
timeRange,
|
timeRange,
|
||||||
cameraPreviews,
|
cameraPreviews,
|
||||||
|
defaultMode = "playback",
|
||||||
onControllerReady,
|
onControllerReady,
|
||||||
}: DynamicVideoPlayerProps) {
|
}: DynamicVideoPlayerProps) {
|
||||||
const apiHost = useApiHost();
|
const apiHost = useApiHost();
|
||||||
@ -60,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(false);
|
const [isScrubbing, setIsScrubbing] = useState(defaultMode == "scrubbing");
|
||||||
const [hasPreview, setHasPreview] = useState(false);
|
const [hasPreview, setHasPreview] = useState(false);
|
||||||
const [focusedItem, setFocusedItem] = useState<Timeline | undefined>(
|
const [focusedItem, setFocusedItem] = useState<Timeline | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
@ -74,10 +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,
|
||||||
setIsScrubbing,
|
setIsScrubbing,
|
||||||
setFocusedItem,
|
setFocusedItem,
|
||||||
);
|
);
|
||||||
}, [camera, config]);
|
}, [camera, config, defaultMode]);
|
||||||
|
|
||||||
// keyboard control
|
// keyboard control
|
||||||
|
|
||||||
@ -178,7 +183,7 @@ export default function DynamicVideoPlayer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!controller || !recordings || recordings.length == 0) {
|
if (!controller || !recordings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +297,7 @@ export class DynamicVideoController {
|
|||||||
private previewRef: MutableRefObject<Player | undefined>;
|
private previewRef: MutableRefObject<Player | undefined>;
|
||||||
private setScrubbing: (isScrubbing: boolean) => void;
|
private setScrubbing: (isScrubbing: boolean) => void;
|
||||||
private setFocusedItem: (timeline: Timeline) => void;
|
private setFocusedItem: (timeline: Timeline) => void;
|
||||||
private playerMode: "playback" | "scrubbing" = "playback";
|
private playerMode: PlayerMode = "playback";
|
||||||
|
|
||||||
// playback
|
// playback
|
||||||
private recordings: Recording[] = [];
|
private recordings: Recording[] = [];
|
||||||
@ -301,6 +306,7 @@ export class DynamicVideoController {
|
|||||||
undefined;
|
undefined;
|
||||||
private annotationOffset: number;
|
private annotationOffset: number;
|
||||||
private timeToStart: number | undefined = undefined;
|
private timeToStart: number | undefined = undefined;
|
||||||
|
private clipChangeLockout: boolean = false;
|
||||||
|
|
||||||
// preview
|
// preview
|
||||||
private preview: Preview | undefined = undefined;
|
private preview: Preview | undefined = undefined;
|
||||||
@ -312,12 +318,14 @@ export class DynamicVideoController {
|
|||||||
playerRef: MutableRefObject<Player | undefined>,
|
playerRef: MutableRefObject<Player | undefined>,
|
||||||
previewRef: MutableRefObject<Player | undefined>,
|
previewRef: MutableRefObject<Player | undefined>,
|
||||||
annotationOffset: number,
|
annotationOffset: number,
|
||||||
|
defaultMode: PlayerMode,
|
||||||
setScrubbing: (isScrubbing: boolean) => void,
|
setScrubbing: (isScrubbing: boolean) => void,
|
||||||
setFocusedItem: (timeline: Timeline) => void,
|
setFocusedItem: (timeline: Timeline) => void,
|
||||||
) {
|
) {
|
||||||
this.playerRef = playerRef;
|
this.playerRef = playerRef;
|
||||||
this.previewRef = previewRef;
|
this.previewRef = previewRef;
|
||||||
this.annotationOffset = annotationOffset;
|
this.annotationOffset = annotationOffset;
|
||||||
|
this.playerMode = defaultMode;
|
||||||
this.setScrubbing = setScrubbing;
|
this.setScrubbing = setScrubbing;
|
||||||
this.setFocusedItem = setFocusedItem;
|
this.setFocusedItem = setFocusedItem;
|
||||||
}
|
}
|
||||||
@ -429,17 +437,39 @@ export class DynamicVideoController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (time > this.preview.end) {
|
if (time > this.preview.end) {
|
||||||
|
if (this.clipChangeLockout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.playerMode == "scrubbing") {
|
if (this.playerMode == "scrubbing") {
|
||||||
this.playerMode = "playback";
|
this.playerMode = "playback";
|
||||||
this.setScrubbing(false);
|
this.setScrubbing(false);
|
||||||
this.timeToSeek = undefined;
|
this.timeToSeek = undefined;
|
||||||
this.seeking = false;
|
this.seeking = false;
|
||||||
this.readyToScrub = false;
|
this.readyToScrub = false;
|
||||||
|
this.clipChangeLockout = true;
|
||||||
this.fireClipChangeEvent("forward");
|
this.fireClipChangeEvent("forward");
|
||||||
}
|
}
|
||||||
return;
|
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") {
|
if (this.playerMode != "scrubbing") {
|
||||||
this.playerMode = "scrubbing";
|
this.playerMode = "scrubbing";
|
||||||
this.playerRef.current?.pause();
|
this.playerRef.current?.pause();
|
||||||
@ -461,6 +491,8 @@ export class DynamicVideoController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.clipChangeLockout = false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.timeToSeek &&
|
this.timeToSeek &&
|
||||||
this.timeToSeek != this.previewRef.current?.currentTime()
|
this.timeToSeek != this.previewRef.current?.currentTime()
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export default function VideoPlayer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-vjs-player>
|
<div data-vjs-player>
|
||||||
<div ref={videoRef} />
|
<div className="rounded-2xl overflow-hidden" ref={videoRef} />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -533,9 +533,7 @@ function MotionReview({
|
|||||||
|
|
||||||
let cameras;
|
let cameras;
|
||||||
if (!filter || !filter.cameras) {
|
if (!filter || !filter.cameras) {
|
||||||
cameras = Object.values(config.cameras).filter(
|
cameras = Object.values(config.cameras);
|
||||||
(cam) => cam.name == "front_cam",
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
const filteredCams = filter.cameras;
|
const filteredCams = filter.cameras;
|
||||||
|
|
||||||
@ -569,6 +567,26 @@ function MotionReview({
|
|||||||
timeRangeSegments.ranges[selectedRangeIdx].start,
|
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(() => {
|
useEffect(() => {
|
||||||
Object.values(videoPlayersRef.current).forEach((controller) => {
|
Object.values(videoPlayersRef.current).forEach((controller) => {
|
||||||
controller.scrubToTimestamp(currentTime);
|
controller.scrubToTimestamp(currentTime);
|
||||||
@ -579,7 +597,7 @@ function MotionReview({
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
ref={contentRef}
|
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) => {
|
{reviewCameras.map((camera) => {
|
||||||
let grow;
|
let grow;
|
||||||
@ -597,9 +615,9 @@ function MotionReview({
|
|||||||
camera={camera.name}
|
camera={camera.name}
|
||||||
timeRange={timeRangeSegments.ranges[selectedRangeIdx]}
|
timeRange={timeRangeSegments.ranges[selectedRangeIdx]}
|
||||||
cameraPreviews={relevantPreviews || []}
|
cameraPreviews={relevantPreviews || []}
|
||||||
|
defaultMode="scrubbing"
|
||||||
onControllerReady={(controller) => {
|
onControllerReady={(controller) => {
|
||||||
videoPlayersRef.current[camera.name] = controller;
|
videoPlayersRef.current[camera.name] = controller;
|
||||||
//setPlayerReady(true);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user