ConnectyCube / connectycube-reactnative-samples

Chat and Video Chat code samples for React Native, ConnectyCube
https://connectycube.com
Apache License 2.0
125 stars 111 forks source link

TypeError: Cannot read property '_local' of null #86

Closed masihur1989 closed 4 years ago

masihur1989 commented 4 years ago

Hi,

I am trying to convert the current RNVideoChat sample with React useContext. So basically i am lifting and shifting the https://github.com/ConnectyCube/connectycube-reactnative-samples/blob/master/RNVideoChat/src/components/VideoScreen/index.js to have a central context. And the code looks like this

import React from 'react';
import {SafeAreaView, StatusBar} from 'react-native';
import ConnectyCube from 'react-native-connectycube';
import AwesomeAlert from 'react-native-awesome-alerts';

import {VideoContext} from '../../context';
import RTCViewGrid from './RTCViewGrid';
import {CallService} from '../../services/videoCall';
import ToolBar from './ToolBar';
import UsersSelect from './UsersSelect';

const VideoScreen = ({navigation, route}) => {
  const {
    setOponentIds,
    showInomingCallModal,
    hideInomingCallModal,
    removeRemoteStream,
    setOnCall,
    resetState,
    updateRemoteStream,
    closeSelect,
    setLocalStream,
    initRemoteStream,
    selectUser,
    unSelectUser,
    localStream,
    remoteStreams,
    selectedUsersIds,
    isActiveSelect,
    isActiveCall,
    isIncomingCall,
    _session,
    opponentsIds,
  } = React.useContext(VideoContext);

  React.useEffect(() => {
    setOponentIds(route.params.opponentsIds);
    _setUpListeners();
    return () => {
      _tearDownListeners();
    };
  }, []);

  const initRemoteStreams = () => {
    const emptyStreams = opponentsIds.map((userId) => ({
      userId,
      stream: null,
    }));

    initRemoteStream(emptyStreams);
  };

  const _setLocalStream = (stream) => {
    console.log('_setLocalStream: ', stream);
    setLocalStream(stream);
  };

  const _setUpListeners = () => {
    ConnectyCube.videochat.onCallListener = _onCallListener;
    ConnectyCube.videochat.onAcceptCallListener = _onAcceptCallListener;
    ConnectyCube.videochat.onRejectCallListener = _onRejectCallListener;
    ConnectyCube.videochat.onStopCallListener = _onStopCallListener;
    ConnectyCube.videochat.onUserNotAnswerListener = _onUserNotAnswerListener;
    ConnectyCube.videochat.onRemoteStreamListener = _onRemoteStreamListener;
  };

  const _tearDownListeners = () => {
    ConnectyCube.videochat.onCallListener = null;
    ConnectyCube.videochat.onAcceptCallListener = null;
    ConnectyCube.videochat.onRejectCallListener = null;
    ConnectyCube.videochat.onStopCallListener = null;
    ConnectyCube.videochat.onUserNotAnswerListener = null;
    ConnectyCube.videochat.onRemoteStreamListener = null;
  };

  const _onPressAccept = () => {
    CallService.acceptCall(this._session).then((stream) => {
      const {opponentsIDs, initiatorID, currentUserID} = this._session;
      const opponentsIds = [initiatorID, ...opponentsIDs].filter(
        (userId) => currentUserID !== userId,
      );

      initRemoteStreams(opponentsIds);
      _setLocalStream(stream);
      closeSelect();
      hideInomingCallModal();
    });
  };

  const _onPressReject = () => {
    CallService.rejectCall(_session);
    hideInomingCallModal();
  };

  const _onCallListener = (session, extension) => {
    CallService.processOnCallListener(session)
      .then(() => showInomingCallModal(session))
      .catch(hideInomingCallModal);
  };

  const _onAcceptCallListener = (session, userId, extension) => {
    CallService.processOnAcceptCallListener(session, userId, extension)
      .then(setOnCall)
      .catch(hideInomingCallModal);
  };

  const _onRejectCallListener = (session, userId, extension) => {
    CallService.processOnRejectCallListener(session, userId, extension)
      .then(() => removeRemoteStream(userId))
      .catch(hideInomingCallModal);
  };

  const _onStopCallListener = (session, userId, extension) => {
    const isStoppedByInitiator = session.initiatorID === userId;

    CallService.processOnStopCallListener(userId, isStoppedByInitiator)
      .then(() => {
        if (isStoppedByInitiator) {
          resetState();
        } else {
          removeRemoteStream(userId);
        }
      })
      .catch(hideInomingCallModal);
  };

  const _onUserNotAnswerListener = (session, userId) => {
    CallService.processOnUserNotAnswerListener(userId)
      .then(() => removeRemoteStream(userId))
      .catch(hideInomingCallModal);
  };

  const _onRemoteStreamListener = (session, userId, stream) => {
    CallService.processOnRemoteStreamListener(userId)
      .then(() => {
        updateRemoteStream(userId, stream);
        setOnCall();
      })
      .catch(hideInomingCallModal);
  };

  const initiatorName = isIncomingCall
    ? CallService.getUserById(_session.initiatorID, 'name')
    : '';
  const localStreamItem = localStream
    ? [{userId: 'localStream', stream: localStream}]
    : [];
  const streams = [...remoteStreams, ...localStreamItem];
  console.log('streams.length: ', streams.length);

  CallService.setSpeakerphoneOn(remoteStreams.length > 0);

  return (
    <SafeAreaView style={{flex: 1, backgroundColor: 'black'}}>
      <StatusBar backgroundColor="black" barStyle="light-content" />
      <RTCViewGrid streams={streams} />
      <UsersSelect
        isActiveSelect={isActiveSelect}
        opponentsIds={opponentsIds}
        selectedUsersIds={selectedUsersIds}
        selectUser={selectUser}
        unselectUser={unSelectUser}
      />
      <ToolBar
        selectedUsersIds={selectedUsersIds}
        localStream={localStream}
        isActiveSelect={isActiveSelect}
        isActiveCall={isActiveCall}
        closeSelect={closeSelect}
        initRemoteStreams={initRemoteStreams}
        setLocalStream={_setLocalStream}
        resetState={resetState}
      />
      <AwesomeAlert
        show={isIncomingCall}
        showProgress={false}
        title={`Incoming call from ${initiatorName}`}
        closeOnTouchOutside={false}
        closeOnHardwareBackPress={true}
        showCancelButton={true}
        showConfirmButton={true}
        cancelText="Reject"
        confirmText="Accept"
        cancelButtonColor="red"
        confirmButtonColor="green"
        onCancelPressed={_onPressReject}
        onConfirmPressed={_onPressAccept}
        onDismiss={hideInomingCallModal}
        alertContainerStyle={{zIndex: 1}}
        titleStyle={{fontSize: 21}}
        cancelButtonTextStyle={{fontSize: 18}}
        confirmButtonTextStyle={{fontSize: 18}}
      />
    </SafeAreaView>
  );
};

