ARCallPlus profile

ARCallPlus is an open source audio and video call project that supports iOS, Android, and Web platforms. This article mainly introduces the encapsulation of iOS local library of ARUICalling module.

Download the source code

Three lines of code, 20 minutes of in-app build, audio and video calls. This project has been put on App Store, welcome to download and experience.

  • GitHub open source address
  • App Store download address

The development environment

  • Development tools: Xcode13 real machine run

  • Development languages: Objective-C, Swift

The project structure

The core API:

  • ARUILogin (Login API)
  • ARUICalling
  • ARUICallingListerner (call callback)

Internal core API:

  • ARTCCalling (Audio and Video)
  • ARTCCallingDelegate (Audio and video callbacks)
  • ARTCCalling+Signal

Core APIS and callbacks

@interface ARUICalling : NSObject + (instancetype)shareInstance; // @param Users User information // @param Type Call type: Video/voice - (void)call:(NSArray<ARCallUser *> *)users type:(ARUICallingType)type; /// @param listener callback - (void)setCallingListener:(id<ARUICallingListerner>)listener; It is recommended that only local audio files are supported within 30 seconds. // @param filePath audio filePath - (void)setCallingBell:(NSString *)filePath; /// enable mute mode (default) - (void)enableMuteMode:(BOOL)enable; // enable custom routing (off by default) // @param enable After this function is enabled, the corresponding ViewController object will be received in the onStart callback. - (void)enableCustomViewRoute:(BOOL)enable; @end@protocol ARUICallingListerner <NSObject> BOOL shouldShowOnCallView NS_SWIFT_NAME(shouldShowOnCallView()); /// The call starts a callback. Both the calling and called parties will trigger; /// When triggered by the called party, the controller is called back through listening, and the access party determines the display scheme. // @param userIDs User ID of this call (excluding yourself) // @param Type Call type: Video/Audio // @param Role Call role: Calling/called // @param viewController Provide the Calling function page to the caller, - (void)callStart:(NSArray<NSString *> *)userIDs type:(ARUICallingType)type role:(ARUICallingRole)role viewController:(UIViewController * _Nullable)viewController NS_SWIFT_NAME(callStart(userIDs:type:role:viewController:));  /// @param userIDs User ID of this call (excluding yourself) /// @param Type Call type: Video/Audio // @param Role Call role: Calling party /// @param totalTime Call duration - (void)callEnd:(NSArray<NSString *> *)userIDs type:(ARUICallingType)type role:(ARUICallingRole)role totalTime:(float)totalTime NS_SWIFT_NAME(callEnd(userIDs:type:role:totalTime:)); /// @param Event Callback event type /// @param Type Call type: video \ audio // @param Role Call role: calling party \ called party /// @param message event - (void)onCallEvent:(ARUICallingEvent)event type:(ARUICallingType)type role:(ARUICallingRole)role message:(NSString *)message NS_SWIFT_NAME(onCallEvent(event:type:role:message:)); // @param userIDs Offline userID // @param Type Call type: Video audio - (void)onPushToOfflineUser:(NSArray<NSString *> *)userIDs type:(ARUICallingType)type; @endCopy the code

The sample code

Effect demonstration (point-to-point audio call)

