IOS技术分享| ARCallPlus 开源项目(一)
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开源下载地址。
【推荐】中国电信天翼云云端翼购节,2核2G云服务器一口价38元/年
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步