I want to develop a video call application using React Native and Java. Although the calling and answering events are happening, the onicecandidate function is never triggered, so the candidates are not working. Is there any error in my code, and if so, where is it? Thanks
I used
“react-native-webrtc”: “^111.0.3”,
“react”: “18.2.0”,
“react-native”: “0.71.6”,
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable no-undef */
import React, { useEffect, useRef, useState } from 'react';
import { View, Text, SafeAreaView, StyleSheet, LogBox, TouchableOpacity } from 'react-native'
import { NavigationContainer, StackActions } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import AuthNavigator from './src/assets/navigations/AuthNavigator';
import OnboardingScreen from './src/screens/Onboarding/Onboarding';
import CallEnd from './src/components/CallEnd';
import CallAnswer from './src/components/CallAnswer';
import IconContainer from './src/components/IconContainer';
import CameraSwitch from './src/components/CameraSwitch';
import VideoOff from './src/components/VideoOff';
import VideoOn from './src/components/VideoOn';
import SocketIOClient from 'socket.io-client';
import InCallManager from 'react-native-incall-manager';
import RNRestart from 'react-native-restart';
import {
mediaDevices,
RTCPeerConnection,
RTCView,
RTCIceCandidate,
RTCSessionDescription,
} from 'react-native-webrtc';
import MicOn from './src/components/MicOn';
import MicOff from './src/components/MicOff';
import { HOST } from './src/api/api';
import AsyncStorage from '@react-native-async-storage/async-storage';
LogBox.ignoreAllLogs();
function App() {
const [auth, setAuth] = useState(false);
const [localStream, setlocalStream] = useState(null);
const [remoteStream, setRemoteStream] = useState(null);
const [localMicOn, setlocalMicOn] = useState(true);
const [localWebcamOn, setlocalWebcamOn] = useState(true);
const [type, setType] = useState('JOIN');
const [userId, setUserId] = useState(null);
const [callerName, setCallerName] = useState(null);
const [calleeName, setCalleeName] = useState(null);
let remoteRTCMessage = useRef(null);
let isFront = false;
const [callerId, setCallerId] = useState("caller");
const otherUserId = useRef(null);
const setOtherUserId = (data) => {
otherUserId.current = data.userId;
setCalleeName(data.name)
}
const socket = SocketIOClient(HOST.websocket, {
transports: ['websocket'],
query: {
callerId,
},
});
const peerConnection = useRef(
new RTCPeerConnection({
iceServers: [
{
urls: 'stun:stun.l.google.com:19302',
},
],
}),
);
useEffect(() => {
socket.on('newCall', data => {
if (userId && data.calleeId == userId) {
remoteRTCMessage.current = data.rtcMessage;
otherUserId.current = data.callerId;
setCallerName(data.callerName);
setType('INCOMING_CALL');
}
});
socket.on('newEnd', data => {
if (userId && (data.user1 == userId || data.user2 == userId)) {
peerConnection.current.close();
RNRestart.Restart();
}
});
socket.on('callAnswered', async data => {
if (userId && (data.callerId == userId)) {
remoteRTCMessage.current = data.rtcMessage;
let message = new RTCSessionDescription(remoteRTCMessage.current);
console.log(message);
await peerConnection.current.setRemoteDescription(message);
console.log(peerConnection);
setType('WEBRTC_ROOM');
}
if (userId && (data.calleeId == userId)) {
setType('WEBRTC_ROOM');
}
});
socket.on('ICEcandidate', async data => {
if (userId && (data.callerId == userId || data.calleeId == userId)) {
let message = data.rtcMessage;
if (peerConnection.current) {
await peerConnection.current
.addIceCandidate(
new RTCIceCandidate({
candidate: message.candidate,
sdpMid: message.id,
sdpMLineIndex: message.label,
}),
)
.then(data => {
console.log('SUCCESS');
})
.catch(err => {
console.log('Error', err);
});
}
}
});
mediaDevices.enumerateDevices().then(sourceInfos => {
let videoSourceId;
for (let i = 0; i < sourceInfos.length; i++) {
const sourceInfo = sourceInfos[i];
if (
sourceInfo.kind == 'videoinput' &&
sourceInfo.facing == (isFront ? 'user' : 'environment')
) {
videoSourceId = sourceInfo.deviceId;
}
}
mediaDevices
.getUserMedia({
audio: true,
video: {
mandatory: {
minWidth: 500, // Provide your own width, height and frame rate here
minHeight: 300,
minFrameRate: 30,
},
facingMode: isFront ? 'user' : 'environment',
optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
},
})
.then(stream => {
setlocalStream(stream);
peerConnection.current.addStream(stream);
})
.catch(error => {
// Log error
});
});
peerConnection.current.onaddstream = event => {
setRemoteStream(event.stream);
};
return () => {
socket.off('newCall');
socket.off('callAnswered');
socket.off('ICEcandidate');
};
}, [userId, otherUserId]);
useEffect(() => {
InCallManager.start();
InCallManager.setKeepScreenOn(true);
InCallManager.setForceSpeakerphoneOn(true);
return () => {
InCallManager.stop();
};
}, []);
function sendICEcandidate(data) {
socket.emit('ICEcandidate', data);
}
async function callUser() {
const sessionDescription = await peerConnection.current.createOffer();
await peerConnection.current.setLocalDescription(sessionDescription);
peerConnection.current.onicecandidate = async (event) => {
await event;
if (event.candidate) {
await sendICEcandidate({
callerId: userId,
userId: userId,
calleeId: otherUserId.current,
type: "offer",
rtcMessage: {
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
},
});
}
};
sendCall({
userId: userId,
calleeId: otherUserId.current,
rtcMessage: sessionDescription,
});
}
async function callEnd(userId, otherUserId) {
sendEnd({
user1: userId,
user2: otherUserId,
});
setType('JOIN');
}
async function callSave(userId) {
sendSave({
userId: userId,
calleeId: null,
rtcMessage: null,
});
}
async function processAccept(otherUserId) {
peerConnection.current.setRemoteDescription(
new RTCSessionDescription(remoteRTCMessage.current),
);
const sessionDescription = await peerConnection.current.createAnswer();
await peerConnection.current.setLocalDescription(sessionDescription);
peerConnection.current.onicecandidate = async event => {
await event;
if (event.candidate) {
await sendICEcandidate({
callerId: otherUserId.current,
userId: userId,
calleeId: userId,
type: "answer",
rtcMessage: {
label: event.candidate.sdpMLineIndex,
id: event.candidate.sdpMid,
candidate: event.candidate.candidate,
},
});
}
};
answerCall({
userId: userId,
callerId: otherUserId.current,
rtcMessage: sessionDescription,
});
}
function answerCall(data) {
socket.emit('answerCall', data);
}
function sendCall(data) {
socket.emit('call', data);
setType('OUTGOING_CALL');
}
function sendEnd(data) {
socket.emit('end', data);
}
function sendSave(data) {
socket.emit('save', data);
}
const OutgoingCallScreen = () => {
return (
<View
style={{
flex: 1,
justifyContent: 'space-around',
backgroundColor: '#050A0E',
}}>
<View
style={{
padding: 35,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 14,
}}>
<Text
style={{
fontSize: 16,
color: '#D0D4DD',
}}>
Calling ...
</Text>
<Text
style={{
fontSize: 36,
marginTop: 12,
color: '#ffff',
letterSpacing: 6,
}}>
{calleeName}
</Text>
</View>
<View
style={{
justifyContent: 'center',
alignItems: 'center',
}}>
<TouchableOpacity
onPress={() => {
callEnd(userId, otherUserId.current);
}}
style={{
backgroundColor: '#FF5D5D',
borderRadius: 30,
height: 60,
aspectRatio: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<CallEnd width={50} height={12} />
</TouchableOpacity>
</View>
</View>
);
};
const IncomingCallScreen = () => {
return (
<View
style={{
flex: 1,
justifyContent: 'space-around',
backgroundColor: '#050A0E',
}}>
<View
style={{
padding: 35,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 14,
}}>
<Text
style={{
fontSize: 36,
marginTop: 12,
color: '#ffff',
}}>
{callerName}
</Text>
<Text
style={{
fontSize: 16,
color: '#D0D4DD',
}}>
is Calling..
</Text>
</View>
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}>
<TouchableOpacity
onPress={() => {
processAccept(otherUserId);
setType('WEBRTC_ROOM');
}}
style={{
backgroundColor: 'green',
borderRadius: 30,
height: 60,
aspectRatio: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<CallAnswer height={28} fill={'#fff'} />
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
callEnd(userId, otherUserId.current);
}}
style={{
backgroundColor: '#FF5D5D',
borderRadius: 30,
height: 60,
aspectRatio: 1,
justifyContent: 'center',
alignItems: 'center',
marginLeft: 30,
}}>
<CallEnd width={50} height={12} />
</TouchableOpacity>
</View>
</View>
);
};
function switchCamera() {
localStream.getVideoTracks().forEach(track => {
track._switchCamera();
});
}
function toggleCamera() {
localWebcamOn ? setlocalWebcamOn(false) : setlocalWebcamOn(true);
localStream.getVideoTracks().forEach(track => {
localWebcamOn ? (track.enabled = false) : (track.enabled = true);
});
}
function toggleMic() {
localMicOn ? setlocalMicOn(false) : setlocalMicOn(true);
localStream.getAudioTracks().forEach(track => {
localMicOn ? (track.enabled = false) : (track.enabled = true);
});
}
function leave() {
peerConnection.current.close();
setlocalStream(null);
setType('JOIN');
}
const WebrtcRoomScreen = () => {
return (
<View
style={{
flex: 1,
backgroundColor: '#050A0E',
paddingHorizontal: 12,
paddingVertical: 12,
}}>
{localStream ? (
<RTCView
objectFit={'cover'}
style={{ flex: 1, backgroundColor: '#050A0E' }}
streamURL={localStream.toURL()}
/>
) : null}
{remoteStream ? (
<RTCView
objectFit={'cover'}
style={{
flex: 1,
backgroundColor: '#050A0E',
marginTop: 8,
}}
streamURL={remoteStream.toURL()}
/>
) : null}
<View
style={{
marginVertical: 12,
flexDirection: 'row',
justifyContent: 'space-evenly',
}}>
<IconContainer
backgroundColor={'red'}
onPress={() => {
leave();
}}
Icon={() => {
return <CallEnd height={26} width={26} fill="#FFF" />;
}}
/>
<IconContainer
style={{
borderWidth: 1.5,
borderColor: '#2B3034',
}}
backgroundColor={!localMicOn ? '#fff' : 'transparent'}
onPress={() => {
toggleMic();
}}
Icon={() => {
return localMicOn ? (
<MicOn height={24} width={24} fill="#FFF" />
) : (
<MicOff height={28} width={28} fill="#1D2939" />
);
}}
/>
<IconContainer
style={{
borderWidth: 1.5,
borderColor: '#2B3034',
}}
backgroundColor={!localWebcamOn ? '#fff' : 'transparent'}
onPress={() => {
toggleCamera();
}}
Icon={() => {
return localWebcamOn ? (
<VideoOn height={24} width={24} fill="#FFF" />
) : (
<VideoOff height={36} width={36} fill="#1D2939" />
);
}}
/>
<IconContainer
style={{
borderWidth: 1.5,
borderColor: '#2B3034',
}}
backgroundColor={'transparent'}
onPress={() => {
switchCamera();
}}
Icon={() => {
return <CameraSwitch height={24} width={24} fill="#FFF" />;
}}
/>
</View>
</View>
);
};
switch (type) {
case 'JOIN':
return (
<SafeAreaView style={styles.safeArea}>
{auth ? (
<View style={styles.conteiner}>
<OnboardingScreen callUser={callUser} setOtherUserId={setOtherUserId} callSave={callSave} callUserId={setUserId} />
</View>
) : (
<NavigationContainer>
<AuthNavigator callUser={callUser} setOtherUserId={setOtherUserId} callSave={callSave} callUserId={setUserId} />
</NavigationContainer>
)}
</SafeAreaView>
);
case 'INCOMING_CALL':
return IncomingCallScreen();
case 'OUTGOING_CALL':
return OutgoingCallScreen();
case 'WEBRTC_ROOM':
return WebrtcRoomScreen();
default:
return (
<SafeAreaView style={styles.safeArea}>
{auth ? (
<View style={styles.conteiner}>
<OnboardingScreen callUser={callUser} setOtherUserId={setOtherUserId} callSave={callSave} callUserId={setUserId} />
</View>
) : (
<NavigationContainer>
<AuthNavigator callUser={callUser} setOtherUserId={setOtherUserId} callSave={callSave} callUserId={setUserId} />
</NavigationContainer>
)}
</SafeAreaView>
);
}
}
export default App;
const styles = StyleSheet.create({
conteiner: { flex: 1, justifyContent: 'center', alignItems: 'center' },
safeArea: {
flex: 1,
},
});