forked from Github/frigate
Create API to create gif from previews and show instead of still thumbnails (#9786)
* Start working on animation * Change output file * Create preview gif * Show animated gif for event thumb * Remove favorite * Cleanup
This commit is contained in:
37
web/src/components/image/AnimatedEventThumbnail.tsx
Normal file
37
web/src/components/image/AnimatedEventThumbnail.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { baseUrl } from "@/api/baseUrl";
|
||||
import { Event as FrigateEvent } from "@/types/event";
|
||||
import TimeAgo from "../dynamic/TimeAgo";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||
|
||||
type AnimatedEventThumbnailProps = {
|
||||
event: FrigateEvent;
|
||||
};
|
||||
export function AnimatedEventThumbnail({ event }: AnimatedEventThumbnailProps) {
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
className="relative rounded bg-cover aspect-video h-24 bg-no-repeat bg-center mr-4"
|
||||
style={{
|
||||
backgroundImage: `url(${baseUrl}api/events/${event.id}/preview.gif)`,
|
||||
}}
|
||||
>
|
||||
<div className="absolute bottom-0 w-full h-6 bg-gradient-to-t from-slate-900/50 to-transparent rounded">
|
||||
<div className="absolute left-1 bottom-0 text-xs text-white w-full">
|
||||
<TimeAgo time={event.start_time * 1000} dense />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{`${event.label} ${
|
||||
event.sub_label ? `(${event.sub_label})` : ""
|
||||
} detected with score of ${(event.data.score * 100).toFixed(0)}% ${
|
||||
event.data.sub_label_score
|
||||
? `(${event.data.sub_label_score * 100}%)`
|
||||
: ""
|
||||
}`}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -40,22 +40,23 @@ export default function LivePlayer({
|
||||
const { activeMotion, activeAudio, activeTracking } =
|
||||
useCameraActivity(cameraConfig);
|
||||
|
||||
const cameraActive = useMemo(() => activeMotion || activeTracking, [activeMotion, activeTracking])
|
||||
const liveMode = useCameraLiveMode(cameraConfig, preferredLiveMode);
|
||||
|
||||
const [liveReady, setLiveReady] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!liveReady) {
|
||||
if (activeMotion && liveMode == "jsmpeg") {
|
||||
if (cameraActive && liveMode == "jsmpeg") {
|
||||
setLiveReady(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activeMotion && !activeTracking) {
|
||||
if (!cameraActive) {
|
||||
setLiveReady(false);
|
||||
}
|
||||
}, [activeMotion, activeTracking, liveReady]);
|
||||
}, [cameraActive, liveReady]);
|
||||
|
||||
const { payload: recording } = useRecordingsState(cameraConfig.name);
|
||||
|
||||
@@ -167,7 +168,7 @@ export default function LivePlayer({
|
||||
: "outline-0"
|
||||
} transition-all duration-500 ${className}`}
|
||||
>
|
||||
{(showStillWithoutActivity == false || activeMotion || activeTracking) &&
|
||||
{(showStillWithoutActivity == false || cameraActive) &&
|
||||
player}
|
||||
|
||||
<div
|
||||
@@ -179,7 +180,7 @@ export default function LivePlayer({
|
||||
className="w-full h-full"
|
||||
camera={cameraConfig.name}
|
||||
showFps={false}
|
||||
reloadInterval={30000}
|
||||
reloadInterval={(cameraActive && !liveReady) ? 200 : 30000}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { EventThumbnail } from "@/components/image/EventThumbnail";
|
||||
import { AnimatedEventThumbnail } from "@/components/image/AnimatedEventThumbnail";
|
||||
import LivePlayer from "@/components/player/LivePlayer";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { Event as FrigateEvent } from "@/types/event";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import axios from "axios";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
function Live() {
|
||||
@@ -13,7 +12,7 @@ function Live() {
|
||||
|
||||
// recent events
|
||||
|
||||
const { data: allEvents, mutate: updateEvents } = useSWR<FrigateEvent[]>(
|
||||
const { data: allEvents } = useSWR<FrigateEvent[]>(
|
||||
["events", { limit: 10 }],
|
||||
{ refreshInterval: 60000 }
|
||||
);
|
||||
@@ -29,19 +28,6 @@ function Live() {
|
||||
return allEvents.filter((event) => event.start_time > cutoff);
|
||||
}, [allEvents]);
|
||||
|
||||
const onFavorite = useCallback(async (e: Event, event: FrigateEvent) => {
|
||||
e.stopPropagation();
|
||||
let response;
|
||||
if (!event.retain_indefinitely) {
|
||||
response = await axios.post(`events/${event.id}/retain`);
|
||||
} else {
|
||||
response = await axios.delete(`events/${event.id}/retain`);
|
||||
}
|
||||
if (response.status === 200) {
|
||||
updateEvents();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// camera live views
|
||||
|
||||
const cameras = useMemo(() => {
|
||||
@@ -61,13 +47,7 @@ function Live() {
|
||||
<TooltipProvider>
|
||||
<div className="flex">
|
||||
{events.map((event) => {
|
||||
return (
|
||||
<EventThumbnail
|
||||
key={event.id}
|
||||
event={event}
|
||||
onFavorite={onFavorite}
|
||||
/>
|
||||
);
|
||||
return <AnimatedEventThumbnail key={event.id} event={event} />;
|
||||
})}
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
Reference in New Issue
Block a user