写点什么

IOS 技术分享| ARCallPlus 开源项目(一)

作者:anyRTC开发者
  • 2022 年 3 月 22 日
  • 本文字数:14849 字

    阅读完需:约 49 分钟

ARCallPlus 简介

ARCallPlus 是 anyRTC 开源的音视频通话项目,同时支持 iOS、Android、Web 等平台。本文主要介绍音视频通话 ARUICalling 模块 iOS 本地库的封装。

源码下载

三行代码、二十分钟应用内构建,实现音视频通话。本项目已上架 App Store,欢迎下载体验。


开发环境

  • 开发工具:Xcode13 真机运行

  • 开发语言:Objective-C、Swift

项目结构


核心 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
复制代码

示例代码

效果展示(点对点音频通话)
代码实现
- (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;}
复制代码
效果展示(点对点视频通话)
代码实现
//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);}
复制代码
效果展示(多人视频通话)
代码实现

- (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]; } }}
复制代码

结束语

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


Github开源下载地址



发布于: 刚刚阅读数: 3
用户头像

实时交互,万物互联! 2020.08.10 加入

实时交互,万物互联,全球实时互动云服务商领跑者!

评论

发布
暂无评论
IOS技术分享| ARCallPlus 开源项目(一)_ios_anyRTC开发者_InfoQ写作平台