mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 13:34:13 +03:00
Miscellaneous fixes (#20875)
* Improve stream fetching logic * Reduce need to revalidate stream info * fix frigate+ frame submission * add UI setting to configure jsmpeg fallback timeout * hide settings dropdown when fullscreen * Fix arcface running on OpenVINO --------- Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
parent
a623150811
commit
f1a05d0f9b
@ -255,6 +255,7 @@ class OpenVINOModelRunner(BaseModelRunner):
|
|||||||
def __init__(self, model_path: str, device: str, model_type: str, **kwargs):
|
def __init__(self, model_path: str, device: str, model_type: str, **kwargs):
|
||||||
self.model_path = model_path
|
self.model_path = model_path
|
||||||
self.device = device
|
self.device = device
|
||||||
|
self.model_type = model_type
|
||||||
|
|
||||||
if device == "NPU" and not OpenVINOModelRunner.is_model_npu_supported(
|
if device == "NPU" and not OpenVINOModelRunner.is_model_npu_supported(
|
||||||
model_type
|
model_type
|
||||||
@ -341,6 +342,13 @@ class OpenVINOModelRunner(BaseModelRunner):
|
|||||||
# Lock prevents concurrent access to infer_request
|
# Lock prevents concurrent access to infer_request
|
||||||
# Needed for JinaV2: genai thread (text) + embeddings thread (vision)
|
# Needed for JinaV2: genai thread (text) + embeddings thread (vision)
|
||||||
with self._inference_lock:
|
with self._inference_lock:
|
||||||
|
from frigate.embeddings.types import EnrichmentModelTypeEnum
|
||||||
|
|
||||||
|
if self.model_type in [EnrichmentModelTypeEnum.arcface.value]:
|
||||||
|
# For face recognition models, create a fresh infer_request
|
||||||
|
# for each inference to avoid state pollution that causes incorrect results.
|
||||||
|
self.infer_request = self.compiled_model.create_infer_request()
|
||||||
|
|
||||||
# Handle single input case for backward compatibility
|
# Handle single input case for backward compatibility
|
||||||
if (
|
if (
|
||||||
len(inputs) == 1
|
len(inputs) == 1
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"masksAndZones": "Mask and Zone Editor - Frigate",
|
"masksAndZones": "Mask and Zone Editor - Frigate",
|
||||||
"motionTuner": "Motion Tuner - Frigate",
|
"motionTuner": "Motion Tuner - Frigate",
|
||||||
"object": "Debug - Frigate",
|
"object": "Debug - Frigate",
|
||||||
"general": "General Settings - Frigate",
|
"general": "UI Settings - Frigate",
|
||||||
"frigatePlus": "Frigate+ Settings - Frigate",
|
"frigatePlus": "Frigate+ Settings - Frigate",
|
||||||
"notifications": "Notification Settings - Frigate"
|
"notifications": "Notification Settings - Frigate"
|
||||||
},
|
},
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"noCamera": "No Camera"
|
"noCamera": "No Camera"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"title": "General Settings",
|
"title": "UI Settings",
|
||||||
"liveDashboard": {
|
"liveDashboard": {
|
||||||
"title": "Live Dashboard",
|
"title": "Live Dashboard",
|
||||||
"automaticLiveView": {
|
"automaticLiveView": {
|
||||||
@ -51,6 +51,10 @@
|
|||||||
"displayCameraNames": {
|
"displayCameraNames": {
|
||||||
"label": "Always Show Camera Names",
|
"label": "Always Show Camera Names",
|
||||||
"desc": "Always show the camera names in a chip in the multi-camera live view dashboard."
|
"desc": "Always show the camera names in a chip in the multi-camera live view dashboard."
|
||||||
|
},
|
||||||
|
"liveFallbackTimeout": {
|
||||||
|
"label": "Live Player Fallback Timeout",
|
||||||
|
"desc": "When a camera's high quality live stream is unavailable, fall back to low bandwidth mode after this many seconds. Default: 3."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"storedLayouts": {
|
"storedLayouts": {
|
||||||
|
|||||||
@ -343,6 +343,10 @@ export function TrackingDetails({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [displayedRecordTime]);
|
}, [displayedRecordTime]);
|
||||||
|
|
||||||
|
const onUploadFrameToPlus = useCallback(() => {
|
||||||
|
return axios.post(`/${event.camera}/plus/${currentTime}`);
|
||||||
|
}, [event.camera, currentTime]);
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
@ -388,6 +392,7 @@ export function TrackingDetails({
|
|||||||
frigateControls={true}
|
frigateControls={true}
|
||||||
onTimeUpdate={handleTimeUpdate}
|
onTimeUpdate={handleTimeUpdate}
|
||||||
onSeekToTime={handleSeekToTime}
|
onSeekToTime={handleSeekToTime}
|
||||||
|
onUploadFrame={onUploadFrameToPlus}
|
||||||
isDetailMode={true}
|
isDetailMode={true}
|
||||||
camera={event.camera}
|
camera={event.camera}
|
||||||
currentTimeOverride={currentTime}
|
currentTimeOverride={currentTime}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { baseUrl } from "@/api/baseUrl";
|
import { baseUrl } from "@/api/baseUrl";
|
||||||
|
import { usePersistence } from "@/hooks/use-persistence";
|
||||||
import {
|
import {
|
||||||
LivePlayerError,
|
LivePlayerError,
|
||||||
PlayerStatsType,
|
PlayerStatsType,
|
||||||
@ -71,6 +72,8 @@ function MSEPlayer({
|
|||||||
const [errorCount, setErrorCount] = useState<number>(0);
|
const [errorCount, setErrorCount] = useState<number>(0);
|
||||||
const totalBytesLoaded = useRef(0);
|
const totalBytesLoaded = useRef(0);
|
||||||
|
|
||||||
|
const [fallbackTimeout] = usePersistence<number>("liveFallbackTimeout", 3);
|
||||||
|
|
||||||
const videoRef = useRef<HTMLVideoElement>(null);
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
const wsRef = useRef<WebSocket | null>(null);
|
const wsRef = useRef<WebSocket | null>(null);
|
||||||
const reconnectTIDRef = useRef<number | null>(null);
|
const reconnectTIDRef = useRef<number | null>(null);
|
||||||
@ -475,7 +478,10 @@ function MSEPlayer({
|
|||||||
setBufferTimeout(undefined);
|
setBufferTimeout(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeoutDuration = bufferTime == 0 ? 5000 : 3000;
|
const timeoutDuration =
|
||||||
|
bufferTime == 0
|
||||||
|
? (fallbackTimeout ?? 3) * 2 * 1000
|
||||||
|
: (fallbackTimeout ?? 3) * 1000;
|
||||||
setBufferTimeout(
|
setBufferTimeout(
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (
|
if (
|
||||||
@ -500,6 +506,7 @@ function MSEPlayer({
|
|||||||
onError,
|
onError,
|
||||||
onPlaying,
|
onPlaying,
|
||||||
playbackEnabled,
|
playbackEnabled,
|
||||||
|
fallbackTimeout,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { LivePlayerMode, LiveStreamMetadata } from "@/types/live";
|
|||||||
export default function useCameraLiveMode(
|
export default function useCameraLiveMode(
|
||||||
cameras: CameraConfig[],
|
cameras: CameraConfig[],
|
||||||
windowVisible: boolean,
|
windowVisible: boolean,
|
||||||
|
activeStreams?: { [cameraName: string]: string },
|
||||||
) {
|
) {
|
||||||
const { data: config } = useSWR<FrigateConfig>("config");
|
const { data: config } = useSWR<FrigateConfig>("config");
|
||||||
|
|
||||||
@ -20,16 +21,20 @@ export default function useCameraLiveMode(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (isRestreamed) {
|
if (isRestreamed) {
|
||||||
|
if (activeStreams && activeStreams[camera.name]) {
|
||||||
|
streamNames.add(activeStreams[camera.name]);
|
||||||
|
} else {
|
||||||
Object.values(camera.live.streams).forEach((streamName) => {
|
Object.values(camera.live.streams).forEach((streamName) => {
|
||||||
streamNames.add(streamName);
|
streamNames.add(streamName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return streamNames.size > 0
|
return streamNames.size > 0
|
||||||
? Array.from(streamNames).sort().join(",")
|
? Array.from(streamNames).sort().join(",")
|
||||||
: null;
|
: null;
|
||||||
}, [cameras, config]);
|
}, [cameras, config, activeStreams]);
|
||||||
|
|
||||||
const streamsFetcher = useCallback(async (key: string) => {
|
const streamsFetcher = useCallback(async (key: string) => {
|
||||||
const streamNames = key.split(",");
|
const streamNames = key.split(",");
|
||||||
@ -68,7 +73,9 @@ export default function useCameraLiveMode(
|
|||||||
[key: string]: LiveStreamMetadata;
|
[key: string]: LiveStreamMetadata;
|
||||||
}>(restreamedStreamsKey, streamsFetcher, {
|
}>(restreamedStreamsKey, streamsFetcher, {
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
dedupingInterval: 10000,
|
revalidateOnReconnect: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
dedupingInterval: 60000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [preferredLiveModes, setPreferredLiveModes] = useState<{
|
const [preferredLiveModes, setPreferredLiveModes] = useState<{
|
||||||
|
|||||||
@ -86,14 +86,6 @@ export default function DraggableGridLayout({
|
|||||||
|
|
||||||
// preferred live modes per camera
|
// preferred live modes per camera
|
||||||
|
|
||||||
const {
|
|
||||||
preferredLiveModes,
|
|
||||||
setPreferredLiveModes,
|
|
||||||
resetPreferredLiveMode,
|
|
||||||
isRestreamedStates,
|
|
||||||
supportsAudioOutputStates,
|
|
||||||
} = useCameraLiveMode(cameras, windowVisible);
|
|
||||||
|
|
||||||
const [globalAutoLive] = usePersistence("autoLiveView", true);
|
const [globalAutoLive] = usePersistence("autoLiveView", true);
|
||||||
const [displayCameraNames] = usePersistence("displayCameraNames", false);
|
const [displayCameraNames] = usePersistence("displayCameraNames", false);
|
||||||
|
|
||||||
@ -106,6 +98,33 @@ export default function DraggableGridLayout({
|
|||||||
}
|
}
|
||||||
}, [allGroupsStreamingSettings, cameraGroup]);
|
}, [allGroupsStreamingSettings, cameraGroup]);
|
||||||
|
|
||||||
|
const activeStreams = useMemo(() => {
|
||||||
|
const streams: { [cameraName: string]: string } = {};
|
||||||
|
cameras.forEach((camera) => {
|
||||||
|
const availableStreams = camera.live.streams || {};
|
||||||
|
const streamNameFromSettings =
|
||||||
|
currentGroupStreamingSettings?.[camera.name]?.streamName || "";
|
||||||
|
const streamExists =
|
||||||
|
streamNameFromSettings &&
|
||||||
|
Object.values(availableStreams).includes(streamNameFromSettings);
|
||||||
|
|
||||||
|
const streamName = streamExists
|
||||||
|
? streamNameFromSettings
|
||||||
|
: Object.values(availableStreams)[0] || "";
|
||||||
|
|
||||||
|
streams[camera.name] = streamName;
|
||||||
|
});
|
||||||
|
return streams;
|
||||||
|
}, [cameras, currentGroupStreamingSettings]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
preferredLiveModes,
|
||||||
|
setPreferredLiveModes,
|
||||||
|
resetPreferredLiveMode,
|
||||||
|
isRestreamedStates,
|
||||||
|
supportsAudioOutputStates,
|
||||||
|
} = useCameraLiveMode(cameras, windowVisible, activeStreams);
|
||||||
|
|
||||||
// grid layout
|
// grid layout
|
||||||
|
|
||||||
const ResponsiveGridLayout = useMemo(() => WidthProvider(Responsive), []);
|
const ResponsiveGridLayout = useMemo(() => WidthProvider(Responsive), []);
|
||||||
|
|||||||
@ -162,6 +162,9 @@ export default function LiveCameraView({
|
|||||||
isRestreamed ? `go2rtc/streams/${streamName}` : null,
|
isRestreamed ? `go2rtc/streams/${streamName}` : null,
|
||||||
{
|
{
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
|
revalidateOnReconnect: false,
|
||||||
|
revalidateIfStale: false,
|
||||||
|
dedupingInterval: 60000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1027,6 +1030,7 @@ function FrigateCameraFeatures({
|
|||||||
disabled={!cameraEnabled || debug || isSnapshotLoading}
|
disabled={!cameraEnabled || debug || isSnapshotLoading}
|
||||||
loading={isSnapshotLoading}
|
loading={isSnapshotLoading}
|
||||||
/>
|
/>
|
||||||
|
{!fullscreen && (
|
||||||
<DropdownMenu modal={false}>
|
<DropdownMenu modal={false}>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<div
|
<div
|
||||||
@ -1155,7 +1159,9 @@ function FrigateCameraFeatures({
|
|||||||
{t("stream.audio.tips.title")}
|
{t("stream.audio.tips.title")}
|
||||||
<div className="mt-2 flex items-center text-primary">
|
<div className="mt-2 flex items-center text-primary">
|
||||||
<Link
|
<Link
|
||||||
to={getLocaleDocUrl("configuration/live")}
|
to={getLocaleDocUrl(
|
||||||
|
"configuration/live",
|
||||||
|
)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="inline"
|
className="inline"
|
||||||
@ -1315,6 +1321,7 @@ function FrigateCameraFeatures({
|
|||||||
</div>
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -202,14 +202,6 @@ export default function LiveDashboardView({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const {
|
|
||||||
preferredLiveModes,
|
|
||||||
setPreferredLiveModes,
|
|
||||||
resetPreferredLiveMode,
|
|
||||||
isRestreamedStates,
|
|
||||||
supportsAudioOutputStates,
|
|
||||||
} = useCameraLiveMode(cameras, windowVisible);
|
|
||||||
|
|
||||||
const [globalAutoLive] = usePersistence("autoLiveView", true);
|
const [globalAutoLive] = usePersistence("autoLiveView", true);
|
||||||
const [displayCameraNames] = usePersistence("displayCameraNames", false);
|
const [displayCameraNames] = usePersistence("displayCameraNames", false);
|
||||||
|
|
||||||
@ -239,6 +231,33 @@ export default function LiveDashboardView({
|
|||||||
[visibleCameraObserver.current],
|
[visibleCameraObserver.current],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const activeStreams = useMemo(() => {
|
||||||
|
const streams: { [cameraName: string]: string } = {};
|
||||||
|
cameras.forEach((camera) => {
|
||||||
|
const availableStreams = camera.live.streams || {};
|
||||||
|
const streamNameFromSettings =
|
||||||
|
currentGroupStreamingSettings?.[camera.name]?.streamName || "";
|
||||||
|
const streamExists =
|
||||||
|
streamNameFromSettings &&
|
||||||
|
Object.values(availableStreams).includes(streamNameFromSettings);
|
||||||
|
|
||||||
|
const streamName = streamExists
|
||||||
|
? streamNameFromSettings
|
||||||
|
: Object.values(availableStreams)[0] || "";
|
||||||
|
|
||||||
|
streams[camera.name] = streamName;
|
||||||
|
});
|
||||||
|
return streams;
|
||||||
|
}, [cameras, currentGroupStreamingSettings]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
preferredLiveModes,
|
||||||
|
setPreferredLiveModes,
|
||||||
|
resetPreferredLiveMode,
|
||||||
|
isRestreamedStates,
|
||||||
|
supportsAudioOutputStates,
|
||||||
|
} = useCameraLiveMode(cameras, windowVisible, activeStreams);
|
||||||
|
|
||||||
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
|
const birdseyeConfig = useMemo(() => config?.birdseye, [config]);
|
||||||
|
|
||||||
const handleError = useCallback(
|
const handleError = useCallback(
|
||||||
|
|||||||
@ -99,6 +99,10 @@ export default function UiSettingsView() {
|
|||||||
const [playbackRate, setPlaybackRate] = usePersistence("playbackRate", 1);
|
const [playbackRate, setPlaybackRate] = usePersistence("playbackRate", 1);
|
||||||
const [weekStartsOn, setWeekStartsOn] = usePersistence("weekStartsOn", 0);
|
const [weekStartsOn, setWeekStartsOn] = usePersistence("weekStartsOn", 0);
|
||||||
const [alertVideos, setAlertVideos] = usePersistence("alertVideos", true);
|
const [alertVideos, setAlertVideos] = usePersistence("alertVideos", true);
|
||||||
|
const [fallbackTimeout, setFallbackTimeout] = usePersistence(
|
||||||
|
"liveFallbackTimeout",
|
||||||
|
3,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -161,6 +165,48 @@ export default function UiSettingsView() {
|
|||||||
<p>{t("general.liveDashboard.displayCameraNames.desc")}</p>
|
<p>{t("general.liveDashboard.displayCameraNames.desc")}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex flex-row items-center justify-start gap-2">
|
||||||
|
<Label
|
||||||
|
className="cursor-pointer"
|
||||||
|
htmlFor="live-fallback-timeout"
|
||||||
|
>
|
||||||
|
{t("general.liveDashboard.liveFallbackTimeout.label")}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="my-2 max-w-5xl text-sm text-muted-foreground">
|
||||||
|
<p>{t("general.liveDashboard.liveFallbackTimeout.desc")}</p>
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
value={fallbackTimeout?.toString()}
|
||||||
|
onValueChange={(value) => setFallbackTimeout(parseInt(value))}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-36">
|
||||||
|
{t("time.second", {
|
||||||
|
ns: "common",
|
||||||
|
time: fallbackTimeout,
|
||||||
|
count: fallbackTimeout,
|
||||||
|
})}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((timeout) => (
|
||||||
|
<SelectItem
|
||||||
|
key={timeout}
|
||||||
|
className="cursor-pointer"
|
||||||
|
value={timeout.toString()}
|
||||||
|
>
|
||||||
|
{t("time.second", {
|
||||||
|
ns: "common",
|
||||||
|
time: timeout,
|
||||||
|
count: timeout,
|
||||||
|
})}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="my-3 flex w-full flex-col space-y-6">
|
<div className="my-3 flex w-full flex-col space-y-6">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user