Use preview thumbnails

This commit is contained in:
Nicolas Mowen 2024-02-17 16:02:37 -07:00
parent 9b9808ca38
commit 7b1c7f9be9
2 changed files with 79 additions and 63 deletions

View File

@ -1,14 +1,7 @@
import VideoPlayer from "./VideoPlayer"; import VideoPlayer from "./VideoPlayer";
import React, { import React, { useCallback, useEffect, useRef, useState } from "react";
useCallback,
useEffect,
useRef,
useState,
} from "react";
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import Player from "video.js/dist/types/player"; import Player from "video.js/dist/types/player";
import { AspectRatio } from "../ui/aspect-ratio";
import { LuPlayCircle } from "react-icons/lu";
import { isCurrentHour } from "@/utils/dateUtil"; import { isCurrentHour } from "@/utils/dateUtil";
import { isSafari } from "@/utils/browserUtil"; import { isSafari } from "@/utils/browserUtil";
@ -116,10 +109,9 @@ export default function PreviewThumbnailPlayer({
); );
return ( return (
<AspectRatio <div
ref={relevantPreview ? inViewRef : null} ref={relevantPreview ? inViewRef : null}
ratio={16 / 9} className="relative w-full h-full"
className="bg-black flex justify-center items-center"
onMouseEnter={() => onPlayback(true)} onMouseEnter={() => onPlayback(true)}
onMouseLeave={() => onPlayback(false)} onMouseLeave={() => onPlayback(false)}
> >
@ -134,7 +126,9 @@ export default function PreviewThumbnailPlayer({
isMobile={isMobile} isMobile={isMobile}
onClick={onClick} onClick={onClick}
/> />
</AspectRatio> <div className="absolute top-0 left-0 right-0 rounded-2xl z-10 w-full h-[30%] bg-gradient-to-b from-black/20 to-transparent pointer-events-none" />
<div className="absolute bottom-0 left-0 right-0 rounded-2xl z-10 w-full h-[10%] bg-gradient-to-t from-black/20 to-transparent pointer-events-none" />
</div>
); );
} }
@ -198,55 +192,48 @@ function PreviewContent({
); );
} else { } else {
return ( return (
<> <VideoPlayer
<div className="w-full"> options={{
<VideoPlayer preload: "auto",
options={{ autoplay: true,
preload: "auto", controls: false,
aspectRatio: "16:9", muted: true,
autoplay: true, fluid: true,
controls: false, loadingSpinner: false,
muted: true, poster: relevantPreview
loadingSpinner: false, ? ""
poster: relevantPreview : `${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`,
? "" sources: relevantPreview
: `${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`, ? [
sources: relevantPreview {
? [ src: `${relevantPreview.src}`,
{ type: "video/mp4",
src: `${relevantPreview.src}`, },
type: "video/mp4", ]
}, : [],
] }}
: [], seekOptions={{}}
}} onReady={(player) => {
seekOptions={{}} playerRef.current = player;
onReady={(player) => {
playerRef.current = player;
if (!relevantPreview) { if (!relevantPreview) {
return; return;
} }
if (!isInitiallyVisible) { if (!isInitiallyVisible) {
player.pause(); // autoplay + pause is required for iOS player.pause(); // autoplay + pause is required for iOS
} }
player.playbackRate(slowPlayack ? 2 : 8); player.playbackRate(slowPlayack ? 2 : 8);
player.currentTime(startTs - relevantPreview.start); player.currentTime(startTs - relevantPreview.start);
if (isMobile && onClick) { if (isMobile && onClick) {
player.on("touchstart", handleTouchStart); player.on("touchstart", handleTouchStart);
} }
}} }}
onDispose={() => { onDispose={() => {
playerRef.current = null; playerRef.current = null;
}} }}
/> />
</div>
{relevantPreview && (
<LuPlayCircle className="absolute z-10 left-1 bottom-1 w-4 h-4 text-white text-opacity-60" />
)}
</>
); );
} }
} }

View File

@ -1,4 +1,5 @@
import TimeAgo from "@/components/dynamic/TimeAgo"; import TimeAgo from "@/components/dynamic/TimeAgo";
import PreviewThumbnailPlayer from "@/components/player/PreviewThumbnailPlayer";
import ActivityIndicator from "@/components/ui/activity-indicator"; import ActivityIndicator from "@/components/ui/activity-indicator";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
@ -14,7 +15,10 @@ export default function Events() {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const [severity, setSeverity] = useState<ReviewSeverity>("alert"); const [severity, setSeverity] = useState<ReviewSeverity>("alert");
const { data: reviewSegments } = useSWR<ReviewSegment[]>("review"); const { data: reviewSegments } = useSWR<ReviewSegment[]>([
"review",
{ limit: 500 },
]);
const previewTimes = useMemo(() => { const previewTimes = useMemo(() => {
if (!reviewSegments) { if (!reviewSegments) {
@ -89,7 +93,7 @@ export default function Events() {
</Button> </Button>
<Button className="mx-1" variant="secondary"> <Button className="mx-1" variant="secondary">
<LuCalendar className=" mr-[10px]" /> <LuCalendar className=" mr-[10px]" />
Fab 13 Fab 17
</Button> </Button>
<Button className="mx-1" variant="secondary"> <Button className="mx-1" variant="secondary">
<LuFilter className=" mr-[10px]" /> <LuFilter className=" mr-[10px]" />
@ -101,10 +105,35 @@ export default function Events() {
<div className="flex flex-wrap gap-2 mt-2"> <div className="flex flex-wrap gap-2 mt-2">
{reviewSegments?.map((value) => { {reviewSegments?.map((value) => {
if (value.severity == severity) { if (value.severity == severity) {
const detectConfig = config.cameras[value.camera].detect;
const relevantPreview = Object.values(allPreviews || []).find(
(preview) =>
preview.camera == value.camera &&
preview.start < value.start_time &&
preview.end > value.end_time
);
return ( return (
<div className="relative h-[234px] w-[416px] bg-blue-500 rounded-lg"> <div
{value.camera} {value.data.objects} className="relative h-[234px] rounded-2xl overflow-hidden"
<div className="absolute left-1 right-1 bottom-0 flex justify-between"> style={{
aspectRatio: detectConfig.width / detectConfig.height,
}}
>
{relevantPreview ? (
<PreviewThumbnailPlayer
relevantPreview={relevantPreview}
camera={value.camera}
startTs={value.start_time}
isMobile={false}
eventId=""
/>
) : (
<div>
{value.camera} {value.data.objects}
</div>
)}
<div className="absolute left-1 right-1 bottom-1 flex justify-between">
<TimeAgo time={value.start_time * 1000} /> <TimeAgo time={value.start_time * 1000} />
{formatUnixTimestampToDateTime(value.start_time, { {formatUnixTimestampToDateTime(value.start_time, {
strftime_fmt: strftime_fmt: