// // webRtcPlayerViewController.m // 双子星云手机 // // Created by xd h on 2024/9/2. // #import "webRtcPlayerViewController.h" #import "webRtcPlayerViewController+AdjustBtnFrame.h" #import "webRtcPlayerViewController+AdjustPlayerViewFrame.h" #import "UIInterface+HXRotation.h" #import "playerSetView.h" @interface webRtcPlayerViewController () { BOOL outputVolumeKVO;/*标记声音监听通知*/ } @property (nonatomic, copy) NSTimer *playerSecondTimer; // 定时器-控制按钮 @property (nonatomic, assign) RTCIceConnectionState linkState; @property (nonatomic, assign) BOOL didHandleRotation;//第一次处理旋转 @end @implementation webRtcPlayerViewController @synthesize controlBtn; - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. [self.toolBar setHidden:YES]; [self.navigationBar setHidden:YES]; [self.navBarBGView setHidden:YES]; [self.view setBackgroundColor:[UIColor blackColor]]; if([DFPlayer sharedPlayer].state == DFPlayerStateBuffering ||[DFPlayer sharedPlayer].state == DFPlayerStatePlaying){ _isCodeSuspendAudioType = YES; [[DFPlayer sharedPlayer] df_pause]; } } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // 屏幕常亮 [UIApplication sharedApplication].idleTimerDisabled = YES; [[UIApplication sharedApplication] setStatusBarHidden:YES]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self noEnablePanRightBack]; [self addKVOObserverFun]; ksharedAppDelegate.supportScreenRotateType = YES; } - (void)viewDidDisappear:(BOOL)animated{ [super viewDidDisappear:animated]; [UIApplication sharedApplication].idleTimerDisabled = NO; [[UIApplication sharedApplication] setStatusBarHidden:NO]; [_mediaStream disconnect]; _mediaStream = nil; [self enablePanRightBack]; [self removeKVOObserverFun]; ksharedAppDelegate.supportScreenRotateType = NO; [self removeNewIndicator]; [_playerSecondTimer invalidate]; _webRtcMsgMod = nil; } - (void)setWebRtcMsgMod:(webRtcMsgModel *)webRtcMsgMod { _webRtcMsgMod = webRtcMsgMod; [self beginWebRtcPlayFun]; } #pragma mark 开始拉流 - (void)beginWebRtcPlayFun { _mediaStream = [[RTC_OBJC_TYPE(AMediaStream) alloc] initWithFrame:CGRectZero]; [_mediaStream setEventDelegate:self]; [self.view addSubview:_mediaStream]; [_mediaStream mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(0.f); make.bottom.mas_equalTo(0.f); make.right.mas_equalTo(0.f); make.top.mas_equalTo(0.f); }]; //判断是否是全屏 BOOL fullscreenType = [HWDataManager getBoolWithKey:Consn_player_full_screen_show]; CGSize phoneSize = CGSizeMake(1080.0, 1920.0); if(fullscreenType){ phoneSize = [RCCommandHelp commondToSetFullScreenPhoneSizeBySize]; } [self linkWebRtcFunWithSize:phoneSize]; [self initBaseUIFun]; [self setTimerCountDown]; //CGFloat curRate = 1080.0/1920.0; //CGFloat curRate = phoneSize.width/phoneSize.height; [self showNewIndicatorWithCanBack:YES canTouch:NO]; } #pragma mark 链接webrtc - (void)linkWebRtcFunWithSize:(CGSize)phoneSize { //链接用 NSString *signallingUrl = [[NSString alloc] initWithFormat:@"%@:%@",_webRtcMsgMod.data.signalling.domainName,_webRtcMsgMod.data.signalling.port]; NSURL *url = [NSURL URLWithString:signallingUrl]; //ice用 NSString *iceUrl = [[NSString alloc] initWithFormat:@"%@:%@",_webRtcMsgMod.data.turn.domainName,_webRtcMsgMod.data.turn.port]; NSMutableDictionary *ice = [NSMutableDictionary new]; if(iceUrl){ [ice setValue:iceUrl forKey:@"CHINANET"]; [ice setValue:iceUrl forKey:@"CMNET"]; [ice setValue:iceUrl forKey:@"UNICOM"]; } NSString *roomName = _webRtcMsgMod.data.uniqueIdentifier; NSInteger result = [_mediaStream start:url ice:ice sn:roomName direct:0 fmt:1//1(h264) 5(h265) videoWidth:(NSInteger)phoneSize.width videoHeight:(NSInteger)phoneSize.height fps:30 bitrate:3000 cardWidth:0//1080//0 cardHeight:0//1920//0 cardDensity:0 token:@"vclusters"]; HLog(@"result:%ld",result) } #pragma mark 重连 - (void)relinkWebRtcFun { if(_linkState == RTCIceConnectionStateConnected || _linkState == RTCIceConnectionStateCompleted) {//链接中 不处理重连事件 HLog(@"链接中 不处理重连事件") return; } HLog(@"发起重连 ......") KWeakSelf mainBlock(^{ [weakSelf showNewIndicatorWithCanBack:YES canTouch:NO]; //判断是否是全屏 BOOL fullscreenType = [HWDataManager getBoolWithKey:Consn_player_full_screen_show]; CGSize phoneSize = CGSizeMake(1080.0, 1920.0); if(fullscreenType){ phoneSize = [RCCommandHelp commondToSetFullScreenPhoneSizeBySize]; } [weakSelf linkWebRtcFunWithSize:phoneSize]; }); } #pragma mark 初始化其他UI - (void)initBaseUIFun { _bottomContrView = [[webRtcPlayerBottomContrView alloc] init]; [self.view addSubview:_bottomContrView]; [self getPlayerBottomNavShowOrHidefun]; KWeakSelf _bottomContrView.didClickButtonFun = ^(NSInteger tag) { [weakSelf didClickBottomFunBy:tag]; }; /*控制按钮*/ UIImage *driftBtnImage = [UIImage imageNamed:@"you_icon"]; controlBtn = [[UIButton alloc] init]; [controlBtn setBackgroundColor:[UIColor clearColor]]; [controlBtn setBackgroundImage:driftBtnImage forState:(UIControlStateNormal)]; [controlBtn addTarget:self action:@selector(controlBtnPressed:) forControlEvents:(UIControlEventTouchUpInside)]; [self.view addSubview:controlBtn]; //拖拽事件等 UIPanGestureRecognizer *gester = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(controlBtnPanGestureCallback:)]; [controlBtn addGestureRecognizer:gester]; //gester.delegate = self; [self initPointForControlBtnFun]; } #pragma mark 设置 controlBtn 的初始位置 - (void)initPointForControlBtnFun { CGFloat h_w_controlBtn = 60.f; /*区分横竖屏*/ if (_mediaStream.hw_w > _mediaStream.hw_h){ [controlBtn setFrame:CGRectMake((_mediaStream.hw_w - h_w_controlBtn)/2.f, _mediaStream.hw_h - h_w_controlBtn - 20.f, h_w_controlBtn, h_w_controlBtn)]; }else{ //默认居右 //[mPlayerView.controlBtn setFrame:CGRectMake(mPlayerView.width - h_w_controlBtn - 20.f, (SCREEN_H - h_w_controlBtn)/2.f, h_w_controlBtn, h_w_controlBtn)]; //默认居左 [controlBtn setFrame:CGRectMake( h_w_controlBtn + 10.f, (SCREEN_H - h_w_controlBtn)/2.f, h_w_controlBtn, h_w_controlBtn)]; } } #pragma mark 定时器 - (void)setTimerCountDown { HLog(@"开启一个"); if (_playerSecondTimer) { // 取消定时器 [_playerSecondTimer invalidate]; _playerSecondTimer = nil; } // 初始化值计时数据 //self.adjustTime = 1; //self.concentTime = [iTools getNowTimeStamp]; _playerSecondTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerChange) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:_playerSecondTimer forMode:NSRunLoopCommonModes]; } #pragma mark 定时器响应事件 - (void)timerChange { [self extensionAdjustBtnFrameCheckAdjustTime];// 1、悬浮球3s后 自动靠边隐藏 //[self checkConcentTime];// 3、30s之后 没有收到拉流数据 自动断开链接 } #pragma mark 控制按钮点击事件 - (void)controlBtnPressed:(UIButton*)but { playerSetView *nextVC = [[playerSetView alloc] init]; [ksharedAppDelegate.window addSubview:nextVC]; [nextVC mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(0); make.right.mas_equalTo(0); make.top.mas_equalTo(0.f); make.bottom.mas_equalTo(0.f); }]; KWeakSelf nextVC.didClickButtonFun = ^(NSInteger tag) { switch (tag) { case 10: {//截图 //self->needScreenShotType = YES; } break; #pragma mark 重启盒子 case 11: { [weakSelf didClickRestartFun]; } break; case 12: {//退出云机 [weakSelf exitCloudPhoneFun]; } break; case 100: { BOOL fullscreenType = [HWDataManager getBoolWithKey:Consn_player_full_screen_show]; if(fullscreenType){ [weakSelf showOpenTVP2PFun]; } else{ [weakSelf openTvShowFun]; } } break; case 101: { weakSelf.controlBtn.userInteractionEnabled = NO; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self->controlBtn.userInteractionEnabled = YES; }); [weakSelf showCloseTVP2PFun]; } break; default: break; } }; } #pragma mark 点击了重启云机 - (void)didClickRestartFun { KWeakSelf /*弹窗提示重启*/ ComontAlretViewController *nextVC = [[ComontAlretViewController alloc] initWithTiTle:NSLocalizedString(@"my_set_no_restart_phone_tips",nil) msg:@"" imageStr:@"" cancelTitle:NSLocalizedString(@"other_cancel",nil) okTitle:NSLocalizedString(@"my_set_no_restart_phone_btn_ok",nil) isOkBtnHighlight:NO didClickOk:^{ [weakSelf needToRebootFun]; //提示语 [[iToast makeText:NSLocalizedString(@"player_link_rebooting_Tips",nil)] show]; } didClickCancel:^{ }]; nextVC.modalPresentationStyle = UIModalPresentationCustom; [self presentViewController:nextVC animated:YES completion:^{ nextVC.view.superview.backgroundColor = [UIColor clearColor]; }]; } #pragma mark p2p通道 重启云机 - (void)needToRebootFun { [[webRtcManager shareManager] needToRebootFun]; [self startForceStartTimerFun]; //最后一帧 没显示正在重启问题 //_canShowImgAndVoiceType = YES; [webRtcManager shareManager].isRebootIngType = YES; } #pragma mark X 秒后检查是否软件重启成功 - (void)startForceStartTimerFun { KWeakSelf //1. 5秒后跟硬件发起硬重启 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf updateForceStartFun]; }); //2. 20秒后尝试重连 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(20 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [weakSelf relinkWebRtcFun]; [[webRtcManager shareManager] relinkWebRtcFun]; }); } #pragma mark 需要强制重启 - (void)updateForceStartFun{ NSString* curSn = ksharedAppDelegate.DeviceThirdIdMod.data.changeSn; NSMutableDictionary *paraDict = [NSMutableDictionary dictionary]; [paraDict setValue:curSn forKey:@"sn"]; [paraDict setValue:[NSNumber numberWithBool:YES] forKey:@"isForceStart"]; [[netWorkManager shareInstance] CommonPostCallBackCode:updateForceStart Parameters:paraDict success:^(id _Nonnull responseObject) { SuperModel *model = [[SuperModel alloc] initWithDictionary:responseObject error:nil]; if (model.status == 0) { } else { } } failure:^(NSError * _Nonnull error) { HLog(@"%@", error); }]; } #pragma mark 退出云机 - (void)exitCloudPhoneFun { //[self setShowImgAndVoiceTypeFun:NO]; if(_isCodeSuspendAudioType){ [[DFPlayer sharedPlayer] df_play]; } [self.navigationController popViewControllerAnimated:YES]; } #pragma mark 显示关闭投屏提示语 - (void)showOpenTVP2PFun{ /*弹窗提示TV投屏*/ ComontAlretViewController *nextVC = [[ComontAlretViewController alloc] initWithTiTle:NSLocalizedString(@"File_upload_Record_clear_Tip_title",nil) msg:NSLocalizedString(@"cloudPhone_fullscreen_tvshow_tip",nil) imageStr:@"" cancelTitle:NSLocalizedString(@"other_cancel",nil) okTitle:NSLocalizedString(@"my_set_TVP2P_Open_sure",nil) isOkBtnHighlight:YES didClickOk:^{ [self openTvShowFun]; //关闭全屏屏 [HWDataManager setBoolWithKey:Consn_player_full_screen_show value:NO]; [self setPlayerFullScreenNotFun]; } didClickCancel:^{ }]; nextVC.modalPresentationStyle = UIModalPresentationCustom; [self presentViewController:nextVC animated:YES completion:^{ nextVC.view.superview.backgroundColor = [UIColor clearColor]; }]; } #pragma mark 开始投屏 - (void)openTvShowFun { self.controlBtn.userInteractionEnabled = NO; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self->controlBtn.userInteractionEnabled = YES; }); [[webRtcManager shareManager] onTvFun]; [[iToast makeText:NSLocalizedString(@"cloudPhone_TV_show_tip",nil)] show]; //数据埋点 [[netWorkManager shareInstance] DataEmbeddingPointBy:3 withEventValue:@"Cloud_tv"]; } #pragma mark 显示关闭投屏提示语 - (void)showCloseTVP2PFun{ /*弹窗提示TV投屏*/ ComontAlretViewController *nextVC = [[ComontAlretViewController alloc] initWithTiTle:NSLocalizedString(@"my_set_no_close_TV_p2p_tip",nil) msg:@"" imageStr:@"" cancelTitle:NSLocalizedString(@"other_cancel",nil) okTitle:NSLocalizedString(@"my_set_TVP2P_Open_sure",nil) isOkBtnHighlight:YES didClickOk:^{ [self gotoCloseTVP2PFun]; } didClickCancel:^{ //HLog(@"2222"); }]; nextVC.modalPresentationStyle = UIModalPresentationCustom; [self presentViewController:nextVC animated:YES completion:^{ nextVC.view.superview.backgroundColor = [UIColor clearColor]; }]; } #pragma mark 确认开始TV投屏 -(void)gotoCloseTVP2PFun { [[webRtcManager shareManager] offTvFun]; } #pragma mark 底部按钮事件 - (void)didClickBottomFunBy:(NSInteger)tag { int keyType = 0; if(tag == 1){//task keyType = 187; } else if(tag == 2){//home keyType = 3; } else if(tag == 3){//back keyType = 4; } if(keyType > 0){ [self didClickKeyEventFunBy:keyType]; } } #pragma mark 按键事件 // home 3 back 4 task 187 volumeUp 24 volumeDown 25 - (void)didClickKeyEventFunBy:(int)keyType { [_mediaStream sendKey:keyType]; } #pragma mark 云机交互的消息发送 -(void)send_data:(NSString*)commandStr { [[webRtcManager shareManager] send_data:commandStr]; } #pragma mark -- /*底部导航栏开关通知*/ - (void)getPlayerBottomNavShowOrHidefun { BOOL haveShowBottonNavType = ![HWDataManager getBoolWithKey:Consn_player_Nav_hide]; if(haveShowBottonNavType){ _bottomContrView.hidden = NO; } else{ _bottomContrView.hidden = YES; } } #pragma mark -- /*全面屏开关通知*/ - (void)setPlayerFullScreenNotFun { BOOL fullscreenType = [HWDataManager getBoolWithKey:Consn_player_full_screen_show]; CGFloat tempRate = 0.0; //重新设置分辨率 if(fullscreenType){ NSInteger cardDensity = 380;//480;//422;//380;//460; CGSize size = [RCCommandHelp commondToSetFullScreenPhoneSizeBySize]; if(isLan){ if(size.width < size.height){ size = CGSizeMake(size.height,size.width); } } [self setCardSize:size.width cardHeight:size.height cardDensity:cardDensity]; tempRate = (size.width *1.0)/(size.height *1.0); } else{ CGSize size = CGSizeMake(1080.0, 1920.0); if(isLan){ size = CGSizeMake(1920.0, 1080.0); } [self setCardSize:(NSInteger)size.width cardHeight:(NSInteger)size.height cardDensity:480]; tempRate = size.width/size.height; } //dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if (tempRate > 1) {/*横屏*/ [self setLanMas_makeWithImageRate:tempRate]; }else{/*竖屏*/ [self setPoMas_makeWithImageRate:tempRate]; } // }); } #pragma mark -- /*推流中修改卡的分辨率*/ - (void)setCardSize:(NSInteger)cardWidth cardHeight:(NSInteger)cardHeight cardDensity:(NSInteger)cardDensit { if(isLan){ HLog(@"云机屏幕 设置 宽:%ld 高:%ld",cardHeight,cardWidth); [_mediaStream setCardSize:cardHeight cardHeight:cardWidth cardDensity:cardDensit]; [_mediaStream setVideoSize:cardHeight videoHeight:cardWidth]; } else{ HLog(@"云机屏幕 设置 宽:%ld 高:%ld",cardWidth,cardHeight); [_mediaStream setCardSize:cardWidth cardHeight:cardHeight cardDensity:cardDensit]; [_mediaStream setVideoSize:cardWidth videoHeight:cardHeight]; } } #pragma mark -- /*通知添加与移除*/ - (void)addKVOObserverFun { outputVolumeKVO = YES; [[AVAudioSession sharedInstance] addObserver:self forKeyPath:@"outputVolume" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:(void *)[AVAudioSession sharedInstance]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getPlayerBottomNavShowOrHidefun) name:getPlayerBottomNavNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setPlayerFullScreenNotFun) name:setPlayerFullScreenNotification object:nil]; } - (void)removeKVOObserverFun { if (outputVolumeKVO) { [[AVAudioSession sharedInstance] removeObserver:self forKeyPath:@"outputVolume" context:(void *)[AVAudioSession sharedInstance]]; outputVolumeKVO = NO; } [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark 系统音量键监听 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ if(context == (__bridge void *)[AVAudioSession sharedInstance]) { /*音量开关打开时 允许发送指令 否则直接返回*/ float newValue = [[change objectForKey:@"new"] floatValue]; float oldValue = [[change objectForKey:@"old"] floatValue]; //HLog(@"音量 --old: %f ---new: %f",oldValue,newValue) if (newValue > oldValue) { HLog(@"\n-----音量增加"); [self didClickKeyEventFunBy:24]; } else { HLog(@"\n-----音量降低"); [self didClickKeyEventFunBy:25]; } } } #pragma mark 监听到云机的宽高以及屏幕方向 - (void)handlUIAfterGetCloudPhoneVideoWidth:(int)videoWidth videoHeight:(int)videoHeight rotation:(int)rotation { HLog(@"云机屏幕 回调 宽:%d 高:%d 横竖屏:%d",videoWidth,videoHeight,rotation); CGFloat curRate = (CGFloat)videoWidth/(CGFloat)videoHeight; if(lastVideoWHRate == curRate){ return; } didAdjusBtnType = NO; if(rotation == 0){//竖屏 //切换到竖屏 isLan = NO; [self hx_rotateToInterfaceOrientation:UIInterfaceOrientationPortrait]; [self setPoMas_makeWithImageRate:curRate]; } else if(rotation == 1){//横屏 //切换到横屏屏 isLan = YES; [self hx_rotateToInterfaceOrientation:UIInterfaceOrientationLandscapeRight]; [self setLanMas_makeWithImageRate:curRate]; } _didHandleRotation = YES; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self extensionAdjustBtnFrameCheckAdjustTime]; }); } #pragma mark WebRTC 回调 MediaStreamClientEventsDelegate #pragma mark - 宽高变化 -(void)onFrameResolutionChangedFromPeerName:(NSString*)peerName videoWidth:(int)videoWidth videoHeight:(int)videoHeight rotation:(int)rotation { HLog(@"onFrameResolutionChangedFromPeerName:%@---%d---%d--%d",peerName,videoWidth,videoHeight,rotation) mainBlock(^{ [self handlUIAfterGetCloudPhoneVideoWidth:videoWidth videoHeight:videoHeight rotation:rotation]; }); } //code 0 成功 1失败 -(void)onAuthResultFromPeerName:(NSString*)peerName code:(int)code descriptions:(NSString*)descriptions { HLog(@"onAuthResultFromPeerName:%@---%d---%@",peerName,code,descriptions) } #pragma mark 链接发生变化 -(void)onChangeConnectionStateFromPeerName:(NSString*)peerName didChangeIceConnectionState:(RTCIceConnectionState)state { HLog(@"onChangeConnectionStateFromPeerName: state:%ld",state) _linkState = state; switch (state) { case RTCIceConnectionStateConnected:{ //链接成功 mainBlock(^{ [self removeNewIndicator]; }); } break; case RTCIceConnectionStateCompleted: //链接完成 break; case RTCIceConnectionStateFailed: //链接失败 [self relinkWebRtcFun]; break; case RTCIceConnectionStateDisconnected: //链接断开 [self relinkWebRtcFun]; break; case RTCIceConnectionStateClosed: //链接关闭 if([webRtcManager shareManager].isRebootIngType){ [self relinkWebRtcFun]; } break; default: break; } } - (void)dataChannelDidChangeFromPeerName:(NSString*)peerName State:(RTCDataChannelState)state{ ; } //-(void)onIceConnectedFromPeerName:(NSString*)peerName{ // HLog(@"onIceConnectedFromPeerName:%@",peerName); // mainBlock(^{ // [self removeNewIndicator]; // }); //} -(void)didGetStats:(NSString*)peerName stats:(RTC_OBJC_TYPE(RTCStatisticsReport) *)stats { HLog(@"stats:%@",stats); } @end