// // LiveVideoRoomViewController.m // KulexiuForTeacher // // Created by Kyle on 2022/3/30. // #import "LiveVideoRoomViewController.h" #import "KSChatInputBarControl.h" #import "LiveRoomHeadView.h" #import "LiveRoomBottomView.h" #import "KSRCMessageModel.h" #import "UserInfoManager.h" #import "KSChatroomMessageCenter.h" #import #import "KSChatroomTextCell.h" #import "KSLiveStreamVideo.h" #import "SeatContentView.h" #import "LiveroomTimeManager.h" #import "KSLiveEmptyView.h" #import "KSChatVideoView.h" #import "LiveRoomLikeLayer.h" #import "KSDragWindowManager.h" #import "KSLiveBadNetView.h" #import #import "KSShopCardView.h" #import "KSLiveWebViewController.h" #import "KSLiveEndView.h" #import "LiveAnimationView.h" #import "LiveRoomAlertView.h" #import "LiveApplyView.h" #import "LiveApplyingView.h" #import "ShareLiveDisplayView.h" #import "UIImage+KSScreenShot.h" #import "TZImageManager.h" #import "RecordCheckManager.h" #import "KSPremissionAlert.h" #import "KSUMShareManager.h" #define AUTOHIDE_TIME (10.0f) #define PACKAGE_LOST (0.30f) typedef NS_ENUM(NSInteger, MICSTATUS) { MICSTATUS_NOMAL, // 正常状态 MICSTATUS_WAITING, // 申请中 MICSTATUS_CONNECTING, // 连麦中 }; @interface LiveVideoRoomViewController () @property (nonatomic, strong) LiveroomTimeManager *timeManager; // 音频配置 @property(strong, nonatomic) RCRTCEngine *engine; @property (nonatomic, strong) RCRTCRoom *room; @property (nonatomic, strong) KSLiveStreamVideo *localVideo; @property (nonatomic) NSMutableArray *streamVideos; // 视频主窗口容器 @property (nonatomic, strong) UIView *videoContainerView; @property (nonatomic, strong) KSChatVideoView *videoView; @property (nonatomic, strong) LiveRoomHeadView *headView; @property (nonatomic, copy) LiveRoomBottomView *bottomView; /** 上次点赞按钮点击时间 */ @property(nonatomic, assign) NSTimeInterval lastClickPraiseTime; // 连麦状态 @property (nonatomic, assign) MICSTATUS micStatus; /// 提示窗 @property (nonatomic, strong) LiveRoomAlertView *alertView; /// 连麦的视图 @property (nonatomic, strong) SeatContentView *seatContainer; // 聊天UI /*! 消息列表CollectionView和输入框都在这个view里 */ @property(nonatomic, strong) UIView *messageContentView; /*! 会话页面的TableView */ @property (nonatomic, strong) UITableView *conversationMessageTableView; /** 输入工具栏 */ @property(nonatomic,strong) KSChatInputBarControl *inputBar; /** * 是否需要滚动到底部 */ @property(nonatomic, assign) BOOL isNeedScrollToButtom; /*! 聊天内容的消息Cell数据模型的数据源 @discussion 数据源中存放的元素为消息Cell的数据模型,即RCDLiveMessageModel对象。 */ @property(nonatomic, strong) NSMutableArray *conversationDataRepository; @property (nonatomic, assign) BOOL isImConnected; /// 老师是否在房间 @property (nonatomic, assign) BOOL isCreaterInRoom; @property (nonatomic, assign) BOOL isCreaterVideoEnable; @property (nonatomic, assign) BOOL isCreaterAudioEnable; @property (nonatomic, strong) KSLiveEmptyView *emptyView; /// 是否在切换身份 @property (nonatomic, assign) BOOL isTransferRole; @property (nonatomic, assign) BOOL hasSendWelcomeMessage; @property (nonatomic, assign) BOOL hasShowSuspendView; @property (nonatomic, assign) BOOL isPackageLostTips; @property (nonatomic, assign) NSTimeInterval lostStartTime; // 开始丢包事件 @property (nonatomic, assign) NSTimeInterval bitErrorTime; // video bit zero start @property (nonatomic, strong) KSLiveBadNetView *netBadView; @property (nonatomic, assign) BOOL isLostPackage; // 严重丢包 @property (nonatomic, assign) BOOL isBitError; // bit error @property (nonatomic, strong) LOTAnimationView *animationView; @property (nonatomic, assign) BOOL isCloseRoom; // 是否直播结束 @property (nonatomic, strong) KSLiveEndView *endView; // 直播结束显示的画面 @property (nonatomic, strong) KSShopCardView *cardView; @property (nonatomic, strong) LiveAnimationView *enterAnimationView; @property (nonatomic, assign) NSTimeInterval lastSendMsgTime; @property (nonatomic, assign) BOOL isOtherLogin; // 是否被顶掉 @property (nonatomic, strong) LiveApplyView *applyView; // 未申请 @property (nonatomic, strong) LiveApplyingView *applyingView; // 申请中 @property (nonatomic, assign) BOOL isPauseLive; @property (nonatomic, strong) ShareLiveDisplayView *shareView; @end // 用于记录点赞消息连续点击的次数 static int clickPraiseBtnTimes = 0; @implementation LiveVideoRoomViewController - (instancetype)init { self = [super init]; if (self) { [self defaultConfig]; } return self; } - (void)defaultConfig { self.enableChat = YES; self.enableSeat = YES; self.enableLike = YES; self.micStatus = MICSTATUS_NOMAL; self.isCreaterInRoom = NO; // 默认老师不在房间 self.isImConnected = [USER_MANAGER checkIMConnected]; self.isCloseRoom = NO; self.isOtherLogin = NO; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.ks_prefersNavigationBarHidden = YES; [self registerNotification]; [self setupUI]; if (self.isImConnected) { [self connectService]; } else { MJWeakSelf; [USER_MANAGER connectionIMCallback:^(BOOL isSuccess) { dispatch_main_async_safe(^{ [weakSelf connectService]; }); }]; } [self countUMEvent]; [self judgeAutoClose]; } - (void)countUMEvent { [USER_MANAGER sendUMEvent:@"klx_live"]; } - (void)muteAllAudio { if (self.room) { [self.room muteAllRemoteAudio:YES]; } } - (void)IMConnetedCallback { dispatch_main_async_safe(^{ // IM 连接成功回调 if (self.hasSendWelcomeMessage) { // 如果已经发送了进入消息 // 查询是否直播间开启 [KSNetworkingManager liveRoomJoinRoomRequest:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) { if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) { // 回调信息 MJWeakSelf; [self notiferJoinSuccessToServiceCallback:^{ [weakSelf MBPShow:@"IM连接成功"]; [weakSelf sendWelcomeMessage]; }]; } else { [self MBPShow:MESSAGEKEY]; [self quitRoom]; } } faliure:^(NSError * _Nonnull error) { }]; } }); } - (void)sendWelcomeMessage { [[RCIMClient sharedRCIMClient] joinExistChatRoom:self.roomId messageCount:-1 success:^{ KSLiveChatroomWelcome *joinChatroomMessage = [[KSLiveChatroomWelcome alloc] init]; [joinChatroomMessage setMsgId:[RCIM sharedRCIM].currentUserInfo.userId]; MJWeakSelf; [self sendMessage:joinChatroomMessage displayMessage:NO callback:^(BOOL success) { [weakSelf showEnterMessageAndJoinRTCRoom]; }]; } error:^(RCErrorCode status) { NSLog(@"error code %zd" ,status); }]; } - (void)showEnterMessageAndJoinRTCRoom { NSString *contentString = [NSString stringWithFormat:@"%@ 进入直播间",UserDefault(NicknameKey)]; [self showAnimationView:YES showMessag:contentString]; [self joinRTCRoom]; } - (void)leaveRTCRoomCallback:(void(^)(BOOL success))callback { [[RCRTCEngine sharedInstance] leaveRoom:^(BOOL isSuccess, RCRTCCode code) { callback(isSuccess); }]; } - (void)joinRTCRoom { // 如果当前加入了房间 只刷新或者退出 if (self.room && self.liveRoleType == RCRTCLiveRoleTypeBroadcaster) { [self connectHostWithStatus:NO]; } else { [self setRoleType]; } } - (void)judgeAutoClose { if (self.isTempRoom == NO) { self.timeManager.autoCloseNetworkRoomTime = [self getCloseTime]; } } - (void)liveroomLogOut { if (self.isOtherLogin) { return; } self.isOtherLogin = YES; [self MBPShow:@"该账号在其他设备上登录"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self quitRoom]; [[NSNotificationCenter defaultCenter] postNotificationName:@"liveroomBackRefresh" object:nil]; }); } - (void)liveroomQuit { if (self.isOtherLogin) { return; } self.isOtherLogin = YES; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self quitRoom]; [[NSNotificationCenter defaultCenter] postNotificationName:@"liveroomBackRefresh" object:nil]; }); } - (void)registerNotification { [KSChatroomMessageCenter registerMessageTypes]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMessageNotification:) name:OnReceiveChatroomMessageNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomLogOut) name:@"liveroomLogout" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomQuit) name:@"liveroomQuit" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(IMConnetedCallback) name:@"RongIMConnected" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(muteAllAudio) name:@"muteLiveAudio" object:nil]; } - (void)setupUI { CGSize size = self.view.bounds.size; 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.top.bottom.right.mas_equalTo(self.view); }]; [self.videoContainerView addSubview:self.videoView]; [self.videoContainerView addSubview:self.netBadView]; [self.videoView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.top.bottom.right.mas_equalTo(self.view); }]; [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.view 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.view); make.height.mas_equalTo(80); if (@available(iOS 11.0, *)) { make.top.mas_equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(0); } else { // Fallback on earlier versions make.top.mas_equalTo(self.view.mas_top).offset(0); } }]; [self.view 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(-14); } else { // Fallback on earlier versions make.bottom.mas_equalTo(self.videoContainerView.mas_bottom).offset(-14); } make.centerX.mas_equalTo(self.videoContainerView.mas_right).offset(-84); make.width.mas_equalTo(48); make.height.mas_equalTo(56); }]; [self.animationView play]; } [self.view addSubview:self.bottomView]; self.bottomView.hideCartVButton = self.hideCartButton; [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.mas_equalTo(self.view); if (@available(iOS 11.0, *)) { make.bottom.mas_equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-10); } else { // Fallback on earlier versions make.bottom.mas_equalTo(self.view.mas_bottom).offset(-10); } make.height.mas_equalTo(44); }]; [self.messageContentView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view.mas_left); make.right.mas_equalTo(self.view.mas_right); make.bottom.mas_equalTo(self.bottomView.mas_top).offset(-10); make.height.mas_equalTo(180); }]; [self.view addSubview:self.inputBar]; [_inputBar setBackgroundColor: [UIColor whiteColor]]; [_inputBar setFrame:CGRectMake(0, kScreenHeight, size.width , 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.view addGestureRecognizer:resetBottomTapGesture]; // if (self.isTempRoom) { // self.bottomView.hideShareButton = NO; // } // else { // self.bottomView.hideShareButton = YES; // } } - (void)connectService { [self setRoleType]; [self joinChatRoom]; } - (void)joinChatRoom { [[RCIMClient sharedRCIMClient] joinChatRoom:self.roomId messageCount:-1 success:^{ dispatch_async(dispatch_get_main_queue(), ^{ [self addConstMessage]; }); KSLiveChatroomWelcome *joinChatroomMessage = [[KSLiveChatroomWelcome alloc] init]; [joinChatroomMessage setMsgId:[RCIM sharedRCIM].currentUserInfo.userId]; MJWeakSelf; [self sendMessage:joinChatroomMessage displayMessage:NO callback:^(BOOL success) { weakSelf.hasSendWelcomeMessage = YES; NSString *contentString = [NSString stringWithFormat:@"%@ 进入直播间",UserDefault(NicknameKey)];; [weakSelf showAnimationView:YES showMessag:contentString]; }]; // 加入成功发送消息 } error:^(RCErrorCode status) { if (status == RC_CHATROOM_NOT_EXIST || status == KICKED_FROM_CHATROOM || status == RC_PARAMETER_INVALID_CHATROOM) { dispatch_async(dispatch_get_main_queue(), ^{ [self MBPShow:@"加入聊天室失败"]; }); } }]; } - (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) { KSLiveStreamVideo *mainVideo = nil; if (self.videoView.streamId) { NSMutableArray *steamArray = [self.streamVideos mutableCopy]; for (KSLiveStreamVideo *streamVideo in steamArray) { if ([streamVideo.streamId isEqualToString:self.videoView.streamId]) { mainVideo = streamVideo; break; } } } if (mainVideo) { [self.videoView addSubview:mainVideo.canvesView]; [mainVideo.canvesView mas_updateConstraints:^(MASConstraintMaker *make) { make.left.right.bottom.top.mas_equalTo(self.videoView); }]; } _hasShowSuspendView = NO; } if (self.room) { [self.room muteAllRemoteAudio:NO]; } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [UIApplication sharedApplication].idleTimerDisabled = 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; } // 退出房间才停止定时器 if (_timeManager) { [_timeManager stopDurationTimer]; } } // 刷新seatView - (void)renderSeatView { NSMutableArray *seatArray = [NSMutableArray array]; NSArray *remoteUserArray = [self.room.remoteUsers mutableCopy]; for (RCRTCRemoteUser *user in remoteUserArray) { if (![user.userId isEqualToString:self.videoView.streamUserId] && ![user.userId isEqualToString:self.createrId] && ![user.userId containsString:@"rc_mc_"]) { [seatArray addObject:user.userId]; } } if (self.micStatus == MICSTATUS_CONNECTING && ![seatArray containsObject:UserDefault(IM_USERID)]) { [seatArray addObject:UserDefault(IM_USERID)]; } 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]; } else { [self removeSeatContainer]; } } - (void)updateSeatContainerConstraint { [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); }]; } // 移除连麦视图 - (void)removeSeatContainer { if ([self.videoContainerView.subviews containsObject:self.seatContainer]) { [self.seatContainer removeFromSuperview]; } } - (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)setRoleType { self.micStatus = MICSTATUS_NOMAL; [self hideApplyingView]; // 1.设置切换听筒为扬声器 [self.engine setDefaultAudioRouteToSpeaker:YES]; // 2.加入RTC房间 [self joinLiveRoom]; } // 观众上下麦 - (void)connectHostWithStatus:(BOOL)isConnect { // 转换中,不再处理转换身份 if (self.isTransferRole) { return; } // 如果需要切换的身份和当前身份相同 拦截 if (isConnect && self.liveRoleType == RCRTCLiveRoleTypeBroadcaster) { return; } else if (isConnect == NO && self.liveRoleType == RCRTCLiveRoleTypeAudience) { return; } self.isTransferRole = YES; // 清理当前视图 [self removeSeatContainer]; [self clearVideoViewSource]; if (isConnect) { // 开启麦克风采集 [self.engine.defaultAudioStream setMicrophoneDisable:NO]; NSArray *streams = @[self.engine.defaultAudioStream]; MJWeakSelf; [self.room.localUser switchToBroadcaster:streams onSucceed:^(RCRTCLiveInfo * _Nonnull liveInfo) { weakSelf.isTransferRole = NO; dispatch_main_async_safe(^{ __strong typeof(weakSelf) strongSelf = weakSelf; //3.切换成功, 订阅远端用户流 strongSelf.liveRoleType = RCRTCLiveRoleTypeBroadcaster; NSMutableArray *streamArray = [NSMutableArray array]; NSArray *remoteUserArray = [strongSelf.room.remoteUsers mutableCopy]; for (RCRTCRemoteUser *user in remoteUserArray) { if (remoteUserArray.count) { [streamArray addObjectsFromArray:user.remoteStreams]; } } if (streamArray.count) { [strongSelf subscribeRemoteResource:streamArray]; } else { [strongSelf updateVideoViewContainer]; } strongSelf.micStatus = MICSTATUS_CONNECTING; // 刷新麦位状态 }); } onFailed:^(RCRTCCode code) { NSLog(@"------- up error code %ld", code); weakSelf.isTransferRole = NO; dispatch_main_async_safe(^{ if (code == RCRTCCodeSwitchRoleSameRoleError) { weakSelf.liveRoleType = RCRTCLiveRoleTypeBroadcaster; // 失败 weakSelf.micStatus = MICSTATUS_CONNECTING; // 刷新麦位状态 [weakSelf renderSeatView]; } else { // 失败 [weakSelf MBPShow:@"上麦失败"]; weakSelf.micStatus = MICSTATUS_NOMAL; [weakSelf renderSeatView]; } }); } onKicked:^{ weakSelf.isTransferRole = NO; dispatch_main_async_safe(^{ // 被踢出 [weakSelf kickOutLiveRoom]; }); }]; } else { // 观众下麦 MJWeakSelf; [self.room.localUser switchToAudienceOnSucceed:^{ weakSelf.isTransferRole = NO; dispatch_main_async_safe(^{ __strong typeof(weakSelf) strongSelf = weakSelf; strongSelf.liveRoleType = RCRTCLiveRoleTypeAudience; [strongSelf.engine.defaultAudioStream setMicrophoneDisable:YES]; NSMutableArray *streamArray = [NSMutableArray array]; NSArray *remoteUserArray = [strongSelf.room.remoteUsers mutableCopy]; for (RCRTCRemoteUser *user in remoteUserArray) { if (user.remoteStreams.count) { [streamArray addObjectsFromArray:user.remoteStreams]; } } strongSelf.micStatus = MICSTATUS_NOMAL; if (streamArray.count) { [strongSelf subscribeRemoteResource:streamArray]; } else { [strongSelf updateVideoViewContainer]; } // 刷新麦位状态 // 发送下麦消息 [weakSelf sendDownSeatMessage]; }); } onFailed:^(RCRTCCode code) { weakSelf.isTransferRole = NO; NSLog(@"------- down error code %ld", code); dispatch_main_async_safe(^{ if (code == RCRTCCodeSwitchRoleSameRoleError) { weakSelf.liveRoleType = RCRTCLiveRoleTypeAudience; weakSelf.micStatus = MICSTATUS_NOMAL; } else { // 失败 [weakSelf MBPShow:@"下麦失败"]; weakSelf.micStatus = MICSTATUS_CONNECTING; } // 刷新麦位状态 [weakSelf renderSeatView]; }); } onKicked:^{ weakSelf.isTransferRole = NO; dispatch_main_async_safe(^{ // 被踢出 [weakSelf kickOutLiveRoom]; }); }]; } } - (void)kickOutLiveRoom { [self MBPShow:@"您已被踢出直播间"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self quitRoom]; }); } - (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; } - (void)clearVideoViewSource { self.videoView.streamUserId = @""; self.videoView.streamTag = @""; self.videoView.streamId = @""; } // 清空视图 - (void)clearRemoteContainer { [self.streamVideos removeAllObjects]; [self.videoView removeAllSubViews]; [self clearVideoViewSource]; [self removeSeatContainer]; } // 更新视图 - (void)updateVideoViewContainer { NSMutableArray *steamArray = [self.streamVideos mutableCopy]; if (steamArray.count > 0) { // 渲染主播的流 优先显示主播的共享流 KSLiveStreamVideo *mainVideo = nil; for (KSLiveStreamVideo *videoSource in steamArray) { if ([videoSource.userId isEqualToString:self.createrId]) { // 主屏只渲染老师 if ([videoSource.streamTag isEqualToString:@"screenshare"]) { // 如果是共享流 if (![self.videoView.streamId isEqualToString:videoSource.streamId]) { // 主屏渲染不同 则重新渲染 mainVideo = videoSource; } break; } else if ([videoSource.streamTag isEqualToString:@"RongCloudRTC"]) { // 默认流 if (![self.videoView.streamId isEqualToString:videoSource.streamId]) { // 主屏渲染不同 则重新渲染 mainVideo = videoSource; } } else { // 其他流 if (mainVideo == nil) { // 如果暂未遍历出其他视频流 先取一个作为主屏展示流 if (![self.videoView.streamId isEqualToString:videoSource.streamId]) { // 主屏渲染不同 则重新渲染 mainVideo = videoSource; } } } } } // 如果需要渲染主屏 if (mainVideo) { [self.videoView removeAllSubViews]; self.videoView.streamUserId = mainVideo.userId; self.videoView.streamId = mainVideo.streamId; self.videoView.streamTag = mainVideo.streamTag; mainVideo.canvesView.tag = 10000; [self.videoView addSubview:mainVideo.canvesView]; [mainVideo.canvesView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.bottom.top.mas_equalTo(self.videoView); }]; } } [self changeEmptyStatusView]; // 刷新 seatView [self renderSeatView]; } - (void)removeViewSubViews:(UIView *)container { while (container.subviews.count) { [container.subviews.lastObject removeFromSuperview]; } } - (void)changeEmptyStatusView { if (self.isCreaterInRoom) { if (self.isCreaterVideoEnable) { self.emptyView.hidden = YES; } else if (self.isCreaterVideoEnable == NO && self.isCreaterAudioEnable == YES) { // 主播关闭了摄像头 [self showEmptyViewDisplay:YES]; } else { // 休息一下 [self showEmptyViewDisplay:NO]; } } else { // 无主播 [self.videoView removeAllSubViews]; [self showEmptyViewDisplay:NO]; } } #pragma mark ---- RCRTCRoomEventDelegate // 远端发布流 - (void)didPublishStreams:(NSArray *)streams { [self subscribeRemoteResource:streams]; NSLog(@"didPublishStreams --------"); } // 订阅远端流 - (void)subscribeRemoteResource:(NSArray *)streams { [self subscribeRemoteResource:streams isTiny:NO]; } - (void)subscribeRemoteResource:(NSArray *)streams isTiny:(BOOL)isTiny { // 订阅房间中远端用户音视频流资源 NSArray *tinyStream = isTiny ? streams : @[]; NSArray *ordinaryStream = isTiny ? @[] : streams; [self.room.localUser subscribeStream:ordinaryStream tinyStreams:tinyStream completion:^(BOOL isSuccess, RCRTCCode desc) { if (desc != RCRTCCodeSuccess) { NSString *errorStr = [NSString stringWithFormat:@"订阅远端流失败:%ld", (long) desc]; [self MBPShow:errorStr]; return; } // 创建并设置远端视频预览视图 for (RCRTCInputStream *stream in streams) { if ([stream.userId isEqualToString:self.createrId]) { self.isPauseLive = NO; } if (stream.mediaType == RTCMediaTypeVideo) { if ([stream.userId isEqualToString:self.createrId]) { // 只渲染老师的流 self.isCreaterVideoEnable = YES; [self setupRemoteViewWithStream:stream]; } } if (stream.mediaType == RTCMediaTypeAudio && [stream.userId isEqualToString:self.createrId]) { self.isCreaterAudioEnable = YES; } } [self updateVideoViewContainer]; }]; } // 创建并设置远端视频预览视图 - (KSLiveStreamVideo *)setupRemoteViewWithStream:(RCRTCInputStream *)stream { KSLiveStreamVideo *sVideo = [self creatStreamVideoWithStreamId:stream.streamId userId:stream.userId tag:stream.tag]; sVideo.userId = stream.userId; RCRTCVideoView *remoteView = (RCRTCVideoView *) sVideo.canvesView; //设置视频流的渲染视图 [(RCRTCVideoInputStream *) stream setVideoView:remoteView]; return sVideo; } // 远端用户取消发布资源 - (void)didUnpublishStreams:(NSArray *)streams { [self unsubscribeRemoteResource:streams orStreamId:nil]; } - (void)stream:(RCRTCInputStream *)stream didVideoEnable:(BOOL)enable { NSLog(@"----- video --- staus %d", enable); } - (void)stream:(RCRTCInputStream *)stream didAudioMute:(BOOL)mute { NSLog(@"----- audio --- staus %d", mute); if ([stream.userId isEqualToString:self.createrId]) { if (mute) { self.isCreaterAudioEnable = NO; } else { self.isCreaterAudioEnable = YES; } [self changeEmptyStatusView]; } } - (void)didOfflineUser:(RCRTCRemoteUser *)user { [self removeRemoteVideoWithUserId:user.userId]; [self renderSeatView]; } // 判断是否已有预览视图 - (KSLiveStreamVideo *)creatStreamVideoWithStreamId:(NSString *)streamId userId:(NSString *)userId tag:(NSString *)streamTag { KSLiveStreamVideo *sVideo = [self fetchStreamVideoWithStreamId:streamId]; if (!sVideo) { sVideo = [[KSLiveStreamVideo alloc] initWithStreamId:streamId streamUserId:userId streamTag:streamTag]; [self.streamVideos insertObject:sVideo atIndex:0]; } return sVideo; } // 根据 streamId 确认唯一的音视频流 - (KSLiveStreamVideo *)fetchStreamVideoWithStreamId:(NSString *)streamId { NSMutableArray *streamArray = [self.streamVideos mutableCopy]; for (KSLiveStreamVideo *sVideo in streamArray) { if ([streamId isEqualToString:sVideo.streamId]) { return sVideo; } } return nil; } // 远端掉线/离开回掉调用,删除远端用户的所有音视频流 - (void)fetchStreamVideoOffLineWithStreamId:(NSString *)streamId { NSArray *arr = [NSArray arrayWithArray:self.streamVideos]; for (KSLiveStreamVideo *sVideo in arr) { if ([streamId isEqualToString:sVideo.streamId]) { if (sVideo) { [sVideo.canvesView removeFromSuperview]; [self.streamVideos removeObject:sVideo]; } } } [self updateVideoViewContainer]; } // 远端用户切换身份(主播接收) - (void)didSwitchRoleWithUser:(RCRTCRemoteUser *)user roleType:(RCRTCLiveRoleType)roleType { if (roleType == RCRTCLiveRoleTypeAudience) { [self renderSeatView]; } NSLog(@"didSwitchRoleWithUser --------"); } - (void)judgeRefreshSeatView:(NSString *)userId { BOOL needRefresh = NO; if (_seatContainer) { if (self.seatContainer.seatMemberArray.count && [self.videoContainerView.subviews containsObject:self.seatContainer]) { NSMutableArray *memerArray = [self.seatContainer.seatMemberArray mutableCopy]; for (NSString *memberId in memerArray) { if ([userId isEqualToString:memberId]) { needRefresh = YES; break; } } } } if (needRefresh) { [self renderSeatView]; } } // 主播加入房间 (观众接收) - (void)didJoinUser:(RCRTCRemoteUser *)user { if ([user.userId isEqualToString:self.createrId]) { // 当主播重新进入房间需要重置连麦状态 self.isCreaterInRoom = YES; // 重置连麦状态 // [self resetMicWaitToNomal]; // 刷新顶部视图 } else { // [self renderSeatView]; } NSLog(@"didJoinUser --------"); } // 主播离开房间 (观众接收) - (void)didLeaveUser:(RCRTCRemoteUser *)user { [self removeRemoteVideoWithUserId:user.userId]; // 移除对应的视频 if ([user.userId isEqualToString:self.createrId]) { // 如果是老师 self.isCreaterInRoom = NO; [self resetMicWaitToNomal]; [self tipsTeacherLeaveRoom]; [self clearVideoView]; [self hideTipsView]; [self showEmptyViewDisplay:NO]; } else { [self renderSeatView]; } } - (void)removeRemoteVideoWithUserId:(NSString *)userId { NSArray *arr = [NSArray arrayWithArray:self.streamVideos]; for (KSLiveStreamVideo *sVideo in arr) { if ([userId isEqualToString:sVideo.userId]) { if (sVideo) { [sVideo.canvesView removeFromSuperview]; [self.streamVideos removeObject:sVideo]; } } } } - (void)clearVideoView { [self clearVideoViewSource]; [self.videoView removeAllSubViews]; } - (void)resetConnectionStatus { if (self.micStatus == MICSTATUS_CONNECTING) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self connectHostWithStatus:NO]; }); } else { [self removeSeatContainer]; } self.micStatus = MICSTATUS_NOMAL; } - (void)resetMicWaitToNomal { if (self.micStatus == MICSTATUS_WAITING) { self.micStatus = MICSTATUS_NOMAL; } } - (void)tipsTeacherLeaveRoom { [self MBPShow:@"老师已离开房间"]; } #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) { [self MBPShow:@"您说话太快啦"]; return; } if (self.lastSendMsgTime == 0) { self.lastSendMsgTime = currentInterval; } if (text.length > 40) { [self MBPShow:@"聊天消息需在40个字以内哦"]; return; } self.lastSendMsgTime = currentInterval; [self touristSendMessage:text]; } - (void)touristSendMessage:(NSString *)text { RCTextMessage *rcTextMessage = [RCTextMessage messageWithContent:text]; MJWeakSelf; [self sendMessage:rcTextMessage displayMessage:YES callback:^(BOOL success) { [weakSelf setDefaultBottomViewStatus]; }]; } #pragma mark ---- RTC - (void)joinLiveRoom { // 1.配置房间 RCRTCRoomConfig *config = [[RCRTCRoomConfig alloc] init]; config.roomType = RCRTCRoomTypeLive; config.liveType = RCRTCLiveTypeAudioVideo; config.roleType = RCRTCLiveRoleTypeAudience; __weak typeof(self) weakSelf = self; [self.engine joinRoom:self.roomId config:config completion:^(RCRTCRoom * _Nullable room, RCRTCCode code) { dispatch_main_async_safe(^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (code == RCRTCCodeSignalServerNotConnect || code == RCRTCCodeParameterError || code == RCRTCCodeNotInRTCRoom || code == RCRTCCodeRTCTokenIsNull || code == RCRTCCodeHttpTimeoutError || code == RCRTCCodeHttpError || code == RCRTCCodeVoIPNotAvailable) { NSLog(@"%@",[NSString stringWithFormat:@"加入失败 code %ld",code]); } else { // 加入直播间成功 [strongSelf notiferJoinSuccessToServiceCallback:^{ }]; if (room) { strongSelf.room = room; strongSelf.room.delegate = self; } [strongSelf countMemberCount]; if (room.remoteUsers.count) { NSMutableArray *streamArray = [NSMutableArray array]; NSMutableArray *remoteUserArray = [room.remoteUsers mutableCopy]; for (RCRTCRemoteUser *user in remoteUserArray) { if ([user.userId isEqualToString:strongSelf.createrId]) { strongSelf.isCreaterInRoom = YES; } if (user.remoteStreams.count) { [streamArray addObjectsFromArray:user.remoteStreams]; [strongSelf subscribeRemoteResource:streamArray]; } } } [self changeEmptyStatusView]; } }); }]; } - (void)closeAlertView { if (self.alertView && self.alertView.isShow) { [self.alertView removeFromSuperview]; self.alertView = nil; } } - (void)displayCloseView { self.isCloseRoom = YES; [self hideTipsView]; // 移除连麦视图 [self clearRemoteContainer]; if (self.micStatus != MICSTATUS_NOMAL) { self.micStatus = MICSTATUS_NOMAL; self.liveRoleType = RCRTCLiveRoleTypeAudience; [self.engine.defaultAudioStream setMicrophoneDisable:YES]; } [self.engine leaveRoom:^(BOOL isSuccess, RCRTCCode code) { if (code != RCRTCCodeSuccess) { // 退出失败 } }]; self.endView.hidden = NO; [self setDefaultBottomViewStatus]; [[NSNotificationCenter defaultCenter] postNotificationName:@"LiveroomClose" object:nil]; } - (void)insertMessage:(RCMessage *)rcMessage userInfo:(NSDictionary *)userInfo { // left NSDictionary *leftDic = userInfo; if (leftDic && [leftDic[@"left"] isEqual:@(0)]) { self.isNeedScrollToButtom = YES; } [self appendAndDisplayMessage:rcMessage]; } - (void)kickSeatWithMessage:(NSString *)message { [self MBPShow:message]; self.micStatus = MICSTATUS_NOMAL; [self connectHostWithStatus:NO]; } - (void)showInviteAlertMessage:(NSString *)tipsMessage { if (self.alertView && self.alertView.isShow) { [self.alertView removeFromSuperview]; self.alertView = nil; } MJWeakSelf; self.alertView = [LiveRoomAlertView liveroomAlertWithTitle: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; KSLiveChatroomSeatResponse *responseMessage = [[KSLiveChatroomSeatResponse alloc] init]; responseMessage.type = type; responseMessage.audienceId = UserDefault(IM_USERID); responseMessage.audienceName = UserDefault(NicknameKey); MJWeakSelf; [self sendMessage:responseMessage displayMessage:YES callback:^(BOOL success) { if (success) { if (isApprove) { [weakSelf connectHostWithStatus:YES]; } } }]; } - (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.emptyView.hidden = NO; } - (void)exitRoom { [self.engine.defaultAudioStream setMicrophoneDisable:YES]; __weak typeof(self) weakSelf = self; [self.engine leaveRoom:^(BOOL isSuccess, RCRTCCode code) { dispatch_main_async_safe(^{ __strong typeof(weakSelf) strongSelf = weakSelf; [strongSelf sendLeaveMessageCallback:^(BOOL success) { // quit 接口 [weakSelf quitNotiferService]; [weakSelf quitChatRoom]; }]; }); }]; } // 取消订阅流 Id 所对应的 View - (void)unsubscribeRemoteResource:(NSArray *)streams orStreamId:(NSString *)streamId { for (RCRTCInputStream *stream in streams) { if (stream.mediaType == RTCMediaTypeVideo) { streamId = stream.streamId; if ([stream.userId isEqualToString:self.createrId]) { [self clearVideoViewSource]; self.isCreaterVideoEnable = NO; // 主讲老师停止发布视频流 NSLog(@"----- 停止发布视频流"); } [self fetchStreamVideoOffLineWithStreamId:streamId]; } else if (stream.mediaType == RTCMediaTypeAudio && [stream.userId isEqualToString:self.createrId]) { // 主讲老师停止发布音频流 NSLog(@"----- 停止发布音频流"); self.isCreaterAudioEnable = NO; } } } #pragma mark -- 加入直播间和退出直播间回到服务 - (void)notiferJoinSuccessToServiceCallback:(void(^)(void))callback { callback(); [KSNetworkingManager liveRoomJoinRoomRequest:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) { callback(); } faliure:^(NSError * _Nonnull error) { callback(); }]; } #pragma mark -- 退出直播间回掉服务 - (void)quitNotiferService { [KSNetworkingManager LiveroomQuit:KS_POST success:^(NSDictionary * _Nonnull dic) { if ([dic ks_integerValueForKey:@"errCode"] == 0) { NSLog(@"success"); } else { NSLog(@"----- error %@", [dic ks_stringValueForKey:@"errMsg"]); } } faliure:^(NSError * _Nonnull error) { }]; } - (void)sendLeaveMessageCallback:(void(^)(BOOL success))callback { RCUserInfo *currentUserInfo = [RCIM sharedRCIM].currentUserInfo; KSLiveChatroomLeave *leaveMessage = [[KSLiveChatroomLeave alloc] init]; leaveMessage.userId = currentUserInfo.userId; leaveMessage.userName = currentUserInfo.name; [self sendMessage:leaveMessage displayMessage:NO callback:^(BOOL success) { callback(success); }]; } - (void)quitChatRoom { [[RCIMClient sharedRCIMClient] quitChatRoom:self.roomId success:^{ NSLog(@"sucess log out--"); } error:^(RCErrorCode status) { NSLog(@"RCErrorCode------%ld ", (long)status); }]; } - (void)addConstMessage { // 固定欢迎词语 RCTextMessage *statement = [RCTextMessage messageWithContent:@"欢迎进入直播课堂,请遵守相关法规,禁止传播低俗、暴力等不良信息。为孩子创造健康绿色的学习环境。"]; statement.extra = @"statement"; RCMessage *constMessage = [[RCMessage alloc] initWithType:ConversationType_CHATROOM targetId:self.roomId direction:MessageDirection_SEND content:statement]; constMessage.content.senderUserInfo = [RCIM sharedRCIM].currentUserInfo; [self appendAndDisplayMessage:constMessage]; } #pragma mark - views init /** * 接收到消息的回调 */ - (void)didReceiveMessageNotification:(NSNotification *)notification { NSDictionary *dic = notification.object; __block RCMessage *rcMessage = dic[@"message"]; KSRCMessageModel *model = [[KSRCMessageModel alloc] initWithMessage:rcMessage]; model.userInfo = rcMessage.content.senderUserInfo; if (model.conversationType == ConversationType_CHATROOM && [model.targetId isEqualToString:self.roomId]) { __weak typeof(&*self) __blockSelf = self; dispatch_main_async_safe((^{ // 对赞消息拦截 ,展示动画,不插入数据源 ,对于seat消息 和踢出消息,弹出alert if (rcMessage) { // 👍🏻消息 if ([rcMessage.content isMemberOfClass:[KSLiveChatroomLike class]]) { return; } else if ([rcMessage.content isMemberOfClass:[RCChatroomLikeCount class]]) { RCChatroomLikeCount *likeCountMessage = (RCChatroomLikeCount *)rcMessage.content; __blockSelf.likeCount = likeCountMessage.count; [__blockSelf countLikeMessageCount]; return; } else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomWelcome class]]) { // 过滤自己发送的欢迎消息 if ([rcMessage.senderUserId isEqualToString:UserDefault(IM_USERID)]) { return; } // if ([rcMessage.senderUserId isEqual:self.createrId]) { // 如果是老师 重置连麦申请状态 // [__blockSelf resetMicWaitToNomal]; // } RCUserInfo *userInfo = rcMessage.content.senderUserInfo; NSString *userName = [userInfo.name stringByAppendingString:@""]; NSString *contentMsg = [NSString stringWithFormat:@"%@ 进入直播间",userName]; [__blockSelf showAnimationView:YES showMessag:contentMsg]; } // 用户退出消息 else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomLeave class]]) { return; } // 用户退出消息 else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomUserQuit class]]) { return; } else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomKickOut class]]) { KSLiveChatroomKickOut *kickMessage = (KSLiveChatroomKickOut *)rcMessage.content; NSString *currentUserId = UserDefault(IM_USERID); if ([kickMessage.targetId isEqualToString:currentUserId]) { __blockSelf.micStatus = MICSTATUS_NOMAL; [__blockSelf kickOutLiveRoom]; } return; } // 人数同步 else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomMemberUp class]]) { KSLiveChatroomMemberUp *member = (KSLiveChatroomMemberUp *)rcMessage.content; __blockSelf.totalCount = member.count; [__blockSelf countMemberCount]; return; } // 连麦控制消息 else if ([rcMessage.content isMemberOfClass:[RCChatroomSeatsControl class]]) { RCChatroomSeatsControl *seatControlMessage = (RCChatroomSeatsControl *)rcMessage.content; if (seatControlMessage.seatBan) { // 禁止连麦 __blockSelf.enableSeat = NO; // 未连麦情况下才重置 if (__blockSelf.micStatus != MICSTATUS_CONNECTING) { __blockSelf.micStatus = MICSTATUS_NOMAL; } [__blockSelf hideApplyView]; [__blockSelf hideApplyingView]; } else { // 开启连麦 __blockSelf.enableSeat = YES; } [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo]; } // 连麦申请消息 else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomSeatApply class]]) { KSLiveChatroomSeatApply *seatApplyMessage = (KSLiveChatroomSeatApply *)rcMessage.content; // 只处理和自己相关的消息 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) { // 老师撤回邀请 [__blockSelf MBPShow:@"老师撤回了连麦邀请"]; if (__blockSelf.alertView && __blockSelf.alertView.isShow) { [__blockSelf.alertView dismissAlertView]; } } else if (seatApplyMessage.type == SEATHANDLE_KICKSEAT) { // 老师将麦上观众抱下麦 [__blockSelf kickSeatWithMessage:@"您已被抱下麦"]; } [__blockSelf hideApplyingView]; [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo]; } // 连麦回复消息(暂不处理) else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomSeatResponse class]]) { KSLiveChatroomSeatResponse *seatResponseMessage = (KSLiveChatroomSeatResponse *)rcMessage.content; if (![seatResponseMessage.audienceId isEqualToString:UserDefault(IM_USERID)]) { // 只处理和自己相关的消息 return; } if (seatResponseMessage.type == SEATRESPONSE_TEACHERAPPROVE && __blockSelf.micStatus == MICSTATUS_WAITING) { // [__blockSelf hideApplyingView]; [__blockSelf connectHostWithStatus:YES]; [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo]; } else if (seatResponseMessage.type == SEATRESPONSE_TEACHERREJECT) { [__blockSelf MBPShow:@"老师拒绝了您的连麦申请"]; __blockSelf.micStatus = MICSTATUS_NOMAL; [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo]; } } else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomDownSeat class]]) { // 成员下麦消息 return; } else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomDownSeat class]]) { // 成员主动下麦消息 return; } else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomClose class]]) { // 直播间已关闭 [__blockSelf MBPShow:@"直播已结束"]; 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 ([rcMessage.content isMemberOfClass:[RCTextMessage class]]) { [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo]; } else if ([rcMessage.content isMemberOfClass:[KSRCPauseLiveMessage class]]) { // 老师暂停直播 __blockSelf.isPauseLive = YES; [__blockSelf MBPShow:@"老师已暂停直播"]; if (__blockSelf.micStatus == MICSTATUS_WAITING) { [__blockSelf sendSeatMessageApply:NO]; } [__blockSelf resetConnectionStatus]; [__blockSelf resetNetTips]; [__blockSelf closeAlertView]; [__blockSelf hideApplyingView]; return; } else if ([rcMessage.content isMemberOfClass:[KSRCShopRushMessage class]]) { // KSRCShopRushMessage *rushMsg = (KSRCShopRushMessage *)rcMessage.content; NSString *contentMsg = [NSString stringWithFormat:@"%@ 正在抢购",[NSString returnNoNullStringWithString:rushMsg.userName]]; [__blockSelf showAnimationView:NO showMessag:contentMsg]; } else if ([rcMessage.content isMemberOfClass:[KSLiveBlockUser class]]) { // 黑名单 KSLiveBlockUser *blockMessage = (KSLiveBlockUser *)rcMessage.content; if ([blockMessage.userId isEqualToString:UserDefault(IM_USERID)]) { __blockSelf.blacklistFlag = YES; [__blockSelf resetConnectionStatus]; } } else if ([rcMessage.content isMemberOfClass:[KSLiveUnBlockUser class]]) { KSLiveUnBlockUser *unBlockMessage = (KSLiveUnBlockUser *)rcMessage.content; if ([unBlockMessage.userId isEqualToString:UserDefault(IM_USERID)]) { __blockSelf.blacklistFlag = NO; } } else if ([rcMessage.content isMemberOfClass:[KSDownSeatAllMessage class]]) { // 所有麦上用户下麦 if (__blockSelf.micStatus == MICSTATUS_CONNECTING) { [__blockSelf kickSeatWithMessage:@"您已被抱下麦"]; [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo]; } } else if ([rcMessage.content isMemberOfClass:[KSRejectAllSeatMessage class]]) { // 拒绝所有连麦申请 if (__blockSelf.micStatus == MICSTATUS_WAITING) { [__blockSelf MBPShow:@"老师拒绝了您的连麦申请"]; // 处理连麦状态 __blockSelf.micStatus = MICSTATUS_NOMAL; [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo]; [__blockSelf hideApplyingView]; } } } })); } } - (void)hideApplyView { if (self.applyView.isShow) { [self.applyView hideView]; } } - (void)hideApplyingView { if (self.applyingView.isShow) { [self.applyingView hideView]; } } // 发送人数同步消息 - (void)sendMemberCountMessage { KSLiveChatroomMemberCount *syncMessage = [[KSLiveChatroomMemberCount alloc] init]; syncMessage.count = self.totalCount; [self sendMessage:syncMessage displayMessage:NO callback:^(BOOL success) { }]; } #pragma mark ----- time manager delegate - (void)quitClassroomNotifer { [self MBPShow:@"直播课已结束!"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self quitAction]; }); } #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 ---- sendMessage/showMessage - (void)sendMessage:(RCMessageContent *)messageContent displayMessage:(BOOL)displayMessage callback:(void(^)(BOOL success))callback { if (_roomId == nil) { return; } messageContent.senderUserInfo = [RCIM sharedRCIM].currentUserInfo; if (messageContent == nil) { return; } __weak typeof(&*self) __weakself = self; [KSChatroomMessageCenter sendChatMessage:self.roomId content:messageContent success:^(long messageId) { dispatch_async(dispatch_get_main_queue(), ^{ RCMessage *message = [[RCMessage alloc] initWithType:ConversationType_CHATROOM targetId:__weakself.roomId direction:MessageDirection_SEND content:messageContent]; message.content.senderUserInfo = [RCIM sharedRCIM].currentUserInfo; message.senderUserId = UserDefault(IM_USERID); if (displayMessage) { [__weakself appendAndDisplayMessage:message]; } [__weakself.inputBar clearInputView]; callback(YES); }); } error:^(RCErrorCode errorCode, long messageId) { if (errorCode == RC_CHATROOM_NOT_EXIST) { dispatch_async(dispatch_get_main_queue(), ^{ [__weakself MBPShow:@"聊天已被解散,请退出后重进。"]; }); } dispatch_async(dispatch_get_main_queue(), ^{ [__weakself.inputBar clearInputView]; NSLog(@"发送失败,errorcode is: %ld",(long)errorCode); callback(NO); }); }]; } /** * 将消息加入本地数组 */ - (void)appendAndDisplayMessage:(RCMessage *)rcMessage { if (!rcMessage) { return; } KSRCMessageModel *model = [[KSRCMessageModel alloc] initWithMessage:rcMessage]; model.userInfo = rcMessage.content.senderUserInfo; if (!model.userInfo) { model.userInfo = [RCIMClient sharedRCIMClient].currentUserInfo; } if ([self appendMessageModel:model]) { 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:YES]; self.isNeedScrollToButtom=NO; } } return; } /** * 如果当前会话没有这个消息id,把消息加入本地数组 */ - (BOOL)appendMessageModel:(KSRCMessageModel *)model { if (!model.content) { return NO; } //这里可以根据消息类型来决定是否显示,如果不希望显示直接return NO //数量不可能无限制的大,这里限制收到消息过多时,就对显示消息数量进行限制。 //用户可以手动下拉更多消息,查看更多历史消息。 if (self.conversationDataRepository.count>500) { // NSRange range = NSMakeRange(0, 1); KSRCMessageModel *message = self.conversationDataRepository[0]; [[RCIMClient sharedRCIMClient]deleteMessages:@[@(message.messageId)]]; [self.conversationDataRepository removeObjectAtIndex:0]; [self.conversationMessageTableView reloadData]; } [self.conversationDataRepository addObject:model]; return YES; } /** * 判断消息是否在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 - 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. } */ #pragma mark ----- table data source - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.conversationDataRepository.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { KSRCMessageModel *model = [self.conversationDataRepository objectAtIndex:indexPath.row]; RCMessageContent *messageContent = model.content; KSChatroomTextCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KSChatroomTextCell"]; if(!cell){ cell = [[KSChatroomTextCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"KSChatroomTextCell"]; } if ([messageContent isMemberOfClass:[RCTextMessage class]] || [messageContent isMemberOfClass:[KSLiveChatroomWelcome class]] || [messageContent isMemberOfClass:[KSLiveChatroomLike class]] || [messageContent isMemberOfClass:[KSLiveChatroomSeatApply class]] || [messageContent isMemberOfClass:[KSLiveChatroomSeatResponse class]] || [messageContent isMemberOfClass:[KSDownSeatAllMessage class]] || [messageContent isMemberOfClass:[KSRejectAllSeatMessage class]] || [messageContent isMemberOfClass:[RCChatroomSeatsControl class]]){ [cell setDataModel:model createrId:self.createrId]; } return cell; } - (void)sendSeatMessageApply:(BOOL)isApply { if (self.micStatus == MICSTATUS_CONNECTING) { [self MBPShow:@"您已上麦"]; return; } SEATHANDLE type = isApply ? SEATHANDLE_APPLY : SEATHANDLE_CANCELAPPLY; KSLiveChatroomSeatApply *applyMessage = [[KSLiveChatroomSeatApply 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; MJWeakSelf; [self sendMessage:applyMessage displayMessage:YES callback:^(BOOL success) { if (success) { weakSelf.micStatus = isApply ? MICSTATUS_WAITING : MICSTATUS_NOMAL; } }]; } /** 点赞 */ - (void)praiseBtnPressed { NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; __weak __typeof(&*self)weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.21 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ if ([[NSDate date] timeIntervalSince1970] - self.lastClickPraiseTime >= 0.2) { KSLiveChatroomLike *praiseSendMessage = [[KSLiveChatroomLike alloc] init]; praiseSendMessage.counts = clickPraiseBtnTimes; [weakSelf sendMessage:praiseSendMessage displayMessage:NO callback:^(BOOL success) { }]; clickPraiseBtnTimes = 0; // self.likeCount += praiseSendMessage.counts; // [self countLikeMessageCount]; } }); KSLiveChatroomLike *praiseMessage = [[KSLiveChatroomLike alloc] init]; clickPraiseBtnTimes++; self.lastClickPraiseTime = currentTime; [self presentLikeMessageAnimation:praiseMessage]; } /** 赞动画 @param likeMessage 赞消息 */ - (void)presentLikeMessageAnimation:(KSLiveChatroomLike *)likeMessage { LiveRoomLikeLayer *layer = [[LiveRoomLikeLayer alloc] initWithName:@"Like"]; [self.view.layer addSublayer:layer]; CGRect tempFrame = [self.view 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]; } #pragma mark - setter & getter - (void)setMicStatus:(MICSTATUS)micStatus { _micStatus = micStatus; if (self.bottomView) { switch (micStatus) { case MICSTATUS_NOMAL: { [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_nomal"] forState:UIControlStateNormal]; } break; case MICSTATUS_WAITING: { [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seating_image"] forState:UIControlStateNormal]; } break; case MICSTATUS_CONNECTING: { [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_image"] forState:UIControlStateNormal]; } break; default: break; } } } #pragma mark ----- RCRTCEngineEventDelegate - (void)didRTCConnectionStateChanged:(RCRTCConnectionState)state { if (state == RCRTCConnectionStateConnecting) { NSLog(@"--------RCRTCConnectionStateConnecting"); } else if (state == RCRTCConnectionStateConnected) { NSLog(@"--------RCRTCConnectionStateConnected"); } } #pragma mark ----- RCRTCStatusReportDelegate - (void)didReportStatusForm:(RCRTCStatusForm *)form { // 获取丢包率 for (RCRTCStreamStat *stat in form.recvStats) { if ([stat.mediaType isEqualToString:@"video"]) { // 接收视频会出现视频黑的情况 bitRate = 0 if (stat.packetLoss >= PACKAGE_LOST) { // 丢包 if (self.lostStartTime == 0) { // 如果之前没有丢包 self.lostStartTime = [[NSDate date] timeIntervalSince1970]; } else { NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; if (currentTime - self.lostStartTime >= 8) { self.isLostPackage = YES; [self showBadNetTips]; } } } else { self.lostStartTime = 0; self.isLostPackage = NO; [self hideTipsView]; } if (stat.bitRate == 0) { if (self.bitErrorTime == 0) { self.bitErrorTime = [[NSDate date] timeIntervalSince1970]; } else { NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; if (currentTime - self.bitErrorTime >= 5) { self.isBitError = YES; [self showBadNetTips]; } } } else { self.isBitError = NO; self.bitErrorTime = 0; [self hideTipsView]; } } } // NSLog(@"%@",form.description); } - (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) { if (self.isBitError == NO && self.isLostPackage == NO) { self.netBadView.hidden = YES; self.videoView.hidden = NO; } } }) } #pragma mark --- lazying - (KSChatVideoView *)videoView { if (!_videoView) { _videoView = [[KSChatVideoView alloc] initWithFrame:self.view.bounds]; _videoView.backgroundColor = [UIColor clearColor]; } return _videoView; } - (UIView *)videoContainerView { if (!_videoContainerView) { _videoContainerView = [[UIView alloc] initWithFrame:CGRectZero]; _videoContainerView.backgroundColor = [UIColor clearColor]; } return _videoContainerView; } - (LiveRoomHeadView *)headView { if (!_headView) { _headView = [LiveRoomHeadView shareInstance]; MJWeakSelf; [_headView quitAction:^{ [weakSelf showQuitAlert]; }]; // [_headView quitAction:^(BOOL isQuit) { // if (isQuit) { // [weakSelf showQuitAlert]; // } // else { // [weakSelf displayTeacherInfo:CART_TYPE_NONE]; // } // }]; } return _headView; } - (void)showTeacherCenter { } - (void)showQuitAlert { if (self.isCloseRoom) { // 如果直播结束 直接点击退出房间 [self quitRoom]; } else if (self.micStatus == MICSTATUS_CONNECTING) { [self quitAction]; } else { MJWeakSelf; self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"是否退出直播间" leftButtonTitle:@"取消" rightTitle:@"退出" inView:self.view cancel:^{ } confirm:^{ [weakSelf quitAction]; }]; } } - (LiveRoomBottomView *)bottomView { if (!_bottomView) { _bottomView = [LiveRoomBottomView shareInstance]; MJWeakSelf; [_bottomView bottomClickAction:^(LIVEROOMACTION action) { [weakSelf bottomViewAction:action]; }]; } return _bottomView; } - (void)bottomViewAction:(LIVEROOMACTION)action { if (self.isCloseRoom) { [self MBPShow:@"直播已结束"]; return; } switch (action) { case LIVEROOMACTION_CHAT: // 聊天 { if (self.blacklistFlag == YES) { [self MBPShow:@"您已被老师禁言"]; return; } [_inputBar setHidden:NO]; [_inputBar setInputBarStatus:KSBottomBarStatusKeyboard]; } break; case LIVEROOMACTION_SEAT: // // 连麦按钮 { // 连麦按钮 // 判断是否是连麦状态 if (self.micStatus == MICSTATUS_CONNECTING) { // 连麦中 // 是否取消连麦 MJWeakSelf; self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"连麦中,确认取消连麦吗?" leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{ } confirm:^{ [weakSelf connectHostWithStatus:NO]; }]; } else if (self.micStatus == MICSTATUS_WAITING) { // 连麦申请中 // 弹窗取消申请 [self displayApplyingView]; } else { if (self.blacklistFlag == YES) { [self MBPShow:@"您已被老师禁言"]; return; } if (self.isCreaterInRoom == NO) { [self MBPShow:@"老师不在直播间"]; return; } if (self.isPauseLive == YES) { [self MBPShow:@"老师暂停中,暂时无法接受邀请"]; return; } if (self.enableSeat) { // 显示弹窗 [self displaySeatApplyView]; } else { [self MBPShow:@"老师关闭连麦申请"]; } } } break; case LIVEROOMACTION_SHOP: { [self showCartAlert]; } break; case LIVEROOMACTION_LIKE: { if (self.isCreaterInRoom) { [self praiseBtnPressed]; } else { [self MBPShow:@"老师不在直播间"]; } } break; // case LIVEROOMACTION_SHARE: // { // [self showShareAlert]; // } // break; default: break; } } - (void)showShareAlert { NSString *qrUrl = [NSString stringWithFormat:@"%@%@?id=%@&userId=%@",WEBHOST,@"/#/shareLiveRoom",self.roomId,UserDefault(UIDKey)]; self.shareView = [ShareLiveDisplayView shareInstance]; [self.shareView configWithTeacherAvatar:self.createrAvatal teacherName:self.createrName qrUrl:qrUrl]; MJWeakSelf; [self.shareView shareActionCallback:^(SHARETYPE type) { UIImage *image = [UIImage getCurrentViewShot:weakSelf.shareView.containerView]; if (type == SHARETYPE_SAVE) { [weakSelf savePicWithImage:image]; } else { [weakSelf showShareUI:image]; } }]; [self.shareView showShareViewInView:self.view]; } - (void)displaySeatApplyView { [self.applyView showInView:self.view]; } - (void)displayApplyingView { [self.applyingView showInView:self.view]; } - (void)showCartAlert { [self.cardView showViewInView:self.view]; } - (void)quitAction { if (self.micStatus == MICSTATUS_CONNECTING) { MJWeakSelf; self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"连麦中,是否退出房间?" leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{ } confirm:^{ [weakSelf quitRoom]; }]; } else { [self quitRoom]; } } - (void)quitRoom { KSDragWindowManager *manager = [KSDragWindowManager sharedManager]; if (manager.hasShowWindow) { [manager resignDragWindow]; } // 清理视图 [self clearRemoteContainer]; // 退出房间 [self exitRoom]; [self.navigationController dismissViewControllerAnimated:YES completion:nil]; } - (RCRTCEngine *)engine { if (!_engine) { _engine = [RCRTCEngine sharedInstance]; [_engine setStatusReportDelegate:self]; [_engine setDelegate:self]; } return _engine; } - (NSMutableArray *)streamVideos { if (!_streamVideos) { _streamVideos = [NSMutableArray array]; } return _streamVideos; } - (KSLiveStreamVideo *)localVideo { if (!_localVideo) { _localVideo = [KSLiveStreamVideo LocalStreamVideo]; } return _localVideo; } - (UIView *)messageContentView { if (!_messageContentView) { _messageContentView = [[UIView alloc] init]; [_messageContentView setBackgroundColor: [UIColor clearColor]]; } return _messageContentView; } - (NSMutableArray *)conversationDataRepository { if (!_conversationDataRepository) { _conversationDataRepository = [NSMutableArray array]; } return _conversationDataRepository; } - (KSChatInputBarControl *)inputBar { if (!_inputBar) { _inputBar = [[KSChatInputBarControl alloc] initWithStatus:KSBottomBarStatusDefault]; [_inputBar setDelegate:self]; } return _inputBar; } - (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:[KSChatroomTextCell class] forCellReuseIdentifier:@"KSChatroomTextCell"]; } return _conversationMessageTableView; } - (SeatContentView *)seatContainer { if (!_seatContainer) { _seatContainer = [[SeatContentView alloc] init]; } return _seatContainer; } - (KSLiveEmptyView *)emptyView { if (!_emptyView) { _emptyView = [[KSLiveEmptyView alloc] init]; _emptyView.backgroundColor = [UIColor clearColor]; BOOL isPhoneLive = YES; [_emptyView dispPlayViewIsPhoneLive:isPhoneLive]; } return _emptyView; } - (KSLiveEndView *)endView { if (!_endView) { _endView = [[KSLiveEndView alloc] init]; _endView.backgroundColor = HexRGB(0x25292e); BOOL isPhoneLive = YES; [_endView dispPlayViewIsPhoneLive:isPhoneLive]; } return _endView; } - (KSLiveBadNetView *)netBadView { if (!_netBadView) { _netBadView = [[KSLiveBadNetView alloc] init]; BOOL isPhoneLive = YES; [_netBadView dispPlayViewIsPhoneLive:isPhoneLive]; MJWeakSelf; [_netBadView configViewWithImageName:@"liveVideo_close" desc:@"当前网络状态不佳" callback:^{ [weakSelf resetAllConnection]; }]; } return _netBadView; } - (LOTAnimationView *)animationView { if (!_animationView) { _animationView = [LOTAnimationView animationWithFilePath:[[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"LiveCard" ofType:@"bundle"]] pathForResource:@"cardAnimation" ofType:@"json"]]; _animationView.loopAnimation = YES; } return _animationView; } - (void)resetNetTips { self.lostStartTime = 0; self.isBitError = NO; self.bitErrorTime = 0; self.isLostPackage = NO; [self hideTipsView]; } // 黑名单 - (void)setBlacklistFlag:(BOOL)blacklistFlag { _blacklistFlag = blacklistFlag; [self refreshBottomChatStatus]; } - (void)refreshBottomChatStatus { if (self.bottomView) { NSString *tipsTitle = self.blacklistFlag ? @"您已被老师禁言" : @"快来互动吧!"; self.bottomView.tipsLabel.text = tipsTitle; } } - (void)resetAllConnection { [self resetNetTips]; // 清理视图 [self.streamVideos removeAllObjects]; [self clearVideoViewSource]; [self removeSeatContainer]; // 立刻房间后重新加入房间 // 退出房间 [self.engine.defaultAudioStream setMicrophoneDisable:YES]; __weak typeof(self) weakSelf = self; [self.engine leaveRoom:^(BOOL isSuccess, RCRTCCode code) { __strong typeof(weakSelf) strongSelf = weakSelf; if (code != RCRTCCodeSuccess) { // 退出失败 } // 3.加入RTC房间 [strongSelf joinLiveRoom]; }]; } - (void)sendDownSeatMessage { KSLiveChatroomDownSeat *downSeatMessage = [[KSLiveChatroomDownSeat alloc] init]; downSeatMessage.audienceId = UserDefault(IM_USERID); downSeatMessage.audienceName = UserDefault(NicknameKey); MJWeakSelf; [self sendMessage:downSeatMessage displayMessage:NO callback:^(BOOL success) { weakSelf.micStatus = MICSTATUS_NOMAL; }]; } - (LiveroomTimeManager *)timeManager { if (!_timeManager) { _timeManager = [[LiveroomTimeManager alloc] initWithDelegate:self]; } return _timeManager; } - (NSInteger)getCloseTime { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; formatter.timeZone = [NSTimeZone systemTimeZone]; NSDate *currentDate = [NSDate date]; NSDate *expireDate = [formatter dateFromString:self.liveEndTime]; NSTimeInterval timeInterval = [expireDate timeIntervalSinceDate:currentDate]; return self.expiredMinute * 60 + timeInterval; } - (KSShopCardView *)cardView { if (!_cardView) { _cardView = [KSShopCardView shareInstance]; MJWeakSelf; [_cardView clickAction:^(CART_TYPE type) { [weakSelf displayTeacherInfo:type]; }]; } return _cardView; } // tabs= practice | 陪练课 live | 直播课 video | 视频课 music | 乐谱 跳转到对应tab页 - (void)displayTeacherInfo:(CART_TYPE)type { [self sendRushMessage]; RCRTCVideoView *videoView = nil; for (UIView *view in self.videoView.subviews) { if ([view isKindOfClass:[RCRTCVideoView class]]) { videoView = (RCRTCVideoView *)view; } } if (videoView) { _hasShowSuspendView = YES; KSDragWindowManager *manager = [KSDragWindowManager sharedManager]; [manager initDragWindowWithPhoneLive:YES]; manager.rootVC = (CustomNavViewController *)self.navigationController; [videoView removeFromSuperview]; [manager.dragWindow.containerView addSubview:videoView]; [videoView 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_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.createrId]; } else { url = [NSString stringWithFormat:@"%@%@%@&tabs=%@", WEBHOST,@"/#/teacherHome?teacherId=",self.createrId,tabs]; } KSLiveWebViewController *ctrl = [[KSLiveWebViewController alloc] init]; ctrl.url = url; [self.navigationController pushViewController:ctrl animated:YES]; } - (void)sendRushMessage { // KSRCShopRushMessage *rushMessage = [[KSRCShopRushMessage alloc] init]; // rushMessage.userId = UserDefault(UIDKey); // rushMessage.userName = UserDefault(NicknameKey); // [self sendMessage:rushMessage displayMessage:NO callback:^(BOOL success) { // // }]; } - (void)showAnimationView:(BOOL)isJoinRoom showMessag:(NSString *)message { 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; }]; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (LiveApplyView *)applyView { if (!_applyView) { _applyView = [LiveApplyView shareInstance]; MJWeakSelf; [_applyView applySeatCallback:^{ [weakSelf applySeatAction]; }]; } return _applyView; } - (void)applySeatAction { [self MBPShow:@"已发起连麦申请"]; [self sendSeatMessageApply:YES]; } - (LiveApplyingView *)applyingView { if (!_applyingView) { _applyingView = [LiveApplyingView shareInstance]; NSString *avatar = UserDefaultObjectForKey(AvatarUrlKey); [_applyingView.myAvatal sd_setImageWithURL:[NSURL URLWithString:[avatar getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:USERDEFAULT_LOGO]]; [_applyingView.teacherAvatal sd_setImageWithURL:[NSURL URLWithString:[self.createrAvatal getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:USERDEFAULT_LOGO]]; MJWeakSelf; [_applyingView cancleApplyCallback:^{ [weakSelf cancleApplyAction]; }]; } return _applyingView; } - (void)cancleApplyAction { MJWeakSelf; self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"连麦申请中,确认取消连麦申请吗?" leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{ } confirm:^{ [weakSelf.applyingView hideView]; [weakSelf sendSeatMessageApply:NO]; }]; } - (void)showShareUI:(UIImage *)image { [KSUMShareManager shareInstanceWithImage:image url:@"" shareTitle:@"" descMessage:@"" shareType:KSSHARETYPE_IMAGE showInView:self callback:^(BOOL isSuccess, NSString * _Nonnull descMessage) { if (isSuccess) { [self.shareView hideView]; } else { } [self MBPShow:descMessage]; }]; } - (void)savePicWithImage:(UIImage *)image { // 判断相册权限 PREMISSIONTYPE albumEnable = [RecordCheckManager checkPhotoLibraryPremissionAvaiable:NO showInView:nil]; if (albumEnable == PREMISSIONTYPE_YES) { // 如果有权限 [[TZImageManager manager] savePhotoWithImage:image completion:^(PHAsset *asset, NSError *error) { if (!error) { [self .shareView hideView]; [self MBPShow:@"保存成功"]; } else { [self MBPShow:@"保存图片失败"]; } }]; } else { if (albumEnable == PREMISSIONTYPE_NO) { [self showAlertWithMessage:@"请开启相册访问权限" type:CHECKDEVICETYPE_CAMREA]; } } } - (void)showAlertWithMessage:(NSString *)message type:(CHECKDEVICETYPE)deviceType { [KSPremissionAlert shareInstanceDisplayImage:deviceType message:message showInView:self.view cancel:^{ } confirm:^{ [self openSettingView]; }]; } - (void)openSettingView { if (@available(iOS 10, *)) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil]; } else { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; } } @end