Dynamically enable/disable cameras (#16894)

* config options

* metrics

* stop and restart ffmpeg processes

* dispatcher

* frontend websocket

* buttons for testing

* don't recreate log pipe

* add/remove cam from birdseye when enabling/disabling

* end all objects and send empty camera activity

* enable/disable switch in ui

* disable buttons when camera is disabled

* use enabled_in_config for some frontend checks

* tweaks

* handle settings pane with disabled cameras

* frontend tweaks

* change to debug log

* mqtt docs

* tweak

* ensure all ffmpeg processes are initially started

* clean up

* use zmq

* remove camera metrics

* remove camera metrics

* tweaks

* frontend tweaks
This commit is contained in:
Josh Hawkins
2025-03-03 09:30:52 -06:00
committed by GitHub
parent 71e6e04d77
commit 531042467a
24 changed files with 713 additions and 202 deletions

View File

@@ -22,6 +22,7 @@ import { TbExclamationCircle } from "react-icons/tb";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import { baseUrl } from "@/api/baseUrl";
import { PlayerStats } from "./PlayerStats";
import { LuVideoOff } from "react-icons/lu";
type LivePlayerProps = {
cameraRef?: (ref: HTMLDivElement | null) => void;
@@ -86,8 +87,13 @@ export default function LivePlayer({
// camera activity
const { activeMotion, activeTracking, objects, offline } =
useCameraActivity(cameraConfig);
const {
enabled: cameraEnabled,
activeMotion,
activeTracking,
objects,
offline,
} = useCameraActivity(cameraConfig);
const cameraActive = useMemo(
() =>
@@ -191,12 +197,37 @@ export default function LivePlayer({
setLiveReady(true);
}, []);
// enabled states
const [isReEnabling, setIsReEnabling] = useState(false);
const prevCameraEnabledRef = useRef(cameraEnabled);
useEffect(() => {
if (!prevCameraEnabledRef.current && cameraEnabled) {
// Camera enabled
setLiveReady(false);
setIsReEnabling(true);
setKey((prevKey) => prevKey + 1);
} else if (prevCameraEnabledRef.current && !cameraEnabled) {
// Camera disabled
setLiveReady(false);
setKey((prevKey) => prevKey + 1);
}
prevCameraEnabledRef.current = cameraEnabled;
}, [cameraEnabled]);
useEffect(() => {
if (liveReady && isReEnabling) {
setIsReEnabling(false);
}
}, [liveReady, isReEnabling]);
if (!cameraConfig) {
return <ActivityIndicator />;
}
let player;
if (!autoLive || !streamName) {
if (!autoLive || !streamName || !cameraEnabled) {
player = null;
} else if (preferredLiveMode == "webrtc") {
player = (
@@ -267,6 +298,22 @@ export default function LivePlayer({
player = <ActivityIndicator />;
}
// if (cameraConfig.name == "lpr")
// console.log(
// cameraConfig.name,
// "enabled",
// cameraEnabled,
// "prev enabled",
// prevCameraEnabledRef.current,
// "offline",
// offline,
// "show still",
// showStillWithoutActivity,
// "live ready",
// liveReady,
// player,
// );
return (
<div
ref={cameraRef ?? internalContainerRef}
@@ -287,16 +334,18 @@ export default function LivePlayer({
}
}}
>
{((showStillWithoutActivity && !liveReady) || liveReady) && (
<>
<div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-[30%] w-full rounded-lg bg-gradient-to-b from-black/20 to-transparent md:rounded-2xl"></div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[10%] w-full rounded-lg bg-gradient-to-t from-black/20 to-transparent md:rounded-2xl"></div>
</>
)}
{cameraEnabled &&
((showStillWithoutActivity && !liveReady) || liveReady) && (
<>
<div className="pointer-events-none absolute inset-x-0 top-0 z-10 h-[30%] w-full rounded-lg bg-gradient-to-b from-black/20 to-transparent md:rounded-2xl"></div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[10%] w-full rounded-lg bg-gradient-to-t from-black/20 to-transparent md:rounded-2xl"></div>
</>
)}
{player}
{!offline && !showStillWithoutActivity && !liveReady && (
<ActivityIndicator />
)}
{cameraEnabled &&
!offline &&
(!showStillWithoutActivity || isReEnabling) &&
!liveReady && <ActivityIndicator />}
{((showStillWithoutActivity && !liveReady) || liveReady) &&
objects.length > 0 && (
@@ -344,7 +393,9 @@ export default function LivePlayer({
<div
className={cn(
"absolute inset-0 w-full",
showStillWithoutActivity && !liveReady ? "visible" : "invisible",
showStillWithoutActivity && !liveReady && !isReEnabling
? "visible"
: "invisible",
)}
>
<AutoUpdatingCameraImage
@@ -371,6 +422,17 @@ export default function LivePlayer({
</div>
)}
{!cameraEnabled && (
<div className="relative flex h-full w-full items-center justify-center">
<div className="flex h-32 flex-col items-center justify-center rounded-lg p-4 md:h-48 md:w-48">
<LuVideoOff className="mb-2 size-8 md:size-10" />
<p className="max-w-32 text-center text-sm md:max-w-40 md:text-base">
Camera is disabled
</p>
</div>
</div>
)}
<div className="absolute right-2 top-2">
{autoLive &&
!offline &&
@@ -378,7 +440,7 @@ export default function LivePlayer({
((showStillWithoutActivity && !liveReady) || liveReady) && (
<MdCircle className="mr-2 size-2 animate-pulse text-danger shadow-danger drop-shadow-md" />
)}
{offline && showStillWithoutActivity && (
{((offline && showStillWithoutActivity) || !cameraEnabled) && (
<Chip
className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize`}
>