export default VideoScreen;

And my context looks like this

...
  const initialState = {
    opponentsIds: [],
    localStream: null,
    remoteStreams: [],
    selectedUsersIds: [],
    isActiveSelect: true,
    isActiveCall: false,
    isIncomingCall: false,
  };
const [state, dispatch] = React.useReducer(videoReducer, initialState);
  const videoContext = React.useMemo(
    () => ({
      setOponentIds: (ids) => {
        dispatch({type: 'SET_OPPONENT_IDS', payload: {ids}});
      },
      setOnCall: () => {
        dispatch({
          type: 'SET_ONCALL',
        });
      },

      showInomingCallModal: (session) => {
        dispatch({
          type: 'SHOW_INCOMING_CALL_MODAL',
          payload: {session},
        });
      },

      hideInomingCallModal: () => {
        dispatch({
          type: 'HIDE_INCOMING_CALL_MODAL',
        });
      },

      initRemoteStream: (streams) => {
        dispatch({type: 'INIT_REMOTE_STREAM', payload: {streams}});
      },

      removeRemoteStream: (userId) => {
        dispatch({
          type: 'REMOVE_REMOTE_STREAM',
          payload: {
            userId,
          },
        });
      },

      updateRemoteStream: (userId, stream) => {
        console.log('updateRemoteStream: ', userId);
        console.log('updateRemoteStream: ', stream);
        dispatch({
          type: 'UPDATE_REMOTE_STREAM',
          payload: {
            userId,
            stream,
          },
        });
      },

      setLocalStream: (stream) => {
        dispatch({
          type: 'SET_LOCAL_STREAM',
          payload: {stream},
        });
      },

      resetState: () => {
        dispatch({
          type: 'RESET',
        });
      },

      selectUser: (userId) => {
        dispatch({type: 'SELECT_USER', payload: {userId}});
      },

      unSelectUser: (userId) => {
        dispatch({type: 'UNSELECT_USER', payload: {userId}});
      },

      closeSelect: () => {
        dispatch({
          type: 'CLOSE_SELECT',
        });
      },
      localStream: state.localStream,
      remoteStreams: state.remoteStreams,
      selectedUsersIds: state.selectedUsersIds,
      isActiveSelect: state.isActiveSelect,
      isActiveCall: state.isActiveCall,
      isIncomingCall: state.isIncomingCall,
      _session: state._session,
      opponentsIds: state.opponentsIds,
    }),
    [state],
  );
