ConnectyCube / connectycube-js-sdk-releases

Releases materials for ConnectyCube JS SDK platform https://connectycube.com
9 stars 2 forks source link

Redux is not updating, hence can't video call or receive call #115

Open hatreact opened 10 months ago

hatreact commented 10 months ago

Versions:

"react-native-connectycube": "^3.22.3",
"react-native-incall-manager": "^4.0.1",
"react-native-invoke-app": "^1.0.6",
"react-native-callkeep": "^4.3.3",
"react-native-notifications": "^4.3.1",

Issue:

Caller's logs:

Screenshot 2023-11-13 at 11 44 35 PM Screenshot 2023-11-13 at 11 45 01 PM

Receiver's logs:

Screenshot 2023-11-13 at 11 50 16 PM

As you can see in receiver's logs, right above [VIDEO-SCREEN] log at the end of the line, you'll see true value. This is isIncoming value. In receiver's logs, [SET_CALL_SESSION] log run when _onCallListener function run on receiver's side. And in [VIDEO-SCREEN] log the session is null, which shouldn't be.

Why ActiveCall reducer behaving strangely despite actions being dispatched correctly?

ccvlad commented 10 months ago

@hatreact I can't see how you pass a remoteStream after getting it in the onRemoteStreamListener or how you pass a localStream to a screen where you want to display it. I am not sure about the issue with the redux. On the last screen, you have the warning "Non-serializable values were found in the navigation state". Referring to this warning, I can assume that you are trying to transmit the media stream (local/remote) through the navigation state, could this be true?

hatreact commented 10 months ago
 const startCall = async (callType = "video") => {
    CallService.getStore(store);
    if (user.UserSetting.ccuid == null) {
      showToast("User does not have a valid connectycube id");
      return;
    }

    const selectedOpponentsIds = [user.UserSetting.ccuid];

    const callSession = await CallService.startCall(
      selectedOpponentsIds,
      callType == "video"
        ? ConnectyCube.videochat.CallType.VIDEO
        : ConnectyCube.videochat.CallType.AUDIO,
      { token, callType, userId: user.id }
    );

    const pushParams = {
      message: `Incoming call from ${user.firstName}`,
      ios_voip: 1,
      handle: user.firstName,
      initiatorId: callSession.initiatorID,
      opponentsIds: selectedOpponentsIds.join(","),
      uuid: callSession.ID,
      callType,
    };

    PushNotificationsService.sendPushNotification(
      selectedOpponentsIds,
      pushParams
    );

    navigation.navigate("VideoScreen", {
      initiatorName: user,
    });
  };

CallService.startCall ran and streams is passed to action ADD_OR_UPDATE_STREAMS. You can see in Caller's log, 2nd image right above ADD_OR_UPDATE_STREAMS[0]. This stream is being accessed in VideoScreen by using this selector

const streams = useSelector(store => store.ActiveCall.streams);

Even though startCall is running successfully without any error, when user navigates to VideoScreen right after startCall.

const callSession = useSelector(store => store.ActiveCall.session); callSession is still null. So I passed callSession in VideoScreen to confirm if session coming through. That's why it was showing warning for Non-serializable values.

_onCallListener ran and session is dispatched. _onRemoteStreamListener is not triggering at all.

ccvlad commented 10 months ago

