Playback recording when clicking on review item

This commit is contained in:
Nick Mowen 2023-12-14 08:10:01 -07:00
parent 6a400a5f12
commit d5f57c117e
5 changed files with 110 additions and 5 deletions

View File

@ -23,12 +23,14 @@ type HistoryCardProps = {
timeline: Card; timeline: Card;
relevantPreview?: Preview; relevantPreview?: Preview;
shouldAutoPlay: boolean; shouldAutoPlay: boolean;
onClick?: () => void;
}; };
export default function HistoryCard({ export default function HistoryCard({
relevantPreview, relevantPreview,
timeline, timeline,
shouldAutoPlay, shouldAutoPlay,
onClick,
}: HistoryCardProps) { }: HistoryCardProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
@ -37,7 +39,10 @@ export default function HistoryCard({
} }
return ( return (
<Card className="my-2 xs:mr-2 bg-secondary w-full xs:w-[48%] sm:w-[284px]"> <Card
className="cursor-pointer my-2 xs:mr-2 bg-secondary w-full xs:w-[48%] sm:w-[284px]"
onClick={onClick}
>
<PreviewThumbnailPlayer <PreviewThumbnailPlayer
camera={timeline.camera} camera={timeline.camera}
relevantPreview={relevantPreview} relevantPreview={relevantPreview}

View File

@ -0,0 +1,74 @@
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import VideoPlayer from "../player/VideoPlayer";
import { useMemo } from "react";
import { useApiHost } from "@/api";
type TimelinePlayerCardProps = {
timeline?: Card;
onDismiss: () => void;
};
export default function TimelinePlayerCard({
timeline,
onDismiss,
}: TimelinePlayerCardProps) {
const { data: config } = useSWR<FrigateConfig>("config");
const apiHost = useApiHost();
const recordingParams = useMemo(() => {
if (!timeline) {
return {};
}
return {
before: timeline.entries.at(-1)!!.timestamp + 30,
after: timeline.entries.at(0)!!.timestamp,
};
}, [timeline]);
const { data: recordings } = useSWR<Recording[]>(
timeline ? [`${timeline.camera}/recordings`, recordingParams] : null,
{ revalidateOnFocus: false }
);
return (
<>
<Dialog open={timeline != null} onOpenChange={onDismiss}>
<DialogContent className="md:max-w-xl lg:max-w-2xl xl:max-w-3xl 2xl:max-w-4xl">
<DialogHeader>
<DialogTitle className="capitalize">
{`${timeline?.camera?.replaceAll(
"_",
" "
)} @ ${formatUnixTimestampToDateTime(timeline?.time ?? 0, {
strftime_fmt:
config?.ui?.time_format == "24hour" ? "%H:%M:%S" : "%I:%M:%S",
})}`}
</DialogTitle>
</DialogHeader>
{recordings && recordings.length > 0 && (
<VideoPlayer
options={{
preload: "auto",
autoplay: true,
sources: [
{
src: `${apiHost}vod/${timeline?.camera}/start/${recordings
.at(0)
?.start_time.toFixed(2)}/end/${recordings
.at(-1)
?.end_time.toFixed(2)}/master.m3u8`,
type: "application/vnd.apple.mpegurl",
},
],
}}
seekOptions={{ forward: 10, backward: 5 }}
/>
)}
</DialogContent>
</Dialog>
</>
);
}

View File

@ -152,6 +152,10 @@ function isCurrentHour(timestamp: number) {
function getPreviewWidth(camera: string, config: FrigateConfig) { function getPreviewWidth(camera: string, config: FrigateConfig) {
const detect = config.cameras[camera].detect; const detect = config.cameras[camera].detect;
if (detect.width / detect.height < 1.0) {
return "w-[120px]";
}
if (detect.width / detect.height < 1.4) { if (detect.width / detect.height < 1.4) {
return "w-[208px]"; return "w-[208px]";
} }

View File

@ -7,6 +7,7 @@ import ActivityIndicator from "@/components/ui/activity-indicator";
import HistoryCard from "@/components/card/HistoryCard"; import HistoryCard from "@/components/card/HistoryCard";
import { formatUnixTimestampToDateTime } from "@/utils/dateUtil"; import { formatUnixTimestampToDateTime } from "@/utils/dateUtil";
import axios from "axios"; import axios from "axios";
import TimelinePlayerCard from "@/components/card/TimelineCardPlayer";
const API_LIMIT = 100; const API_LIMIT = 100;
@ -44,15 +45,17 @@ function History() {
isValidating, isValidating,
} = useSWRInfinite<HourlyTimeline>(getKey, timelineFetcher); } = useSWRInfinite<HourlyTimeline>(getKey, timelineFetcher);
const { data: allPreviews } = useSWR<Preview[]>( const { data: allPreviews } = useSWR<Preview[]>(
`preview/all/start/${(timelinePages ?? [])?.at(0)?.start ?? 0}/end/${ timelinePages
(timelinePages ?? [])?.at(-1)?.end ?? 0 ? `preview/all/start/${timelinePages?.at(0)
}`, ?.start}/end/${timelinePages?.at(-1)?.end}`
: null,
{ revalidateOnFocus: false } { revalidateOnFocus: false }
); );
const [detailLevel, setDetailLevel] = useState<"normal" | "extra" | "full">( const [detailLevel, setDetailLevel] = useState<"normal" | "extra" | "full">(
"normal" "normal"
); );
const [playback, setPlayback] = useState<Card | undefined>();
const timelineCards: CardsData | never[] = useMemo(() => { const timelineCards: CardsData | never[] = useMemo(() => {
if (!timelinePages) { if (!timelinePages) {
@ -172,6 +175,11 @@ function History() {
Dates and times are based on the timezone {timezone} Dates and times are based on the timezone {timezone}
</div> </div>
<TimelinePlayerCard
timeline={playback}
onDismiss={() => setPlayback(undefined)}
/>
<div> <div>
{Object.entries(timelineCards) {Object.entries(timelineCards)
.reverse() .reverse()
@ -225,6 +233,9 @@ function History() {
timeline={timeline} timeline={timeline}
shouldAutoPlay={shouldAutoPlay} shouldAutoPlay={shouldAutoPlay}
relevantPreview={relevantPreview} relevantPreview={relevantPreview}
onClick={() => {
setPlayback(timeline);
}}
/> />
); );
} }

View File

@ -0,0 +1,11 @@
type Recording = {
id: string,
camera: string,
start_time: number,
end_time: number,
path: string,
segment_size: number,
motion: number,
objects: number,
dBFS: number,
}