// // VideoCallingViewController.m // TRTC-API-Example-OC // // Created by bluedang on 2021/4/12. // /* 实时视频通话功能 TRTC APP 实时视频通话功能 本文件展示如何集成实时视频通话功能 1、切换摄像头 API:[[_trtcCloud getDeviceManager] switchCamera:_isFrontCamera]; 2、打开关闭摄像头 API: [self.trtcCloud startLocalPreview:_isFrontCamera view:_localVideoView]; [self.trtcCloud stopLocalPreview]; 3、切换听筒与扬声器 API:[[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteEarpiece]; [[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteSpeakerphone]; 4、静音当前设备,其他人将无法听到该设备的声音 API: [_trtcCloud muteLocalAudio:YES]; 参考文档:https://cloud.tencent.com/document/product/647/42044 */ /* Real-Time Audio Call TRTC Audio Call This document shows how to integrate the real-time audio call feature. 1. Switch between the speaker and receiver: [[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteSpeakerphone] 2. Mute the device so that others won’t hear the audio of the device: [_trtcCloud muteLocalAudio:YES] 3. Display other network and volume information: delegate -> onNetworkQuality, onUserVoiceVolume Documentation: https://cloud.tencent.com/document/product/647/42046 */ #import "VideoCallingViewController.h" #import "TrtcUserInfo.h" #import "CordovaEventKit.h" #import "UIView+Toast.h" #import "Events.h" #import "UserUpdateListener.h" static const NSInteger maxRemoteUserNum = 7; @interface VideoCallingViewController () @property (weak, nonatomic) IBOutlet UIButton *backButton; @property (weak, nonatomic) IBOutlet UIButton *micButton; @property (weak, nonatomic) IBOutlet UIButton *spakeButton; @property (weak, nonatomic) IBOutlet UIButton *camareButton; @property (weak, nonatomic) IBOutlet UIButton *subVisibleButton; @property (weak, nonatomic) IBOutlet UIButton *viewRotateButton; @property (strong, nonatomic) IBOutletCollection(UIView) NSArray *remoteViewArr; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *subViewBtnArr; @property (strong, nonatomic) IBOutletCollection(UILabel) NSArray *displayLabelArr; @property (weak, nonatomic) IBOutlet UILabel *displayLabel; @property (assign, nonatomic) UInt32 roomId; @property (strong, nonatomic) NSString* userId; @property(nonatomic, assign) UInt32 sdkAppId; @property (strong, nonatomic) NSString* userSig; @property (strong, nonatomic) TRTCCloud *trtcCloud; @property (strong, nonatomic) NSMutableOrderedSet *remoteUidSet; @property (strong, nonatomic) NSMutableArray *viewUsers; @property (assign, nonatomic) BOOL isFrontCamera; @property (assign, nonatomic) BOOL *hasShareUser; @property (assign, nonatomic) BOOL *hiddenAllSubView; @property (assign, nonatomic) NSInteger rotation; @end @implementation VideoCallingViewController static VideoCallingViewController* _self; +(VideoCallingViewController*)viewController{ return _self; } - (TRTCCloud*)trtcCloud { if (!_trtcCloud) { _trtcCloud = [TRTCCloud sharedInstance]; } return _trtcCloud; } - (NSMutableOrderedSet *)remoteUidSet { if (!_remoteUidSet) { _remoteUidSet = [[NSMutableOrderedSet alloc] initWithCapacity:maxRemoteUserNum]; } return _remoteUidSet; } - (NSMutableArray *)viewUsers { if (!_viewUsers) { _viewUsers = [[NSMutableArray alloc] initWithCapacity:maxRemoteUserNum]; } return _viewUsers; } - (instancetype)initWithRoomId:(UInt32)roomId userId:(NSString *)userId appId:(UInt32)appId userSig:(NSString *)userSig{ NSLog(@"TRTC - initWithRoomId:::::"); self = [super initWithNibName:NSStringFromClass([self class]) bundle:nil]; if (self) { _roomId = roomId; _userId = userId; _sdkAppId = appId; _userSig = userSig; } NSLog(@"TRTC - roomid:%d,userID:%@,sdkAppid:%d,userSig:%@",_roomId,_userId,_sdkAppId,_userSig); _self = self; return self; } - (void) updateUser:(NSDictionary*)extra{ NSLog(@"TRTC - userinfo.update -- userID:%@,displayname:%@",extra[@"userId"],extra[@"displayName"]); TrtcUserInfo *user = [[TrtcUserInfo alloc]initWithPersonid:extra[@"userId"]]; NSInteger index = [[self remoteUidSet] indexOfObject: user]; NSLog(@"TRTC - userinfo.update -- userId:%@,index:%ld",extra[@"userId"],index); if (index == NSNotFound) { return; } TrtcUserInfo *obj = [self remoteUidSet][index]; [obj setDisplayName: extra[@"displayName"]]; dispatch_async(dispatch_get_main_queue(), ^{ [self refreshRemoteVideoViews]; }); } - (void)viewDidLoad { [super viewDidLoad]; self.isFrontCamera = NO; self.trtcCloud.delegate = self; [self setupDefaultUIConfig]; [self setupTRTCCloud]; [self.view sendSubviewToBack:self.view]; } - (void)setupDefaultUIConfig { NSLog(@"TRTC - setupDefaultUIConfig:::::"); // UIFont*font = [UIFont size:24]; // NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:@"\U0000e679" attributes: @{NSFontAttributeName:font}]; // [_backButton setFont:(API_DEPRECATED("Specify an attributed title with a custom font", ios(2.0, 3.0)) UIFont *)] // [_backButton setAttributedTitle:attributedStr forState:UIControlStateNormal]; // self.backButton.titleLabel.font = ; [_backButton setTitle:@"\U0000e679" forState:UIControlStateNormal]; [_micButton setTitle:@"\U0000e67a" forState:UIControlStateNormal]; [_micButton setTitle:@"\U0000e658" forState:UIControlStateSelected]; [_spakeButton setTitle:@"\U0000e664" forState:UIControlStateNormal]; [_spakeButton setTitle:@"\U0000e775" forState:UIControlStateSelected]; [_camareButton setTitle:@"\U0000e824" forState:UIControlStateNormal]; [_subVisibleButton setTitle:@"\U0000e695" forState:UIControlStateNormal]; [_subVisibleButton setTitle:@"\U0000e81a" forState:UIControlStateSelected]; [_viewRotateButton setTitle:@"\U0000e657" forState:UIControlStateNormal]; _displayLabel.text = @"我"; _displayLabel.hidden = NO; _rotation = 0; [Events addListener:@"userinfo.update" listener: [[UserUpdateListener alloc] init] ]; } - (void)setupTRTCCloud { [[self remoteUidSet] removeAllObjects]; [self.trtcCloud startLocalPreview:_isFrontCamera view:self.view]; TRTCParams *params = [TRTCParams new]; params.sdkAppId = _sdkAppId; params.roomId = _roomId; params.userId = _userId; params.role = TRTCRoleAnchor; params.userSig = _userSig; [self.trtcCloud enterRoom:params appScene:TRTCAppSceneVideoCall]; TRTCVideoEncParam *encParams = [TRTCVideoEncParam new]; encParams.videoResolution = TRTCVideoResolution_640_360; encParams.videoBitrate = 550; encParams.videoFps = 15; [self.trtcCloud setVideoEncoderParam:encParams]; [self.trtcCloud startLocalAudio:TRTCAudioQualityMusic]; } - (void)dealloc { [self.trtcCloud exitRoom]; [TRTCCloud destroySharedIntance]; } - (void)changeUser:(NSInteger) a and:(NSInteger) b{ if(a == b || a < 0 || b < 0){return;} NSInteger x = a > b ? b : a; NSInteger y = a > b ? a : b; if([self remoteUidSet].count > y && x>-1){ // 交换 NSLog(@"TRTC - onSubViewClcik:::::Before change:%@,%@",[[self remoteUidSet][x] personid],[[self remoteUidSet][y] personid]); TrtcUserInfo* info = [self remoteUidSet][x]; TrtcUserInfo* info2 = [self remoteUidSet][y]; [_remoteUidSet removeObjectAtIndex:x]; [_remoteUidSet setObject:info atIndex: y-1]; [_remoteUidSet insertObject:info2 atIndex:x]; NSLog(@"TRTC - onSubViewClcik:::::after change:%@,%@",[[self remoteUidSet][x] personid],[[self remoteUidSet][y] personid]); [self refreshRemoteVideoViews]; NSDictionary *event = [[NSDictionary alloc]initWithObjectsAndKeys: [[NSString alloc] initWithFormat:@"%d",_roomId],@"room",info2.personid,@"userId", nil]; [CordovaEventKit fireEvent:@"onLayoutChangeMessage" obj:event]; } } #pragma mark - IBActions - (IBAction)onSubVisibleChange:(UIButton*)sender { NSLog(@"TRTC - onSubVisibleChange:::::,before:%@",[NSNumber numberWithBool:sender.selected]); sender.selected = !sender.selected; NSLog(@"TRTC - onSubVisibleChange:::::,after:%@",[NSNumber numberWithBool:sender.selected]); [self refreshRemoteVideoViews]; } - (IBAction)onVideoRotate:(UIButton*)sender { NSLog(@"TRTC - onRouteChange:::::,%@,local index: %ld",[[self remoteUidSet][0] personid],[_viewUsers indexOfObject:_userId]); TRTCRenderParams *params = [[TRTCRenderParams alloc] init]; params.fillMode = TRTCVideoFillMode_Fit; _rotation += 1; _rotation = _rotation > 3 ? 0 : _rotation; params.rotation = _rotation; // [_trtcCloud stopRemoteView:[[self viewUsers][0] personid] streamType:TRTCVideoStreamTypeBig]; [_trtcCloud setRemoteRenderParams:[[self remoteUidSet][0] personid] streamType:TRTCVideoStreamTypeBig params:params]; // [_trtcCloud updateRemoteView:self.view streamType:TRTCVideoStreamTypeBig forUser:[[self remoteUidSet][0] personid]]; // [_trtcCloud startRemoteView:[[self viewUsers][0] personid] streamType: TRTCVideoStreamTypeBig view:self.view]; } - (IBAction)onBackClick:(UIButton*)sender { NSLog(@"TRTC - onBackClick:::::"); [self.trtcCloud exitRoom]; [TRTCCloud destroySharedIntance]; [self dismissViewControllerAnimated:YES completion:nil]; } - (IBAction)onSwitchCameraClick:(UIButton*)sender { NSLog(@"TRTC - onSwitchCameraClick:::::"); _isFrontCamera = !_isFrontCamera; [[_trtcCloud getDeviceManager] switchCamera:_isFrontCamera]; } - (IBAction)onMicCaptureClick:(UIButton*)sender { NSLog(@"TRTC - onMicCaptureClick:::::"); sender.selected = !sender.selected; if ([sender isSelected]) { [_trtcCloud muteLocalAudio:YES]; } else { [_trtcCloud muteLocalAudio:NO]; } } - (IBAction)onSwitchSpeakerClick:(UIButton*)sender { NSLog(@"TRTC - onSwitchSpeakerClick:::::"); sender.selected = !sender.selected; if ([sender isSelected]) { [[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteEarpiece]; } else { [[_trtcCloud getDeviceManager] setAudioRoute:TXAudioRouteSpeakerphone]; } } - (IBAction)onSubViewClcik:(UIButton*)sender { NSLog(@"TRTC - onSubViewClcik:::::button"); NSInteger index = [_subViewBtnArr indexOfObject:sender]; if (index == NSNotFound) {return;} index = index + 1; [self changeUser:0 and:index]; } #pragma mark - TRTCCloud Delegate - (void)onEnterRoom:(NSInteger) result{ NSLog(@"TRTC - onEnterRoom, %ld",result); [_trtcCloud startLocalPreview:_isFrontCamera view:self.view]; // 添加本地用户到用户列表 TrtcUserInfo *info = [[TrtcUserInfo alloc] initWithPersonid:_userId]; [info setLocal:YES]; NSInteger index = [[self remoteUidSet] indexOfObject:info]; if (index != NSNotFound) { return; } else { [[self remoteUidSet] addObject:info]; } NSDictionary *event = [[NSDictionary alloc]initWithObjectsAndKeys: [[NSString alloc] initWithFormat:@"%d",_roomId],@"room",_userId,@"userId", nil]; [CordovaEventKit fireEvent:@"onLayoutChangeMessage" obj:event]; } - (void)onUserVideoAvailable:(NSString *)userId available:(BOOL)available { NSLog(@"TRTC - onUserVideoAvailable, user:%@,available%@",userId, available ? @"YES":@"NO"); // 添加远端用户到用户列表 TrtcUserInfo *info = [[TrtcUserInfo alloc] initWithPersonid:userId]; NSInteger index = [[self remoteUidSet] indexOfObject:info]; if (available) { if (index != NSNotFound) { return; } if([info isShareUser]){ _hasShareUser = YES; } [[self remoteUidSet] addObject:info]; } else { if (index == NSNotFound) { return; } if([info isShareUser]){ _hasShareUser = NO; _hiddenAllSubView = NO; } [_trtcCloud stopRemoteView:userId streamType:TRTCVideoStreamTypeSmall]; [[self remoteUidSet] removeObject:userId]; } [self refreshRemoteVideoViews]; if(_hasShareUser){ // 切换分享屏幕到主窗口 [self changeUser:0 and:[[self remoteUidSet] indexOfObject:info]]; // 隐藏所有子窗口 _hiddenAllSubView = YES; [self refreshRemoteVideoViews]; // [_trtcCloud stopLocalPreview]; } NSDictionary *event = @{@"room": [[NSString alloc] initWithFormat:@"%d", _roomId], @"userId": userId, @"available": [NSNumber numberWithBool:available]}; [CordovaEventKit fireEvent:@"onUserVideoAvailable" obj:event]; } - (void)refreshRemoteVideoViews { NSLog(@"TRTC - refreshRemoteVideoViews:%ld",_remoteViewArr.count); _subVisibleButton.hidden = _hasShareUser != nil; _viewRotateButton.hidden = _hasShareUser == nil; _viewRotateButton.enabled = !_viewRotateButton.hidden; for (int i = 0; i <= _remoteViewArr.count; i++) { TrtcUserInfo* nUser = [self remoteUidSet].count > i ? [self remoteUidSet][i] : nil; TrtcUserInfo* oUser = [self viewUsers].count > i ? [self viewUsers][i] : nil; UIView* view = i == 0 ? self.view : _remoteViewArr[i-1]; UILabel* label = i ==0 ? _displayLabel : _displayLabelArr[i-1]; UIButton* btn = i ==0 ? nil : _subViewBtnArr[i-1]; NSLog(@"TRTC - refreshRemoteVideoViews - new : %@, o: %@",nUser == nil ? nil : [nUser personid],oUser == nil ? nil : [oUser personid]); if((_hiddenAllSubView || ![_subVisibleButton isSelected]) && i!=0){ [view setHidden:YES]; [label setHidden:YES]; }else{ if(nUser != nil){ if([nUser isEqual:oUser] && ![view isHidden]){ if([nUser isLocalUser]){ [label setText:@"我"]; } else if([nUser displayName] == nil || [nUser displayName].length == 0) { [label setText:[NSString stringWithFormat:@"%@%@",[nUser personid], [nUser isShareUser] ? @"的屏幕分享" : @""]]; } else { [label setText:[NSString stringWithFormat:@"%@%@",[nUser displayName], [nUser isShareUser] ? @"的屏幕分享" : @""]]; } [label sizeToFit]; continue; } TRTCRenderParams *params = [[TRTCRenderParams alloc] init]; params.rotation = 0; params.fillMode = [nUser isShareUser] ? TRTCVideoFillMode_Fit : TRTCVideoFillMode_Fill; if([nUser isLocalUser]){ [_trtcCloud startLocalPreview:_isFrontCamera view:view]; [_trtcCloud setLocalRenderParams:params]; [label setText:@"我"]; [label sizeToFit]; } else { [_trtcCloud setRemoteRenderParams:[nUser personid] streamType:i == 0 ? TRTCVideoStreamTypeBig : TRTCVideoStreamTypeSmall params:params]; [_trtcCloud startRemoteView:[nUser personid] streamType:i == 0 ? TRTCVideoStreamTypeBig : TRTCVideoStreamTypeSmall view:view]; if([nUser displayName] == nil || [nUser displayName].length == 0) { [label setText:[NSString stringWithFormat:@"%@%@",[nUser personid], [nUser isShareUser] ? @"的屏幕分享" : @""]]; } else { [label setText:[NSString stringWithFormat:@"%@%@",[nUser displayName], [nUser isShareUser] ? @"的屏幕分享" : @""]]; } [label sizeToFit]; } [label setHidden:NO]; [view setHidden:NO]; if(btn != nil){ [btn setHidden:NO]; } } else { [view setHidden:YES]; [label setHidden:YES]; if(btn != nil){ [btn setHidden:YES]; } } } if(nUser != nil){ [self viewUsers].count > i ? [self viewUsers][i] = nUser : [[self viewUsers] addObject:nUser]; }else { while([self viewUsers].count > i){ [[self viewUsers] removeObjectAtIndex:i]; } } } } - (void) onError:(TXLiteAVError)errCode errMsg:(NSString *)errMsg extInfo:(NSDictionary *)extInfo{ NSLog(@"TRTC - onError::sdk callback onError code: %d,msg: %@,extInfo: %@",errCode,errMsg,extInfo); dispatch_async(dispatch_get_main_queue(), ^{ [self.view makeToast: [[NSString alloc] initWithFormat:@"%@[%d]",errMsg,errCode]]; }); // [self.trtcCloud exitRoom]; // [TRTCCloud destroySharedIntance]; // [self dismissViewControllerAnimated:YES completion:nil]; } - (void) onExitRoom:(NSInteger)reason{ if (reason == 2) { dispatch_async(dispatch_get_main_queue(), ^{ [self.view makeToast: @"远程协助已结束"]; }); } [self dismissViewControllerAnimated:YES completion:nil]; } @end