mirror of https://github.com/jetkvm/kvm.git
refactor(WebRTC): simplify WebRTCVideo component and enhance connection error handling
This commit is contained in:
parent
344a549f0b
commit
e660dd3870
|
@ -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 } =
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue