mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-09 04:35:25 +03:00
Update to get preview player working
This commit is contained in:
parent
0b8859c76c
commit
f01bc18196
@ -186,7 +186,7 @@ class PreviewRecorder:
|
|||||||
os.unlink(os.path.join(PREVIEW_CACHE_DIR, file))
|
os.unlink(os.path.join(PREVIEW_CACHE_DIR, file))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ts = float(file.split("-")[1][:-4])
|
ts = float(file.split("-")[1][: -(len(PREVIEW_FRAME_TYPE) + 1)])
|
||||||
|
|
||||||
if self.start_time == 0:
|
if self.start_time == 0:
|
||||||
self.start_time = ts
|
self.start_time = ts
|
||||||
@ -242,7 +242,11 @@ class PreviewRecorder:
|
|||||||
small_frame,
|
small_frame,
|
||||||
cv2.COLOR_YUV2BGR_I420,
|
cv2.COLOR_YUV2BGR_I420,
|
||||||
)
|
)
|
||||||
cv2.imwrite(get_cache_image_name(self.config.name, frame_time), small_frame, [int(cv2.IMWRITE_WEBP_QUALITY), 80])
|
cv2.imwrite(
|
||||||
|
get_cache_image_name(self.config.name, frame_time),
|
||||||
|
small_frame,
|
||||||
|
[int(cv2.IMWRITE_WEBP_QUALITY), 80],
|
||||||
|
)
|
||||||
|
|
||||||
def write_data(
|
def write_data(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@ -9,9 +9,7 @@ import useKeyboardListener from "@/hooks/use-keyboard-listener";
|
|||||||
import { Recording } from "@/types/record";
|
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";
|
||||||
import PreviewVideoPlayer, {
|
import PreviewPlayer, { PreviewController } from "./PreviewPlayer";
|
||||||
PreviewVideoController,
|
|
||||||
} from "./PreviewVideoPlayer";
|
|
||||||
|
|
||||||
type PlayerMode = "playback" | "scrubbing";
|
type PlayerMode = "playback" | "scrubbing";
|
||||||
|
|
||||||
@ -63,7 +61,7 @@ export default function DynamicVideoPlayer({
|
|||||||
|
|
||||||
const [playerRef, setPlayerRef] = useState<Player | null>(null);
|
const [playerRef, setPlayerRef] = useState<Player | null>(null);
|
||||||
const [previewController, setPreviewController] =
|
const [previewController, setPreviewController] =
|
||||||
useState<PreviewVideoController | null>(null);
|
useState<PreviewController | null>(null);
|
||||||
const [isScrubbing, setIsScrubbing] = useState(previewOnly);
|
const [isScrubbing, setIsScrubbing] = useState(previewOnly);
|
||||||
const [focusedItem, setFocusedItem] = useState<Timeline | undefined>(
|
const [focusedItem, setFocusedItem] = useState<Timeline | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
@ -280,7 +278,7 @@ export default function DynamicVideoPlayer({
|
|||||||
)}
|
)}
|
||||||
</VideoPlayer>
|
</VideoPlayer>
|
||||||
</div>
|
</div>
|
||||||
<PreviewVideoPlayer
|
<PreviewPlayer
|
||||||
className={`${isScrubbing ? "visible" : "hidden"} ${className ?? ""}`}
|
className={`${isScrubbing ? "visible" : "hidden"} ${className ?? ""}`}
|
||||||
camera={camera}
|
camera={camera}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
@ -298,7 +296,7 @@ export class DynamicVideoController {
|
|||||||
// main state
|
// main state
|
||||||
public camera = "";
|
public camera = "";
|
||||||
private playerController: Player;
|
private playerController: Player;
|
||||||
private previewController: PreviewVideoController;
|
private previewController: PreviewController;
|
||||||
private setScrubbing: (isScrubbing: boolean) => void;
|
private setScrubbing: (isScrubbing: boolean) => void;
|
||||||
private setFocusedItem: (timeline: Timeline) => void;
|
private setFocusedItem: (timeline: Timeline) => void;
|
||||||
private playerMode: PlayerMode = "playback";
|
private playerMode: PlayerMode = "playback";
|
||||||
@ -315,7 +313,7 @@ export class DynamicVideoController {
|
|||||||
constructor(
|
constructor(
|
||||||
camera: string,
|
camera: string,
|
||||||
playerController: Player,
|
playerController: Player,
|
||||||
previewController: PreviewVideoController,
|
previewController: PreviewController,
|
||||||
annotationOffset: number,
|
annotationOffset: number,
|
||||||
defaultMode: PlayerMode,
|
defaultMode: PlayerMode,
|
||||||
setScrubbing: (isScrubbing: boolean) => void,
|
setScrubbing: (isScrubbing: boolean) => void,
|
||||||
@ -438,7 +436,7 @@ export class DynamicVideoController {
|
|||||||
const scrubResult = this.previewController.scrubToTimestamp(time);
|
const scrubResult = this.previewController.scrubToTimestamp(time);
|
||||||
|
|
||||||
if (!scrubResult && saveIfNotReady) {
|
if (!scrubResult && saveIfNotReady) {
|
||||||
this.previewController.setNewPreviewStartTime(time);
|
//this.previewController.setNewPreviewStartTime(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrubResult && this.playerMode != "scrubbing") {
|
if (scrubResult && this.playerMode != "scrubbing") {
|
||||||
|
|||||||
458
web/src/components/player/PreviewPlayer.tsx
Normal file
458
web/src/components/player/PreviewPlayer.tsx
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
import {
|
||||||
|
MutableRefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
|
import { Preview } from "@/types/preview";
|
||||||
|
import { PreviewPlayback } from "@/types/playback";
|
||||||
|
import { isCurrentHour } from "@/utils/dateUtil";
|
||||||
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
|
|
||||||
|
type PreviewPlayerProps = {
|
||||||
|
className?: string;
|
||||||
|
camera: string;
|
||||||
|
timeRange: { start: number; end: number };
|
||||||
|
cameraPreviews: Preview[];
|
||||||
|
startTime?: number;
|
||||||
|
onControllerReady: (controller: PreviewController) => void;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
export default function PreviewPlayer({
|
||||||
|
className,
|
||||||
|
camera,
|
||||||
|
timeRange,
|
||||||
|
cameraPreviews,
|
||||||
|
startTime,
|
||||||
|
onControllerReady,
|
||||||
|
onClick,
|
||||||
|
}: PreviewPlayerProps) {
|
||||||
|
if (isCurrentHour(timeRange.end)) {
|
||||||
|
return (
|
||||||
|
<PreviewFramesPlayer
|
||||||
|
className={className}
|
||||||
|
camera={camera}
|
||||||
|
timeRange={timeRange}
|
||||||
|
startTime={startTime}
|
||||||
|
onControllerReady={onControllerReady}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PreviewVideoPlayer
|
||||||
|
className={className}
|
||||||
|
camera={camera}
|
||||||
|
timeRange={timeRange}
|
||||||
|
cameraPreviews={cameraPreviews}
|
||||||
|
startTime={startTime}
|
||||||
|
onControllerReady={onControllerReady}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class PreviewController {
|
||||||
|
public camera = "";
|
||||||
|
|
||||||
|
constructor(camera: string) {
|
||||||
|
this.camera = camera;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract scrubToTimestamp(time: number): boolean;
|
||||||
|
|
||||||
|
abstract finishedSeeking(): void;
|
||||||
|
|
||||||
|
abstract setNewPreviewStartTime(time: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewVideoPlayerProps = {
|
||||||
|
className?: string;
|
||||||
|
camera: string;
|
||||||
|
timeRange: { start: number; end: number };
|
||||||
|
cameraPreviews: Preview[];
|
||||||
|
startTime?: number;
|
||||||
|
onControllerReady: (controller: PreviewVideoController) => void;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
function PreviewVideoPlayer({
|
||||||
|
className,
|
||||||
|
camera,
|
||||||
|
timeRange,
|
||||||
|
cameraPreviews,
|
||||||
|
startTime,
|
||||||
|
onControllerReady,
|
||||||
|
onClick,
|
||||||
|
}: PreviewVideoPlayerProps) {
|
||||||
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
|
// controlling playback
|
||||||
|
|
||||||
|
const previewRef = useRef<HTMLVideoElement | null>(null);
|
||||||
|
const controller = useMemo(() => {
|
||||||
|
if (!config || !previewRef.current) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PreviewVideoController(camera, previewRef);
|
||||||
|
// we only care when preview is ready
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [camera, config, previewRef.current]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller) {
|
||||||
|
onControllerReady(controller);
|
||||||
|
}
|
||||||
|
// we only want to fire once when players are ready
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
|
||||||
|
const initialPreview = useMemo(() => {
|
||||||
|
return cameraPreviews.find(
|
||||||
|
(preview) =>
|
||||||
|
preview.camera == camera &&
|
||||||
|
Math.round(preview.start) >= timeRange.start &&
|
||||||
|
Math.floor(preview.end) <= timeRange.end,
|
||||||
|
);
|
||||||
|
|
||||||
|
// we only want to calculate this once
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [currentPreview, setCurrentPreview] = useState(initialPreview);
|
||||||
|
|
||||||
|
const onPreviewSeeked = useCallback(() => {
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.finishedSeeking();
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const preview = cameraPreviews.find(
|
||||||
|
(preview) =>
|
||||||
|
preview.camera == camera &&
|
||||||
|
Math.round(preview.start) >= timeRange.start &&
|
||||||
|
Math.floor(preview.end) <= timeRange.end,
|
||||||
|
);
|
||||||
|
setCurrentPreview(preview);
|
||||||
|
|
||||||
|
controller.newPlayback({
|
||||||
|
preview,
|
||||||
|
timeRange,
|
||||||
|
});
|
||||||
|
|
||||||
|
// we only want this to change when recordings update
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [controller, timeRange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentPreview || !previewRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewRef.current.load();
|
||||||
|
}, [currentPreview, previewRef]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative w-full ${className ?? ""} ${onClick ? "cursor-pointer" : ""}`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
ref={previewRef}
|
||||||
|
className={`size-full rounded-2xl bg-black`}
|
||||||
|
preload="auto"
|
||||||
|
autoPlay
|
||||||
|
playsInline
|
||||||
|
muted
|
||||||
|
disableRemotePlayback
|
||||||
|
onSeeked={onPreviewSeeked}
|
||||||
|
onLoadedData={() => {
|
||||||
|
if (controller) {
|
||||||
|
controller.previewReady();
|
||||||
|
} else {
|
||||||
|
previewRef.current?.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previewRef.current && startTime && currentPreview) {
|
||||||
|
previewRef.current.currentTime = startTime - currentPreview.start;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentPreview != undefined && (
|
||||||
|
<source src={currentPreview.src} type={currentPreview.type} />
|
||||||
|
)}
|
||||||
|
</video>
|
||||||
|
{cameraPreviews && !currentPreview && (
|
||||||
|
<div className="absolute inset-x-0 top-1/2 -y-translate-1/2 bg-black text-white rounded-2xl align-center text-center">
|
||||||
|
No Preview Found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PreviewVideoController extends PreviewController {
|
||||||
|
// main state
|
||||||
|
private previewRef: MutableRefObject<HTMLVideoElement | null>;
|
||||||
|
private timeRange: { start: number; end: number } | undefined = undefined;
|
||||||
|
|
||||||
|
// preview
|
||||||
|
private preview: Preview | undefined = undefined;
|
||||||
|
private timeToSeek: number | undefined = undefined;
|
||||||
|
private seeking = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
camera: string,
|
||||||
|
previewRef: MutableRefObject<HTMLVideoElement | null>,
|
||||||
|
) {
|
||||||
|
super(camera);
|
||||||
|
this.previewRef = previewRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
newPlayback(newPlayback: PreviewPlayback) {
|
||||||
|
this.preview = newPlayback.preview;
|
||||||
|
this.seeking = false;
|
||||||
|
|
||||||
|
this.timeRange = newPlayback.timeRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
override scrubToTimestamp(time: number): boolean {
|
||||||
|
if (!this.preview || !this.timeRange) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time < this.preview.start || time > this.preview.end) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.seeking) {
|
||||||
|
this.timeToSeek = time;
|
||||||
|
} else {
|
||||||
|
if (this.previewRef.current) {
|
||||||
|
this.previewRef.current.currentTime = Math.max(
|
||||||
|
0,
|
||||||
|
time - this.preview.start,
|
||||||
|
);
|
||||||
|
this.seeking = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override finishedSeeking() {
|
||||||
|
if (!this.previewRef.current || !this.preview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.timeToSeek &&
|
||||||
|
this.timeToSeek != this.previewRef.current?.currentTime
|
||||||
|
) {
|
||||||
|
this.previewRef.current.currentTime =
|
||||||
|
this.timeToSeek - this.preview.start;
|
||||||
|
} else {
|
||||||
|
this.seeking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override setNewPreviewStartTime(time: number) {
|
||||||
|
this.timeToSeek = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewReady() {
|
||||||
|
this.seeking = false;
|
||||||
|
this.previewRef.current?.pause();
|
||||||
|
|
||||||
|
if (this.timeToSeek) {
|
||||||
|
this.finishedSeeking();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreviewFramesPlayerProps = {
|
||||||
|
className?: string;
|
||||||
|
camera: string;
|
||||||
|
timeRange: { start: number; end: number };
|
||||||
|
startTime?: number;
|
||||||
|
onControllerReady: (controller: PreviewController) => void;
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
function PreviewFramesPlayer({
|
||||||
|
className,
|
||||||
|
camera,
|
||||||
|
timeRange,
|
||||||
|
startTime,
|
||||||
|
onControllerReady,
|
||||||
|
onClick,
|
||||||
|
}: PreviewFramesPlayerProps) {
|
||||||
|
// frames data
|
||||||
|
|
||||||
|
const { data: previewFrames } = useSWR<string[]>(
|
||||||
|
`preview/${camera}/start/${Math.floor(timeRange.start)}/end/${Math.ceil(
|
||||||
|
timeRange.end,
|
||||||
|
)}/frames`,
|
||||||
|
{ revalidateOnFocus: false },
|
||||||
|
);
|
||||||
|
const frameTimes = useMemo(() => {
|
||||||
|
if (!previewFrames) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return previewFrames.map((frame) =>
|
||||||
|
parseFloat(frame.split("-")[1].slice(undefined, -5)),
|
||||||
|
);
|
||||||
|
}, [previewFrames]);
|
||||||
|
|
||||||
|
// controlling frames
|
||||||
|
|
||||||
|
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||||
|
const controller = useMemo(() => {
|
||||||
|
if (!frameTimes || !imgRef.current) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PreviewFramesController(camera, imgRef, frameTimes);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [imgRef, frameTimes, imgRef.current]);
|
||||||
|
|
||||||
|
// initial state
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controller) {
|
||||||
|
onControllerReady(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only want to fire once when players are ready
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
|
const onImageLoaded = useCallback(() => {
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.finishedSeeking();
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startTime) {
|
||||||
|
controller.scrubToTimestamp(timeRange.start);
|
||||||
|
} else {
|
||||||
|
controller.scrubToTimestamp(startTime);
|
||||||
|
}
|
||||||
|
// we only want to calculate this once
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`relative w-full ${className ?? ""} ${onClick ? "cursor-pointer" : ""}`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
ref={imgRef}
|
||||||
|
className={`size-full object-contain rounded-2xl bg-black`}
|
||||||
|
onLoad={onImageLoaded}
|
||||||
|
/>
|
||||||
|
{previewFrames?.length === 0 && (
|
||||||
|
<div className="absolute inset-x-0 top-1/2 -y-translate-1/2 bg-black text-white rounded-2xl align-center text-center">
|
||||||
|
No Preview Found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PreviewFramesController extends PreviewController {
|
||||||
|
imgController: MutableRefObject<HTMLImageElement | null>;
|
||||||
|
frameTimes: number[];
|
||||||
|
seeking: boolean = false;
|
||||||
|
private timeToSeek: number | undefined = undefined;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
camera: string,
|
||||||
|
imgController: MutableRefObject<HTMLImageElement | null>,
|
||||||
|
frameTimes: number[],
|
||||||
|
) {
|
||||||
|
super(camera);
|
||||||
|
this.imgController = imgController;
|
||||||
|
this.frameTimes = frameTimes;
|
||||||
|
}
|
||||||
|
|
||||||
|
override scrubToTimestamp(time: number): boolean {
|
||||||
|
if (!this.imgController.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const frame = this.frameTimes.find((p) => {
|
||||||
|
return time < p;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!frame) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.seeking) {
|
||||||
|
this.timeToSeek = frame;
|
||||||
|
} else {
|
||||||
|
const newSrc = `${baseUrl}api/preview/preview_${this.camera}-${frame}.webp/thumbnail.webp`;
|
||||||
|
|
||||||
|
if (this.imgController.current.src != newSrc) {
|
||||||
|
this.imgController.current.src = newSrc;
|
||||||
|
this.seeking = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
override finishedSeeking() {
|
||||||
|
if (!this.imgController.current) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timeToSeek) {
|
||||||
|
const newSrc = `${baseUrl}api/preview/preview_${this.camera}-${this.timeToSeek}.webp/thumbnail.webp`;
|
||||||
|
|
||||||
|
if (this.imgController.current.src != newSrc) {
|
||||||
|
this.imgController.current.src = newSrc;
|
||||||
|
} else {
|
||||||
|
this.timeToSeek = undefined;
|
||||||
|
this.seeking = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.seeking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override setNewPreviewStartTime(time: number) {
|
||||||
|
this.timeToSeek = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -33,9 +33,9 @@ import { MdCircle } from "react-icons/md";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline";
|
import MotionReviewTimeline from "@/components/timeline/MotionReviewTimeline";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import PreviewVideoPlayer, {
|
import PreviewPlayer, {
|
||||||
PreviewVideoController,
|
PreviewController,
|
||||||
} from "@/components/player/PreviewVideoPlayer";
|
} from "@/components/player/PreviewPlayer";
|
||||||
|
|
||||||
type EventViewProps = {
|
type EventViewProps = {
|
||||||
reviews?: ReviewSegment[];
|
reviews?: ReviewSegment[];
|
||||||
@ -578,9 +578,7 @@ function MotionReview({
|
|||||||
return cameras.sort((a, b) => a.ui.order - b.ui.order);
|
return cameras.sort((a, b) => a.ui.order - b.ui.order);
|
||||||
}, [config, filter]);
|
}, [config, filter]);
|
||||||
|
|
||||||
const videoPlayersRef = useRef<{ [camera: string]: PreviewVideoController }>(
|
const videoPlayersRef = useRef<{ [camera: string]: PreviewController }>({});
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
// motion data
|
// motion data
|
||||||
|
|
||||||
@ -596,14 +594,9 @@ function MotionReview({
|
|||||||
|
|
||||||
// timeline time
|
// timeline time
|
||||||
|
|
||||||
const lastFullHour = useMemo(() => {
|
|
||||||
const end = new Date(timeRange.before * 1000);
|
|
||||||
end.setMinutes(0, 0, 0);
|
|
||||||
return end.getTime() / 1000;
|
|
||||||
}, [timeRange]);
|
|
||||||
const timeRangeSegments = useMemo(
|
const timeRangeSegments = useMemo(
|
||||||
() => getChunkedTimeRange(timeRange.after, lastFullHour),
|
() => getChunkedTimeRange(timeRange.after, timeRange.before),
|
||||||
[lastFullHour, timeRange],
|
[timeRange],
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialIndex = useMemo(() => {
|
const initialIndex = useMemo(() => {
|
||||||
@ -674,7 +667,7 @@ function MotionReview({
|
|||||||
grow = "aspect-video";
|
grow = "aspect-video";
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<PreviewVideoPlayer
|
<PreviewPlayer
|
||||||
key={camera.name}
|
key={camera.name}
|
||||||
className={`${grow}`}
|
className={`${grow}`}
|
||||||
camera={camera.name}
|
camera={camera.name}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user