NewClassRoomViewController.m 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590
  1. //
  2. // NewClassRoomViewController.m
  3. // StudentDaya
  4. //
  5. // Created by Kyle on 2020/6/12.
  6. // Copyright © 2020 DayaMusic. All rights reserved.
  7. //
  8. #import "NewClassRoomViewController.h"
  9. #import "ClassTitleView.h"
  10. #import "ClassroomMainContainer.h"
  11. #import "ClassVideoListView.h"
  12. #import "KSNormalAlertView.h"
  13. // 聊天列表
  14. #import "LoginHelper.h"
  15. #import "ChatAreaView.h"
  16. #import "RTCService.h"
  17. #import "Classroom.h"
  18. #import "ClassroomService.h"
  19. #import <MBProgressHUD/MBProgressHUD.h>
  20. #import "AppDelegate.h"
  21. #import "KSWhiteboardControl.h"
  22. #import "UIDevice+TFDevice.h"
  23. #import "KSRemoteUserManager.h"
  24. #import "LocalRenderManager.h"
  25. #import "NSDate+Transform.h"
  26. #import "UIViewController+zhStatusBarStyle.h"
  27. #import "KSTipsView.h"
  28. #import "ClassroomTimerManager.h"
  29. #define TitleViewHeight (82)
  30. #define KSExamSongFileKey (@"KSDownloadAccompany")
  31. #define TTimeLableWidth (180)
  32. #define TTimeLableHeight (20)
  33. typedef NS_ENUM(NSInteger, ACCOMPANYACTION) {
  34. ACCOMPANYACTION_NONE, // 没有在播放
  35. ACCOMPANYACTION_SONG, // 播放原声
  36. ACCOMPANYACTION_ACCOMPANY, // 播放伴奏
  37. };
  38. @interface NewClassRoomViewController ()<ClassTitleViewDelegate, RCRTCRoomEventDelegate, ClassroomDelegate, ClassroomMainContainerDelegate, UIGestureRecognizerDelegate, RCRTCStatusReportDelegate, RTCServiceDelegate,KSWhiteboardControlDelegate, ClassVideoListViewDelegate,ClassroomTimerManagerDelegate,RCRTCEngineEventDelegate>
  39. @property (nonatomic, strong) ClassTitleView *titleView;
  40. @property (nonatomic, strong) ClassroomMainContainer *containerView;
  41. @property (nonatomic, strong) ClassVideoListView *videoListView;
  42. @property (nonatomic, strong) ChatAreaView *chatAreaView;
  43. @property (nonatomic, strong) KSWhiteboardControl *wBoardCtrl;
  44. @property (nonatomic, strong) MBProgressHUD *hud;
  45. @property (nonatomic, strong) NSMutableArray<NSNumber*>* packetLossStore;
  46. @property (nonatomic, strong) UILabel *networkLabel;
  47. @property (nonatomic, assign) BOOL cancleAlert;
  48. @property (nonatomic, assign) BOOL isQuitRoom; // 是否正在退出房间
  49. @property (nonatomic, assign) BOOL hasShowAlert;
  50. @property (nonatomic, assign) BOOL isSwitchLine; // 是否线路切换
  51. @property (nonatomic, assign) BOOL isChat;
  52. /** 定时器 */
  53. @property (nonatomic, strong) NSTimer *timer;
  54. /** 定时器多少秒循环一次 */
  55. @property (nonatomic, assign) float timerLength;
  56. /** 当前是否播放状态 */
  57. @property (nonatomic, assign) BOOL isPlay;
  58. /** 当前发出的声次数(用来判断播放"嘀"还是"咚"声音) */
  59. @property (nonatomic, assign) int currentTotalNo;
  60. /** 节拍类型 */
  61. @property (nonatomic, assign) KSMetronomeType metronomeStat;
  62. /** 播放速率,范围40~240 */
  63. @property (nonatomic, assign) int rate;
  64. // 播放节拍器音量
  65. @property (nonatomic, assign) NSInteger volume;
  66. /** 记录上一次的频率 */
  67. @property (nonatomic, assign) int lastRate;
  68. @property (nonatomic, strong) NSString *pathDing;
  69. @property (nonatomic, strong) NSString *pathDong;
  70. @property (nonatomic, strong) KSTipsView *tipsView;
  71. @property (nonatomic, strong) ClassroomTimerManager *timeManager;
  72. @property (nonatomic, strong) UILabel *timeLabel;
  73. @property (nonatomic, strong) KSNormalAlertView *alertView;
  74. @end
  75. @implementation NewClassRoomViewController
  76. - (void)changeOrientation:(BOOL)isLandScape {
  77. if (isLandScape) {
  78. // 切换到横屏
  79. AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  80. delegate.allowAutoRotate = YES;
  81. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  82. [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight inController:self];
  83. });
  84. }
  85. else {
  86. // 切换到横屏
  87. AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  88. delegate.allowAutoRotate = NO;
  89. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  90. [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait inController:self];
  91. });
  92. }
  93. }
  94. #pragma mark - Life cycle
  95. - (void)viewWillAppear:(BOOL)animated {
  96. [super viewWillAppear:animated];
  97. self.zh_statusBarHidden = YES;
  98. // 切换到横屏
  99. [self changeOrientation:YES];
  100. [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
  101. [RCRTCEngine sharedInstance].statusReportDelegate = self;
  102. [RCRTCEngine sharedInstance].delegate = self;
  103. }
  104. - (void)viewWillDisappear:(BOOL)animated {
  105. [super viewWillDisappear:animated];
  106. if (_isPlay) {
  107. [self pause];
  108. [self removeAll];
  109. }
  110. // 竖屏
  111. [self changeOrientation:NO];
  112. [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
  113. self.zh_statusBarHidden = NO;
  114. [self hideAlertView];
  115. [self judgeShowAlert];
  116. }
  117. - (void)hideAlertView {
  118. if (self.alertView && self.alertView.isShow) {
  119. [self.alertView dismissAlertView];
  120. }
  121. }
  122. - (void)judgeShowAlert {
  123. if (self.cancleAlert || self.hasShowAlert) {
  124. return;
  125. }
  126. if (![NSString isEmptyString:self.classEndTime]) {
  127. NSDateFormatter *dateFormatter=[[NSDateFormatter alloc] init];
  128. dateFormatter.timeZone = [NSTimeZone systemTimeZone];
  129. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  130. NSDate *endDate = [dateFormatter dateFromString:self.classEndTime];
  131. NSDate *currentDate = [NSDate date];
  132. NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:endDate];
  133. if (timeInterval >= 0) { // 课程结束弹出评价
  134. self.hasShowAlert = YES;
  135. [[NSNotificationCenter defaultCenter] postNotificationName:@"showEvaluateAlert" object:self.courseId];
  136. }
  137. }
  138. }
  139. - (void)viewDidLoad {
  140. [super viewDidLoad];
  141. // Do any additional setup after loading the view.
  142. self.view.backgroundColor = HexRGB(0x35343e);
  143. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(leaveRoomAction) name:@"classroomLogout" object:nil];
  144. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(quitRoomAction) name:@"classroomQuit" object:nil];
  145. // 切换到横屏
  146. [self changeOrientation:YES];
  147. [self addSubviews];
  148. [self bindDelegates];
  149. [self publishStream];
  150. [self renderMainContainerView];
  151. self.timeManager.isDisplay = YES;
  152. [[RTCService sharedInstance] useSpeaker:YES];
  153. UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
  154. [self.view addGestureRecognizer:tapGes];
  155. tapGes.delegate = self;
  156. [self showRoleHud];
  157. self.isQuitRoom = NO;
  158. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundQuitRoomAction) name:@"backgroundQuit" object:nil];
  159. }
  160. - (void)backgroundQuitRoomAction {
  161. self.isQuitRoom = YES;
  162. self.isSwitchLine = NO;
  163. [[LoginHelper sharedInstance] logout:^{
  164. } error:^(RCRTCCode code) {
  165. }];
  166. self.cancleAlert = YES;
  167. }
  168. #pragma mark ----- 账号异地登陆
  169. - (void)leaveRoomAction {
  170. [self showMessage:@"该账号在其他设备上登录"];
  171. self.isQuitRoom = YES;
  172. self.isSwitchLine = NO;
  173. [[LoginHelper sharedInstance] logout:^{
  174. } error:^(RCRTCCode code) {
  175. }];
  176. self.cancleAlert = YES;
  177. [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil];
  178. }
  179. - (void)quitRoomAction {
  180. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  181. self.isQuitRoom = YES;
  182. self.isSwitchLine = NO;
  183. [[LoginHelper sharedInstance] logout:^{
  184. } error:^(RCRTCCode code) {
  185. }];
  186. self.cancleAlert = YES;
  187. [[NSNotificationCenter defaultCenter] postNotificationName:@"showOtherLoginAlert" object:nil];
  188. });
  189. }
  190. // 提示信息
  191. - (void)showMessage:(NSString *)message {
  192. MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  193. [self.view bringSubviewToFront:hud];
  194. hud.removeFromSuperViewOnHide =YES;
  195. hud.mode = MBProgressHUDModeText;
  196. hud.label.text = message;
  197. hud.minSize = CGSizeMake(132.f, 60.0f);
  198. hud.label.textColor = [UIColor whiteColor];
  199. hud.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
  200. hud.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
  201. [hud hideAnimated:YES afterDelay:2];
  202. }
  203. - (void)tapGesture: (UITapGestureRecognizer *)tapGesture {
  204. self.titleView.isDisplay = YES;
  205. self.timeManager.isDisplay = YES;
  206. [self hideChatAreaView];
  207. }
  208. -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
  209. if ([touch.view isDescendantOfView:self.chatAreaView] || [touch.view isDescendantOfView:self.videoListView]) {
  210. return NO;
  211. }
  212. if ([touch.view isDescendantOfView:self.wBoardCtrl.wbView] || [touch.view isDescendantOfView:self.containerView]) {
  213. [self tapGesture:(UITapGestureRecognizer *)gestureRecognizer];
  214. }
  215. return YES;
  216. }
  217. - (void)viewDidAppear:(BOOL)animated {
  218. [super viewDidAppear:animated];
  219. }
  220. - (void)showRoleHud {
  221. Role role = [ClassroomService sharedService].currentRoom.currentMember.role;
  222. if(role == RoleAudience) {
  223. [self.tipsView showTipsMessage:@"你当前身份是旁听人,其他人看/听不见你" inView:self.view];
  224. [self performSelector:@selector(showOnlyYouHUD) withObject:nil afterDelay:5.0f];
  225. }else{
  226. [self showOnlyYouHUD];
  227. }
  228. }
  229. - (void)showOnlyYouHUD {
  230. if ([ClassroomService sharedService].currentRoom.memberList.count == 1) {
  231. [self.tipsView showTipsMessage:@"当前课堂只有你一人,你可以等待或离开" inView:self.view];
  232. }
  233. }
  234. #pragma mark --- ClassTitleViewDelegate
  235. - (void)classTitleView:(UIButton *)button didTapAtTag:(ClassTitleViewActionTag)tag {
  236. [self.chatAreaView.inputBarControl setInputBarStatus:InputBarControlStatusDefault];
  237. switch (tag) {
  238. case ClassTitleViewActionTagMicrophone:
  239. {
  240. if (button.selected == NO) {
  241. if (self.alertView && self.alertView.isShow) {
  242. [self.alertView dismissAlertView];
  243. }
  244. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭麦克风吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  245. } confirm:^{
  246. button.selected = YES;
  247. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeMicrophone];
  248. }];
  249. }
  250. else {
  251. button.selected = NO;
  252. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeMicrophone];
  253. }
  254. }
  255. break;
  256. case ClassTitleViewActionTagCamera:
  257. {
  258. if (button.selected == NO) {
  259. if (self.alertView && self.alertView.isShow) {
  260. [self.alertView dismissAlertView];
  261. }
  262. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"确认关闭摄像头吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  263. } confirm:^{
  264. button.selected = YES;
  265. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeCamera];
  266. }];
  267. }
  268. else {
  269. button.selected = NO;
  270. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeCamera];
  271. }
  272. }
  273. break;
  274. case ClassTitleViewActionTagSwitchLine: // 切换线路
  275. {
  276. MJWeakSelf;
  277. if (self.alertView && self.alertView.isShow) {
  278. [self.alertView dismissAlertView];
  279. }
  280. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"确认切换线路吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  281. } confirm:^{
  282. [weakSelf restartRoom];
  283. }];
  284. }
  285. break;
  286. case ClassTitleViewActionTagSwitchCamera:
  287. {
  288. [[RTCService sharedInstance] switchCamera];
  289. }
  290. break;
  291. case ClassTitleViewActionTagChat:
  292. {
  293. button.selected = !button.selected;
  294. button.selected ? [self showChatAreaView] : [self hideChatAreaView];
  295. self.isChat = button.selected;
  296. }
  297. break;
  298. case ClassTitleViewActionTagHangup:
  299. {
  300. MJWeakSelf;
  301. if (self.alertView && self.alertView.isShow) {
  302. [self.alertView dismissAlertView];
  303. }
  304. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"确认退出课堂吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  305. } confirm:^{
  306. weakSelf.isQuitRoom = YES;
  307. SealClassLog(@"ActionTagHangup");
  308. weakSelf.hud = [self createProgressHUD];
  309. [[LoginHelper sharedInstance] logout:^{
  310. } error:^(RCRTCCode code) {
  311. }];
  312. }];
  313. }
  314. break;
  315. default:
  316. break;
  317. }
  318. }
  319. #pragma mark ---- ClassroomTimerManager delegate
  320. - (void)formatTime:(NSString *)timeString {
  321. self.timeLabel.text = timeString;
  322. }
  323. - (void)timeLabelAddAnimation:(CABasicAnimation *)animation {
  324. self.timeLabel.textColor = HexRGB(0x2DC7AA);
  325. [self.timeLabel.layer addAnimation:animation forKey:nil];
  326. }
  327. - (void)hiddenTitleView {
  328. [self.titleView hiddenView];
  329. }
  330. - (void)quitClassroomNotifer {
  331. // 课程结束后15分钟自动退出房间 连堂课不退出(对内)
  332. if (self.isQuitRoom == NO) {
  333. self.isQuitRoom = YES;
  334. // 退出房间
  335. [self.tipsView showTipsMessage:@"课程已结束" inView:self.view];
  336. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  337. // 课程结束退出房间
  338. SealClassLog(@"ClassEnd!");
  339. self.hud = [self createProgressHUD];
  340. [[LoginHelper sharedInstance] logout:^{
  341. } error:^(RCRTCCode code) {
  342. }];
  343. });
  344. }
  345. }
  346. #pragma mark ------ 退出RTC房间并重新加入
  347. - (void)restartRoom {
  348. self.isSwitchLine = YES;
  349. if (_isPlay) {
  350. [self pause];
  351. [self removeAll];
  352. }
  353. self.hud = [self createProgressHUD];
  354. [self.wBoardCtrl.wbView leaveRoom];
  355. [[LoginHelper sharedInstance] logout:^{
  356. } error:^(RCRTCCode code) {
  357. }];
  358. }
  359. - (void)reJoinRTCRoom {
  360. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  361. NSString *roomId = [LoginHelper sharedInstance].roomId;
  362. NSString *userName = [LoginHelper sharedInstance].userName;
  363. [[ClassroomService sharedService] joinClassroom:roomId userName:userName isAudience:NO success:^(Classroom * _Nonnull classroom) {
  364. self.isSwitchLine = NO;
  365. [self restartRTCConnection];
  366. } error:^(ErrorCode errorCode, NSString * _Nonnull message) {
  367. [self.tipsView showTipsMessage:@"切换线路失败!" inView:self.view];
  368. [self.hud hideAnimated:YES];
  369. self.isSwitchLine = NO;
  370. }];
  371. });
  372. }
  373. - (void)restartRTCConnection {
  374. [[RTCService sharedInstance] joinRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^(RCRTCRoom * _Nonnull room) {
  375. [self.hud hideAnimated:YES];
  376. [self.tipsView showTipsMessage:@"切换线路成功" inView:self.view];
  377. // 重置UI 重新发布音视频流
  378. [LocalRenderManager shareInstance].hadRenderMainView = NO;
  379. self.timeManager.isDisplay = YES;
  380. [self publishStream];
  381. [self bindDelegates];
  382. [self.titleView refreshTitleView];
  383. [self.containerView refreshToolView];
  384. [self.hud hideAnimated:YES];
  385. [RCRTCEngine sharedInstance].statusReportDelegate = self;
  386. [RCRTCEngine sharedInstance].delegate = self;
  387. [[RTCService sharedInstance] useSpeaker:YES];
  388. [self renderMainContainerView];
  389. [self.videoListView reloadVideoList];
  390. } error:^(RCRTCCode code) {
  391. [self.hud hideAnimated:YES];
  392. if (code == RCRTCCodeAudioDeviceStartError) {
  393. [self.tipsView showTipsMessage:@"音频设备启动失败,请重启设备后重试!" inView:self.view];
  394. }
  395. else {
  396. [self.tipsView showTipsMessage:@"切换线路失败!" inView:self.view];
  397. }
  398. }];
  399. }
  400. #pragma mark - ClassroomDelegate
  401. - (void)roomDidLeave {
  402. NSLog(@"roomDidLeave");
  403. if (_isSwitchLine) {
  404. if ([RTCService sharedInstance].rtcRoom) {
  405. [[KSRemoteUserManager shareInstance] removeAllUser];
  406. [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
  407. [self reJoinRTCRoom];
  408. } error:^(RCRTCCode code) {
  409. [self.hud hideAnimated:YES];
  410. [self.tipsView showTipsMessage:@"切换线路失败!" inView:self.view];
  411. self.isSwitchLine = NO;
  412. }];
  413. }
  414. }
  415. else {
  416. [[KSRemoteUserManager shareInstance] removeAllUser];
  417. [[RCIMClient sharedRCIMClient] clearMessagesUnreadStatus:ConversationType_GROUP targetId:[ClassroomService sharedService].currentRoom.roomId];
  418. int unreadMsgCount = [[RCIMClient sharedRCIMClient] getUnreadCount:@[
  419. @(ConversationType_PRIVATE), @(ConversationType_APPSERVICE), @(ConversationType_GROUP),@(ConversationType_SYSTEM)
  420. ]];
  421. if (unreadMsgCount >= 1) {
  422. [(KSTabBarViewController *)self.tabBarController noteNewsWithIndex:2 count:unreadMsgCount];
  423. } else {
  424. [(KSTabBarViewController *)self.tabBarController clearNewsWithIndex:2];
  425. }
  426. // 关闭白板连接
  427. [self.wBoardCtrl.wbView.sdk setCommonCallbackDelegate:nil];
  428. [self.wBoardCtrl.wbView.room disconnect:^{
  429. }];
  430. if ([RTCService sharedInstance].rtcRoom) {
  431. [[RTCService sharedInstance] leaveRongRTCRoom:[ClassroomService sharedService].currentRoom.roomId success:^{
  432. } error:^(RCRTCCode code) {
  433. }];
  434. }
  435. [self dismissClassroom];
  436. }
  437. }
  438. - (void)dismissClassroom {
  439. [self.hud hideAnimated:YES];
  440. [ClassroomService sharedService].currentRoom = nil;
  441. [self dismissViewControllerAnimated:NO completion:^{
  442. [self.timeManager stopDurationTimer];
  443. [self.timeLabel.layer removeAllAnimations];
  444. }];
  445. }
  446. - (void)memberDidJoin:(RoomMember *)member {
  447. NSLog(@"memberDidJoin %@",member);
  448. if(member.role != RoleAudience) {
  449. [self.videoListView reloadVideoList];
  450. }
  451. if (member.role == RoleTeacher) {
  452. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"老师%@已经进入教室", member.name] inView:self.view];
  453. if (_isPlay) {
  454. [self pause];
  455. [self removeAll];
  456. [[RTCService sharedInstance] playAllRemoteUserAudioStream];
  457. }
  458. if ([ClassroomService sharedService].currentRoom.isPlaySong) {
  459. [[RCRTCAudioMixer sharedInstance] stop];
  460. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  461. [[RTCService sharedInstance] playAllRemoteUserAudioStream];
  462. }
  463. }
  464. }
  465. - (void)memberDidLeave:(RoomMember *)member {
  466. NSLog(@"memberDidLeave %@",member);
  467. // 此处移除
  468. [[KSRemoteUserManager shareInstance] removeUser:member.userId];
  469. if(member.role != RoleAudience) {
  470. [self.videoListView reloadVideoList];
  471. }
  472. if (member.role == RoleTeacher) {
  473. // 老师退出房间后,学生如果在节拍器播放时停止节拍器
  474. if (_isPlay) {
  475. [self pause];
  476. [self removeAll];
  477. [[RTCService sharedInstance] playAllRemoteUserAudioStream];
  478. }
  479. if ([ClassroomService sharedService].currentRoom.isPlaySong) {
  480. [[RCRTCAudioMixer sharedInstance] stop];
  481. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  482. [[RTCService sharedInstance] playAllRemoteUserAudioStream];
  483. }
  484. }
  485. }
  486. - (void)memberDidKick:(RoomMember *)member {
  487. NSLog(@"memberDidKick %@",member);
  488. [[KSRemoteUserManager shareInstance] removeUser:member.userId];
  489. if ([ClassroomService sharedService].currentRoom.currentMember.role == RoleTeacher) {
  490. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"你已将%@移出课堂", member.name] inView:self.view];
  491. }
  492. [self.videoListView reloadVideoList];
  493. if (self.containerView.member.role == member.role) {
  494. [self.containerView cancelRenderView];
  495. }
  496. }
  497. - (void)errorDidOccur:(ErrorCode)code {
  498. NSLog(@"errorDidOccur %@",@(code));
  499. [self.hud hideAnimated:YES];
  500. if (code != ErrorCodeSuccess) {
  501. if (code == ErrorCodeOverMaxUserCount) {
  502. [self.tipsView showTipsMessage:@"超过人数限制" inView:self.view];
  503. }else {
  504. [self.tipsView showTipsMessage:@"操作失败,请稍后再试" inView:self.view];
  505. }
  506. }
  507. }
  508. - (void)deviceDidEnable:(BOOL)enable type:(DeviceType)type forUser:(RoomMember *)member operator:(nonnull NSString *)operatorId {
  509. NSLog(@"deviceDidEnable devicetype:%@ enable:%@ memeber:%@",@(type),@(enable),member);
  510. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  511. NSString *hudMessage = @"";
  512. //只有老师和自己才有提示
  513. if (curMember.role == RoleTeacher && ![curMember.userId isEqualToString:operatorId]) {
  514. if (type == DeviceTypeCamera) {
  515. hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetCameraOpen", @"SealClass", nil),member.name];
  516. } else if (type == DeviceTypeMicrophone) {
  517. hudMessage = !enable ? [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneClose", @"SealClass", nil),member.name] : [NSString stringWithFormat:NSLocalizedStringFromTable(@"SetMicorophoneOpen", @"SealClass", nil),member.name];
  518. }
  519. else if (type == DeviceTypeMusicMode) {
  520. hudMessage = !enable ? [NSString stringWithFormat:@"%@的音乐模式已关闭",member.name] : [NSString stringWithFormat:@"%@的音乐模式已开启",member.name];
  521. }
  522. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  523. } else {
  524. if ([curMember.userId isEqualToString:member.userId]) {
  525. if (type == DeviceTypeCamera) { // 摄像头
  526. // 非自己操作才需要做提示
  527. if(![curMember.userId isEqualToString:operatorId]){
  528. hudMessage = !enable ? @"老师已关闭你的摄像头" : NSLocalizedStringFromTable(@"CameraOpend", @"SealClass", nil);
  529. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  530. }
  531. [self.containerView refreshToolView];
  532. [[RTCService sharedInstance] setCameraDisable:!enable];
  533. } else if (type == DeviceTypeMicrophone) { // 麦克风
  534. // 非自己操作才需要做提示
  535. if(![curMember.userId isEqualToString:operatorId]){
  536. hudMessage = !enable ? @"老师已关闭你的麦克风" : NSLocalizedStringFromTable(@"MicorophoneOpend", @"SealClass", nil);
  537. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  538. }
  539. [self.containerView refreshToolView];
  540. [[RTCService sharedInstance] setMicrophoneDisable:!enable];
  541. }
  542. else if (type == DeviceTypeMusicMode) {
  543. // 非自己操作才需要做提示
  544. if(![curMember.userId isEqualToString:operatorId]) {
  545. // hudMessage = !enable ? NSLocalizedStringFromTable(@"老师关闭了你的音乐模式", @"SealClass", nil) : NSLocalizedStringFromTable(@"音乐模式已打开", @"SealClass", nil);
  546. // [self.tipsView showTipsMessage:hudMessage inView:self.view];
  547. }
  548. [self.containerView refreshToolView];
  549. [self changeModeAction:enable];
  550. }
  551. else if (type == DeviceTypeHandup) {
  552. // if ([curMember.userId isEqualToString:operatorId]) {
  553. // if (enable) {
  554. // [self.tipsView showTipsMessage:@"您已举手" inView:self.view];
  555. // }
  556. // }
  557. }
  558. else if (type == DeviceTypeExamSong) {
  559. if (enable) {
  560. // 播放伴奏
  561. [self playSongAction];
  562. }
  563. else {
  564. // 停止伴奏
  565. [[RCRTCAudioMixer sharedInstance] stop];
  566. [self playSongEnable:NO];
  567. }
  568. }
  569. else if (type == DeviceTypeAccompany || type == DeviceTypeMusicScore) { // 原音或伴奏
  570. if (enable) {
  571. // 检测是否存在该曲目
  572. RoomMember *member = [ClassroomService sharedService].currentRoom.currentMember;
  573. NSString *songId = [NSString stringWithFormat:@"%d", member.musicScoreAccompanimentId];
  574. NSString *songUrl = member.url;
  575. NSString *accompnayUrl = member.mp3Url;
  576. // 播放伴奏
  577. [self checkAccompnayDownloadNotifer:songUrl accompanyUrl:accompnayUrl songId:songId needPlay:YES];
  578. }
  579. else {
  580. // 停止伴奏
  581. [[RCRTCAudioMixer sharedInstance] stop];
  582. [self playSongEnable:NO];
  583. }
  584. }
  585. }
  586. }
  587. if ([ClassroomService sharedService].currentRoom.currentDisplayType != DisplayWhiteboard && type == DeviceTypeCamera) {
  588. // 延迟调用,防止刷新出现灰屏
  589. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  590. [self renderMainContainerView];
  591. });
  592. }
  593. [self.titleView refreshTitleView];
  594. [self.containerView refreshToolView];
  595. }
  596. - (void)playSongAction {
  597. if ([ClassroomService sharedService].currentRoom.isPlaySong || [ClassroomService sharedService].currentRoom.isPlayBeat) {
  598. // 停止伴奏
  599. [[RCRTCAudioMixer sharedInstance] stop];
  600. }
  601. // 获取路径 播放伴奏
  602. RoomMember *member = [ClassroomService sharedService].currentRoom.currentMember;
  603. NSString *songId = [NSString stringWithFormat:@"%d", member.musicScoreAccompanimentId];
  604. NSDictionary *fileDic = UserDefaultObjectForKey(KSExamSongFileKey);
  605. NSDictionary *storeDic = [fileDic ks_dictionaryValueForKey:songId];
  606. NSString *fileName = nil;
  607. NSString *filePath = nil;
  608. if (member.accompanimentPlayStatus) {
  609. fileName = [[storeDic ks_dictionaryValueForKey:@"accompany"] ks_stringValueForKey:@"fileName"];
  610. filePath = [self getFilePathWithName:fileName];
  611. }
  612. else if (member.playStatus) {
  613. fileName = [[storeDic ks_dictionaryValueForKey:@"music"] ks_stringValueForKey:@"fileName"];
  614. filePath = [self getFilePathWithName:fileName];
  615. }
  616. // 判断文件是否存在
  617. NSLog(@"-------songid %@", songId);
  618. NSLog(@"------filePath %@", filePath);
  619. NSFileManager *fileManager = [NSFileManager defaultManager];
  620. if ([fileManager fileExistsAtPath:filePath] && ![NSString isEmptyString:fileName]) {
  621. NSInteger soundVolome = [ClassroomService sharedService].currentRoom.soundVolume;
  622. NSLog(@"soundVolome------%zd",soundVolome);
  623. [[RCRTCAudioMixer sharedInstance] stop];
  624. @try {
  625. // 混音伴奏音量
  626. [RCRTCAudioMixer sharedInstance].mixingVolume = soundVolome;
  627. [RCRTCAudioMixer sharedInstance].playingVolume = soundVolome;
  628. [[RCRTCAudioMixer sharedInstance] startMixingWithURL:[NSURL fileURLWithPath:filePath] playback:YES mixerMode:RCRTCMixerModeMixing loopCount:NSUIntegerMax];
  629. } @catch (NSException *exception) {
  630. NSLog(@"%@", exception);
  631. } @finally {
  632. }
  633. }
  634. [self playSongEnable:YES];
  635. }
  636. - (void)playSongEnable:(BOOL)enable {
  637. NSString *teacherId = [ClassroomService sharedService].currentRoom.teacher.userId;
  638. if (enable) {
  639. if ([ClassroomService sharedService].currentRoom.isPlayBeat) {
  640. [self pause];
  641. [self removeAll];
  642. }
  643. [ClassroomService sharedService].currentRoom.isPlaySong = YES;
  644. [[RTCService sharedInstance] unPlayRemoteUserAudioStreamExcept:teacherId];
  645. }
  646. else {
  647. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  648. [[RTCService sharedInstance] playAllRemoteUserAudioStream];
  649. }
  650. }
  651. // 请求用户打开设备,助教关闭用户设备没有回调。
  652. - (void)deviceDidInviteEnable:(DeviceType)type ticket:(NSString *)ticket {
  653. NSLog(@"deviceDidInviteEnable devicetype:%@",@(type));
  654. if (type == DeviceTypeCamera) {
  655. if (self.alertView && self.alertView.isShow) {
  656. [self.alertView dismissAlertView];
  657. }
  658. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"老师邀请你打开摄像头" leftTitle:@"拒绝" rightTitle:@"同意" cancel:^{
  659. [[ClassroomService sharedService] rejectEnableDevice:type];
  660. } confirm:^{
  661. [[ClassroomService sharedService] approveEnableDevice:type];
  662. }];
  663. }else if (type == DeviceTypeMicrophone) {
  664. [self.tipsView showTipsMessage:@"老师开启了你的麦克风" inView:self.view];
  665. [[ClassroomService sharedService] approveEnableDevice:type];
  666. }
  667. else if (type == DeviceTypeExamSong) { // 接口自动处理同意
  668. }
  669. else if (type == DeviceTypeAccompany || type == DeviceTypeMusicScore) { // 伴奏接口自动处理成同意
  670. }
  671. else {
  672. [[ClassroomService sharedService] approveEnableDevice:type];
  673. }
  674. }
  675. //只有助教能收到这个回调,可以不在这里处理文字,因为设备的回调还会走
  676. - (void)deviceInviteEnableDidApprove:(RoomMember *)member type:(DeviceType)type {
  677. NSLog(@"deviceInviteEnableDidApprove devicetype:%@ member:%@ ",@(type),member);
  678. }
  679. //拒绝只有助教能收到这个回调,且只走这个回调
  680. - (void)deviceInviteEnableDidReject:(RoomMember *)member type:(DeviceType)type {
  681. NSLog(@"deviceInviteEnableDidReject devicetype:%@ member:%@ ",@(type),member);
  682. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"%@拒绝了你的邀请",member.name] inView:self.view];
  683. }
  684. #pragma mark ----- 伴奏下载
  685. - (void)accompanyDownloadCallback:(NSString *)songUrl accompanyUrl:(NSString *)accompnayUrl songId:(NSString *)songId {
  686. [self checkAccompnayDownloadNotifer:songUrl accompanyUrl:accompnayUrl songId:songId needPlay:NO];
  687. }
  688. - (void)checkAccompnayDownloadNotifer:(NSString *)songUrl accompanyUrl:(NSString *)accompnayUrl songId:(NSString *)songId needPlay:(BOOL)isNeedPlay {
  689. if ([self checkSongHasSave:songId songUrl:songUrl accompanyUrl:accompnayUrl]) { // 判断文件是否已下载
  690. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:YES];
  691. if (isNeedPlay) {
  692. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  693. [self playSongAction];
  694. });
  695. }
  696. }
  697. else { // 如果不存在则去下载
  698. if (![NSString isEmptyString:songUrl]) {
  699. [self downloadSongAction:songUrl songId:songId isAccompany:NO success:^{
  700. if (![NSString isEmptyString:accompnayUrl]) {
  701. [self downloadSongAction:accompnayUrl songId:songId isAccompany:YES success:^{
  702. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:YES];
  703. if (isNeedPlay) {
  704. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  705. [self playSongAction];
  706. });
  707. }
  708. } faliure:^{
  709. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:NO];
  710. }];
  711. }
  712. else {
  713. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:YES];
  714. if (isNeedPlay) {
  715. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  716. [self playSongAction];
  717. });
  718. }
  719. }
  720. } faliure:^{
  721. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:NO];
  722. }];
  723. }
  724. else if (![NSString isEmptyString:accompnayUrl]){
  725. [self downloadSongAction:accompnayUrl songId:songId isAccompany:YES success:^{
  726. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:YES];
  727. if (isNeedPlay) {
  728. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  729. [self playSongAction];
  730. });
  731. }
  732. } faliure:^{
  733. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:NO];
  734. }];
  735. }
  736. else { // 原声和伴奏均为空
  737. [[ClassroomService sharedService] adjustAccompanySongNotifer:songId status:NO];
  738. }
  739. }
  740. }
  741. - (void)downloadSongAction:(NSString *)url songId:(NSString *)songId isAccompany:(BOOL)isAccompany success:(void(^)(void))success faliure:(void(^)(void))faliure {
  742. [KSNetworkingManager downloadFileRequestWithFileUrl:url progress:^(int64_t bytesRead, int64_t totalBytes) {
  743. NSLog(@"%.2f", bytesRead *1.0 / totalBytes);
  744. } success:^(NSURL * _Nonnull fileUrl) {
  745. if ([self saveUrlWithFilePath:fileUrl songUrl:url songId:songId isAccompany:isAccompany]) {
  746. success();
  747. }
  748. else {
  749. faliure();
  750. }
  751. } faliure:^(NSError * _Nonnull error) {
  752. faliure();
  753. }];
  754. }
  755. - (BOOL)saveUrlWithFilePath:(NSURL *)fileUrl songUrl:(NSString *)songUrl songId:(NSString *)songId isAccompany:(BOOL)isAccompany {
  756. NSData *sourceData = [NSData dataWithContentsOfURL:fileUrl];
  757. NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
  758. NSString *filePath= [cachePath stringByAppendingPathComponent:@"AccompanySong"];
  759. // 先创建子目录
  760. NSFileManager *fileManager = [NSFileManager defaultManager];
  761. if (![fileManager fileExistsAtPath:filePath]) {
  762. [fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil];
  763. }else{
  764. NSLog(@"已创建文件夹");
  765. }
  766. NSString *fileName = [[songUrl componentsSeparatedByString:@"/"] lastObject];
  767. NSString *tempPath = [filePath stringByAppendingPathComponent:fileName];
  768. BOOL success = [sourceData writeToFile:tempPath atomically:NO];
  769. if (success) {
  770. NSDictionary *fileDic = UserDefaultObjectForKey(KSExamSongFileKey); // 只能存NSDictionary
  771. NSMutableDictionary *storeDic = [NSMutableDictionary dictionary];
  772. if (!fileDic) {
  773. fileDic = [NSMutableDictionary dictionary];
  774. }
  775. else {
  776. storeDic = [NSMutableDictionary dictionaryWithDictionary:[fileDic ks_dictionaryValueForKey:songId]];
  777. }
  778. NSDictionary *parm = @{@"url" : songUrl,
  779. @"fileName" : fileName};
  780. NSString *keyString = isAccompany ? @"accompany" :@"music";
  781. [storeDic setValue:parm forKey:keyString];
  782. NSMutableDictionary *saveDic = [NSMutableDictionary dictionaryWithDictionary:fileDic];
  783. [saveDic setValue:storeDic forKey:songId];
  784. UserDefaultSetObjectForKey(saveDic, KSExamSongFileKey);
  785. }
  786. return success;
  787. }
  788. - (NSString *)getFilePathWithName:(NSString *)fileName {
  789. NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
  790. NSString *filePath= [cachePath stringByAppendingPathComponent:@"AccompanySong"];
  791. return [filePath stringByAppendingPathComponent:fileName];;
  792. }
  793. - (BOOL)checkSongHasSave:(NSString *)songId songUrl:(NSString *)songUrl accompanyUrl:(NSString *)accompanyUrl {
  794. BOOL hasSaveFile = NO;
  795. NSMutableDictionary *fileDic = UserDefaultObjectForKey(KSExamSongFileKey);
  796. NSDictionary *storeDic = [fileDic ks_dictionaryValueForKey:songId];
  797. NSFileManager *fileManager = [NSFileManager defaultManager];
  798. if (![NSString isEmptyString:songUrl]) {
  799. NSString *fileName = [[storeDic ks_dictionaryValueForKey:@"music"] ks_stringValueForKey:@"fileName"];
  800. NSString *filePath = [self getFilePathWithName:fileName];
  801. if ([[fileDic allKeys] containsObject:songId] && [fileManager fileExistsAtPath:filePath] && ![NSString isEmptyString:fileName]) {
  802. hasSaveFile = YES;
  803. }
  804. else {
  805. hasSaveFile = NO;
  806. }
  807. }
  808. if (![NSString isEmptyString:accompanyUrl]) {
  809. NSString *fileName = [[storeDic ks_dictionaryValueForKey:@"accompany"] ks_stringValueForKey:@"fileName"];
  810. NSString *filePath = [self getFilePathWithName:fileName];
  811. if ([[fileDic allKeys] containsObject:songId] && [fileManager fileExistsAtPath:filePath] && ![NSString isEmptyString:fileName]) {
  812. hasSaveFile = YES;
  813. }
  814. else {
  815. hasSaveFile = NO;
  816. }
  817. }
  818. return hasSaveFile;
  819. }
  820. #pragma mark - private method
  821. - (void)bindDelegates {
  822. self.titleView.delegate = self;
  823. [[RTCService sharedInstance] setRTCRoomDelegate:self];
  824. [RTCService sharedInstance].delegate = self;
  825. [ClassroomService sharedService].classroomDelegate = self;
  826. }
  827. - (void)addSubviews {
  828. [self.view addSubview:self.containerView];
  829. [self.view addSubview:self.timeLabel];
  830. [self.containerView bringSubviewToFront:self.timeLabel];
  831. [self.view addSubview:self.titleView];
  832. [self wBoardCtrl];
  833. [self chatAreaView];
  834. [self.view addSubview:self.videoListView];
  835. [self.view bringSubviewToFront:self.titleView];
  836. }
  837. - (void)showChatAreaView{
  838. [self.view addSubview:self.chatAreaView];
  839. }
  840. - (void)hideChatAreaView{
  841. [UIView animateWithDuration:0.2 animations:^{
  842. self.titleView.chatBtn.selected = NO;
  843. [self.chatAreaView removeFromSuperview];
  844. }];
  845. }
  846. - (CGRect)mainContainerViewFrame {
  847. CGFloat x = iPhoneXSafeTopMargin;
  848. CGFloat y = 0;
  849. CGFloat width = KLandscapeWidth - x - iPhoneXSafeBottomMargin;
  850. CGFloat height = KLandscapeHeight - y;
  851. return CGRectMake(x, y, width, height);
  852. }
  853. - (void)displayWhiteboard:(NSString *)boardId {
  854. CGRect frame = [self getWBoardFrame];
  855. [self.wBoardCtrl loadWBoardWithFrame:frame name:boardId];
  856. for (UIView * view in self.view.subviews) {
  857. if ([view isKindOfClass:[ChatAreaView class]]) {
  858. [self.view bringSubviewToFront:view];
  859. }
  860. }
  861. [self.view bringSubviewToFront:self.timeLabel];
  862. }
  863. - (CGRect)getWBoardFrame {
  864. CGRect mainVideoFrame = self.containerView.currentVideoFrame;
  865. mainVideoFrame.origin.x += iPhoneXSafeTopMargin;
  866. mainVideoFrame.origin.y = 10;
  867. return mainVideoFrame;
  868. }
  869. - (void)publishStream {
  870. if ([ClassroomService sharedService].currentRoom.currentMember.role != RoleAudience) {
  871. [[RTCService sharedInstance] publishLocalUserDefaultAVStream];
  872. }
  873. }
  874. - (void)renderMainContainerView {
  875. self.titleView.onShowLabel.hidden = YES;
  876. RoomMember *teacher = [ClassroomService sharedService].currentRoom.teacher;
  877. if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent) {
  878. // 将之前订阅的流切换成小流
  879. [self changeLastDisplayToTinyStream];
  880. // 有打开的屏幕
  881. for (RoomMember *displayMember in [ClassroomService sharedService].currentRoom.memberList) {
  882. if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI]) {
  883. if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
  884. self.titleView.onShowLabel.hidden = NO;
  885. // 重置举手状态
  886. [ClassroomService sharedService].currentRoom.currentMember.handUpOn = NO;
  887. [self.containerView refreshToolView];
  888. }
  889. [self.wBoardCtrl hideBoard];
  890. [self.containerView containerViewRenderView:displayMember];
  891. [self.videoListView showTeacherPrompt:NO];
  892. break;
  893. }
  894. }
  895. }
  896. else {
  897. // 将之前订阅的流切换成小流
  898. [self changeLastDisplayToTinyStream];
  899. if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTeacher && teacher.cameraEnable)) {
  900. NSLog(@"%@",[ClassroomService sharedService].currentRoom.currentDisplayURI);
  901. [self.wBoardCtrl hideBoard];
  902. [self.containerView containerViewRenderView:[ClassroomService sharedService].currentRoom.teacher];
  903. [self.videoListView showTeacherPrompt:YES];
  904. } else if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayWhiteboard) {
  905. [self.containerView cancelRenderView];
  906. [self displayWhiteboard:[ClassroomService sharedService].currentRoom.currentDisplayURI];
  907. [self.videoListView showTeacherPrompt:NO];
  908. } else if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplaySharedScreen)) {
  909. [self.wBoardCtrl hideBoard];
  910. [[RTCService sharedInstance] renderUserSharedScreenOnView:self.containerView.videoView forUser:[ClassroomService sharedService].currentRoom.currentDisplayURI];
  911. // 分享视频
  912. [self.videoListView showTeacherPrompt:NO];
  913. } else {
  914. [self.wBoardCtrl hideBoard];
  915. [self.containerView cancelRenderView];
  916. }
  917. }
  918. }
  919. - (void)changeLastDisplayToTinyStream {
  920. RoomMember *lastMember = self.containerView.member;
  921. if (![NSString isEmptyString:lastMember.userId] && ![lastMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI] && ![lastMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
  922. [[RTCService sharedInstance] exchangeRemoteUserAVStreamToTinySteam:lastMember.userId callback:^{
  923. }];
  924. }
  925. }
  926. - (void)whiteboardDidDisplay:(NSString *)boardId {
  927. NSLog(@"whiteboardDidDisplay %@ ",boardId);
  928. [self renderMainContainerView];
  929. }
  930. - (void)sharedScreenDidDisplay:(NSString *)userId {
  931. NSLog(@"sharedScreenDidDisplay %@ ",userId);
  932. [self renderMainContainerView];
  933. }
  934. - (void)studentDidDisplay:(NSString *)studentId {
  935. NSLog(@"studentDidDisplay %@", studentId);
  936. [self renderMainContainerView];
  937. }
  938. - (void)teacherDidDisplay {
  939. NSLog(@"teacherDidDisplay %@ ",[ClassroomService sharedService].currentRoom.teacher.userId);
  940. [self renderMainContainerView];
  941. }
  942. - (void)noneDidDisplay {
  943. NSLog(@"noneDidDisplay");
  944. [self renderMainContainerView];
  945. }
  946. - (void)checkNodePlayAndSongDownload {
  947. RoomMember *currentMember = [ClassroomService sharedService].currentRoom.currentMember;
  948. if (currentMember.enable) {
  949. // 延迟播放
  950. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  951. [self playNode:currentMember.enable rate:currentMember.rate beatType:currentMember.customType volume:currentMember.volume];
  952. });
  953. }
  954. else {
  955. if (currentMember.musicScoreAccompanimentId != 0) { // 如果有记录当前播放曲目
  956. BOOL needPlay = currentMember.accompanimentPlayStatus || currentMember.playStatus;
  957. [self checkAccompnayDownloadNotifer:currentMember.url accompanyUrl:currentMember.mp3Url songId:[NSString stringWithFormat:@"%d", currentMember.musicScoreAccompanimentId] needPlay:needPlay];
  958. }
  959. }
  960. }
  961. // 播放节拍器的回调
  962. - (void)playNodeAction:(BOOL)enable userId:(NSString *)userId rate:(int)rate beatType:(BeatType)type volume:(NSInteger)volume {
  963. NSLog(@"playNodeAction userId:%@ enable:%@ beatType:%@ rate:%@ volume:%@",userId,@(enable),@(type), @(rate), @(volume));
  964. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  965. if (curMember.role == RoleStudent) {
  966. if ([NSString isEmptyString:userId]) { // 操作全部
  967. [self playNode:enable rate:rate beatType:type volume:volume];
  968. }
  969. else {
  970. NSArray *userIdArray = [userId componentsSeparatedByString:@","];
  971. for (NSString *subUserId in userIdArray) {
  972. if ([curMember.userId isEqualToString:subUserId]) { // 操作个人
  973. [self playNode:enable rate:rate beatType:type volume:volume];
  974. break;
  975. }
  976. }
  977. }
  978. }
  979. }
  980. - (void)playNode:(BOOL)enable rate:(int)rate beatType:(BeatType)type volume:(NSInteger)volume {
  981. NSString *teacherId = [ClassroomService sharedService].currentRoom.teacher.userId;
  982. if (enable) {
  983. if ([ClassroomService sharedService].currentRoom.isPlaySong) { // 停止当前伴奏播放
  984. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  985. [[RCRTCAudioMixer sharedInstance] stop];
  986. }
  987. [self setMetronomeStat:type];
  988. [self setRate:rate];
  989. self.volume = volume;
  990. [self.videoListView updateBeatNode:type beat:rate];
  991. [self play];
  992. [[RTCService sharedInstance] unPlayRemoteUserAudioStreamExcept:teacherId];
  993. }
  994. else {
  995. [self pause];
  996. [self removeAll];
  997. [[RTCService sharedInstance] playAllRemoteUserAudioStream];
  998. }
  999. }
  1000. #pragma mark --- RTCServiceDelegate 自己发布流成功回调
  1001. - (void)successPublishLocalStream {
  1002. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1003. // 检测是否需要通知
  1004. [self notiferTeacherRefreshSongStatus];
  1005. [self checkNodePlayAndSongDownload];
  1006. });
  1007. }
  1008. - (void)notiferTeacherRefreshSongStatus {
  1009. NSArray *songArray = [ClassroomService sharedService].currentRoom.currentMember.songMessage;
  1010. if (songArray.count) {
  1011. [[ClassroomService sharedService] adjustAccompanySongNotifer:nil status:YES];
  1012. }
  1013. }
  1014. #pragma mark - RongRTCRoomDelegate
  1015. - (void)didPublishStreams:(NSArray <RCRTCInputStream *>*)streams {
  1016. NSString *displayUserId;
  1017. if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTeacher)) {
  1018. displayUserId = [ClassroomService sharedService].currentRoom.teacher.userId;
  1019. }
  1020. else if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent) {
  1021. displayUserId = [ClassroomService sharedService].currentRoom.currentDisplayURI;
  1022. }
  1023. // 当用户非正常退出的时候,如果再次进入需要重新订阅流
  1024. RCRTCInputStream *firstStream = [streams lastObject];
  1025. if ([[KSRemoteUserManager shareInstance] isContentUser:firstStream.userId]) {
  1026. [[KSRemoteUserManager shareInstance] removeUser:firstStream.userId];
  1027. }
  1028. for (RCRTCInputStream *stream in streams) {
  1029. if (stream.mediaType == RTCMediaTypeVideo) {
  1030. if ([stream.userId isEqualToString:displayUserId]) {
  1031. [self renderMainContainerView];
  1032. }
  1033. [self.videoListView updateUserVideo:stream.userId];
  1034. }
  1035. }
  1036. }
  1037. - (void)didConnectToStream:(RCRTCInputStream *)stream {
  1038. if (stream.streamId && stream.streamId.length > 0) {
  1039. NSLog(@"didConnectToStream userId:%@ streamID:%@",stream.userId,stream.streamId);
  1040. }
  1041. }
  1042. #pragma mark ----- ClassVideoListViewDelegate
  1043. - (void)videoListView:(ClassVideoListView *)view didTap:(RoomMember *)member {
  1044. self.titleView.isDisplay = YES;
  1045. self.timeManager.isDisplay = YES;
  1046. /*
  1047. // 将视频投到主屏幕上
  1048. NSString *currentDisplayUserId = member.userId;
  1049. NSString *lastDisplayUserId = [ClassroomService sharedService].currentRoom.currentDisplayURI;
  1050. if([currentDisplayUserId isEqualToString:lastDisplayUserId]) {
  1051. [self.videoListView updateUserVideo:lastDisplayUserId];
  1052. return;
  1053. }
  1054. DisplayType type = member.role == RoleTeacher ? DisplayTeacher : DisplayTypeStudent;
  1055. [ClassroomService sharedService].currentRoom.currentDisplayURI = currentDisplayUserId;
  1056. [ClassroomService sharedService].currentRoom.currentDisplayType = type;
  1057. // 如果之前有切换成大流的视频
  1058. if (![NSString isEmptyString:lastDisplayUserId] && ![lastDisplayUserId isEqualToString:[ClassroomService sharedService].currentRoom.currentMemberId]) {
  1059. [[RTCService sharedInstance] exchangeRemoteUserAVStreamToTinySteam:lastDisplayUserId];
  1060. }
  1061. if(![[ClassroomService sharedService].currentRoom.currentMemberId isEqualToString:member.userId]) {
  1062. // 记录当前播放音频的学生
  1063. [ClassroomService sharedService].currentRoom.currentPlayUserId = currentDisplayUserId;
  1064. [[RTCService sharedInstance] exchangeRemoteUserAVStreamToNomalSteam:member.userId];
  1065. }
  1066. [self renderMainContainerView];
  1067. */
  1068. }
  1069. #pragma mark - Getters & setters
  1070. - (ClassroomTimerManager *)timeManager {
  1071. if (!_timeManager) {
  1072. _timeManager = [[ClassroomTimerManager alloc] initWithDelegate:self];
  1073. _timeManager.autoClose = [ClassroomService sharedService].currentRoom.autoCloseFlag;
  1074. // 统一15分钟关闭
  1075. _timeManager.autoCloseNetworkRoomTime = [ClassroomService sharedService].currentRoom.autoCloseNetworkRoomTime;
  1076. }
  1077. return _timeManager;
  1078. }
  1079. - (UILabel *)timeLabel {
  1080. if (!_timeLabel) {
  1081. _timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(KLandscapeWidth - self.videoListView.width - 40 - TTimeLableWidth - iPhoneXSafeBottomMargin, 25, TTimeLableWidth, TTimeLableHeight)];
  1082. _timeLabel.font = [UIFont systemFontOfSize:16];
  1083. _timeLabel.textAlignment = NSTextAlignmentRight;
  1084. _timeLabel.textColor = HexRGB(0xFF5500);
  1085. }
  1086. return _timeLabel;
  1087. }
  1088. - (KSTipsView *)tipsView {
  1089. if (!_tipsView) {
  1090. _tipsView = [[KSTipsView alloc] initWithFrame:CGRectZero];
  1091. }
  1092. return _tipsView;
  1093. }
  1094. - (ClassTitleView *)titleView {
  1095. if (!_titleView) {
  1096. _titleView = [[ClassTitleView alloc] initWithFrame:CGRectMake(iPhoneXSafeTopMargin, 0, KLandscapeWidth - iPhoneXSafeTopMargin - iPhoneXSafeBottomMargin, TitleViewHeight)];
  1097. }
  1098. return _titleView;
  1099. }
  1100. - (ClassVideoListView *)videoListView {
  1101. if(!_videoListView) {
  1102. CGFloat width = 0.0f;
  1103. CGFloat height = 0.0f;
  1104. height = KLandscapeHeight - 10;
  1105. width = height / 3.0f * 4;
  1106. if (width > KLandscapeWidth - 160) {
  1107. width = KLandscapeWidth - 160;
  1108. }
  1109. _videoListView = [[ClassVideoListView alloc] initWithFrame:CGRectMake(width + iPhoneXSafeTopMargin + 5, 10, KLandscapeWidth - (width + 10 + iPhoneXSafeTopMargin) - iPhoneXSafeBottomMargin, height)];
  1110. _videoListView.delegate = self;
  1111. }
  1112. return _videoListView;
  1113. }
  1114. - (ClassroomMainContainer *)containerView {
  1115. if (!_containerView) {
  1116. _containerView = [[ClassroomMainContainer alloc] initWithFrame:[self mainContainerViewFrame]];
  1117. _containerView.delegate = self;
  1118. }
  1119. return _containerView;
  1120. }
  1121. - (ChatAreaView *)chatAreaView {
  1122. if(!_chatAreaView) {
  1123. _chatAreaView = [[ChatAreaView alloc] initWithFrame:CGRectMake(KLandscapeWidth - iPhoneXSafeBottomMargin - 300,0, 300, KLandscapeHeight) conversationType:ConversationType_GROUP targetId:[ClassroomService sharedService].currentRoom.roomId];
  1124. }
  1125. return _chatAreaView;
  1126. }
  1127. - (KSWhiteboardControl *)wBoardCtrl {
  1128. if (!_wBoardCtrl) {
  1129. CGRect frame = [self getWBoardFrame];
  1130. _wBoardCtrl = [[KSWhiteboardControl alloc] initWithDelegate:self viewFrame:frame];
  1131. NSString *roomId = [ClassroomService sharedService].currentRoom.roomId;
  1132. _wBoardCtrl.roomId = roomId;
  1133. _wBoardCtrl.isRatationImage = self.isRorationImage;
  1134. [_wBoardCtrl moveToSuperView:self.view];
  1135. }
  1136. return _wBoardCtrl;
  1137. }
  1138. #pragma mark ---- 网络监测
  1139. - (void)didReportStatusForm:(RCRTCStatusForm *)form {
  1140. const CGFloat lossRateBase = 0.15;
  1141. if (self.packetLossStore.count + form.sendStats.count > 10) {
  1142. NSInteger diff = (self.packetLossStore.count + form.sendStats.count) - 10;
  1143. [self.packetLossStore removeObjectsInRange:NSMakeRange(0,diff)];
  1144. }
  1145. for (RCRTCStreamStat* stat in form.sendStats) {
  1146. [self.packetLossStore addObject:@(stat.packetLoss)];
  1147. }
  1148. float totalSum = 0;
  1149. for (NSNumber* lossRate in self.packetLossStore) {
  1150. totalSum += [lossRate floatValue];
  1151. }
  1152. if (totalSum > 0 && self.packetLossStore.count) {
  1153. float avgLoss = totalSum / self.packetLossStore.count;
  1154. [self showNetworkingStatus:avgLoss];
  1155. if (avgLoss > lossRateBase) { // 网络质量不佳
  1156. dispatch_async(dispatch_get_main_queue(), ^{
  1157. [self.view addSubview:self.networkLabel];
  1158. [self.view bringSubviewToFront:self.networkLabel];
  1159. self.networkLabel.hidden = NO;
  1160. self.networkLabel.text = @"当前通话连接质量不佳";
  1161. });
  1162. }
  1163. else { // 正常
  1164. dispatch_async(dispatch_get_main_queue(), ^{
  1165. self.networkLabel.hidden = YES;
  1166. self.networkLabel.text = @"";
  1167. [self.networkLabel removeFromSuperview];
  1168. });
  1169. }
  1170. }
  1171. }
  1172. - (void)showNetworkingStatus:(float)packageLose {
  1173. dispatch_async(dispatch_get_main_queue(), ^{
  1174. if (packageLose < 0.05) {
  1175. self.titleView.netStatus = NetWorkingStatus_Good;
  1176. }
  1177. else if (packageLose <= 0.1) {
  1178. self.titleView.netStatus = NetWorkingStatus_Well;
  1179. }
  1180. else if (packageLose <= 0.15) {
  1181. self.titleView.netStatus = NetWorkingStatus_Bad;
  1182. }
  1183. else {
  1184. self.titleView.netStatus = NetWorkingStatus_Poor;
  1185. }
  1186. });
  1187. }
  1188. - (NSMutableArray<NSNumber*>*)packetLossStore {
  1189. if (!_packetLossStore) {
  1190. _packetLossStore = [[NSMutableArray alloc] initWithCapacity:10];
  1191. }
  1192. return _packetLossStore;
  1193. }
  1194. - (UILabel *)networkLabel {
  1195. if (!_networkLabel) {
  1196. _networkLabel = [[UILabel alloc] initWithFrame:CGRectMake((KLandscapeWidth - 200) / 2.0f, (KLandscapeHeight - 60) / 2.0f, 200, 60)];
  1197. _networkLabel.backgroundColor = HexRGBAlpha(0x000000, 0.8f);
  1198. _networkLabel.textColor = HexRGB(0xffffff);
  1199. _networkLabel.textAlignment = NSTextAlignmentCenter;
  1200. _networkLabel.font = [UIFont systemFontOfSize:14.0f];
  1201. _networkLabel.layer.cornerRadius = 5.0f;
  1202. }
  1203. return _networkLabel;
  1204. }
  1205. #pragma mark ---- 修改声音模式
  1206. - (void)changeModeAction:(BOOL)enable {
  1207. if (enable) { // 音乐模式切换
  1208. [[RCRTCEngine sharedInstance].defaultAudioStream setAudioQuality:RCRTCAudioQualityMusicHigh Scenario:RCRTCAudioScenarioMusicClassRoom];
  1209. }
  1210. else {
  1211. [[RCRTCEngine sharedInstance].defaultAudioStream setAudioQuality:RCRTCAudioQualityMusicHigh Scenario:RCRTCAudioScenarioDefault];
  1212. }
  1213. }
  1214. - (void)dealloc {
  1215. [[NSNotificationCenter defaultCenter] removeObserver:self];
  1216. if (_timer) {
  1217. [_timer invalidate];
  1218. _timer = nil;
  1219. }
  1220. }
  1221. - (void)setMetronomeStat:(KSMetronomeType)metronomeStat {
  1222. _metronomeStat = metronomeStat;
  1223. self.currentTotalNo = 0;
  1224. if (_timer && _isPlay) {
  1225. [self resetTimer];
  1226. }
  1227. }
  1228. - (void)setRate:(int)rate{
  1229. _rate = rate;
  1230. CGFloat rateFloat = rate;
  1231. self.timerLength = 1 /(rateFloat /60);
  1232. if (self.lastRate == 0) {
  1233. self.lastRate = _rate;
  1234. }
  1235. // 超过10间隔再重置定时器,设置最新间隔秒数
  1236. if (abs(self.lastRate -_rate) >= 10 && _isPlay) {
  1237. self.lastRate = _rate;
  1238. [self resetTimer];
  1239. }
  1240. if (abs(self.lastRate -_rate) >= 10 && !_isPlay) {
  1241. if (_timer) {
  1242. [_timer invalidate];
  1243. _timer = nil;
  1244. }
  1245. }
  1246. }
  1247. - (void)play{
  1248. _isPlay = YES;
  1249. [ClassroomService sharedService].currentRoom.isPlayBeat = YES;
  1250. [self.timer setFireDate:[NSDate distantPast]];
  1251. }
  1252. - (void)pause{
  1253. [[RCRTCAudioMixer sharedInstance] stop];
  1254. [self.timer setFireDate:[NSDate distantFuture]];//暂停计时器
  1255. _isPlay = NO;
  1256. [ClassroomService sharedService].currentRoom.isPlayBeat = NO;
  1257. }
  1258. - (NSTimer *)timer{
  1259. if (!_timer) {
  1260. _timer = [NSTimer scheduledTimerWithTimeInterval:self.timerLength target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
  1261. [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
  1262. }
  1263. return _timer;
  1264. }
  1265. - (void)timerAction {
  1266. if (_metronomeStat ==KSMetronomeType1V4) {
  1267. [self playAction:YES];
  1268. }
  1269. else {
  1270. if (_metronomeStat == KSMetronomeType2V4 && self.currentTotalNo %2 == 0) {
  1271. [self playAction:YES];
  1272. }else if (_metronomeStat == KSMetronomeType3V4 && self.currentTotalNo %3 == 0){
  1273. [self playAction:YES];
  1274. }else if (_metronomeStat == KSMetronomeType4V4 && self.currentTotalNo %4 == 0){
  1275. [self playAction:YES];
  1276. }else if (_metronomeStat == KSMetronomeType6V8 && self.currentTotalNo %6 == 0){
  1277. [self playAction:YES];
  1278. }else{
  1279. [self playAction:NO];
  1280. }
  1281. }
  1282. [self.videoListView showBeatNode:self.currentTotalNo];
  1283. self.currentTotalNo ++;
  1284. }
  1285. - (void)playAction:(BOOL)isDing {
  1286. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  1287. NSString *path = isDing ? self.pathDing : self.pathDong;
  1288. [[RCRTCAudioMixer sharedInstance] stop];
  1289. @try {
  1290. [RCRTCAudioMixer sharedInstance].mixingVolume = self.volume;
  1291. [RCRTCAudioMixer sharedInstance].playingVolume = self.volume;
  1292. [[RCRTCAudioMixer sharedInstance] startMixingWithURL:[NSURL fileURLWithPath:path] playback:YES mixerMode:RCRTCMixerModeMixing loopCount:1];
  1293. } @catch (NSException *exception) {
  1294. NSLog(@"%@", exception);
  1295. } @finally {
  1296. }
  1297. });
  1298. }
  1299. - (NSString *)pathDing {
  1300. if (!_pathDing) {
  1301. _pathDing = [[NSBundle mainBundle] pathForResource:@"tick" ofType:@"wav"];
  1302. }
  1303. return _pathDing;
  1304. }
  1305. - (NSString *)pathDong {
  1306. if (!_pathDong) {
  1307. _pathDong = [[NSBundle mainBundle] pathForResource:@"tock" ofType:@"wav"];
  1308. }
  1309. return _pathDong;
  1310. }
  1311. #pragma mark -- 重置定时器
  1312. - (void)resetTimer {
  1313. [_timer invalidate];
  1314. _timer = nil;
  1315. [self.timer setFireDate:[NSDate distantPast]];
  1316. }
  1317. - (void)removeAll {
  1318. if (_timer) {
  1319. [_timer invalidate];
  1320. _timer = nil;
  1321. }
  1322. }
  1323. #pragma mark --- KSWhiteboardControlDelegate
  1324. - (void)didTurnPage:(NSInteger)pageNum {
  1325. }
  1326. - (void)whiteboardViewDidChangeZoomScale:(float)scale {
  1327. }
  1328. // 白板连接状态回调
  1329. - (void)connectionWhiteboardStatus:(JOINROOMSTATUS)status {
  1330. if (status == JOINROOMSTATUS_FAILED) {
  1331. [self.tipsView showTipsMessage:@"白板连接失败,请检查您的网络" inView:self.view];
  1332. }
  1333. else if (status == JOINROOMSTATUS_PARAMERROR) {
  1334. [self.tipsView showTipsMessage:@"获取白板参数错误" inView:self.view];
  1335. }
  1336. }
  1337. #pragma mark -- mainContainer
  1338. - (void)mainContainerOpertionMember:(RoomMember *)member tapButton:(UIButton *)button {
  1339. // 🙋操作
  1340. if (button.selected) { // 取消举手
  1341. if (self.alertView && self.alertView.isShow) {
  1342. [self.alertView dismissAlertView];
  1343. }
  1344. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"确认取消举手吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  1345. } confirm:^{
  1346. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeHandup];
  1347. }];
  1348. }
  1349. else {
  1350. if (self.alertView && self.alertView.isShow) {
  1351. [self.alertView dismissAlertView];
  1352. }
  1353. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"确认举手吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  1354. } confirm:^{
  1355. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeHandup];
  1356. }];
  1357. }
  1358. }
  1359. #pragma mark ------ 创建MBProgress
  1360. - (MBProgressHUD *)createProgressHUD {
  1361. [MBProgressHUD hideHUD];
  1362. MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  1363. hud.mode = MBProgressHUDModeIndeterminate;
  1364. hud.contentColor = [UIColor whiteColor];
  1365. hud.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
  1366. hud.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
  1367. [hud hideAnimated:YES afterDelay:60.0f]; // 自动关闭时间60s
  1368. return hud;
  1369. }
  1370. /*
  1371. #pragma mark - Navigation
  1372. // In a storyboard-based application, you will often want to do a little preparation before navigation
  1373. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  1374. // Get the new view controller using [segue destinationViewController].
  1375. // Pass the selected object to the new view controller.
  1376. }
  1377. */
  1378. #pragma mark ----- RCRTCEngineEventDelegate
  1379. - (void)didOccurError:(RCRTCCode)errorCode {
  1380. if (errorCode == RCRTCCodeAudioDeviceStartError) {
  1381. // 提示
  1382. [self.tipsView showTipsMessage:@"音频设备启动失败,请尝试重启设备后重新进入" inView:self.view];
  1383. }
  1384. }
  1385. @end