I have now solved this issue, the below code is my result. Note my WebRTC is very low quality. And I have a question that needs help here Help - Video Call Low quality, Low Performance. Calls Freeze. Jitter Problems and Low Framerate
Code (long)
import React, { useReducer, useEffect, useRef } from 'react';
import {
StyleSheet,
View,
Dimensions,
Platform,
TextInput,
TouchableOpacity,
} from 'react-native';
import inCallManager from 'react-native-incall-manager';
import RNCallKeep from 'react-native-callkeep';
import { RTCView, registerGlobals } from 'react-native-webrtc';
import { v4 as uuid } from 'uuid';
import Wazo from '@wazo/sdk/lib/simple';
import AsyncStorage from '@react-native-community/async-storage';
import CommonText from '../components/common/text/common_text';
// Polyfill WebRTC
registerGlobals();
const options = {
ios: { appName: 'euclid', includesCallsInRecents: false },
android: {
additionalPermissions: [],
alertTitle: 'Permission Required',
alertDescription: 'This application needs to access to allow calls',
okButton: 'Ok',
cancelButton: 'Cancel',
},
};
const Dialer = ({ setLogout }: { setLogout: () => void }) => {
const reducer = (state: any, action: any) => ({ ...state, ...action });
const initialState = {
isCall: false,
ready: false,
held: false,
cameraOn: false,
ext: '',
};
const [state, dispatch] = useReducer(reducer, initialState);
const { ext, isCall, ready, held, cameraOn } = state;
const session = useRef<any>(null);
const remote = useRef<any>(null);
const local = useRef<any>(null);
const callId = useRef<string>('');
const isIOS = Platform.OS === 'ios';
const isVideo = session.current?.cameraEnabled;
const oneTimeId = () => {
callId.current = callId.current || uuid();
return callId.current;
};
const call = async (extNum: string, video = false) => {
if (!extNum || isCall) return;
try {
await Wazo.Phone.call(extNum, video);
} catch (err) {
console.warn(err);
}
};
const terminate = () => {
dispatch(initialState);
session.current = null;
local.current = null;
remote.current = null;
callId.current = '';
inCallManager.stop();
};
const ringing = (type: 'startCall' | 'displayIncomingCall') => (
sess: any
) => {
dispatch({ isCall: true });
session.current = sess;
const id = oneTimeId();
const { number, displayName, cameraEnabled } = sess;
RNCallKeep[type](id, number, displayName, 'number', cameraEnabled);
};
const initializeWebRtc = async () => {
await Wazo.Phone.connect({ audio: true, video: true });
Wazo.Phone.on(Wazo.Phone.ON_CALL_OUTGOING, ringing('startCall'));
Wazo.Phone.on(Wazo.Phone.ON_CALL_INCOMING, ringing('displayIncomingCall'));
Wazo.Phone.on(Wazo.Phone.ON_CALL_FAILED, () => {
const id = oneTimeId();
terminate();
RNCallKeep.endCall(id);
});
Wazo.Phone.on(Wazo.Phone.ON_CALL_ENDED, () => {
const id = oneTimeId();
terminate();
RNCallKeep.endCall(id);
});
Wazo.Phone.on(Wazo.Phone.ON_CALL_ACCEPTED, async (sess: any) => {
if (isCall || ready) return;
session.current = sess;
const sipSession = Wazo.Phone.getCurrentSipSession();
if (session.current?.cameraEnabled) {
const { peerConnection } = sipSession.sessionDescriptionHandler;
const locStreams = peerConnection.getLocalStreams();
const remStreams = peerConnection.getRemoteStreams();
const anyTrack = (stream: any) => !!stream.getVideoTracks().length;
local.current = locStreams.find(anyTrack);
remote.current = remStreams.find(anyTrack);
if (!isIOS) {
RNCallKeep.backToForeground();
}
await inCallManager.start({ media: 'video', auto: true });
} else if (session.current) {
await inCallManager.start({ media: 'audio', auto: true });
}
await sipSession.sendReinvite();
dispatch({ ready: true });
});
};
const handleAnswer = async () => {
RNCallKeep.setCurrentCallActive();
Wazo.Phone.accept(session.current, session.current?.cameraEnabled);
};
const handleHangup = async () => {
if (!session.current) return;
try {
await Wazo.Phone.hangup(session.current);
} catch (err) {
console.warn(err);
}
terminate();
};
const initializeCallKeep = () => {
RNCallKeep.setup(options);
RNCallKeep.setAvailable(true);
RNCallKeep.addEventListener('answerCall', handleAnswer);
RNCallKeep.addEventListener('endCall', handleHangup);
};
const initialize = async () => {
await initializeWebRtc();
initializeCallKeep();
};
const cleanup = () => {
Wazo.Phone.unbind();
inCallManager.stop();
RNCallKeep.removeEventListener('answerCall');
RNCallKeep.removeEventListener('endCall');
};
useEffect(() => {
initialize();
return cleanup;
}, []);
useEffect(() => {
if (ready) {
setInterval(async () => {
if (remote.current) {
const sipSession = Wazo.Phone.getCurrentSipSession();
const { peerConnection } = sipSession.sessionDescriptionHandler;
const videoStats = await peerConnection.getStats(
remote.current?._tracks.find(t => t.kind === 'video')
);
const audioStats = await peerConnection.getStats(
remote.current?._tracks.find(t => t.kind === 'audio')
);
console.log({ audioStats, videoStats });
}
}, 10000);
}
}, [ready]);
const logout = async () => {
await handleHangup();
await Wazo.Auth.logout();
await AsyncStorage.removeItem('call-session-token');
setLogout();
};
const toggleHold = () => {
Wazo.Phone[held ? 'unhold' : 'hold'](session.current);
dispatch({ held: !held });
};
const toggleCamera = () => {
Wazo.Phone[cameraOn ? 'turnCameraOn' : 'turnCameraOff'](session.current);
dispatch({ cameraOn: !cameraOn });
};
return (
<View style={{ flex: 1 }}>
<View>
<View style={{ marginTop: 40, flexDirection: 'row' }}>
<TextInput
style={styles.input}
autoCapitalize='none'
onChangeText={value => dispatch({ ext: value })}
value={ext}
/>
<TouchableOpacity style={styles.button} onPress={logout}>
<CommonText style={{ color: 'white' }}>Logout</CommonText>
</TouchableOpacity>
</View>
<View
style={{
height: '60%',
width: '100%',
}}>
{remote.current && isCall && ready && (
<View style={{ borderColor: 'red', borderWidth: 5, flex: 1 }}>
<RTCView
objectFit='cover'
streamURL={remote.current?.toURL()}
style={styles.remoteVideo}
zOrder={15}
/>
</View>
)}
</View>
{!isCall && (
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity
onPress={() => call(ext, false)}
style={styles.button}>
<CommonText style={{ color: 'white' }}>Call</CommonText>
</TouchableOpacity>
<TouchableOpacity
onPress={() => call(ext, true)}
style={styles.button}>
<CommonText style={{ color: 'white' }}>Video call</CommonText>
</TouchableOpacity>
</View>
)}
{local.current && isCall && ready && (
<RTCView
mirror
streamURL={local.current?.toURL()}
style={styles.localVideo}
zOrder={1}
/>
)}
{isCall && (
<>
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity onPress={handleHangup} style={styles.button}>
<CommonText style={{ color: 'white' }}>Hangup</CommonText>
</TouchableOpacity>
<TouchableOpacity onPress={toggleHold} style={styles.button}>
<CommonText style={{ color: 'white' }}>
{held ? 'Unhold' : 'Hold'}
</CommonText>
</TouchableOpacity>
</View>
{isVideo && (
<TouchableOpacity onPress={toggleCamera} style={styles.button}>
<CommonText style={{ color: 'white' }}>
{cameraOn ? 'Camera On' : 'Camera Off'}
</CommonText>
</TouchableOpacity>
)}
</>
)}
</View>
</View>
);
};
const styles = StyleSheet.create({
button: {
padding: 5,
margin: 10,
width: 175,
height: 50,
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black',
},
localVideo: {
width: 100,
height: 100,
position: 'absolute',
right: 10,
bottom: -50,
},
remoteVideo: {
flex: 1,
position: 'absolute',
left: 0,
top: 0,
margin: 0,
padding: 0,
aspectRatio: 1,
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
overflow: 'hidden',
alignItems: 'center',
},
input: {
height: 40,
width: 150,
borderColor: 'black',
borderWidth: 3,
borderRadius: 10,
margin: 10,
},
});
export default Dialer;