Code implementation
- (ARtcEngineKit *)rtcEngine { if (! _rtcEngine = [ARtcEngineKit sharedEngineWithAppId:[ARUILogin getSdkAppID] delegate:self]; / / / live model [_rtcEngine setChannelProfile: ARChannelProfileLiveBroadcasting]; [_rtcEngine setClientRole: ARClientRoleBroadcaster]; / / / encoding configuration ARVideoEncoderConfiguration * configuration = [[ARVideoEncoderConfiguration alloc] init]; configuration.dimensions = CGSizeMake(960, 540); configuration.frameRate = 15; configuration.bitrate = 500; [_rtcEngine setVideoEncoderConfiguration:configuration]; / / / to enable the speaker volume prompt [_rtcEngine enableAudioVolumeIndication: 2000 smooth: 3 report_vad: YES]; / / / open skin care [_rtcEngine setBeautyEffectOptions: YES options: [[ARBeautyOptions alloc] init]]. } return _rtcEngine; } //MARK: -publish Method - (void)call:(NSArray *)userIDs type:(CallType)type { self.isOnCalling) { self.curLastModel.inviter = [ARUILogin getUserID]; self.curLastModel.action = CallAction_Call; self.curLastModel.calltype = type; self.curRoomID = [NSString stringWithFormat:@"%d", [ARTCCallingUtils generateRoomID]]; self.isMembers = userIDs.count >= 2 ? YES : NO; self.calleeUserIDs = [@[] mutableCopy]; self.curType = type; self.isOnCalling = YES; self.isBeingCalled = NO; [self joinRoom]; [self createMemberChannel]; } // If it is not in the current invite list, add NSMutableArray *newInviteList = [NSMutableArray array]; for (NSString *userID in userIDs) { if (! [self.curInvitingList containsObject:userID]) { [newInviteList addObject:userID]; } } [self.curInvitingList addObjectsFromArray:newInviteList]; [self.calleeUserIDs addObjectsFromArray:newInviteList]; if (! (self.curInvitingList && self.curInvitingList.count > 0)) return; self.currentCallingUserID = newInviteList.firstObject; for (NSString *userID in self.curInvitingList) { [self invite:userID action:CallAction_Call]; }} - (void)accept:(BOOL)isVideo {/// ARLog(@"Calling - accept Call"); if (! isVideo) { [self.rtcEngine disableVideo]; self.curType = CallType_Audio; if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:YES message:@""]; } } [self joinRoom]; self.currentCallingUserID = self.curSponsorForMe; [self invite:self.curSponsorForMe action:CallAction_Accept]; self.isCallSucess = YES; [self dealWithException:10]; } - (void)reject {/// ARLog(@" calling-reject Call"); [self invite:self.curSponsorForMe action:CallAction_Reject]; self.isOnCalling = NO; [self.rtcEngine disableVideo]; } - (void)hangup {/// hangup the call __block BOOL hasCallUser = NO; [self.curRoomList enumerateObjectsUsingBlock:^(NSString *user, NSUInteger idx, BOOL * _Nonnull stop) { if ((user && user.length > 0) && ! [self.curinvitingList containsObject:user]) {hasCallUser = YES; [self invite:user action:CallAction_End];  *stop = YES; } }]; If (hasCallUser == NO) {ARLog(@" calling-grouphangup Send CallAction_Cancel"); if (hasCallUser == NO) {ARLog(@" calling-grouphangup Send CallAction_Cancel"); [self.curInvitingList enumerateObjectsUsingBlock:^(NSString *invitedId, NSUInteger idx, BOOL * _Nonnull stop) { [self invite:invitedId action:CallAction_Cancel]; }]; } [self leaveRoom]; self.isOnCalling = NO; } - (void)switchToAudio {/// switchToAudio (call) self.curType = CallType_Audio; [self.rtcEngine disableVideo]; [self invite:self.currentCallingUserID action:CallAction_SwitchToAudio]; if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:YES message:@""]; }} - (void)startRemoteView:(NSString *)userID view:(UIView *)view {/// enable remote user video rendering ARLog(@"Calling - startRemoteView) userID = %@", userID); if (userID.length ! = 0) { ARtcVideoCanvas *canvas = [[ARtcVideoCanvas alloc] init]; canvas.uid = userID; canvas.view = view; [self.rtcEngine setupRemoteVideo:canvas]; }} - (void)stopRemoteView:(NSString *)userID {/// disable ARLog(@"Calling - stopRemoteView userID = %@", userID); ARtcVideoCanvas *canvas = [[ARtcVideoCanvas alloc] init]; canvas.uid = userID; canvas.view = nil; [self.rtcEngine setupRemoteVideo:canvas]; } - (void)openCamera:(BOOL)frontCamera view:(UIView *)view {/// openCamera ARLog(@"Calling - openCamera"); if (self.curType == CallType_Video) { [self.rtcEngine enableVideo]; } ARtcVideoCanvas *canvas = [[ARtcVideoCanvas alloc] init]; canvas.uid = [ARUILogin getUserID]; canvas.view = view; [self.rtcEngine setupLocalVideo:canvas]; [self.rtcEngine startPreview]; self.isFrontCamera = frontCamera; }Copy the code
Effect demonstration (point-to-point video call)

