aws / amazon-chime-sdk-ios

An iOS client library for integrating multi-party communications powered by the Amazon Chime service.
https://aws.amazon.com/chime/chime-sdk/
Apache License 2.0
139 stars 64 forks source link

Callkit integration Issue after ending normal phone call by caller. #633

Open ashirkhan94 opened 6 months ago

ashirkhan94 commented 6 months ago

Describe the bug We integrate the callkit in the react-native-demo app with the help of below link https://aws.amazon.com/blogs/business-productivity/how-to-integrate-apples-callkit-into-ios-applications-using-the-amazon-chime-sdk/ and everything is working fine except for one case that is when a meeting is going on with outgoing callkit call and we got an incoming phone call, we put the chime meeting in the hold and if we end the phone call from our side there is no issue rejoined on meeting, But if the phone call ended by the caller then the reconnection is not happening. we are still out of the meeting that is the issue

To Reproduce Steps to reproduce the behavior:

  1. setup the demo app for react native
  2. integrate call kit with above link like step 1: start the callkitcall step2: configure audiosession step3: start the meeting session step4: make normal phone call to the device wich running demo app with callkit and take the phone call and make call kit call on hold step5: end the phone call at caller side
  3. got the issue

code sample:

in NativeMobileSDKBridge.m

CXHandle *handle;
CXCallController *callController;
CXProvider *provider;
NSUUID *uuid;
BOOL isOnHold;

RCT_EXPORT_METHOD(startCallKitConfiguration)
{
  CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:@"Demo"];
  configuration.maximumCallGroups=1;
  configuration.maximumCallsPerCallGroup=1;
  configuration.supportsVideo = YES;
  configuration.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypeGeneric)];
  configuration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:@"callkit-icon"]);
  _provider = [[CXProvider alloc] initWithConfiguration:configuration];
    callObserver = [[CXCallObserver alloc] init]; 
  [callObserver setDelegate:self queue:dispatch_get_main_queue()];
  [_provider setDelegate:self queue:dispatch_get_main_queue()];
  [self startCall];

}

RCT_EXPORT_METHOD(startCall){
  handle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:@"Demo"];
  _uuid = [NSUUID UUID];
  _isOnHold=false;
  CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:_uuid handle:handle];
  callController = [[CXCallController alloc] init];
  CXTransaction *transaction = [[CXTransaction alloc] initWithActions:@[startCallAction]];
  [callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
    if (error) {
      NSLog(@"Call_KIT Error starting call: %@", error);
    } else {
      NSLog(@"Call_KIT started successfully");
    }
  }];
}

- (void)configureAudioSession {
    AVAudioSession *audioSession = [AVAudioSession sharedInstance];
    NSError *error = nil;

    @try {
        if (audioSession.category != AVAudioSessionCategoryPlayAndRecord) {
            [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord
                           withOptions:AVAudioSessionCategoryOptionAllowBluetooth
                                 error:&error];
        }
        if (audioSession.mode != AVAudioSessionModeVoiceChat) {
            [audioSession setMode:AVAudioSessionModeVoiceChat error:&error];
        }
    } @catch (NSException *exception) {
        NSLog(@"Error configuring AVAudioSession: %@", [error localizedDescription]);
    }
}

- (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallAction *)action {
  [self configureAudioSession];
  [action fulfill];
}

- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
  [meetingSession.audioVideo stop];
  [action fulfill];
}

- (void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAction *)action {
  if ([action isOnHold])
  {
    _isOnHold=true;
    [meetingSession.audioVideo stop];
  }
  [action fulfill];
}

- (void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCallAction *)action {
  BOOL success = true;
  if ([action isMuted])
  {
    success=[meetingSession.audioVideo realtimeLocalMute];
  }
  else
  {
    success=[meetingSession.audioVideo realtimeLocalUnmute];
  }
  [action fulfill];
}

