All of these libraries are broken in some way for me, -- please help!

These are my issues on github:

Video Starts on Hold and Muted, Audio Call Starts Muted
CallKeep Not Keeping Call in IOS

I Could not fix these issues and I still only hear Audio for Audio calls when I press (hold) and then press (unhold) and only see video and hear audio in Video Calls when doing the same, I tried to fix this by installing react-native-incall-manager but this has now just given me different issues to deal with, why is nothing working ? Now I’m hearing the speaker of 1 phone during call but nothing from the other phone. I can see video on both but only after doing this:

  useEffect(() => {
    if (ready && session.current) {
      setTimeout(() => {
        Wazo.Phone.hold(session.current);
        console.log('simulate hold');
        dispatch({ held: true });
      }, 1000);
      setTimeout(() => {
        console.log('simulate unhold');

        if (session.current?.cameraEnabled) {
          inCallManager.start({ media: 'video' });
        } else if (session.current) {
          inCallManager.start({ media: 'audio' });
          inCallManager.setForceSpeakerphoneOn(false);
        }
        Wazo.Phone.unhold(session.current);
        dispatch({ held: false });
      }, 2000);
    }
  }, [ready]);

It really is getting absurd what I’m having to do to get the calls to work, these libs are all broken and don’t work mashed together like all the readme’s are saying. Here is my full code in the hope that someone might be out there:

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';

export const OUTGOING = [
  'ON_CALL_OUTGOING',
  'ON_PROGRESS',
  'ON_CALL_ACCEPTED',
  'ON_CALL_ENDED',
];

export const INCOMING = [
  'ON_CALL_INCOMING',
  'ON_CALL_ANSWERED',
  'ON_CALL_ACCEPTED',
  'ON_CALL_ENDED',
];

// 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 isIOS = Platform.OS === 'ios';

const Dialer = ({ setLogout }: { setLogout: () => void }) => {
  const reducer = (state: any, action: any) => ({ ...state, ...action });
  const initialState = {
    ext: '',
    isCall: false,
    ready: false,
    held: false,
    cameraOn: false,
  };
  const [state, dispatch] = useReducer(reducer, initialState);
  const { ext, isCall, ready, held, cameraOn } = state;

  const session = useRef<any>(null);
  const callId = useRef<string>('');
  const remote = useRef<any>(null);
  const local = useRef<any>(null);

  const call = async (num: any, video = false) => {
    if (!num || isCall) return;
    try {
      await Wazo.Phone.call(num, video);
    } catch (err) {
      console.warn(err);
    }
  };

  const terminate = () => {
    dispatch(initialState);
    session.current = null;
    local.current = null;
    remote.current = null;
    callId.current = '';
    inCallManager.stop();
  };

  const oneTimeId = () => {
    callId.current = callId.current || uuid();
    return callId.current;
  };

  const initializeWebRtc = async () => {
    await Wazo.Phone.connect({ audio: true, video: true });

    Wazo.Phone.on(Wazo.Phone.ON_CALL_REINVITE, () =>
      console.log('reinvite occuring')
    );

    Wazo.Phone.on(Wazo.Phone.ON_CALL_OUTGOING, (outgoingSess: any) => {
      dispatch({ isCall: true });
      session.current = outgoingSess;
      const { number, displayName, cameraEnabled } = outgoingSess;
      RNCallKeep.startCall(
        oneTimeId(),
        number,
        displayName,
        'number',
        cameraEnabled
      );
    });
    Wazo.Phone.on(Wazo.Phone.ON_CALL_INCOMING, (incomingSess: any) => {
      dispatch({ isCall: true });
      session.current = incomingSess;
      const { number, displayName, cameraEnabled } = incomingSess;
      RNCallKeep.displayIncomingCall(
        oneTimeId(),
        number,
        displayName,
        'number',
        cameraEnabled
      );
    });
    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 (acceptedSess: any) => {
      if (isCall) return;
      session.current = acceptedSess;

      if (session.current?.cameraEnabled) {
        const sipSession = Wazo.Phone.getCurrentSipSession();
        const { peerConnection } = sipSession.sessionDescriptionHandler;
        const lStreams = peerConnection.getLocalStreams();
        const rStreams = peerConnection.getRemoteStreams();

        const anyStream = (stream: any) => !!stream.getVideoTracks().length;
        local.current = lStreams.find(anyStream);
        remote.current = rStreams.find(anyStream);
      }

      // @ts-ignore
      RNCallKeep.setCurrentCallActive(oneTimeId());
      dispatch({ ready: true });
    });
  };

  useEffect(() => {
    if (ready && session.current) {
      setTimeout(() => {
        Wazo.Phone.hold(session.current);
        console.log('simulate hold');
        dispatch({ held: true });
      }, 1000);
      setTimeout(() => {
        console.log('simulate unhold');

        if (session.current?.cameraEnabled) {
          inCallManager.start({ media: 'video' });
        } else if (session.current) {
          inCallManager.start({ media: 'audio' });
          inCallManager.setForceSpeakerphoneOn(false);
        }
        Wazo.Phone.unhold(session.current);
        dispatch({ held: false });
      }, 2000);
    }
  }, [ready]);

  const handleAnswer = async () =>
    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 logout = async () => {
    await handleHangup();
    await Wazo.Auth.logout();
    await AsyncStorage.removeItem('call-session-token');
    setLogout();
  };

  const handleMute = (muted: boolean) => {
    Wazo.Phone[muted ? 'mute' : 'unmute'](session.current);
  };

  const initializeCallKeep = () => {
    RNCallKeep.setup(options);
    RNCallKeep.setAvailable(true);
    RNCallKeep.addEventListener('answerCall', handleAnswer);
    RNCallKeep.addEventListener('endCall', handleHangup);
    RNCallKeep.addEventListener('didPerformSetMutedCallAction', handleMute);
  };

  const initialize = async () => {
    await initializeWebRtc();
    initializeCallKeep();
  };

  const uninitialize = () => {
    Wazo.Phone.unbind();
    RNCallKeep.removeEventListener('answerCall');
    RNCallKeep.removeEventListener('endCall');
    RNCallKeep.removeEventListener('didPerformSetMutedCallAction');
  };

  useEffect(() => {
    initialize();
    return uninitialize;
  }, []);

  const toggleHold = () => {
    Wazo.Phone[held ? 'unhold' : 'hold'](session.current);
    console.log('hold', !held);
    dispatch({ held: !held });
  };

  const toggleCamera = () => {
    Wazo.Phone[cameraOn ? 'turnCameraOn' : 'turnCameraOff'](session.current);
    dispatch({ cameraOn: !cameraOn });
  };
  const isVideo = session.current?.cameraEnabled;

  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>
        )}
        {!isIOS && local.current && isCall && ready && (
          <RTCView
            mirror
            streamURL={local.current?.toURL()}
            style={styles.localVideo}
            zOrder={1}
          />
        )}
        {isIOS && 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;

I’ve got 100 tabs open and it’s just getting worse and worse, if someone could walk me through this. I’d really appreciate it.

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;