look for backchannel on all registered streams on save

avoids potential issues with a timeout in stream registration
This commit is contained in:
Josh Hawkins 2025-12-12 14:22:24 -06:00
parent 224bf16a4a
commit c7f99ee37c

View File

@ -42,6 +42,9 @@ export default function Step4Validation({
const [measuredBandwidth, setMeasuredBandwidth] = useState< const [measuredBandwidth, setMeasuredBandwidth] = useState<
Map<string, number> Map<string, number>
>(new Map()); >(new Map());
const [registeredStreamIds, setRegisteredStreamIds] = useState<
Map<string, string>
>(new Map());
const streams = useMemo(() => wizardData.streams || [], [wizardData.streams]); const streams = useMemo(() => wizardData.streams || [], [wizardData.streams]);
@ -127,15 +130,10 @@ export default function Step4Validation({
); );
const checkBackchannel = useCallback( const checkBackchannel = useCallback(
async (go2rtcStreamId: string, useFfmpeg: boolean) => { async (go2rtcStreamId: string, useFfmpeg: boolean): Promise<boolean> => {
if (wizardData.hasBackchannel !== undefined) {
return;
}
// ffmpeg compatibility mode guarantees no backchannel connection // ffmpeg compatibility mode guarantees no backchannel connection
if (useFfmpeg) { if (useFfmpeg) {
onUpdate({ hasBackchannel: false }); return false;
return;
} }
try { try {
@ -144,12 +142,12 @@ export default function Step4Validation({
); );
const audioFeatures = detectCameraAudioFeatures(response.data, false); const audioFeatures = detectCameraAudioFeatures(response.data, false);
onUpdate({ hasBackchannel: audioFeatures.twoWayAudio }); return audioFeatures.twoWayAudio;
} catch { } catch {
onUpdate({ hasBackchannel: false }); return false;
} }
}, },
[wizardData.hasBackchannel, onUpdate], [],
); );
const validateStream = useCallback( const validateStream = useCallback(
@ -235,12 +233,31 @@ export default function Step4Validation({
} }
}, [streams, onUpdate, t, performStreamValidation]); }, [streams, onUpdate, t, performStreamValidation]);
const handleSave = useCallback(() => { const handleSave = useCallback(async () => {
if (!wizardData.cameraName || !wizardData.streams?.length) { if (!wizardData.cameraName || !wizardData.streams?.length) {
toast.error(t("cameraWizard.step4.saveError")); toast.error(t("cameraWizard.step4.saveError"));
return; return;
} }
const candidateStreams =
wizardData.streams?.filter(
(s) => s.testResult?.success && !(s.useFfmpeg ?? false),
) || [];
let hasBackchannelResult = false;
if (candidateStreams.length > 0) {
// Check all candidate streams for backchannel support
const backchanelChecks = candidateStreams.map((stream) => {
const actualStreamId = registeredStreamIds.get(stream.id);
return actualStreamId
? checkBackchannel(actualStreamId, stream.useFfmpeg ?? false)
: Promise.resolve(false);
});
const results = await Promise.all(backchanelChecks);
hasBackchannelResult = results.some((result) => result);
}
onUpdate({ hasBackchannel: hasBackchannelResult });
// Convert wizard data to final config format // Convert wizard data to final config format
const configData = { const configData = {
cameraName: wizardData.cameraName, cameraName: wizardData.cameraName,
@ -254,7 +271,7 @@ export default function Step4Validation({
}; };
onSave(configData); onSave(configData);
}, [wizardData, onSave, t]); }, [wizardData, onSave, t, onUpdate, checkBackchannel, registeredStreamIds]);
const canSave = useMemo(() => { const canSave = useMemo(() => {
return ( return (
@ -353,9 +370,8 @@ export default function Step4Validation({
stream={stream} stream={stream}
onBandwidthUpdate={handleBandwidthUpdate} onBandwidthUpdate={handleBandwidthUpdate}
onStreamRegistered={(go2rtcStreamId) => { onStreamRegistered={(go2rtcStreamId) => {
checkBackchannel( setRegisteredStreamIds((prev) =>
go2rtcStreamId, new Map(prev).set(stream.id, go2rtcStreamId),
stream.useFfmpeg ?? false,
); );
}} }}
/> />
@ -763,9 +779,10 @@ function StreamPreview({
}) })
.then(async () => { .then(async () => {
// Add small delay to allow go2rtc api to run and initialize the stream // Add small delay to allow go2rtc api to run and initialize the stream
await new Promise((resolve) => setTimeout(resolve, 1000)); setTimeout(() => {
setRegistered(true); setRegistered(true);
onStreamRegistered?.(streamId); onStreamRegistered?.(streamId);
}, 500);
}) })
.catch(() => { .catch(() => {
setError(true); setError(true);