-(void)startAudioVideo
{
   NSError* error = nil;
   BOOL started = [meetingSession.audioVideo startWithCallKitEnabled:true error:&error];
   if (started && error == nil)
   {
     [logger infoWithMsg:@"RN meeting session was started successfully"];
     [meetingSession.audioVideo startRemoteVideo];
   }
   else
   {
     NSString *errorMsg = [NSString stringWithFormat:@"Failed to start meeting, error: %@", error.description];
     [logger errorWithMsg:errorMsg];
     // Handle missing permission error
     if ([error.domain isEqual:@"AmazonChimeSDK.PermissionError"])
     {
       AVAudioSessionRecordPermission permissionStatus = [[AVAudioSession sharedInstance] recordPermission];
       if (permissionStatus == AVAudioSessionRecordPermissionUndetermined)
       {
         [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted)
         {
           if (granted)
           {
             [logger infoWithMsg:@"Audio permission granted"];
             // Retry after permission is granted
             [self startAudioVideo];
           }
           else
           {
             [logger infoWithMsg:@"Audio permission not granted"];
             [self sendEventWithName:kEventOnMeetingEnd body:nil];
           }
         }];
       }
       else if (permissionStatus == AVAudioSessionRecordPermissionDenied)
       {
         [logger errorWithMsg:@"User did not grant permission, should redirect to Settings"];
         [self sendEventWithName:kEventOnMeetingEnd body:nil];
       }
     }
     else
     {
       // Uncaught error
       [self sendEventWithName:kEventOnError body: errorMsg];
       [self sendEventWithName:kEventOnMeetingEnd body:nil];
     }
   }
}

