// // TRTCCall+Signal.m // TXIMSDK_TUIKit_iOS // // Created by xiangzhang on 2020/7/3. // #import "TRTCCalling+Signal.h" #import "TRTCCallingUtils.h" #import "TRTCCallingHeader.h" #import #import "TUILogin.h" #import "CallingLocalized.h" #import "TRTCSignalFactory.h" #import "TUICallingConstants.h" @implementation TRTCCalling (Signal) - (void)addSignalListener { [[V2TIMManager sharedInstance] addSignalingListener:self]; [[V2TIMManager sharedInstance] addSimpleMsgListener:self]; } - (void)removeSignalListener { [[V2TIMManager sharedInstance] removeSignalingListener:self]; } - (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo { return [self invite:receiver action:action model:model cmdInfo:cmdInfo userIds:nil]; } - (NSString *)invite:(NSString *)receiver action:(CallAction)action model:(CallModel *)model cmdInfo:(NSString *)cmdInfo userIds:(NSArray *)userIds { TRTCLog(@"Calling - invite receiver:%@ action:%ld cmdInfo:%@", receiver, action, cmdInfo); NSString *callID = @""; CallModel *realModel = [self generateModel:action]; BOOL isGroup = [self.curGroupID isEqualToString:receiver]; if (model) { realModel = [model copy]; realModel.action = action; if (model.groupid.length > 0) { isGroup = YES; } } /// 邀请 ID, 说明:不存在GroupID 组通话,分成多个C to C, 在Map中存储userID 和inviteID 关系。 GroupID组 和 单个 C to C 通话,inviteID 从Model中取。 NSString *inviteID = [self getCallIDWithUserID:receiver] ?: realModel.callid; switch (realModel.action) { case CallAction_Call: { NSString *cmd = @""; switch (realModel.calltype) { case CallType_Video: cmd = SIGNALING_CMD_VIDEOCALL; break; case CallType_Audio: cmd = SIGNALING_CMD_AUDIOCALL; break; default: break; } NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:cmd cmdInfo:@"" userIds:userIds ?: @[] message:@"" callType:realModel.calltype]; [dataDic setValue:@(realModel.roomid) forKey:SIGNALING_EXTRA_KEY_ROOM_ID]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; if (isGroup) { @weakify(self) callID = [[V2TIMManager sharedInstance] inviteInGroup:realModel.groupid inviteeList:realModel.invitedList data:data onlineUserOnly:self.onlineUserOnly timeout:SIGNALING_EXTRA_KEY_TIME_OUT succ:^{ TRTCLog(@"Calling - CallAction_Call inviteInGroup success"); @strongify(self) // 发起 Apns 推送,群组的邀请,需要单独对每个被邀请人发起推送 for (NSString *invitee in realModel.invitedList) { [self sendAPNsForCall:invitee inviteeList:realModel.invitedList callID:self.callID groupid:realModel.groupid roomid:realModel.roomid]; } } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_Call inviteInGroup failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; self.callID = callID; } else { @weakify(self) V2TIMOfflinePushInfo *info = [self getOfflinePushInfoWithInviteeList:realModel.invitedList callID:nil groupid:nil roomid:realModel.roomid]; callID = [[V2TIMManager sharedInstance] invite:receiver data:data onlineUserOnly:self.onlineUserOnly offlinePushInfo:info timeout:SIGNALING_EXTRA_KEY_TIME_OUT succ:^{ TRTCLog(@"Calling - CallAction_Call invite success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_Call invite failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; self.callID = callID; } } break; case CallAction_Accept: { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:@"" callType:realModel.calltype]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; @weakify(self) [[V2TIMManager sharedInstance] accept:inviteID data:data succ:^{ TRTCLog(@"Calling - CallAction_Accept accept success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_Accept accept failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; } break; case CallAction_Reject: { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:@"" callType:realModel.calltype]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; @weakify(self) [[V2TIMManager sharedInstance] reject:inviteID data:data succ:^{ TRTCLog(@"Calling - CallAction_Reject reject success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_Reject reject failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; } break; case CallAction_Linebusy: { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:SIGNALING_MESSAGE_LINEBUSY callType:realModel.calltype]; [dataDic setValue:SIGNALING_EXTRA_KEY_LINE_BUSY forKey:SIGNALING_EXTRA_KEY_LINE_BUSY]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; @weakify(self) [[V2TIMManager sharedInstance] reject:inviteID data:data succ:^{ TRTCLog(@"Calling - CallAction_Linebusy reject success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_Linebusy reject failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; } break; case CallAction_Cancel: { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:@"" cmdInfo:@"" message:@"" callType:realModel.calltype]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; @weakify(self) [[V2TIMManager sharedInstance] cancel:inviteID data:data succ:^{ TRTCLog(@"Calling - CallAction_Cancel cancel success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_Cancel cancel failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; } break; case CallAction_End: { if (isGroup) { // 群通话不需要计算通话时长 NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:SIGNALING_CMD_HANGUP cmdInfo:@"0" message:@"" callType:realModel.calltype]; [dataDic setValue:@(0) forKey:SIGNALING_EXTRA_KEY_CALL_END]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; // 这里发结束事件的时候,inviteeList 已经为 nil 了,可以伪造一个被邀请用户,把结束的信令发到群里展示。 // timeout 这里传 0,结束的事件不需要做超时检测 @weakify(self) callID = [[V2TIMManager sharedInstance] inviteInGroup:realModel.groupid inviteeList:@[@"inviteeList"] data:data onlineUserOnly:self.onlineUserOnly timeout:0 succ:^{ TRTCLog(@"Calling - CallAction_End inviteInGroup success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_End inviteInGroup failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; } else { NSDate *now = [NSDate date]; NSString *cmdInfo = [NSString stringWithFormat:@"%llu", (UInt64)[now timeIntervalSince1970] - self.startCallTS]; NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:SIGNALING_CMD_HANGUP cmdInfo:cmdInfo message:@"" callType:realModel.calltype]; [dataDic setValue:@((UInt64)[now timeIntervalSince1970] - self.startCallTS) forKey:SIGNALING_EXTRA_KEY_CALL_END]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; @weakify(self) callID = [[V2TIMManager sharedInstance] invite:receiver data:data onlineUserOnly:self.onlineUserOnly offlinePushInfo:nil timeout:0 succ:^{ TRTCLog(@"Calling - CallAction_End invite success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_End invite failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; self.startCallTS = 0; } } break; case CallAction_SwitchToAudio: { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:realModel.roomid cmd:SIGNALING_CMD_SWITCHTOVOICECALL cmdInfo:@"" message:@"" callType:realModel.calltype]; [dataDic setValue:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; @weakify(self) [[V2TIMManager sharedInstance] invite:receiver data:data onlineUserOnly:self.onlineUserOnly offlinePushInfo:nil timeout:SIGNALING_EXTRA_KEY_TIME_OUT succ:^{ TRTCLog(@"Calling - CallAction_SwitchToAudio invite success"); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - CallAction_SwitchToAudio invite failed, code: %d desc: %@", code, desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onError:msg:)]) { [self.delegate onError:code msg:desc]; }; }]; } break; default: break; } if (realModel.action != CallAction_Reject && realModel.action != CallAction_Accept && realModel.action != CallAction_End && realModel.action != CallAction_Cancel && realModel.action != CallAction_SwitchToAudio && realModel.action != CallAction_AcceptSwitchToAudio && realModel.action != CallAction_RejectSwitchToAudio && model == nil) { self.curLastModel = [realModel copy]; } return callID; } - (void)sendAPNsForCall:(NSString *)receiver inviteeList:(NSArray *)inviteeList callID:(NSString *)callID groupid:(NSString *)groupid roomid:(UInt32)roomid { TRTCLog(@"Calling - sendAPNsForCall receiver:%@ inviteeList:%@ groupid:%@ roomid:%d", receiver, inviteeList, groupid, roomid); if (callID.length == 0 || inviteeList.count == 0 || roomid == 0) { TRTCLog(@"sendAPNsForCall failed"); return; } V2TIMOfflinePushInfo *info = [self getOfflinePushInfoWithInviteeList:inviteeList callID:callID groupid:groupid roomid:roomid]; NSData *customMessage = [TRTCCallingUtils dictionary2JsonData:@{@"version" : @(Version) , @"businessID" : SIGNALING_BUSINESSID}]; V2TIMMessage *msg = [[V2TIMManager sharedInstance] createCustomMessage:customMessage]; // 针对每个被邀请成员单独邀请 [[V2TIMManager sharedInstance] sendMessage:msg receiver:receiver groupID:nil priority:V2TIM_PRIORITY_HIGH onlineUserOnly:YES offlinePushInfo:info progress:nil succ:nil fail:nil]; } - (V2TIMOfflinePushInfo *)getOfflinePushInfoWithInviteeList:(NSArray *)inviteeList callID:(NSString *)callID groupid:(NSString *)groupid roomid:(UInt32)roomid{ int chatType; //单聊:1 群聊:2 if (groupid.length > 0) { chatType = 2; } else { chatType = 1; groupid = @""; } /** {"entity":{"version":1,"content":"{\"action\":1,\"call_type\":2,\"room_id\":804544637,\"call_id\":\"144115224095613335-1595234230-3304653590\",\"timeout\":30,\"version\":4,\"invited_list\":[\"2019\"],\"group_id\":\"@TGS#1PWYXLTGA\"}","sendTime":1595234231,"sender":"10457","chatType":2,"action":2}} */ NSDictionary *contentParam = @{@"action":@(SignalingActionType_Invite), @"call_id":callID ?: @"", @"call_type":@(self.curType), @"invited_list":inviteeList.count > 0 ? inviteeList : @[], @"room_id":@(roomid), @"group_id":groupid, @"timeout":@(SIGNALING_EXTRA_KEY_TIME_OUT), @"version":@(Version)}; // TUIkit 业务版本 NSDictionary *entityParam = @{@"action" : @(APNs_Business_Call), // 音视频业务逻辑推送 @"chatType" : @(chatType), @"content" : [TRTCCallingUtils dictionary2JsonStr:contentParam], @"sendTime" : @((UInt32)[[V2TIMManager sharedInstance] getServerTime]), @"sender" : TUILogin.getUserID ?: @"", @"version" : @(APNs_Version)}; // 推送版本 NSDictionary *extParam = @{@"entity" : entityParam}; V2TIMOfflinePushInfo *info = [[V2TIMOfflinePushInfo alloc] init]; info.desc = TUICallingLocalize(@"Demo.TRTC.calling.callingrequest"); info.ext = [TRTCCallingUtils dictionary2JsonStr:extParam]; info.iOSSound = @"phone_ringing.mp3"; return info; } - (void)onReceiveGroupCallAPNs:(V2TIMSignalingInfo *)signalingInfo { if (signalingInfo.inviteID.length > 0 && signalingInfo.inviter.length > 0 && signalingInfo.inviteeList.count > 0 && signalingInfo.groupID.length > 0) { [[V2TIMManager sharedInstance] addInvitedSignaling:signalingInfo succ:^{ [self onReceiveNewInvitation:signalingInfo.inviteID inviter:signalingInfo.inviter groupID:signalingInfo.groupID inviteeList:signalingInfo.inviteeList data:signalingInfo.data]; } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - onReceiveAPNsForGroupCall failed,code:%d desc:%@",code,desc); }]; } } #pragma mark - V2TIMSignalingListener /// 收到邀请 - 回调 - (void)onReceiveNewInvitation:(NSString *)inviteID inviter:(NSString *)inviter groupID:(NSString *)groupID inviteeList:(NSArray *)inviteeList data:(NSString *)data { TRTCLog(@"Calling - onReceiveNewInvitation inviteID:%@ inviter:%@ inviteeList:%@ data:%@", inviteID, inviter, inviteeList, data); NSDictionary *param = [self check:data]; if (param) { self.isBeingCalled = YES; NSString *cmdInfoStr = @""; NSArray *userIds = nil; if ([param.allKeys containsObject:SIGNALING_EXTRA_KEY_DATA]) { NSDictionary *data = param[SIGNALING_EXTRA_KEY_DATA]; if ([data.allKeys containsObject:SIGNALING_EXTRA_KEY_CMDINFO]) { cmdInfoStr = data[SIGNALING_EXTRA_KEY_CMDINFO]; } if ([data.allKeys containsObject:SIGNALING_EXTRA_KEY_USERIDS]) { userIds = data[SIGNALING_EXTRA_KEY_USERIDS]; } } NSDictionary *data = [TRTCSignalFactory getDataDictionary:param]; CallModel *model = [[CallModel alloc] init]; model.callid = inviteID; model.groupid = groupID; model.inviter = inviter; model.invitedList = [NSMutableArray arrayWithArray:inviteeList]; model.calltype = [TRTCSignalFactory convertCmdToCallType:data[SIGNALING_EXTRA_KEY_CMD]]; model.roomid = [data[SIGNALING_EXTRA_KEY_ROOMID] intValue]; if ([data[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { /// 多端登录,A1和A2同时登录账号A,A1呼叫B,B接听,B点击切换语音按钮,A2需要过滤掉A1的「邀请信令」 /// 过滤条件:当前不在通话流程中, 收到被邀请者的点击切换按钮的「邀请信令」,不做处理。 if (!self.isOnCalling) { return; } model.action = CallAction_SwitchToAudio; self.switchToAudioCallID = inviteID; } else { /// 多端登录,A1和A2同时登录账号A,账号A1呼叫账号B 或者 A1点击切换语音,过滤掉A2收到的自己的邀请信令 if ([self checkLoginUserIsEqualTo:inviter]) { return; } self.currentCallingUserID = inviter; model.action = CallAction_Call; } [self handleCallModel:inviter model:model message:cmdInfoStr userIds:userIds]; } } /// 邀请被取消 - 回调 - (void)onInvitationCancelled:(NSString *)inviteID inviter:(NSString *)inviter data:(NSString *)data { TRTCLog(@"Calling - onInvitationCancelled inviteID:%@ inviter:%@ data:%@", inviteID, inviter, data); /// 多端登录,A1和A2同时登录账号A,账号A呼叫账号B ,账号A取消通话,过滤掉A1和A2自己收到的此信令 if ([self checkLoginUserIsEqualTo:inviter]) { return; } NSDictionary *param = [self check:data]; if (!(param && [param isKindOfClass:[NSDictionary class]])) { return; } CallModel *model = [[CallModel alloc] init]; model.callid = inviteID; model.action = CallAction_Cancel; [self handleCallModel:inviter model:model message:@""]; } /// 邀请者接受邀请 - 回调 - (void)onInviteeAccepted:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString *)data { TRTCLog(@"Calling - onInviteeAccepted inviteID:%@ invitee:%@ data:%@", inviteID, invitee, data); /// 多端登录,A1和A2同时登录账号A,A1呼叫B,B接听,A2 需要过滤掉B「接受邀请信令」 /// 过滤条件:当前不在通话流程中, 收到邀请者接受邀请,不做处理。 if (!self.isOnCalling) { return; } NSDictionary *param = [self check:data]; if (!(param && [param isKindOfClass:[NSDictionary class]])) { return; } [TRTCCloud sharedInstance].delegate = self; CallModel *model = [[CallModel alloc] init]; model.callid = inviteID; NSDictionary *paramData = [TRTCSignalFactory getDataDictionary:param]; if ([paramData[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { model.action = CallAction_AcceptSwitchToAudio; } else { /// 多端登录,A1和A2同时登录账号A,账号B呼叫账号A ,A1接听正常处理,A2需要退出界面 if (!self.isProcessedBySelf && [self checkLoginUserIsEqualTo:invitee]) { [self exitRoom]; return; } model.action = CallAction_Accept; } [self handleCallModel:invitee model:model message:@""]; } /// 被邀请者拒绝邀请 - 回调 - (void)onInviteeRejected:(NSString *)inviteID invitee:(NSString *)invitee data:(NSString *)data { TRTCLog(@"Calling - onInviteeRejected inviteID:%@ invitee:%@ data:%@", inviteID, invitee, data); /// 多端登录,A1和A2同时登录账号A,A1呼叫B,B拒绝接听,A2 需要过滤掉B「拒绝邀请信令」 /// 过滤条件:当前不在通话流程中, 收到被邀请者拒绝邀请信令,不做处理。 if (!self.isOnCalling) { return; } NSDictionary *param = [self check:data]; if (!param || ![param isKindOfClass:[NSDictionary class]]) { return; } CallModel *model = [[CallModel alloc] init]; model.callid = inviteID; NSDictionary *dataDic = [TRTCSignalFactory getDataDictionary:param]; if ([dataDic[SIGNALING_EXTRA_KEY_MESSAGE] isEqualToString:SIGNALING_MESSAGE_LINEBUSY]) { model.action = CallAction_Linebusy; } else if ([dataDic[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_SWITCHTOVOICECALL]) { model.action = CallAction_AcceptSwitchToAudio; } else { /// 多端登录,A1和A2同时登录账号A,账号B呼叫账号A ,A1拒接通话,A2退出界面 if (!self.isProcessedBySelf && [self checkLoginUserIsEqualTo:invitee]) { [self exitRoom]; return; } model.action = CallAction_Reject; } [self handleCallModel:invitee model:model message:@"Other status error"]; } /// 邀请超时 - 回调 - (void)onInvitationTimeout:(NSString *)inviteID inviteeList:(NSArray *)invitedList { TRTCLog(@"Calling - onInvitationTimeout inviteID:%@ invitedList:%@", inviteID, invitedList); /// 多端登录,A1和A2同时登录账号A,A1呼叫B,B未接听,A2 需要过滤掉B「超时信令」 /// 过滤条件:当前不在通话流程中, 收到被邀请者拒绝邀请信令,不做处理。 if (!self.isOnCalling) { return; } CallModel *model = [[CallModel alloc] init]; model.callid = inviteID; model.invitedList = [NSMutableArray arrayWithArray:invitedList]; model.action = CallAction_Timeout; [self handleCallModel:[invitedList firstObject] model:model message:@"Timeout"]; } - (NSDictionary *)check:(NSString *)data { NSDictionary *signalingDictionary = [TRTCCallingUtils jsonSring2Dictionary:data]; if(!signalingDictionary[SIGNALING_EXTRA_KEY_PLATFORM] || !signalingDictionary[SIGNALING_EXTRA_KEY_DATA]) { signalingDictionary = [TRTCSignalFactory convertOldSignalingToNewSignaling:signalingDictionary]; } if (!signalingDictionary[SIGNALING_EXTRA_KEY_BUSINESSID] || ![signalingDictionary[SIGNALING_EXTRA_KEY_BUSINESSID] isKindOfClass:[NSString class]] || ![signalingDictionary[SIGNALING_EXTRA_KEY_BUSINESSID] isEqualToString:SIGNALING_BUSINESSID]) { return nil; } NSInteger version = [signalingDictionary[SIGNALING_EXTRA_KEY_VERSION] integerValue]; if (version > Version) { return nil; } NSDictionary *dataDictionary = [TRTCSignalFactory getDataDictionary:signalingDictionary]; if (dataDictionary[SIGNALING_EXTRA_KEY_CMD] && [dataDictionary[SIGNALING_EXTRA_KEY_CMD] isKindOfClass:[NSString class]] && [dataDictionary[SIGNALING_EXTRA_KEY_CMD] isEqualToString:SIGNALING_CMD_HANGUP]) { // 结束的事件只用于 UI 展示通话时长,不参与业务逻辑的处理 return nil; } return signalingDictionary; } - (void)handleCallModel:(NSString *)user model:(CallModel *)model message:(NSString *)message { [self handleCallModel:user model:model message:message userIds:nil]; } - (void)handleCallModel:(NSString *)user model:(CallModel *)model message:(NSString *)message userIds:(NSArray *)userIds { TRTCLog(@"Calling - handleCallModel user:%@ model:%@ message:%@ userIds:%@", user, model, message, userIds); BOOL checkCallID = [self.curCallID isEqualToString:model.callid] || [[self getCallIDWithUserID:user] isEqualToString:model.callid]; switch (model.action) { case CallAction_Call: { [self sendInviteAction:CallAction_Call user:user model:model]; void(^syncInvitingList)(void) = ^(){ for (NSString *invitee in model.invitedList) { if (![self.curInvitingList containsObject:invitee]) { [self.curInvitingList addObject:invitee]; } } }; if (model.groupid != nil && ![model.invitedList containsObject:TUILogin.getUserID] ) { // 群聊但是邀请不包含自己不处理 if ([self.curCallID isEqualToString:model.callid]) { // 在房间中更新列表 syncInvitingList(); if (self.curInvitingList.count > 0) { if ([self canDelegateRespondMethod:@selector(onGroupCallInviteeListUpdate:)]) { [self.delegate onGroupCallInviteeListUpdate:self.curInvitingList]; } } } return; } if (self.isOnCalling) { // tell busy if (!checkCallID) { BOOL isGroup = (model.groupid && model.groupid > 0); [self invite:isGroup ? model.groupid : user action:CallAction_Linebusy model:model cmdInfo:nil]; } } else { self.isOnCalling = true; self.curCallID = model.callid; self.curRoomID = model.roomid; if (model.groupid.length > 0) { self.curGroupID = model.groupid; } self.curType = model.calltype; self.curSponsorForMe = user; syncInvitingList(); if ([self canDelegateRespondMethod:@selector(onInvited:userIds:isFromGroup:callType:)]) { NSMutableArray *userIdAry = model.invitedList; [userIds enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (![userIdAry containsObject:obj]) { [userIdAry addObject:obj]; } }]; [self.delegate onInvited:user userIds:userIdAry isFromGroup:self.curGroupID.length > 0 ? YES : NO callType:model.calltype]; } } } break; case CallAction_Cancel: { [self sendInviteAction:CallAction_Cancel user:user model:model]; if (checkCallID && self.delegate) { [self preExitRoom]; self.isOnCalling = NO; [self.delegate onCallingCancel:user]; } } break; case CallAction_Reject: { [self sendInviteAction:CallAction_Reject user:user model:model]; if (checkCallID && self.delegate) { if ([self.curInvitingList containsObject:user]) { [self.curInvitingList removeObject:user]; } if ([self canDelegateRespondMethod:@selector(onReject:)]) { [self.delegate onReject:user]; } [self preExitRoom]; } } break; case CallAction_Timeout: { [self sendInviteAction:CallAction_Timeout user:user model:model]; if (checkCallID && self.delegate) { // 这里需要判断下是否是自己超时了,自己超时,直接退出界面 if ([model.invitedList containsObject:TUILogin.getUserID] && self.delegate) { self.isOnCalling = false; if ([self canDelegateRespondMethod:@selector(onCallingTimeOut)]) { [self.delegate onCallingTimeOut]; } } else { for (NSString *userID in model.invitedList) { if ([self canDelegateRespondMethod:@selector(onNoResp:)]) { [self.delegate onNoResp:userID]; } if ([self.curInvitingList containsObject:userID]) { [self.curInvitingList removeObject:userID]; } } } // 每次超时都需要判断当前是否需要结束通话 [self preExitRoom]; } } break; case CallAction_Linebusy: { TRTCLog(@"Calling - CallAction_Linebusy_out user:%@ userIds:%@ curCallID:%@ model: %@", user, userIds, self.curCallID, model); [self sendInviteAction:CallAction_Linebusy user:user model:model]; if (checkCallID && self.delegate) { TRTCLog(@"Calling - CallAction_Linebusy_in user:%@ userIds:%@", user, userIds); if ([self.curInvitingList containsObject:user]) { [self.curInvitingList removeObject:user]; } [self.delegate onLineBusy:user]; [self preExitRoom]; } } break; case CallAction_Error: { [self sendInviteAction:CallAction_Error user:user model:model]; if (checkCallID && self.delegate) { if ([self.curInvitingList containsObject:user]) { [self.curInvitingList removeObject:user]; } [self.delegate onError:-1 msg:TUICallingLocalize(@"Demo.TRTC.calling.syserror")]; [self preExitRoom]; } } break; case CallAction_SwitchToAudio: { if (!self.switchToAudioCallID) { break; } int res = [self checkAudioStatus]; if (res == 0) { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:0 cmd:SIGNALING_CMD_SWITCHTOVOICECALL cmdInfo:@"" message:@"" callType:CallType_Video]; [dataDic setValue:@(1) forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; @weakify(self) [[V2TIMManager sharedInstance] accept:self.switchToAudioCallID data:data succ:^{ TRTCLog(@"res==0 - accept success"); @strongify(self) if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:YES message:@""]; } } fail:^(int code, NSString *desc) { TRTCLog(@"res==0 - accept failed, code: %d desc: %@",code,desc); @strongify(self) if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:NO message:desc]; } }]; } else { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:0 cmd:@"" cmdInfo:@"" message:@"" callType:CallType_Video]; [dataDic setValue:@(0) forKey:SIGNALING_EXTRA_KEY_SWITCH_AUDIO_CALL]; NSString *data = [TRTCCallingUtils dictionary2JsonStr:dataDic]; [[V2TIMManager sharedInstance] reject:self.switchToAudioCallID data:data succ:^{ TRTCLog(@"res!=0 - reject success"); } fail:^(int code, NSString *desc) { TRTCLog(@"res!=0 - reject failed, code: %d desc: %@",code,desc); }]; if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:NO message:@"Local status error"]; } } } break; case CallAction_AcceptSwitchToAudio: { if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:YES message:@""]; } } break; case CallAction_RejectSwitchToAudio: { if ([self canDelegateRespondMethod:@selector(onSwitchToAudio:message:)]) { [self.delegate onSwitchToAudio:NO message:message]; } } break; default: { TRTCLog(@"Default CallAction"); } break; } } #pragma mark - V2TIMSimpleMsgListener /// 收到 C2C 自定义(信令)消息 - (void)onRecvC2CCustomMessage:(NSString *)msgID sender:(V2TIMUserInfo *)info customData:(NSData *)data { TRTCLog(@"Calling - onRecvC2CCustomMessage inviteID:%@ inviter:%@", msgID, data); if (!self.isBeingCalled) return; NSDictionary *param = [self check:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; if (!param || ![param isKindOfClass:[NSDictionary class]]) { return; } NSDictionary *dataDic; NSString *cmdStr; if ([param.allKeys containsObject:SIGNALING_EXTRA_KEY_DATA] ) { dataDic = param[SIGNALING_EXTRA_KEY_DATA]; if ([dataDic.allKeys containsObject:SIGNALING_EXTRA_KEY_CMD]) { cmdStr = dataDic[SIGNALING_EXTRA_KEY_CMD]; } } if (![cmdStr isEqualToString:@"sync_info"]) return; CallModel *model = [[CallModel alloc] init]; model.callid = param[SIGNALING_CUSTOM_CALLID]; model.inviter = param[SIGNALING_CUSTOM_USER]; model.action = [param[SIGNALING_CUSTOM_CALL_ACTION] integerValue]; model.roomid = [dataDic[SIGNALING_EXTRA_KEY_ROOMID] intValue]; if (model.inviter && model.action == CallAction_Timeout) { model.invitedList = [@[model.inviter] mutableCopy]; } [self handleCallModel:model.inviter model:model message:@"" userIds:nil]; } #pragma mark - Utils - (CallModel *)generateModel:(CallAction)action { CallModel *model = [self.curLastModel copy]; model.action = action; return model; } - (void)preExitRoom { [self preExitRoom:nil]; } - (void)preExitRoom:(NSString *)leaveUser { if (!self.isInRoom && self.curInvitingList.count == 0) { [self exitRoom]; return; } // 当前房间中存在成员,不能自动退房 if (self.curRoomList.count > 0) return; if (self.curGroupID.length > 0) { // IM 多人通话逻辑 if (self.curInvitingList.count == 0) { if (leaveUser) { [self invite:@"" action:CallAction_End model:nil cmdInfo:nil]; } [self exitRoom]; } return; } // C2C多人通话 和 单人通话 逻辑 if (self.curInvitingList.count >= 1) { return; } if (leaveUser) { [self invite:leaveUser action:CallAction_End model:nil cmdInfo:nil]; } [self exitRoom]; } - (void)exitRoom { TRTCLog(@"Calling - autoHangUp"); if ([self canDelegateRespondMethod:@selector(onCallEnd)]) { [self.delegate onCallEnd]; } [self quitRoom]; self.isOnCalling = NO; } #pragma mark - private method /// 检查当前登录用户是否是邀请者/被邀请者 - (BOOL)checkLoginUserIsEqualTo:(NSString *)inviteUser { if (inviteUser && [inviteUser isKindOfClass:NSString.class] && inviteUser.length > 0 && [inviteUser isEqualToString:TUILogin.getUserID]) { return YES; } return NO; } - (void)sendInviteAction:(CallAction)action user:(NSString *)user model:(CallModel *)model { BOOL isGroupidCall = (model.groupid && model.groupid > 0); if (self.isBeingCalled || isGroupidCall || [user isEqualToString:TUILogin.getUserID]) { return; } @weakify(self) [self.calleeUserIDs enumerateObjectsUsingBlock:^(NSString * _Nonnull calleeUserID, NSUInteger idx, BOOL * _Nonnull stop) { if (![calleeUserID isEqualToString:user]) { @strongify(self) NSString *callid = [self getCallIDWithUserID:calleeUserID]; if (callid && callid.length > 0) { NSMutableDictionary *dataDic = [TRTCSignalFactory packagingSignalingWithExtInfo:@"" roomID:model.roomid cmd:@"sync_info" cmdInfo:@"" message:@"" callType:model.calltype]; [dataDic setValue:@(action) forKey:SIGNALING_CUSTOM_CALL_ACTION]; [dataDic setValue:callid forKey:SIGNALING_CUSTOM_CALLID]; [dataDic setValue:user ?: @"" forKey:SIGNALING_CUSTOM_USER]; [[V2TIMManager sharedInstance] sendC2CCustomMessage:[TRTCCallingUtils dictionary2JsonData:dataDic] to:calleeUserID succ:^{ TRTCLog(@"Calling - sendC2CCustomMessage success %@ %@", dataDic, calleeUserID); } fail:^(int code, NSString *desc) { TRTCLog(@"Calling - sendC2CCustomMessage failed, code: %d desc: %@", code, desc); }]; } else { TRTCLog(@"Calling - sendInviteAction callid error"); } } }]; } - (BOOL)canDelegateRespondMethod:(SEL)selector { return self.delegate && [self.delegate respondsToSelector:selector]; } - (NSString * _Nullable)getCallIDWithUserID:(NSString *)userID { NSString *callID; if (userID && [userID isKindOfClass:NSString.class] && userID.length > 0) { callID = self.curCallIdDic[userID]; } if ([callID isKindOfClass:NSString.class]) { return callID; } return nil; } @end