|
@@ -7,18 +7,806 @@
|
|
|
//
|
|
|
|
|
|
#import "ClassroomViewController.h"
|
|
|
+#import "ClassTitleView.h"
|
|
|
+#import "ClassroomMainContainer.h"
|
|
|
+#import "ClassVideoListView.h"
|
|
|
+#import "KSNormalAlertView.h"
|
|
|
|
|
|
-@interface ClassroomViewController ()
|
|
|
+#import "RTCService.h"
|
|
|
+#import "Classroom.h"
|
|
|
+#import "ClassroomService.h"
|
|
|
+#import <MBProgressHUD/MBProgressHUD.h>
|
|
|
+#import "AppDelegate.h"
|
|
|
+#import "UIDevice+TFDevice.h"
|
|
|
+#import "KSRemoteUserManager.h"
|
|
|
+#import "LocalRenderManager.h"
|
|
|
+#import "NSDate+Transform.h"
|
|
|
+
|
|
|
+#import "UIViewController+zhStatusBarStyle.h"
|
|
|
+#import "KSTipsView.h"
|
|
|
+#import "RoomLoginHelper.h"
|
|
|
+
|
|
|
+#define TitleViewHeight (64)
|
|
|
+
|
|
|
+@interface ClassroomViewController ()<ClassTitleViewDelegate, RongRTCRoomDelegate, ClassroomDelegate, ClassroomMainContainerDelegate, UIGestureRecognizerDelegate, RongRTCActivityMonitorDelegate, RTCServiceDelegate, ClassVideoListViewDelegate>
|
|
|
+
|
|
|
+@property (nonatomic, strong) ClassTitleView *titleView;
|
|
|
+@property (nonatomic, strong) ClassroomMainContainer *containerView;
|
|
|
+@property (nonatomic, strong) ClassVideoListView *videoListView;
|
|
|
+@property (nonatomic, strong) MBProgressHUD *hud;
|
|
|
+@property (nonatomic, strong) NSMutableArray<NSNumber*>* packetLossStore;
|
|
|
+
|
|
|
+@property (nonatomic, strong) UILabel *networkLabel;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL cancleAlert;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL isQuitRoom; // 是否正在退出房间
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL hasShowAlert;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL isSwitchLine; // 是否线路切换
|
|
|
+
|
|
|
+@property (nonatomic, strong) KSTipsView *tipsView;
|
|
|
|
|
|
@end
|
|
|
|
|
|
@implementation ClassroomViewController
|
|
|
|
|
|
+#pragma mark - Life cycle
|
|
|
+
|
|
|
+- (void)viewWillAppear:(BOOL)animated {
|
|
|
+ [super viewWillAppear:animated];
|
|
|
+ self.zh_statusBarHidden = YES;
|
|
|
+ // 切换到横屏
|
|
|
+ AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
|
|
|
+ delegate.allowAutoRotate = YES;
|
|
|
+ [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
|
|
|
+ [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
|
|
|
+ [RongRTCEngine sharedEngine].monitorDelegate = self;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewWillDisappear:(BOOL)animated {
|
|
|
+ [super viewWillDisappear:animated];
|
|
|
+ // 竖屏
|
|
|
+ AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
|
|
|
+ delegate.allowAutoRotate = NO;
|
|
|
+ [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait];
|
|
|
+ [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
|
|
|
+ self.zh_statusBarHidden = NO;
|
|
|
+}
|
|
|
+
|
|
|
- (void)viewDidLoad {
|
|
|
[super viewDidLoad];
|
|
|
// Do any additional setup after loading the view.
|
|
|
+
|
|
|
+ self.view.backgroundColor = [UIColor colorWithHexString:@"141414" alpha:1];
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(leaveRoomAction) name:@"classroomLogout" object:nil];
|
|
|
+ // 切换到横屏
|
|
|
+ AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
|
|
|
+ delegate.allowAutoRotate = YES;
|
|
|
+ [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
|
|
|
+ [self addSubviews];
|
|
|
+ [self bindDelegates];
|
|
|
+ [self publishStream];
|
|
|
+ [self renderMainContainerView];
|
|
|
+ [[RTCService sharedInstance] useSpeaker:YES];
|
|
|
+ UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
|
|
|
+ [self.view addGestureRecognizer:tapGes];
|
|
|
+ tapGes.delegate = self;
|
|
|
+ [self showRoleHud];
|
|
|
+ self.isQuitRoom = NO;
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundQuitRoomAction) name:@"backgroundQuit" object:nil];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)backgroundQuitRoomAction {
|
|
|
+ self.isQuitRoom = YES;
|
|
|
+ self.isSwitchLine = NO;
|
|
|
+ [[RoomLoginHelper sharedInstance] logout:^{
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ }];
|
|
|
+ self.cancleAlert = YES;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ----- 账号异地登陆
|
|
|
+- (void)leaveRoomAction {
|
|
|
+ [self showMessage:@"该账号在其他地方登录"];
|
|
|
+ self.isQuitRoom = YES;
|
|
|
+ self.isSwitchLine = NO;
|
|
|
+ [[RoomLoginHelper sharedInstance] logout:^{
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ }];
|
|
|
+ self.cancleAlert = YES;
|
|
|
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil];
|
|
|
+}
|
|
|
+
|
|
|
+// 提示信息
|
|
|
+- (void)showMessage:(NSString *)message {
|
|
|
+ MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
|
|
|
+ [self.view bringSubviewToFront:hud];
|
|
|
+ hud.removeFromSuperViewOnHide =YES;
|
|
|
+ hud.mode = MBProgressHUDModeText;
|
|
|
+ hud.label.text = message;
|
|
|
+ hud.minSize = CGSizeMake(132.f, 60.0f);
|
|
|
+ hud.label.textColor = [UIColor whiteColor];
|
|
|
+ hud.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
|
|
|
+ [hud hideAnimated:YES afterDelay:2];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)tapGesture: (UITapGestureRecognizer *)tapGesture {
|
|
|
+ self.titleView.isDisplay = YES;
|
|
|
+}
|
|
|
+-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
|
|
|
+ if ([touch.view isDescendantOfView:self.videoListView]) {
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+ if ([touch.view isDescendantOfView:self.containerView]) {
|
|
|
+ [self tapGesture:(UITapGestureRecognizer *)gestureRecognizer];
|
|
|
+ }
|
|
|
+ return YES;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showRoleHud {
|
|
|
+ Role role = [ClassroomService sharedService].currentRoom.currentMember.role;
|
|
|
+ if(role == RoleAudience) {
|
|
|
+ [self.tipsView showTipsMessage:@"你当前身份是旁听人,其他人看/听不见你" inView:self.view];
|
|
|
+
|
|
|
+ [self performSelector:@selector(showOnlyYouHUD) withObject:nil afterDelay:5.0f];
|
|
|
+ }else{
|
|
|
+ [self showOnlyYouHUD];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showOnlyYouHUD {
|
|
|
+ if ([ClassroomService sharedService].currentRoom.memberList.count == 1) {
|
|
|
+ [self.tipsView showTipsMessage:@"当前课堂只有你一人,你可以等待或离开" inView:self.view];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark --- ClassTitleViewDelegate
|
|
|
+- (void)classTitleView:(UIButton *)button didTapAtTag:(ClassTitleViewActionTag)tag {
|
|
|
+ switch (tag) {
|
|
|
+ case ClassTitleViewActionTagMicrophone:
|
|
|
+ {
|
|
|
+ if (button.selected == NO) {
|
|
|
+ [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭麦克风吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
|
|
|
+ } confirm:^{
|
|
|
+ button.selected = YES;
|
|
|
+ [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeMicrophone];
|
|
|
+ }];
|
|
|
+
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ button.selected = NO;
|
|
|
+ [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeMicrophone];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ClassTitleViewActionTagCamera:
|
|
|
+ {
|
|
|
+ if (button.selected == NO) {
|
|
|
+ [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭摄像头吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
|
|
|
+ } confirm:^{
|
|
|
+ button.selected = YES;
|
|
|
+ [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeCamera];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ button.selected = NO;
|
|
|
+ [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeCamera];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ClassTitleViewActionTagSwitchLine: // 切换线路
|
|
|
+ {
|
|
|
+ [KSNormalAlertView ks_showAlertWithTitle:@"确认切换线路吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
|
|
|
+ } confirm:^{
|
|
|
+ [self restartRoom];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ClassTitleViewActionTagSwitchCamera:
|
|
|
+ {
|
|
|
+ [[RTCService sharedInstance] switchCamera];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case ClassTitleViewActionTagHangup:
|
|
|
+ {
|
|
|
+ [KSNormalAlertView ks_showAlertWithTitle:@"确认退出课堂吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
|
|
|
+ } confirm:^{
|
|
|
+ self.isQuitRoom = YES;
|
|
|
+ SealClassLog(@"ActionTagHangup");
|
|
|
+ self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
|
|
|
+ [[RoomLoginHelper sharedInstance] logout:^{
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ }];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 判断是否需要退出房间
|
|
|
+- (void)refreshClassStatus {
|
|
|
+ /*
|
|
|
+ NSString *userType = UserDefault(UserTypeKey);
|
|
|
+ if (![userType isEqualToString:@"1"] && self.isQuitRoom == NO) {
|
|
|
+ NSString *formatString = @"yyyy-MM-dd HH:mm:ss";
|
|
|
+ NSDate *endDate = [NSDate dateFromString:self.classEndTime format:formatString];
|
|
|
+ NSTimeInterval endDuration = [[NSDate date] timeIntervalSinceDate:endDate];
|
|
|
+ if (endDuration >= self.quitRoomTime * 60) {
|
|
|
+ self.isQuitRoom = YES;
|
|
|
+ // 退出房间
|
|
|
+ [self showMessage:@"课程已结束"];
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ // 课程结束退出房间
|
|
|
+ SealClassLog(@"ClassEnd!");
|
|
|
+ self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
|
|
|
+ [[LoginHelper sharedInstance] logout:^{
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ }];
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ */
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ------ 退出RTC房间并重新加入
|
|
|
+- (void)restartRoom {
|
|
|
+ FwLogI(RC_Type_RTC, @"L-SwitchLine-S", @"UserId:%@", [ClassroomService sharedService].currentRoom.currentMemberId);
|
|
|
+ self.isSwitchLine = YES;
|
|
|
+ self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
|
|
|
+ [[RoomLoginHelper sharedInstance] logout:^{
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)reJoinRTCRoom {
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ NSString *roomId = [RoomLoginHelper sharedInstance].roomId;
|
|
|
+ NSString *userName = [RoomLoginHelper sharedInstance].userName;
|
|
|
+ [[ClassroomService sharedService] joinClassroom:roomId userName:userName isAudience:NO success:^(Classroom * _Nonnull classroom) {
|
|
|
+ self.isSwitchLine = NO;
|
|
|
+ [self restartRTCConnection];
|
|
|
+ } error:^(ErrorCode errorCode) {
|
|
|
+ [self showMessage:@"切换线路失败"];
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ self.isSwitchLine = NO;
|
|
|
+ }];
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+- (void)restartRTCConnection {
|
|
|
+
|
|
|
+ [[RTCService sharedInstance] joinRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^(RongRTCRoom * _Nonnull room) {
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ [self showMessage:@"切换线路成功"];
|
|
|
+ // 重置UI 重新发布音视频流
|
|
|
+ [LocalRenderManager shareInstance].hadRenderMainView = NO;
|
|
|
+ [self publishStream];
|
|
|
+ [self bindDelegates];
|
|
|
+ [self.titleView refreshTitleView];
|
|
|
+ [self.containerView refreshToolView];
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ [RongRTCEngine sharedEngine].monitorDelegate = self;
|
|
|
+ [[RTCService sharedInstance] useSpeaker:YES];
|
|
|
+ [self renderMainContainerView];
|
|
|
+ [self.videoListView reloadVideoList];
|
|
|
+
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ [self showMessage:@"切换线路失败!"];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - ClassroomDelegate
|
|
|
+- (void)roomDidLeave {
|
|
|
+ NSLog(@"roomDidLeave");
|
|
|
+ if (_isSwitchLine) {
|
|
|
+ if ([RTCService sharedInstance].rtcRoom) {
|
|
|
+ [[KSRemoteUserManager shareInstance] removeAllUser];
|
|
|
+ [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
|
|
|
+ [self reJoinRTCRoom];
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ [self showMessage:@"切换线路失败"];
|
|
|
+ self.isSwitchLine = NO;
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [[KSRemoteUserManager shareInstance] removeAllUser];
|
|
|
+ if ([RTCService sharedInstance].rtcRoom) {
|
|
|
+ [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
|
|
|
+ } error:^(RongRTCCode code) {
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ [self dismissClassroom];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)dismissClassroom {
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ [ClassroomService sharedService].currentRoom = nil;
|
|
|
+ [self dismissViewControllerAnimated:NO completion:^{
|
|
|
+ [self.titleView stopDurationTimer];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)memberDidJoin:(RoomMember *)member {
|
|
|
+ NSLog(@"memberDidJoin %@",member);
|
|
|
+ if(member.role != RoleAudience) {
|
|
|
+ [self.videoListView reloadVideoList];
|
|
|
+ }
|
|
|
+ if (member.role == RoleTeacher || member.role == RoleAssistant) {
|
|
|
+ [self.tipsView showTipsMessage:[NSString stringWithFormat:@"老师%@已经进入教室", member.name] inView:self.view];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)memberDidLeave:(RoomMember *)member {
|
|
|
+ NSLog(@"memberDidLeave %@",member);
|
|
|
+ // 此处移除
|
|
|
+ [[KSRemoteUserManager shareInstance] removeUser:member.userId];
|
|
|
+
|
|
|
+ if(member.role != RoleAudience) {
|
|
|
+ [self.videoListView reloadVideoList];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)memberDidKick:(RoomMember *)member {
|
|
|
+ NSLog(@"memberDidKick %@",member);
|
|
|
+ [[KSRemoteUserManager shareInstance] removeUser:member.userId];
|
|
|
+ if ([ClassroomService sharedService].currentRoom.currentMember.role == RoleTeacher) {
|
|
|
+ [self.tipsView showTipsMessage:[NSString stringWithFormat:@"你已将%@移出课堂", member.name] inView:self.view];
|
|
|
+ }
|
|
|
+ [self.videoListView reloadVideoList];
|
|
|
+ if (self.containerView.member.role == member.role) {
|
|
|
+ [self.containerView cancelRenderView];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)errorDidOccur:(ErrorCode)code {
|
|
|
+ NSLog(@"errorDidOccur %@",@(code));
|
|
|
+ [self.hud hideAnimated:YES];
|
|
|
+ if (code != ErrorCodeSuccess) {
|
|
|
+ if (code == ErrorCodeOverMaxUserCount) {
|
|
|
+ [self.tipsView showTipsMessage:@"超过人数限制" inView:self.view];
|
|
|
+ }else {
|
|
|
+ [self.tipsView showTipsMessage:@"操作失败,请稍后再试" inView:self.view];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)deviceDidEnable:(BOOL)enable type:(DeviceType)type forUser:(RoomMember *)member operator:(nonnull NSString *)operatorId {
|
|
|
+ NSLog(@"deviceDidEnable devicetype:%@ enable:%@ memeber:%@",@(type),@(enable),member);
|
|
|
+ RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
|
|
|
+ NSString *hudMessage = @"";
|
|
|
+ //只有老师和自己才有提示
|
|
|
+ if (curMember.role == RoleTeacher && ![curMember.userId isEqualToString:operatorId]) {
|
|
|
+ if (type == DeviceTypeCamera) {
|
|
|
+ hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraOpen", @"SealClass", nil),member.name];
|
|
|
+ } else if (type == DeviceTypeMicrophone) {
|
|
|
+ hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneOpen", @"SealClass", nil),member.name];
|
|
|
+ }
|
|
|
+ else if (type == DeviceTypeMusicMode) {
|
|
|
+ hudMessage = !enable ? [NSString stringWithFormat:@"%@的音乐模式已关闭",member.name] : [NSString stringWithFormat:@"%@的音乐模式已开启",member.name];
|
|
|
+ }
|
|
|
+ [self.tipsView showTipsMessage:hudMessage inView:self.view];
|
|
|
+ } else {
|
|
|
+ if ([curMember.userId isEqualToString:member.userId]) {
|
|
|
+ if (type == DeviceTypeCamera) { // 摄像头
|
|
|
+ // 非自己操作才需要做提示
|
|
|
+ if(![curMember.userId isEqualToString:operatorId]){
|
|
|
+ hudMessage = !enable ? @"老师已关闭你的摄像头" : NSLocalizedStringFromTable(@"CameraOpend", @"SealClass", nil);
|
|
|
+ [self.tipsView showTipsMessage:hudMessage inView:self.view];
|
|
|
+ }
|
|
|
+
|
|
|
+ [self.containerView refreshToolView];
|
|
|
+ [[RTCService sharedInstance] setCameraDisable:!enable];
|
|
|
+ } else if (type == DeviceTypeMicrophone) { // 麦克风
|
|
|
+ // 非自己操作才需要做提示
|
|
|
+ if(![curMember.userId isEqualToString:operatorId]){
|
|
|
+ hudMessage = !enable ? @"老师已关闭你的麦克风" : NSLocalizedStringFromTable(@"MicorophoneOpend", @"SealClass", nil);
|
|
|
+ [self.tipsView showTipsMessage:hudMessage inView:self.view];
|
|
|
+ }
|
|
|
+
|
|
|
+ [self.containerView refreshToolView];
|
|
|
+ [[RTCService sharedInstance] setMicrophoneDisable:!enable];
|
|
|
+
|
|
|
+ }
|
|
|
+ else if (type == DeviceTypeMusicMode) {
|
|
|
+ // 非自己操作才需要做提示
|
|
|
+ if(![curMember.userId isEqualToString:operatorId]) {
|
|
|
+// hudMessage = !enable ? NSLocalizedStringFromTable(@"老师关闭了你的音乐模式", @"SealClass", nil) : NSLocalizedStringFromTable(@"音乐模式已打开", @"SealClass", nil);
|
|
|
+// [self.tipsView showTipsMessage:hudMessage inView:self.view];
|
|
|
+ }
|
|
|
+
|
|
|
+ [self.containerView refreshToolView];
|
|
|
+ [self changeModeAction:enable];
|
|
|
+ }
|
|
|
+ else if (type == DeviceTypeHandup) {
|
|
|
+// if ([curMember.userId isEqualToString:operatorId]) {
|
|
|
+// if (enable) {
|
|
|
+// [self.tipsView showTipsMessage:@"您已举手" inView:self.view];
|
|
|
+// }
|
|
|
+// }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ([ClassroomService sharedService].currentRoom.currentDisplayType != DisplayWhiteboard && type == DeviceTypeCamera) {
|
|
|
+ // 延迟调用,防止刷新出现灰屏
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ [self renderMainContainerView];
|
|
|
+ });
|
|
|
+ }
|
|
|
+ [self.titleView refreshTitleView];
|
|
|
+ [self.containerView refreshToolView];
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+//助教请求用户打开设备,助教关闭用户设备没有回调。
|
|
|
+- (void)deviceDidInviteEnable:(DeviceType)type ticket:(NSString *)ticket {
|
|
|
+ NSLog(@"deviceDidInviteEnable devicetype:%@ ticket:%@ ",@(type),ticket);
|
|
|
+ if (type == DeviceTypeCamera) {
|
|
|
+ [KSNormalAlertView ks_showAlertWithTitle:@"老师邀请你打开摄像头" leftTitle:@"拒绝" rightTitle:@"同意" cancel:^{
|
|
|
+ [[ClassroomService sharedService] rejectEnableDevice:ticket];
|
|
|
+ } confirm:^{
|
|
|
+ [[ClassroomService sharedService] approveEnableDevice:ticket];
|
|
|
+ }];
|
|
|
+ }else if (type == DeviceTypeMicrophone) {
|
|
|
+ [KSNormalAlertView ks_showAlertWithTitle:@"老师邀请你打开麦克风" leftTitle:@"拒绝" rightTitle:@"同意" cancel:^{
|
|
|
+ [[ClassroomService sharedService] rejectEnableDevice:ticket];
|
|
|
+ } confirm:^{
|
|
|
+ [[ClassroomService sharedService] approveEnableDevice:ticket];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [[ClassroomService sharedService] approveEnableDevice:ticket];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//只有助教能收到这个回调,可以不在这里处理文字,因为设备的回调还会走
|
|
|
+- (void)deviceInviteEnableDidApprove:(RoomMember *)member type:(DeviceType)type {
|
|
|
+ NSLog(@"deviceInviteEnableDidApprove devicetype:%@ member:%@ ",@(type),member);
|
|
|
+}
|
|
|
+
|
|
|
+//拒绝只有助教能收到这个回调,且只走这个回调
|
|
|
+- (void)deviceInviteEnableDidReject:(RoomMember *)member type:(DeviceType)type {
|
|
|
+ NSLog(@"deviceInviteEnableDidReject devicetype:%@ member:%@ ",@(type),member);
|
|
|
+ [self.tipsView showTipsMessage:[NSString stringWithFormat:@"%@拒绝了你的邀请",member.name] inView:self.view];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - private method
|
|
|
+- (void)bindDelegates {
|
|
|
+ self.titleView.delegate = self;
|
|
|
+ [[RTCService sharedInstance] setRTCRoomDelegate:self];
|
|
|
+ [RTCService sharedInstance].delegate = self;
|
|
|
+ [ClassroomService sharedService].classroomDelegate = self;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)addSubviews {
|
|
|
+ [self.view addSubview:self.containerView];
|
|
|
+ [self.view addSubview:self.titleView];
|
|
|
+ [self.view addSubview:self.videoListView];
|
|
|
+ [self.view bringSubviewToFront:self.titleView];
|
|
|
+}
|
|
|
+
|
|
|
+- (CGRect)mainContainerViewFrame {
|
|
|
+ CGFloat x = iPhoneXSafeTopMargin;
|
|
|
+ CGFloat y = 0;
|
|
|
+ CGFloat width = UIScreenWidth - x - iPhoneXSafeBottomMargin;
|
|
|
+ CGFloat height = UIScreenHeight - y;
|
|
|
+ return CGRectMake(x, y, width, height);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (CGRect)getWBoardFrame {
|
|
|
+ CGRect mainVideoFrame = self.containerView.currentVideoFrame;
|
|
|
+ mainVideoFrame.origin.x += iPhoneXSafeTopMargin;
|
|
|
+ mainVideoFrame.origin.y = 10;
|
|
|
+ return mainVideoFrame;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+- (void)publishStream {
|
|
|
+ if ([ClassroomService sharedService].currentRoom.currentMember.role != RoleAudience) {
|
|
|
+ [[RTCService sharedInstance] publishLocalUserDefaultAVStream];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)renderMainContainerView {
|
|
|
+ self.titleView.onShowLabel.hidden = YES;
|
|
|
+ RoomMember *teacher = [ClassroomService sharedService].currentRoom.teacher;
|
|
|
+ if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent) {
|
|
|
+ // 将之前订阅的流切换成小流
|
|
|
+ [self changeLastDisplayToTinyStream];
|
|
|
+ // 有打开的屏幕
|
|
|
+ for (RoomMember *displayMember in [ClassroomService sharedService].currentRoom.memberList) {
|
|
|
+ if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI]) {
|
|
|
+ if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
|
|
|
+ self.titleView.onShowLabel.hidden = NO;
|
|
|
+ }
|
|
|
+ [self.containerView containerViewRenderView:displayMember];
|
|
|
+ [self.videoListView showTeacherPrompt:NO];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 将之前订阅的流切换成小流
|
|
|
+ [self changeLastDisplayToTinyStream];
|
|
|
+
|
|
|
+ if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTeacher && teacher.cameraEnable)) {
|
|
|
+ NSLog(@"%@",[ClassroomService sharedService].currentRoom.currentDisplayURI);
|
|
|
+ [self.containerView containerViewRenderView:[ClassroomService sharedService].currentRoom.teacher];
|
|
|
+ [self.videoListView showTeacherPrompt:YES];
|
|
|
+ } else if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayWhiteboard) {
|
|
|
+ [self.containerView cancelRenderView];
|
|
|
+ [self.videoListView showTeacherPrompt:NO];
|
|
|
+ } else if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplaySharedScreen)) {
|
|
|
+ [[RTCService sharedInstance] renderUserSharedScreenOnView:self.containerView.videoView forUser:[ClassroomService sharedService].currentRoom.currentDisplayURI];
|
|
|
+ // 分享视频
|
|
|
+ [self.videoListView showTeacherPrompt:NO];
|
|
|
+ } else {
|
|
|
+ [self.containerView cancelRenderView];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)changeLastDisplayToTinyStream {
|
|
|
+ RoomMember *lastMember = self.containerView.member;
|
|
|
+ if (![NSString isEmptyString:lastMember.userId] && ![lastMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI] && ![lastMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
|
|
|
+ [[RTCService sharedInstance] exchangeRemoteUserAVStreamToTinySteam:lastMember.userId callback:^{
|
|
|
+
|
|
|
+ }];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)whiteboardDidDisplay:(NSString *)boardId {
|
|
|
+ NSLog(@"whiteboardDidDisplay %@ ",boardId);
|
|
|
+ [self renderMainContainerView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)sharedScreenDidDisplay:(NSString *)userId {
|
|
|
+ NSLog(@"sharedScreenDidDisplay %@ ",userId);
|
|
|
+ [self renderMainContainerView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)studentDidDisplay:(NSString *)studentId {
|
|
|
+ NSLog(@"studentDidDisplay %@", studentId);
|
|
|
+ [self renderMainContainerView];
|
|
|
+
|
|
|
+}
|
|
|
+- (void)teacherDidDisplay {
|
|
|
+ NSLog(@"teacherDidDisplay %@ ",[ClassroomService sharedService].currentRoom.teacher);
|
|
|
+ [self renderMainContainerView];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)noneDidDisplay {
|
|
|
+ NSLog(@"noneDidDisplay");
|
|
|
+ [self renderMainContainerView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)checkNodePlay {
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+// 播放节拍器的回调
|
|
|
+- (void)playNodeAction:(BOOL)enable userId:(NSString *)userId rate:(int)rate beatType:(BeatType)type volume:(NSInteger)volume {
|
|
|
+ NSLog(@"playNodeAction userId:%@ enable:%@ beatType:%@ rate:%@ volume:%@",userId,@(enable),@(type), @(rate), @(volume));
|
|
|
+ RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
|
|
|
+ if (curMember.role == RoleStudent) {
|
|
|
+
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark --- RTCServiceDelegate 自己发布流成功回调
|
|
|
+- (void)successPublishLocalStream {
|
|
|
+
|
|
|
+// [self checkNodePlay];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - RongRTCRoomDelegate
|
|
|
+- (void)didPublishStreams:(NSArray <RongRTCAVInputStream *>*)streams {
|
|
|
+
|
|
|
+ NSString *displayUserId;
|
|
|
+ if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayAssistant)) {
|
|
|
+ displayUserId = [ClassroomService sharedService].currentRoom.assistant.userId;
|
|
|
+ } else if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTeacher)) {
|
|
|
+ displayUserId = [ClassroomService sharedService].currentRoom.teacher.userId;
|
|
|
+ }
|
|
|
+ else if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent) {
|
|
|
+ displayUserId = [ClassroomService sharedService].currentRoom.currentDisplayURI;
|
|
|
+ }
|
|
|
+ // 当用户非正常退出的时候,如果再次进入需要重新订阅流
|
|
|
+ RongRTCAVInputStream *firstStream = [streams lastObject];
|
|
|
+ if ([[KSRemoteUserManager shareInstance] isContentUser:firstStream.userId]) {
|
|
|
+ [[KSRemoteUserManager shareInstance] removeUser:firstStream.userId];
|
|
|
+ }
|
|
|
+
|
|
|
+ for (RongRTCAVInputStream *stream in streams) {
|
|
|
+ if (stream.streamType == RTCMediaTypeVideo) {
|
|
|
+ if ([stream.userId isEqualToString:displayUserId]) {
|
|
|
+ [self renderMainContainerView];
|
|
|
+ }
|
|
|
+ [self.videoListView updateUserVideo:stream.userId];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)didConnectToStream:(RongRTCAVInputStream *)stream {
|
|
|
+ if (stream.streamId && stream.streamId.length > 0) {
|
|
|
+ NSLog(@"didConnectToStream userId:%@ streamID:%@",stream.userId,stream.streamId);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)didReportFirstKeyframe:(RongRTCAVInputStream *)stream {
|
|
|
+ if (stream.streamId && stream.streamId.length > 0) {
|
|
|
+ FwLogI(RC_Type_RTC, @"A-DYReportFirstKeyFrame-R", @"DYReportFirstKeyFrame:%@,userID:%@",stream.streamId,stream.userId);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark ----- ClassVideoListViewDelegate
|
|
|
+- (void)videoListView:(ClassVideoListView *)view didTap:(RoomMember *)member {
|
|
|
+ /*
|
|
|
+ // 将视频投到主屏幕上
|
|
|
+ NSString *currentDisplayUserId = member.userId;
|
|
|
+ NSString *lastDisplayUserId = [ClassroomService sharedService].currentRoom.currentDisplayURI;
|
|
|
+
|
|
|
+ if([currentDisplayUserId isEqualToString:lastDisplayUserId]) {
|
|
|
+ [self.videoListView updateUserVideo:lastDisplayUserId];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ DisplayType type = member.role == RoleTeacher ? DisplayTeacher : DisplayTypeStudent;
|
|
|
+ [ClassroomService sharedService].currentRoom.currentDisplayURI = currentDisplayUserId;
|
|
|
+ [ClassroomService sharedService].currentRoom.currentDisplayType = type;
|
|
|
+ // 如果之前有切换成大流的视频
|
|
|
+ if (![NSString isEmptyString:lastDisplayUserId] && ![lastDisplayUserId isEqualToString:[ClassroomService sharedService].currentRoom.currentMemberId]) {
|
|
|
+ [[RTCService sharedInstance] exchangeRemoteUserAVStreamToTinySteam:lastDisplayUserId];
|
|
|
+ }
|
|
|
+
|
|
|
+ if(![[ClassroomService sharedService].currentRoom.currentMemberId isEqualToString:member.userId]) {
|
|
|
+ // 记录当前播放音频的学生
|
|
|
+ [ClassroomService sharedService].currentRoom.currentPlayUserId = currentDisplayUserId;
|
|
|
+ [[RTCService sharedInstance] exchangeRemoteUserAVStreamToNomalSteam:member.userId];
|
|
|
+ }
|
|
|
+ [self renderMainContainerView];
|
|
|
+ */
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - Getters & setters
|
|
|
+- (KSTipsView *)tipsView {
|
|
|
+ if (!_tipsView) {
|
|
|
+ _tipsView = [[KSTipsView alloc] initWithFrame:CGRectZero];
|
|
|
+ }
|
|
|
+ return _tipsView;
|
|
|
+}
|
|
|
+
|
|
|
+- (ClassTitleView *)titleView {
|
|
|
+ if (!_titleView) {
|
|
|
+ _titleView = [[ClassTitleView alloc] initWithFrame:CGRectMake(iPhoneXSafeTopMargin, 0, kScreenWidth - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin, TitleViewHeight)];
|
|
|
+ }
|
|
|
+ return _titleView;
|
|
|
+}
|
|
|
+
|
|
|
+- (ClassVideoListView *)videoListView {
|
|
|
+ if(!_videoListView) {
|
|
|
+ CGFloat width = 0.0f;
|
|
|
+ CGFloat height = 0.0f;
|
|
|
+ height = kScreenHeight - 10;
|
|
|
+ width = height / 3.0f * 4;
|
|
|
+ if (width > kScreenWidth - 160) {
|
|
|
+ width = kScreenWidth - 160;
|
|
|
+ }
|
|
|
+ _videoListView = [[ClassVideoListView alloc] initWithFrame:CGRectMake(width + iPhoneXSafeTopMargin + 5, 10, kScreenWidth - (width + 10 + iPhoneXSafeTopMargin) - iPhoneXSafeBottomMargin, height)];
|
|
|
+ _videoListView.delegate = self;
|
|
|
+ }
|
|
|
+ return _videoListView;
|
|
|
+}
|
|
|
+
|
|
|
+- (ClassroomMainContainer *)containerView {
|
|
|
+ if (!_containerView) {
|
|
|
+ _containerView = [[ClassroomMainContainer alloc] initWithFrame:[self mainContainerViewFrame]];
|
|
|
+ _containerView.delegate = self;
|
|
|
+ }
|
|
|
+ return _containerView;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ---- 网络监测
|
|
|
+- (void)didReportStatForm:(RongRTCStatisticalForm *)form {
|
|
|
+ const CGFloat lossRateBase = 0.15;
|
|
|
+ if (self.packetLossStore.count + form.sendStats.count > 10) {
|
|
|
+ NSInteger diff = (self.packetLossStore.count + form.sendStats.count) - 10;
|
|
|
+ [self.packetLossStore removeObjectsInRange:NSMakeRange(0,diff)];
|
|
|
+ }
|
|
|
+
|
|
|
+ for (RongRTCStreamStat* stat in form.sendStats) {
|
|
|
+ [self.packetLossStore addObject:@(stat.packetLoss)];
|
|
|
+ }
|
|
|
+
|
|
|
+ float totalSum = 0;
|
|
|
+ for (NSNumber* lossRate in self.packetLossStore) {
|
|
|
+ totalSum += [lossRate floatValue];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (totalSum > 0 && self.packetLossStore.count) {
|
|
|
+ float avgLoss = totalSum / self.packetLossStore.count;
|
|
|
+ [self showNetworkingStatus:avgLoss];
|
|
|
+ if (avgLoss > lossRateBase) { // 网络质量不佳
|
|
|
+
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ [self.view addSubview:self.networkLabel];
|
|
|
+ [self.view bringSubviewToFront:self.networkLabel];
|
|
|
+ self.networkLabel.hidden = NO;
|
|
|
+ self.networkLabel.text = @"当前通话连接质量不佳";
|
|
|
+ });
|
|
|
+ }
|
|
|
+ else { // 正常
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ self.networkLabel.hidden = YES;
|
|
|
+ self.networkLabel.text = @"";
|
|
|
+ [self.networkLabel removeFromSuperview];
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showNetworkingStatus:(float)packageLose {
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ if (packageLose < 0.05) {
|
|
|
+ self.titleView.netStatus = NetWorkingStatus_Good;
|
|
|
+ }
|
|
|
+ else if (packageLose <= 0.1) {
|
|
|
+ self.titleView.netStatus = NetWorkingStatus_Well;
|
|
|
+ }
|
|
|
+ else if (packageLose <= 0.15) {
|
|
|
+ self.titleView.netStatus = NetWorkingStatus_Bad;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.titleView.netStatus = NetWorkingStatus_Poor;
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (NSMutableArray<NSNumber*>*)packetLossStore {
|
|
|
+ if (!_packetLossStore) {
|
|
|
+ _packetLossStore = [[NSMutableArray alloc] initWithCapacity:10];
|
|
|
+ }
|
|
|
+ return _packetLossStore;
|
|
|
+}
|
|
|
+
|
|
|
+- (UILabel *)networkLabel {
|
|
|
+ if (!_networkLabel) {
|
|
|
+ _networkLabel = [[UILabel alloc] initWithFrame:CGRectMake((kScreenWidth - 200) / 2.0f, (kScreenHeight - 60) / 2.0f, 200, 60)];
|
|
|
+ _networkLabel.backgroundColor = HexRGBAlpha(0x000000, 0.8f);
|
|
|
+ _networkLabel.textColor = HexRGB(0xffffff);
|
|
|
+ _networkLabel.textAlignment = NSTextAlignmentCenter;
|
|
|
+ _networkLabel.font = [UIFont systemFontOfSize:14.0f];
|
|
|
+ _networkLabel.layer.cornerRadius = 5.0f;
|
|
|
+ }
|
|
|
+ return _networkLabel;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ---- 修改声音模式
|
|
|
+- (void)changeModeAction:(BOOL)enable {
|
|
|
+ if (enable) {
|
|
|
+ [[RongRTCAudioCapturer sharedInstance] changeMusicPlayMode:RongRTCAudioScenarioMusicSingleNotePlay];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [[RongRTCAudioCapturer sharedInstance] changeMusicPlayMode:RongRTCAudioScenarioMusicNomalPlay];
|
|
|
+ }
|
|
|
+}
|
|
|
/*
|
|
|
#pragma mark - Navigation
|
|
|
|