Code implementation
//MARK: - ARtcEngineDelegate - (void)rtcEngine:(ARtcEngineKit *)engine didOccurError:(ARErrorCode)errorCode {/// an error callback occurs ARLog(@"Calling - didOccurError = %ld", (long)errorCode); } - (void)rtcEngine:(ARtcEngineKit *)engine firstRemoteVideoDecodedOfUid:(NSString *)uid size:(CGSize)size elapsed:(NSInteger)elapsed { ARLog(@"Calling - firstRemoteVideoDecodedOfUid = %@", uid); } - (void)rtcEngine:(ARtcEngineKit *)engine didJoinedOfUid:(NSString *)uid elapsed:(NSInteger)elapsed {/// the remote user/anchor will add the callback ARLog(@"Calling - didJoinedOfUid = %@", uid); [self dealWithException:0]; // C2C curInvitingList do not remove userID; [self removeTimer:uid]; if ([self.curInvitingList containsObject:uid]) { [self.curInvitingList removeObject:uid]; } if (! [self.curRoomList containsObject:uid]) { [self.curRoomList addObject:uid]; } / / C2C calls to calculate duration if ([self canDelegateRespondMethod: @ the selector (onUserEnter:)]) {[self. The delegate onUserEnter: uid]; } } - (void)rtcEngine:(ARtcEngineKit *)engine didOfflineOfUid:(NSString *)uid reason:(ARUserOfflineReason)reason { /// Call ARLog(@"Calling - didOfflineOfUid = %@", uid) when the remote user (communication scenario)/anchor (live broadcast scenario) leaves the current channel. / / C2C curInvitingList don't remove the userID, if is your invite each other, here was removed, and finally hair signaling the end of time can't find the if (self. IsMembers | | (! self.isMembers && reason == ARUserOfflineReasonQuit)) { if ([self.curInvitingList containsObject:uid]) { [self.curInvitingList removeObject:uid]; } if ([self.curRoomList containsObject:uid]) { [self.curRoomList removeObject:uid]; } if ([self canDelegateRespondMethod:@selector(onUserLeave:)]) { [self.delegate onUserLeave:uid]; } [self preExitRoom]; } else if (reason == ARUserOfflineReasonDropped) { [self dealWithException:10]; } } - (void)rtcEngine:(ARtcEngineKit *)engine remoteVideoStateChangedOfUid:(NSString *)uid State :(ARVideoRemoteState)state reason:(ARVideoRemoteStateReason)reason elapsed:(NSInteger)elapsed {/// the remote video state change callback if  (reason == ARVideoRemoteStateReasonRemoteMuted || reason == ARVideoRemoteStateReasonRemoteUnmuted) { if ([self canDelegateRespondMethod:@selector(onUserVideoAvailable:available:)]) { [self.delegate onUserVideoAvailable:uid available:(reason == ARVideoRemoteStateReasonRemoteMuted) ? NO : YES]; } } } - (void)rtcEngine:(ARtcEngineKit *)engine remoteAudioStateChangedOfUid:(NSString *)uid State :(ARAudioRemoteState)state reason:(ARAudioRemoteStateReason)reason elapsed:(NSInteger)elapsed {/// the remote audio state change callback if  (reason == ARAudioRemoteReasonRemoteMuted || reason == ARAudioRemoteReasonRemoteUnmuted) { if ([self canDelegateRespondMethod:@selector(onUserAudioAvailable:available:)]) { [self.delegate onUserAudioAvailable:uid available:(reason == ARAudioRemoteReasonRemoteMuted) ? NO : YES]; } } } - (void)rtcEngine:(ARtcEngineKit *)engine reportAudioVolumeIndicationOfSpeakers:(NSArray<ARtcAudioVolumeInfo *> *)speakers totalVolume:(NSInteger)totalVolume {/// the callback to indicate who is speaking in channel, speaker volume and whether local user is speaking if ([self) canDelegateRespondMethod:@selector(onUserVoiceVolume:volume:)]) { for (ARtcAudioVolumeInfo *info in speakers) { if ([info.uid isEqualToString:@"0"]) { [self.delegate onUserVoiceVolume:[ARUILogin getUserID] volume:(UInt32)info.volume]; } else { [self.delegate onUserVoiceVolume:info.uid volume:(UInt32)info.volume]; } } } } - (void)rtcEngine:(ARtcEngineKit *)engine connectionChangedToState:(ARConnectionStateType)state reason:(ARConnectionChangedReason)reason { //ARLog(@"Calling - rtc connectionStateChanged state = %ld reason = %ld", (long)state, (long)reason); } - (void)rtcEngine:(ARtcEngineKit *)engine didVideoSubscribeStateChange:(NSString *)channel withUid:(NSString *)uid oldState:(ARStreamSubscribeState)oldState newState:(ARStreamSubscribeState)newState elapseSinceLastState:(NSInteger)elapseSinceLastState { ARLog(@"Calling - didVideoSubscribeStateChange = %@ %@ %lu %lu", channel, uid, (unsigned long)oldState, (unsigned long)newState); }Copy the code
Effect display (Multi-party video call)

