mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-07 11:45:24 +03:00
Playback recording when clicking on review item
This commit is contained in:
parent
6a400a5f12
commit
d5f57c117e
@ -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}
|
||||||
|
|||||||
74
web-new/src/components/card/TimelineCardPlayer.tsx
Normal file
74
web-new/src/components/card/TimelineCardPlayer.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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]";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
@ -161,7 +164,7 @@ function History() {
|
|||||||
[size, setSize, isValidating, isDone]
|
[size, setSize, isValidating, isDone]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!config || !timelineCards ||timelineCards.length == 0) {
|
if (!config || !timelineCards || timelineCards.length == 0) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
11
web-new/src/types/record.ts
Normal file
11
web-new/src/types/record.ts
Normal 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,
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user