ClassroomViewController.m 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. //
  2. // ClassroomViewController.m
  3. // MusicGradeExam
  4. //
  5. // Created by Kyle on 2020/7/10.
  6. // Copyright © 2020 DayaMusic. All rights reserved.
  7. //
  8. #import "ClassroomViewController.h"
  9. #import "ClassTitleView.h"
  10. #import "ClassroomMainContainer.h"
  11. #import "ClassVideoListView.h"
  12. #import "KSNormalAlertView.h"
  13. #import "RTCService.h"
  14. #import "Classroom.h"
  15. #import "ClassroomService.h"
  16. #import <MBProgressHUD/MBProgressHUD.h>
  17. #import "AppDelegate.h"
  18. #import "UIDevice+TFDevice.h"
  19. #import "KSRemoteUserManager.h"
  20. #import "LocalRenderManager.h"
  21. #import "NSDate+Transform.h"
  22. #import "UIViewController+zhStatusBarStyle.h"
  23. #import "KSTipsView.h"
  24. #import "RoomLoginHelper.h"
  25. #define TitleViewHeight (64)
  26. typedef NS_ENUM(NSInteger, KICKTYPE) {
  27. KICKTYPE_NONE,
  28. KICKTYPE_FINISH = 1,
  29. KICKTYPE_RECORD = 2,
  30. KICKTYPE_RESIGN = 3,
  31. };
  32. @interface ClassroomViewController ()<ClassTitleViewDelegate, RongRTCRoomDelegate, ClassroomDelegate, UIGestureRecognizerDelegate, RongRTCActivityMonitorDelegate, RTCServiceDelegate, ClassVideoListViewDelegate>
  33. @property (nonatomic, strong) ClassTitleView *titleView;
  34. @property (nonatomic, strong) ClassroomMainContainer *containerView;
  35. @property (nonatomic, strong) ClassVideoListView *videoListView;
  36. @property (nonatomic, strong) MBProgressHUD *hud;
  37. @property (nonatomic, strong) NSMutableArray<NSNumber*>* packetLossStore;
  38. @property (nonatomic, strong) UILabel *networkLabel;
  39. @property (nonatomic, assign) BOOL cancleAlert;
  40. @property (nonatomic, assign) BOOL hasShowAlert;
  41. @property (nonatomic, assign) BOOL isSwitchLine; // 是否线路切换
  42. @property (nonatomic, strong) KSTipsView *tipsView;
  43. @property (nonatomic, assign) KICKTYPE kickType; // 退出状态
  44. @end
  45. @implementation ClassroomViewController
  46. #pragma mark - Life cycle
  47. - (void)viewWillAppear:(BOOL)animated {
  48. [super viewWillAppear:animated];
  49. self.zh_statusBarHidden = YES;
  50. // 切换到横屏
  51. AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  52. delegate.allowAutoRotate = YES;
  53. [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
  54. [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
  55. [RongRTCEngine sharedEngine].monitorDelegate = self;
  56. }
  57. - (void)viewWillDisappear:(BOOL)animated {
  58. [super viewWillDisappear:animated];
  59. // 竖屏
  60. AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  61. delegate.allowAutoRotate = NO;
  62. [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait];
  63. [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
  64. self.zh_statusBarHidden = NO;
  65. }
  66. - (void)viewDidLoad {
  67. [super viewDidLoad];
  68. // Do any additional setup after loading the view.
  69. self.view.backgroundColor = [UIColor colorWithHexString:@"141414" alpha:1];
  70. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(leaveRoomAction) name:@"classroomLogout" object:nil];
  71. // 切换到横屏
  72. AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  73. delegate.allowAutoRotate = YES;
  74. [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight];
  75. [self addSubviews];
  76. [self bindDelegates];
  77. [self publishStream];
  78. [self renderMainContainerView];
  79. [[RTCService sharedInstance] useSpeaker:YES];
  80. UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
  81. [self.view addGestureRecognizer:tapGes];
  82. tapGes.delegate = self;
  83. [self showRoleHud];
  84. self.kickType = KICKTYPE_NONE;
  85. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundQuitRoomAction) name:@"backgroundQuit" object:nil];
  86. }
  87. - (void)backgroundQuitRoomAction {
  88. self.isSwitchLine = NO;
  89. [[RoomLoginHelper sharedInstance] logout:^{
  90. } error:^(RongRTCCode code) {
  91. }];
  92. self.cancleAlert = YES;
  93. }
  94. #pragma mark ----- 账号异地登陆
  95. - (void)leaveRoomAction {
  96. [self.tipsView showTipsMessage:@"该账号在其他地方登录" inView:self.view];
  97. self.isSwitchLine = NO;
  98. [[RoomLoginHelper sharedInstance] logout:^{
  99. } error:^(RongRTCCode code) {
  100. }];
  101. self.cancleAlert = YES;
  102. [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil];
  103. }
  104. // 提示信息
  105. - (void)showMessage:(NSString *)message {
  106. MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  107. [self.view bringSubviewToFront:hud];
  108. hud.removeFromSuperViewOnHide =YES;
  109. hud.mode = MBProgressHUDModeText;
  110. hud.label.text = message;
  111. hud.minSize = CGSizeMake(132.f, 60.0f);
  112. hud.label.textColor = [UIColor whiteColor];
  113. hud.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
  114. [hud hideAnimated:YES afterDelay:2];
  115. }
  116. - (void)tapGesture: (UITapGestureRecognizer *)tapGesture {
  117. self.titleView.isDisplay = YES;
  118. }
  119. -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
  120. if ([touch.view isDescendantOfView:self.videoListView]) {
  121. return NO;
  122. }
  123. if ([touch.view isDescendantOfView:self.containerView]) {
  124. [self tapGesture:(UITapGestureRecognizer *)gestureRecognizer];
  125. }
  126. return YES;
  127. }
  128. - (void)showRoleHud {
  129. Role role = [ClassroomService sharedService].currentRoom.currentMember.role;
  130. if(role == RoleAudience) {
  131. [self.tipsView showTipsMessage:@"你当前身份是旁听人,其他人看/听不见你" inView:self.view];
  132. [self performSelector:@selector(showOnlyYouHUD) withObject:nil afterDelay:5.0f];
  133. }else{
  134. [self showOnlyYouHUD];
  135. }
  136. }
  137. - (void)showOnlyYouHUD {
  138. if ([ClassroomService sharedService].currentRoom.memberList.count == 1) {
  139. [self.tipsView showTipsMessage:@"当前课堂只有你一人,你可以等待或离开" inView:self.view];
  140. }
  141. }
  142. #pragma mark --- ClassTitleViewDelegate
  143. - (void)classTitleView:(UIButton *)button didTapAtTag:(ClassTitleViewActionTag)tag {
  144. switch (tag) {
  145. case ClassTitleViewActionTagMicrophone:
  146. {
  147. if (button.selected == NO) {
  148. [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭麦克风吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  149. } confirm:^{
  150. button.selected = YES;
  151. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeMicrophone];
  152. }];
  153. }
  154. else {
  155. button.selected = NO;
  156. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeMicrophone];
  157. }
  158. }
  159. break;
  160. case ClassTitleViewActionTagCamera:
  161. {
  162. if (button.selected == NO) {
  163. [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭摄像头吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  164. } confirm:^{
  165. button.selected = YES;
  166. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeCamera];
  167. }];
  168. }
  169. else {
  170. button.selected = NO;
  171. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeCamera];
  172. }
  173. }
  174. break;
  175. case ClassTitleViewActionTagSwitchLine: // 切换线路
  176. {
  177. [KSNormalAlertView ks_showAlertWithTitle:@"确认切换线路吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  178. } confirm:^{
  179. [self restartRoom];
  180. }];
  181. }
  182. break;
  183. case ClassTitleViewActionTagSwitchCamera:
  184. {
  185. [[RTCService sharedInstance] switchCamera];
  186. }
  187. break;
  188. case ClassTitleViewActionTagHangup:
  189. {
  190. [KSNormalAlertView ks_showAlertWithTitle:@"确认退出课堂吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  191. } confirm:^{
  192. SealClassLog(@"ActionTagHangup");
  193. self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  194. [[RoomLoginHelper sharedInstance] logout:^{
  195. } error:^(RongRTCCode code) {
  196. [self.hud hideAnimated:YES];
  197. }];
  198. }];
  199. }
  200. break;
  201. default:
  202. break;
  203. }
  204. }
  205. // 判断是否需要退出房间
  206. - (void)refreshClassStatus {
  207. /*
  208. NSString *userType = UserDefault(UserTypeKey);
  209. if (![userType isEqualToString:@"1"] && self.isQuitRoom == NO) {
  210. NSString *formatString = @"yyyy-MM-dd HH:mm:ss";
  211. NSDate *endDate = [NSDate dateFromString:self.classEndTime format:formatString];
  212. NSTimeInterval endDuration = [[NSDate date] timeIntervalSinceDate:endDate];
  213. if (endDuration >= self.quitRoomTime * 60) {
  214. self.isQuitRoom = YES;
  215. // 退出房间
  216. [self showMessage:@"课程已结束"];
  217. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  218. // 课程结束退出房间
  219. SealClassLog(@"ClassEnd!");
  220. self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  221. [[LoginHelper sharedInstance] logout:^{
  222. } error:^(RongRTCCode code) {
  223. [self.hud hideAnimated:YES];
  224. }];
  225. });
  226. }
  227. }
  228. */
  229. }
  230. #pragma mark ------ 退出RTC房间并重新加入
  231. - (void)restartRoom {
  232. self.isSwitchLine = YES;
  233. self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  234. [[RoomLoginHelper sharedInstance] logout:^{
  235. } error:^(RongRTCCode code) {
  236. }];
  237. }
  238. - (void)reJoinRTCRoom {
  239. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  240. NSString *roomId = [RoomLoginHelper sharedInstance].roomId;
  241. NSString *userName = [RoomLoginHelper sharedInstance].userName;
  242. [[ClassroomService sharedService] joinClassroom:roomId userName:userName isAudience:NO success:^(Classroom * _Nonnull classroom) {
  243. self.isSwitchLine = NO;
  244. [self restartRTCConnection];
  245. } error:^(ErrorCode errorCode, NSString * _Nonnull message) {
  246. [self.tipsView showTipsMessage:@"切换线路失败!" inView:self.view];
  247. [self.hud hideAnimated:YES];
  248. self.isSwitchLine = NO;
  249. }];
  250. });
  251. }
  252. - (void)restartRTCConnection {
  253. [[RTCService sharedInstance] joinRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^(RongRTCRoom * _Nonnull room) {
  254. [self.hud hideAnimated:YES];
  255. [self.tipsView showTipsMessage:@"切换线路成功" inView:self.view];
  256. // 重置UI 重新发布音视频流
  257. [LocalRenderManager shareInstance].hadRenderMainView = NO;
  258. [self publishStream];
  259. [self bindDelegates];
  260. [self.titleView refreshTitleView];
  261. [self.hud hideAnimated:YES];
  262. [RongRTCEngine sharedEngine].monitorDelegate = self;
  263. [[RTCService sharedInstance] useSpeaker:YES];
  264. [self renderMainContainerView];
  265. [self.videoListView reloadVideoList];
  266. } error:^(RongRTCCode code) {
  267. [self.hud hideAnimated:YES];
  268. [self.tipsView showTipsMessage:@"切换线路失败!" inView:self.view];
  269. }];
  270. }
  271. #pragma mark - ClassroomDelegate
  272. - (void)roomDidLeave {
  273. NSLog(@"roomDidLeave");
  274. if (_isSwitchLine) {
  275. if ([RTCService sharedInstance].rtcRoom) {
  276. [[KSRemoteUserManager shareInstance] removeAllUser];
  277. [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
  278. [self reJoinRTCRoom];
  279. } error:^(RongRTCCode code) {
  280. [self.hud hideAnimated:YES];
  281. [self.tipsView showTipsMessage:@"切换线路失败!" inView:self.view];
  282. self.isSwitchLine = NO;
  283. }];
  284. }
  285. }
  286. else if (_kickType != KICKTYPE_NONE) { // 退出房间,但是不dismiss
  287. [[KSRemoteUserManager shareInstance] removeAllUser];
  288. if ([RTCService sharedInstance].rtcRoom) {
  289. [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
  290. } error:^(RongRTCCode code) {
  291. }];
  292. }
  293. [self showKickAlert];
  294. }
  295. else {
  296. [[KSRemoteUserManager shareInstance] removeAllUser];
  297. if ([RTCService sharedInstance].rtcRoom) {
  298. [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
  299. } error:^(RongRTCCode code) {
  300. }];
  301. }
  302. [self dismissClassroom];
  303. }
  304. }
  305. - (void)showKickAlert {
  306. if (_kickType == KICKTYPE_FINISH) {
  307. [KSNormalAlertView ks_showAlertWithTitle:@"您已完成考试,请耐心等待考试结果。" confirmTitle:@"退出考场" confirm:^{
  308. [self dismissClassroom];
  309. }];
  310. }
  311. else if (_kickType == KICKTYPE_RECORD) {
  312. // 弹窗提示去录播
  313. [KSNormalAlertView ks_showAlertWithTitle:@"当前状态无法正常完成在线直播考试\n主考官建议您录播上传考试视频。" leftTitle:@"取消" rightTitle:@"去录播" cancel:^{
  314. // 离开教室
  315. [self dismissClassroom];
  316. } confirm:^{
  317. // 去录播
  318. [self dismissClassroom];
  319. [[NSNotificationCenter defaultCenter] postNotificationName:RecordExamNotification object:nil];
  320. }];
  321. }
  322. else if (_kickType == KICKTYPE_RESIGN) {
  323. [KSNormalAlertView ks_showAlertWithTitle:@"当前状态无法正常完成考试,请检查设备及网络状态正常后再次签到考试。" confirmTitle:@"确定" confirm:^{
  324. // 离开教室
  325. [self dismissClassroom];
  326. }];
  327. }
  328. }
  329. - (void)dismissClassroom {
  330. [self.hud hideAnimated:YES];
  331. [ClassroomService sharedService].currentRoom = nil;
  332. [self dismissViewControllerAnimated:NO completion:^{
  333. [self.titleView stopDurationTimer];
  334. }];
  335. }
  336. - (void)memberDidJoin:(RoomMember *)member {
  337. NSLog(@"memberDidJoin %@",member);
  338. if(member.role == RoleMainTeacher || member.role == RoleAssistantTeacher) {
  339. [self.videoListView reloadVideoList];
  340. }
  341. if (member.role == RoleMainTeacher || member.role == RoleAssistantTeacher) {
  342. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"老师%@已经进入教室", member.name] inView:self.view];
  343. }
  344. }
  345. - (void)memberDidLeave:(RoomMember *)member {
  346. NSLog(@"memberDidLeave %@",member);
  347. // 此处移除
  348. [[KSRemoteUserManager shareInstance] removeUser:member.userId];
  349. if(member.role == RoleMainTeacher || member.role == RoleAssistantTeacher) {
  350. [self.videoListView reloadVideoList];
  351. }
  352. }
  353. - (void)memberDidKick:(RoomMember *)member {
  354. NSLog(@"memberDidKick %@",member);
  355. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  356. [[ClassroomService sharedService].currentRoom removeMemeber:member];
  357. if ([curMember.userId isEqualToString:member.userId]) {
  358. self.kickType = KICKTYPE_FINISH;
  359. [[RoomLoginHelper sharedInstance] logout:^{
  360. } error:^(RongRTCCode code) {
  361. }];
  362. return;
  363. }
  364. [[KSRemoteUserManager shareInstance] removeUser:member.userId];
  365. Role curRole = [ClassroomService sharedService].currentRoom.currentMember.role;
  366. if (curRole == RoleMainTeacher || curRole == RoleAssistantTeacher) {
  367. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"你已将%@移出课堂", member.name] inView:self.view];
  368. }
  369. [self.videoListView reloadVideoList];
  370. if (self.containerView.member.role == member.role) {
  371. [self.containerView cancelRenderView];
  372. }
  373. }
  374. - (void)memberRecordAction:(RoomMember *)member {
  375. NSLog(@"memberRecordAction %@", member);
  376. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  377. if ([curMember.userId isEqualToString:member.userId]) {
  378. self.kickType = KICKTYPE_RECORD;
  379. [[RoomLoginHelper sharedInstance] logout:^{
  380. } error:^(RongRTCCode code) {
  381. }];
  382. }
  383. }
  384. // 不播放声音列表
  385. - (void)memberIgnoreAction:(RoomMember *)member {
  386. NSLog(@"memberRecordAction %@", member);
  387. NSString *shieldUserId = member.shieldUserId;
  388. NSArray *memberArray = [shieldUserId componentsSeparatedByString:@","];
  389. [[RTCService sharedInstance] unPlayRemoteUserAudioStream:memberArray];
  390. }
  391. - (void)memberNoFinishAction:(RoomMember *)member {
  392. NSLog(@"memberRecordAction %@", member);
  393. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  394. if ([curMember.userId isEqualToString:member.userId]) {
  395. self.kickType = KICKTYPE_RESIGN;
  396. [[RoomLoginHelper sharedInstance] logout:^{
  397. } error:^(RongRTCCode code) {
  398. }];
  399. }
  400. }
  401. - (void)errorDidOccur:(ErrorCode)code {
  402. NSLog(@"errorDidOccur %@",@(code));
  403. [self.hud hideAnimated:YES];
  404. if (code != ErrorCodeSuccess) {
  405. if (code == ErrorCodeOverMaxUserCount) {
  406. [self.tipsView showTipsMessage:@"超过人数限制" inView:self.view];
  407. }else {
  408. [self.tipsView showTipsMessage:@"操作失败,请稍后再试" inView:self.view];
  409. }
  410. }
  411. }
  412. - (void)deviceDidEnable:(BOOL)enable type:(DeviceType)type forUser:(RoomMember *)member operator:(nonnull NSString *)operatorId {
  413. NSLog(@"deviceDidEnable devicetype:%@ enable:%@ memeber:%@",@(type),@(enable),member);
  414. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  415. NSString *hudMessage = @"";
  416. //只有老师和自己才有提示
  417. if ((curMember.role == RoleMainTeacher || curMember.role == RoleAssistantTeacher) && ![curMember.userId isEqualToString:operatorId]) {
  418. if (type == DeviceTypeCamera) {
  419. hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraOpen", @"SealClass", nil),member.name];
  420. } else if (type == DeviceTypeMicrophone) {
  421. hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneOpen", @"SealClass", nil),member.name];
  422. }
  423. else if (type == DeviceTypeMusicMode) {
  424. hudMessage = !enable ? [NSString stringWithFormat:@"%@的音乐模式已关闭",member.name] : [NSString stringWithFormat:@"%@的音乐模式已开启",member.name];
  425. }
  426. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  427. } else {
  428. if ([curMember.userId isEqualToString:member.userId]) {
  429. if (type == DeviceTypeCamera) { // 摄像头
  430. // 非自己操作才需要做提示
  431. if(![curMember.userId isEqualToString:operatorId]){
  432. hudMessage = !enable ? @"老师已关闭你的摄像头" : NSLocalizedStringFromTable(@"CameraOpend", @"SealClass", nil);
  433. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  434. }
  435. [[RTCService sharedInstance] setCameraDisable:!enable];
  436. } else if (type == DeviceTypeMicrophone) { // 麦克风
  437. // 非自己操作才需要做提示
  438. if(![curMember.userId isEqualToString:operatorId]){
  439. hudMessage = !enable ? @"老师已关闭你的麦克风" : NSLocalizedStringFromTable(@"MicorophoneOpend", @"SealClass", nil);
  440. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  441. }
  442. [[RTCService sharedInstance] setMicrophoneDisable:!enable];
  443. }
  444. else if (type == DeviceTypeMusicMode) {
  445. // 非自己操作才需要做提示
  446. if(![curMember.userId isEqualToString:operatorId]) {
  447. }
  448. [self changeModeAction:enable];
  449. }
  450. else if (type == DeviceTypeHandup) {
  451. }
  452. }
  453. }
  454. if (type == DeviceTypeCamera && [curMember.userId isEqualToString:member.userId]) {
  455. // 延迟调用,防止刷新出现灰屏
  456. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  457. [self renderMainContainerView];
  458. });
  459. }
  460. [self.titleView refreshTitleView];
  461. }
  462. //助教请求用户打开设备,助教关闭用户设备没有回调。
  463. - (void)deviceDidInviteEnable:(DeviceType)type ticket:(NSString *)ticket {
  464. NSLog(@"deviceDidInviteEnable devicetype:%@ ticket:%@ ",@(type),ticket);
  465. if (type == DeviceTypeCamera) {
  466. [KSNormalAlertView ks_showAlertWithTitle:@"老师邀请你打开摄像头" leftTitle:@"拒绝" rightTitle:@"同意" cancel:^{
  467. [[ClassroomService sharedService] rejectEnableDevice:ticket];
  468. } confirm:^{
  469. [[ClassroomService sharedService] approveEnableDevice:ticket];
  470. }];
  471. }else if (type == DeviceTypeMicrophone) {
  472. [KSNormalAlertView ks_showAlertWithTitle:@"老师邀请你打开麦克风" leftTitle:@"拒绝" rightTitle:@"同意" cancel:^{
  473. [[ClassroomService sharedService] rejectEnableDevice:ticket];
  474. } confirm:^{
  475. [[ClassroomService sharedService] approveEnableDevice:ticket];
  476. }];
  477. }
  478. else {
  479. [[ClassroomService sharedService] approveEnableDevice:ticket];
  480. }
  481. }
  482. //只有助教能收到这个回调,可以不在这里处理文字,因为设备的回调还会走
  483. - (void)deviceInviteEnableDidApprove:(RoomMember *)member type:(DeviceType)type {
  484. NSLog(@"deviceInviteEnableDidApprove devicetype:%@ member:%@ ",@(type),member);
  485. }
  486. //拒绝只有助教能收到这个回调,且只走这个回调
  487. - (void)deviceInviteEnableDidReject:(RoomMember *)member type:(DeviceType)type {
  488. NSLog(@"deviceInviteEnableDidReject devicetype:%@ member:%@ ",@(type),member);
  489. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"%@拒绝了你的邀请",member.name] inView:self.view];
  490. }
  491. #pragma mark - private method
  492. - (void)bindDelegates {
  493. self.titleView.delegate = self;
  494. [[RTCService sharedInstance] setRTCRoomDelegate:self];
  495. [RTCService sharedInstance].delegate = self;
  496. [ClassroomService sharedService].classroomDelegate = self;
  497. }
  498. - (void)addSubviews {
  499. [self.view addSubview:self.containerView];
  500. [self.view addSubview:self.titleView];
  501. [self.view addSubview:self.videoListView];
  502. [self.view bringSubviewToFront:self.titleView];
  503. }
  504. - (CGRect)mainContainerViewFrame {
  505. CGFloat x = iPhoneXSafeTopMargin;
  506. CGFloat y = 0;
  507. CGFloat width = UIScreenWidth - x - iPhoneXSafeBottomMargin;
  508. CGFloat height = UIScreenHeight - y;
  509. return CGRectMake(x, y, width, height);
  510. }
  511. - (void)publishStream {
  512. if ([ClassroomService sharedService].currentRoom.currentMember.role != RoleAudience) {
  513. [[RTCService sharedInstance] publishLocalUserDefaultAVStream];
  514. }
  515. }
  516. - (void)renderMainContainerView {
  517. // 主屏显示学生
  518. [self.containerView containerViewRenderView:[ClassroomService sharedService].currentRoom.currentMember];
  519. }
  520. - (void)whiteboardDidDisplay:(NSString *)boardId {
  521. NSLog(@"whiteboardDidDisplay %@ ",boardId);
  522. [self renderMainContainerView];
  523. }
  524. - (void)sharedScreenDidDisplay:(NSString *)userId {
  525. NSLog(@"sharedScreenDidDisplay %@ ",userId);
  526. [self renderMainContainerView];
  527. }
  528. - (void)studentDidDisplay:(NSString *)studentId {
  529. NSLog(@"studentDidDisplay %@", studentId);
  530. [self renderMainContainerView];
  531. }
  532. - (void)teacherDidDisplay {
  533. NSLog(@"teacherDidDisplay %@ ",[ClassroomService sharedService].currentRoom.teacher);
  534. [self renderMainContainerView];
  535. }
  536. - (void)noneDidDisplay {
  537. NSLog(@"noneDidDisplay");
  538. [self renderMainContainerView];
  539. }
  540. #pragma mark --- RTCServiceDelegate 自己发布流成功回调
  541. - (void)successPublishLocalStream {
  542. }
  543. #pragma mark - RongRTCRoomDelegate
  544. - (void)didPublishStreams:(NSArray <RongRTCAVInputStream *>*)streams {
  545. // 当用户非正常退出的时候,如果再次进入需要重新订阅流
  546. RongRTCAVInputStream *firstStream = [streams lastObject];
  547. if ([[KSRemoteUserManager shareInstance] isContentUser:firstStream.userId]) {
  548. [[KSRemoteUserManager shareInstance] removeUser:firstStream.userId];
  549. }
  550. for (RongRTCAVInputStream *stream in streams) {
  551. if (stream.streamType == RTCMediaTypeVideo) {
  552. [self.videoListView updateUserVideo:stream.userId];
  553. }
  554. }
  555. }
  556. - (void)didConnectToStream:(RongRTCAVInputStream *)stream {
  557. if (stream.streamId && stream.streamId.length > 0) {
  558. NSLog(@"didConnectToStream userId:%@ streamID:%@",stream.userId,stream.streamId);
  559. }
  560. }
  561. - (void)didReportFirstKeyframe:(RongRTCAVInputStream *)stream {
  562. if (stream.streamId && stream.streamId.length > 0) {
  563. }
  564. }
  565. #pragma mark ----- ClassVideoListViewDelegate
  566. - (void)videoListView:(ClassVideoListView *)view didTap:(RoomMember *)member {
  567. }
  568. #pragma mark - Getters & setters
  569. - (KSTipsView *)tipsView {
  570. if (!_tipsView) {
  571. _tipsView = [[KSTipsView alloc] initWithFrame:CGRectZero];
  572. }
  573. return _tipsView;
  574. }
  575. - (ClassTitleView *)titleView {
  576. if (!_titleView) {
  577. _titleView = [[ClassTitleView alloc] initWithFrame:CGRectMake(iPhoneXSafeTopMargin, 0, kScreenWidth - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin, TitleViewHeight)];
  578. }
  579. return _titleView;
  580. }
  581. - (ClassVideoListView *)videoListView {
  582. if(!_videoListView) {
  583. CGFloat width = 0.0f;
  584. CGFloat height = 0.0f;
  585. height = kScreenHeight - 10;
  586. width = height / 3.0f * 4;
  587. if (width > kScreenWidth - 160) {
  588. width = kScreenWidth - 160;
  589. }
  590. _videoListView = [[ClassVideoListView alloc] initWithFrame:CGRectMake(width + iPhoneXSafeTopMargin + 5, 10, kScreenWidth - (width + 10 + iPhoneXSafeTopMargin) - iPhoneXSafeBottomMargin, height)];
  591. _videoListView.delegate = self;
  592. }
  593. return _videoListView;
  594. }
  595. - (ClassroomMainContainer *)containerView {
  596. if (!_containerView) {
  597. _containerView = [[ClassroomMainContainer alloc] initWithFrame:[self mainContainerViewFrame]];
  598. }
  599. return _containerView;
  600. }
  601. #pragma mark ---- 网络监测
  602. - (void)didReportStatForm:(RongRTCStatisticalForm *)form {
  603. const CGFloat lossRateBase = 0.15;
  604. if (self.packetLossStore.count + form.sendStats.count > 10) {
  605. NSInteger diff = (self.packetLossStore.count + form.sendStats.count) - 10;
  606. [self.packetLossStore removeObjectsInRange:NSMakeRange(0,diff)];
  607. }
  608. for (RongRTCStreamStat* stat in form.sendStats) {
  609. [self.packetLossStore addObject:@(stat.packetLoss)];
  610. }
  611. float totalSum = 0;
  612. for (NSNumber* lossRate in self.packetLossStore) {
  613. totalSum += [lossRate floatValue];
  614. }
  615. if (totalSum > 0 && self.packetLossStore.count) {
  616. float avgLoss = totalSum / self.packetLossStore.count;
  617. [self showNetworkingStatus:avgLoss];
  618. if (avgLoss > lossRateBase) { // 网络质量不佳
  619. dispatch_async(dispatch_get_main_queue(), ^{
  620. [self.view addSubview:self.networkLabel];
  621. [self.view bringSubviewToFront:self.networkLabel];
  622. self.networkLabel.hidden = NO;
  623. self.networkLabel.text = @"当前通话连接质量不佳";
  624. });
  625. }
  626. else { // 正常
  627. dispatch_async(dispatch_get_main_queue(), ^{
  628. self.networkLabel.hidden = YES;
  629. self.networkLabel.text = @"";
  630. [self.networkLabel removeFromSuperview];
  631. });
  632. }
  633. }
  634. }
  635. - (void)showNetworkingStatus:(float)packageLose {
  636. dispatch_async(dispatch_get_main_queue(), ^{
  637. if (packageLose < 0.05) {
  638. self.titleView.netStatus = NetWorkingStatus_Good;
  639. }
  640. else if (packageLose <= 0.1) {
  641. self.titleView.netStatus = NetWorkingStatus_Well;
  642. }
  643. else if (packageLose <= 0.15) {
  644. self.titleView.netStatus = NetWorkingStatus_Bad;
  645. }
  646. else {
  647. self.titleView.netStatus = NetWorkingStatus_Poor;
  648. }
  649. });
  650. }
  651. - (NSMutableArray<NSNumber*>*)packetLossStore {
  652. if (!_packetLossStore) {
  653. _packetLossStore = [[NSMutableArray alloc] initWithCapacity:10];
  654. }
  655. return _packetLossStore;
  656. }
  657. - (UILabel *)networkLabel {
  658. if (!_networkLabel) {
  659. _networkLabel = [[UILabel alloc] initWithFrame:CGRectMake((kScreenWidth - 200) / 2.0f, (kScreenHeight - 60) / 2.0f, 200, 60)];
  660. _networkLabel.backgroundColor = HexRGBAlpha(0x000000, 0.8f);
  661. _networkLabel.textColor = HexRGB(0xffffff);
  662. _networkLabel.textAlignment = NSTextAlignmentCenter;
  663. _networkLabel.font = [UIFont systemFontOfSize:14.0f];
  664. _networkLabel.layer.cornerRadius = 5.0f;
  665. }
  666. return _networkLabel;
  667. }
  668. #pragma mark ---- 修改声音模式
  669. - (void)changeModeAction:(BOOL)enable {
  670. if (enable) {
  671. [[RongRTCAudioCapturer sharedInstance] changeMusicPlayMode:RongRTCAudioScenarioMusicSingleNotePlay];
  672. }
  673. else {
  674. [[RongRTCAudioCapturer sharedInstance] changeMusicPlayMode:RongRTCAudioScenarioMusicNomalPlay];
  675. }
  676. }
  677. /*
  678. #pragma mark - Navigation
  679. // In a storyboard-based application, you will often want to do a little preparation before navigation
  680. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  681. // Get the new view controller using [segue destinationViewController].
  682. // Pass the selected object to the new view controller.
  683. }
  684. */
  685. @end