IOS技術分享| ARCallPlus 開源專案(一)
ARCallPlus 簡介
ARCallPlus 是開源的音視訊通話專案,同時支援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
/// 通話回撥
/// @param listener 回撥
- (void)setCallingListener:(id
/// 設定鈴聲,建議在30s以內,只支援本地音訊檔案 /// @param filePath 音訊檔案路徑 - (void)setCallingBell:(NSString *)filePath;
/// 開啟靜音模式(預設關) - (void)enableMuteMode:(BOOL)enable;
/// 開啟自定義路由(預設關) /// @param enable 開啟後,在onStart回撥中,會收到對應的ViewController物件,可以自行決定檢視展示方式 - (void)enableCustomViewRoute:(BOOL)enable;
@end
@protocol ARUICallingListerner
// 收到呼叫時,先通過此方法詢問是否可以喚起被叫UI. // 返回為true,直接喚起UI。返回為false,內部返回忙線 // 不實現預設直接可以喚起UI - (BOOL)shouldShowOnCallView NS_SWIFT_NAME(shouldShowOnCallView());
/// 呼叫開始回撥。主叫、被叫均會觸發;
/// 被叫觸發時,會將控制器通過監聽回調出來,由接入方決定顯示方案。
/// @param userIDs 本次通話使用者id(自己除外)
/// @param type 通話型別:視訊\音訊
/// @param role 通話角色:主叫\被叫
/// @param viewController 提供Calling功能頁面給呼叫方,可以讓使用者在此基礎上自定義
- (void)callStart:(NSArray
/// 通話結束回撥
/// @param userIDs 本次通話使用者id(自己除外)
/// @param type 通話型別:視訊\音訊
/// @param role 通話角色:主叫\被叫
/// @param totalTime 通話時長
- (void)callEnd:(NSArray
/// 通話事件回撥 /// @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
@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
-
(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和待完善的功能點。Github開源下載地址。
- Android技術分享| ViewPager2離屏載入,實現抖音上下視訊滑動
- Android技術分享| Activity 過渡動畫 — 讓切換更加炫酷
- Linux下玩轉nginx系列(七)---nginx如何實現限流功能
- 技術分享| 如何部署安裝分散式序列號生成器系統
- web技術分享| 【地圖】實現自定義的軌跡回放
- 解決方案| 快對講綜合排程系統
- 實時訊息RTM| 多活架構中的資料一致性問題
- Android技術分享| Context淺析
- Android技術分享| Context淺析
- 螢幕共享的實現與應用
- 技術分析| 即時通訊和實時通訊的區別
- IOS技術分享| ARCallPlus 開源專案(二)
- Android技術分享| Android 中部分記憶體洩漏示例及解決方案
- Android技術分享| 安卓3行程式碼,實現整套音視訊通話功能
- 行業分析| 快對講Poc方案的優勢
- Android技術分享|【自定義View】實現Material Design的Loading效果
- IOS技術分享| ARCallPlus 開源專案(一)
- web技術分享| WebRTC控制攝像機平移、傾斜和縮放
- Android技術分享| anyLive 開源專案
- Android技術分享| 【Android 自定義View】多人視訊通話控制元件