Compare commits

...

2 Commits

Author SHA1 Message Date
Josh Hawkins
c5fec3271f
Improve matching go2rtc stream names with cameras (#20586)
Some checks are pending
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* improve matching go2rtc stream names with cameras

* fix unrelated lint issue
2025-10-20 10:33:02 -05:00
Josh Hawkins
0743cb57c2
fix birdseye and empty card (#20582) 2025-10-20 07:03:22 -06:00
5 changed files with 36 additions and 28 deletions

View File

@ -175,8 +175,8 @@
"exitEdit": "Exit Editing" "exitEdit": "Exit Editing"
}, },
"noCameras": { "noCameras": {
"title": "No Cameras Set Up", "title": "No Cameras Configured",
"description": "Get started by connecting a camera.", "description": "Get started by connecting a camera to Frigate.",
"buttonText": "Add Camera" "buttonText": "Add Camera"
} }
} }

View File

@ -1,27 +1,30 @@
import React from "react"; import React from "react";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import Heading from "../ui/heading"; import Heading from "../ui/heading";
import { Link } from "react-router-dom";
type EmptyCardProps = { type EmptyCardProps = {
icon: React.ReactNode; icon: React.ReactNode;
title: string; title: string;
description: string; description: string;
buttonText?: string; buttonText?: string;
link?: string;
}; };
export function EmptyCard({ export function EmptyCard({
icon, icon,
title, title,
description, description,
buttonText, buttonText,
link,
}: EmptyCardProps) { }: EmptyCardProps) {
return ( return (
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
{icon} {icon}
<Heading as="h4">{title}</Heading> <Heading as="h4">{title}</Heading>
<div className="text-secondary-foreground">{description}</div> <div className="mb-3 text-secondary-foreground">{description}</div>
{buttonText?.length && ( {buttonText?.length && (
<Button size="sm" variant="select"> <Button size="sm" variant="select">
{buttonText} <Link to={link ?? "#"}>{buttonText}</Link>
</Button> </Button>
)} )}
</div> </div>

View File

@ -150,7 +150,9 @@ export default function SearchThumbnail({
.filter( .filter(
(item) => item !== undefined && !item.includes("-verified"), (item) => item !== undefined && !item.includes("-verified"),
) )
.map((text) => getTranslatedLabel(text, searchResult.data.type)) .map((text) =>
getTranslatedLabel(text, searchResult.data.type),
)
.sort() .sort()
.join(", ") .join(", ")
.replaceAll("-verified", "")} .replaceAll("-verified", "")}

View File

@ -152,36 +152,38 @@ export default function CameraEditForm({
})) }))
: defaultValues.ffmpeg.inputs; : defaultValues.ffmpeg.inputs;
// Load go2rtc streams for this camera
const go2rtcStreams = config.go2rtc?.streams || {}; const go2rtcStreams = config.go2rtc?.streams || {};
const cameraStreams: Record<string, string[]> = {}; const cameraStreams: Record<string, string[]> = {};
// Find streams that match this camera's name pattern // get candidate stream names for this camera. could be the camera's own name,
Object.entries(go2rtcStreams).forEach(([streamName, urls]) => { // any restream names referenced by this camera, or any keys under live --> streams
if (streamName.startsWith(cameraName) || streamName === cameraName) { const validNames = new Set<string>();
cameraStreams[streamName] = Array.isArray(urls) ? urls : [urls]; validNames.add(cameraName);
}
});
// Also deduce go2rtc streams from restream URLs in camera inputs // deduce go2rtc stream names from rtsp restream inputs
camera.ffmpeg?.inputs?.forEach((input, index) => { camera.ffmpeg?.inputs?.forEach((input) => {
// exclude any query strings or trailing slashes from the stream name
const restreamMatch = input.path.match( const restreamMatch = input.path.match(
/^rtsp:\/\/127\.0\.0\.1:8554\/(.+)$/, /^rtsp:\/\/127\.0\.0\.1:8554\/([^?#/]+)(?:[?#].*)?$/,
); );
if (restreamMatch) { if (restreamMatch) {
const streamName = restreamMatch[1]; const streamName = restreamMatch[1];
// Find the corresponding go2rtc stream validNames.add(streamName);
const go2rtcStream = Object.entries(go2rtcStreams).find( }
([name]) => });
name === streamName ||
name === `${cameraName}_${index + 1}` || // Include live --> streams keys
name === cameraName, const liveStreams = camera?.live?.streams;
); if (liveStreams) {
if (go2rtcStream) { Object.keys(liveStreams).forEach((key) => {
cameraStreams[go2rtcStream[0]] = Array.isArray(go2rtcStream[1]) validNames.add(key);
? go2rtcStream[1] });
: [go2rtcStream[1]]; }
}
// Map only go2rtc entries that match the collected names
Object.entries(go2rtcStreams).forEach(([name, urls]) => {
if (validNames.has(name)) {
cameraStreams[name] = Array.isArray(urls) ? urls : [urls];
} }
}); });

View File

@ -354,7 +354,7 @@ export default function LiveDashboardView({
onSaveMuting(true); onSaveMuting(true);
}; };
if (cameras.length == 0) { if (cameras.length == 0 && !includeBirdseye) {
return <NoCameraView />; return <NoCameraView />;
} }
@ -625,6 +625,7 @@ function NoCameraView() {
title={t("noCameras.title")} title={t("noCameras.title")}
description={t("noCameras.description")} description={t("noCameras.description")}
buttonText={t("noCameras.buttonText")} buttonText={t("noCameras.buttonText")}
link="/settings?page=cameraManagement"
/> />
</div> </div>
); );