2023-12-13 03:48:52 +01:00
|
|
|
import { FrigateConfig } from "@/types/frigateConfig";
|
|
|
|
import VideoPlayer from "./VideoPlayer";
|
|
|
|
import useSWR from "swr";
|
2023-12-14 04:15:28 +01:00
|
|
|
import { useCallback, useRef } from "react";
|
2023-12-13 03:48:52 +01:00
|
|
|
import { useApiHost } from "@/api";
|
|
|
|
import Player from "video.js/dist/types/player";
|
2023-12-14 04:15:28 +01:00
|
|
|
import { AspectRatio } from "../ui/aspect-ratio";
|
2023-12-13 03:48:52 +01:00
|
|
|
|
|
|
|
type PreviewPlayerProps = {
|
2023-12-14 04:15:28 +01:00
|
|
|
camera: string;
|
|
|
|
relevantPreview?: Preview;
|
|
|
|
startTs: number;
|
|
|
|
eventId: string;
|
|
|
|
shouldAutoPlay: boolean;
|
|
|
|
};
|
2023-12-13 03:48:52 +01:00
|
|
|
|
|
|
|
type Preview = {
|
2023-12-14 04:15:28 +01:00
|
|
|
camera: string;
|
|
|
|
src: string;
|
|
|
|
type: string;
|
|
|
|
start: number;
|
|
|
|
end: number;
|
|
|
|
};
|
2023-12-13 03:48:52 +01:00
|
|
|
|
2023-12-14 04:15:28 +01:00
|
|
|
export default function PreviewThumbnailPlayer({
|
|
|
|
camera,
|
|
|
|
relevantPreview,
|
|
|
|
startTs,
|
|
|
|
eventId,
|
|
|
|
shouldAutoPlay,
|
|
|
|
}: PreviewPlayerProps) {
|
|
|
|
const { data: config } = useSWR("config");
|
|
|
|
const playerRef = useRef<Player | null>(null);
|
|
|
|
const apiHost = useApiHost();
|
2023-12-13 03:48:52 +01:00
|
|
|
|
2023-12-14 04:15:28 +01:00
|
|
|
const onPlayback = useCallback(
|
|
|
|
(isHovered: Boolean) => {
|
|
|
|
if (!relevantPreview || !playerRef.current) {
|
|
|
|
return;
|
|
|
|
}
|
2023-12-13 03:48:52 +01:00
|
|
|
|
2023-12-14 04:15:28 +01:00
|
|
|
if (isHovered) {
|
|
|
|
playerRef.current.play();
|
|
|
|
} else {
|
|
|
|
playerRef.current.pause();
|
|
|
|
playerRef.current.currentTime(startTs - relevantPreview.start);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[relevantPreview, startTs]
|
|
|
|
);
|
2023-12-13 03:48:52 +01:00
|
|
|
|
2023-12-14 04:15:28 +01:00
|
|
|
const observer = useRef<IntersectionObserver | null>();
|
|
|
|
const inViewRef = useCallback(
|
|
|
|
(node: HTMLElement | null) => {
|
|
|
|
if (!shouldAutoPlay || observer.current) {
|
|
|
|
return;
|
|
|
|
}
|
2023-12-13 03:48:52 +01:00
|
|
|
|
2023-12-14 04:15:28 +01:00
|
|
|
try {
|
|
|
|
observer.current = new IntersectionObserver(
|
|
|
|
(entries) => {
|
|
|
|
if (entries[0].isIntersecting) {
|
|
|
|
onPlayback(true);
|
|
|
|
} else {
|
|
|
|
onPlayback(false);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{ threshold: 1.0 }
|
|
|
|
);
|
|
|
|
if (node) observer.current.observe(node);
|
|
|
|
} catch (e) {
|
|
|
|
// no op
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[observer, onPlayback]
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!relevantPreview) {
|
|
|
|
if (isCurrentHour(startTs)) {
|
2023-12-13 03:48:52 +01:00
|
|
|
return (
|
2023-12-14 04:15:28 +01:00
|
|
|
<AspectRatio
|
|
|
|
ratio={16 / 9}
|
|
|
|
className="bg-black flex justify-center items-center"
|
|
|
|
>
|
|
|
|
<img
|
|
|
|
className={`${getPreviewWidth(camera, config)}`}
|
|
|
|
loading="lazy"
|
|
|
|
src={`${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`}
|
|
|
|
/>
|
|
|
|
</AspectRatio>
|
2023-12-13 03:48:52 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2023-12-14 04:15:28 +01:00
|
|
|
<AspectRatio
|
|
|
|
ratio={16 / 9}
|
|
|
|
className="bg-black flex justify-center items-center"
|
2023-12-13 03:48:52 +01:00
|
|
|
>
|
2023-12-14 04:15:28 +01:00
|
|
|
<img
|
|
|
|
className="w-[160px]"
|
|
|
|
loading="lazy"
|
|
|
|
src={`${apiHost}api/events/${eventId}/thumbnail.jpg`}
|
|
|
|
/>
|
|
|
|
</AspectRatio>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<AspectRatio
|
|
|
|
ref={shouldAutoPlay ? inViewRef : null}
|
|
|
|
ratio={16 / 9}
|
|
|
|
className="bg-black flex justify-center items-center"
|
|
|
|
onMouseEnter={() => onPlayback(true)}
|
|
|
|
onMouseLeave={() => onPlayback(false)}
|
|
|
|
>
|
|
|
|
<div className={`${getPreviewWidth(camera, config)}`}>
|
2023-12-13 03:48:52 +01:00
|
|
|
<VideoPlayer
|
|
|
|
options={{
|
2023-12-14 04:15:28 +01:00
|
|
|
preload: "auto",
|
2023-12-13 03:48:52 +01:00
|
|
|
autoplay: false,
|
|
|
|
controls: false,
|
|
|
|
muted: true,
|
|
|
|
loadingSpinner: false,
|
|
|
|
sources: [
|
|
|
|
{
|
|
|
|
src: `${relevantPreview.src}`,
|
2023-12-14 04:15:28 +01:00
|
|
|
type: "video/mp4",
|
2023-12-13 03:48:52 +01:00
|
|
|
},
|
|
|
|
],
|
|
|
|
}}
|
|
|
|
seekOptions={{}}
|
|
|
|
onReady={(player) => {
|
|
|
|
playerRef.current = player;
|
|
|
|
player.playbackRate(8);
|
|
|
|
player.currentTime(startTs - relevantPreview.start);
|
|
|
|
}}
|
|
|
|
onDispose={() => {
|
|
|
|
playerRef.current = null;
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
2023-12-14 04:15:28 +01:00
|
|
|
</AspectRatio>
|
|
|
|
);
|
2023-12-13 03:48:52 +01:00
|
|
|
}
|
|
|
|
|
2023-12-14 04:15:28 +01:00
|
|
|
function isCurrentHour(timestamp: number) {
|
|
|
|
const now = new Date();
|
|
|
|
now.setMinutes(0, 0, 0);
|
|
|
|
return timestamp > now.getTime() / 1000;
|
2023-12-13 03:48:52 +01:00
|
|
|
}
|
|
|
|
|
2023-12-14 04:15:28 +01:00
|
|
|
function getPreviewWidth(camera: string, config: FrigateConfig) {
|
|
|
|
const detect = config.cameras[camera].detect;
|
|
|
|
|
|
|
|
if (detect.width / detect.height < 1.4) {
|
|
|
|
return "w-[208px]";
|
|
|
|
}
|
|
|
|
|
|
|
|
return "w-full";
|
|
|
|
}
|