Code implementation

- (void)addSignalListener {
    ARUILogin.kit.aRtmDelegate = self;
    self.callEngine.callDelegate = self;
    /// 用户进入后台推送问题
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(enterBackground:) name:UIApplicationWillResignActiveNotification object:nil];
    [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(becomeActive:) name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)removeSignalListener {
    self.callEngine.callDelegate = nil;
    self.callEngine = nil;
    [NSNotificationCenter.defaultCenter removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
    [NSNotificationCenter.defaultCenter removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)invite:(NSString *)receiver action:(CallAction)action {
    if (action == CallAction_Call) {
        /// 发起呼叫邀请
        NSMutableArray *arr = [[NSMutableArray alloc] initWithObjects:[ARUILogin getUserID], nil];
        [arr addObjectsFromArray:self.calleeUserIDs];
        
        NSMutableArray *infoArr = [NSMutableArray array];
        for (NSInteger i = 0; i < arr.count; i++) {
            ARCallUser *user = [ARUILogin getCallUserInfo:arr[i]];
            [infoArr addObject:[NSObject ar_dictionaryWithObject: user]];
        }
        
        NSDictionary *dic = @{@"Mode": @(self.curType == CallType_Video ? 0 : 1),
                             @"Conference": [NSNumber numberWithBool:self.isMembers],
                             @"ChanId": self.curRoomID,
                             @"UserData": arr,
                             @"UserInfo": infoArr
        };
        ARtmLocalInvitation *localInvitation = [[ARtmLocalInvitation alloc] initWithCalleeId:receiver];
        localInvitation.content = [ARTCCallingUtils dictionary2JsonStr:dic];
        [self.callEngine sendLocalInvitation:localInvitation completion:^(ARtmInvitationApiCallErrorCode errorCode) {
            ARLog(@"sendLocalInvitation code = %ld", (long)errorCode);
        }];
        [self.callingDic setObject:localInvitation forKey:receiver];
    } else if (action == CallAction_Cancel) {
        /// 取消呼叫邀请
        id invitation = [self.callingDic objectForKey:receiver];
        if (invitation) {
            ARtmLocalInvitation *localInvitation = (ARtmLocalInvitation *)invitation;
            [self.callEngine cancelLocalInvitation:localInvitation completion:^(ARtmInvitationApiCallErrorCode errorCode) {
                ARLog(@"cancelLocalInvitation code = %ld", (long)errorCode);
            }];
        }
    } else if (action == CallAction_Accept) {
        /// 接受呼叫邀请
        id invitation = [self.calledDic objectForKey:receiver];
        if (invitation) {
            ARtmRemoteInvitation *remoteInvitation = (ARtmRemoteInvitation *)invitation;
            NSDictionary *dic = @{@"Mode": @(self.curType == CallType_Video ? 0: 1), @"Conference": [NSNumber numberWithBool:self.isMembers]};
            remoteInvitation.response = [ARTCCallingUtils dictionary2JsonStr:dic];
            [self.callEngine acceptRemoteInvitation:remoteInvitation completion:^(ARtmInvitationApiCallErrorCode errorCode) {
                ARLog(@"acceptRemoteInvitation code = %ld", (long)errorCode);
            }];
        }
    } else if (action == CallAction_Reject) {
        /// 拒绝呼叫邀请
        id invitation = [self.calledDic objectForKey:receiver];
        if (invitation) {
            ARtmRemoteInvitation *remoteInvitation = (ARtmRemoteInvitation *)invitation;
            [self.callEngine refuseRemoteInvitation:remoteInvitation completion:^(ARtmInvitationApiCallErrorCode errorCode) {
                ARLog(@"refuseRemoteInvitation code = %ld", (long)errorCode);
            }];
        }
    } else if (action == CallAction_SwitchToAudio) {
        /// 切换成语音通话
        NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:@"SwitchAudio", @"Cmd",nil];
        ARtmMessage *message = [[ARtmMessage alloc] initWithText:[ARTCCallingUtils dictionary2JsonStr:dic]];
        [self sendPeerMessage:message user:receiver];
    } else if (action == CallAction_End) {
        /// 通话中断
        if (!self.isMembers) {
            NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:@"EndCall", @"Cmd",nil];
            ARtmMessage *message = [[ARtmMessage alloc] initWithText:[ARTCCallingUtils dictionary2JsonStr:dic]];
            [self sendPeerMessage:message user:receiver];
        }
    }
}

