// // TUIChatCallingDataProvider.m // TUIChat // // Created by harvy on 2022/12/21. // Copyright © 2023 Tencent. All rights reserved. // #import "TUIChatCallingDataProvider.h" #import #import #import #import "TUIMessageBaseDataProvider.h" typedef NSString *TUIChatMessageID; typedef NSDictionary *TUIChatCallingJsonData; // ******************************************************************** // TUIChatCallingInfo // ******************************************************************** @interface TUIChatCallingInfo : NSObject @property(nonatomic, strong) TUIChatMessageID msgID; @property(nonatomic, strong, nullable) TUIChatCallingJsonData jsonData; @property(nonatomic, strong, nullable) V2TIMSignalingInfo *signalingInfo; @property(nonatomic, strong, nullable) V2TIMMessage *innerMessage; @property(nonatomic, assign) TUIChatCallingMessageAppearance style; @end @implementation TUIChatCallingInfo #pragma mark - TUIChatCallingInfoProtocol - (TUICallProtocolType)protocolType { if (self.jsonData == nil || self.signalingInfo == nil || self.innerMessage == nil) { return TUICallProtocolTypeUnknown; } TUICallProtocolType type = TUICallProtocolTypeUnknown; switch (self.signalingInfo.actionType) { case SignalingActionType_Invite: { NSDictionary *data = [self.jsonData objectForKey:@"data"]; if (data && [data isKindOfClass:NSDictionary.class]) { // New version for calling NSString *cmd = [data objectForKey:@"cmd"]; if ([cmd isKindOfClass:NSString.class]) { if ([cmd isEqualToString:@"switchToAudio"]) { type = TUICallProtocolTypeSwitchToAudio; } else if ([cmd isEqualToString:@"hangup"]) { type = TUICallProtocolTypeHangup; } else if ([cmd isEqualToString:@"videoCall"]) { type = TUICallProtocolTypeSend; } else if ([cmd isEqualToString:@"audioCall"]) { type = TUICallProtocolTypeSend; } else { type = TUICallProtocolTypeUnknown; } } else { NSLog(@"calling protocol error, %@", self.jsonData); type = TUICallProtocolTypeUnknown; } } else { // Compatiable NSNumber *callEnd = [self.jsonData objectForKey:@"call_end"]; if (callEnd && [callEnd isKindOfClass:NSNumber.class]) { type = TUICallProtocolTypeHangup; } else { type = TUICallProtocolTypeSend; } } } break; case SignalingActionType_Cancel_Invite: { type = TUICallProtocolTypeCancel; } break; case SignalingActionType_Accept_Invite: { NSDictionary *data = [self.jsonData objectForKey:@"data"]; if (data && [data isKindOfClass:NSDictionary.class]) { // New version for calling NSString *cmd = [data objectForKey:@"cmd"]; if ([cmd isKindOfClass:NSString.class]) { if ([cmd isEqualToString:@"switchToAudio"]) { type = TUICallProtocolTypeSwitchToAudioConfirm; } else { type = TUICallProtocolTypeAccept; } } else { NSLog(@"calling protocol error, %@", self.jsonData); type = TUICallProtocolTypeAccept; } } else { // Compatiable type = TUICallProtocolTypeAccept; } } break; case SignalingActionType_Reject_Invite: { if ([self.jsonData objectForKey:@"line_busy"]) { type = TUICallProtocolTypeLineBusy; } else { type = TUICallProtocolTypeReject; } } break; case SignalingActionType_Invite_Timeout: { type = TUICallProtocolTypeTimeout; } break; default: type = TUICallProtocolTypeUnknown; break; } return type; } - (TUICallStreamMediaType)streamMediaType { TUICallProtocolType protocolType = self.protocolType; if (protocolType == TUICallProtocolTypeUnknown) { return TUICallStreamMediaTypeUnknown; } // Default type TUICallStreamMediaType type = TUICallStreamMediaTypeUnknown; NSNumber *callType = [self.jsonData objectForKey:@"call_type"]; if (callType && [callType isKindOfClass:NSNumber.class]) { if ([callType integerValue] == 1) { type = TUICallStreamMediaTypeVoice; } else if ([callType integerValue] == 2) { type = TUICallStreamMediaTypeVideo; } } // Read from special protocol if (protocolType == TUICallProtocolTypeSend) { NSDictionary *data = [self.jsonData objectForKey:@"data"]; if (data && [data isKindOfClass:NSDictionary.class]) { NSString *cmd = [data objectForKey:@"cmd"]; if ([cmd isEqual:@"audioCall"]) { type = TUICallStreamMediaTypeVoice; } else if ([cmd isEqual:@"videoCall"]) { type = TUICallStreamMediaTypeVideo; } } } else if (protocolType == TUICallProtocolTypeSwitchToAudio || protocolType == TUICallProtocolTypeSwitchToAudioConfirm) { type = TUICallStreamMediaTypeVideo; } return type; } - (TUICallParticipantType)participantType { if (self.protocolType == TUICallProtocolTypeUnknown) { return TUICallParticipantTypeUnknown; } if (self.signalingInfo.groupID.length > 0) { return TUICallParticipantTypeGroup; } else { return TUICallParticipantTypeC2C; } } - (NSString *)caller { NSString *callerID = nil; NSDictionary *data = [self.jsonData objectForKey:@"data"]; if (data && [data isKindOfClass:NSDictionary.class]) { NSString *inviter = [data objectForKey:@"inviter"]; if (inviter && [inviter isKindOfClass:NSString.class]) { callerID = inviter; } } if (callerID == nil) { callerID = TUILogin.getUserID; } return callerID; } - (TUICallParticipantRole)participantRole { if ([self.caller isEqualToString:TUILogin.getUserID]) { return TUICallParticipantRoleCaller; } else { return TUICallParticipantRoleCallee; } } - (BOOL)excludeFromHistory { if (self.style == TUIChatCallingMessageAppearanceSimplify) { return self.protocolType != TUICallProtocolTypeUnknown && self.innerMessage.isExcludedFromLastMessage && self.innerMessage.isExcludedFromUnreadCount; } else { return NO; } } - (NSString *)content { if (self.style == TUIChatCallingMessageAppearanceSimplify) { return [self contentForSimplifyAppearance]; } else { return [self contentForDetailsAppearance]; } } - (TUICallMessageDirection)direction { if (self.style == TUIChatCallingMessageAppearanceSimplify) { return [self directionForSimplifyAppearance]; } else { return [self directionForDetailsAppearance]; } } - (BOOL)showUnreadPoint { if (self.excludeFromHistory) { return NO; } return (self.innerMessage.localCustomInt == 0) && (self.participantRole == TUICallParticipantRoleCallee) && (self.participantType == TUICallParticipantTypeC2C) && (self.protocolType == TUICallProtocolTypeCancel || self.protocolType == TUICallProtocolTypeTimeout || self.protocolType == TUICallProtocolTypeLineBusy); } - (BOOL)isUseReceiverAvatar { if (self.style == TUIChatCallingMessageAppearanceSimplify) { return [self isUseReceiverAvatarForSimplifyAppearance]; } else { return [self isUseReceiverAvatarForDetailsAppearance]; } } - (NSArray *)participantIDList { NSMutableArray *arrayM = [NSMutableArray array]; if (self.signalingInfo.inviter) { [arrayM addObject:self.signalingInfo.inviter]; } if (self.signalingInfo.inviteeList.count > 0) { [arrayM addObjectsFromArray:self.signalingInfo.inviteeList]; } return [NSArray arrayWithArray:arrayM]; } #pragma mark - Details style - (NSString *)contentForDetailsAppearance { TUICallProtocolType protocolType = self.protocolType; BOOL isGroup = (self.participantType == TUICallParticipantTypeGroup); if (protocolType == TUICallProtocolTypeUnknown) { return TIMCommonLocalizableString(TUIkitSignalingUnrecognlize); } NSString *display = TIMCommonLocalizableString(TUIkitSignalingUnrecognlize); NSString *showName = [TUIMessageBaseDataProvider getShowName:self.innerMessage]; if (protocolType == TUICallProtocolTypeSend) { // Launch call display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingNewGroupCallFormat), showName] : TIMCommonLocalizableString(TUIKitSignalingNewCall); } else if (protocolType == TUICallProtocolTypeAccept) { // Accept call display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingHangonCallFormat), showName] : TIMCommonLocalizableString(TUIkitSignalingHangonCall); } else if (protocolType == TUICallProtocolTypeReject) { // Reject call display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingDeclineFormat), showName] : TIMCommonLocalizableString(TUIkitSignalingDecline); } else if (protocolType == TUICallProtocolTypeCancel) { // Cancel pending call display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIkitSignalingCancelGroupCallFormat), showName] : TIMCommonLocalizableString(TUIkitSignalingCancelCall); } else if (protocolType == TUICallProtocolTypeHangup) { // Hang up NSUInteger duration = [[self.jsonData objectForKey:@"call_end"] unsignedIntegerValue]; display = isGroup ? TIMCommonLocalizableString(TUIKitSignalingFinishGroupChat) : [NSString stringWithFormat:@"%@:%.2d:%.2d",TIMCommonLocalizableString(TUIKitSignalingFinishConversationAndTimeFormat),duration / 60, duration % 60]; } else if (protocolType == TUICallProtocolTypeTimeout) { // Call timeout NSMutableString *mutableContent = [NSMutableString string]; if (isGroup) { for (NSString *invitee in self.signalingInfo.inviteeList) { [mutableContent appendString:@"\"{"]; [mutableContent appendString:invitee]; [mutableContent appendString:@"}\"、"]; } if (mutableContent.length > 0) { [mutableContent replaceCharactersInRange:NSMakeRange(mutableContent.length - 1, 1) withString:@" "]; } } [mutableContent appendString:TIMCommonLocalizableString(TUIKitSignalingNoResponse)]; display = [NSString stringWithString:mutableContent]; } else if (protocolType == TUICallProtocolTypeLineBusy) { // Hang up with line busy display = isGroup ? [NSString stringWithFormat:TIMCommonLocalizableString(TUIKitSignalingBusyFormat), showName] : TIMCommonLocalizableString(TUIKitSignalingCallBusy); } else if (protocolType == TUICallProtocolTypeSwitchToAudio) { // Change video-call to voice-call display = TIMCommonLocalizableString(TUIKitSignalingSwitchToAudio); } else if (protocolType == TUICallProtocolTypeSwitchToAudioConfirm) { // Confirm the change of video-voice-call display = TIMCommonLocalizableString(TUIKitSignalingComfirmSwitchToAudio); } return rtlString(display); } - (TUICallMessageDirection)directionForDetailsAppearance { if (self.innerMessage.isSelf) { return TUICallMessageDirectionOutgoing; } else { return TUICallMessageDirectionIncoming; } } - (BOOL)isUseReceiverAvatarForDetailsAppearance { return NO; } #pragma mark - Simplify style - (NSString *)contentForSimplifyAppearance { if (self.excludeFromHistory) { return nil; } TUICallParticipantType participantType = self.participantType; TUICallProtocolType protocolType = self.protocolType; BOOL isCaller = (self.participantRole == TUICallParticipantRoleCaller); NSString *display = nil; NSString *showName = [TUIMessageBaseDataProvider getShowName:self.innerMessage]; if (participantType == TUICallParticipantTypeC2C) { // C2C shown: reject、cancel、hangup、timeout、line_busy if (protocolType == TUICallProtocolTypeReject) { display = isCaller ? TUIChatLocalizableString(TUIChatCallRejectInCaller) : TUIChatLocalizableString(TUIChatCallRejectInCallee); } else if (protocolType == TUICallProtocolTypeCancel) { display = isCaller ? TUIChatLocalizableString(TUIChatCallCancelInCaller) : TUIChatLocalizableString(TUIChatCallCancelInCallee); } else if (protocolType == TUICallProtocolTypeHangup) { NSInteger duration = [[self.jsonData objectForKey:@"call_end"] integerValue]; display = [NSString stringWithFormat:@"%@:%.2d:%.2d",TUIChatLocalizableString(TUIChatCallDurationFormat),duration / 60, duration % 60]; } else if (protocolType == TUICallProtocolTypeTimeout) { display = isCaller ? TUIChatLocalizableString(TUIChatCallTimeoutInCaller) : TUIChatLocalizableString(TUIChatCallTimeoutInCallee); } else if (protocolType == TUICallProtocolTypeLineBusy) { display = isCaller ? TUIChatLocalizableString(TUIChatCallLinebusyInCaller) : TUIChatLocalizableString(TUIChatCallLinebusyInCallee); } // C2C compatiable else if (protocolType == TUICallProtocolTypeSend) { display = TUIChatLocalizableString(TUIChatCallSend); } else if (protocolType == TUICallProtocolTypeAccept) { display = TUIChatLocalizableString(TUIChatCallAccept); } else if (protocolType == TUICallProtocolTypeSwitchToAudio) { display = TUIChatLocalizableString(TUIChatCallSwitchToAudio); } else if (protocolType == TUICallProtocolTypeSwitchToAudioConfirm) { display = TUIChatLocalizableString(TUIChatCallConfirmSwitchToAudio); } else { display = TUIChatLocalizableString(TUIChatCallUnrecognized); } } else if (participantType == TUICallParticipantTypeGroup) { // Group shown: invite、cancel、hangup、timeout、line_busy if (protocolType == TUICallProtocolTypeSend) { display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallSendFormat), showName]; } else if (protocolType == TUICallProtocolTypeCancel) { display = TUIChatLocalizableString(TUIChatGroupCallEnd); } else if (protocolType == TUICallProtocolTypeHangup) { display = TUIChatLocalizableString(TUIChatGroupCallEnd); } else if (protocolType == TUICallProtocolTypeTimeout || protocolType == TUICallProtocolTypeLineBusy) { NSMutableString *mutableContent = [NSMutableString string]; if (participantType == TUICallParticipantTypeGroup) { for (NSString *invitee in self.signalingInfo.inviteeList) { [mutableContent appendString:@"\"{"]; [mutableContent appendString:invitee]; [mutableContent appendString:@"}\"、"]; } [mutableContent replaceCharactersInRange:NSMakeRange(mutableContent.length - 1, 1) withString:@" "]; } if (protocolType == TUICallProtocolTypeLineBusy) { [mutableContent appendString:TUIChatLocalizableString(TUIChatCallLinebusyInCallee)]; } else { [mutableContent appendString:TUIChatLocalizableString(TUIChatGroupCallNoAnswer)]; } display = [NSString stringWithString:mutableContent]; } // Group compatiable else if (protocolType == TUICallProtocolTypeReject) { display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallRejectFormat), showName]; } else if (protocolType == TUICallProtocolTypeAccept) { display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallAcceptFormat), showName]; } else if (protocolType == TUICallProtocolTypeSwitchToAudio) { display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallSwitchToAudioFormat), showName]; } else if (protocolType == TUICallProtocolTypeSwitchToAudioConfirm) { display = [NSString stringWithFormat:TUIChatLocalizableString(TUIChatGroupCallConfirmSwitchToAudioFormat), showName]; } else { display = TUIChatLocalizableString(TUIChatCallUnrecognized); } } else { display = TUIChatLocalizableString(TUIChatCallUnrecognized); } return rtlString(display); } - (TUICallMessageDirection)directionForSimplifyAppearance { if (self.participantRole == TUICallParticipantRoleCaller) { return TUICallMessageDirectionOutgoing; } else { return TUICallMessageDirectionIncoming; } } - (BOOL)isUseReceiverAvatarForSimplifyAppearance { if (self.direction == TUICallMessageDirectionOutgoing) { return !self.innerMessage.isSelf; } else { return self.innerMessage.isSelf; } } #pragma mark - Utils - (NSString *)convertProtocolTypeToString:(TUICallProtocolType)type { static NSDictionary *dict = nil; if (dict == nil) { dict = @{ @(TUICallProtocolTypeSend) : @"TUICallProtocolTypeSend", @(TUICallProtocolTypeAccept) : @"TUICallProtocolTypeAccept", @(TUICallProtocolTypeReject) : @"TUICallProtocolTypeReject", @(TUICallProtocolTypeCancel) : @"TUICallProtocolTypeCancel", @(TUICallProtocolTypeHangup) : @"TUICallProtocolTypeHangup", @(TUICallProtocolTypeTimeout) : @"TUICallProtocolTypeTimeout", @(TUICallProtocolTypeLineBusy) : @"TUICallProtocolTypeLineBusy", @(TUICallProtocolTypeSwitchToAudio) : @"TUICallProtocolTypeSwitchToAudio", @(TUICallProtocolTypeSwitchToAudioConfirm) : @"TUICallProtocolTypeSwitchToAudioConfirm", }; } return [dict objectForKey:@(type)] ?: @"unknown"; } @end // ******************************************************************** // ******************************************************************** // TUIChatCallingDataProvider // ******************************************************************** @interface TUIChatCallingDataProvider () @property(nonatomic, assign) TUIChatCallingMessageAppearance style; @property(nonatomic, strong) NSCache *callingCache; @end @implementation TUIChatCallingDataProvider - (instancetype)init { if (self = [super init]) { self.style = TUIChatCallingMessageAppearanceSimplify; } return self; } - (void)setCallingMessageStyle:(TUIChatCallingMessageAppearance)style { self.style = style; } - (void)redialFromMessage:(V2TIMMessage *)innerMessage { NSDictionary *param = nil; id callingInfo = nil; if ([self isCallingMessage:innerMessage callingInfo:&callingInfo]) { if (callingInfo.streamMediaType == TUICallStreamMediaTypeVoice) { param = @{ TUICore_TUICallingService_ShowCallingViewMethod_UserIDsKey : @[ innerMessage.userID ], TUICore_TUICallingService_ShowCallingViewMethod_CallTypeKey : @"0" }; } else if (callingInfo.streamMediaType == TUICallStreamMediaTypeVideo) { param = @{ TUICore_TUICallingService_ShowCallingViewMethod_UserIDsKey : @[ innerMessage.userID ], TUICore_TUICallingService_ShowCallingViewMethod_CallTypeKey : @"1" }; } if (param) { [TUICore callService:TUICore_TUICallingService method:TUICore_TUICallingService_ShowCallingViewMethod param:param]; } } } - (BOOL)isCallingMessage:(V2TIMMessage *)innerMessage callingInfo:(id __nullable *__nullable)callingInfo { TUIChatCallingInfo *item = [self callingInfoForMesssage:innerMessage]; if (item == nil) { if (callingInfo) { *callingInfo = nil; } return NO; } else { if (callingInfo) { *callingInfo = item; } return YES; } } - (TUIChatCallingInfo *__nullable)callingInfoForMesssage:(V2TIMMessage *)innerMessage { // 1. Fetch from cache TUIChatMessageID msgID = innerMessage.msgID ?: @""; TUIChatCallingInfo *item = [self.callingCache objectForKey:msgID]; if (item) { item.innerMessage = innerMessage; return item; } // 2. Parse V2TIMSignalingInfo *info = [V2TIMManager.sharedInstance getSignallingInfo:innerMessage]; if (info == nil || info.data.length == 0) { return nil; } NSData *data = [info.data dataUsingEncoding:NSUTF8StringEncoding]; if (data == nil) { return nil; } NSError *err = nil; NSDictionary *param = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&err]; if (param == nil || ![param isKindOfClass:NSDictionary.class]) { return nil; } NSString *businessID = [param objectForKey:@"businessID"]; if (businessID == nil || ![businessID isKindOfClass:NSString.class]) { return nil; } if (![businessID isEqualToString:@"av_call"] && ![businessID isEqualToString:@"rtc_call"] ) { return nil; } // 3 Cached and return item = [[TUIChatCallingInfo alloc] init]; item.style = self.style; item.signalingInfo = info; item.jsonData = param; item.innerMessage = innerMessage; [self.callingCache setObject:item forKey:msgID]; return item; } #pragma mark - Lazy - (NSCache *)callingCache { if (_callingCache == nil) { _callingCache = [[NSCache alloc] init]; } return _callingCache; } @end // ********************************************************************