- (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession{
  NSLog(@"Call_KIT didActivateAudioSession successfully");
  @try {
    DefaultActiveSpeakerPolicy* defaultPolicy = [[DefaultActiveSpeakerPolicy alloc] init];
    observer = [[MeetingObservers alloc] initWithBridge:self logger:logger];
    [meetingSession.audioVideo addRealtimeObserverWithObserver:observer];
    [meetingSession.audioVideo addVideoTileObserverWithObserver:observer];
    [meetingSession.audioVideo addAudioVideoObserverWithObserver:observer];
    [meetingSession.audioVideo addDeviceChangeObserverWithObserver:observer];
    [meetingSession.audioVideo addRealtimeDataMessageObserverWithTopic:@"chat" observer:observer];
    [self startAudioVideo]; 
    [self configureActiveAudioDevice:[meetingSession.audioVideo listAudioDevices]];
  } @catch (NSException *exception) {
      NSLog(@"Exception: %@", exception.reason);
  }
};

In MeetingObservers.m file added

- (void)audioSessionDidStartConnectingWithReconnecting:(BOOL)reconnecting
{
  if(!reconnecting){
   [_bridge.provider reportOutgoingCallWithUUID:[_bridge uuid] startedConnectingAtDate:[NSDate date]];
  }
}

- (void)audioSessionDidStartWithReconnecting:(BOOL)reconnecting
{
  if (!reconnecting)
  {
    [_bridge.provider reportOutgoingCallWithUUID:[_bridge uuid] connectedAtDate:[NSDate date]];

    [_logger infoWithMsg:@"Meeting Started!"];
    [_bridge sendEventWithName:kEventOnMeetingStart body:nil];
  }
}

- (void)audioSessionDidStopWithStatusWithSessionStatus:(MeetingSessionStatus * _Nonnull)sessionStatus
{
  NSLog(@"Call_KIT audioSessionTest----DidStop SessionStatus: %u" ,[sessionStatus statusCode]);

  switch ([sessionStatus statusCode]) {
        case 0:
            // If the call is on hold, do not exit the meeting
            if (_bridge.isOnHold) {
              NSLog(@"Call_KIT audioSessionTest 0");
                return;
            }
            break;
    case 75:NSLog(@"Call_KIT audioSessionTest 75");
        case 60:
      NSLog(@"Call_KIT audioSessionTest 60");
      [_bridge.provider reportCallWithUUID:[_bridge uuid] endedAtDate:[NSDate date] reason:CXCallEndedReasonRemoteEnded];

            break;
        case 61:
      NSLog(@"Call_KIT audioSessionTest 61");
      [_bridge.provider reportCallWithUUID:[_bridge uuid] endedAtDate:[NSDate date] reason:CXCallEndedReasonAnsweredElsewhere];

            break;
        case 69:
      NSLog(@"Call_KIT audioSessionTest 69");
      [_bridge.provider reportCallWithUUID:[_bridge uuid] endedAtDate:[NSDate date] reason:CXCallEndedReasonDeclinedElsewhere];

            break;
        default:
      NSLog(@"Call_KIT audioSessionTest default");
      [_bridge.provider reportCallWithUUID:[_bridge uuid] endedAtDate:[NSDate date]         reason:CXCallEndedReasonFailed];

            break;
    }
}

 **// Here we only get `case 0`**

Expected behavior if the phone call ended by the caller then the reconnection should happen

Test environment Info (please complete the following information):

Pls take a look Thanks

ashirkhan94 commented 6 months ago

Hi Team We got the event when phone call ended by the caller with CXCallObserver and we call the CXSetHeldCallAction with NO value for callKit call based on the callkit documentation, But the reconnection is not happening

Screenshot 2024-01-03 at 5 11 05 PM
- (void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call {
  NSLog(@"Call_KIT check_callObserver has_Connected:%d  TTT hasEnded:%d isOutgoing:%d isOnHold:%d",[call hasConnected], [call hasEnded],[call isOutgoing],[call isOnHold]);

  if ([call hasConnected]) {
    if([call hasEnded] && ![call isOutgoing]&& _isOnHold){
      CXSetHeldCallAction *setHeldCallAction = [[CXSetHeldCallAction alloc] initWithCallUUID:_uuid onHold:NO];
      CXTransaction *transaction = [[CXTransaction alloc] initWithAction:setHeldCallAction];
      [callController requestTransaction:transaction completion:^(NSError * _Nullable error) {
          if (error) {
              NSLog(@"Call_KIT Error requesting CXSetHeldCallAction transaction: %@", error);
          } else {
            NSLog(@"Call_KIT Requested CXSetHeldCallAction transaction successfully");
          }
      }];
    }
  }
}

thanks

ashirkhan94 commented 6 months ago

Hi Team We tested the same case with Amazone Chime iOS App and the Amazone Chime Web App. In Amzone Chime App if the caller ends the call then there is one popup modal coming with Resume this meeting button But when we press that button modal disappears and gets a blank screen and the meeting still exists in Web and the Mobile attendee is not in active attendee list in web. This same case we face in our React-native demo app after implementing the callKit.

84D3D795-CC0D-4194-8FD8-0636604331EA

ashirkhan94 commented 6 months ago

Hi team After the caller ended the GSM call and put callkit call active (Set hold:false) Then we tried to activate the audio session and meeting session But getting audioFailedToStart Error 🙂

2024-01-05 18:49:23.732229+0530 DemoApp[617:51261] [ERROR] NativeMobileSDKBridge - Failed to start meeting, error: audioFailedToStart
dylonChime commented 6 months ago

Hi @ashirkhan94, Thanks for raising this issue. I am able to reproduce this with our demo app. When the regular call is ended by the remote party, this causes the iOS SDK attendee to leave the meeting for some reason. We will continue to investigate.

ashirkhan94 commented 6 months ago

Hi @dylonChime Thanks for the replay hope you will reach out soon

salimkt commented 5 months ago

This issue also happens when we gets a zoom call. On getting zoom call and selected hold and accept then the user will leave from meeting. after zoom call user will get back to meeting app setmute function is failing. if we try to turn on camera user will rejoin in meeting. But still audio issue there. any solutions?