TXClassroomViewController.m 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497
  1. //
  2. // TXClassroomViewController.m
  3. // KulexiuForStudent
  4. //
  5. // Created by 王智 on 2023/8/16.
  6. //
  7. #import "TXClassroomViewController.h"
  8. #import "TXClassTitleView.h"
  9. #import "TXClassroomMainContainer.h"
  10. #import "TXClassroomVideoListView.h"
  11. #import "TXClassRoomAlertView.h"
  12. // 聊天列表
  13. #import "LoginHelper.h"
  14. #import "TXChatAreaView.h"
  15. #import "TXRTCService.h"
  16. #import "Classroom.h"
  17. #import "ClassroomService.h"
  18. #import <MBProgressHUD/MBProgressHUD.h>
  19. #import "AppDelegate.h"
  20. #import "KSWhiteboardControl.h"
  21. #import "UIDevice+TFDevice.h"
  22. #import "KSRemoteUserManager.h"
  23. #import "LocalRenderManager.h"
  24. #import "NSDate+Transform.h"
  25. #import "UIViewController+zhStatusBarStyle.h"
  26. #import "KSTipsView.h"
  27. #import "ClassroomTimerManager.h"
  28. #import "VideoMaskView.h"
  29. #import "TXDanMuView.h"
  30. #import "TXTimeView.h"
  31. #define TitleViewHeight (66)
  32. #define KSExamSongFileKey (@"KSAccompanyDownload")
  33. #define KSSongFolderName (@"KSClassroomSongFile")
  34. #define TTimeLableWidth (180)
  35. #define TTimeLableHeight (20)
  36. @interface TXClassroomViewController ()<TXClassTitleViewDelegate, ClassroomDelegate, TXClassroomMainContainerDelegate, UIGestureRecognizerDelegate, KSWhiteboardControlDelegate, TXClassVideoListViewDelegate, ClassroomTimerManagerDelegate,TXRTCServiceDelegate>
  37. @property (nonatomic, strong) TXClassTitleView *titleView;
  38. @property (nonatomic, strong) TXClassroomMainContainer *containerView;
  39. @property (nonatomic, strong) TXClassroomVideoListView *videoListView;
  40. @property (nonatomic, strong) TXChatAreaView *chatAreaView;
  41. @property (nonatomic, strong) KSWhiteboardControl *wBoardCtrl;
  42. @property (nonatomic, strong) MBProgressHUD *hud;
  43. @property (nonatomic, strong) UILabel *networkLabel;
  44. @property (nonatomic, assign) BOOL cancleAlert;
  45. @property (nonatomic, assign) BOOL isQuitRoom; // 是否正在退出房间
  46. @property (nonatomic, assign) BOOL hasShowAlert;
  47. @property (nonatomic, assign) BOOL isChat;
  48. /** 定时器 */
  49. @property (nonatomic, strong) NSTimer *timer;
  50. /** 定时器多少秒循环一次 */
  51. @property (nonatomic, assign) float timerLength;
  52. /** 当前是否播放状态 */
  53. @property (nonatomic, assign) BOOL isPlay;
  54. /** 当前发出的声次数(用来判断播放"嘀"还是"咚"声音) */
  55. @property (nonatomic, assign) int currentTotalNo;
  56. /** 节拍类型 */
  57. @property (nonatomic, assign) TXKSMetronomeType metronomeStat;
  58. /** 播放速率,范围40~240 */
  59. @property (nonatomic, assign) int rate;
  60. // 播放节拍器音量
  61. @property (nonatomic, assign) NSInteger volume;
  62. /** 记录上一次的频率 */
  63. @property (nonatomic, assign) int lastRate;
  64. @property (nonatomic, strong) NSString *pathDing;
  65. @property (nonatomic, strong) NSString *pathDong;
  66. @property (nonatomic, strong) KSTipsView *tipsView;
  67. @property (nonatomic, strong) ClassroomTimerManager *timeManager;
  68. @property (nonatomic, strong) UILabel *timeLabel;
  69. @property (nonatomic, strong) TXClassRoomAlertView *alertView;
  70. @property (nonatomic, strong) VideoMaskView *maskView;
  71. @property (nonatomic, assign) BOOL squareOn; // 是否开大屏
  72. // 是否隐藏了列表
  73. @property (nonatomic, assign) BOOL videoListHidden;
  74. @property (nonatomic, strong) UIButton *showButton;
  75. @property (nonatomic, strong) TXDanMuView *danView;
  76. @property (nonatomic, strong) TXTimeView *timeView;
  77. // 群属性
  78. @property (nonatomic, assign) BOOL cancelMuteFlag;
  79. @property (nonatomic, assign) BOOL isMuteAll;
  80. @end
  81. @implementation TXClassroomViewController
  82. - (void)changeOrientation:(BOOL)isLandScape {
  83. if (isLandScape) {
  84. // 切换到横屏
  85. if (IS_IPAD) {
  86. self.zh_statusBarHidden = YES;
  87. }
  88. AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  89. delegate.allowAutoRotate = YES;
  90. [UIDevice switchNewOrientation:UIInterfaceOrientationLandscapeRight inController:self];
  91. }
  92. else {
  93. // 切换到横屏
  94. AppDelegate* delegate = (AppDelegate*)[UIApplication sharedApplication].delegate;
  95. delegate.allowAutoRotate = NO;
  96. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  97. if (IS_IPAD) {
  98. self.zh_statusBarHidden = NO;
  99. }
  100. [UIDevice switchNewOrientation:UIInterfaceOrientationPortrait inController:self];
  101. }); }
  102. }
  103. #pragma mark - Life cycle
  104. - (void)viewWillAppear:(BOOL)animated {
  105. [super viewWillAppear:animated];
  106. [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
  107. [IQKeyboardManager sharedManager].enableAutoToolbar = NO;
  108. }
  109. - (void)viewWillDisappear:(BOOL)animated {
  110. [super viewWillDisappear:animated];
  111. if (_isPlay) {
  112. [self pause];
  113. [self removeAll];
  114. }
  115. // 竖屏
  116. [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
  117. [IQKeyboardManager sharedManager].enableAutoToolbar = YES;
  118. [self hideAlertView];
  119. [self judgeShowAlert];
  120. }
  121. - (void)hideAlertView {
  122. if (self.alertView && self.alertView.isShow) {
  123. [self.alertView dismissAlertView];
  124. }
  125. }
  126. - (void)judgeShowAlert {
  127. if (self.cancleAlert || self.hasShowAlert) {
  128. return;
  129. }
  130. if (![NSString isEmptyString:self.classEndTime]) {
  131. NSDateFormatter *dateFormatter = [NSObject getDateformatter];
  132. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  133. NSDate *endDate = [dateFormatter dateFromString:self.classEndTime];
  134. NSDate *currentDate = [NSDate date];
  135. NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:endDate];
  136. if (timeInterval >= 0) { // 课程结束弹出评价
  137. self.hasShowAlert = YES;
  138. [[NSNotificationCenter defaultCenter] postNotificationName:@"showEvaluateAlert" object:self.courseId];
  139. }
  140. }
  141. }
  142. - (void)viewDidAppear:(BOOL)animated {
  143. [super viewDidAppear:animated];
  144. // 切换到横屏
  145. [self changeOrientation:YES];
  146. [self getRoomGroupConfig];
  147. }
  148. - (void)viewDidLoad {
  149. [super viewDidLoad];
  150. // Do any additional setup after loading the view.
  151. self.ks_prefersNavigationBarHidden = YES;
  152. self.view.backgroundColor = HexRGB(0xffffff);
  153. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(leaveRoomAction) name:@"classroomLogout" object:nil];
  154. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(quitRoomAction) name:@"classroomQuit" object:nil];
  155. [self addSubviews];
  156. [self bindDelegates];
  157. [self publishStream];
  158. [self renderMainContainerView];
  159. self.timeManager.isDisplay = YES;
  160. [TRTC_MANAGER useSpeaker:YES];
  161. UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
  162. [self.view addGestureRecognizer:tapGes];
  163. tapGes.delegate = self;
  164. [self showRoleHud];
  165. self.isQuitRoom = NO;
  166. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundQuitRoomAction) name:@"backgroundQuit" object:nil];
  167. [self preloadNodeMusic];
  168. }
  169. - (void)backgroundQuitRoomAction {
  170. self.isQuitRoom = YES;
  171. [[LoginHelper sharedInstance] logout];
  172. self.cancleAlert = YES;
  173. }
  174. #pragma mark ----- 账号异地登陆
  175. - (void)leaveRoomAction {
  176. [self showMessage:@"该账号在其他设备上登录"];
  177. self.isQuitRoom = YES;
  178. [[LoginHelper sharedInstance] logout];
  179. self.cancleAlert = YES;
  180. [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil];
  181. }
  182. - (void)quitRoomAction {
  183. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  184. self.isQuitRoom = YES;
  185. [[LoginHelper sharedInstance] logout];
  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. if (self.titleView.isDisplay) {
  205. self.titleView.isDisplay = NO;
  206. self.timeManager.isDisplay = NO;
  207. }
  208. else {
  209. self.titleView.isDisplay = YES;
  210. self.timeManager.isDisplay = YES;
  211. }
  212. [self hideChatAreaView];
  213. [self.danView resetInputStatus];
  214. }
  215. -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
  216. if ([touch.view isDescendantOfView:self.chatAreaView] || [touch.view isDescendantOfView:self.videoListView] || [touch.view isDescendantOfView:self.maskView] || [touch.view isDescendantOfView:self.wBoardCtrl.customWebView.refreshButton] || [touch.view isDescendantOfView:self.danView] || [touch.view isDescendantOfView:self.titleView]) {
  217. return NO;
  218. }
  219. // if ([touch.view isDescendantOfView:self.wBoardCtrl.wbView] || [touch.view isDescendantOfView:self.wBoardCtrl.customWebView] || [touch.view isDescendantOfView:self.containerView]) {
  220. // [self tapGesture:(UITapGestureRecognizer *)gestureRecognizer];
  221. // }
  222. return YES;
  223. }
  224. - (void)showRoleHud {
  225. Role role = [ClassroomService sharedService].currentRoom.currentMember.role;
  226. if(role == RoleAudience) {
  227. [self.tipsView showTipsMessage:@"你当前身份是旁听人,其他人看/听不见你" inView:self.view];
  228. [self performSelector:@selector(showOnlyYouHUD) withObject:nil afterDelay:5.0f];
  229. }else{
  230. [self showOnlyYouHUD];
  231. }
  232. }
  233. - (void)showOnlyYouHUD {
  234. if ([ClassroomService sharedService].currentRoom.memberList.count == 1) {
  235. [self.tipsView showTipsMessage:@"当前课堂只有你一人,你可以等待或离开" inView:self.view];
  236. }
  237. }
  238. #pragma mark --- TXClassTitleViewDelegate
  239. - (void)classTitleView:(UIButton *)button didTapAtTag:(ClassTitleViewActionTag)tag {
  240. [self.chatAreaView resetInputStatus];
  241. [self.danView resetInputStatus];
  242. ClassRoomTitleButton *tagButton = [self.titleView viewWithTag:tag+1000];
  243. switch (tag) {
  244. case ClassTitleViewActionTagMicrophone:
  245. {
  246. if (tagButton.isSelected == NO) {
  247. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeMicrophone];
  248. }
  249. else {
  250. if (self.isMuteAll && self.cancelMuteFlag == NO) {
  251. [self.tipsView showTipsMessage:@"老师不允许自行解除静音" inView:self.view];
  252. return;
  253. }
  254. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeMicrophone];
  255. }
  256. }
  257. break;
  258. case ClassTitleViewActionTagCamera:
  259. {
  260. if (tagButton.isSelected == NO) {
  261. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeCamera];
  262. }
  263. else {
  264. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeCamera];
  265. }
  266. }
  267. break;
  268. case ClassTitleViewActionTagSwitchCamera:
  269. {
  270. [TRTC_MANAGER switchCamera];
  271. }
  272. break;
  273. case ClassTitleViewActionTagDan: // 弹幕开关
  274. {
  275. tagButton.isSelected = !tagButton.isSelected;
  276. if (tagButton.isSelected) { // 关闭弹幕
  277. [self hideDanChatView];
  278. }
  279. else { // 开启弹幕
  280. [self showDanChatView];
  281. }
  282. }
  283. break;
  284. case ClassTitleViewActionTagChat:
  285. {
  286. tagButton.isSelected = !tagButton.isSelected;
  287. tagButton.isSelected ? [self showChatAreaView] : [self hideChatAreaView];
  288. self.isChat = tagButton.isSelected;
  289. }
  290. break;
  291. case ClassTitleViewActionTagHangup:
  292. {
  293. MJWeakSelf;
  294. self.alertView = [TXClassRoomAlertView shareInstance];
  295. [self.alertView configWithTitle:@"提示" desc:@"确认退出课堂吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  296. } confirm:^{
  297. weakSelf.isQuitRoom = YES;
  298. weakSelf.hud = [self createProgressHUD];
  299. [[LoginHelper sharedInstance] logout];
  300. }];
  301. }
  302. break;
  303. default:
  304. break;
  305. }
  306. }
  307. #pragma mark ---- ClassroomTimerManager delegate
  308. - (void)formatTime:(NSString *)timeString {
  309. self.timeView.timeLabel.text = timeString;
  310. }
  311. - (void)timeLabelAddAnimation:(CABasicAnimation *)animation {
  312. self.timeView.timeLabel.textColor = HexRGB(0x2DC7AA);
  313. [self.timeView.timeLabel.layer addAnimation:animation forKey:nil];
  314. }
  315. - (void)hiddenTitleView {
  316. self.titleView.isDisplay = NO;
  317. }
  318. - (void)quitClassroomNotifer {
  319. // 课程结束后15分钟自动退出房间 连堂课不退出(对内)
  320. if (self.isQuitRoom == NO) {
  321. self.isQuitRoom = YES;
  322. // 退出房间
  323. [self.tipsView showTipsMessage:@"课程已结束" inView:self.view];
  324. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  325. // 课程结束退出房间
  326. SealClassLog(@"ClassEnd!");
  327. self.hud = [self createProgressHUD];
  328. [[LoginHelper sharedInstance] logout];
  329. });
  330. }
  331. }
  332. #pragma mark - ClassroomDelegate
  333. - (void)roomDidLeave {
  334. NSLog(@"roomDidLeave");
  335. // 关闭白板连接
  336. [self.wBoardCtrl.wbView.sdk setCommonCallbackDelegate:nil];
  337. [self.wBoardCtrl.wbView.room disconnect:^{
  338. }];
  339. [TRTC_MANAGER leaveRoom:^(NSInteger code) {
  340. }];
  341. [self dismissClassroom];
  342. }
  343. - (void)dismissClassroom {
  344. [self.hud hideAnimated:YES];
  345. [ClassroomService sharedService].currentRoom = nil;
  346. [self changeOrientation:NO];
  347. [self.timeManager stopDurationTimer];
  348. [self.timeLabel.layer removeAllAnimations];
  349. if(_isPlay) {
  350. [self pause];
  351. [self removeAll];
  352. }
  353. [self.navigationController popViewControllerAnimated:YES];
  354. }
  355. - (void)memberDidJoin:(RoomMember *)member {
  356. NSLog(@"memberDidJoin %@",member);
  357. if(member.role != RoleAudience) {
  358. [self.videoListView reloadVideoList];
  359. }
  360. if (member.role == RoleTeacher || member.role == RoleAssistant) {
  361. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"老师%@已经进入教室", member.name] inView:self.view];
  362. if (_isPlay) {
  363. [self pause];
  364. [self removeAll];
  365. [TRTC_MANAGER playAllRemoteStudentAudioStream];
  366. }
  367. if ([ClassroomService sharedService].currentRoom.isPlaySong) {
  368. [self stopMixUrl];
  369. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  370. [TRTC_MANAGER playAllRemoteStudentAudioStream];
  371. }
  372. [self renderMainContainerView];
  373. }
  374. else if ((member.role == RoleStudent) && (_isPlay || [ClassroomService sharedService].currentRoom.isPlaySong)) {
  375. [TRTC_MANAGER unPlayRemoteStudentWithId:member.userId];
  376. }
  377. }
  378. - (void)memberDidLeave:(RoomMember *)member {
  379. NSLog(@"memberDidLeave %@",member);
  380. // 此处移除
  381. [[KSRemoteUserManager shareInstance] removeUser:member.userId];
  382. if(member.role != RoleAudience) {
  383. [self.videoListView reloadVideoList];
  384. }
  385. if (member.role == RoleTeacher || member.role == RoleAssistant) {
  386. // 老师退出房间后,学生如果在节拍器播放时停止节拍器
  387. if (_isPlay) {
  388. [self pause];
  389. [self removeAll];
  390. [TRTC_MANAGER playAllRemoteStudentAudioStream];
  391. }
  392. if ([ClassroomService sharedService].currentRoom.isPlaySong) {
  393. [self stopMixUrl];
  394. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  395. [TRTC_MANAGER playAllRemoteStudentAudioStream];
  396. }
  397. [[ClassroomService sharedService].currentRoom changeDisplayNone];
  398. [self noneDidDisplay];
  399. }
  400. }
  401. - (void)memberDidKick:(RoomMember *)member {
  402. NSLog(@"memberDidKick %@",member);
  403. [[KSRemoteUserManager shareInstance] removeUser:member.userId];
  404. if ([ClassroomService sharedService].currentRoom.currentMember.role == RoleTeacher) {
  405. [self.tipsView showTipsMessage:[NSString stringWithFormat:@"你已将%@移出课堂", member.name] inView:self.view];
  406. }
  407. [self.videoListView reloadVideoList];
  408. if (self.containerView.member.role == member.role) {
  409. [self.containerView cancelRenderView];
  410. }
  411. }
  412. - (void)errorDidOccur:(ErrorCode)code {
  413. NSLog(@"errorDidOccur %@",@(code));
  414. [self.hud hideAnimated:YES];
  415. if (code != ErrorCodeSuccess) {
  416. if (code == ErrorCodeOverMaxUserCount) {
  417. [self.tipsView showTipsMessage:@"超过人数限制" inView:self.view];
  418. }else {
  419. [self.tipsView showTipsMessage:@"操作失败,请稍后再试" inView:self.view];
  420. }
  421. }
  422. }
  423. - (void)deviceDidEnable:(BOOL)enable type:(DeviceType)type forUser:(RoomMember *)member operator:(nonnull NSString *)operatorId {
  424. NSLog(@"deviceDidEnable devicetype:%@ enable:%@ memeber:%@",@(type),@(enable),member);
  425. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  426. NSString *hudMessage = @"";
  427. if ([curMember.userId isEqualToString:member.userId]) {
  428. if (type == DeviceTypeCamera) { // 摄像头
  429. // 非自己操作才需要做提示
  430. if(![curMember.userId isEqualToString:operatorId]){
  431. hudMessage = !enable ? @"老师已关闭你的摄像头" : NSLocalizedStringFromTable(@"CameraOpend", @"SealClass", nil);
  432. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  433. }
  434. [self.titleView refreshTitleView];
  435. [TRTC_MANAGER setCameraDisable:!enable];
  436. [self refreshVideoView:member];
  437. } else if (type == DeviceTypeMicrophone) { // 麦克风
  438. // 非自己操作才需要做提示
  439. if(![curMember.userId isEqualToString:operatorId]){
  440. hudMessage = !enable ? @"老师已关闭你的麦克风" : NSLocalizedStringFromTable(@"MicorophoneOpend", @"SealClass", nil);
  441. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  442. }
  443. [self.titleView refreshTitleView];
  444. [TRTC_MANAGER setMicrophoneDisable:!enable];
  445. }
  446. else if (type == DeviceTypeMusicMode) {
  447. }
  448. else if (type == DeviceTypeHandup) {
  449. [self.containerView refreshToolView];
  450. [self.videoListView updateUserVideo:member.userId];
  451. }
  452. else if (type == DeviceTypeExamSong) {
  453. }
  454. else if (type == DeviceTypeAccompany || type == DeviceTypeMusicScore) { // 原音或伴奏
  455. if (enable) {
  456. [self playSongAction];
  457. }
  458. else {
  459. [self playSongEnable:NO];
  460. }
  461. }
  462. }
  463. else {
  464. if (type == DeviceTypeHandup) { // 摄像头
  465. [self.videoListView updateUserVideo:member.userId];
  466. }
  467. // else if (type == DeviceTypeMicrophone) { // 麦克风
  468. //
  469. // }
  470. }
  471. }
  472. // 刷新单个视频显示
  473. - (void)refreshVideoView:(RoomMember *)member {
  474. NSString *currentUserID = [ClassroomService sharedService].currentRoom.currentMemberId;
  475. if (self.squareOn && [member.userId isEqualToString:currentUserID]) {
  476. if (member.cameraEnable) {
  477. self.maskView.closeCameraView.hidden = YES;
  478. }
  479. else {
  480. self.maskView.closeCameraView.hidden = NO;
  481. }
  482. }
  483. else {
  484. // 刷新单个数据
  485. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  486. if ([member.userId isEqualToString:curMember.userId]) {
  487. if ([ClassroomService sharedService].currentRoom.currentDisplayType != DisplayWhiteboard && [[ClassroomService sharedService].currentRoom.currentDisplayURI isEqualToString:member.userId]) {
  488. [self.wBoardCtrl hideBoard];
  489. [self.containerView containerViewRenderView:member];
  490. }
  491. else {
  492. [self.videoListView updateUserVideo:member.userId];
  493. }
  494. [self.titleView refreshTitleView];
  495. }
  496. else {
  497. if ([ClassroomService sharedService].currentRoom.currentDisplayType != DisplayWhiteboard && [[ClassroomService sharedService].currentRoom.currentDisplayURI isEqualToString:member.userId]) {
  498. [self.wBoardCtrl hideBoard];
  499. [self.containerView containerViewRenderView:member];
  500. }
  501. else {
  502. [self.videoListView updateUserVideo:member.userId];
  503. }
  504. }
  505. }
  506. [self.containerView refreshToolView];
  507. }
  508. - (void)controlDeviceDidEnable:(BOOL)enable type:(DeviceType)type forUser:(RoomMember *)member {
  509. NSLog(@"controlDeviceDidEnable devicetype:%@ enable:%@ memeber:%@",@(type),@(enable),member);
  510. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  511. NSString *hudMessage = @"";
  512. if ([curMember.userId isEqualToString:member.userId]) {
  513. if (type == DeviceTypeCamera) { // 摄像头
  514. hudMessage = !enable ? @"老师已关闭你的摄像头" : NSLocalizedStringFromTable(@"CameraOpend", @"SealClass", nil);
  515. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  516. [[ClassroomService sharedService] enableDevice:enable withType:DeviceTypeCamera];
  517. } else if (type == DeviceTypeMicrophone) { // 麦克风
  518. hudMessage = !enable ? @"老师已关闭你的麦克风" : NSLocalizedStringFromTable(@"MicorophoneOpend", @"SealClass", nil);
  519. [self.tipsView showTipsMessage:hudMessage inView:self.view];
  520. [[ClassroomService sharedService] enableDevice:enable withType:DeviceTypeMicrophone];
  521. }
  522. else if (type == DeviceTypeAccompany || type == DeviceTypeMusicScore) {
  523. if (enable) {
  524. [self playSongAction];
  525. }
  526. else {
  527. [self playSongEnable:NO];
  528. }
  529. }
  530. }
  531. }
  532. //助教请求用户打开设备,助教关闭用户设备没有回调。
  533. - (void)deviceDidInviteEnable:(DeviceType)type ticket:(NSString *)ticket {
  534. NSLog(@"deviceDidInviteEnable devicetype:%@ ticket:%@ ",@(type),ticket);
  535. }
  536. #pragma mark - private method
  537. - (void)bindDelegates {
  538. self.titleView.delegate = self;
  539. [ClassroomService sharedService].classroomDelegate = self;
  540. TRTC_MANAGER.delegate = self;
  541. }
  542. - (void)addSubviews {
  543. [self.view addSubview:self.containerView];
  544. [self.view addSubview:self.timeView];
  545. [self.timeView mas_makeConstraints:^(MASConstraintMaker *make) {
  546. make.right.mas_equalTo(self.containerView.emptyView.mas_right).offset(-20);
  547. make.top.mas_equalTo(self.view.mas_top);
  548. make.height.mas_equalTo(44);
  549. }];
  550. [self.view bringSubviewToFront:self.timeView];
  551. [self.view addSubview:self.titleView];
  552. [self showDanChatView];
  553. [self wBoardCtrl];
  554. [self chatAreaView];
  555. [self.view addSubview:self.videoListView];
  556. [self.view bringSubviewToFront:self.titleView];
  557. }
  558. - (void)showChatAreaView{
  559. [self.view addSubview:self.chatAreaView];
  560. [self.chatAreaView scrollToBottom];
  561. }
  562. - (void)hideChatAreaView{
  563. [UIView animateWithDuration:0.2 animations:^{
  564. self.titleView.chatBtn.isSelected = NO;
  565. [self.chatAreaView removeFromSuperview];
  566. }];
  567. }
  568. - (void)showDanChatView {
  569. // 视频页面添加弹幕
  570. [self.view addSubview:self.danView];
  571. [self.view bringSubviewToFront:self.danView];
  572. self.danView.isShow = YES;
  573. }
  574. - (void)hideDanChatView {
  575. //
  576. self.danView.isShow = NO;
  577. [self.danView removeFromSuperview];
  578. }
  579. - (CGRect)mainContainerViewFrame {
  580. CGFloat x = 0;
  581. CGFloat y = 0;
  582. CGFloat width = KLandscapeWidth - x;
  583. CGFloat height = KLandscapeHeight - y;
  584. return CGRectMake(x, y, width, height);
  585. }
  586. - (void)displayWhiteboard:(NSString *)boardId {
  587. CGRect frame = [self getWBoardFrame];
  588. [self.wBoardCtrl loadWBoardWithFrame:frame name:boardId];
  589. for (UIView * view in self.view.subviews) {
  590. if ([view isKindOfClass:[TXChatAreaView class]]) {
  591. [self.view bringSubviewToFront:view];
  592. }
  593. }
  594. if (self.videoListHidden) {
  595. [self.view bringSubviewToFront:self.showButton];
  596. }
  597. if (self.danView.isShow) {
  598. [self.view bringSubviewToFront:self.danView];
  599. }
  600. [self.view bringSubviewToFront:self.timeView];
  601. [self.view bringSubviewToFront:self.titleView];
  602. }
  603. - (void)refreshWhiteBoardFrame {
  604. [self.wBoardCtrl setWBoardFrame:[self getWBoardFrame]];
  605. }
  606. - (CGRect)getWBoardFrame {
  607. CGRect mainVideoFrame = CGRectZero;
  608. CGFloat width = 0;
  609. CGFloat height = 0;
  610. if (self.videoListHidden) {
  611. width = KLandscapeWidth;
  612. height = KLandscapeHeight;
  613. }
  614. else {
  615. width = KLandscapeWidth -(144 + 20 + 10);
  616. height = KLandscapeHeight;
  617. }
  618. mainVideoFrame.size.width = width;
  619. mainVideoFrame.size.height = height;
  620. mainVideoFrame.origin.x += 0;
  621. mainVideoFrame.origin.y = 0;
  622. return mainVideoFrame;
  623. }
  624. - (void)publishStream {
  625. if ([ClassroomService sharedService].currentRoom.currentMember.role != RoleAudience) {
  626. [TRTC_MANAGER publishStream];
  627. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  628. // 同步状态
  629. [self checkNodePlayAndSongPlay];
  630. });
  631. }
  632. }
  633. - (void)checkNodePlayAndSongPlay {
  634. RoomMember *currentMember = [ClassroomService sharedService].currentRoom.currentMember;
  635. if (currentMember.enable) {
  636. // 延迟播放
  637. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  638. [self playNode:currentMember.enable rate:currentMember.rate beatType:currentMember.customType volume:currentMember.volume];
  639. });
  640. }
  641. else {
  642. if (currentMember.musicScoreAccompanimentId != 0) { // 如果有记录当前播放曲目
  643. BOOL needPlay = currentMember.accompanimentPlayStatus || currentMember.playStatus;
  644. if (needPlay) {
  645. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  646. [self playSongAction];
  647. });
  648. }
  649. }
  650. }
  651. }
  652. - (void)renderMainContainerView {
  653. if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent) {
  654. // 有打开的屏幕
  655. for (RoomMember *displayMember in [ClassroomService sharedService].currentRoom.memberList) {
  656. if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI]) {
  657. if ([displayMember.userId isEqualToString:[ClassroomService sharedService].currentRoom.currentMember.userId]) {
  658. }
  659. [self.wBoardCtrl hideBoard];
  660. [self.containerView containerViewRenderView:displayMember];
  661. [self.videoListView showTeacherPrompt:NO];
  662. break;
  663. }
  664. }
  665. }
  666. else {
  667. if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTeacher) {
  668. NSLog(@"%@",[ClassroomService sharedService].currentRoom.currentDisplayURI);
  669. [self.wBoardCtrl hideBoard];
  670. [self.containerView containerViewRenderView:[ClassroomService sharedService].currentRoom.teacher];
  671. [self.videoListView showTeacherPrompt:YES];
  672. } else if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayWhiteboard) {
  673. [self.containerView cancelRenderView];
  674. [self displayWhiteboard:[ClassroomService sharedService].currentRoom.currentDisplayURI];
  675. [self.videoListView showTeacherPrompt:NO];
  676. } else if (([ClassroomService sharedService].currentRoom.currentDisplayType == DisplaySharedScreen)) {
  677. [self.wBoardCtrl hideBoard];
  678. [TRTC_MANAGER renderRemoteUser:[ClassroomService sharedService].currentRoom.currentDisplayURI inView:self.containerView.videoView];
  679. // 分享视频
  680. [self.videoListView showTeacherPrompt:NO];
  681. } else {
  682. [self.wBoardCtrl hideBoard];
  683. [self.containerView cancelRenderView];
  684. [self.videoListView showTeacherPrompt:NO];
  685. }
  686. }
  687. }
  688. - (void)whiteboardDidDisplay:(NSString *)boardId {
  689. NSLog(@"whiteboardDidDisplay %@ ",boardId);
  690. [self renderMainContainerView];
  691. }
  692. - (void)sharedScreenDidDisplay:(NSString *)userId {
  693. NSLog(@"sharedScreenDidDisplay %@ ",userId);
  694. [self renderMainContainerView];
  695. }
  696. - (void)studentDidDisplay:(NSString *)studentId {
  697. NSLog(@"studentDidDisplay %@", studentId);
  698. [self renderMainContainerView];
  699. }
  700. - (void)teacherDidDisplay {
  701. NSLog(@"teacherDidDisplay %@ ",[ClassroomService sharedService].currentRoom.teacher.userId);
  702. [self renderMainContainerView];
  703. }
  704. - (void)noneDidDisplay {
  705. NSLog(@"noneDidDisplay");
  706. [self renderMainContainerView];
  707. }
  708. // 播放节拍器的回调
  709. - (void)playNodeAction:(BOOL)enable userId:(NSString *)userId rate:(int)rate beatType:(BeatType)type volume:(NSInteger)volume {
  710. NSLog(@"playNodeAction userId:%@ enable:%@ beatType:%@ rate:%@ volume:%@",userId,@(enable),@(type), @(rate), @(volume));
  711. RoomMember *curMember = [ClassroomService sharedService].currentRoom.currentMember;
  712. if (curMember.role == RoleStudent) {
  713. if ([NSString isEmptyString:userId]) { // 操作全部
  714. [self playNode:enable rate:rate beatType:type volume:volume];
  715. }
  716. else {
  717. NSArray *userIdArray = [userId componentsSeparatedByString:@","];
  718. for (NSString *subUserId in userIdArray) {
  719. if ([curMember.userId isEqualToString:subUserId]) { // 操作个人
  720. [self playNode:enable rate:rate beatType:type volume:volume];
  721. break;
  722. }
  723. }
  724. }
  725. }
  726. }
  727. - (void)playNode:(BOOL)enable rate:(int)rate beatType:(BeatType)type volume:(NSInteger)volume {
  728. if (enable) {
  729. if ([ClassroomService sharedService].currentRoom.isPlaySong) { // 停止当前伴奏播放
  730. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  731. }
  732. [self setMetronomeStat:type];
  733. [self setRate:rate];
  734. self.volume = volume;
  735. [self.videoListView updateBeatNode:type beat:rate];
  736. [self play];
  737. [TRTC_MANAGER unPlayRemoteStudentAudioStream];
  738. }
  739. else {
  740. [self.videoListView hideBeatView];
  741. [self pause];
  742. [self removeAll];
  743. [TRTC_MANAGER playAllRemoteStudentAudioStream];
  744. }
  745. }
  746. #pragma mark ----- ClassVideoListViewDelegate
  747. - (void)videoListView:(TXClassroomVideoListView *)view didTap:(RoomMember *)member {
  748. self.titleView.isDisplay = YES;
  749. self.timeManager.isDisplay = YES;
  750. NSString *currentUserID = [ClassroomService sharedService].currentRoom.currentMemberId;
  751. if ([member.userId isEqualToString:currentUserID]) { // 如果是当前用户
  752. if (member.cameraEnable) {
  753. self.maskView.closeCameraView.hidden = YES;
  754. }
  755. else {
  756. self.maskView.closeCameraView.hidden = NO;
  757. }
  758. [ClassroomService sharedService].currentRoom.currentMaskUserId = currentUserID;
  759. [TRTC_MANAGER renderLocalUserInView:self.maskView.maskVideoView isCameraEnable:member.cameraEnable];
  760. [self.view addSubview:self.maskView];
  761. self.squareOn = YES;
  762. }
  763. }
  764. - (void)videoListView:(TXClassroomVideoListView *)view didHideView:(BOOL)isHideView {
  765. if (isHideView) { // 隐藏video list
  766. [self hideVideoList];
  767. }
  768. else { // 显示
  769. [self showVideoList];
  770. }
  771. }
  772. - (void)hideVideoList {
  773. self.videoListHidden = YES;
  774. CGRect frame = self.videoListView.frame;
  775. self.videoListView.frame = CGRectMake(KLandscapeWidth, frame.origin.y, frame.size.width, frame.size.height);
  776. [self.containerView changeVideoFrameFull:YES];
  777. [self refreshWhiteBoardFrame];
  778. [self.view addSubview:self.showButton];
  779. }
  780. - (void)showVideoList {
  781. self.videoListHidden = NO;
  782. CGRect frame = self.videoListView.frame;
  783. self.videoListView.frame = CGRectMake(KLandscapeWidth - frame.size.width, frame.origin.y, frame.size.width, frame.size.height);
  784. [self.containerView changeVideoFrameFull:NO];
  785. [self refreshWhiteBoardFrame];
  786. [self.showButton removeFromSuperview];
  787. }
  788. #pragma mark ---- TXRTCServiceDelegate
  789. - (void)didReportNetworkQuality:(TRTCQuality)quality remoteQuality:(NSArray<TRTCQualityInfo *> *)remoteQuality {
  790. if (quality >= 5) { // 网络质量不佳
  791. dispatch_async(dispatch_get_main_queue(), ^{
  792. [self.view addSubview:self.networkLabel];
  793. [self.view bringSubviewToFront:self.networkLabel];
  794. self.networkLabel.hidden = NO;
  795. self.networkLabel.text = @"当前通话连接质量不佳";
  796. });
  797. }
  798. else { // 正常
  799. dispatch_async(dispatch_get_main_queue(), ^{
  800. self.networkLabel.hidden = YES;
  801. self.networkLabel.text = @"";
  802. [self.networkLabel removeFromSuperview];
  803. });
  804. }
  805. [self showNetworkingStatus:quality remoteQuality:remoteQuality];
  806. }
  807. - (void)showNetworkingStatus:(TRTCQuality)quality remoteQuality:(NSArray<TRTCQualityInfo *> *)remoteQuality {
  808. dispatch_async(dispatch_get_main_queue(), ^{
  809. // 显示本地
  810. NSString *currentUserID = [ClassroomService sharedService].currentRoom.currentMemberId;
  811. if ([ClassroomService sharedService].currentRoom.currentDisplayType == DisplayTypeStudent && [currentUserID isEqualToString:[ClassroomService sharedService].currentRoom.currentDisplayURI]) { // 如果主屏是学生自己
  812. [self.containerView.displayView updateUserQualityNetWorkingStatus:[self getNetStatusWithQuality:quality]];
  813. }
  814. else {
  815. [self.videoListView updateUserQuality:currentUserID netWorkingStatus:[self getNetStatusWithQuality:quality]];
  816. }
  817. // 远端
  818. for (TRTCQualityInfo *info in remoteQuality) {
  819. NSString *userId = info.userId;
  820. if ([info.userId isEqualToString:self.containerView.member.userId]) {
  821. [self.videoListView updateUserQuality:userId netWorkingStatus:[self getNetStatusWithQuality:quality]];
  822. }
  823. }
  824. });
  825. }
  826. - (TXNetWorkingStatus)getNetStatusWithQuality:(TRTCQuality)quality {
  827. TXNetWorkingStatus status = TXNetWorkingStatus_Full;
  828. if (quality == 1 || quality == 2) {
  829. status = TXNetWorkingStatus_Full;
  830. }
  831. else if (quality == 3) {
  832. status = TXNetWorkingStatus_Good;
  833. }
  834. else if (quality == 4) {
  835. status = TXNetWorkingStatus_Nomal;
  836. }
  837. else if (quality == 5) {
  838. status = TXNetWorkingStatus_Bad;
  839. }
  840. else { //
  841. status = TXNetWorkingStatus_Poor;
  842. }
  843. return status;
  844. }
  845. - (void)didReportUserVolume:(NSArray<TRTCVolumeInfo *> *)userVolumes {
  846. NSString *currentUserID = [ClassroomService sharedService].currentRoom.currentMemberId;
  847. for (TRTCVolumeInfo *info in userVolumes) {
  848. NSString *userId = info.userId;
  849. if ([NSString isEmptyString:userId]) {
  850. userId = currentUserID;
  851. }
  852. [self.videoListView updateMicStatus:userId volume:info.volume];
  853. // 更新title
  854. if ([currentUserID isEqualToString:userId]) {
  855. [self.titleView updateUserVolume:info.volume];
  856. }
  857. // 更新container
  858. if ([self.containerView.member.userId isEqualToString:userId]) {
  859. [self.containerView updateUserVolume:info.volume];
  860. }
  861. }
  862. }
  863. #pragma mark - Getters & setters
  864. - (VideoMaskView *)maskView {
  865. if (!_maskView) {
  866. CGFloat width = (KLandscapeWidth);
  867. CGRect frame = CGRectMake(0, 0, width, KLandscapeHeight);
  868. _maskView = [[VideoMaskView alloc] initWithFrame:frame];
  869. [_maskView addDismisTarget:self action:@selector(dismissMaskViewEvent)];
  870. }
  871. return _maskView;
  872. }
  873. - (void)dismissMaskViewEvent {
  874. self.squareOn = NO;
  875. [ClassroomService sharedService].currentRoom.currentMaskUserId = nil;
  876. // 切换成小流
  877. [self removeMaskViewIfNeed];
  878. [self renderMainContainerView];
  879. [self.videoListView reloadVideoList];
  880. }
  881. - (void)removeMaskViewIfNeed {
  882. [self.maskView removeFromSuperview];
  883. _maskView = nil;
  884. }
  885. - (ClassroomTimerManager *)timeManager {
  886. if (!_timeManager) {
  887. _timeManager = [[ClassroomTimerManager alloc] initWithDelegate:self];
  888. // NSString *userType = UserDefault(UserTypeKey);
  889. NSString *userType = @"1";
  890. if ([userType isEqualToString:@"1"]) {
  891. _timeManager.autoClose = [ClassroomService sharedService].currentRoom.autoCloseFlag;
  892. }
  893. else {
  894. _timeManager.autoClose = YES;
  895. }
  896. // 统一15分钟关闭
  897. _timeManager.autoCloseNetworkRoomTime = [ClassroomService sharedService].currentRoom.autoCloseNetworkRoomTime;
  898. }
  899. return _timeManager;
  900. }
  901. - (TXTimeView *)timeView {
  902. if (!_timeView) {
  903. _timeView = [TXTimeView shareInstance];
  904. }
  905. return _timeView;
  906. }
  907. - (KSTipsView *)tipsView {
  908. if (!_tipsView) {
  909. _tipsView = [[KSTipsView alloc] initWithFrame:CGRectZero];
  910. }
  911. return _tipsView;
  912. }
  913. - (TXClassTitleView *)titleView {
  914. if (!_titleView) {
  915. _titleView = [[TXClassTitleView alloc] initWithFrame:CGRectMake(0, 0, KLandscapeWidth, TitleViewHeight)];
  916. }
  917. return _titleView;
  918. }
  919. - (TXClassroomVideoListView *)videoListView {
  920. if(!_videoListView) {
  921. CGFloat width = VIDEO_WIDTH + 20 + 10;
  922. CGFloat height = KLandscapeHeight;
  923. _videoListView = [[TXClassroomVideoListView alloc] initWithFrame:CGRectMake(KLandscapeWidth - width, 0, width, height)];
  924. _videoListView.delegate = self;
  925. }
  926. return _videoListView;
  927. }
  928. - (UIButton *)showButton {
  929. if (!_showButton) {
  930. _showButton = [UIButton buttonWithType:UIButtonTypeCustom];
  931. _showButton.frame = CGRectMake(KLandscapeWidth-30, (KLandscapeHeight-30)/2.0, 30, 30);
  932. [_showButton setImage:[UIImage imageNamed:@"videoList_show"] forState:UIControlStateNormal];
  933. [_showButton addTarget:self action:@selector(showVideoList) forControlEvents:UIControlEventTouchUpInside];
  934. }
  935. return _showButton;
  936. }
  937. - (TXClassroomMainContainer *)containerView {
  938. if (!_containerView) {
  939. _containerView = [[TXClassroomMainContainer alloc] initWithFrame:[self mainContainerViewFrame]];
  940. _containerView.delegate = self;
  941. }
  942. return _containerView;
  943. }
  944. - (TXChatAreaView *)chatAreaView {
  945. if (!_chatAreaView) {
  946. _chatAreaView = [[TXChatAreaView alloc] initWithFrame:CGRectMake(KLandscapeWidth - 300,0, 300, KLandscapeHeight) chatGroupId:[ClassroomService sharedService].currentRoom.roomId];
  947. }
  948. return _chatAreaView;
  949. }
  950. - (KSWhiteboardControl *)wBoardCtrl {
  951. if (!_wBoardCtrl) {
  952. CGRect frame = [self getWBoardFrame];
  953. _wBoardCtrl = [[KSWhiteboardControl alloc] initWithDelegate:self viewFrame:frame];
  954. NSString *roomId = [ClassroomService sharedService].currentRoom.roomId;
  955. _wBoardCtrl.randomNumeric = [ClassroomService sharedService].currentRoom.randomNumeric;
  956. if ([roomId containsString:@"S"] || [roomId containsString:@"I"]) {
  957. roomId = [roomId substringFromIndex:1];
  958. }
  959. _wBoardCtrl.roomId = roomId;
  960. [_wBoardCtrl moveToSuperView:self.view];
  961. }
  962. return _wBoardCtrl;
  963. }
  964. - (void)dealloc {
  965. NSLog(@"--------");
  966. [[NSNotificationCenter defaultCenter] removeObserver:self];
  967. if (_timer) {
  968. [_timer invalidate];
  969. _timer = nil;
  970. }
  971. }
  972. - (void)stopBeatMix {
  973. TXAudioEffectManager *audioManager = [TRTCCloud sharedInstance].getAudioEffectManager;
  974. [audioManager stopPlayMusic:100];
  975. [audioManager stopPlayMusic:101];
  976. }
  977. - (void)preloadNodeMusic {
  978. TXAudioEffectManager *audioManager = [TRTCCloud sharedInstance].getAudioEffectManager;
  979. [audioManager preloadMusic:[self getTickAudioParms] onProgress:^(NSInteger progress) {
  980. } onError:^(NSInteger errorCode) {
  981. }];
  982. [audioManager preloadMusic:[self getTockAudioParms] onProgress:^(NSInteger progress) {
  983. } onError:^(NSInteger errorCode) {
  984. }];
  985. }
  986. - (TXAudioMusicParam *)getTickAudioParms {
  987. TXAudioMusicParam *parms = [[TXAudioMusicParam alloc] init];
  988. parms.ID = 100;
  989. parms.isShortFile = YES;
  990. parms.publish = YES;
  991. parms.loopCount = 0;
  992. parms.path = self.pathDing;
  993. return parms;
  994. }
  995. - (TXAudioMusicParam *)getTockAudioParms {
  996. TXAudioMusicParam *parms = [[TXAudioMusicParam alloc] init];
  997. parms.ID = 101;
  998. parms.isShortFile = YES;
  999. parms.publish = YES;
  1000. parms.loopCount = 0;
  1001. parms.path = self.pathDong;
  1002. return parms;
  1003. }
  1004. - (void)setMetronomeStat:(TXKSMetronomeType)metronomeStat {
  1005. _metronomeStat = metronomeStat;
  1006. self.currentTotalNo = 0;
  1007. if (_timer && _isPlay) {
  1008. [self resetTimer];
  1009. }
  1010. }
  1011. - (void)setRate:(int)rate{
  1012. _rate = rate;
  1013. CGFloat rateFloat = rate;
  1014. self.timerLength = 1 /(rateFloat /60);
  1015. if (self.lastRate == 0) {
  1016. self.lastRate = _rate;
  1017. }
  1018. // 超过10间隔再重置定时器,设置最新间隔秒数
  1019. if (abs(self.lastRate -_rate) >= 10 && _isPlay) {
  1020. self.lastRate = _rate;
  1021. [self resetTimer];
  1022. }
  1023. if (abs(self.lastRate -_rate) >= 10 && !_isPlay) {
  1024. if (_timer) {
  1025. [_timer invalidate];
  1026. _timer = nil;
  1027. }
  1028. }
  1029. }
  1030. - (void)play{
  1031. _isPlay = YES;
  1032. [ClassroomService sharedService].currentRoom.isPlayBeat = YES;
  1033. [self.timer setFireDate:[NSDate distantPast]];
  1034. }
  1035. - (void)pause{
  1036. [self.timer setFireDate:[NSDate distantFuture]];//暂停计时器
  1037. _isPlay = NO;
  1038. [ClassroomService sharedService].currentRoom.isPlayBeat = NO;
  1039. }
  1040. - (NSTimer *)timer{
  1041. if (!_timer) {
  1042. _timer = [NSTimer scheduledTimerWithTimeInterval:self.timerLength target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
  1043. [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
  1044. }
  1045. return _timer;
  1046. }
  1047. - (void)timerAction {
  1048. if (_metronomeStat ==TXKSMetronomeType1V4) {
  1049. [self playAction:YES];
  1050. }
  1051. else {
  1052. if (_metronomeStat == TXKSMetronomeType2V4 && self.currentTotalNo %2 == 0) {
  1053. [self playAction:YES];
  1054. }else if (_metronomeStat == TXKSMetronomeType3V4 && self.currentTotalNo %3 == 0){
  1055. [self playAction:YES];
  1056. }else if (_metronomeStat == TXKSMetronomeType4V4 && self.currentTotalNo %4 == 0){
  1057. [self playAction:YES];
  1058. }else if (_metronomeStat == TXKSMetronomeType6V8 && self.currentTotalNo %6 == 0){
  1059. [self playAction:YES];
  1060. }else{
  1061. [self playAction:NO];
  1062. }
  1063. }
  1064. [self.videoListView showBeatNode:self.currentTotalNo];
  1065. self.currentTotalNo ++;
  1066. }
  1067. - (void)playAction:(BOOL)isDing {
  1068. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  1069. [self stopBeatMix];
  1070. @try {
  1071. [self startMixBeat:isDing];
  1072. } @catch (NSException *exception) {
  1073. NSLog(@"%@", exception);
  1074. } @finally {
  1075. }
  1076. });
  1077. }
  1078. - (void)startMixBeat:(BOOL)isDing {
  1079. TXAudioEffectManager *audioManager = [TRTCCloud sharedInstance].getAudioEffectManager;
  1080. TXAudioMusicParam *parms = isDing ? [self getTickAudioParms] : [self getTockAudioParms];
  1081. [audioManager startPlayMusic:parms onStart:^(NSInteger errCode) {
  1082. [audioManager setAllMusicVolume:self.volume];
  1083. } onProgress:^(NSInteger progressMs, NSInteger durationMs) {
  1084. } onComplete:^(NSInteger errCode) {
  1085. }];
  1086. }
  1087. - (NSString *)pathDing {
  1088. if (!_pathDing) {
  1089. _pathDing = [[NSBundle mainBundle] pathForResource:@"tick" ofType:@"wav"];
  1090. }
  1091. return _pathDing;
  1092. }
  1093. - (NSString *)pathDong {
  1094. if (!_pathDong) {
  1095. _pathDong = [[NSBundle mainBundle] pathForResource:@"tock" ofType:@"wav"];
  1096. }
  1097. return _pathDong;
  1098. }
  1099. #pragma mark -- 重置定时器
  1100. - (void)resetTimer {
  1101. [_timer invalidate];
  1102. _timer = nil;
  1103. [self.timer setFireDate:[NSDate distantPast]];
  1104. }
  1105. - (void)removeAll {
  1106. if (_timer) {
  1107. [_timer invalidate];
  1108. _timer = nil;
  1109. }
  1110. }
  1111. #pragma mark --- KSWhiteboardControlDelegate
  1112. - (void)didTurnPage:(NSInteger)pageNum {
  1113. }
  1114. - (void)whiteboardViewDidChangeZoomScale:(float)scale {
  1115. }
  1116. // 白板连接状态回调
  1117. - (void)connectionWhiteboardStatus:(JOINROOMSTATUS)status {
  1118. if (status == JOINROOMSTATUS_FAILED) {
  1119. [self.tipsView showTipsMessage:@"白板连接失败,请检查您的网络" inView:self.view];
  1120. }
  1121. else if (status == JOINROOMSTATUS_PARAMERROR) {
  1122. [self.tipsView showTipsMessage:@"获取白板参数错误" inView:self.view];
  1123. }
  1124. }
  1125. #pragma mark -- mainContainer
  1126. - (void)mainContainerOpertionMember:(RoomMember *)member tapButton:(UIButton *)button {
  1127. // 🙋操作
  1128. MainToolButton *toolButton = self.containerView.toolView.shareButton;
  1129. if (toolButton.isSelected) { // 取消举手
  1130. self.alertView = [TXClassRoomAlertView shareInstance];
  1131. [self.alertView configWithTitle:@"提示?" desc:@"确认取消举手吗?" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  1132. } confirm:^{
  1133. [[ClassroomService sharedService] enableDevice:NO withType:DeviceTypeHandup];
  1134. }];
  1135. }
  1136. else {
  1137. self.alertView = [TXClassRoomAlertView shareInstance];
  1138. MJWeakSelf;
  1139. [self.alertView configWithTitle:@"确认要举手吗?" desc:@"举手后,有机会被老师选中上台演示哦!" leftTitle:@"取消" rightTitle:@"确认" cancel:^{
  1140. } confirm:^{
  1141. [weakSelf.tipsView showTipsMessage:@"您已成功举手" inView:weakSelf.view];
  1142. [[ClassroomService sharedService] enableDevice:YES withType:DeviceTypeHandup];
  1143. }];
  1144. }
  1145. }
  1146. #pragma mark ------ 创建MBProgress
  1147. - (MBProgressHUD *)createProgressHUD {
  1148. [MBProgressHUD hideHUD];
  1149. MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  1150. hud.mode = MBProgressHUDModeIndeterminate;
  1151. hud.contentColor = [UIColor whiteColor];
  1152. hud.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
  1153. hud.bezelView.backgroundColor = [UIColor colorWithHexString:@"#000000" alpha:0.8];
  1154. [hud hideAnimated:YES afterDelay:60.0f]; // 自动关闭时间60s
  1155. return hud;
  1156. }
  1157. #pragma mark ----- 混音功能
  1158. - (void)playSongAction {
  1159. // 获取路径 播放伴奏
  1160. RoomMember *member = [ClassroomService sharedService].currentRoom.currentMember;
  1161. NSString *musicUrl = nil;
  1162. if (member.accompanimentPlayStatus) {
  1163. musicUrl = member.mp3Url;
  1164. }
  1165. else if (member.playStatus) {
  1166. musicUrl = member.url;
  1167. }
  1168. if (![NSString isEmptyString:musicUrl]) {
  1169. [self playSongEnable:YES];
  1170. [self mixWithUrl:musicUrl];
  1171. }
  1172. }
  1173. - (void)playSongEnable:(BOOL)enable {
  1174. if (enable) {
  1175. if ([ClassroomService sharedService].currentRoom.isPlayBeat) {
  1176. [self pause];
  1177. [self removeAll];
  1178. }
  1179. [ClassroomService sharedService].currentRoom.isPlaySong = YES;
  1180. [TRTC_MANAGER unPlayRemoteStudentAudioStream];
  1181. }
  1182. else {
  1183. [self stopMixUrl];
  1184. [ClassroomService sharedService].currentRoom.isPlaySong = NO;
  1185. [TRTC_MANAGER playAllRemoteStudentAudioStream];
  1186. }
  1187. }
  1188. - (void)mixWithUrl:(NSString *)remoteUrl {
  1189. NSInteger soundVolome = [ClassroomService sharedService].currentRoom.soundVolume;
  1190. TXAudioEffectManager *audioManager = [TRTCCloud sharedInstance].getAudioEffectManager;
  1191. TXAudioMusicParam *parms = [[TXAudioMusicParam alloc] init];
  1192. parms.ID = 10;
  1193. parms.isShortFile = NO;
  1194. parms.publish = YES;
  1195. parms.loopCount = 100;
  1196. parms.path = [remoteUrl getUrlEndcodeString];
  1197. [audioManager startPlayMusic:parms onStart:^(NSInteger errCode) {
  1198. [audioManager setAllMusicVolume:soundVolome];
  1199. } onProgress:^(NSInteger progressMs, NSInteger durationMs) {
  1200. } onComplete:^(NSInteger errCode) {
  1201. }];
  1202. }
  1203. - (void)stopMixUrl {
  1204. TXAudioEffectManager *audioManager = [TRTCCloud sharedInstance].getAudioEffectManager;
  1205. [audioManager pausePlayMusic:10];
  1206. }
  1207. #pragma mark ----- 首页弹幕聊天
  1208. - (TXDanMuView *)danView {
  1209. if (!_danView) {
  1210. _danView = [[TXDanMuView alloc] initWithFrame:CGRectMake(38, KLandscapeHeight - 160 - 20, 260, 160) chatGroupId:[ClassroomService sharedService].currentRoom.roomId];
  1211. }
  1212. return _danView;
  1213. }
  1214. - (void)getRoomGroupConfig {
  1215. [self addGroupNotifer];
  1216. [self getClassroomConfig];
  1217. }
  1218. - (void)addGroupNotifer {
  1219. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveGroupMessage:) name:OnReceiveTXClassroomGroupNotification object:nil];
  1220. }
  1221. - (void)getClassroomConfig {
  1222. NSString *roomId = [ClassroomService sharedService].currentRoom.roomId;
  1223. [[V2TIMManager sharedInstance] getGroupAttributes:roomId keys:nil succ:^(NSMutableDictionary<NSString *,NSString *> *groupAttributeList) {
  1224. [self evaluateGroupInfo:groupAttributeList];
  1225. } fail:^(int code, NSString *desc) {
  1226. NSLog(@"");
  1227. }];
  1228. }
  1229. #pragma mark ------ 群属性
  1230. - (void)didReceiveGroupMessage:(NSNotification *)notification {
  1231. NSDictionary *dic = notification.object; /// __block TXLiveMessageModel *liveMessage
  1232. NSString *groupId = [dic ks_stringValueForKey:@"groupID"];
  1233. NSDictionary *content = [dic ks_dictionaryValueForKey:@"message"];
  1234. NSString *roomId = [ClassroomService sharedService].currentRoom.roomId;
  1235. if ([groupId isEqualToString:roomId]) {
  1236. [self evaluateGroupInfo:content];
  1237. }
  1238. }
  1239. - (void)evaluateGroupInfo:(NSDictionary *)groupInfo {
  1240. if ([[groupInfo allKeys] containsObject:@"cancelMuteFlag"]) {
  1241. self.cancelMuteFlag = [groupInfo ks_boolValueForKey:@"cancelMuteFlag"];
  1242. }
  1243. else {
  1244. self.cancelMuteFlag = YES;
  1245. }
  1246. if ([[groupInfo allKeys] containsObject:@"isMuteAll"]) {
  1247. self.isMuteAll = [groupInfo ks_boolValueForKey:@"isMuteAll"];
  1248. }
  1249. else {
  1250. self.isMuteAll = NO;
  1251. }
  1252. }
  1253. /*
  1254. #pragma mark - Navigation
  1255. // In a storyboard-based application, you will often want to do a little preparation before navigation
  1256. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  1257. // Get the new view controller using [segue destinationViewController].
  1258. // Pass the selected object to the new view controller.
  1259. }
  1260. */
  1261. @end