...

The reason of this approach is to add the capability to add the listeners globally. But i am getting this error when i want to start a call.

TypeError: Cannot read property '_local' of null
TypeError: Cannot read property '_local' of null
    at userCurrentJid (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:171236:26)
    at createNewSession (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:169824:71)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:190989:85)
    at _callee$ (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:193099:51)
    at call (native)
    at tryCatch (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26929:23)
    at invoke (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27105:32)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26972:30)
    at call (native)
    at tryCatch (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26929:23)
    at invoke (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27005:30)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27035:19)
    at tryCallTwo (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:28876:9)
    at doResolve (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:29040:25)
    at Promise (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:28899:14)
    at callInvokeWithMethodAndArg (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27034:33)
    at enqueue (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27039:157)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:26972:30)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:27056:69)
    at _callee (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:193086:42)
    at onPress (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:57071:34)
    at _performTransitionSideEffects (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:56620:22)
    at _receiveSignal (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:56558:45)
    at onResponderRelease (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:56476:34)
    at apply (native)
    at invokeGuardedCallbackImpl (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9143:21)
    at apply (native)
    at invokeGuardedCallback (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9237:42)
    at apply (native)
    at invokeGuardedCallbackAndCatchFirstError (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9241:36)
    at executeDispatch (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9346:48)
    at executeDispatchesInOrder (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9366:26)
    at executeDispatchesAndRelease (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9467:35)
    at executeDispatchesAndReleaseTopLevel (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9476:43)
    at forEach (native)
    at forEachAccumulated (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9457:22)
    at runEventsInBatch (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9491:27)
    at runExtractedPluginEventsInBatch (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:9575:25)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:10615:42)
    at batchedUpdates$1 (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:22846:20)
    at batchedUpdates (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:10553:36)
    at _receiveRootNodeIDEvent (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:10614:23)
    at receiveTouches (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:10644:34)
    at apply (native)
    at __callFunction (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2750:49)
    at anonymous (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2472:31)
    at __guard (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2704:15)
    at callFunctionReturnFlushedQueue (http://localhost:8081/index.bundle?platform=android&dev=true&minify=false:2471:21)

on debugging i figured out the ConnectyCube.videochat.createNewSession is throwing this error as the jid object is null. WebRTCHelpers.userCurrentJid(client) is throwing this error. I am not sure where i am missing the jid. My user is logged in as per the recommendation here.

versions: "react-native-connectycube": "^3.2.0", "react": "16.11.0", "react-native": "0.62.2",

Definitely i am missing something. But i cannot figure it out. It would be great if someone with much better understanding of this architecture can guide me what i am doing wrong.

Thanks

DaveLomber commented 4 years ago

Before calling ConnectyCube.videochat.createNewSession you should be connected to chat

Please check the preparations section https://developers.connectycube.com/js/videocalling?id=preparations

masihur1989 commented 4 years ago

@DaveLomber i am connected to chat.

DaveLomber commented 4 years ago

The crash logs says otherwise..

Let's do a quick check

please do the following log before creating a call session:

console.log("IsConnected", ConnectyCube.chat.isConnected);
const session = ConnectyCube.videochat.createNewSession(calleesIds, sessionType, additionalOptions);
ccvlad commented 4 years ago

Since there is no activity I’m going to close the issue. Please raise a separate issue in case of any other questions.