forked from Github/frigate
Remove gifs and use existing views (#11027)
* Use existing components for preview filmstrip instead of gif * Allow setting format
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
import { baseUrl } from "@/api/baseUrl";
|
||||
import TimeAgo from "../dynamic/TimeAgo";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import useSWR from "swr";
|
||||
import { FrigateConfig } from "@/types/frigateConfig";
|
||||
import { ReviewSegment } from "@/types/review";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import { RecordingStartingPoint } from "@/types/record";
|
||||
import axios from "axios";
|
||||
import { Preview } from "@/types/preview";
|
||||
import {
|
||||
InProgressPreview,
|
||||
VideoPreview,
|
||||
} from "../player/PreviewThumbnailPlayer";
|
||||
|
||||
type AnimatedEventCardProps = {
|
||||
event: ReviewSegment;
|
||||
@@ -16,6 +19,12 @@ type AnimatedEventCardProps = {
|
||||
export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
|
||||
const { data: config } = useSWR<FrigateConfig>("config");
|
||||
|
||||
// preview
|
||||
|
||||
const { data: previews } = useSWR<Preview[]>(
|
||||
`/preview/${event.camera}/start/${Math.round(event.start_time)}/end/${Math.round(event.end_time || event.start_time + 20)}`,
|
||||
);
|
||||
|
||||
// interaction
|
||||
|
||||
const navigate = useNavigate();
|
||||
@@ -35,16 +44,6 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
|
||||
|
||||
// image behavior
|
||||
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [error, setError] = useState(0);
|
||||
const imageUrl = useMemo(() => {
|
||||
if (error > 0) {
|
||||
return `${baseUrl}api/review/${event.id}/preview.gif?key=${error}`;
|
||||
}
|
||||
|
||||
return `${baseUrl}api/review/${event.id}/preview.gif`;
|
||||
}, [error, event]);
|
||||
|
||||
const aspectRatio = useMemo(() => {
|
||||
if (!config) {
|
||||
return 1;
|
||||
@@ -63,18 +62,36 @@ export function AnimatedEventCard({ event }: AnimatedEventCardProps) {
|
||||
aspectRatio: aspectRatio,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className="size-full rounded object-cover object-center cursor-pointer"
|
||||
src={imageUrl}
|
||||
<div
|
||||
className="size-full rounded cursor-pointer"
|
||||
onClick={onOpenReview}
|
||||
onLoad={() => setLoaded(true)}
|
||||
onError={() => {
|
||||
if (error < 2) {
|
||||
setError(error + 1);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!loaded && <Skeleton className="absolute inset-0" />}
|
||||
>
|
||||
{previews ? (
|
||||
<VideoPreview
|
||||
relevantPreview={previews[previews.length - 1]}
|
||||
startTime={event.start_time}
|
||||
endTime={event.end_time}
|
||||
loop
|
||||
showProgress={false}
|
||||
setReviewed={() => {}}
|
||||
setIgnoreClick={() => {}}
|
||||
isPlayingBack={() => {}}
|
||||
/>
|
||||
) : (
|
||||
<InProgressPreview
|
||||
review={event}
|
||||
timeRange={{
|
||||
after: event.start_time,
|
||||
before: event.end_time ?? event.start_time + 20,
|
||||
}}
|
||||
loop
|
||||
showProgress={false}
|
||||
setReviewed={() => {}}
|
||||
setIgnoreClick={() => {}}
|
||||
isPlayingBack={() => {}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute bottom-0 inset-x-0 h-6 bg-gradient-to-t from-slate-900/50 to-transparent rounded">
|
||||
<div className="w-full absolute left-1 bottom-0 text-xs text-white">
|
||||
<TimeAgo time={event.start_time * 1000} dense />
|
||||
|
||||
@@ -342,15 +342,19 @@ type VideoPreviewProps = {
|
||||
relevantPreview: Preview;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
showProgress?: boolean;
|
||||
loop?: boolean;
|
||||
setReviewed: () => void;
|
||||
setIgnoreClick: (ignore: boolean) => void;
|
||||
isPlayingBack: (ended: boolean) => void;
|
||||
onTimeUpdate?: (time: number | undefined) => void;
|
||||
};
|
||||
function VideoPreview({
|
||||
export function VideoPreview({
|
||||
relevantPreview,
|
||||
startTime,
|
||||
endTime,
|
||||
showProgress = true,
|
||||
loop = false,
|
||||
setReviewed,
|
||||
setIgnoreClick,
|
||||
isPlayingBack,
|
||||
@@ -425,6 +429,11 @@ function VideoPreview({
|
||||
if (playerPercent > 100) {
|
||||
setReviewed();
|
||||
|
||||
if (loop && playerRef.current) {
|
||||
playerRef.current.currentTime = playerStartTime;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
isPlayingBack(false);
|
||||
|
||||
@@ -553,17 +562,19 @@ function VideoPreview({
|
||||
>
|
||||
<source src={relevantPreview.src} type={relevantPreview.type} />
|
||||
</video>
|
||||
<NoThumbSlider
|
||||
ref={sliderRef}
|
||||
className="absolute inset-x-0 bottom-0 z-30"
|
||||
value={[progress]}
|
||||
onValueChange={onManualSeek}
|
||||
onValueCommit={onStopManualSeek}
|
||||
min={0}
|
||||
step={1}
|
||||
max={100}
|
||||
onMouseMove={isMobile ? undefined : onProgressHover}
|
||||
/>
|
||||
{showProgress && (
|
||||
<NoThumbSlider
|
||||
ref={sliderRef}
|
||||
className="absolute inset-x-0 bottom-0 z-30"
|
||||
value={[progress]}
|
||||
onValueChange={onManualSeek}
|
||||
onValueCommit={onStopManualSeek}
|
||||
min={0}
|
||||
step={1}
|
||||
max={100}
|
||||
onMouseMove={isMobile ? undefined : onProgressHover}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -572,14 +583,18 @@ const MIN_LOAD_TIMEOUT_MS = 200;
|
||||
type InProgressPreviewProps = {
|
||||
review: ReviewSegment;
|
||||
timeRange: TimeRange;
|
||||
showProgress?: boolean;
|
||||
loop?: boolean;
|
||||
setReviewed: (reviewId: string) => void;
|
||||
setIgnoreClick: (ignore: boolean) => void;
|
||||
isPlayingBack: (ended: boolean) => void;
|
||||
onTimeUpdate?: (time: number | undefined) => void;
|
||||
};
|
||||
function InProgressPreview({
|
||||
export function InProgressPreview({
|
||||
review,
|
||||
timeRange,
|
||||
showProgress = true,
|
||||
loop = false,
|
||||
setReviewed,
|
||||
setIgnoreClick,
|
||||
isPlayingBack,
|
||||
@@ -615,6 +630,11 @@ function InProgressPreview({
|
||||
setReviewed(review.id);
|
||||
}
|
||||
|
||||
if (loop) {
|
||||
setKey(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
isPlayingBack(false);
|
||||
|
||||
@@ -717,17 +737,19 @@ function InProgressPreview({
|
||||
src={`${apiHost}api/preview/${previewFrames[key]}/thumbnail.webp`}
|
||||
onLoad={handleLoad}
|
||||
/>
|
||||
<NoThumbSlider
|
||||
ref={sliderRef}
|
||||
className="absolute inset-x-0 bottom-0 z-30"
|
||||
value={[key]}
|
||||
onValueChange={onManualSeek}
|
||||
onValueCommit={onStopManualSeek}
|
||||
min={0}
|
||||
step={1}
|
||||
max={previewFrames.length - 1}
|
||||
onMouseMove={isMobile ? undefined : onProgressHover}
|
||||
/>
|
||||
{showProgress && (
|
||||
<NoThumbSlider
|
||||
ref={sliderRef}
|
||||
className="absolute inset-x-0 bottom-0 z-30"
|
||||
value={[key]}
|
||||
onValueChange={onManualSeek}
|
||||
onValueCommit={onStopManualSeek}
|
||||
min={0}
|
||||
step={1}
|
||||
max={previewFrames.length - 1}
|
||||
onMouseMove={isMobile ? undefined : onProgressHover}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user