From 59963fc47e98d515734e3a6e327eb7110db02f7e Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:42:38 -0600 Subject: [PATCH] Camera Wizard tweaks (#20773) * add switch to use go2rtc ffmpeg mode * i18n * move testing state outside of button --- web/public/locales/en/views/settings.json | 2 + .../settings/CameraWizardDialog.tsx | 10 +-- .../settings/wizard/Step1NameCamera.tsx | 11 ++-- .../settings/wizard/Step2StreamConfig.tsx | 18 ++---- .../settings/wizard/Step3Validation.tsx | 62 +++++++++++++++++-- web/src/types/cameraWizard.ts | 4 +- 6 files changed, 80 insertions(+), 27 deletions(-) diff --git a/web/public/locales/en/views/settings.json b/web/public/locales/en/views/settings.json index f50272596..fe837663c 100644 --- a/web/public/locales/en/views/settings.json +++ b/web/public/locales/en/views/settings.json @@ -271,6 +271,8 @@ "disconnectStream": "Disconnect", "estimatedBandwidth": "Estimated Bandwidth", "roles": "Roles", + "ffmpegModule": "Use stream compatibility mode", + "ffmpegModuleDescription": "If the stream does not load after several attempts, try enabling this. When enabled, Frigate will use the ffmpeg module with go2rtc. This may provide better compatibility with some camera streams.", "none": "None", "error": "Error", "streamValidated": "Stream {{number}} validated successfully", diff --git a/web/src/components/settings/CameraWizardDialog.tsx b/web/src/components/settings/CameraWizardDialog.tsx index db1804982..6611a9dd6 100644 --- a/web/src/components/settings/CameraWizardDialog.tsx +++ b/web/src/components/settings/CameraWizardDialog.tsx @@ -174,9 +174,7 @@ export default function CameraWizardDialog({ ...(friendlyName && { friendly_name: friendlyName }), ffmpeg: { inputs: wizardData.streams.map((stream, index) => { - const isRestreamed = - wizardData.restreamIds?.includes(stream.id) ?? false; - if (isRestreamed) { + if (stream.restream) { const go2rtcStreamName = wizardData.streams!.length === 1 ? finalCameraName @@ -234,7 +232,11 @@ export default function CameraWizardDialog({ wizardData.streams!.length === 1 ? finalCameraName : `${finalCameraName}_${index + 1}`; - go2rtcStreams[streamName] = [stream.url]; + + const streamUrl = stream.useFfmpeg + ? `ffmpeg:${stream.url}` + : stream.url; + go2rtcStreams[streamName] = [streamUrl]; }); if (Object.keys(go2rtcStreams).length > 0) { diff --git a/web/src/components/settings/wizard/Step1NameCamera.tsx b/web/src/components/settings/wizard/Step1NameCamera.tsx index df6e55269..6eeb2f91c 100644 --- a/web/src/components/settings/wizard/Step1NameCamera.tsx +++ b/web/src/components/settings/wizard/Step1NameCamera.tsx @@ -608,6 +608,12 @@ export default function Step1NameCamera({ )} + {isTesting && ( +
+ + {testStatus} +
+ )}
)}
diff --git a/web/src/components/settings/wizard/Step2StreamConfig.tsx b/web/src/components/settings/wizard/Step2StreamConfig.tsx index 5827e6467..a9cb00c2e 100644 --- a/web/src/components/settings/wizard/Step2StreamConfig.tsx +++ b/web/src/components/settings/wizard/Step2StreamConfig.tsx @@ -201,16 +201,12 @@ export default function Step2StreamConfig({ const setRestream = useCallback( (streamId: string) => { - const currentIds = wizardData.restreamIds || []; - const isSelected = currentIds.includes(streamId); - const newIds = isSelected - ? currentIds.filter((id) => id !== streamId) - : [...currentIds, streamId]; - onUpdate({ - restreamIds: newIds, - }); + const stream = streams.find((s) => s.id === streamId); + if (!stream) return; + + updateStream(streamId, { restream: !stream.restream }); }, - [wizardData.restreamIds, onUpdate], + [streams, updateStream], ); const hasDetectRole = streams.some((s) => s.roles.includes("detect")); @@ -435,9 +431,7 @@ export default function Step2StreamConfig({ {t("cameraWizard.step2.go2rtc")} setRestream(stream.id)} /> diff --git a/web/src/components/settings/wizard/Step3Validation.tsx b/web/src/components/settings/wizard/Step3Validation.tsx index 9f4b25330..a0dd72e7e 100644 --- a/web/src/components/settings/wizard/Step3Validation.tsx +++ b/web/src/components/settings/wizard/Step3Validation.tsx @@ -1,7 +1,13 @@ import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; +import { Switch } from "@/components/ui/switch"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { useTranslation } from "react-i18next"; -import { LuRotateCcw } from "react-icons/lu"; +import { LuRotateCcw, LuInfo } from "react-icons/lu"; import { useState, useCallback, useMemo, useEffect } from "react"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import axios from "axios"; @@ -216,7 +222,6 @@ export default function Step3Validation({ brandTemplate: wizardData.brandTemplate, customUrl: wizardData.customUrl, streams: wizardData.streams, - restreamIds: wizardData.restreamIds, }; onSave(configData); @@ -322,6 +327,51 @@ export default function Step3Validation({ )} + {result?.success && ( +
+
+ + {t("cameraWizard.step3.ffmpegModule")} + + + + + + +
+
+ {t("cameraWizard.step3.ffmpegModule")} +
+
+ {t( + "cameraWizard.step3.ffmpegModuleDescription", + )} +
+
+
+
+
+ { + onUpdate({ + streams: streams.map((s) => + s.id === stream.id + ? { ...s, useFfmpeg: checked } + : s, + ), + }); + }} + /> +
+ )} +
{stream.url} @@ -491,8 +541,7 @@ function StreamIssues({ // Restreaming check if (stream.roles.includes("record")) { - const restreamIds = wizardData.restreamIds || []; - if (restreamIds.includes(stream.id)) { + if (stream.restream) { result.push({ type: "warning", message: t("cameraWizard.step3.issues.restreamingWarning"), @@ -660,9 +709,10 @@ function StreamPreview({ stream, onBandwidthUpdate }: StreamPreviewProps) { useEffect(() => { // Register stream with go2rtc + const streamUrl = stream.useFfmpeg ? `ffmpeg:${stream.url}` : stream.url; axios .put(`go2rtc/streams/${streamId}`, null, { - params: { src: stream.url }, + params: { src: streamUrl }, }) .then(() => { // Add small delay to allow go2rtc api to run and initialize the stream @@ -680,7 +730,7 @@ function StreamPreview({ stream, onBandwidthUpdate }: StreamPreviewProps) { // do nothing on cleanup errors - go2rtc won't consume the streams }); }; - }, [stream.url, streamId]); + }, [stream.url, stream.useFfmpeg, streamId]); const resolution = stream.testResult?.resolution; let aspectRatio = "16/9"; diff --git a/web/src/types/cameraWizard.ts b/web/src/types/cameraWizard.ts index f80dc60c2..a37eafafc 100644 --- a/web/src/types/cameraWizard.ts +++ b/web/src/types/cameraWizard.ts @@ -85,6 +85,8 @@ export type StreamConfig = { quality?: string; testResult?: TestResult; userTested?: boolean; + useFfmpeg?: boolean; + restream?: boolean; }; export type TestResult = { @@ -105,7 +107,6 @@ export type WizardFormData = { brandTemplate?: CameraBrand; customUrl?: string; streams?: StreamConfig[]; - restreamIds?: string[]; }; // API Response Types @@ -146,6 +147,7 @@ export type CameraConfigData = { inputs: { path: string; roles: string[]; + input_args?: string; }[]; }; live?: {