- (void)preExitRoom {
    /// 当前房间中存在成员,不能自动退房
    if (self.curRoomList.count > 0) return;
    
    /// 存在正在呼叫的通话
    if (self.curInvitingList.count >= 1) {
        return;
    }
    
    [self exitRoom];
}

- (void)exitRoom {
    ARLog(@"Calling - exitRoom");
    if ([self canDelegateRespondMethod:@selector(onCallEnd)]) {
        [self.delegate onCallEnd];
    }
    
    for (NSString *uid in self.timerDic.allKeys) {
        [self removeTimer:uid];
    }
    
    [self dealWithException:0];
    [self leaveRoom];
    self.isOnCalling = NO;
    
    if(UIApplication.sharedApplication.applicationState == UIApplicationStateBackground) {
        [self logout];
    }
}

// MARK: - privite

- (ARtmCallKit *)callEngine {
    return [ARUILogin.kit getRtmCallKit];
}

- (void)sendPeerMessage:(ARtmMessage *)message user:(NSString *)uid {
    ARLog(@"Calling - sendPeerMessage = %@", message.text);
    ARtmSendMessageOptions *options = [[ARtmSendMessageOptions alloc] init];
    [[ARUILogin kit] sendMessage:message toPeer:uid sendMessageOptions:options completion:^(ARtmSendPeerMessageErrorCode errorCode) {
        ARLog(@"Calling - SendPeerMessage code = %ld", (long)errorCode);
    }];
}

