import ActivityIndicator from "@/components/indicators/activity-indicator"; import { Button } from "@/components/ui/button"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import type { HwaccelFamily, HwaccelRecommendation } from "@/types/hardware"; import axios from "axios"; import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import useSWR from "swr"; const AUTO = "auto"; const NONE = "none"; const ANY_CODEC = "any"; // ffprobe names h265 streams hevc const CODEC_ALIASES: Record = { hevc: "h265" }; function normalizeCodec(codec: string): string { const lower = codec.toLowerCase(); return CODEC_ALIASES[lower] ?? lower; } type SetupHwAccelProps = { detectorHardwareKey?: string; // camera name -> detect stream codec, the only stream hwaccel applies to detectCodecs: Record; // saved tells the wizard whether finishing needs a restart onNext: (saved: boolean) => void; onBack: () => void; onSkip: () => void; }; export default function SetupHwAccel({ detectorHardwareKey, detectCodecs, onNext, onBack, onSkip, }: SetupHwAccelProps) { const { t } = useTranslation(["views/setup"]); const cameraCodecs = useMemo( () => Object.entries(detectCodecs).map(([camera, codec]) => ({ camera, codec: normalizeCodec(codec), })), [detectCodecs], ); const query = useMemo(() => { const params = new URLSearchParams(); if (detectorHardwareKey) { params.set("detector", detectorHardwareKey); } const codecs = [...new Set(cameraCodecs.map((entry) => entry.codec))]; if (codecs.length > 0) { params.set("codecs", codecs.join(",")); } return params.toString(); }, [detectorHardwareKey, cameraCodecs]); const { data: recommendation, isLoading, error: recommendError, } = useSWR( query ? `hardware/hwaccel?${query}` : "hardware/hwaccel", { revalidateOnFocus: false }, ); const [selected, setSelected] = useState(AUTO); const [saving, setSaving] = useState(false); const families = useMemo( () => recommendation?.available ?? [], [recommendation], ); const derived = recommendation?.recommended ?? ""; /** The config a family should be saved as, or null when it writes nothing. */ const configFor = useCallback( (family: HwaccelFamily | undefined): Record | null => { if (!family) { return null; } const shared = family.presets[ANY_CODEC]; if (shared) { return { ffmpeg: { hwaccel_args: shared } }; } const perCamera = cameraCodecs .map((entry) => ({ ...entry, preset: family.presets[entry.codec] })) .filter((entry) => entry.preset); if (perCamera.length === 0) { const fallback = Object.values(family.presets)[0]; return fallback ? { ffmpeg: { hwaccel_args: fallback } } : null; } const presets = new Set(perCamera.map((entry) => entry.preset)); if (presets.size === 1 && perCamera.length === cameraCodecs.length) { return { ffmpeg: { hwaccel_args: [...presets][0] } }; } // the global stays on auto so cameras added later resolve at startup // instead of inheriting one camera's codec return { cameras: Object.fromEntries( perCamera.map((entry) => [ entry.camera, { ffmpeg: { hwaccel_args: entry.preset } }, ]), ), }; }, [cameraCodecs], ); const handleSave = useCallback(async () => { const key = selected === AUTO ? derived : selected; const configData = selected === NONE ? // an empty string would make config/set delete the key, reviving // the "auto" default { ffmpeg: { hwaccel_args: [] } } : configFor(families.find((family) => family.key === key)); // nothing to write leaves the config default of "auto" in place if (!configData) { onNext(false); return; } setSaving(true); try { await axios.put("config/set", { config_data: configData, requires_restart: 1, }); onNext(true); } catch { toast.error(t("setupWizard.errors.saveFailed")); } finally { setSaving(false); } }, [selected, derived, families, configFor, onNext, t]); if (isLoading) { return (

{t("setupWizard.hwaccel.detecting")}

); } const radioClass = (value: string) => selected === value ? "bg-selected from-selected/50 to-selected/90 text-selected" : "bg-secondary from-secondary/50 to-secondary/90 text-secondary"; return (

{t("setupWizard.hwaccel.title")}

{t("setupWizard.hwaccel.description")}

{derived ? t("setupWizard.hwaccel.autoResolved", { family: t(`setupWizard.hwaccel.families.${derived}`), }) : recommendError ? t("setupWizard.hwaccel.recommendFailed") : t("setupWizard.hwaccel.autoNone")}

{families.map((family) => (
))}
); }