mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-09-26 19:41:29 +08:00
Implement support for no recordings indicator on timeline (#18363)
* Indicate no recordings on the history timeline with gray hash marks This commit includes a new backend API endpoint and the frontend changes needed to support this functionality * don't show slashes for now
This commit is contained in:

committed by
Blake Blackshear

parent
e1340443f5
commit
4ebc4f6d21
@@ -17,6 +17,7 @@ import {
|
||||
VirtualizedMotionSegments,
|
||||
VirtualizedMotionSegmentsRef,
|
||||
} from "./VirtualizedMotionSegments";
|
||||
import { RecordingSegment } from "@/types/record";
|
||||
|
||||
export type MotionReviewTimelineProps = {
|
||||
segmentDuration: number;
|
||||
@@ -38,6 +39,7 @@ export type MotionReviewTimelineProps = {
|
||||
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
|
||||
events: ReviewSegment[];
|
||||
motion_events: MotionData[];
|
||||
noRecordingRanges?: RecordingSegment[];
|
||||
contentRef: RefObject<HTMLDivElement>;
|
||||
timelineRef?: RefObject<HTMLDivElement>;
|
||||
onHandlebarDraggingChange?: (isDragging: boolean) => void;
|
||||
@@ -66,6 +68,7 @@ export function MotionReviewTimeline({
|
||||
setExportEndTime,
|
||||
events,
|
||||
motion_events,
|
||||
noRecordingRanges,
|
||||
contentRef,
|
||||
timelineRef,
|
||||
onHandlebarDraggingChange,
|
||||
@@ -97,6 +100,17 @@ export function MotionReviewTimeline({
|
||||
motion_events,
|
||||
);
|
||||
|
||||
const getRecordingAvailability = useCallback(
|
||||
(time: number): boolean | undefined => {
|
||||
if (!noRecordingRanges?.length) return undefined;
|
||||
|
||||
return !noRecordingRanges.some(
|
||||
(range) => time >= range.start_time && time < range.end_time,
|
||||
);
|
||||
},
|
||||
[noRecordingRanges],
|
||||
);
|
||||
|
||||
const segmentTimes = useMemo(() => {
|
||||
const segments = [];
|
||||
let segmentTime = timelineStartAligned;
|
||||
@@ -206,6 +220,7 @@ export function MotionReviewTimeline({
|
||||
dense={dense}
|
||||
motionOnly={motionOnly}
|
||||
getMotionSegmentValue={getMotionSegmentValue}
|
||||
getRecordingAvailability={getRecordingAvailability}
|
||||
/>
|
||||
</ReviewTimeline>
|
||||
);
|
||||
|
@@ -15,6 +15,7 @@ type MotionSegmentProps = {
|
||||
timestampSpread: number;
|
||||
firstHalfMotionValue: number;
|
||||
secondHalfMotionValue: number;
|
||||
hasRecording?: boolean;
|
||||
motionOnly: boolean;
|
||||
showMinimap: boolean;
|
||||
minimapStartTime?: number;
|
||||
@@ -31,6 +32,7 @@ export function MotionSegment({
|
||||
timestampSpread,
|
||||
firstHalfMotionValue,
|
||||
secondHalfMotionValue,
|
||||
hasRecording,
|
||||
motionOnly,
|
||||
showMinimap,
|
||||
minimapStartTime,
|
||||
@@ -176,6 +178,12 @@ export function MotionSegment({
|
||||
segmentClasses,
|
||||
severity[0] && "bg-gradient-to-r",
|
||||
severity[0] && severityColorsBg[severity[0]],
|
||||
// TODO: will update this for 0.17
|
||||
false &&
|
||||
hasRecording == false &&
|
||||
firstHalfMotionValue == 0 &&
|
||||
secondHalfMotionValue == 0 &&
|
||||
"bg-slashes",
|
||||
)}
|
||||
onClick={segmentClick}
|
||||
onTouchEnd={(event) => handleTouchStart(event, segmentClick)}
|
||||
|
@@ -24,6 +24,7 @@ type VirtualizedMotionSegmentsProps = {
|
||||
dense: boolean;
|
||||
motionOnly: boolean;
|
||||
getMotionSegmentValue: (timestamp: number) => number;
|
||||
getRecordingAvailability: (timestamp: number) => boolean | undefined;
|
||||
};
|
||||
|
||||
export interface VirtualizedMotionSegmentsRef {
|
||||
@@ -55,6 +56,7 @@ export const VirtualizedMotionSegments = forwardRef<
|
||||
dense,
|
||||
motionOnly,
|
||||
getMotionSegmentValue,
|
||||
getRecordingAvailability,
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -154,6 +156,8 @@ export const VirtualizedMotionSegments = forwardRef<
|
||||
(item.end_time ?? segmentTime) >= motionEnd),
|
||||
);
|
||||
|
||||
const hasRecording = getRecordingAvailability(segmentTime);
|
||||
|
||||
if ((!segmentMotion || overlappingReviewItems) && motionOnly) {
|
||||
return null; // Skip rendering this segment in motion only mode
|
||||
}
|
||||
@@ -172,6 +176,7 @@ export const VirtualizedMotionSegments = forwardRef<
|
||||
events={events}
|
||||
firstHalfMotionValue={firstHalfMotionValue}
|
||||
secondHalfMotionValue={secondHalfMotionValue}
|
||||
hasRecording={hasRecording}
|
||||
segmentDuration={segmentDuration}
|
||||
segmentTime={segmentTime}
|
||||
timestampSpread={timestampSpread}
|
||||
@@ -189,6 +194,7 @@ export const VirtualizedMotionSegments = forwardRef<
|
||||
[
|
||||
events,
|
||||
getMotionSegmentValue,
|
||||
getRecordingAvailability,
|
||||
motionOnly,
|
||||
segmentDuration,
|
||||
showMinimap,
|
||||
|
@@ -43,7 +43,11 @@ import Logo from "@/components/Logo";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { FaVideo } from "react-icons/fa";
|
||||
import { VideoResolutionType } from "@/types/live";
|
||||
import { ASPECT_VERTICAL_LAYOUT, ASPECT_WIDE_LAYOUT } from "@/types/record";
|
||||
import {
|
||||
ASPECT_VERTICAL_LAYOUT,
|
||||
ASPECT_WIDE_LAYOUT,
|
||||
RecordingSegment,
|
||||
} from "@/types/record";
|
||||
import { useResizeObserver } from "@/hooks/resize-observer";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useFullscreen } from "@/hooks/use-fullscreen";
|
||||
@@ -808,6 +812,16 @@ function Timeline({
|
||||
},
|
||||
]);
|
||||
|
||||
const { data: noRecordings } = useSWR<RecordingSegment[]>([
|
||||
"recordings/unavailable",
|
||||
{
|
||||
before: timeRange.before,
|
||||
after: timeRange.after,
|
||||
scale: Math.round(zoomSettings.segmentDuration / 2),
|
||||
cameras: mainCamera,
|
||||
},
|
||||
]);
|
||||
|
||||
const [exportStart, setExportStartTime] = useState<number>(0);
|
||||
const [exportEnd, setExportEndTime] = useState<number>(0);
|
||||
|
||||
@@ -853,6 +867,7 @@ function Timeline({
|
||||
setHandlebarTime={setCurrentTime}
|
||||
events={mainCameraReviewItems}
|
||||
motion_events={motionData ?? []}
|
||||
noRecordingRanges={noRecordings ?? []}
|
||||
contentRef={contentRef}
|
||||
onHandlebarDraggingChange={(scrubbing) => setScrubbing(scrubbing)}
|
||||
isZooming={isZooming}
|
||||
|
@@ -42,6 +42,10 @@ module.exports = {
|
||||
wide: "32 / 9",
|
||||
tall: "8 / 9",
|
||||
},
|
||||
backgroundImage: {
|
||||
slashes:
|
||||
"repeating-linear-gradient(45deg, hsl(var(--primary-variant) / 0.2), hsl(var(--primary-variant) / 0.2) 2px, transparent 2px, transparent 8px)",
|
||||
},
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
|
Reference in New Issue
Block a user