- (void)logout {
    if (!self.isOnCalling && ARUILogin.kit != nil) {
        [ARUILogin.kit logoutWithCompletion:nil];
        self.interrupt = YES;
    }
}

- (void)enterBackground:(NSNotification *)notification {
    [self logout];
}

- (void)becomeActive:(NSNotification *)notification {
    if (!self.isOnCalling && self.interrupt) {
        [ARUILogin.kit loginByToken:nil user:ARUILogin.getUserID completion:nil];
        self.interrupt = NO;
    }
}

//MARK: - ARtmDelegate

- (void)rtmKit:(ARtmKit *)kit connectionStateChanged:(ARtmConnectionState)state reason:(ARtmConnectionChangeReason)reason {
    ARLog(@"Calling - rtm connectionStateChanged state = %ld reason = %ld", (long)state, (long)reason);
    if (reason == ARtmConnectionChangeReasonRemoteLogin) {
        [self.delegate onError:401 msg:@"RemoteLogin"];
        [self exitRoom];
        return;
    }
    
    if (!self.isMembers) {
        if (state == ARtmConnectionStateDisconnected || state == ARtmConnectionStateReconnecting) {
            self.isReconnection = YES;
            [self dealWithException: 30];
            
        } else if (state == ARtmConnectionStateConnected) {
            [self dealWithException:0];
            
            if (self.isReconnection && self.isOnCalling && self.currentCallingUserID) {
                /// 兼容异常
                [self dealWithException:10];
                self.isReconnection = NO;
                NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:@"CallState", @"Cmd",nil];
                ARtmMessage *message = [[ARtmMessage alloc] initWithText:[ARTCCallingUtils dictionary2JsonStr:dic]];
                [self sendPeerMessage:message user:self.currentCallingUserID];
            }
        }
    }
}

- (void)rtmKit:(ARtmKit *)kit messageReceived:(ARtmMessage *)message fromPeer:(NSString *)peerId {
    /// 收到点对点消息回调
    ARLog(@"Calling - messageReceived text = %@ ", message.text);
    if (message.text.length != 0) {
        NSDictionary *dic = [ARTCCallingUtils jsonSring2Dictionary:message.text];
        NSString *value = [dic objectForKey:@"Cmd"];
        if ([value isEqualToString:@"SwitchAudio"]) {
            /// 切换成语音通话
            if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) {
                [self.delegate onSwitchToAudio:YES message:@""];
            }
            self.curType = CallType_Audio;
            
        } else if ([value isEqualToString:@"EndCall"]) {
            /// 结束通话
            if (!self.isMembers) {
                if ([self canDelegateRespondMethod:@selector(onUserLeave:)]) {
                    [self.delegate onUserLeave:peerId];
                }
                [self exitRoom];
            }
        } else if ([value isEqualToString:@"CallState"]) {
            /// 确认通话状态
            NSDictionary *dic;
            if (self.isCallSucess) {
                dic = @{@"Cmd": @"CallStateResult", @"state": @(2), @"Mode": (self.curType == CallType_Video ? @(0) : @(1))};
            } else {
                dic= @{@"Cmd": @"CallStateResult", @"state": @(1)};
            }
            ARtmMessage *message = [[ARtmMessage alloc] initWithText:[ARTCCallingUtils dictionary2JsonStr:dic]];
            [self sendPeerMessage:message user:peerId];
            
        } else if ([value isEqualToString:@"CallStateResult"]) {
            /// 对方通话状态回复结果
            [self dealWithException:0];
            
            int state = [[dic objectForKey:@"state"] intValue];
            if (state == 0) {
                /// 已挂断
                [self exitRoom];
            } else if (state == 1) {
                /// 呼叫等待
            } else {
                /// 已同意
                int mode = [[dic objectForKey:@"Mode"] intValue];
                if (self.curType == CallType_Video && mode == 1) {
                    self.curType = CallType_Audio;
                    [self switchToAudio];
                }
            }
        }
    }
}

