diff --git a/web/src/components/card/TimelineItemCard.tsx b/web/src/components/card/TimelineItemCard.tsx index bb8e82e8c..fa3eb408c 100644 --- a/web/src/components/card/TimelineItemCard.tsx +++ b/web/src/components/card/TimelineItemCard.tsx @@ -6,6 +6,7 @@ import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import VideoPlayer from "../player/VideoPlayer"; import { Card } from "../ui/card"; +import { useApiHost } from "@/api"; type TimelineItemCardProps = { timeline: Timeline; @@ -18,35 +19,44 @@ export default function TimelineItemCard({ onSelect, }: TimelineItemCardProps) { const { data: config } = useSWR("config"); + const apiHost = useApiHost(); return ( - +
- {relevantPreview && ( - { + { + if (relevantPreview) { player.pause(); // autoplay + pause is required for iOS player.currentTime(timeline.timestamp - relevantPreview.start); - }} - /> - )} + } + }} + />
diff --git a/web/src/components/overlay/TimelineDataOverlay.tsx b/web/src/components/overlay/TimelineDataOverlay.tsx index 9ab08bc16..a020e6b5d 100644 --- a/web/src/components/overlay/TimelineDataOverlay.tsx +++ b/web/src/components/overlay/TimelineDataOverlay.tsx @@ -14,6 +14,10 @@ export default function TimelineEventOverlay({ timeline, cameraConfig, }: TimelineEventOverlayProps) { + if (!timeline.data.box) { + return null; + } + const boxLeftEdge = Math.round(timeline.data.box[0] * 100); const boxTopEdge = Math.round(timeline.data.box[1] * 100); const boxRightEdge = Math.round( @@ -25,6 +29,10 @@ export default function TimelineEventOverlay({ const [isHovering, setIsHovering] = useState(false); const getHoverStyle = () => { + if (!timeline.data.box) { + return {}; + } + if (boxLeftEdge < 15) { // show object stats on right side return { @@ -40,12 +48,20 @@ export default function TimelineEventOverlay({ }; const getObjectArea = () => { + if (!timeline.data.box) { + return 0; + } + const width = timeline.data.box[2] * cameraConfig.detect.width; const height = timeline.data.box[3] * cameraConfig.detect.height; return Math.round(width * height); }; const getObjectRatio = () => { + if (!timeline.data.box) { + return 0.0; + } + const width = timeline.data.box[2] * cameraConfig.detect.width; const height = timeline.data.box[3] * cameraConfig.detect.height; return Math.round(100 * (width / height)) / 100; diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 1fdcaa8b5..267d8a404 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -1,6 +1,4 @@ -import { FrigateConfig } from "@/types/frigateConfig"; import VideoPlayer from "./VideoPlayer"; -import useSWR from "swr"; import React, { useCallback, useEffect, @@ -12,6 +10,7 @@ import { useApiHost } from "@/api"; import Player from "video.js/dist/types/player"; import { AspectRatio } from "../ui/aspect-ratio"; import { LuPlayCircle } from "react-icons/lu"; +import { isCurrentHour } from "@/utils/dateUtil"; type PreviewPlayerProps = { camera: string; @@ -38,7 +37,6 @@ export default function PreviewThumbnailPlayer({ isMobile, onClick, }: PreviewPlayerProps) { - const { data: config } = useSWR("config"); const playerRef = useRef(null); const isSafari = useMemo(() => { return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); @@ -105,6 +103,7 @@ export default function PreviewThumbnailPlayer({ { threshold: 1.0, root: document.getElementById("pageRoot"), + rootMargin: "-15% 0px -15% 0px", } ); if (node) autoPlayObserver.current.observe(node); @@ -131,7 +130,6 @@ export default function PreviewThumbnailPlayer({ isInitiallyVisible={isInitiallyVisible} startTs={startTs} camera={camera} - config={config} eventId={eventId} isMobile={isMobile} isSafari={isSafari} @@ -143,7 +141,6 @@ export default function PreviewThumbnailPlayer({ type PreviewContentProps = { playerRef: React.MutableRefObject; - config: FrigateConfig; camera: string; relevantPreview: Preview | undefined; eventId: string; @@ -156,7 +153,6 @@ type PreviewContentProps = { }; function PreviewContent({ playerRef, - config, camera, relevantPreview, eventId, @@ -195,22 +191,13 @@ function PreviewContent({ if (relevantPreview && !isVisible) { return
; - } else if (!relevantPreview) { - if (isCurrentHour(startTs)) { - return ( - - ); - } else { - return ( - - ); - } + } else if (!relevantPreview && !isCurrentHour(startTs)) { + return ( + + ); } else { return ( <> @@ -223,17 +210,26 @@ function PreviewContent({ controls: false, muted: true, loadingSpinner: false, - sources: [ - { - src: `${relevantPreview.src}`, - type: "video/mp4", - }, - ], + poster: relevantPreview + ? "" + : `${apiHost}api/preview/${camera}/${startTs}/thumbnail.jpg`, + sources: relevantPreview + ? [ + { + src: `${relevantPreview.src}`, + type: "video/mp4", + }, + ] + : [], }} seekOptions={{}} onReady={(player) => { playerRef.current = player; + if (!relevantPreview) { + return; + } + if (!isInitiallyVisible) { player.pause(); // autoplay + pause is required for iOS } @@ -249,28 +245,10 @@ function PreviewContent({ }} />
- + {relevantPreview && ( + + )} ); } } - -function isCurrentHour(timestamp: number) { - const now = new Date(); - now.setMinutes(0, 0, 0); - return timestamp > now.getTime() / 1000; -} - -function getPreviewWidth(camera: string, config: FrigateConfig) { - const detect = config.cameras[camera].detect; - - if (detect.width / detect.height < 1) { - return "w-1/2"; - } - - if (detect.width / detect.height < 16 / 9) { - return "w-2/3"; - } - - return "w-full"; -} diff --git a/web/src/utils/dateUtil.ts b/web/src/utils/dateUtil.ts index 6f955d6dd..40f5a2a27 100644 --- a/web/src/utils/dateUtil.ts +++ b/web/src/utils/dateUtil.ts @@ -285,3 +285,9 @@ export function getRangeForTimestamp(timestamp: number) { const end = date.getTime() / 1000; return { start, end }; } + +export function isCurrentHour(timestamp: number) { + const now = new Date(); + now.setMinutes(0, 0, 0); + return timestamp > now.getTime() / 1000; +}