Only allow visible cameras to go live on dashboard (#10671)

* Only show live cameras that are currently visible

* Add back black background

* fix
This commit is contained in:
Nicolas Mowen 2024-03-25 14:56:13 -06:00 committed by GitHub
parent 51db63e42b
commit 6dd6ca5de5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 13 deletions

View File

@ -10,10 +10,10 @@ import { useCameraActivity } from "@/hooks/use-camera-activity";
import { useRecordingsState } from "@/api/ws";
import { LivePlayerMode } from "@/types/live";
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
import { isDesktop } from "react-device-detect";
import CameraActivityIndicator from "../indicators/CameraActivityIndicator";
type LivePlayerProps = {
cameraRef?: (ref: HTMLDivElement | null) => void;
className?: string;
cameraConfig: CameraConfig;
preferredLiveMode?: LivePlayerMode;
@ -26,6 +26,7 @@ type LivePlayerProps = {
};
export default function LivePlayer({
cameraRef = undefined,
className,
cameraConfig,
preferredLiveMode,
@ -140,6 +141,8 @@ export default function LivePlayer({
return (
<div
ref={cameraRef}
data-camera={cameraConfig.name}
className={`relative flex justify-center ${liveMode == "jsmpeg" ? "size-full" : "w-full"} outline cursor-pointer ${
activeTracking
? "outline-severity_alert outline-3 rounded-2xl shadow-severity_alert"
@ -171,13 +174,11 @@ export default function LivePlayer({
)}
</div>
{isDesktop && (
<div className="absolute right-2 top-2 size-4">
{recording == "ON" && (
<MdCircle className="size-2 drop-shadow-md shadow-danger text-danger animate-pulse" />
)}
</div>
)}
</div>
);
}

View File

@ -234,7 +234,7 @@ function PreviewVideoPlayer({
return (
<div
className={`relative w-full rounded-2xl overflow-hidden ${className ?? ""} ${onClick ? "cursor-pointer" : ""}`}
className={`relative w-full rounded-2xl bg-black overflow-hidden ${className ?? ""} ${onClick ? "cursor-pointer" : ""}`}
onClick={onClick}
>
{currentHourFrame && (
@ -247,7 +247,7 @@ function PreviewVideoPlayer({
ref={canvasRef}
width={videoWidth}
height={videoHeight}
className={`absolute h-full left-1/2 -translate-x-1/2 bg-black ${!loaded && hasCanvas ? "" : "hidden"}`}
className={`absolute h-full left-1/2 -translate-x-1/2 ${!loaded && hasCanvas ? "" : "hidden"}`}
/>
<video
ref={previewRef}
@ -281,7 +281,7 @@ function PreviewVideoPlayer({
<Skeleton className="absolute inset-0" />
)}
{cameraPreviews && !currentPreview && (
<div className="absolute inset-0 bg-black text-white rounded-2xl flex justify-center items-center">
<div className="absolute inset-0 text-white rounded-2xl flex justify-center items-center">
No Preview Found
</div>
)}

View File

@ -11,7 +11,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
import { usePersistence } from "@/hooks/use-persistence";
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
import { ReviewSegment } from "@/types/review";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { isDesktop, isMobile, isSafari } from "react-device-detect";
import useSWR from "swr";
@ -79,6 +79,53 @@ export default function LiveDashboardView({
};
}, [visibilityListener]);
const [visibleCameras, setVisibleCameras] = useState<string[]>([]);
const visibleCameraObserver = useRef<IntersectionObserver | null>(null);
useEffect(() => {
const visibleCameras = new Set<string>();
visibleCameraObserver.current = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const camera = (entry.target as HTMLElement).dataset.camera;
if (!camera) {
return;
}
if (entry.isIntersecting) {
visibleCameras.add(camera);
} else {
visibleCameras.delete(camera);
}
setVisibleCameras([...visibleCameras]);
});
},
{ threshold: 0.5 },
);
return () => {
visibleCameraObserver.current?.disconnect();
};
}, []);
const cameraRef = useCallback(
(node: HTMLElement | null) => {
if (!visibleCameraObserver.current) {
return;
}
try {
if (node) visibleCameraObserver.current.observe(node);
} catch (e) {
// no op
}
},
// we need to listen on the value of the ref
// eslint-disable-next-line react-hooks/exhaustive-deps
[visibleCameraObserver.current],
);
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
return (
@ -149,9 +196,12 @@ export default function LiveDashboardView({
}
return (
<LivePlayer
cameraRef={cameraRef}
key={camera.name}
className={grow}
windowVisible={windowVisible}
windowVisible={
windowVisible && visibleCameras.includes(camera.name)
}
cameraConfig={camera}
preferredLiveMode={isSafari ? "webrtc" : "mse"}
onClick={() => onSelectCamera(camera.name)}