mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-04-05 14:47:40 +03:00
Merge pull request #64 from ibs0d/claude/fix-zoom-statistics-WFvOm
fix: replace callback motion dot with direct WS subscription component
This commit is contained in:
commit
9836871718
@ -50,7 +50,7 @@ type LivePlayerProps = {
|
||||
showStats?: boolean;
|
||||
onStatsUpdate?: (stats: PlayerStatsType) => void;
|
||||
onLoadingChange?: (loading: boolean) => void;
|
||||
onActiveMotionChange?: (active: boolean) => void;
|
||||
showMotionDot?: boolean;
|
||||
onClick?: () => void;
|
||||
setFullResolution?: React.Dispatch<React.SetStateAction<VideoResolutionType>>;
|
||||
onError?: (error: LivePlayerError) => void;
|
||||
@ -78,7 +78,7 @@ export default function LivePlayer({
|
||||
showStats = false,
|
||||
onStatsUpdate,
|
||||
onLoadingChange,
|
||||
onActiveMotionChange,
|
||||
showMotionDot = true,
|
||||
onClick,
|
||||
setFullResolution,
|
||||
onError,
|
||||
@ -113,10 +113,8 @@ export default function LivePlayer({
|
||||
|
||||
const onStatsUpdateRef = useRef(onStatsUpdate);
|
||||
const onLoadingChangeRef = useRef(onLoadingChange);
|
||||
const onActiveMotionChangeRef = useRef(onActiveMotionChange);
|
||||
onStatsUpdateRef.current = onStatsUpdate;
|
||||
onLoadingChangeRef.current = onLoadingChange;
|
||||
onActiveMotionChangeRef.current = onActiveMotionChange;
|
||||
|
||||
useEffect(() => {
|
||||
onStatsUpdateRef.current?.(stats);
|
||||
@ -301,14 +299,6 @@ export default function LivePlayer({
|
||||
onLoadingChangeRef.current?.(loading);
|
||||
}, [cameraEnabled, offline, showStillWithoutActivity, isReEnabling, liveReady]);
|
||||
|
||||
// When the parent manages the dot via callback (grid view), show motion
|
||||
// without gating on liveReady — the dot should reflect actual motion state
|
||||
// regardless of stream load status to avoid the dot not appearing for
|
||||
// cameras in continuous mode while the stream is reconnecting.
|
||||
useEffect(() => {
|
||||
onActiveMotionChangeRef.current?.(!!(autoLive && !offline && activeMotion));
|
||||
}, [autoLive, offline, activeMotion]);
|
||||
|
||||
if (!cameraConfig) {
|
||||
return <ActivityIndicator />;
|
||||
}
|
||||
@ -566,7 +556,7 @@ export default function LivePlayer({
|
||||
{cameraName}
|
||||
</Chip>
|
||||
)}
|
||||
{!onActiveMotionChangeRef.current &&
|
||||
{showMotionDot &&
|
||||
autoLive &&
|
||||
!offline &&
|
||||
activeMotion &&
|
||||
|
||||
@ -30,6 +30,7 @@ import {
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import { PlayerStats } from "@/components/player/PlayerStats";
|
||||
import { MdCircle } from "react-icons/md";
|
||||
import { useCameraActivity } from "@/hooks/use-camera-activity";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
|
||||
import { isEqual } from "lodash";
|
||||
@ -441,9 +442,6 @@ export default function DraggableGridLayout({
|
||||
const [cameraLoadingStates, setCameraLoadingStates] = useState<
|
||||
Record<string, boolean>
|
||||
>({});
|
||||
const [cameraMotionStates, setCameraMotionStates] = useState<
|
||||
Record<string, boolean>
|
||||
>({});
|
||||
const cameraZoomViewportRefs = useRef<Record<string, HTMLDivElement | null>>(
|
||||
{},
|
||||
);
|
||||
@ -881,12 +879,7 @@ export default function DraggableGridLayout({
|
||||
[camera.name]: loading,
|
||||
}))
|
||||
}
|
||||
onActiveMotionChange={(active) =>
|
||||
setCameraMotionStates((prev) => ({
|
||||
...prev,
|
||||
[camera.name]: active,
|
||||
}))
|
||||
}
|
||||
showMotionDot={false}
|
||||
onClick={() => {
|
||||
!isEditMode && onSelectCamera(camera.name);
|
||||
}}
|
||||
@ -918,11 +911,10 @@ export default function DraggableGridLayout({
|
||||
minimal={true}
|
||||
/>
|
||||
)}
|
||||
{cameraMotionStates[camera.name] && (
|
||||
<div className="absolute right-2 top-2 z-40">
|
||||
<MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
|
||||
</div>
|
||||
)}
|
||||
<CameraMotionDot
|
||||
camera={camera}
|
||||
autoLive={autoLive ?? globalAutoLive}
|
||||
/>
|
||||
</div>
|
||||
{isEditMode && showCircles && <CornerCircles />}
|
||||
</GridLiveContextMenu>
|
||||
@ -1089,6 +1081,25 @@ const BirdseyeLivePlayerGridItem = React.forwardRef<
|
||||
},
|
||||
);
|
||||
|
||||
// Separate component so it can call useCameraActivity as a hook (no hooks in loops).
|
||||
// Direct WS subscription guarantees the dot reacts to motion changes in real-time
|
||||
// without relying on an intermediate callback → parent-state chain.
|
||||
function CameraMotionDot({
|
||||
camera,
|
||||
autoLive,
|
||||
}: {
|
||||
camera: CameraConfig;
|
||||
autoLive: boolean;
|
||||
}) {
|
||||
const { activeMotion, offline } = useCameraActivity(camera);
|
||||
if (!autoLive || offline || !activeMotion) return null;
|
||||
return (
|
||||
<div className="absolute right-2 top-2 z-40">
|
||||
<MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type GridLiveContextMenuProps = {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user