// // TXLiveRoomViewController.m // StudentDaya // // Created by 王智 on 2023/2/21. // Copyright © 2023 DayaMusic. All rights reserved. // #import "TXLiveRoomViewController.h" #import "TXLiteAVSDK_Professional/TXLiteAVSDK.h" #import "KSLiveEmptyView.h" #import "TXLiveVideoView.h" #import "LiveRoomHeadView.h" #import "LiveRoomBottomView.h" #import #import "KSChatInputBarControl.h" #import "TXLiveChatListCell.h" #import "AppDelegate.h" #import #import "LiveRoomLikeLayer.h" #import "LiveAlertView.h" #import "KSShopCardView.h" #import "KSLiveWebViewController.h" #import "KSLiveBadNetView.h" #import "KSLiveEndView.h" #import "LiveAnimationView.h" #import "LiveApplyView.h" #import "LiveApplyingView.h" #import #import "TXIMLinsenter.h" #import "TXLiveMessageModel.h" #import "TXLiveURLUtils.h" #import "TXLiveMessageCenter.h" #import "KSDragWindowManager.h" #import "TXLiveLoadingView.h" #import "TXUISeatMember.h" #import "TXSeatContainerView.h" #import "LiveRoomConfirmAlert.h" #import "LiveModuleService.h" #define AUTOHIDE_TIME (10.0f) typedef NS_ENUM(NSInteger, MICSTATUS) { MICSTATUS_NOMAL, // 正常状态 MICSTATUS_WAITING, // 申请中 MICSTATUS_CONNECTING, // 连麦中 }; @interface TXLiveRoomViewController () // 是否是观众 @property (nonatomic, assign) BOOL isAudience; // rtc 连麦时使用 @property (nonatomic, strong) TRTCCloud *trtcCloud; @property (nonatomic, strong) V2TXLivePlayer *livePlayer; @property (nonatomic, strong) LiveRoomHeadView *headView; @property (nonatomic, strong) LiveRoomBottomView *bottomView; // 视频主窗口容器 @property (nonatomic, strong) UIView *videoContainerView; // 主屏videoView @property (nonatomic, strong) UIView *videoView; // 画面渲染的画面 @property (nonatomic, strong) TXLiveVideoView *videoRenderView; // 切换按钮 @property (nonatomic, strong) UIButton *switchButton; @property (nonatomic, strong) LOTAnimationView *animationView; /// 连麦的视图 @property (nonatomic, strong) TXSeatContainerView *seatContainer; // 聊天UI /*! 消息列表CollectionView和输入框都在这个view里 */ @property(nonatomic, strong) UIView *messageContentView; /*! 会话页面的TableView */ @property (nonatomic, strong) UITableView *conversationMessageTableView; /*! 聊天内容的消息Cell数据模型的数据源 @discussion 数据源中存放的元素为消息Cell的数据模型,即RCDLiveMessageModel对象。 */ @property(nonatomic, strong) NSMutableArray *conversationDataRepository; /** 输入工具栏 */ @property(nonatomic,strong) KSChatInputBarControl *inputBar; /** * 是否需要滚动到底部 */ @property(nonatomic, assign) BOOL isNeedScrollToButtom; /** 上次点赞按钮点击时间 */ @property(nonatomic, assign) NSTimeInterval lastClickPraiseTime; @property (nonatomic, assign) BOOL landScape; @property (nonatomic, assign) BOOL isHiddenHeadView; // 连麦状态 @property (nonatomic, assign) MICSTATUS micStatus; @property (nonatomic, strong) LiveApplyView *applyView; // 未申请 @property (nonatomic, strong) LiveApplyingView *applyingView; // 申请中 @property (nonatomic, assign) BOOL isCreaterVideoEnable; @property (nonatomic, assign) BOOL isCloseRoom; // 是否直播结束 /// 提示窗 @property (nonatomic, strong) LiveAlertView *alertView; @property (nonatomic, strong) KSShopCardView *cardView; @property (nonatomic, assign) NSTimeInterval lastSendMsgTime; @property (nonatomic, strong) LiveAnimationView *enterAnimationView; @property (nonatomic, strong) KSLiveEndView *endView; // 直播结束显示的画面 @property (nonatomic, strong) KSLiveEmptyView *emptyView; @property (nonatomic, strong) KSLiveBadNetView *netBadView; /// 是否在切换身份 @property (nonatomic, assign) BOOL isTransferRole; @property (nonatomic, assign) BOOL hasSendWelcomeMessage; // 远端连麦用户 @property (nonatomic, strong) NSMutableArray *remoteMemberArray; @property (nonatomic, assign) BOOL isPauseLive; @property (nonatomic, strong) TXLiveLoadingView *loadingView; @property (nonatomic, assign) BOOL hasShowSuspendView; @property (nonatomic, assign) BOOL muteRoomMic; @property (nonatomic, assign) BOOL micEnable; @property (nonatomic, strong) LiveRoomConfirmAlert *closeAlert; @property (nonatomic, assign) BOOL isConnectedFailed; #pragma mark --- 时间统计 // 后端返回进入教室时间戳 ms @property (nonatomic, assign) NSTimeInterval enterTime; // 进入教室时间date @property (nonatomic, strong) NSDate *enterDate; @end // 用于记录点赞消息连续点击的次数 static int clickPraiseBtnTimes = 0; @implementation TXLiveRoomViewController - (instancetype)init { self = [super init]; if (self) { [self defaultConfig]; [self default3AConfig]; } return self; } - (void)default3AConfig { self.ANS = 0; self.AEC = 100; self.AGC = 0; } - (void)joinChatRoom { if (TXIM_LINSENTER.loginIMSuccess == NO) { [TXIM_LINSENTER TXIMLoginWithUserId:UserDefault(IM_USERID) sig:self.UserSig callback:^(BOOL isSuccess, NSString * _Nullable msg) { [self liveConversationJoin]; }]; } else { [self liveConversationJoin]; } } - (void)liveConversationJoin { [[V2TIMManager sharedInstance] joinGroup:self.roomId msg:nil succ:^{ dispatch_async(dispatch_get_main_queue(), ^{ [self addConstMessage]; [self sendWelcomeMessage]; [self getGroupAttrInfo]; [self getGroupCount]; }); } fail:^(int code, NSString *desc) { NSLog(@""); }]; } - (void)getGroupAttrInfo { [[V2TIMManager sharedInstance] getGroupAttributes:self.roomId keys:nil succ:^(NSMutableDictionary *groupAttributeList) { NSLog(@""); [self evaluateGroupInfo:groupAttributeList]; } fail:^(int code, NSString *desc) { }]; } - (void)getGroupCount { [[V2TIMManager sharedInstance] getGroupCounters:self.roomId keys:nil succ:^(NSDictionary *groupCounters) { NSLog(@""); [self evaluateGroupCount:groupCounters]; } fail:^(int code, NSString *desc) { }]; } - (void)addConstMessage { TXConstMessage *statement = [[TXConstMessage alloc] init]; statement.userInfo = [self getCurrentUser]; statement.text = @"欢迎进入直播课堂,请遵守相关法规,禁止传播低俗、暴力等不良信息。为孩子创造健康绿色的学习环境。"; [self appendAndDisplayMessage:statement]; } - (void)sendWelcomeMessage { TXLiveMessageWelcome *welcomeMessage = [[TXLiveMessageWelcome alloc] init]; welcomeMessage.userInfo = [self getCurrentUser]; MJWeakSelf; [self sendCustomMessage:welcomeMessage priority:V2TIM_PRIORITY_DEFAULT displayMessage:NO callback:^(BOOL success) { weakSelf.hasSendWelcomeMessage = YES; [weakSelf showEnterMessageAndJoinRoom]; }]; } - (void)showEnterMessageAndJoinRoom { NSString *contentString = [NSString stringWithFormat:@"%@ 进入直播间",UserDefault(NicknameKey)]; [self showAnimationView:YES showMessag:contentString]; } - (void)joinLiveRoom { [self changeEmptyStatusView]; [self resetNetTips]; [self startPlay]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [UIApplication sharedApplication].idleTimerDisabled = YES; [IQKeyboardManager sharedManager].enableAutoToolbar = NO; [IQKeyboardManager sharedManager].enable = NO; [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent; // 重新渲染view KSDragWindowManager *manager = [KSDragWindowManager sharedManager]; if (manager.hasShowWindow) { [manager resignDragWindow]; } if (self.hasShowSuspendView) { [self.videoView addSubview:self.videoRenderView]; [self.videoRenderView mas_updateConstraints:^(MASConstraintMaker *make) { make.left.right.bottom.top.mas_equalTo(self.videoView); }]; _hasShowSuspendView = NO; } if (self.bottomView.isMute == NO) { [self muteVoiceAction]; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [UIApplication sharedApplication].idleTimerDisabled = NO; if (self.landScape) { [self changeOrientation:NO]; } [IQKeyboardManager sharedManager].enableAutoToolbar = YES; [IQKeyboardManager sharedManager].enable = YES; if (@available(iOS 13.0, *)) { [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDarkContent; } else { // Fallback on earlier versions [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault; } } - (void)defaultConfig { self.enableChat = YES; self.enableSeat = YES; self.enableLike = YES; self.needSwitchButton = NO; self.micStatus = MICSTATUS_NOMAL; self.isCreateInRoom = NO; // 默认主讲人不在房间 self.blacklistFlag = NO; self.isCloseRoom = NO; self.lastClickPraiseTime = 0; self.isAudience = YES; self.isCreaterVideoEnable = YES; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.ks_prefersNavigationBarHidden = YES; [self setupUI]; [self registerNotification]; [self joinChatRoom]; [self joinLiveRoom]; [self notiferJoinSuccessToServiceCallback:^{ }]; } - (void)registerNotification { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMessageNotification:) name:OnReceiveTXLiveMessageNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomLogOut) name:@"liveroomLogout" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(IMConnetedCallback) name:@"TXIMConnected" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(muteAllAudio) name:@"muteLiveAudio" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveGroupMessage:) name:OnReceiveTXLiveGroupNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveGroupCountMessage:) name:OnReceiveTXLiveGroupCountNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkTips) name:@"networkError" object:nil]; } - (void)networkTips { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"直播已断开,请检查您的网络"]; self.isConnectedFailed = YES; } - (void)IMConnetedCallback { self.isConnectedFailed = NO; dispatch_main_async_safe(^{ // IM 连接成功回调 if (self.hasSendWelcomeMessage) { // 如果已经发送了进入消息 // 查询是否直播间开启 [LiveModuleService imLiveBroadcastRoomQueryRoomRequest:KS_GET roomId:self.roomId success:^(NSDictionary * _Nonnull dic) { if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) { // 回调信息 MJWeakSelf; [self notiferJoinSuccessToServiceCallback:^{ [weakSelf sendWelcomeMessage]; [weakSelf checkPlayerStatus]; }]; } else { [LOADING_MANAGER MBShowAUTOHidingInWindow:MESSAGEKEY]; [self quitRoom]; } } faliure:^(NSError * _Nonnull error) { }]; // 查询群属性 [self getGroupAttrInfo]; } }); } - (void)checkPlayerStatus { if (self.isCloseRoom) { return; } if (self.isAudience) { if (self.livePlayer.isPlaying == NO) { [self resetNetTips]; [self startPlay]; } } else { [self connectHostWithStatus:NO]; } } - (void)liveroomLogOut { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"该账号在其他设备上登录"]; [self quitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil]; }); } - (void)muteAllAudio { [self muteVoiceAction]; } #pragma mark ----- // 观众上下麦 - (void)connectHostWithStatus:(BOOL)isConnect { // 如果直播结束 不转换身份 if (self.isCloseRoom == YES) { return; } // 转换中,不再处理转换身份 if (self.isTransferRole) { return; } if (isConnect && self.isAudience == NO) { self.micStatus = MICSTATUS_CONNECTING; return; } else if (isConnect == NO && self.isAudience) { self.micStatus = MICSTATUS_NOMAL; return; } self.isTransferRole = YES; self.micEnable = YES; // 清理当前视图 [self removeSeatContainer]; self.remoteMemberArray = [NSMutableArray array]; // 移除状态视图 if (isConnect) { [self stopPlay]; [self hideTipsView]; if (self.isCreaterVideoEnable) { [self hideEmptyView]; } self.isAudience = NO; [self enterLiveRTCRoom]; self.isTransferRole = NO; self.micStatus = MICSTATUS_CONNECTING; } else { self.isAudience = YES; if (self.isPauseLive == NO) { [self startPlay]; } if (_trtcCloud) { [self.trtcCloud stopAllRemoteView]; [self.trtcCloud stopLocalAudio]; [self.trtcCloud exitRoom]; _trtcCloud = nil; [TRTCCloud destroySharedInstance]; } self.micStatus = MICSTATUS_NOMAL; self.isTransferRole = NO; } [self muteVoiceAction]; } - (void)setupUI { self.view.backgroundColor = HexRGB(0x25292e); [self.view addSubview:self.emptyView]; [self.view addSubview:self.endView]; [self.view addSubview:self.videoContainerView]; [self.videoContainerView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(self.view); }]; [self.videoContainerView addSubview:self.videoView]; // 异常的view [self.videoContainerView addSubview:self.netBadView]; // 按钮和视频的布局 if (self.needSwitchButton) { [self.videoView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.videoContainerView); if (@available(iOS 11.0, *)) { make.top.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideTop).offset(160); } else { // Fallback on earlier versions make.top.mas_equalTo(self.videoContainerView.mas_top).offset(160); } CGFloat rate = 9 / 16.0f; make.height.mas_equalTo(self.videoContainerView.mas_width).multipliedBy(rate); make.width.height.mas_equalTo(self.videoContainerView).priorityLow(); make.width.height.lessThanOrEqualTo(self.videoContainerView); }]; // 显示切换按钮 [self.videoContainerView addSubview:self.switchButton]; [self.switchButton mas_makeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.videoContainerView.mas_right).offset(-10); make.width.height.mas_equalTo(33); make.bottom.mas_equalTo(self.videoView.mas_bottom).offset(-10); }]; self.switchButton.hidden = NO; } else { [self.videoView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.bottom.right.mas_equalTo(self.videoContainerView); }]; } // 添加视频预览图 [self.videoView addSubview:self.videoRenderView]; [self.videoRenderView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(self.videoView); }]; [self.emptyView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(self.videoView); }]; self.emptyView.hidden = YES; // 直播结束的画面 [self.endView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(self.videoView); }]; self.endView.hidden = YES; [self.netBadView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(self.videoView); }]; self.netBadView.hidden = YES; [self.videoContainerView addSubview:self.headView]; self.headView.boardcastName.text = [NSString returnNoNullStringWithString:self.createrName]; if (![NSString isEmptyString:self.createrAvatal]) { [self.headView.boardcastAvatal sd_setImageWithURL:[NSURL URLWithString:[self.createrAvatal getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:@"teacher_logo"]]; } [self countLikeMessageCount]; [self countMemberCount]; [self.headView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.videoContainerView); make.height.mas_equalTo(80); if (@available(iOS 11.0, *)) { make.top.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideTop).offset(0); } else { // Fallback on earlier versions make.top.mas_equalTo(self.videoContainerView.mas_top).offset(0); } }]; // 聊天区域 [self.videoContainerView addSubview:self.messageContentView]; if (self.hideCartButton == NO) { [self.videoContainerView addSubview:self.animationView]; [self.animationView mas_makeConstraints:^(MASConstraintMaker *make) { if (@available(iOS 11.0, *)) { make.bottom.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideBottom).offset(-9); } else { // Fallback on earlier versions make.bottom.mas_equalTo(self.videoContainerView.mas_bottom).offset(-9); } make.centerX.mas_equalTo(self.videoContainerView.mas_right).offset(-80); make.width.mas_equalTo(48); make.height.mas_equalTo(56); }]; [self.animationView play]; } [self.videoContainerView addSubview:self.bottomView]; self.bottomView.hideCartVButton = self.hideCartButton; [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.videoContainerView); if (@available(iOS 11.0, *)) { make.bottom.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideBottom).offset(-5); } else { // Fallback on earlier versions make.bottom.mas_equalTo(self.videoContainerView.mas_bottom).offset(-5); } make.height.mas_equalTo(44); }]; // 聊天页面 if (self.needSwitchButton) { [self.messageContentView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.videoContainerView.mas_left); make.right.mas_equalTo(self.videoContainerView.mas_right); make.bottom.mas_equalTo(self.bottomView.mas_top).offset(-5); make.top.mas_equalTo(self.videoView.mas_bottom).offset(10).priorityLow(); }]; } else { [self.messageContentView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.videoContainerView.mas_left); make.right.mas_equalTo(self.videoContainerView.mas_right); make.bottom.mas_equalTo(self.bottomView.mas_top).offset(-10); make.height.mas_equalTo(180); }]; } [self.videoContainerView addSubview:self.inputBar]; [_inputBar setBackgroundColor: [UIColor whiteColor]]; [_inputBar setFrame:CGRectMake(0, KPortraitHeight, KPortraitWidth , 50)]; [_inputBar setHidden:YES]; [self.messageContentView addSubview:self.conversationMessageTableView]; [self.conversationMessageTableView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.mas_equalTo(self.messageContentView); make.width.mas_equalTo(KPortraitWidth); make.bottom.mas_equalTo(self.messageContentView.mas_bottom); }]; UITapGestureRecognizer *resetBottomTapGesture =[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(resetBottomGesture:)]; resetBottomTapGesture.delegate = self; [self.videoContainerView addGestureRecognizer:resetBottomTapGesture]; UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(headViewAnimation:)]; tapGesture.delegate = self; [self.videoView addGestureRecognizer:tapGesture]; } - (void)headViewAnimation:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateEnded) { [self setDefaultBottomViewStatus]; } if (self.landScape) { self.isHiddenHeadView = !self.isHiddenHeadView; } } #pragma mark -- 加入直播间和退出直播间回到服务 - (void)notiferJoinSuccessToServiceCallback:(void(^)(void))callback { [LiveModuleService LiveroomJoinRequest:KS_GET roomId:self.roomId success:^(NSDictionary * _Nonnull dic) { NSTimeInterval enterTime = [dic ks_integerValueForKey:@"data"]; self.enterTime = enterTime; NSDate *currentDate = [NSDate date]; self.enterDate = currentDate; callback(); } faliure:^(NSError * _Nonnull error) { callback(); }]; } - (void)quitNotiferService { NSDate *currentDate = [NSDate date]; NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:self.enterDate]; NSInteger time = (NSInteger)(timeInterval *1000); if (self.enterTime > 0) { time = self.enterTime + time; } else { time = (NSInteger)([currentDate timeIntervalSince1970] *1000); } [LiveModuleService LiveroomQuit:KS_POST time:time success:^(NSDictionary * _Nonnull dic) { if ([dic ks_integerValueForKey:@"errCode"] == 0) { NSLog(@"success"); } else { NSLog(@"----- error %@", [dic ks_stringValueForKey:@"errMsg"]); } } faliure:^(NSError * _Nonnull error) { }]; } #pragma mark ------ 播放控制 - (void)startPlay { if ([self.livePlayer isPlaying] == NO) { [self resetNetTips]; if (self.isCreaterVideoEnable) { [self hideEmptyView]; } [self showLoading]; self.videoRenderView.hasMainStream = YES; self.videoRenderView.hasSubStream = NO; [self.livePlayer setRenderView:self.videoRenderView.mainView]; if (self.needSwitchButton) { [self.livePlayer setRenderFillMode:V2TXLiveFillModeFit]; } else { [self.livePlayer setRenderFillMode:V2TXLiveFillModeFill]; } // 获取url的方法 NSString *streamId = [NSString stringWithFormat:@"%@_%@", self.roomId, self.createrId]; NSString *url = [TXLiveURLUtils generateRtmpPlayUrl:streamId playDomain:self.playDomain liveUrlKey:self.liveUrlKey]; [self.livePlayer startLivePlay:url]; [self muteVoiceAction]; } } - (void)stopPlay { if (_livePlayer) { if ([self.livePlayer isPlaying]) { [self hideLoading]; [self.livePlayer stopPlay]; } } } // 不播放声音 - (void)pauserAudio { [self.livePlayer pauseAudio]; } // 恢复声音 - (void)resumeAudio { [self.livePlayer resumeAudio]; } #pragma mark ---- V2TXLivePlayerObserver - (void)onError:(id)player code:(V2TXLiveCode)code message:(NSString *)msg extraInfo:(NSDictionary *)extraInfo { if (code == V2TXLIVE_ERROR_DISCONNECTED) { // 连接断开 if (self.isPauseLive == NO && self.isCreateInRoom) { [self hideLoading]; [self showBadNetTips]; } } else if (code == V2TXLIVE_WARNING_VIDEO_BLOCK) { } NSLog(@"---- V2TXLivePlayer %zd message:%@", code, msg); } - (void)onConnected:(id)player extraInfo:(NSDictionary *)extraInfo { NSLog(@" ------ connected %@", extraInfo); self.isPauseLive = NO; self.isCreateInRoom = YES; if (self.isCreaterVideoEnable) { [self hideEmptyView]; } } - (void)onVideoPlaying:(id)player firstPlay:(BOOL)firstPlay extraInfo:(NSDictionary *)extraInfo { NSLog(@" ------ onVideoPlaying %@", extraInfo); [self hideLoading]; } - (void)onVideoResolutionChanged:(id)player width:(NSInteger)width height:(NSInteger)height { NSLog(@" ------ onVideoResolutionChanged %zd %zd", width, height); } // 直播状态 - (void)onStatisticsUpdate:(id)player statistics:(V2TXLivePlayerStatistics *)statistics { NSLog(@"----- V2TXLivePlayerStatistics desc %@", statistics.description); } - (void)onReceiveSeiMessage:(id)player payloadType:(int)payloadType data:(NSData *)data { NSLog(@"----- onReceiveSeiMessage desc %@", [data mj_JSONObject]); if (self.isAudience == YES && self.isPauseLive == NO) { NSDictionary *parm = [data mj_JSONObject]; if ([[parm ks_stringValueForKey:@"api"] isEqualToString:@"sync"]) { // 同步连麦人数 NSArray *memberArray = [parm ks_arrayValueForKey:@"seatUser"]; NSMutableArray *remoteSeatArray = [NSMutableArray arrayWithArray:memberArray]; // 移除自己 NSMutableArray *seatArray = [NSMutableArray array]; for (NSDictionary *userParm in remoteSeatArray) { if (![[userParm ks_stringValueForKey:@"userId"] isEqualToString:UserDefault(IM_USERID)]) { TXUISeatMember *member = [[TXUISeatMember alloc] init]; member.userId = [userParm ks_stringValueForKey:@"userId"]; member.muteMic = [userParm ks_boolValueForKey:@"micStatus"]; [seatArray addObject:member]; } } [self refreshSeatWithMember:seatArray]; // if (self.remoteMemberArray.count != seatArray.count) { // 如果连麦人数不同 // [self refreshSeatWithMember:seatArray]; // } // else { // BOOL isSameUser = YES; // NSMutableArray *seatMemberArr = [self.remoteMemberArray mutableCopy]; // // for (TXUISeatMember *member in seatMemberArr) { // // if (![remoteSeatArray containsObject:member]) { // isSameUser = NO; // } // } // if (isSameUser == NO) { // [self refreshSeatWithMember:seatArray]; // } // } } } else { [self refreshSeatWithMember:@[]]; } } - (void)refreshSeatWithMember:(NSArray *)memberArray { self.remoteMemberArray = [NSMutableArray arrayWithArray:memberArray]; [self renderSeatView]; } #pragma mark --- trtcCloud delegate // 远端主播加入房间 - (void)onRemoteUserEnterRoom:(NSString *)userId { if (![self judgeContainerRemoteUser:userId] && ![userId isEqualToString:self.createrId]) { // 如果不包含当前用户 添加 和刷新连麦 TXUISeatMember *member = [[TXUISeatMember alloc] init]; member.userId = userId; member.muteMic = YES; [self.remoteMemberArray addObject:member]; } [self renderSeatView]; } // 远端主播离开 - (void)onRemoteUserLeaveRoom:(NSString *)userId reason:(NSInteger)reason { if ([self judgeContainerRemoteUser:userId]) { // 如果包含当前用户 移除当前用和刷新连麦 [self removeMemberUserId:userId]; } [self renderSeatView]; } - (BOOL)judgeContainerRemoteUser:(NSString *)userId { BOOL containUser = NO; NSMutableArray *remoteMemArray = [self.remoteMemberArray mutableCopy]; for (TXUISeatMember *member in remoteMemArray) { if ([member.userId isEqualToString:userId]) { containUser = YES; } } return containUser; } - (void)removeMemberUserId:(NSString *)userId { NSMutableArray *remoteMemArray = [self.remoteMemberArray mutableCopy]; for (TXUISeatMember *member in remoteMemArray) { if ([member.userId isEqualToString:userId]) { [self.remoteMemberArray removeObject:member]; return; } } } - (void)onWarning:(TXLiteAVWarning)warningCode warningMsg:(NSString *)warningMsg extInfo:(NSDictionary *)extInfo { NSLog(@" warningMsg %@ extInfo:%@", warningMsg, extInfo.description); } // 远端视频 - (void)onUserVideoAvailable:(NSString *)userId available:(BOOL)available { NSLog(@"creater remote videoStream status: %d", available); if (available) { self.videoRenderView.hasMainStream = YES; if (self.videoRenderView.hasSubStream) { [self.trtcCloud startRemoteView:self.createrId streamType:TRTCVideoStreamTypeBig view:self.videoRenderView.smallView]; [self.trtcCloud startRemoteView:self.createrId streamType:TRTCVideoStreamTypeSub view:self.videoRenderView.mainView]; } else { [self.trtcCloud stopRemoteView:self.createrId streamType:TRTCVideoStreamTypeSub]; [self.trtcCloud startRemoteView:self.createrId streamType:TRTCVideoStreamTypeBig view:self.videoRenderView.mainView]; } } else { self.videoRenderView.hasMainStream = NO; [self.trtcCloud stopRemoteView:self.createrId streamType:TRTCVideoStreamTypeBig]; } } - (void)onUserSubStreamAvailable:(NSString *)userId available:(BOOL)available { NSLog(@"creater remote subStream status: %d", available); if (available) { self.videoRenderView.hasSubStream = YES; [self.trtcCloud startRemoteView:self.createrId streamType:TRTCVideoStreamTypeSub view:self.videoRenderView.mainView]; if (self.videoRenderView.hasMainStream) { [self.trtcCloud startRemoteView:self.createrId streamType:TRTCVideoStreamTypeBig view:self.videoRenderView.smallView]; } } else { self.videoRenderView.hasSubStream = NO; [self.trtcCloud stopRemoteView:self.createrId streamType:TRTCVideoStreamTypeSub]; if (self.videoRenderView.hasMainStream) { [self.trtcCloud startRemoteView:self.createrId streamType:TRTCVideoStreamTypeBig view:self.videoRenderView.mainView]; } } } - (void)onFirstVideoFrame:(NSString *)userId streamType:(TRTCVideoStreamType)streamType width:(int)width height:(int)height { } - (void)onRecvSEIMsg:(NSString *)userId message:(NSData *)message { // NSDictionary *parm = [message mj_JSONObject]; } - (void)onUserAudioAvailable:(NSString *)userId available:(BOOL)available { for (TXUISeatMember *member in self.remoteMemberArray) { if ([member.userId isEqualToString:userId]) { member.muteMic = !available; } } [self renderSeatView]; } #pragma mark - gesture and button action - (void)resetBottomGesture:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateEnded) { [self setDefaultBottomViewStatus]; } } - (void)setDefaultBottomViewStatus { [self.inputBar setInputBarStatus:KSBottomBarStatusDefault]; [self.inputBar setHidden:YES]; } #pragma mark --- setter - (void)setIsHiddenHeadView:(BOOL)isHiddenHeadView { if (_isHiddenHeadView == isHiddenHeadView) { return; } _isHiddenHeadView = isHiddenHeadView; //告知需要更改约束 [self.view setNeedsUpdateConstraints]; [UIView animateWithDuration:0.5 animations:^{ if (isHiddenHeadView) { [self.headView mas_updateConstraints:^(MASConstraintMaker *make) { if (@available(iOS 11.0, *)) { make.top.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideTop).offset(-80); } else { // Fallback on earlier versions make.top.mas_equalTo(self.videoContainerView.mas_top).offset(-80); } }]; } else { [self.headView mas_updateConstraints:^(MASConstraintMaker *make) { if (@available(iOS 11.0, *)) { make.top.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideTop).offset(10); } else { // Fallback on earlier versions make.top.mas_equalTo(self.videoContainerView.mas_top).offset(10); } }]; } [self.headView.superview layoutIfNeeded]; }]; } #pragma mark ----- landscape action - (void)transferToLandscape { [self setDefaultBottomViewStatus]; [self changeOrientation:!self.landScape]; } - (void)changeOrientation:(BOOL)isLandScape { self.landScape = isLandScape; if (isLandScape) { if (IS_IPAD) { self.zh_statusBarHidden = YES; } // 切换到横屏 AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate; delegate.allowAutoRotate = YES; [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight inController:self]; } else { if (IS_IPAD) { self.zh_statusBarHidden = NO; } AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate; delegate.allowAutoRotate = NO; [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait inController:self]; } self.isHiddenHeadView = NO; [self updateConstraint]; } - (void)updateConstraint { if (self.landScape) { self.bottomView.hideChatView = YES; self.messageContentView.hidden = YES; self.headView.hideQuitButton = YES; [self.videoView mas_remakeConstraints:^(MASConstraintMaker *make) { if (IS_IPAD) { make.left.right.mas_equalTo(self.videoContainerView); make.centerX.mas_equalTo(self.videoContainerView.mas_centerX); CGFloat rate = 9 / 16.0f; make.height.mas_equalTo(self.videoContainerView.mas_width).multipliedBy(rate); make.width.height.mas_equalTo(self.videoContainerView).priorityLow(); make.width.height.lessThanOrEqualTo(self.videoContainerView); } else { make.top.bottom.mas_equalTo(self.videoContainerView); make.centerX.mas_equalTo(self.videoContainerView.mas_centerX); // 宽高设置 CGFloat rate = 16 / 9.0f; make.width.mas_equalTo(self.videoContainerView.mas_height).multipliedBy(rate); make.width.height.mas_equalTo(self.videoContainerView).priorityLow(); make.width.height.lessThanOrEqualTo(self.videoContainerView); } }]; [self.switchButton mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.videoContainerView.mas_right).offset(-10); make.width.height.mas_equalTo(33); make.centerY.mas_equalTo(self.videoView.mas_centerY); }]; } else { self.bottomView.hideChatView = NO; self.messageContentView.hidden = NO; self.headView.hideQuitButton = NO; [self.videoView mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.videoContainerView); if (@available(iOS 11.0, *)) { make.top.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideTop).offset(160); } else { // Fallback on earlier versions make.top.mas_equalTo(self.videoContainerView.mas_top).offset(160); } CGFloat rate = 9 / 16.0f; make.height.mas_equalTo(self.videoContainerView.mas_width).multipliedBy(rate); make.width.height.mas_equalTo(self.videoContainerView).priorityLow(); make.width.height.lessThanOrEqualTo(self.videoContainerView); }]; [self.switchButton mas_remakeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.videoContainerView.mas_right).offset(-10); make.width.height.mas_equalTo(33); make.bottom.mas_equalTo(self.videoView.mas_bottom).offset(-10); }]; } // 更新连麦UI if ([self.videoContainerView.subviews containsObject:self.seatContainer]) { [self updateSeatContainerConstraint]; } } - (void)showEmptyViewDisplay:(BOOL)isPublish { NSString *imageName = @""; NSString *descMessage = @""; if (isPublish) { imageName = @"liveVideo_close"; descMessage = @"主讲人已关闭画面!"; } else { imageName = @"liveroom_empty"; descMessage = @"休息一下,马上回来!"; } [self.emptyView configViewWithImageName:imageName desc:descMessage]; [self hideTipsView]; self.videoView.hidden = YES; self.emptyView.hidden = NO; } - (void)hideEmptyView { self.emptyView.hidden = YES; self.videoView.hidden = NO; } - (void)changeEmptyStatusView { if (self.isCloseRoom == NO) { // 只有在非结束直播状态下 if (self.isPauseLive) { // 暂停直播 [self showEmptyViewDisplay:NO]; } else { // 如果未暂停直播 if (self.isCreaterVideoEnable) { // 如果摄像头开启 [self hideTipsView]; [self hideEmptyView]; } else { // 主播关闭了摄像头 [self showEmptyViewDisplay:YES]; } } } } - (void)renderSeatView { NSMutableArray *seatArray = [NSMutableArray array]; NSArray *remoteUserArray = [self.remoteMemberArray mutableCopy]; for (TXUISeatMember *member in remoteUserArray) { [seatArray addObject:member]; } BOOL isContainSelf = NO; for (TXUISeatMember *member in seatArray) { if ([member.userId isEqualToString:UserDefault(IM_USERID)]) { isContainSelf = YES; break; } } if (self.micStatus == MICSTATUS_CONNECTING && !isContainSelf) { TXUISeatMember *currentMember = [[TXUISeatMember alloc] init]; currentMember.userId = UserDefault(IM_USERID); currentMember.muteMic = !self.micEnable; [seatArray addObject:currentMember]; } NSLog(@"-- seat member--- %zd -", seatArray.count); if (seatArray.count) { if (![self.videoContainerView.subviews containsObject:self.seatContainer]) { [self.videoContainerView addSubview:self.seatContainer]; } [self updateSeatContainerConstraint]; // self.seatContainer.seatMemberArray = [NSMutableArray arrayWithArray:seatArray]; // [self.seatContainer refreshSeatUI]; [self.seatContainer refreshSeatUIWithMember:[NSMutableArray arrayWithArray:seatArray]]; } else { [self removeSeatContainer]; } } // 移除连麦视图 - (void)removeSeatContainer { if ([self.videoContainerView.subviews containsObject:self.seatContainer]) { [self.seatContainer removeFromSuperview]; } } - (void)updateSeatContainerConstraint { if (self.landScape) { [self.seatContainer mas_remakeConstraints:^(MASConstraintMaker *make) { if (@available(iOS 11.0, *)) { make.left.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideLeft); } else { // Fallback on earlier versions make.left.mas_equalTo(self.videoContainerView.mas_left); } make.right.mas_equalTo(self.videoContainerView.mas_right).offset(-70); if (@available(iOS 11.0, *)) { make.top.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideTop).offset(85); } else { // Fallback on earlier versions make.top.mas_equalTo(self.videoContainerView.mas_top).offset(85); } make.height.mas_equalTo(70); }]; } else { [self.seatContainer mas_remakeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.videoContainerView); make.top.mas_equalTo(self.headView.mas_bottom).offset(0); make.height.mas_equalTo(70); }]; } } #pragma mark ----- 加入TRC房间 - (void)enterLiveRTCRoom { self.videoRenderView.hasMainStream = NO; self.videoRenderView.hasSubStream = NO; TRTCParams *parms = [[TRTCParams alloc] init]; parms.sdkAppId = CONFIG_TXSDKAPPID; parms.userSig = self.UserSig; parms.role = TRTCRoleAnchor; parms.strRoomId = self.roomId; parms.userId = UserDefault(IM_USERID); [self.trtcCloud addDelegate:self]; [self config3AParams]; [self configVideoAndAudioSetting]; [self.trtcCloud enterRoom:parms appScene:TRTCAppSceneLIVE]; TRTCRenderParams *rendParams = [[TRTCRenderParams alloc] init]; if (self.needSwitchButton) { rendParams.fillMode = TRTCVideoFillMode_Fit; } else { rendParams.fillMode = TRTCVideoFillMode_Fill; } [self.trtcCloud setRemoteRenderParams:self.createrId streamType:TRTCVideoStreamTypeBig params:rendParams]; [self.trtcCloud setRemoteRenderParams:self.createrId streamType:TRTCVideoStreamTypeSub params:rendParams]; } - (void)configVideoAndAudioSetting { [self.trtcCloud startLocalAudio:TRTCAudioQualityMusic]; TRTCVideoEncParam *videoEncParam = [[TRTCVideoEncParam alloc] init]; videoEncParam.videoFps = 24; videoEncParam.resMode = TRTCVideoResolutionModePortrait; videoEncParam.videoResolution = TRTCVideoResolution_960_540; [self.trtcCloud setVideoEncoderParam:videoEncParam]; if (self.muteRoomMic) { [self micButtonAction:self.muteRoomMic]; } } // 配置3A参数 - (void)config3AParams { [self configANSParm:self.ANS]; [self configAECParm:self.AEC]; [self configAGCParm:self.AGC]; } // 背景音抑制 - (void)configANSParm:(NSInteger)number { NSMutableDictionary *dic = [NSMutableDictionary dictionary]; [dic setValue:@"enableAudioANS" forKey:@"api"]; NSMutableDictionary *subDic = [NSMutableDictionary dictionary]; if (number > 0) { [subDic setValue:@(1) forKey:@"enable"]; [subDic setValue:@(number) forKey:@"level"]; } else { [subDic setValue:@(0) forKey:@"enable"]; [subDic setValue:@(0) forKey:@"level"]; } [dic setValue:subDic forKey:@"params"]; NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:nil]; NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [self.trtcCloud callExperimentalAPI:jsonString]; } // 回声消除 - (void)configAECParm:(NSInteger)number { NSMutableDictionary *dic = [NSMutableDictionary dictionary]; [dic setValue:@"enableAudioAEC" forKey:@"api"]; NSMutableDictionary *subDic = [NSMutableDictionary dictionary]; if (number > 0) { [subDic setValue:@(1) forKey:@"enable"]; [subDic setValue:@(number) forKey:@"level"]; } else { [subDic setValue:@(0) forKey:@"enable"]; [subDic setValue:@(0) forKey:@"level"]; } [dic setValue:subDic forKey:@"params"]; NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:nil]; NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [self.trtcCloud callExperimentalAPI:jsonString]; } // 自动增益 - (void)configAGCParm:(NSInteger)number { NSMutableDictionary *dic = [NSMutableDictionary dictionary]; [dic setValue:@"enableAudioAGC" forKey:@"api"]; NSMutableDictionary *subDic = [NSMutableDictionary dictionary]; if (number > 0) { [subDic setValue:@(1) forKey:@"enable"]; [subDic setValue:@(number) forKey:@"level"]; } else { [subDic setValue:@(0) forKey:@"enable"]; [subDic setValue:@(0) forKey:@"level"]; } [dic setValue:subDic forKey:@"params"]; NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:nil]; NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; [self.trtcCloud callExperimentalAPI:jsonString]; } #pragma mark -------- live message handle - (NSString *)getMsgClassName:(NSString *)messageObjectName { TXLiveMessageModel *configModel = [[TXLiveMessageModel alloc] init]; NSString *className = [configModel getMessageClassName:messageObjectName]; return className; } /** * 接收到消息的回调 */ - (void)didReceiveMessageNotification:(NSNotification *)notification { NSDictionary *dic = notification.object; /// __block TXLiveMessageModel *liveMessage NSDictionary *content = [dic ks_dictionaryValueForKey:@"message"]; NSString *objectName = [content ks_stringValueForKey:@"objectName"]; NSString *className = [self getMsgClassName:objectName]; __block TXLiveMessageModel *message = [[NSClassFromString(className) alloc] init]; message.messageId = [dic ks_stringValueForKey:@"msgID"]; message.groupId = [dic ks_stringValueForKey:@"groupID"]; [message evaluateSource:content]; if ([message.groupId isEqualToString:self.roomId]) { __weak typeof(&*self) __blockSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ if ([message isMemberOfClass:[TXLiveMessageLike class]]) { TXLiveMessageLike *likeMessage = (TXLiveMessageLike *)message; NSLog(@"receive like count ------- %d",likeMessage.counts); return; } else if ([message isMemberOfClass:[TXLiveMessageLikeCount class]]) { return; } else if ([message isMemberOfClass:[TXLiveMessageWelcome class]]) { TXLiveMessageWelcome *welcomeMessage = (TXLiveMessageWelcome *)message; // 过滤自己发送的欢迎消息 if ([welcomeMessage.userInfo.sendUserId isEqualToString:UserDefault(IM_USERID)]) { return; } if ([welcomeMessage.userInfo.sendUserId isEqual:__blockSelf.createrId]) { // 如果是老师 重置连麦申请状态 [__blockSelf resetMicWaitToNomal]; __blockSelf.isCreateInRoom = YES; } NSString *userName = [welcomeMessage.userInfo.sendUserName stringByAppendingString:@""]; NSString *contentMsg = [NSString stringWithFormat:@"%@ 进入直播间",userName]; [__blockSelf showAnimationView:YES showMessag:contentMsg]; return; } // 用户退出消息 else if ([message isMemberOfClass:[TXLiveMessageLeave class]]) { // TXLiveMessageLeave *userLeaveMsg = (TXLiveMessageLeave *)message; // if ([userLeaveMsg.userInfo.sendUserId isEqualToString:__blockSelf.createrId]) { // __blockSelf.isCreateInRoom = NO; // // 下麦 // if (__blockSelf.micStatus == MICSTATUS_CONNECTING) { // [__blockSelf downSeatAction]; // } // } return; } // 用户退出回调 else if ([message isMemberOfClass:[TXLiveMessageUserQuit class]]) { // TXLiveMessageUserQuit *userLeaveMsg = (TXLiveMessageUserQuit *)message; // if ([userLeaveMsg.targetId isEqualToString:__blockSelf.createrId]) { // __blockSelf.isCreateInRoom = NO; // // 下麦 // if (__blockSelf.micStatus == MICSTATUS_CONNECTING) { // [__blockSelf downSeatAction]; // } // } return; } else if ([message isMemberOfClass:[TXLiveMessageMemberUp class]]) { return; } else if ([message isMemberOfClass:[TXLiveMessageStatSync class]]) { // 数据同步 不取点赞数 return; } else if ([message isMemberOfClass:[TXLiveMessageKickOut class]]) { TXLiveMessageKickOut *kickMessage = (TXLiveMessageKickOut *)message; NSString *currentUserId = UserDefault(IM_USERID); if ([kickMessage.targetId isEqualToString:currentUserId]) { __blockSelf.micStatus = MICSTATUS_NOMAL; [__blockSelf kickOutLiveRoom]; } return; } // 禁言控制 else if ([message isMemberOfClass:[TXLiveMessageChatBan class]]) { TXLiveMessageChatBan *banMessage = (TXLiveMessageChatBan *)message; if (banMessage.chatBan) { // 禁止发言 __blockSelf.enableChat = NO; } else { // 开启发言 __blockSelf.enableChat = YES; } [__blockSelf insertMessage:banMessage]; } // 连麦控制消息 else if ([message isMemberOfClass:[TXLiveMessageSeatControl class]]) { TXLiveMessageSeatControl *seatControlMessage = (TXLiveMessageSeatControl *)message; if (seatControlMessage.seatBan) { // 禁止连麦 __blockSelf.enableSeat = NO; // 未连麦情况下才重置 if (__blockSelf.micStatus != MICSTATUS_CONNECTING) { // 如果在申请 发送取消申请消息 if (__blockSelf.micStatus == MICSTATUS_WAITING) { [__blockSelf sendSeatMessageApply:NO]; } __blockSelf.micStatus = MICSTATUS_NOMAL; } [__blockSelf hideApplyView]; [__blockSelf hideApplyingView]; } else { // 开启连麦 __blockSelf.enableSeat = YES; } [__blockSelf insertMessage:seatControlMessage]; } else if ([message isMemberOfClass:[TXLiveMessageSeatApply class]]) { // 只处理主讲人发起的连麦邀请或取消操作 TXLiveMessageSeatApply *seatApplyMessage = (TXLiveMessageSeatApply *)message; NSLog(@"----- RCChatroomSeatApply type:%ld audienceName:%@ ",seatApplyMessage.type,seatApplyMessage.audienceName); // 只处理和自己相关的消息 if (![seatApplyMessage.audienceId isEqualToString:UserDefault(IM_USERID)]) { // 刷新麦序 return; } if (seatApplyMessage.type == SEATHANDLE_INVITE) { // 主讲人发起申请 NSString *titles = [NSString stringWithFormat:@"%@邀请您连麦",[NSString returnNoNullStringWithString:seatApplyMessage.teacherName]]; [__blockSelf showInviteAlertMessage:titles]; } else if (seatApplyMessage.type == SEATHANDLE_DISINVITE) { // 主讲人撤回申请 [LOADING_MANAGER MBShowAUTOHidingInWindow:@"主讲人撤回了连麦申请"]; [__blockSelf closeAlertView]; if (__blockSelf.micStatus == MICSTATUS_CONNECTING) { [__blockSelf downSeatAction]; } } else if (seatApplyMessage.type == SEATHANDLE_KICKSEAT) { // 主讲人断开连麦 [__blockSelf kickSeatWithMessage:@"主讲人断开连麦"]; } [__blockSelf hideApplyingView]; [__blockSelf insertMessage:seatApplyMessage]; } else if ([message isMemberOfClass:[TXLiveMessageSeatResponse class]]) { // 只处理主讲人的回应 TXLiveMessageSeatResponse *seatResponseMessage = (TXLiveMessageSeatResponse *)message; NSLog(@"----- RCChatroomSeatResponse type:%ld audienceName:%@ ",seatResponseMessage.type,seatResponseMessage.audienceName); if (![seatResponseMessage.audienceId isEqualToString:UserDefault(IM_USERID)]) { // 只处理和自己相关的消息 return; } if (seatResponseMessage.type == SEATRESPONSE_TEACHERAPPROVE && __blockSelf.micStatus == MICSTATUS_WAITING) { // [__blockSelf hideApplyingView]; [__blockSelf connectHostWithStatus:YES]; [__blockSelf insertMessage:seatResponseMessage]; } else if (seatResponseMessage.type == SEATRESPONSE_TEACHERREJECT) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"主讲人拒绝了您的连麦申请"]; // 处理连麦状态 [__blockSelf connectHostWithStatus:NO]; [__blockSelf insertMessage:seatResponseMessage]; } } else if ([message isMemberOfClass:[TXLiveMessageDownSeat class]]) { // 成员主动下麦消息 return; } else if ([message isMemberOfClass:[TXLiveMessageClose class]]) { // 直播间已关闭 [LOADING_MANAGER MBShowAUTOHidingInWindow:@"直播已结束"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 显示直播间关闭状态 [__blockSelf closeAlertView]; [__blockSelf displayCloseView]; [__blockSelf hideApplyingView]; }); return; } else if ([message isMemberOfClass:[TXLiveTextMessage class]]) { TXLiveTextMessage *textMessage = (TXLiveTextMessage *)message; [__blockSelf insertMessage:textMessage]; } else if ([message isMemberOfClass:[TXLiveMessageCardMessage class]]) { TXLiveMessageCardMessage *cardMessage = (TXLiveMessageCardMessage *)message; [__blockSelf sendCardMessage:cardMessage.goodsContent]; } else if ([message isMemberOfClass:[TXLiveMessagePauseLive class]]) { // 主讲人暂停直播 // __blockSelf.isPauseLive = YES; // if (__blockSelf.micStatus == MICSTATUS_WAITING) { // [__blockSelf sendSeatMessageApply:NO]; // } // [__blockSelf resetNetTips]; // [__blockSelf resetConnectionStatus]; // [__blockSelf closeAlertView]; // [__blockSelf hideApplyingView]; // [__blockSelf changeEmptyStatusView]; // [__blockSelf closePlayStatus]; return; } else if ([message isMemberOfClass:[TXLiveMessageOpenLive class]]) { // 开始直播 // __blockSelf.isPauseLive = NO; // __blockSelf.isCreateInRoom = YES; // [__blockSelf resetNetTips]; // [__blockSelf hideEmptyView]; // [__blockSelf startPlay]; return; } else if ([message isMemberOfClass:[TXLiveMessageShopRush class]]) { // TXLiveMessageShopRush *rushMsg = (TXLiveMessageShopRush *)message; NSString *contentMsg = [NSString stringWithFormat:@"%@ 正在抢购",[NSString returnNoNullStringWithString:rushMsg.userInfo.sendUserName]]; [__blockSelf showAnimationView:NO showMessag:contentMsg]; } else if ([message isMemberOfClass:[TXLiveMessageBlockUser class]]) { // 黑名单 TXLiveMessageBlockUser *blockMessage = (TXLiveMessageBlockUser *)message; if ([blockMessage.userId isEqualToString:UserDefault(IM_USERID)]) { __blockSelf.blacklistFlag = YES; [__blockSelf resetConnectionStatus]; } } else if ([message isMemberOfClass:[TXLiveMessageUnBlockUser class]]) { TXLiveMessageUnBlockUser *unBlockMessage = (TXLiveMessageUnBlockUser *)message; if ([unBlockMessage.userId isEqualToString:UserDefault(IM_USERID)]) { __blockSelf.blacklistFlag = NO; } } else if ([message isMemberOfClass:[TXLiveMessageDownSeatAll class]]) { // 所有麦上用户下麦 TXLiveMessageDownSeatAll *downSeatMessage = (TXLiveMessageDownSeatAll *)message; if (__blockSelf.micStatus == MICSTATUS_CONNECTING) { [__blockSelf kickSeatWithMessage:@"主讲人断开连麦"]; [__blockSelf insertMessage:downSeatMessage]; } } else if ([message isMemberOfClass:[TXLiveMessageRejectAllSeat class]]) { // 拒绝所有连麦申请 TXLiveMessageRejectAllSeat *rejectAllMessage = (TXLiveMessageRejectAllSeat *)message; if (__blockSelf.micStatus == MICSTATUS_WAITING) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"主讲人拒绝了您的连麦申请"]; // 处理连麦状态 __blockSelf.micStatus = MICSTATUS_NOMAL; [__blockSelf insertMessage:rejectAllMessage]; [__blockSelf hideApplyingView]; } } else if ([message isMemberOfClass:[TXControlMemberMic class]]) { // 控制学生麦克风 TXControlMemberMic *micCtrlMsg = (TXControlMemberMic *)message; // 如果是控制当前学生的mic if ([micCtrlMsg.userId isEqualToString:UserDefault(IM_USERID)]) { [__blockSelf changeCurrentMicStatus:micCtrlMsg.muteMic]; } } else if ([message isMemberOfClass:[TXLiveForceKickMsg class]]) { TXLiveForceKickMsg *changeMsg = (TXLiveForceKickMsg *)message; if (changeMsg.targetIds.count == 0 || changeMsg.targetIds == nil) { // 如果是全员 [__blockSelf courseChangeTips:changeMsg]; } else { for (NSString *userId in changeMsg.targetIds) { // 如果部分学员中包含自己 if ([userId isEqualToString:UserDefault(IM_USERID)]) { [__blockSelf courseChangeTips:changeMsg]; break; } } } } }); } } - (void)courseChangeTips:(TXLiveForceKickMsg *)msg { // 显示直播间关闭状态 [self closeAlertView]; [self displayCloseView]; [self hideApplyingView]; // 退出房间 NSString *tipsMsg = [NSString isEmptyString:msg.reason] ? @"当前课程已调整,请退出直播间" : msg.reason; MJWeakSelf; self.closeAlert = [LiveRoomConfirmAlert liveroomAlertWithTitle:tipsMsg sureTitle:@"确定" inView:self.view confirm:^{ [weakSelf exitRoom]; }]; } - (void)changeCurrentMicStatus:(BOOL)isMute { if (isMute == !self.micEnable) { // 如果麦克风状态和当前状态一致,剔除 return; } if (_trtcCloud && self.isAudience == NO) { self.micEnable = !self.micEnable; [self.trtcCloud muteLocalAudio:!self.micEnable]; NSString *desc = @""; if (self.micEnable) { desc = @"主讲人已将您的麦克风开启"; } else { desc = @"主讲人已将您的麦克风关闭"; } [LOADING_MANAGER MBShowAUTOHidingInWindow:desc]; [self syncMicStatus:!self.micEnable]; } } - (void)didReceiveGroupMessage:(NSNotification *)notification { NSDictionary *dic = notification.object; /// __block TXLiveMessageModel *liveMessage NSString *groupId = [dic ks_stringValueForKey:@"groupID"]; NSDictionary *content = [dic ks_dictionaryValueForKey:@"message"]; if ([groupId isEqualToString:self.roomId]) { [self evaluateGroupInfo:content]; } } - (void)evaluateGroupInfo:(NSDictionary *)groupInfo { // 主播在房间 if ([[groupInfo ks_stringValueForKey:@"LIVE_STATUS"] isEqualToString:@"ON"]) { // 直播中 self.isPauseLive = NO; if (self.isAudience) { [self startPlay]; } } else if ([[groupInfo ks_stringValueForKey:@"LIVE_STATUS"] isEqualToString:@"OFF"]) { // 暂停直播 self.isPauseLive = YES; [self closePlayStatus]; } // 摄像头状态 if ([[groupInfo ks_stringValueForKey:@"ANCHOR_CAMERA"] isEqualToString:@"OFF"]) { self.isCreaterVideoEnable = NO; } else { self.isCreaterVideoEnable = YES; } // 禁言状态 if ([[groupInfo ks_stringValueForKey:@"GLOBAL_BAN"] isEqualToString:@"ON"]) { self.enableChat = NO; } else { self.enableChat = YES; } // 在线人数 if ([[groupInfo allKeys] containsObject:@"MEMBER_ONLINE"]) { self.totalCount = [groupInfo ks_integerValueForKey:@"MEMBER_ONLINE"]; [self countMemberCount]; } [self changeEmptyStatusView]; // 主播全员闭麦状态 if ([[groupInfo allKeys] containsObject:@"ANCHOR_MIC"]) { if ([[groupInfo ks_stringValueForKey:@"ANCHOR_MIC"] isEqualToString:@"ON"]) { // 禁麦 if (self.muteRoomMic == NO) { self.muteRoomMic = YES; } } else { if (self.muteRoomMic == YES) { self.muteRoomMic = NO; } } } else { if (self.muteRoomMic == YES) { self.muteRoomMic = NO; } } // 禁止连麦 if ([[groupInfo allKeys] containsObject:@"BAN_LINK_MIC"]) { if ([[groupInfo ks_stringValueForKey:@"BAN_LINK_MIC"] isEqualToString:@"ON"]) { // 禁止 self.enableSeat = NO; } else { self.enableSeat = YES; } } else { self.enableSeat = YES; } } - (void)setMuteRoomMic:(BOOL)muteRoomMic { _muteRoomMic = muteRoomMic; [self changeCurrentMicStatus:muteRoomMic]; } // 群属性变更信息 - (void)didReceiveGroupCountMessage:(NSNotification *)notification { NSDictionary *dic = notification.object; /// __block TXLiveMessageModel *liveMessage NSString *groupId = [dic ks_stringValueForKey:@"groupID"]; NSDictionary *content = [dic ks_dictionaryValueForKey:@"message"]; if ([groupId isEqualToString:self.roomId]) { [self evaluateGroupCount:content]; } } - (void)evaluateGroupCount:(NSDictionary *)groupCount { // 点赞数据 if ([[groupCount allKeys] containsObject:@"LIKES"]) { self.likeCount = [groupCount ks_integerValueForKey:@"LIKES"]; [self countLikeMessageCount]; } } - (void)closePlayStatus { if (self.micStatus == MICSTATUS_WAITING) { [self sendSeatMessageApply:NO]; } [self resetNetTips]; [self resetConnectionStatus]; [self closeAlertView]; [self hideApplyingView]; if (self.isAudience) { [self stopPlay]; } else { if (_trtcCloud) { [self.trtcCloud stopAllRemoteView]; [self.trtcCloud stopLocalAudio]; [self.trtcCloud exitRoom]; } } } - (void)kickOutLiveRoom { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"您已被踢出直播间"]; @weakObj(self); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @strongObj(self); [self quitRoom]; }); } #pragma mark ------ message operation - (void)resetConnectionStatus { if (self.micStatus == MICSTATUS_CONNECTING) { @weakObj(self); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @strongObj(self); [self sendDownSeatMsg]; [self connectHostWithStatus:NO]; }); } else { self.micStatus = MICSTATUS_NOMAL; [self removeSeatContainer]; } } - (void)resetMicWaitToNomal { if (self.micStatus == MICSTATUS_WAITING) { self.micStatus = MICSTATUS_NOMAL; } } - (void)tipsTeacherLeaveRoom { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"主讲人已离开房间"]; } - (void)hideApplyView { if (self.applyView.isShow) { [self.applyView hideView]; } } - (void)hideApplyingView { if (self.applyingView.isShow) { [self.applyingView hideView]; } } - (void)closeAlertView { if (self.alertView && self.alertView.isShow) { if (self.alertView.isQuitAlert == NO) { [self.alertView removeFromSuperview]; self.alertView = nil; } } } - (void)displayCloseView { self.isCloseRoom = YES; [self hideTipsView]; // 移除连麦视图 [self removeSeatContainer]; if (self.micStatus != MICSTATUS_NOMAL) { self.micStatus = MICSTATUS_NOMAL; if (_trtcCloud) { [self.trtcCloud stopLocalAudio]; [self.trtcCloud exitRoom]; } } [self stopPlay]; self.videoView.hidden = YES; self.endView.hidden = NO; [self setDefaultBottomViewStatus]; [[NSNotificationCenter defaultCenter] postNotificationName:@"LiveroomClose" object:nil]; } - (void)sendCardMessage:(NSArray *)content { // if (_webContainer) { // NSMutableDictionary *sendParm = [NSMutableDictionary dictionary]; // [sendParm setValue:@"cardChange" forKey:@"api"]; // [sendParm setValue:content forKey:@"content"]; // [self.webContainer postMessage:sendParm]; // } } - (void)showInviteAlertMessage:(NSString *)tipsMessage { if (self.alertView && self.alertView.isShow) { [self.alertView removeFromSuperview]; self.alertView = nil; } MJWeakSelf; self.alertView = [LiveAlertView shareInstance]; [self.alertView alertWithTitle:@"连麦邀请" desc:tipsMessage leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{ weakSelf.micStatus = MICSTATUS_NOMAL; [weakSelf sendSeatResponseMessage:NO]; } confirm:^{ [weakSelf sendSeatResponseMessage:YES]; }]; } - (void)sendSeatResponseMessage:(BOOL)isApprove { SEATRESPONSE type = isApprove ? SEATRESPONSE_AUDIENCEAPPROVE : SEATRESPONSE_AUDIENCEREJECT; TXLiveMessageSeatResponse *responseMessage = [[TXLiveMessageSeatResponse alloc] init]; responseMessage.userInfo = [self getCurrentUser]; responseMessage.type = type; responseMessage.audienceId = UserDefault(IM_USERID); responseMessage.audienceName = UserDefault(NicknameKey); MJWeakSelf; [self sendCustomMessage:responseMessage priority:V2TIM_PRIORITY_HIGH displayMessage:YES callback:^(BOOL success) { if (success) { if (isApprove) { [weakSelf connectHostWithStatus:YES]; } } }]; } - (void)kickSeatWithMessage:(NSString *)message { [LOADING_MANAGER MBShowAUTOHidingInWindow:message]; [self sendDownSeatMsg]; [self connectHostWithStatus:NO]; } - (void)insertMessage:(TXLiveMessageModel *)liveMessage { [self appendAndDisplayMessage:liveMessage]; } #pragma mark table data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.conversationDataRepository.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TXLiveMessageModel *model = [self.conversationDataRepository objectAtIndex:indexPath.row]; TXLiveChatListCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TXLiveChatListCell"]; if (!cell) { cell = [[TXLiveChatListCell appearance] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"TXLiveChatListCell"]; } if ([model isMemberOfClass:[TXConstMessage class]] || [model isMemberOfClass:[TXLiveTextMessage class]] || [model isMemberOfClass:[TXLiveMessageWelcome class]] || [model isMemberOfClass:[TXLiveMessageLike class]] || [model isMemberOfClass:[TXLiveMessageSeatControl class]] ||[model isMemberOfClass:[TXLiveMessageChatBan class]] || [model isMemberOfClass:[TXLiveMessageSeatApply class]] || [model isMemberOfClass:[TXLiveMessageSeatResponse class]] || [model isMemberOfClass:[TXLiveMessageShopRush class]] || [model isMemberOfClass:[TXLiveMessageDownSeatAll class]] || [model isMemberOfClass:[TXLiveMessageRejectAllSeat class]] ){ [cell setDataModel:model createrId:self.createrId]; } return cell; } #pragma mark ----- lazying - (TRTCCloud *)trtcCloud { if (!_trtcCloud) { _trtcCloud = [TRTCCloud sharedInstance]; [_trtcCloud addDelegate:self]; } return _trtcCloud; } - (V2TXLivePlayer *)livePlayer { if (!_livePlayer) { _livePlayer = [[V2TXLivePlayer alloc] init]; [_livePlayer enableReceiveSeiMessage:YES payloadType:243]; [_livePlayer setObserver:self]; } return _livePlayer; } - (UIView *)videoContainerView { if (!_videoContainerView) { _videoContainerView = [[UIView alloc] initWithFrame:CGRectZero]; _videoContainerView.backgroundColor = [UIColor clearColor]; } return _videoContainerView; } - (UIView *)videoView { if (!_videoView) { _videoView = [[UIView alloc] initWithFrame:CGRectZero]; _videoView.backgroundColor = HexRGB(0x1a1a1a); } return _videoView; } - (TXLiveVideoView *)videoRenderView { if (!_videoRenderView) { _videoRenderView = [[TXLiveVideoView alloc] initWithFrame:CGRectZero]; } return _videoRenderView; } - (LiveRoomHeadView *)headView { if (!_headView) { _headView = [LiveRoomHeadView shareInstance]; MJWeakSelf; [_headView quitAction:^{ [weakSelf showQuitAlert]; }]; } return _headView; } - (void)showQuitAlert { if (self.isCloseRoom) { // 如果直播结束 直接点击退出房间 [self quitRoom]; } else if (self.micStatus == MICSTATUS_CONNECTING) { [self quitAction]; } else { MJWeakSelf; self.alertView = [LiveAlertView shareInstance]; self.alertView.isQuitAlert = YES; [self.alertView alertWithTitle:@"确定要退出直播间吗?" desc:@"退出后如再次观看可重新进入~" leftButtonTitle:@"取消" rightTitle:@"确认" inView:self.view cancel:^{ } confirm:^{ [weakSelf quitAction]; }]; } } - (void)quitRoom { KSDragWindowManager *manager = [KSDragWindowManager sharedManager]; if (manager.hasShowWindow) { [manager resignDragWindow]; } if (self.landScape) { [self changeOrientation:!self.landScape]; @weakObj(self); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @strongObj(self); // 退出房间 [self exitRoom]; }); } else { // 退出房间 [self exitRoom]; } } // 顶部退出房间 - (void)quitAction { if (self.micStatus == MICSTATUS_CONNECTING) { MJWeakSelf; self.alertView = [LiveAlertView shareInstance]; [self.alertView alertWithTitle:@"提示" desc:@"连麦中,是否退出房间?" leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{ } confirm:^{ [weakSelf quitRoom]; }]; } else { [self quitRoom]; } } - (void)exitRoom { if (self.isAudience) { [self stopPlay]; } else { if (_trtcCloud) { [self.trtcCloud stopLocalAudio]; [self.trtcCloud exitRoom]; self.trtcCloud = nil; } } TXLiveMessageLeave *leaveMsg = [[TXLiveMessageLeave alloc] init]; leaveMsg.userInfo = [self getCurrentUser]; MJWeakSelf; [self sendCustomMessage:leaveMsg priority:V2TIM_PRIORITY_NORMAL displayMessage:NO callback:^(BOOL success) { // quit 接口 [weakSelf quitNotiferService]; [weakSelf quitChatRoom]; [weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil]; }]; } - (void)quitChatRoom { [[V2TIMManager sharedInstance] quitGroup:self.roomId succ:^{ } fail:^(int code, NSString *desc) { }]; } - (LiveRoomBottomView *)bottomView { if (!_bottomView) { _bottomView = [LiveRoomBottomView shareInstance]; MJWeakSelf; [_bottomView bottomClickAction:^(LIVEROOMACTION action) { [weakSelf bottomViewAction:action]; }]; } return _bottomView; } - (void)bottomViewAction:(LIVEROOMACTION)action { if (self.isCloseRoom) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"直播已结束"]; return; } if (action == LIVEROOMACTION_CHAT) { if (self.blacklistFlag == YES) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"您已被管理员禁言"]; return; } if (self.enableChat) { // 判断是否全员禁言 [_inputBar setHidden:NO]; [_inputBar setInputBarStatus:KSBottomBarStatusKeyboard]; } else { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"管理员关闭聊天"]; } } else if (action == LIVEROOMACTION_SEAT) { // 连麦按钮 // 判断是否是连麦状态 if (self.micStatus == MICSTATUS_CONNECTING) { // 连麦中 // 是否取消连麦 MJWeakSelf; self.alertView = [LiveAlertView shareInstance]; [self.alertView alertWithTitle:@"连麦中,确认取消连麦吗?" desc:@"断开后如想再次连麦可重新发起" leftButtonTitle:@"取消" rightTitle:@"确定关闭" inView:self.view cancel:^{ } confirm:^{ [weakSelf downSeatAction]; }]; } else if (self.micStatus == MICSTATUS_WAITING) { // 连麦申请中 // 弹窗取消申请 [self displayApplyingView]; } else { // 禁止聊天可以连麦(测试确认) if (self.blacklistFlag == YES) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"您已被管理员禁言"]; return; } if (self.isPauseLive == YES) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"老师暂停中,暂时无法接受申请"]; return; } if (self.enableSeat) { [self displaySeatApplyView]; } else { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"管理员关闭连麦申请"]; } } } else if (action == LIVEROOMACTION_LIKE) { // 点赞 [self praiseBtnPressed]; } else if (action == LIVEROOMACTION_SHOP) { // shop [self showCartAlert]; } else if (action == LIVEROOMACTION_MUTE) { [self muteVoiceAction]; } } - (void)showCartAlert { [self.cardView showViewInView:self.view]; } - (void)downSeatAction { [self sendDownSeatMsg]; [self connectHostWithStatus:NO]; } - (void)sendDownSeatMsg { TXLiveMessageDownSeat *downSeatMsg = [[TXLiveMessageDownSeat alloc] init]; TXLiveUser *mineInfo = [self getCurrentUser]; downSeatMsg.userInfo = mineInfo; downSeatMsg.audienceId = mineInfo.sendUserId; downSeatMsg.audienceName = mineInfo.sendUserName; [self sendCustomMessage:downSeatMsg priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) { }]; } - (KSShopCardView *)cardView { if (!_cardView) { _cardView = [KSShopCardView shareInstance]; MJWeakSelf; [_cardView clickAction:^(CART_TYPE type) { [weakSelf displayTeacherInfo:type]; }]; } return _cardView; } // tabs= vip | VIP定制课 practice | 趣纠课 live | 直播课 video | 视频课 music | 乐谱 跳转到对应tab页 - (void)displayTeacherInfo:(CART_TYPE)type { [self sendRushMessage]; if (self.isPauseLive == NO) { _hasShowSuspendView = YES; KSDragWindowManager *manager = [KSDragWindowManager sharedManager]; BOOL isPhoneLive = self.needSwitchButton ? NO : YES; [manager initDragWindowWithPhoneLive:isPhoneLive]; manager.rootVC = (CustomNavViewController *)self.navigationController; [self.videoRenderView removeFromSuperview]; [manager.dragWindow.containerView addSubview:self.videoRenderView]; [self.videoRenderView mas_updateConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(manager.dragWindow.containerView); }]; [manager.dragWindow bringSubviewToFront:manager.dragWindow.cancelButton]; } NSString *tabs = @""; switch (type) { case CART_TYPE_NONE: { tabs = @""; } break; case CART_TYPE_VIP: { tabs = @"vip"; } break; case CART_TYPE_ACCOMPANY: { tabs = @"practice"; } break; case CART_TYPE_LIVE: { tabs = @"live"; } break; case CART_TYPE_VIDEO: { tabs = @"video"; } break; case CART_TYPE_MUSIC: { tabs = @"music"; } break; default: break; } NSString *url = @""; if ([NSString isEmptyString:tabs]) { url = [NSString stringWithFormat:@"%@%@%@", WEBHOST,@"/#/teacherHome?teacherId=",self.createrUserId]; } else { url = [NSString stringWithFormat:@"%@%@%@&tabs=%@", WEBHOST,@"/#/teacherHome?teacherId=",self.createrUserId,tabs]; } KSLiveWebViewController *ctrl = [[KSLiveWebViewController alloc] init]; ctrl.url = url; [self.navigationController pushViewController:ctrl animated:YES]; } - (void)sendRushMessage { TXLiveMessageShopRush *rushMessage = [[TXLiveMessageShopRush alloc] init]; rushMessage.userInfo = [self getCurrentUser]; [self sendCustomMessage:rushMessage priority:V2TIM_PRIORITY_DEFAULT displayMessage:NO callback:^(BOOL success) { }]; } - (void)toDetailView:(NSString *)linkUrl { [self sendRushMessage]; if (self.isPauseLive == NO) { _hasShowSuspendView = YES; KSDragWindowManager *manager = [KSDragWindowManager sharedManager]; BOOL isPhoneLive = self.needSwitchButton ? NO : YES; [manager initDragWindowWithPhoneLive:isPhoneLive]; manager.rootVC = (CustomNavViewController *)self.navigationController; [self.videoRenderView removeFromSuperview]; [manager.dragWindow.containerView addSubview:self.videoRenderView]; [self.videoRenderView mas_updateConstraints:^(MASConstraintMaker *make) { make.left.right.top.bottom.mas_equalTo(manager.dragWindow.containerView); }]; [manager.dragWindow bringSubviewToFront:manager.dragWindow.cancelButton]; } KSLiveWebViewController *ctrl = [[KSLiveWebViewController alloc] init]; ctrl.url = linkUrl; [self.navigationController pushViewController:ctrl animated:YES]; } - (void)muteVoiceAction { if (self.bottomView.isMute) { if ([self.livePlayer isPlaying]) { [self.livePlayer setPlayoutVolume:0]; } if (_trtcCloud) { [self.trtcCloud muteAllRemoteAudio:YES]; } } else { if ([self.livePlayer isPlaying]) { [self.livePlayer setPlayoutVolume:100]; } if (_trtcCloud) { [self.trtcCloud muteAllRemoteAudio:NO]; } } } - (KSLiveEmptyView *)emptyView { if (!_emptyView) { _emptyView = [[KSLiveEmptyView alloc] init]; BOOL isPhoneLive = self.needSwitchButton ? NO : YES; [_emptyView dispPlayViewIsPhoneLive:isPhoneLive]; } return _emptyView; } - (KSLiveEndView *)endView { if (!_endView) { _endView = [[KSLiveEndView alloc] init]; _endView.backgroundColor = HexRGB(0x25292e); BOOL isPhoneLive = self.needSwitchButton ? NO : YES; [_endView dispPlayViewIsPhoneLive:isPhoneLive]; } return _endView; } - (KSLiveBadNetView *)netBadView { if (!_netBadView) { _netBadView = [[KSLiveBadNetView alloc] init]; BOOL isPhoneLive = self.needSwitchButton ? NO : YES; [_netBadView dispPlayViewIsPhoneLive:isPhoneLive]; MJWeakSelf; [_netBadView configViewWithImageName:@"liveVideo_close" desc:@"视频加载失败" callback:^{ [weakSelf resetAllConnection]; }]; } return _netBadView; } - (void)resetNetTips { [self hideTipsView]; } - (void)showBadNetTips { dispatch_main_async_safe(^{ if (self.isCloseRoom == NO) { if (self.netBadView.hidden == YES) { self.netBadView.hidden = NO; self.videoView.hidden = YES; } } }); } - (void)hideTipsView { dispatch_main_async_safe(^{ if (self.netBadView.hidden == NO) { self.netBadView.hidden = YES; self.videoView.hidden = NO; } }) } // 黑名单 - (void)setBlacklistFlag:(BOOL)blacklistFlag { _blacklistFlag = blacklistFlag; [self refreshBottomChatStatus]; } - (void)refreshBottomChatStatus { NSString *tipsTitle = self.blacklistFlag ? @"您已被管理员禁言" : @"快来互动吧!"; self.bottomView.tipsLabel.text = tipsTitle; self.bottomView.tipsLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; } - (void)resetAllConnection { [self resetNetTips]; // 清理视图 [self hideEmptyView]; [self removeSeatContainer]; // 立刻房间后重新加入房间 [self startPlay]; } - (LOTAnimationView *)animationView { if (!_animationView) { _animationView = [LOTAnimationView animationWithFilePath:[[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"LiveCard" ofType:@"bundle"]] pathForResource:@"cardAnimation" ofType:@"json"]]; _animationView.loopAnimation = YES; } return _animationView; } - (UIButton *)switchButton { if (!_switchButton) { _switchButton = [UIButton buttonWithType:UIButtonTypeCustom]; [_switchButton setImage:[UIImage imageNamed:@"live_switch"] forState:UIControlStateNormal]; [_switchButton addTarget:self action:@selector(transferToLandscape) forControlEvents:UIControlEventTouchUpInside]; } return _switchButton; } - (UIView *)messageContentView { if (!_messageContentView) { _messageContentView = [[UIView alloc] init]; [_messageContentView setBackgroundColor: [UIColor clearColor]]; } return _messageContentView; } - (UITableView *)conversationMessageTableView { if (!_conversationMessageTableView) { _conversationMessageTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; _conversationMessageTableView.backgroundColor = [UIColor clearColor]; _conversationMessageTableView.separatorStyle = UITableViewCellSeparatorStyleNone; _conversationMessageTableView.dataSource = self; _conversationMessageTableView.delegate = self; _conversationMessageTableView.rowHeight = UITableViewAutomaticDimension; _conversationMessageTableView.estimatedRowHeight = 40.0f; _conversationMessageTableView.showsVerticalScrollIndicator = NO; [_conversationMessageTableView registerClass:[TXLiveChatListCell class] forCellReuseIdentifier:@"TXLiveChatListCell"]; } return _conversationMessageTableView; } - (NSMutableArray *)conversationDataRepository { if (!_conversationDataRepository) { _conversationDataRepository = [NSMutableArray array]; } return _conversationDataRepository; } - (TXSeatContainerView *)seatContainer { if (!_seatContainer) { _seatContainer = [[TXSeatContainerView alloc] init]; MJWeakSelf; [_seatContainer seatActionCallback:^(NSString * _Nonnull userId, BOOL muteMic) { [weakSelf muteUserAction:userId muteStatus:muteMic]; }]; } return _seatContainer; } - (void)muteUserAction:(NSString *)userId muteStatus:(BOOL)muteMic { [self micButtonAction:muteMic]; TXMicStatusSync *ctrlMsg = [[TXMicStatusSync alloc] init]; ctrlMsg.userInfo = [self getCurrentUser]; ctrlMsg.userId = userId; ctrlMsg.muteMic = muteMic; [self sendCustomMessage:ctrlMsg priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) { }]; } - (KSChatInputBarControl *)inputBar { if (!_inputBar) { _inputBar = [[KSChatInputBarControl alloc] initWithStatus:KSBottomBarStatusDefault]; [_inputBar setDelegate:self]; } return _inputBar; } - (void)setMicStatus:(MICSTATUS)micStatus { _micStatus = micStatus; if (self.bottomView) { switch (micStatus) { case MICSTATUS_NOMAL: { [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_image"] forState:UIControlStateNormal]; } break; case MICSTATUS_WAITING: { [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_connecting"] forState:UIControlStateNormal]; } break; case MICSTATUS_CONNECTING: { [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_connected"] forState:UIControlStateNormal]; } break; default: break; } } } #pragma mark ----- 人数和点赞数统计 - (void)countLikeMessageCount { NSInteger count = self.likeCount; NSString *desc = @""; if (count > 10000) { count = count / 10000; desc = [NSString stringWithFormat:@"本场点赞%zd万+",count]; } else { desc = [NSString stringWithFormat:@"本场点赞%zd",count]; } self.headView.likeCount.text = desc; } - (void)countMemberCount { NSInteger count = self.totalCount; NSString *desc = @""; if (count > 10000) { count = count / 10000; desc = [NSString stringWithFormat:@"%zd万+人",count]; } else { desc = [NSString stringWithFormat:@"%zd人",count]; } self.headView.roomMemberCount.text = desc; } /** * 判断消息是否在collectionView的底部 * * @return 是否在底部 */ - (BOOL)isAtTheBottomOfTableView { if (self.conversationMessageTableView.contentSize.height <= self.conversationMessageTableView.frame.size.height) { return YES; } if(self.conversationMessageTableView.contentOffset.y +200 >= (self.conversationMessageTableView.contentSize.height - self.conversationMessageTableView.frame.size.height)) { return YES; }else{ return NO; } } /** * 消息滚动到底部 * * @param animated 是否开启动画效果 */ - (void)scrollToBottomAnimated:(BOOL)animated { if ([self.conversationMessageTableView numberOfSections] == 0) { return; } NSUInteger finalRow = MAX(0, [self.conversationMessageTableView numberOfRowsInSection:0] - 1); if (0 == finalRow) { return; } NSIndexPath *finalIndexPath = [NSIndexPath indexPathForItem:finalRow inSection:0]; [self.conversationMessageTableView scrollToRowAtIndexPath:finalIndexPath atScrollPosition:UITableViewScrollPositionTop animated:animated]; } #pragma mark ---- dealloc - (void)dealloc { NSLog(@"---- live room dealloc"); [[NSNotificationCenter defaultCenter] removeObserver:self]; if (_livePlayer) { [self.livePlayer stopPlay]; _livePlayer = nil; } if (_trtcCloud) { [self.trtcCloud stopAllRemoteView]; [self.trtcCloud stopLocalAudio]; [self.trtcCloud exitRoom]; [self.trtcCloud removeDelegate:self]; _trtcCloud = nil; [TRTCCloud destroySharedInstance]; } } #pragma mark - KSChatInputBarControlDelegate // 根据inputBar 回调来修改页面布局 - (void)onInputBarControlContentSizeChanged:(CGRect)frame withAnimationDuration:(CGFloat)duration andAnimationCurve:(UIViewAnimationCurve)curve { CGRect originFrame = _inputBar.frame; __weak __typeof(&*self)weakSelf = self; // 目前只使用y值即可 -- 只修改messageContentView高度,让展示消息view和输入框随之移动 [UIView animateWithDuration:duration animations:^{ [UIView setAnimationCurve:curve]; [weakSelf.inputBar setFrame:CGRectMake(0, frame.origin.y - originFrame.size.height, originFrame.size.width, originFrame.size.height)]; [UIView commitAnimations]; }]; } // 发送消息 - (void)onTouchSendButton:(NSString *)text { if (self.blacklistFlag == YES) { [self setDefaultBottomViewStatus]; return; } NSDate *date = [NSDate date]; NSTimeInterval currentInterval = [date timeIntervalSince1970]; if (currentInterval - self.lastSendMsgTime < 3) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"您说话太快啦"]; return; } if (self.lastSendMsgTime == 0) { self.lastSendMsgTime = currentInterval; } if (text.length > 40) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"聊天消息需在40个字以内哦"]; return; } if (self.enableChat) { self.lastSendMsgTime = currentInterval; [self touristSendMessage:text]; } else { [self setDefaultBottomViewStatus]; } } - (void)touristSendMessage:(NSString *)text { TXLiveTextMessage *message = [[TXLiveTextMessage alloc] init]; message.userInfo = [self getCurrentUser]; message.text = text; MJWeakSelf; [self sendTextMessage:message priority:V2TIM_PRIORITY_NORMAL displayMessage:YES callback:^(BOOL success) { [weakSelf setDefaultBottomViewStatus]; }]; } - (TXLiveUser *)getCurrentUser { TXLiveUser *user = [[TXLiveUser alloc] init]; user.sendUserId = UserDefault(IM_USERID); user.sendUserName = UserDefault(NicknameKey); user.avatarUrl = UserDefault(AvatarUrlKey); return user; } /** 点赞 */ - (void)praiseBtnPressed { if (self.enableLike == NO) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"管理员已关闭点赞功能"]; return; } NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; @weakObj(self); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.21 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @strongObj(self); if ([[NSDate date] timeIntervalSince1970] - self.lastClickPraiseTime >= 0.2) { [self modifyLikeCount:clickPraiseBtnTimes]; clickPraiseBtnTimes = 0; } }); clickPraiseBtnTimes++; self.lastClickPraiseTime = currentTime; [self presentLikeMessageAnimation]; } - (void)modifyLikeCount:(NSInteger)count { [[V2TIMManager sharedInstance] increaseGroupCounter:self.roomId key:@"LIKES" value:count succ:^(NSDictionary *groupCounters) { } fail:^(int code, NSString *desc) { }]; } /** 赞动画 */ - (void)presentLikeMessageAnimation { LiveRoomLikeLayer *layer = [[LiveRoomLikeLayer alloc] initWithName:@"Like"]; [self.videoContainerView.layer addSublayer:layer]; CGRect tempFrame = [self.videoContainerView convertRect:self.bottomView.likeButton.frame fromView:self.bottomView]; CGFloat width = tempFrame.size.width; CGFloat height = tempFrame.size.height; CGFloat dx = CGRectGetMidX(tempFrame) - width *0.5; CGFloat dy = CGRectGetMidY(tempFrame) - 8 - height; layer.frame = CGRectMake(dx, dy, width, height); [layer startAnimation]; } - (void)displaySeatApplyView { [self.applyView showInView:self.view]; } - (void)displayApplyingView { [self.applyingView showInView:self.view]; } - (LiveApplyView *)applyView { if (!_applyView) { _applyView = [LiveApplyView shareInstance]; MJWeakSelf; [_applyView applySeatCallback:^{ [weakSelf applySeatAction]; }]; } return _applyView; } - (void)applySeatAction { [self sendSeatMessageApply:YES]; } - (void)sendSeatMessageApply:(BOOL)isApply { if (self.micStatus == MICSTATUS_CONNECTING) { [LOADING_MANAGER MBShowAUTOHidingInWindow:@"您已上麦"]; return; } SEATHANDLE type = isApply ? SEATHANDLE_APPLY : SEATHANDLE_CANCELAPPLY; TXLiveMessageSeatApply *applyMessage = [[TXLiveMessageSeatApply alloc] init]; applyMessage.type = type; applyMessage.audienceId = UserDefault(IM_USERID); applyMessage.audienceName = UserDefault(NicknameKey); applyMessage.audienceAvatar = UserDefault(AvatarUrlKey); applyMessage.teacherId = self.createrId; applyMessage.teacherName = self.createrName; applyMessage.userInfo = [self getCurrentUser]; MJWeakSelf; [self sendCustomMessage:applyMessage priority:V2TIM_PRIORITY_HIGH displayMessage:YES callback:^(BOOL success) { if (success) { weakSelf.micStatus = isApply ? MICSTATUS_WAITING : MICSTATUS_NOMAL; } }]; } - (LiveApplyingView *)applyingView { if (!_applyingView) { _applyingView = [LiveApplyingView shareInstance]; NSString *avatar = UserDefaultObjectForKey(AvatarUrlKey); [_applyingView.myAvatal sd_setImageWithURL:[NSURL URLWithString:[avatar getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:STUDENT_AVATAR]]; [_applyingView.teacherAvatal sd_setImageWithURL:[NSURL URLWithString:[self.createrAvatal getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:TEACHER_AVATAR]]; MJWeakSelf; [_applyingView cancleApplyCallback:^{ [weakSelf cancleApplyAction]; }]; } return _applyingView; } - (void)cancleApplyAction { MJWeakSelf; self.alertView = [LiveAlertView shareInstance]; [self.alertView alertWithTitle:@"连麦申请中,确认取消连麦申请吗?" desc:@"断开后如想再次连麦可重新发起" leftButtonTitle:@"取消" rightTitle:@"确定关闭" inView:self.view cancel:^{ } confirm:^{ [weakSelf.applyingView hideView]; [weakSelf sendSeatMessageApply:NO]; }]; } #pragma mark ---- sendMessage/showMessage // 文本消息 - (void)sendTextMessage:(TXLiveMessageModel *)messageContent priority:(V2TIMMessagePriority)priority displayMessage:(BOOL)displayMessage callback:(void(^)(BOOL success))callback { NSData *msgData = [messageContent encodeMessage]; if (msgData == nil) { return; } __weak typeof(&*self) __weakself = self; [TXLiveMessageCenter sendLiveChatMessage:msgData groupId:self.roomId priority:priority success:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (displayMessage) { [__weakself appendAndDisplayMessage:messageContent]; } [__weakself.inputBar clearInputView]; callback(YES); }); } failer:^(int code, NSString * _Nonnull msg) { dispatch_async(dispatch_get_main_queue(), ^{ [__weakself.inputBar clearInputView]; NSLog(@"发送失败,errorcode is: %d message: %@",code, msg); callback(NO); }); }]; } // 其他消息 - (void)sendCustomMessage:(TXLiveMessageModel *)messageContent priority:(V2TIMMessagePriority)priority displayMessage:(BOOL)displayMessage callback:(void(^)(BOOL success))callback { NSData *msgData = [messageContent encodeMessage]; if (msgData == nil) { return; } __weak typeof(&*self) __weakself = self; [TXLiveMessageCenter sendLiveChatMessage:msgData groupId:self.roomId priority:priority success:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (displayMessage) { [__weakself appendAndDisplayMessage:messageContent]; } callback(YES); }); } failer:^(int code, NSString * _Nonnull msg) { dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"发送失败,errorcode is: %d message: %@",code, msg); callback(NO); }); }]; } /** * 将消息加入本地数组 */ - (void)appendAndDisplayMessage:(TXLiveMessageModel *)liveMessage { if (!liveMessage) { return; } if ([self appendMessageModel:liveMessage]) { NSIndexPath *indexPath = [NSIndexPath indexPathForItem:self.conversationDataRepository.count - 1 inSection:0]; if ([self.conversationMessageTableView numberOfRowsInSection:0] != self.conversationDataRepository.count - 1) { return; } // view刷新 [self.conversationMessageTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone]; if ([self isAtTheBottomOfTableView] || self.isNeedScrollToButtom) { [self scrollToBottomAnimated:NO]; self.isNeedScrollToButtom=NO; } } return; } /** * 如果当前会话没有这个消息id,把消息加入本地数组 */ - (BOOL)appendMessageModel:(TXLiveMessageModel *)model { if (!model) { return NO; } //这里可以根据消息类型来决定是否显示,如果不希望显示直接return NO //数量不可能无限制的大,这里限制收到消息过多时,就对显示消息数量进行限制。 //用户可以手动下拉更多消息,查看更多历史消息。 if (self.conversationDataRepository.count>500) { [self.conversationDataRepository removeObjectAtIndex:0]; [self.conversationMessageTableView reloadData]; } [self.conversationDataRepository addObject:model]; return YES; } - (void)showAnimationView:(BOOL)isJoinRoom showMessag:(NSString *)message { if (self.landScape) { return; } if (self.enterAnimationView && self.enterAnimationView.isShow) { return; } else { ANIMATIONTYPE type = isJoinRoom ? ANIMATIONTYPE_JOIN : ANIMATIONTYPE_RUSH; self.enterAnimationView = [[LiveAnimationView alloc] initWithTitle:message animationType:type]; [self.view addSubview:self.enterAnimationView]; [self.enterAnimationView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.view); make.width.mas_equalTo(KPortraitWidth); make.height.mas_equalTo(24.0f); make.bottom.mas_equalTo(self.messageContentView.mas_top).offset(-6); }]; [self.view bringSubviewToFront:self.enterAnimationView]; MJWeakSelf; [self.enterAnimationView startAnimationEndCallback:^{ weakSelf.enterAnimationView.isShow = NO; weakSelf.enterAnimationView = nil; }]; } } - (NSMutableArray *)remoteMemberArray { if (!_remoteMemberArray) { _remoteMemberArray = [NSMutableArray array]; } return _remoteMemberArray; } - (TXLiveLoadingView *)loadingView { if (!_loadingView) { _loadingView = [TXLiveLoadingView shareInstance]; } return _loadingView; } - (void)showLoading { if (![self.videoView.subviews containsObject:self.loadingView]) { [self.loadingView showLoadingView:self.videoView]; } } - (void)hideLoading { if ([self.videoView.subviews containsObject:self.loadingView]) { [self.loadingView hideLoadingView]; } } - (void)micButtonAction:(BOOL)isMute { if (isMute == !self.micEnable) { return; } if (_trtcCloud && self.isAudience == NO) { self.micEnable = !isMute; [self.trtcCloud muteLocalAudio:!self.micEnable]; [self syncMicStatus:!self.micEnable]; } } - (void)syncMicStatus:(BOOL)isMute { TXMicStatusSync *syncMic = [[TXMicStatusSync alloc] init]; syncMic.userInfo = [self getCurrentUser]; syncMic.userId = UserDefault(IM_USERID); syncMic.muteMic = isMute; [self sendCustomMessage:syncMic priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) { }]; [self renderSeatView]; } #pragma mark ---- 3A config - (void)setSubjectId:(NSString *)subjectId { _subjectId = subjectId; // 设置 3A参数 self.ANS = 0; self.AEC = 100; // 长号14和萨克斯5 AGC 一样 if ([subjectId isEqualToString:@"5"] || [subjectId isEqualToString:@"14"]) { self.AGC = 45; } else if ([subjectId isEqualToString:@"16"]) { // 次中音号 self.AGC = 80; } else if ([subjectId isEqualToString:@"12"]) { self.AGC = 50; } else { self.AGC = 0; } } /* #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // Get the new view controller using [segue destinationViewController]. // Pass the selected object to the new view controller. } */ @end