mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 21:44:13 +03:00
Camera Wizard tweaks (#20773)
* add switch to use go2rtc ffmpeg mode * i18n * move testing state outside of button
This commit is contained in:
parent
31fa87ce73
commit
59963fc47e
@ -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",
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -608,6 +608,12 @@ export default function Step1NameCamera({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isTesting && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<ActivityIndicator className="size-4" />
|
||||
{testStatus}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-3 pt-3 sm:flex-row sm:justify-end sm:gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
@ -635,10 +641,7 @@ export default function Step1NameCamera({
|
||||
variant="select"
|
||||
className="flex items-center justify-center gap-2 sm:flex-1"
|
||||
>
|
||||
{isTesting && <ActivityIndicator className="size-4" />}
|
||||
{isTesting && testStatus
|
||||
? testStatus
|
||||
: t("cameraWizard.step1.testConnection")}
|
||||
{t("cameraWizard.step1.testConnection")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -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")}
|
||||
</span>
|
||||
<Switch
|
||||
checked={(wizardData.restreamIds || []).includes(
|
||||
stream.id,
|
||||
)}
|
||||
checked={stream.restream || false}
|
||||
onCheckedChange={() => setRestream(stream.id)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -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({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result?.success && (
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm">
|
||||
{t("cameraWizard.step3.ffmpegModule")}
|
||||
</span>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-4 w-4 p-0"
|
||||
>
|
||||
<LuInfo className="size-3" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="pointer-events-auto w-80 text-xs">
|
||||
<div className="space-y-2">
|
||||
<div className="font-medium">
|
||||
{t("cameraWizard.step3.ffmpegModule")}
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
{t(
|
||||
"cameraWizard.step3.ffmpegModuleDescription",
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<Switch
|
||||
checked={stream.useFfmpeg || false}
|
||||
onCheckedChange={(checked) => {
|
||||
onUpdate({
|
||||
streams: streams.map((s) =>
|
||||
s.id === stream.id
|
||||
? { ...s, useFfmpeg: checked }
|
||||
: s,
|
||||
),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-2 flex flex-col justify-between gap-1 md:flex-row md:items-center">
|
||||
<span className="break-all text-sm text-muted-foreground">
|
||||
{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";
|
||||
|
||||
@ -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?: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user