Files
frigate/web/src/hooks/use-camera-live-mode.ts
T

151 lines
4.9 KiB
TypeScript
Raw Normal View History

2024-02-10 05:30:53 -07:00
import { CameraConfig, FrigateConfig } from "@/types/frigateConfig";
2025-08-22 07:04:30 -05:00
import { useCallback, useEffect, useState, useMemo } from "react";
2024-02-10 05:30:53 -07:00
import useSWR from "swr";
2025-11-30 06:54:42 -06:00
import { LivePlayerMode } from "@/types/live";
import useDeferredStreamMetadata from "./use-deferred-stream-metadata";
import { detectCameraAudioFeatures } from "@/utils/cameraUtil";
2024-02-10 05:30:53 -07:00
export default function useCameraLiveMode(
2024-08-17 13:16:48 -05:00
cameras: CameraConfig[],
windowVisible: boolean,
2025-11-11 16:00:54 -07:00
activeStreams?: { [cameraName: string]: string },
2024-08-17 13:16:48 -05:00
) {
2024-02-10 05:30:53 -07:00
const { data: config } = useSWR<FrigateConfig>("config");
2025-08-22 07:04:30 -05:00
2025-11-30 06:54:42 -06:00
// Compute which streams need metadata (restreamed streams only)
const restreamedStreamNames = useMemo(() => {
if (!cameras || !config) return [];
2025-08-22 07:04:30 -05:00
const streamNames = new Set<string>();
cameras.forEach((camera) => {
2026-02-27 21:02:19 -06:00
if (activeStreams && activeStreams[camera.name]) {
const selectedStreamName = activeStreams[camera.name];
const isRestreamed = Object.keys(config.go2rtc.streams || {}).includes(
selectedStreamName,
);
2025-08-22 07:04:30 -05:00
2026-02-27 21:02:19 -06:00
if (isRestreamed) {
streamNames.add(selectedStreamName);
2025-11-11 16:00:54 -07:00
}
2026-02-27 21:02:19 -06:00
} else {
Object.values(camera.live.streams).forEach((streamName) => {
const isRestreamed = Object.keys(
config.go2rtc.streams || {},
).includes(streamName);
if (isRestreamed) {
streamNames.add(streamName);
}
});
2025-08-22 07:04:30 -05:00
}
});
2025-11-30 06:54:42 -06:00
return Array.from(streamNames);
2025-11-11 16:00:54 -07:00
}, [cameras, config, activeStreams]);
2025-08-22 07:04:30 -05:00
2025-11-30 06:54:42 -06:00
// Fetch stream metadata with deferred loading (doesn't block initial render)
const streamMetadata = useDeferredStreamMetadata(restreamedStreamNames);
2025-02-10 10:42:35 -06:00
2025-11-30 06:54:42 -06:00
// Compute live mode states
2024-08-17 13:16:48 -05:00
const [preferredLiveModes, setPreferredLiveModes] = useState<{
[key: string]: LivePlayerMode;
}>({});
2025-02-10 10:42:35 -06:00
const [isRestreamedStates, setIsRestreamedStates] = useState<{
[key: string]: boolean;
}>({});
const [supportsAudioOutputStates, setSupportsAudioOutputStates] = useState<{
[key: string]: {
supportsAudio: boolean;
cameraName: string;
};
}>({});
2024-02-10 05:30:53 -07:00
2024-08-17 13:16:48 -05:00
useEffect(() => {
2025-12-16 21:35:43 -07:00
if (!cameras || cameras.length === 0) return;
2024-02-10 05:30:53 -07:00
2024-08-17 13:16:48 -05:00
const mseSupported =
"MediaSource" in window || "ManagedMediaSource" in window;
2025-02-10 10:42:35 -06:00
const newPreferredLiveModes: { [key: string]: LivePlayerMode } = {};
const newIsRestreamedStates: { [key: string]: boolean } = {};
const newSupportsAudioOutputStates: {
[key: string]: { supportsAudio: boolean; cameraName: string };
} = {};
2024-08-17 13:16:48 -05:00
2025-02-10 10:42:35 -06:00
cameras.forEach((camera) => {
2026-02-27 21:02:19 -06:00
const selectedStreamName =
activeStreams?.[camera.name] ?? Object.values(camera.live.streams)[0];
2025-02-10 10:42:35 -06:00
const isRestreamed =
config &&
2026-02-27 21:02:19 -06:00
Object.keys(config.go2rtc.streams || {}).includes(selectedStreamName);
2025-02-10 10:42:35 -06:00
newIsRestreamedStates[camera.name] = isRestreamed ?? false;
if (!mseSupported) {
newPreferredLiveModes[camera.name] = isRestreamed ? "webrtc" : "jsmpeg";
} else {
newPreferredLiveModes[camera.name] = isRestreamed ? "mse" : "jsmpeg";
}
2025-11-30 06:54:42 -06:00
// Check each stream for audio support
2025-02-10 10:42:35 -06:00
if (isRestreamed) {
Object.values(camera.live.streams).forEach((streamName) => {
2025-11-30 06:54:42 -06:00
const metadata = streamMetadata[streamName];
const audioFeatures = detectCameraAudioFeatures(metadata);
2025-02-10 10:42:35 -06:00
newSupportsAudioOutputStates[streamName] = {
supportsAudio: audioFeatures.audioOutput,
2025-02-10 10:42:35 -06:00
cameraName: camera.name,
};
});
} else {
newSupportsAudioOutputStates[camera.name] = {
supportsAudio: false,
cameraName: camera.name,
};
}
});
2024-02-10 05:30:53 -07:00
2024-08-17 13:16:48 -05:00
setPreferredLiveModes(newPreferredLiveModes);
2025-02-10 10:42:35 -06:00
setIsRestreamedStates(newIsRestreamedStates);
setSupportsAudioOutputStates(newSupportsAudioOutputStates);
2026-02-27 21:02:19 -06:00
}, [activeStreams, cameras, config, windowVisible, streamMetadata]);
2024-02-10 05:30:53 -07:00
2024-08-17 13:16:48 -05:00
const resetPreferredLiveMode = useCallback(
(cameraName: string) => {
const mseSupported =
"MediaSource" in window || "ManagedMediaSource" in window;
2026-02-27 21:02:19 -06:00
const cameraConfig = cameras.find((camera) => camera.name === cameraName);
const selectedStreamName =
activeStreams?.[cameraName] ??
(cameraConfig
? Object.values(cameraConfig.live.streams)[0]
: cameraName);
2024-08-17 13:16:48 -05:00
const isRestreamed =
2026-02-27 21:02:19 -06:00
config &&
Object.keys(config.go2rtc.streams || {}).includes(selectedStreamName);
2024-08-17 13:16:48 -05:00
setPreferredLiveModes((prevModes) => {
const newModes = { ...prevModes };
if (!mseSupported) {
newModes[cameraName] = isRestreamed ? "webrtc" : "jsmpeg";
} else {
newModes[cameraName] = isRestreamed ? "mse" : "jsmpeg";
}
return newModes;
});
},
2026-02-27 21:02:19 -06:00
[activeStreams, cameras, config],
2024-02-10 05:30:53 -07:00
);
2025-02-10 10:42:35 -06:00
return {
preferredLiveModes,
setPreferredLiveModes,
resetPreferredLiveMode,
isRestreamedStates,
supportsAudioOutputStates,
2025-11-30 06:54:42 -06:00
streamMetadata,
2025-02-10 10:42:35 -06:00
};
2024-02-10 05:30:53 -07:00
}