refactor(WebRTC): simplify WebRTCVideo component and enhance connection error handling

This commit is contained in:
Adam Shiervani 2025-03-24 20:12:31 +01:00
parent 344a549f0b
commit e660dd3870
2 changed files with 28 additions and 21 deletions

View File

@ -19,7 +19,7 @@ import { HDMIErrorOverlay } from "./VideoOverlay";
import { ConnectionErrorOverlay } from "./VideoOverlay"; import { ConnectionErrorOverlay } from "./VideoOverlay";
import { LoadingOverlay } from "./VideoOverlay"; import { LoadingOverlay } from "./VideoOverlay";
export default function WebRTCVideo({ connectionFailed }: { connectionFailed: boolean }) { export default function WebRTCVideo() {
// Video and stream related refs and states // Video and stream related refs and states
const videoElm = useRef<HTMLVideoElement>(null); const videoElm = useRef<HTMLVideoElement>(null);
const mediaStream = useRTCStore(state => state.mediaStream); const mediaStream = useRTCStore(state => state.mediaStream);
@ -47,10 +47,9 @@ export default function WebRTCVideo({ connectionFailed }: { connectionFailed: bo
const hdmiState = useVideoStore(state => state.hdmiState); const hdmiState = useVideoStore(state => state.hdmiState);
const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState); const hdmiError = ["no_lock", "no_signal", "out_of_range"].includes(hdmiState);
const isLoading = !hdmiError && !isPlaying; const isLoading = !hdmiError && !isPlaying;
const isConnectionError = const isConnectionError = ["error", "failed", "disconnected", "closed"].includes(
["error", "failed", "disconnected"].includes(peerConnectionState || "") || peerConnectionState || "",
// Connection failed is passed from the parent component and becomes true after multiple failed connection attempts, indicating we should stop trying );
connectionFailed;
// Keyboard related states // Keyboard related states
const { setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive } = const { setIsNumLockActive, setIsCapsLockActive, setIsScrollLockActive } =

View File

@ -142,13 +142,27 @@ export default function KvmIdRoute() {
const navigate = useNavigate(); const navigate = useNavigate();
const { otaState, setOtaState, setModalView } = useUpdateStore(); const { otaState, setOtaState, setModalView } = useUpdateStore();
const closePeerConnection = useCallback(
function closePeerConnection() {
peerConnection?.close();
// "closed" is a valid RTCPeerConnection state according to the WebRTC spec
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState#closed
// However, the onconnectionstatechange event doesn't fire when close() is called manually
// So we need to explicitly update our state to maintain consistency
// I don't know why this is happening, but this is the best way I can think of to handle it
setPeerConnectionState("closed");
},
[peerConnection, setPeerConnectionState],
);
useEffect(() => { useEffect(() => {
const connectionAttemptsThreshold = 30; const connectionAttemptsThreshold = 30;
if (connectionAttempts > connectionAttemptsThreshold) { if (connectionAttempts > connectionAttemptsThreshold) {
console.log(`Connection failed after ${connectionAttempts} attempts.`); console.log(`Connection failed after ${connectionAttempts} attempts.`);
setConnectionFailed(true); setConnectionFailed(true);
closePeerConnection();
} }
}, [connectionAttempts]); }, [connectionAttempts, closePeerConnection]);
useEffect(() => { useEffect(() => {
// Skip if already connected // Skip if already connected
@ -168,21 +182,15 @@ export default function KvmIdRoute() {
); );
// Fail connection if it's been over X seconds since we started connecting // Fail connection if it's been over X seconds since we started connecting
if (elapsedTime > 30 * 1000) { if (elapsedTime > 1 * 1000) {
console.error(`Connection failed after ${elapsedTime} ms.`); console.error(`Connection failed after ${elapsedTime} ms.`);
setConnectionFailed(true); setConnectionFailed(true);
closePeerConnection();
} }
}, 1000); }, 1000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [connectedAt, connectionFailed, startedConnectingAt]); }, [closePeerConnection, connectedAt, connectionFailed, startedConnectingAt]);
useEffect(() => {
if (connectionFailed) {
console.log("Closing peer connection");
peerConnection?.close();
}
}, [connectionFailed, peerConnection]);
const sdp = useCallback( const sdp = useCallback(
async (event: RTCPeerConnectionIceEvent, pc: RTCPeerConnection) => { async (event: RTCPeerConnectionIceEvent, pc: RTCPeerConnection) => {
@ -219,7 +227,7 @@ export default function KvmIdRoute() {
// - In device mode, the device api would timeout, the fetch would throw an error, therefore the catch block would be hit // - In device mode, the device api would timeout, the fetch would throw an error, therefore the catch block would be hit
// Regardless, we should close the peer connection and let the useInterval handle reconnecting // Regardless, we should close the peer connection and let the useInterval handle reconnecting
if (!res.ok) { if (!res.ok) {
pc?.close(); closePeerConnection();
console.error(`Error setting SDP - Status: ${res.status}}`, json); console.error(`Error setting SDP - Status: ${res.status}}`, json);
return; return;
} }
@ -230,10 +238,10 @@ export default function KvmIdRoute() {
).catch(e => console.log(`Error setting remote description: ${e}`)); ).catch(e => console.log(`Error setting remote description: ${e}`));
} catch (error) { } catch (error) {
console.error(`Error setting SDP: ${error}`); console.error(`Error setting SDP: ${error}`);
pc?.close(); closePeerConnection();
} }
}, },
[navigate, params.id], [closePeerConnection, navigate, params.id],
); );
const connectWebRTC = useCallback(async () => { const connectWebRTC = useCallback(async () => {
@ -310,9 +318,9 @@ export default function KvmIdRoute() {
return; return;
} }
// This is final and we declare connection failure // In certain cases, we want to never connect again. This happens when we've tried for a long time and failed
if (connectionFailed) { if (connectionFailed) {
console.log("Connection failed. We're unable to establish a connection"); console.log("Connection failed. We won't attempt to connect again.");
return; return;
} }
@ -587,7 +595,7 @@ export default function KvmIdRoute() {
/> />
<div className="flex h-full overflow-hidden"> <div className="flex h-full overflow-hidden">
<WebRTCVideo connectionFailed={connectionFailed} /> <WebRTCVideo />
<SidebarContainer sidebarView={sidebarView} /> <SidebarContainer sidebarView={sidebarView} />
</div> </div>
</div> </div>