// MARK: - ARtmCallDelegate

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit localInvitationReceivedByPeer:(ARtmLocalInvitation * _Nonnull)localInvitation {
    /// 被叫已收到呼叫邀请
    ARLog(@"Calling - localInvitationReceivedByPeer");
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit localInvitationAccepted:(ARtmLocalInvitation * _Nonnull)localInvitation withResponse:(NSString * _Nullable) response {
    /// 被叫已接受呼叫邀请
    ARLog(@"Calling - localInvitationAccepted response = %@", response);
    [self.callingDic removeObjectForKey:localInvitation.calleeId];
    if (response != nil) {
        NSDictionary * dic = [ARTCCallingUtils jsonSring2Dictionary:response];
        if (self.curType == CallType_Video && [[dic objectForKey:@"Mode"] intValue] == 1) {
            if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) {
                [self.delegate onSwitchToAudio:YES message:@""];
            }
            self.curType = CallType_Audio;
        }
    }
    
    self.isCallSucess = YES;
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit localInvitationRefused:(ARtmLocalInvitation * _Nonnull)localInvitation withResponse:(NSString * _Nullable) response {
    /// 被叫已拒绝呼叫邀请
    ARLog(@"Calling - localInvitationRefused");
    [self.callingDic removeObjectForKey:localInvitation.calleeId];
    
    BOOL isBusy = NO;
    if (localInvitation.response.length != 0) {
        NSDictionary * dic = [ARTCCallingUtils jsonSring2Dictionary:localInvitation.response];
        if ([dic.allValues containsObject:@"Calling"]) {
            isBusy = YES;
        }
    }
    
    if (self.delegate) {
        NSString *uid = localInvitation.calleeId;
        if ([self.curInvitingList containsObject:uid]) {
            [self.curInvitingList removeObject:uid];
        }
        if (isBusy) {
            if ([self canDelegateRespondMethod:@selector(onLineBusy:)]) {
                [self.delegate onLineBusy:localInvitation.calleeId];
            }
        } else {
            if ([self canDelegateRespondMethod:@selector(onReject:)]) {
                [self.delegate onReject:uid];
            }
        }
        [self preExitRoom];
    }
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit localInvitationCanceled:(ARtmLocalInvitation * _Nonnull)localInvitation {
    /// 呼叫邀请已被取消
    ARLog(@"Calling - localInvitationCanceled");
    [self.callingDic removeObjectForKey:localInvitation.calleeId];
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit localInvitationFailure:(ARtmLocalInvitation * _Nonnull)localInvitation errorCode:(ARtmLocalInvitationErrorCode)errorCode {
    /// 呼叫邀请发送失败
    ARLog(@"Calling - localInvitationFailure");
    NSString *calleeId = localInvitation.calleeId;
    [self.callingDic removeObjectForKey:calleeId];
    
    if ([self canDelegateRespondMethod:@selector(onNoResp:)]) {
        [self.delegate onNoResp:calleeId];
    }
    if ([self.curInvitingList containsObject:calleeId]) {
        [self.curInvitingList removeObject:calleeId];
    }
    [self preExitRoom];
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit remoteInvitationReceived:(ARtmRemoteInvitation * _Nonnull)remoteInvitation {
    /// 收到一个呼叫邀请
    ARLog(@"Calling - remoteInvitationReceived");
    [self.calledDic setObject:remoteInvitation forKey:remoteInvitation.callerId];
    if (!self.isOnCalling) {
        self.isOnCalling = YES;
        self.curSponsorForMe = remoteInvitation.callerId;
        self.currentCallingUserID = remoteInvitation.callerId;
        
        NSDictionary *dic = [ARTCCallingUtils jsonSring2Dictionary:remoteInvitation.content];
        self.isMembers = [[dic objectForKey:@"Conference"] boolValue];
        self.curRoomID = [dic objectForKey:@"ChanId"];
        CallType type = ([[dic objectForKey:@"Mode"] intValue] == 0) ? CallType_Video : CallType_Audio;
        self.curType = type;
        if ([dic.allKeys containsObject:@"UserInfo"]) {
            NSArray *infoArr = [dic objectForKey:@"UserInfo"];
            for (NSInteger i = 0; i < infoArr.count; i++) {
                ARCallUser *user = [ARCallUser ar_objectWithDictionary: infoArr[i]];
                [ARUILogin setCallUserInfo:user];
            }
        }
        
        if (self.isMembers) {
            /// 多人通话
            NSArray *arr = [dic objectForKey:@"UserData"];
            [self.delegate onInvited:remoteInvitation.callerId userIds:arr isFromGroup:NO callType:type];
            [self createMemberChannel];
            
            /// 30s
            for (NSInteger i = 0; i < arr.count; i++) {
                NSString *uid = arr[i];
                /// 被叫对其他受邀者倒计时 -- 异常处理
                if (![uid isEqualToString:[ARUILogin getUserID]] && ![uid isEqualToString:self.curSponsorForMe]) {
                    __block NSInteger totalTime = 0;
                    NSTimeInterval interval = 1.0;
                    __weak typeof(self) weakSelf = self;
                    NSString *timerName = [ARTCGCDTimer timerTask:^{
                        totalTime += (NSInteger)interval;
                        if (totalTime == 30) {
                            if ([weakSelf canDelegateRespondMethod:@selector(onNoResp:)]) {
                                [weakSelf.delegate onNoResp:uid];
                                [weakSelf removeTimer: uid];
                            }
                        }
                        ARLog(@"%@ ==> %ld \n", uid, (long)totalTime);
                    } start:0 interval:interval repeats:YES async:NO];
                    [self.timerDic setObject:timerName forKey:uid];
                }
            }
        } else {
            /// 单人通话
            [self.delegate onInvited:remoteInvitation.callerId userIds:@[[ARUILogin getUserID]] isFromGroup:NO callType:type];
        }
    } else {
        NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:@"Calling", @"Cmd",nil];
        remoteInvitation.response = [ARTCCallingUtils dictionary2JsonStr:dic];
        [self invite:remoteInvitation.callerId action:CallAction_Reject];
    }
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit remoteInvitationRefused:(ARtmRemoteInvitation * _Nonnull)remoteInvitation {
    /// 拒绝呼叫邀请成功
    ARLog(@"Calling - remoteInvitationRefused");
    [self.calledDic removeObjectForKey:remoteInvitation.callerId];
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit remoteInvitationAccepted:(ARtmRemoteInvitation * _Nonnull)remoteInvitation {
    /// 接受呼叫邀请成功
    ARLog(@"Calling - remoteInvitationAccepted");
    [self.calledDic removeObjectForKey:remoteInvitation.callerId];
}

- (void)rtmCallKit:(ARtmCallKit * _Nonnull)callKit remoteInvitationCanceled:(ARtmRemoteInvitation * _Nonnull)remoteInvitation {
    /// 主叫已取消呼叫邀请
    ARLog(@"Calling - remoteInvitationCanceled");
    [self.calledDic removeObjectForKey:remoteInvitation.callerId];
    
    if ([self.curSponsorForMe isEqualToString:remoteInvitation.callerId]) {
        [self exitRoom];
        if ([self canDelegateRespondMethod:@selector(onCallingCancel:)]) {
            [self.delegate onCallingCancel:remoteInvitation.callerId];
        }
    }
}

Copy the code

conclusion

Finally, there are some bugs and feature points to be improved in the ARCallPlus open source project. Github open source download address.

Github open source download address.