IOS技术分享| ARCallPlus 开源项目

知识养成了思想,思想同时又在融化知识。这篇文章主要讲述IOS技术分享| ARCallPlus 开源项目相关的知识,希望能为你提供帮助。
ARCallPlus 简介
ARCallPlus 是 anyRTC 开源的音视频通话项目,同时支持ios、android、Web等平台。本文主要介绍音视频通话 ARUICalling 模块 iOS 本地库的封装。
源码下载【IOS技术分享| ARCallPlus 开源项目】三行代码、二十分钟应用内构建,实现音视频通话。本项目已上架App Store,欢迎下载体验。

  • GitHub 开源地址
  • App Store 下载地址
开发环境
  • 开发工具:Xcode13 真机运行
  • 开发语言:Objective-C、Swift
项目结构
IOS技术分享| ARCallPlus 开源项目

文章图片

核心 API:
  • ARUILogin(登录 API)
  • ARUICalling(通话 API)
  • ARUICallingListerner(通话回调)
内部核心 API:
  • ARTCCalling(音视频)
  • ARTCCallingDelegate(音视频回调)
  • ARTCCalling+Signal(实时消息)
核心 API 和回调
@interface ARUICalling : NSObject+ (instancetype)shareInstance; /// 通话接口 /// @param users 用户信息 /// @param type 呼叫类型:视频/语音 - (void)call:(NSArray< ARCallUser *> *)users type:(ARUICallingType)type; /// 通话回调 /// @param listener 回调 - (void)setCallingListener:(id< ARUICallingListerner> )listener; /// 设置铃声,建议在30s以内,只支持本地音频文件 /// @param filePath 音频文件路径 - (void)setCallingBell:(NSString *)filePath; /// 开启静音模式(默认关) - (void)enableMuteMode:(BOOL)enable; /// 开启自定义路由(默认关) /// @param enable 打开后,在onStart回调中,会收到对应的ViewController对象,可以自行决定视图展示方式 - (void)enableCustomViewRoute:(BOOL)enable; @end@protocol ARUICallingListerner < NSObject> // 收到呼叫时,先通过此方法询问是否可以唤起被叫UI. // 返回为true,直接唤起UI。返回为false,内部返回忙线 // 不实现默认直接可以唤起UI - (BOOL)shouldShowOnCallView NS_SWIFT_NAME(shouldShowOnCallView()); /// 呼叫开始回调。主叫、被叫均会触发; /// 被叫触发时,会将控制器通过监听回调出来,由接入方决定显示方案。 /// @param userIDs 本次通话用户id(自己除外) /// @param type 通话类型:视频\\音频 /// @param role 通话角色:主叫\\被叫 /// @param viewController 提供Calling功能页面给调用方,可以让用户在此基础上自定义 - (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 本次通话用户id(自己除外) /// @param type 通话类型:视频\\音频 /// @param role 通话角色:主叫\\被叫 /// @param totalTime 通话时长 - (void)callEnd:(NSArray< NSString *> *)userIDs type:(ARUICallingType)type role:(ARUICallingRole)role totalTime:(float)totalTime NS_SWIFT_NAME(callEnd(userIDs:type:role:totalTime:)); /// 通话事件回调 /// @param event 回调事件类型 /// @param type 通话类型:视频\\音频 /// @param role 通话角色:主叫\\被叫 /// @param message 事件 - (void)onCallEvent:(ARUICallingEvent)event type:(ARUICallingType)type role:(ARUICallingRole)role message:(NSString *)message NS_SWIFT_NAME(onCallEvent(event:type:role:message:)); /// 推送事件回调 /// @param userIDs 不在线的用户id /// @param type 通话类型:视频\\音频 - (void)onPushToOfflineUser:(NSArray< NSString *> *)userIDs type:(ARUICallingType)type; @end

示例代码
效果展示(点对点音频通话)
IOS技术分享| ARCallPlus 开源项目

文章图片

代码实现
- (ARtcEngineKit *)rtcEngine if (!_rtcEngine) /// 实例化音视频引擎对象 _rtcEngine = [ARtcEngineKit sharedEngineWithAppId:[ARUILogin getSdkAppID] delegate:self]; /// 直播模式 [_rtcEngine setChannelProfile: ARChannelProfileLiveBroadcasting]; [_rtcEngine setClientRole: ARClientRoleBroadcaster]; /// 编码配置 ARVideoEncoderConfiguration *configuration = [[ARVideoEncoderConfiguration alloc] init]; configuration.dimensions = CGSizeMake(960, 540); configuration.frameRate = 15; configuration.bitrate = 500; [_rtcEngine setVideoEncoderConfiguration:configuration]; /// 启用说话者音量提示 [_rtcEngine enableAudioVolumeIndication:2000 smooth:3 report_vad: YES]; /// 开启美颜 [_rtcEngine setBeautyEffectOptions:YES options:[[ARBeautyOptions alloc]init]]; return _rtcEngine; //MARK: - Publish Method- (void)call:(NSArray *)userIDs type:(CallType)type /// 发起通话 if (!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]; // 如果不在当前邀请列表,则新增 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 /// 主动挂断通话 __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"); [self.curInvitingList enumerateObjectsUsingBlock:^(NSString *invitedId, NSUInteger idx, BOOL * _Nonnull stop) [self invite:invitedId action:CallAction_Cancel]; ]; [self leaveRoom]; self.isOnCalling = NO; - (void)switchToAudio /// 切换到语音通话(通话中) 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 /// 开启远程用户视频渲染 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 /// 关闭远程用户视频渲染 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 /// 打开摄像头 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;

效果展示(点对点视频通话)
IOS技术分享| ARCallPlus 开源项目

文章图片

代码实现
//MARK: - ARtcEngineDelegate- (void)rtcEngine:(ARtcEngineKit *)engine didOccurError:(ARErrorCode)errorCode /// 发生错误回调 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 /// 远端用户/主播加入回调 ARLog(@"Calling - didJoinedOfUid = %@", uid); // C2C curInvitingList 不要移除 userID,如果是自己邀请的对方,这里移除后,最后发结束信令的时候找不到人 [self dealWithException:0]; [self removeTimer:uid]; if ([self.curInvitingList containsObject:uid]) [self.curInvitingList removeObject:uid]; if (![self.curRoomList containsObject:uid]) [self.curRoomList addObject:uid]; // C2C 通话要计算通话时长 if ([self canDelegateRespondMethod:@selector(onUserEnter:)]) [self.delegate onUserEnter:uid]; - (void)rtcEngine:(ARtcEngineKit *)engine didOfflineOfUid:(NSString *)uid reason:(ARUserOfflineReason)reason /// 远端用户(通信场景)/主播(直播场景)离开当前频道回调 ARLog(@"Calling - didOfflineOfUid = %@", uid); // C2C curInvitingList 不要移除 userID,如果是自己邀请的对方,这里移除后,最后发结束信令的时候找不到人 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 /// 远端视频状态发生改变回调 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 /// 远端音频状态发生改变回调 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 /// 提示频道内谁正在说话、说话者音量及本地用户是否在说话的回调 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);

效果展示(多人视频通话)
IOS技术分享| ARCallPlus 开源项目

文章图片

代码实现
- (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 = https://www.songbingjia.com/android/[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];

结束语
最后,ARCallPlus开源项目中还存在一些bug和待完善的功能点。有不足之处欢迎大家指出issues。最后再贴一下 Github开源下载地址。
Github开源下载地址。
IOS技术分享| ARCallPlus 开源项目

文章图片


    推荐阅读