Can you display the localStream (a media stream from a device's camera)? Check this.

_onCallListener ran and session is dispatched. _onRemoteStreamListener is not triggering at all.

Both users should get a remote stream in onRemoteStreamListener after the receiver does session. accept(extension) inside the onCallListener = (session, extension) => {...}. Also, check this guide.

======================================

Your steps to set a call between users should look like this:

Make sure you have remote streams, then you can dispatch them to Redux to be able to use them from Redux state on any screen. You can test Redux with a local stream first because it is created locally.

hatreact commented 10 months ago

No I'm not able to see media stream from device's camera.

With my code

static MEDIA_OPTIONS_AUDIO = { audio: true };

 async startCall(usersIds, callType, options = {}) {
    const session = ConnectyCube.videochat.createNewSession(
      usersIds,
      callType,
      options
    );
    store.dispatch(setCallSession(session));

    await this.setMediaDevices();

    // create local stream
    // I write local stream logic like this for readability

    const stream = await this.callSession.getUserMedia(
      callType == 2
        ? CallService.MEDIA_OPTIONS_AUDIO
        : CallService.MEDIA_OPTIONS
    );
    console.log("[START-STREAM][0]", stream);

    // store streams
    const streams = [{ userId: LOCAL_STREAM_USER_ID, stream: stream }];
    for (let uId of usersIds) {
      streams.push({ userId: parseInt(uId), stream: null });
    }
    console.log("streams", streams);
    store.dispatch(addOrUpdateStreams(streams));

    this.callSession.call({ options });

    const userName = `${this.currentUser.firstName} ${this.currentUser.lastName}`;
    const receivedNames = await getCallRecipientString(usersIds);

    // report to CallKit (iOS only)
    this.reportStartCall(
      this.callSession.ID,
      userName,
      receivedNames,
      "generic",
      callType === "video"
    );

    this.playSound("outgoing");

    return session;
  }
Screenshot 2023-11-15 at 6 23 57 PM

With connectycube sample code

async startCall(usersIds, callType, options = {}) {
    const session = ConnectyCube.videochat.createNewSession(
      usersIds,
      callType,
      options
    );
    store.dispatch(setCallSession(session));

    await this.setMediaDevices();

     // create local stream
     const mediaOptions = {...CallService.MEDIA_OPTIONS};
     if (callType === ConnectyCube.videochat.CallType.AUDIO) {
       mediaOptions.video = false;
     }
     const stream = await this.callSession.getUserMedia(mediaOptions);
    console.log("[START-STREAM]", stream);

    // store streams
    const streams = [{ userId: LOCAL_STREAM_USER_ID, stream: stream }];
    for (let uId of usersIds) {
      streams.push({ userId: parseInt(uId), stream: null });
    }
    console.log("streams", streams);
    store.dispatch(addOrUpdateStreams(streams));

    this.callSession.call({ options });

    const userName = `${this.currentUser.firstName} ${this.currentUser.lastName}`;
    const receivedNames = await getCallRecipientString(usersIds);

    // report to CallKit (iOS only)
    this.reportStartCall(
      this.callSession.ID,
      userName,
      receivedNames,
      "generic",
      callType === "video"
    );

    this.playSound("outgoing");

    return session;
  }
Screenshot 2023-11-15 at 6 18 54 PM

And localStream is shown in <VideoGrid streams={streams} /> in VideoScreen.

I tried to follow documentation and compare code with connectycube-reactnative-sdk multiple times. And there is no step I have missed.

My guess is it could be due to non-serialized values which could be session or streams being passed in redux. Maybe non-serialized values could be causing this unexpected behaviour. Do you think that it could cause serious problems?

ccvlad commented 10 months ago

Ensure you use a real device instead of an iOS simulator or Android emulator. Add the prop "video: true" like this. There isn't a video track in your stream. Only the audio track is present.

hatreact commented 10 months ago

Sorry, previous logs were from audio call. I have tested on emulators/simulators and real devices.

Tested on following Devices. I got same behaviour. iPhone XS MAX ios 15 OnePluse 6t android 11 Galaxy A52 android 13

Screenshot 2023-11-20 at 3 27 16 PM

I added log in _createAndStoreSession inside cubeWebRTCClient.js

Screenshot 2023-11-20 at 4 38 12 PM
ccvlad commented 10 months ago

I added log in _createAndStoreSession inside cubeWebRTCClient.js

You've gotten the log of the new session, it’s obvious that it’s empty.

From previous logs I see that you have a correct localStream and sent it. Check this call on a receiver side and accept the call session. You should get this stream as remote for the receiver (into the onRemoteStreamListener)

hatreact commented 10 months ago
Screenshot 2023-12-04 at 1 03 42 PM

In _onCallListener, value for isIcoming is passed as true. But still it's not invoking IncomingCallScreen. When I log isIcoming value it is false which is very strange behaviour.

  async _onCallListener(session, extension) {
    this.token = extension.options.token;
    this.otherUserId = extension.options.userId;

    // if already on a call
    if (this.callSession && !this.isDummySession) {
      console.log("[CallService][_onCallListener] reject, already_on_call");
      this.rejectCall(session, { already_on_call: true });
      return;
    }

    this.playSound("incoming");

    console.log("[CallService][_onCallListener]", {
      isEarlyAccepted: this.isEarlyAccepted,
      isAccepted: this.isAccepted,
    });

    if (this.isEarlyAccepted && !this.isAccepted) {
      setTimeout(() => {
        // wait until redux updated the data
        this.acceptCall();
      });
    }

    console.log("[_onCallListener][Session]", session);

    store.dispatch(setCallSession(session, true));
  }
ccvlad commented 10 months ago

Somewhere you also call the store.dispatch(setCallSession(session)) without the second argument or it is "false". In our RNVideoChat sample the store.dispatch(setCallSession(session, true)) calls in _onCallListener to mark a call as incoming (isIncoming = true). When the store.dispatch(setCallSession(session)) (with isIncoming = false) can be called only from the sender side due to the process of starting a new call (select callee/callees and press the call button).

It means that isIncoming === true is possible on the receiver side and isIncoming === false on the side. Check your code to block the unnecessary store.dispatch(setCallSession(...)).

===========

Are you using our sample as is or did you change something in the code instead of configs?

hatreact commented 9 months ago

Yeah I tried to integrate sample code as well but facing the same issue.

I created a repo with produceable behavior. It has the sample sdk code. https://github.com/hatreact/RAApp.git

DaveLomber commented 8 months ago

@ccvlad could you follow up?

ccvlad commented 8 months ago

Hey, @hatreact !

I've used your project (RAApp) to find the issue and prepared diff file with fixes - fix_redux_configurations.diff.zip I think you can apply this file by the command git apply < path/to/fix_redux_configurations.diff from the root of your project.

You made a lot of mistakes in setting up the redux lib, so I advise you to understand the redux documentation.

I haven't fixed other issues you might encounter later because you should figure it out for yourself.