Fix preview players at the hour rollover (#24157)

* fix preview players at the hour rollover

* clean up
This commit is contained in:
Josh Hawkins
2026-09-12 07:30:04 -06:00
committed by Nicolas Mowen
parent 6ae8050974
commit 53b04f44ef
9 changed files with 320 additions and 22 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { Preview } from "@/types/preview";
import { PreviewPlayback } from "@/types/playback";
import { isCurrentHour } from "@/utils/dateUtil";
import { isCurrentOrPreviousHour } from "@/utils/dateUtil";
import { baseUrl } from "@/api/baseUrl";
import { isAndroid, isChrome, isMobile } from "react-device-detect";
import { TimeRange } from "@/types/timeline";
@@ -76,7 +76,7 @@ export default function PreviewPlayer({
);
}
if (isCurrentHour(timeRange.before)) {
if (isCurrentOrPreviousHour(timeRange.before)) {
return (
<PreviewFramesPlayer
className={className}
@@ -345,7 +345,7 @@ function PreviewVideoPlayer({
)}
{cameraPreviews && !currentPreview && (
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-background_alt text-primary dark:bg-black md:rounded-2xl">
{t("noPreviewFoundFor", { camera: cameraName })}
{t("noPreviewFoundFor", { cameraName: cameraName })}
</div>
)}
{firstLoad && <Skeleton className="absolute aspect-video size-full" />}
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useApiHost } from "@/api";
import { isCurrentHour } from "@/utils/dateUtil";
import { isCurrentOrPreviousHour } from "@/utils/dateUtil";
import {
ReviewSegment,
ThreatLevel,
@@ -139,9 +139,19 @@ export default function PreviewThumbnailPlayer({
const [hoverTimeout, setHoverTimeout] = useState<NodeJS.Timeout | null>();
const [playback, setPlayback] = useState(false);
const [tooltipHovering, setTooltipHovering] = useState(false);
const thumbnailUrl = `${apiHost}${review.thumb_path.replace("/media/frigate/", "")}`;
// not memoized: depends on the wall clock, and a stale value blanks the card
// for a whole hour after a rollover
const hasPreviewContent =
relevantPreview != undefined || isCurrentOrPreviousHour(review.start_time);
// playback hides the thumbnail below, and only a mounted player calls
// isPlayingBack(false), so entering it empty leaves the card black
const playingBack = useMemo(
() => playback && !tooltipHovering,
[playback, tooltipHovering],
() => playback && !tooltipHovering && hasPreviewContent,
[playback, tooltipHovering, hasPreviewContent],
);
const [isHovered, setIsHovered] = useState(false);
@@ -208,6 +218,7 @@ export default function PreviewThumbnailPlayer({
review={review}
relevantPreview={relevantPreview}
timeRange={timeRange}
defaultImageUrl={thumbnailUrl}
setReviewed={handleSetReviewed}
setIgnoreClick={setIgnoreClick}
isPlayingBack={setPlayback}
@@ -234,7 +245,7 @@ export default function PreviewThumbnailPlayer({
: undefined
}
draggable={false}
src={`${apiHost}${review.thumb_path.replace("/media/frigate/", "")}`}
src={thumbnailUrl}
loading={isSafari ? "eager" : "lazy"}
onLoad={() => {
onImgLoad();
@@ -394,6 +405,7 @@ type PreviewContentProps = {
review: ReviewSegment;
relevantPreview: Preview | undefined;
timeRange: TimeRange;
defaultImageUrl: string;
setReviewed: () => void;
setIgnoreClick: (ignore: boolean) => void;
isPlayingBack: (ended: boolean) => void;
@@ -403,6 +415,7 @@ function PreviewContent({
review,
relevantPreview,
timeRange,
defaultImageUrl,
setReviewed,
setIgnoreClick,
isPlayingBack,
@@ -423,13 +436,14 @@ function PreviewContent({
windowVisible={true}
/>
);
} else if (isCurrentHour(review.start_time)) {
} else if (isCurrentOrPreviousHour(review.start_time)) {
return (
<InProgressPreview
camera={review.camera}
startTime={review.start_time}
endTime={review.end_time}
timeRange={timeRange}
defaultImageUrl={defaultImageUrl}
setReviewed={setReviewed}
setIgnoreClick={setIgnoreClick}
isPlayingBack={isPlayingBack}
@@ -438,4 +452,7 @@ function PreviewContent({
/>
);
}
// unreachable while the caller gates on hasPreviewContent
return <img className="size-full" src={defaultImageUrl} />;
}