diff --git a/src/components/webRTCpoc.tsx b/src/components/webRTCpoc.tsx index c2eeeb2..f5ae7d3 100644 --- a/src/components/webRTCpoc.tsx +++ b/src/components/webRTCpoc.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import {Text} from 'react-native'; + import {MediaStream, RTCPeerConnection, RTCView} from 'react-native-webrtc'; import {API_BASE} from '@env'; @@ -16,6 +18,8 @@ export const WebRTCPOC = ({cameraName}: WebRTCPocProps) => { const cameraURL = API_BASE.replace('http', 'ws') + '/live/webrtc/api/ws?src=' + cameraName; + const [reload, setReload] = React.useState(false); + const [loading, setLoading] = React.useState(true); const [remoteStream, setRemoteStream] = React.useState( null, ); @@ -26,11 +30,20 @@ export const WebRTCPOC = ({cameraName}: WebRTCPocProps) => { const pcRef = React.useRef(null); const wsRef = React.useRef(null); + function onIceConnect() { + if (pcRef?.current?.iceConnectionState === 'connected') { + setLoading(false); + } + } + const connect = React.useCallback(async (url: string) => { + setLoading(true); pcRef.current = new RTCPeerConnection(webRTCconfig); - wsRef.current = new WebSocket(url); - const pc = pcRef.current; + // add this before connecting to websocket to ensure it is always captured. + pcRef.current.addEventListener('iceconnectionstatechange', onIceConnect); + + wsRef.current = new WebSocket(url); const ws = wsRef.current; const tracks = [ @@ -45,15 +58,12 @@ export const WebRTCPOC = ({cameraName}: WebRTCPocProps) => { setLocalStream(new MediaStream(tracks)); - pc.addEventListener('track', (event: any) => { + const remoteMediaStream = new MediaStream(undefined); + + pc.addEventListener('track', async (event: any) => { // Grab the remote track from the connected participant. const track = event?.track; if (track) { - const remoteMediaStream = new MediaStream(undefined); - console.log( - '🪵 | file: webRTCpoc.tsx:58 | ws.addEventListener | track:', - track, - ); remoteMediaStream.addTrack(event.track); setRemoteStream(remoteMediaStream); } @@ -65,6 +75,7 @@ export const WebRTCPOC = ({cameraName}: WebRTCPocProps) => { if (!ev.candidate) { return; } + const msg = { type: 'webrtc/candidate', value: ev.candidate.candidate, @@ -91,26 +102,52 @@ export const WebRTCPOC = ({cameraName}: WebRTCPocProps) => { pc.setRemoteDescription({type: 'answer', sdp: msg.value}); } }); + + // the following is to ensure that ice connection eventually becomes connected or completed + // as it can get stuck in other states which are an error state + let attempts = 0; + while ( + pc.iceConnectionState !== 'connected' && + pc.iceConnectionState !== 'completed' + ) { + // async timeout for 100ms + await new Promise(resolve => setTimeout(resolve, 100)); + attempts++; + + if (attempts > 5) { + throw new Error('Could not connect'); + } + } + + // we no longer want to listen to connected state change events + pc.removeEventListener('iceconnectedstatechange', onIceConnect); }, []); React.useEffect(() => { if (cameraURL && cameraName) { - connect(cameraURL); + connect(cameraURL).catch(() => { + // toggle the reload value on error so this useEffect will run again + setReload(!reload); + }); } return () => { remoteStream?.getTracks().forEach(t => t.stop()); - remoteStream?.release(); + remoteStream?.release(true); localStream?.getTracks().forEach(t => t.stop()); localStream?.release(); pcRef?.current?.close(); }; - }, [cameraURL, cameraName]); + }, [cameraURL, cameraName, reload]); if (!cameraName) { return null; } + if (loading) { + return Loading ...; + } + if (remoteStream) { return ( { /> ); } - return null; + return No video stream set; }; diff --git a/src/screens/homeScreen.tsx b/src/screens/homeScreen.tsx index 817fb03..693bd52 100644 --- a/src/screens/homeScreen.tsx +++ b/src/screens/homeScreen.tsx @@ -52,7 +52,7 @@ export const HomeScreen = () => { {currentCamera && ( - Vieweing: {currentCamera} + Viewing: {currentCamera} )}