LiveVideoRoomViewController.m 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708
  1. //
  2. // LiveVideoRoomViewController.m
  3. // KulexiuForTeacher
  4. //
  5. // Created by Kyle on 2022/3/30.
  6. //
  7. #import "LiveVideoRoomViewController.h"
  8. #import "KSChatInputBarControl.h"
  9. #import "LiveRoomHeadView.h"
  10. #import "LiveRoomBottomView.h"
  11. #import "KSRCMessageModel.h"
  12. #import "UserInfoManager.h"
  13. #import "KSChatroomMessageCenter.h"
  14. #import <RongIMKit/RongIMKit.h>
  15. #import "KSChatroomTextCell.h"
  16. #import "KSNormalAlertView.h"
  17. #import "KSLiveStreamVideo.h"
  18. #import "SeatContentView.h"
  19. #import "LiveroomTimeManager.h"
  20. #import "KSLiveEmptyView.h"
  21. #import "KSChatVideoView.h"
  22. #import "LiveRoomLikeLayer.h"
  23. #import "KSDragWindowManager.h"
  24. //#import <Lottie/Lottie.h>
  25. #import "KSShopCardView.h"
  26. #import "KSLiveWebViewController.h"
  27. typedef NS_ENUM(NSInteger, MICSTATUS) {
  28. MICSTATUS_NOMAL, // 正常状态
  29. MICSTATUS_WAITING, // 申请中
  30. MICSTATUS_CONNECTING, // 连麦中
  31. };
  32. @interface LiveVideoRoomViewController ()<RCRTCEngineEventDelegate,RCRTCRoomEventDelegate,RCRTCStatusReportDelegate,UIGestureRecognizerDelegate,KSChatInputBarControlDelegate,UITableViewDataSource,UITableViewDelegate,LiveroomTimeManagerDelegate>
  33. @property (nonatomic, strong) LiveroomTimeManager *timeManager;
  34. // 音频配置
  35. @property(strong, nonatomic) RCRTCEngine *engine;
  36. @property (nonatomic, strong) RCRTCRoom *room;
  37. @property (nonatomic, strong) RCRTCLiveInfo *liveInfo;
  38. @property (nonatomic, strong) KSLiveStreamVideo *localVideo;
  39. @property (nonatomic) NSMutableArray <KSLiveStreamVideo *>*streamVideos;
  40. // 视频主窗口容器
  41. @property (nonatomic, strong) UIView *videoContainerView;
  42. @property (nonatomic, strong) KSChatVideoView *videoView;
  43. @property (nonatomic, strong) LiveRoomHeadView *headView;
  44. @property (nonatomic, copy) LiveRoomBottomView *bottomView;
  45. /**
  46. 上次点赞按钮点击时间
  47. */
  48. @property(nonatomic, assign) NSTimeInterval lastClickPraiseTime;
  49. // 连麦状态
  50. @property (nonatomic, assign) MICSTATUS micStatus;
  51. /// 提示窗
  52. @property (nonatomic, strong) KSNormalAlertView *alertView;
  53. /// 连麦的视图
  54. @property (nonatomic, strong) SeatContentView *seatContainer;
  55. // 聊天UI
  56. /*!
  57. 消息列表CollectionView和输入框都在这个view里
  58. */
  59. @property(nonatomic, strong) UIView *messageContentView;
  60. /*!
  61. 会话页面的TableView
  62. */
  63. @property (nonatomic, strong) UITableView *conversationMessageTableView;
  64. /**
  65. 输入工具栏
  66. */
  67. @property(nonatomic,strong) KSChatInputBarControl *inputBar;
  68. /**
  69. * 是否需要滚动到底部
  70. */
  71. @property(nonatomic, assign) BOOL isNeedScrollToButtom;
  72. /*!
  73. 聊天内容的消息Cell数据模型的数据源
  74. @discussion 数据源中存放的元素为消息Cell的数据模型,即RCDLiveMessageModel对象。
  75. */
  76. @property(nonatomic, strong) NSMutableArray<KSRCMessageModel *> *conversationDataRepository;
  77. @property (nonatomic, assign) BOOL isImConnected;
  78. // 远端连麦用户
  79. @property (nonatomic, strong) NSMutableArray *remoteMemberArray;
  80. /// 主讲人是否在房间
  81. @property (nonatomic, assign) BOOL isCreaterInRoom;
  82. @property (nonatomic, assign) BOOL isCreaterVideoEnable;
  83. @property (nonatomic, assign) BOOL isCreaterAudioEnable;
  84. @property (nonatomic, strong) KSLiveEmptyView *emptyView;
  85. /// 是否在切换身份
  86. @property (nonatomic, assign) BOOL isTransferRole;
  87. @property (nonatomic, assign) BOOL hasSendWelcomeMessage;
  88. @property (nonatomic, assign) BOOL hasShowSuspendView;
  89. @property (nonatomic, assign) BOOL isPackageLostTips;
  90. @property (nonatomic, assign) NSTimeInterval lostStartTime; // 开始丢包事件
  91. @property (nonatomic, assign) NSTimeInterval bitErrorTime; // video bit zero start
  92. @property (nonatomic, assign) BOOL isLostPackage; // 严重丢包
  93. @property (nonatomic, assign) BOOL isBitError; // bit error
  94. //@property (nonatomic, strong) LOTAnimationView *animationView;
  95. @property (nonatomic, assign) BOOL isCloseRoom; // 是否直播结束
  96. @property (nonatomic, strong) KSShopCardView *cardView;
  97. @end
  98. // 用于记录点赞消息连续点击的次数
  99. static int clickPraiseBtnTimes = 0;
  100. @implementation LiveVideoRoomViewController
  101. - (instancetype)init {
  102. self = [super init];
  103. if (self) {
  104. [self defaultConfig];
  105. }
  106. return self;
  107. }
  108. - (void)defaultConfig {
  109. self.micStatus = MICSTATUS_NOMAL;
  110. self.isCreaterInRoom = NO; // 默认主讲人不在房间
  111. self.isImConnected = [USER_MANAGER checkIMConnected];
  112. self.isCloseRoom = NO;
  113. }
  114. - (void)viewDidLoad {
  115. [super viewDidLoad];
  116. // Do any additional setup after loading the view.
  117. self.ks_prefersNavigationBarHidden = YES;
  118. [self registerNotification];
  119. [self setupUI];
  120. if (self.isImConnected) {
  121. [self connectService];
  122. }
  123. else {
  124. MJWeakSelf;
  125. [USER_MANAGER connectionIMCallback:^(BOOL isSuccess) {
  126. dispatch_main_async_safe(^{
  127. [weakSelf connectService];
  128. });
  129. }];
  130. }
  131. [self judgeAutoClose];
  132. }
  133. - (void)muteAllAudio {
  134. if (self.room) {
  135. [self.room muteAllRemoteAudio:YES];
  136. }
  137. }
  138. - (void)IMConnetedCallback {
  139. dispatch_main_async_safe(^{
  140. // IM 连接成功回调
  141. if (self.hasSendWelcomeMessage) { // 如果已经发送了进入消息
  142. // 查询是否直播间开启
  143. [KSNetworkingManager liveRoomJoinRoomRequest:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) {
  144. if ([dic integerValueForKey:@"code"] == 200 && [dic boolValueForKey:@"status"]) {
  145. // 回调信息
  146. MJWeakSelf;
  147. [self notiferJoinSuccessToServiceCallback:^{
  148. [self MBPShow:@"IM连接成功"];
  149. [weakSelf sendWelcomeMessage];
  150. }];
  151. }
  152. else {
  153. [self MBPShow:MESSAGEKEY];
  154. [self quitRoom];
  155. }
  156. } faliure:^(NSError * _Nonnull error) {
  157. }];
  158. }
  159. });
  160. }
  161. - (void)sendWelcomeMessage {
  162. [[RCIMClient sharedRCIMClient] joinExistChatRoom:self.roomId messageCount:-1 success:^{
  163. KSLiveChatroomWelcome *joinChatroomMessage = [[KSLiveChatroomWelcome alloc] init];
  164. [joinChatroomMessage setMsgId:[RCIM sharedRCIM].currentUserInfo.userId];
  165. [self sendMessage:joinChatroomMessage displayMessage:YES callback:^(BOOL success) {
  166. }];
  167. } error:^(RCErrorCode status) {
  168. NSLog(@"error code %zd" ,status);
  169. }];
  170. }
  171. - (void)judgeAutoClose {
  172. if (self.isTempRoom == NO) {
  173. self.timeManager.autoCloseNetworkRoomTime = [self getCloseTime];
  174. }
  175. }
  176. - (void)liveroomLogOut {
  177. [self MBPShow:@"该账号在其他设备上登录"];
  178. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  179. [self quitAction];
  180. });
  181. }
  182. - (void)liveroomQuit {
  183. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  184. [self quitAction];
  185. });
  186. }
  187. - (void)registerNotification {
  188. [KSChatroomMessageCenter registerMessageTypes];
  189. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMessageNotification:) name:OnReceiveChatroomMessageNotification object:nil];
  190. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomLogOut) name:@"liveroomLogout" object:nil];
  191. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomQuit) name:@"liveroomQuit" object:nil];
  192. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(IMConnetedCallback) name:@"RongIMConnected" object:nil];
  193. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(muteAllAudio) name:@"muteLiveAudio" object:nil];
  194. }
  195. - (void)setupUI {
  196. CGSize size = self.view.bounds.size;
  197. self.view.backgroundColor = HexRGB(0x25292e);
  198. // 视图主容器
  199. [self.view addSubview:self.videoContainerView];
  200. [self.videoContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
  201. make.left.top.bottom.right.mas_equalTo(self.view);
  202. }];
  203. [self.videoContainerView addSubview:self.videoView];
  204. [self.videoView mas_makeConstraints:^(MASConstraintMaker *make) {
  205. make.left.top.bottom.right.mas_equalTo(self.view);
  206. }];
  207. [self.view addSubview:self.headView];
  208. self.headView.boardcastName.text = [NSString returnNoNullStringWithString:self.createrName];
  209. if (![NSString isEmptyString:self.createrAvatal]) {
  210. [self.headView.boardcastAvatal sd_setImageWithURL:[NSURL URLWithString:[self.createrAvatal getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:@"teacher_logo"]];
  211. }
  212. [self countLikeMessageCount];
  213. [self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
  214. make.left.right.mas_equalTo(self.view);
  215. make.height.mas_equalTo(90);
  216. if (@available(iOS 11.0, *)) {
  217. make.top.mas_equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(10);
  218. } else {
  219. // Fallback on earlier versions
  220. make.top.mas_equalTo(self.view.mas_top).offset(10);
  221. }
  222. }];
  223. [self.view addSubview:self.messageContentView];
  224. [self.view addSubview:self.bottomView];
  225. [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
  226. make.left.right.mas_equalTo(self.view);
  227. if (@available(iOS 11.0, *)) {
  228. make.bottom.mas_equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-10);
  229. } else {
  230. // Fallback on earlier versions
  231. make.bottom.mas_equalTo(self.view.mas_bottom).offset(-10);
  232. }
  233. make.height.mas_equalTo(44);
  234. }];
  235. [self.messageContentView mas_makeConstraints:^(MASConstraintMaker *make) {
  236. make.left.mas_equalTo(self.view.mas_left);
  237. make.right.mas_equalTo(self.view.mas_right);
  238. make.bottom.mas_equalTo(self.bottomView.mas_top).offset(-10);
  239. make.height.mas_equalTo(270);
  240. }];
  241. [self.view addSubview:self.inputBar];
  242. [_inputBar setBackgroundColor: [UIColor whiteColor]];
  243. [_inputBar setFrame:CGRectMake(0, kScreenHeight, size.width , 50)];
  244. [_inputBar setHidden:YES];
  245. [self.messageContentView addSubview:self.conversationMessageTableView];
  246. [self.conversationMessageTableView mas_makeConstraints:^(MASConstraintMaker *make) {
  247. make.left.top.mas_equalTo(self.messageContentView);
  248. make.width.mas_equalTo(240);
  249. make.bottom.mas_equalTo(self.messageContentView.mas_bottom);
  250. }];
  251. UITapGestureRecognizer *resetBottomTapGesture =[[UITapGestureRecognizer alloc]
  252. initWithTarget:self
  253. action:@selector(resetBottomGesture:)];
  254. resetBottomTapGesture.delegate = self;
  255. [self.view addGestureRecognizer:resetBottomTapGesture];
  256. }
  257. - (void)connectService {
  258. [self setRoleType];
  259. [self joinChatRoom];
  260. }
  261. - (void)joinChatRoom {
  262. [[RCIMClient sharedRCIMClient] joinChatRoom:self.roomId messageCount:-1 success:^{
  263. dispatch_async(dispatch_get_main_queue(), ^{
  264. [self addConstMessage];
  265. });
  266. KSLiveChatroomWelcome *joinChatroomMessage = [[KSLiveChatroomWelcome alloc] init];
  267. [joinChatroomMessage setMsgId:[RCIM sharedRCIM].currentUserInfo.userId];
  268. MJWeakSelf;
  269. [self sendMessage:joinChatroomMessage displayMessage:YES callback:^(BOOL success) {
  270. weakSelf.hasSendWelcomeMessage = YES;
  271. }];
  272. // 加入成功发送消息
  273. } error:^(RCErrorCode status) {
  274. if (status == RC_CHATROOM_NOT_EXIST || status == KICKED_FROM_CHATROOM || status == RC_PARAMETER_INVALID_CHATROOM) {
  275. dispatch_async(dispatch_get_main_queue(), ^{
  276. [self MBPShow:@"加入聊天室失败"];
  277. });
  278. }
  279. }];
  280. }
  281. - (void)viewWillAppear:(BOOL)animated {
  282. [super viewWillAppear:animated];
  283. [UIApplication sharedApplication].idleTimerDisabled = YES;
  284. [IQKeyboardManager sharedManager].enableAutoToolbar = NO;
  285. [IQKeyboardManager sharedManager].enable = NO;
  286. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
  287. // 重新渲染view
  288. KSDragWindowManager *manager = [KSDragWindowManager sharedManager];
  289. if (manager.hasShowWindow) {
  290. [manager resignDragWindow];
  291. }
  292. if (self.hasShowSuspendView) {
  293. KSLiveStreamVideo *mainVideo = nil;
  294. if (self.videoView.streamId) {
  295. for (KSLiveStreamVideo *streamVideo in self.streamVideos) {
  296. if ([streamVideo.streamId isEqualToString:self.videoView.streamId]) {
  297. mainVideo = streamVideo;
  298. break;
  299. }
  300. }
  301. }
  302. if (mainVideo) {
  303. [self.videoView addSubview:mainVideo.canvesView];
  304. [mainVideo.canvesView mas_updateConstraints:^(MASConstraintMaker *make) {
  305. make.left.right.bottom.top.mas_equalTo(self.videoView);
  306. }];
  307. }
  308. _hasShowSuspendView = NO;
  309. }
  310. if (self.room) {
  311. [self.room muteAllRemoteAudio:NO];
  312. }
  313. }
  314. - (void)viewWillDisappear:(BOOL)animated {
  315. [super viewWillDisappear:animated];
  316. [UIApplication sharedApplication].idleTimerDisabled = NO;
  317. [IQKeyboardManager sharedManager].enableAutoToolbar = YES;
  318. [IQKeyboardManager sharedManager].enable = YES;
  319. if (@available(iOS 13.0, *)) {
  320. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDarkContent;
  321. } else {
  322. // Fallback on earlier versions
  323. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
  324. }
  325. // 退出房间才停止定时器
  326. if (_timeManager) {
  327. [_timeManager stopDurationTimer];
  328. }
  329. }
  330. // 刷新seatView
  331. - (void)renderSeatView {
  332. NSMutableArray *seatArray = [NSMutableArray array];
  333. NSArray *remoteUserArray = [self.room.remoteUsers mutableCopy];
  334. for (RCRTCRemoteUser *user in remoteUserArray) {
  335. if (![user.userId isEqualToString:self.videoView.streamUserId] && ![user.userId isEqualToString:self.createrId]) {
  336. [seatArray addObject:user.userId];
  337. }
  338. }
  339. if (self.micStatus == MICSTATUS_CONNECTING && ![seatArray containsObject:UserDefault(UIDKey)]) {
  340. [seatArray addObject:UserDefault(UIDKey)];
  341. }
  342. if (seatArray.count) {
  343. if (![self.videoContainerView.subviews containsObject:self.seatContainer]) {
  344. [self.videoContainerView addSubview:self.seatContainer];
  345. }
  346. [self updateSeatContainerConstraint];
  347. self.seatContainer.seatMemberArray = [NSMutableArray arrayWithArray:seatArray];
  348. [self.seatContainer refreshSeatUI];
  349. }
  350. else {
  351. [self removeSeatContainer];
  352. }
  353. }
  354. - (void)updateSeatContainerConstraint {
  355. [self.seatContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
  356. make.left.right.mas_equalTo(self.videoContainerView);
  357. make.top.mas_equalTo(self.headView.mas_bottom).offset(0);
  358. make.height.mas_equalTo(70);
  359. }];
  360. }
  361. // 移除连麦视图
  362. - (void)removeSeatContainer {
  363. if ([self.videoContainerView.subviews containsObject:self.seatContainer]) {
  364. [self.seatContainer removeFromSuperview];
  365. }
  366. }
  367. - (void)countLikeMessageCount {
  368. NSInteger count = self.likeCount;
  369. NSString *desc = @"";
  370. if (count > 10000) {
  371. count = count / 10000;
  372. desc = [NSString stringWithFormat:@"本场点赞%zd万+",count];
  373. }
  374. else {
  375. desc = [NSString stringWithFormat:@"本场点赞%zd",count];
  376. }
  377. self.headView.likeCount.text = desc;
  378. if (count > 10000) {
  379. count = count / 10000;
  380. desc = [NSString stringWithFormat:@"%zd万+人",count];
  381. }
  382. else {
  383. desc = [NSString stringWithFormat:@"%zd人",count];
  384. }
  385. }
  386. - (void)setRoleType {
  387. // 1.设置切换听筒为扬声器
  388. [self.engine enableSpeaker:YES];
  389. // 2.加入RTC房间
  390. [self joinLiveRoom];
  391. }
  392. // 观众上下麦
  393. - (void)connectHostWithStatus:(BOOL)isConnect {
  394. // 转换中,不再处理转换身份
  395. if (self.isTransferRole) {
  396. return;
  397. }
  398. // 如果需要切换的身份和当前身份相同 拦截
  399. if (isConnect && self.liveRoleType == RCRTCLiveRoleTypeBroadcaster) {
  400. return;
  401. }
  402. else if (isConnect == NO && self.liveRoleType == RCRTCLiveRoleTypeAudience) {
  403. return;
  404. }
  405. self.isTransferRole = YES;
  406. // 清理当前视图
  407. [self.seatContainer removeFromSuperview];
  408. if (isConnect) {
  409. // 开启麦克风采集
  410. [self.engine.defaultAudioStream setMicrophoneDisable:NO];
  411. NSArray *streams = @[self.engine.defaultAudioStream];
  412. MJWeakSelf;
  413. [self.room.localUser switchToBroadcaster:streams onSucceed:^(RCRTCLiveInfo * _Nonnull liveInfo) {
  414. weakSelf.isTransferRole = NO;
  415. dispatch_main_async_safe(^{
  416. __strong typeof(weakSelf) strongSelf = weakSelf;
  417. //3.切换成功, 订阅远端用户流
  418. strongSelf.liveRoleType = RCRTCLiveRoleTypeBroadcaster;
  419. NSMutableArray *streamArray = [NSMutableArray array];
  420. for (RCRTCRemoteUser *user in self.room.remoteUsers) {
  421. if (user.remoteStreams.count) {
  422. [streamArray addObjectsFromArray:user.remoteStreams];
  423. }
  424. }
  425. if (streamArray.count) {
  426. [strongSelf subscribeRemoteResource:streamArray];
  427. }
  428. else {
  429. }
  430. strongSelf.micStatus = MICSTATUS_CONNECTING;
  431. // 刷新麦位状态
  432. strongSelf.remoteMemberArray = [NSMutableArray arrayWithArray:self.room.remoteUsers];
  433. NSLog(@"------- room member %ld, ", strongSelf.remoteMemberArray.count);
  434. [strongSelf renderSeatView];
  435. });
  436. } onFailed:^(RCRTCCode code) {
  437. NSLog(@"------- up error code %ld", code);
  438. weakSelf.isTransferRole = NO;
  439. dispatch_main_async_safe(^{
  440. // 失败
  441. [weakSelf MBPShow:@"上麦失败"];
  442. weakSelf.micStatus = MICSTATUS_NOMAL;
  443. });
  444. } onKicked:^{
  445. weakSelf.isTransferRole = NO;
  446. dispatch_main_async_safe(^{
  447. // 被踢出
  448. [weakSelf kickOutLiveRoom];
  449. });
  450. }];
  451. }
  452. else { // 观众下麦
  453. MJWeakSelf;
  454. [self.room.localUser switchToAudienceOnSucceed:^{
  455. weakSelf.isTransferRole = NO;
  456. dispatch_main_async_safe(^{
  457. __strong typeof(weakSelf) strongSelf = weakSelf;
  458. strongSelf.liveRoleType = RCRTCLiveRoleTypeAudience;
  459. [strongSelf.engine.defaultAudioStream setMicrophoneDisable:YES];
  460. if (strongSelf.room.remoteUsers.count) {
  461. NSMutableArray *streamArray = [NSMutableArray array];
  462. for (RCRTCRemoteUser *user in self.room.remoteUsers) {
  463. if (user.remoteStreams.count) {
  464. [streamArray addObjectsFromArray:user.remoteStreams];
  465. [strongSelf subscribeRemoteResource:streamArray];
  466. }
  467. }
  468. }
  469. else {
  470. }
  471. strongSelf.micStatus = MICSTATUS_NOMAL;
  472. strongSelf.remoteMemberArray = [NSMutableArray arrayWithArray:self.room.remoteUsers];
  473. // 刷新麦位状态
  474. [strongSelf renderSeatView];
  475. // 发送下麦消息
  476. [weakSelf sendDownSeatMessage];
  477. });
  478. } onFailed:^(RCRTCCode code) {
  479. weakSelf.isTransferRole = NO;
  480. NSLog(@"------- down error code %ld", code);
  481. dispatch_main_async_safe(^{
  482. // 失败
  483. [weakSelf MBPShow:@"下麦失败"];
  484. weakSelf.micStatus = MICSTATUS_CONNECTING;
  485. });
  486. } onKicked:^{
  487. weakSelf.isTransferRole = NO;
  488. dispatch_main_async_safe(^{
  489. // 被踢出
  490. [weakSelf kickOutLiveRoom];
  491. });
  492. }];
  493. }
  494. }
  495. - (void)kickOutLiveRoom {
  496. [self MBPShow:@"您已被踢出直播间"];
  497. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  498. [self quitAction];
  499. });
  500. }
  501. - (void)countMemberCount {
  502. NSInteger count = self.totalCount;
  503. NSString *desc = @"";
  504. if (count > 10000) {
  505. count = count / 10000;
  506. desc = [NSString stringWithFormat:@"%zd万+人",count];
  507. }
  508. else {
  509. desc = [NSString stringWithFormat:@"%zd人",count];
  510. }
  511. self.headView.roomMemberCount.text = desc;
  512. }
  513. // 清空视图
  514. - (void)clearRemoteContainer {
  515. [self.streamVideos removeAllObjects];
  516. for (UIView *subview in self.videoView.subviews) {
  517. [subview removeFromSuperview];
  518. }
  519. self.videoView.streamUserId = @"";
  520. [self.seatContainer removeFromSuperview];
  521. }
  522. // 更新视图
  523. - (void)updateVideoViewContainer {
  524. if (self.streamVideos.count > 0) {
  525. KSLiveStreamVideo *firstVideo = [self.streamVideos lastObject];
  526. if ([self.videoView.streamUserId isEqualToString:firstVideo.userId]) {
  527. return;
  528. }
  529. else {
  530. [self removeViewSubViews:self.videoView];
  531. self.videoView.streamUserId = firstVideo.userId;
  532. [self.videoView addSubview:firstVideo.canvesView];
  533. [firstVideo.canvesView mas_makeConstraints:^(MASConstraintMaker *make) {
  534. make.left.right.bottom.top.mas_equalTo(self.videoView);
  535. }];
  536. }
  537. }
  538. [self changeEmptyStatusView];
  539. [self renderSeatView];
  540. }
  541. - (void)removeViewSubViews:(UIView *)container {
  542. while (container.subviews.count) {
  543. [container.subviews.lastObject removeFromSuperview];
  544. }
  545. }
  546. - (void)changeEmptyStatusView {
  547. if (self.isCreaterInRoom) {
  548. if (self.isCreaterVideoEnable) {
  549. self.emptyView.hidden = YES;
  550. }
  551. else if (self.isCreaterVideoEnable == NO && self.isCreaterAudioEnable == YES) {
  552. // 主播关闭了摄像头
  553. [self showEmptyViewDisplay:YES];
  554. }
  555. else { // 休息一下
  556. [self showEmptyViewDisplay:NO];
  557. }
  558. }
  559. else {
  560. // 无主播
  561. [self showEmptyViewDisplay:NO];
  562. }
  563. }
  564. #pragma mark ---- RCRTCRoomEventDelegate
  565. // 远端发布流
  566. - (void)didPublishStreams:(NSArray<RCRTCInputStream *> *)streams {
  567. [self subscribeRemoteResource:streams];
  568. NSLog(@"didPublishStreams --------");
  569. }
  570. // 订阅远端流
  571. - (void)subscribeRemoteResource:(NSArray<RCRTCInputStream *> *)streams {
  572. [self subscribeRemoteResource:streams isTiny:NO];
  573. }
  574. - (void)subscribeRemoteResource:(NSArray<RCRTCInputStream *> *)streams isTiny:(BOOL)isTiny {
  575. // 订阅房间中远端用户音视频流资源
  576. NSArray *tinyStream = isTiny ? streams : @[];
  577. NSArray *ordinaryStream = isTiny ? @[] : streams;
  578. [self.room.localUser subscribeStream:ordinaryStream
  579. tinyStreams:tinyStream
  580. completion:^(BOOL isSuccess, RCRTCCode desc) {
  581. if (desc != RCRTCCodeSuccess) {
  582. NSString *errorStr = [NSString stringWithFormat:@"订阅远端流失败:%ld", (long) desc];
  583. [self MBPShow:errorStr];
  584. return;
  585. }
  586. // 创建并设置远端视频预览视图
  587. for (RCRTCInputStream *stream in streams) {
  588. if (stream.mediaType == RTCMediaTypeVideo) {
  589. if ([stream.userId isEqualToString:self.createrId]) { // 只渲染主讲人的流
  590. self.isCreaterVideoEnable = YES;
  591. [self setupRemoteViewWithStream:stream];
  592. }
  593. }
  594. if (stream.mediaType == RTCMediaTypeAudio && [stream.userId isEqualToString:self.createrId]) {
  595. self.isCreaterAudioEnable = YES;
  596. }
  597. }
  598. [self updateVideoViewContainer];
  599. }];
  600. }
  601. // 创建并设置远端视频预览视图
  602. - (KSLiveStreamVideo *)setupRemoteViewWithStream:(RCRTCInputStream *)stream {
  603. KSLiveStreamVideo *sVideo = [self creatStreamVideoWithStreamId:stream.streamId];
  604. sVideo.userId = stream.userId;
  605. RCRTCVideoView *remoteView = (RCRTCVideoView *) sVideo.canvesView;
  606. //设置视频流的渲染视图
  607. [(RCRTCVideoInputStream *) stream setVideoView:remoteView];
  608. return sVideo;
  609. }
  610. // 远端用户取消发布资源
  611. - (void)didUnpublishStreams:(NSArray<RCRTCInputStream *> *)streams {
  612. [self unsubscribeRemoteResource:streams orStreamId:nil];
  613. }
  614. - (void)stream:(RCRTCInputStream *)stream didVideoEnable:(BOOL)enable {
  615. NSLog(@"----- video --- staus %d", enable);
  616. }
  617. - (void)stream:(RCRTCInputStream *)stream didAudioMute:(BOOL)mute {
  618. NSLog(@"----- audio --- staus %d", mute);
  619. if ([stream.userId isEqualToString:self.createrId]) {
  620. if (mute) {
  621. self.isCreaterAudioEnable = NO;
  622. }
  623. else {
  624. self.isCreaterAudioEnable = YES;
  625. }
  626. [self changeEmptyStatusView];
  627. }
  628. }
  629. - (void)didOfflineUser:(RCRTCRemoteUser *)user {
  630. [self unsubscribeRemoteResource:user.remoteStreams orStreamId:nil];
  631. }
  632. // 判断是否已有预览视图
  633. - (KSLiveStreamVideo *)creatStreamVideoWithStreamId:(NSString *)streamId {
  634. KSLiveStreamVideo *sVideo = [self fetchStreamVideoWithStreamId:streamId];
  635. if (!sVideo) {
  636. sVideo = [[KSLiveStreamVideo alloc] initWithStreamId:streamId];
  637. [self.streamVideos insertObject:sVideo atIndex:0];
  638. }
  639. return sVideo;
  640. }
  641. // 根据 streamId 确认唯一的音视频流
  642. - (KSLiveStreamVideo *)fetchStreamVideoWithStreamId:(NSString *)streamId {
  643. for (KSLiveStreamVideo *sVideo in self.streamVideos) {
  644. if ([streamId isEqualToString:sVideo.streamId]) {
  645. return sVideo;
  646. }
  647. }
  648. return nil;
  649. }
  650. // 远端掉线/离开回掉调用,删除远端用户的所有音视频流
  651. - (void)fetchStreamVideoOffLineWithStreamId:(NSString *)streamId {
  652. NSArray *arr = [NSArray arrayWithArray:self.streamVideos];
  653. for (KSLiveStreamVideo *sVideo in arr) {
  654. if ([streamId isEqualToString:sVideo.streamId]) {
  655. if (sVideo) {
  656. [sVideo.canvesView removeFromSuperview];
  657. [self.streamVideos removeObject:sVideo];
  658. }
  659. }
  660. }
  661. [self updateVideoViewContainer];
  662. }
  663. // 远端用户切换身份(主播接收)
  664. - (void)didSwitchRoleWithUser:(RCRTCRemoteUser *)user roleType:(RCRTCLiveRoleType)roleType {
  665. if (roleType == RCRTCLiveRoleTypeBroadcaster) {
  666. if ([self judgeContainUser:user.userId] == NO) {
  667. [self.remoteMemberArray addObject:user];
  668. }
  669. }
  670. else {
  671. [self removeMemberUserId:user.userId];
  672. }
  673. [self renderSeatView];
  674. NSLog(@"didSwitchRoleWithUser --------");
  675. }
  676. - (BOOL)judgeContainUser:(NSString *)userId {
  677. BOOL containUser = NO;
  678. for (RCRTCRemoteUser *remoteUser in self.remoteMemberArray) {
  679. if ([remoteUser.userId isEqualToString:userId]) {
  680. containUser = YES;
  681. }
  682. }
  683. return containUser;
  684. }
  685. - (void)removeMemberUserId:(NSString *)userId {
  686. for (RCRTCRemoteUser *remoteUser in self.remoteMemberArray) {
  687. if ([remoteUser.userId isEqualToString:userId]) {
  688. [self.remoteMemberArray removeObject:remoteUser];
  689. return;
  690. }
  691. }
  692. }
  693. // 主播加入房间 (观众接收)
  694. - (void)didJoinUser:(RCRTCRemoteUser *)user {
  695. if ([user.userId isEqualToString:self.createrId]) { // 当主播重新进入房间需要重置连麦状态
  696. self.isCreaterInRoom = YES;
  697. // 重置连麦状态
  698. [self resetConnectionStatus];
  699. }
  700. if ([self judgeContainUser:user.userId] == NO) {
  701. [self.remoteMemberArray addObject:user];
  702. [self renderSeatView];
  703. }
  704. NSLog(@"didJoinUser --------");
  705. }
  706. // 主播离开房间 (观众接收)
  707. - (void)didLeaveUser:(RCRTCRemoteUser *)user {
  708. [self removeMemberUserId:user.userId];
  709. if (self.remoteMemberArray.count <= 0 && self.liveRoleType != RCRTCLiveRoleTypeBroadcaster) {
  710. [self remoteUserIsNull];
  711. }
  712. [self unsubscribeRemoteResource:user.remoteStreams orStreamId:nil];
  713. if ([user.userId isEqualToString:self.createrId]) { //
  714. self.isCreaterInRoom = NO;
  715. [self resetConnectionStatus];
  716. }
  717. else {
  718. [self renderSeatView];
  719. }
  720. }
  721. - (void)resetConnectionStatus {
  722. if (self.micStatus == MICSTATUS_CONNECTING) {
  723. [self connectHostWithStatus:NO];
  724. }
  725. self.micStatus = MICSTATUS_NOMAL;
  726. [self.seatContainer removeFromSuperview];
  727. }
  728. - (void)remoteUserIsNull {
  729. [self MBPShow:@"主讲人已离开房间"];
  730. }
  731. #pragma mark - KSChatInputBarControlDelegate
  732. // 根据inputBar 回调来修改页面布局
  733. - (void)onInputBarControlContentSizeChanged:(CGRect)frame withAnimationDuration:(CGFloat)duration andAnimationCurve:(UIViewAnimationCurve)curve {
  734. CGRect originFrame = _inputBar.frame;
  735. __weak __typeof(&*self)weakSelf = self;
  736. // 目前只使用y值即可 -- 只修改messageContentView高度,让展示消息view和输入框随之移动
  737. [UIView animateWithDuration:duration animations:^{
  738. [UIView setAnimationCurve:curve];
  739. [weakSelf.inputBar setFrame:CGRectMake(0, frame.origin.y - originFrame.size.height, originFrame.size.width, originFrame.size.height)];
  740. [UIView commitAnimations];
  741. }];
  742. }
  743. // 发送消息
  744. - (void)onTouchSendButton:(NSString *)text {
  745. [self touristSendMessage:text];
  746. }
  747. - (void)touristSendMessage:(NSString *)text {
  748. RCTextMessage *rcTextMessage = [RCTextMessage messageWithContent:text];
  749. MJWeakSelf;
  750. [self sendMessage:rcTextMessage displayMessage:YES callback:^(BOOL success) {
  751. [weakSelf setDefaultBottomViewStatus];
  752. }];
  753. }
  754. #pragma mark ---- RTC
  755. - (void)joinLiveRoom {
  756. // 1.配置房间
  757. RCRTCRoomConfig *config = [[RCRTCRoomConfig alloc] init];
  758. config.roomType = RCRTCRoomTypeLive;
  759. config.liveType = RCRTCLiveTypeAudioVideo;
  760. config.roleType = RCRTCLiveRoleTypeAudience;
  761. __weak typeof(self) weakSelf = self;
  762. [self.engine joinRoom:self.roomId config:config completion:^(RCRTCRoom * _Nullable room, RCRTCCode code) {
  763. dispatch_main_async_safe(^{
  764. __strong typeof(weakSelf) strongSelf = weakSelf;
  765. if (code == RCRTCCodeSignalServerNotConnect || code == RCRTCCodeParameterError || code == RCRTCCodeNotInRTCRoom || code == RCRTCCodeRTCTokenIsNull || code == RCRTCCodeHttpTimeoutError || code == RCRTCCodeHttpError || code == RCRTCCodeVoIPNotAvailable) {
  766. NSLog(@"%@",[NSString stringWithFormat:@"加入失败 code %ld",code]);
  767. [strongSelf MBPShow:@"加入直播间失败"];
  768. [strongSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
  769. }
  770. else { // 加入直播间成功
  771. strongSelf.room = room;
  772. strongSelf.room.delegate = self;
  773. [strongSelf countMemberCount];
  774. // 刷新视图
  775. strongSelf.remoteMemberArray = [NSMutableArray arrayWithArray:room.remoteUsers];
  776. if (room.remoteUsers.count) {
  777. NSMutableArray *streamArray = [NSMutableArray array];
  778. for (RCRTCRemoteUser *user in room.remoteUsers) {
  779. if ([user.userId isEqualToString:strongSelf.createrId]) {
  780. strongSelf.isCreaterInRoom = YES;
  781. }
  782. if (user.remoteStreams.count) {
  783. [streamArray addObjectsFromArray:user.remoteStreams];
  784. [strongSelf subscribeRemoteResource:streamArray];
  785. }
  786. }
  787. }
  788. [self changeEmptyStatusView];
  789. }
  790. });
  791. }];
  792. }
  793. - (void)showEmptyViewDisplay:(BOOL)isPublish {
  794. NSString *imageName = @"";
  795. NSString *descMessage = @"";
  796. if (isPublish) {
  797. imageName = @"liveVideo_close";
  798. descMessage = @"主讲人已关闭画面!";
  799. }
  800. else {
  801. imageName = @"liveroom_empty";
  802. descMessage = @"休息一下,马上回来!";
  803. }
  804. [self.emptyView configViewWithImageName:imageName desc:descMessage];
  805. self.emptyView.hidden = NO;
  806. }
  807. - (void)exitRoom {
  808. __weak typeof(self) weakSelf = self;
  809. [self.engine leaveRoom:^(BOOL isSuccess, RCRTCCode code) {
  810. dispatch_main_async_safe(^{
  811. __strong typeof(weakSelf) strongSelf = weakSelf;
  812. [strongSelf sendLeaveMessageCallback:^(BOOL success) {
  813. // quit 接口
  814. [weakSelf quitNotiferService];
  815. [weakSelf quitChatRoom];
  816. }];
  817. });
  818. }];
  819. }
  820. // 取消订阅流 Id 所对应的 View
  821. - (void)unsubscribeRemoteResource:(NSArray<RCRTCInputStream *> *)streams orStreamId:(NSString *)streamId {
  822. for (RCRTCInputStream *stream in streams) {
  823. if (stream.mediaType == RTCMediaTypeVideo) {
  824. streamId = stream.streamId;
  825. if ([stream.userId isEqualToString:self.createrId]) {
  826. self.videoView.streamUserId = @"";
  827. self.isCreaterVideoEnable = NO;
  828. // 主讲老师停止发布视频流
  829. NSLog(@"----- 停止发布视频流");
  830. }
  831. [self fetchStreamVideoOffLineWithStreamId:streamId];
  832. }
  833. else if (stream.mediaType == RTCMediaTypeAudio && [stream.userId isEqualToString:self.createrId]) {
  834. // 主讲老师停止发布音频流
  835. NSLog(@"----- 停止发布音频流");
  836. self.isCreaterAudioEnable = NO;
  837. }
  838. }
  839. }
  840. #pragma mark -- 加入直播间和退出直播间回到服务
  841. - (void)notiferJoinSuccessToServiceCallback:(void(^)(void))callback {
  842. callback();
  843. // [KSNetworkingManager LiveroomJoinRequest:KS_GET roomId:self.roomId userId:UserDefault(UIDKey) success:^(NSDictionary * _Nonnull dic) {
  844. // callback();
  845. // } faliure:^(NSError * _Nonnull error) {
  846. // callback();
  847. // }];
  848. }
  849. #pragma mark -- 退出直播间回掉服务
  850. - (void)quitNotiferService {
  851. [KSNetworkingManager LiveroomQuit:KS_POST success:^(NSDictionary * _Nonnull dic) {
  852. if ([dic integerValueForKey:@"errCode"] == 0) {
  853. NSLog(@"success");
  854. }
  855. else {
  856. NSLog(@"----- error %@", [dic stringValueForKey:@"errMsg"]);
  857. }
  858. } faliure:^(NSError * _Nonnull error) {
  859. }];
  860. }
  861. - (void)sendLeaveMessageCallback:(void(^)(BOOL success))callback {
  862. RCUserInfo *currentUserInfo = [RCIM sharedRCIM].currentUserInfo;
  863. KSLiveChatroomLeave *leaveMessage = [[KSLiveChatroomLeave alloc] init];
  864. leaveMessage.userId = currentUserInfo.userId;
  865. leaveMessage.userName = currentUserInfo.name;
  866. [self sendMessage:leaveMessage displayMessage:NO callback:^(BOOL success) {
  867. callback(success);
  868. }];
  869. }
  870. - (void)quitChatRoom {
  871. [[RCIMClient sharedRCIMClient] quitChatRoom:self.roomId success:^{
  872. NSLog(@"sucess log out--");
  873. } error:^(RCErrorCode status) {
  874. NSLog(@"RCErrorCode------%ld ", (long)status);
  875. }];
  876. }
  877. - (void)addConstMessage { // 固定欢迎词语
  878. RCTextMessage *statement = [RCTextMessage messageWithContent:@"欢迎进入直播课堂,请遵守相关法规,禁止传播低俗、暴力等不良信息。为孩子创造健康绿色的学习环境。"];
  879. statement.extra = @"statement";
  880. RCMessage *constMessage = [[RCMessage alloc] initWithType:ConversationType_CHATROOM targetId:self.roomId direction:MessageDirection_SEND content:statement];
  881. constMessage.content.senderUserInfo = [RCIM sharedRCIM].currentUserInfo;
  882. [self appendAndDisplayMessage:constMessage];
  883. }
  884. #pragma mark - views init
  885. /**
  886. * 接收到消息的回调
  887. */
  888. - (void)didReceiveMessageNotification:(NSNotification *)notification {
  889. NSDictionary *dic = notification.object;
  890. __block RCMessage *rcMessage = dic[@"message"];
  891. KSRCMessageModel *model = [[KSRCMessageModel alloc] initWithMessage:rcMessage];
  892. model.userInfo = rcMessage.content.senderUserInfo;
  893. if (model.conversationType == ConversationType_CHATROOM && [model.targetId isEqualToString:self.roomId]) {
  894. __weak typeof(&*self) __blockSelf = self;
  895. dispatch_main_async_safe(^{
  896. // 对赞消息拦截 ,展示动画,不插入数据源 ,对于seat消息 和踢出消息,弹出alert
  897. if (rcMessage) {
  898. // 👍🏻消息
  899. if ([rcMessage.content isMemberOfClass:[KSLiveChatroomLike class]]) {
  900. KSLiveChatroomLike *likeMessage = (KSLiveChatroomLike *)rcMessage.content;
  901. // 统计点赞数量
  902. __blockSelf.likeCount += likeMessage.counts;
  903. [__blockSelf countLikeMessageCount];
  904. }
  905. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomWelcome class]]) {
  906. // 过滤自己发送的欢迎消息
  907. if ([rcMessage.senderUserId isEqualToString:UserDefault(UIDKey)]) {
  908. return;
  909. }
  910. if ([rcMessage.senderUserId isEqual:self.createrId]) { // 如果是老师 重置连麦状态
  911. [__blockSelf resetConnectionStatus];
  912. }
  913. }
  914. // 用户退出消息
  915. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomLeave class]]) {
  916. return;
  917. }
  918. // 用户退出消息
  919. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomUserQuit class]]) {
  920. return;
  921. }
  922. // 人数同步
  923. // else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomMemberCount class]]) {
  924. // KSLiveChatroomMemberCount *member = (KSLiveChatroomMemberCount *)rcMessage.content;
  925. // __blockSelf.totalCount = member.count;
  926. // [__blockSelf countMemberCount];
  927. // return;
  928. // }
  929. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomMemberUp class]]) {
  930. KSLiveChatroomMemberUp *member = (KSLiveChatroomMemberUp *)rcMessage.content;
  931. __blockSelf.totalCount = member.count;
  932. [__blockSelf countMemberCount];
  933. return;
  934. }
  935. // 连麦申请消息
  936. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomSeatApply class]]) {
  937. KSLiveChatroomSeatApply *seatApplyMessage = (KSLiveChatroomSeatApply *)rcMessage.content;
  938. // 只处理和自己相关的消息
  939. if (![seatApplyMessage.audienceId isEqualToString:UserDefault(UIDKey)]) {
  940. // 刷新麦序
  941. return;
  942. }
  943. if (seatApplyMessage.type == SEATHANDLE_KICKSEAT) { // 主讲人将麦上观众报下麦
  944. [__blockSelf kickSeatWithMessage:@"您已被报下麦"];
  945. }
  946. return;
  947. }
  948. // 连麦回复消息(暂不处理)
  949. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomSeatResponse class]]) {
  950. KSLiveChatroomSeatResponse *seatResponseMessage = (KSLiveChatroomSeatResponse *)rcMessage.content;
  951. if (![seatResponseMessage.audienceId isEqualToString:UserDefault(UIDKey)]) { // 只处理和自己相关的消息
  952. return;
  953. }
  954. if (seatResponseMessage.type == SEATRESPONSE_TEACHERAPPROVE) { //
  955. [__blockSelf connectHostWithStatus:YES];
  956. }
  957. else if (seatResponseMessage.type == SEATRESPONSE_TEACHERREJECT) {
  958. [__blockSelf MBPShow:@"主讲人拒绝了您的连麦申请"];
  959. __blockSelf.micStatus = MICSTATUS_NOMAL;
  960. }
  961. }
  962. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomDownSeat class]]) { // 成员下麦消息
  963. return;
  964. }
  965. // left
  966. NSDictionary *leftDic = notification.userInfo;
  967. if (leftDic && [leftDic[@"left"] isEqual:@(0)]) {
  968. __blockSelf.isNeedScrollToButtom = YES;
  969. }
  970. [__blockSelf appendAndDisplayMessage:rcMessage];
  971. }
  972. });
  973. }
  974. }
  975. - (void)kickSeatWithMessage:(NSString *)message {
  976. [self MBPShow:message];
  977. [self connectHostWithStatus:NO];
  978. }
  979. // 发送人数同步消息
  980. - (void)sendMemberCountMessage {
  981. KSLiveChatroomMemberCount *syncMessage = [[KSLiveChatroomMemberCount alloc] init];
  982. syncMessage.count = self.totalCount;
  983. [self sendMessage:syncMessage displayMessage:NO callback:^(BOOL success) {
  984. }];
  985. }
  986. #pragma mark ----- time manager delegate
  987. - (void)quitClassroomNotifer {
  988. [self MBPShow:@"直播课已结束!"];
  989. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  990. [self quitAction];
  991. });
  992. }
  993. #pragma mark - gesture and button action
  994. - (void)resetBottomGesture:(UIGestureRecognizer *)gestureRecognizer {
  995. if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
  996. [self setDefaultBottomViewStatus];
  997. }
  998. }
  999. - (void)setDefaultBottomViewStatus {
  1000. [self.inputBar setInputBarStatus:KSBottomBarStatusDefault];
  1001. [self.inputBar setHidden:YES];
  1002. }
  1003. #pragma mark ---- sendMessage/showMessage
  1004. - (void)sendMessage:(RCMessageContent *)messageContent displayMessage:(BOOL)displayMessage callback:(void(^)(BOOL success))callback {
  1005. if (_roomId == nil) {
  1006. return;
  1007. }
  1008. messageContent.senderUserInfo = [RCIM sharedRCIM].currentUserInfo;
  1009. if (messageContent == nil) {
  1010. return;
  1011. }
  1012. __weak typeof(&*self) __weakself = self;
  1013. [KSChatroomMessageCenter sendChatMessage:self.roomId content:messageContent success:^(long messageId) {
  1014. dispatch_async(dispatch_get_main_queue(), ^{
  1015. RCMessage *message = [[RCMessage alloc] initWithType:ConversationType_CHATROOM targetId:__weakself.roomId direction:MessageDirection_SEND content:messageContent];
  1016. message.content.senderUserInfo = [RCIM sharedRCIM].currentUserInfo;
  1017. message.senderUserId = UserDefault(UIDKey);
  1018. if (displayMessage) {
  1019. [__weakself appendAndDisplayMessage:message];
  1020. }
  1021. [__weakself.inputBar clearInputView];
  1022. callback(YES);
  1023. });
  1024. } error:^(RCErrorCode errorCode, long messageId) {
  1025. if (errorCode == RC_CHATROOM_NOT_EXIST) {
  1026. dispatch_async(dispatch_get_main_queue(), ^{
  1027. [self MBPShow:@"聊天已被解散,请退出后重进。"];
  1028. });
  1029. }
  1030. dispatch_async(dispatch_get_main_queue(), ^{
  1031. [__weakself.inputBar clearInputView];
  1032. NSLog(@"发送失败,errorcode is: %ld",(long)errorCode);
  1033. callback(NO);
  1034. });
  1035. }];
  1036. }
  1037. /**
  1038. * 将消息加入本地数组
  1039. */
  1040. - (void)appendAndDisplayMessage:(RCMessage *)rcMessage {
  1041. if (!rcMessage) {
  1042. return;
  1043. }
  1044. KSRCMessageModel *model = [[KSRCMessageModel alloc] initWithMessage:rcMessage];
  1045. model.userInfo = rcMessage.content.senderUserInfo;
  1046. if (!model.userInfo) {
  1047. model.userInfo = [RCIMClient sharedRCIMClient].currentUserInfo;
  1048. }
  1049. if ([self appendMessageModel:model]) {
  1050. NSIndexPath *indexPath =
  1051. [NSIndexPath indexPathForItem:self.conversationDataRepository.count - 1
  1052. inSection:0];
  1053. if ([self.conversationMessageTableView numberOfRowsInSection:0] !=
  1054. self.conversationDataRepository.count - 1) {
  1055. return;
  1056. }
  1057. // view刷新
  1058. [self.conversationMessageTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
  1059. if ([self isAtTheBottomOfTableView] || self.isNeedScrollToButtom) {
  1060. [self scrollToBottomAnimated:YES];
  1061. self.isNeedScrollToButtom=NO;
  1062. }
  1063. }
  1064. return;
  1065. }
  1066. /**
  1067. * 如果当前会话没有这个消息id,把消息加入本地数组
  1068. */
  1069. - (BOOL)appendMessageModel:(KSRCMessageModel *)model {
  1070. if (!model.content) {
  1071. return NO;
  1072. }
  1073. //这里可以根据消息类型来决定是否显示,如果不希望显示直接return NO
  1074. //数量不可能无限制的大,这里限制收到消息过多时,就对显示消息数量进行限制。
  1075. //用户可以手动下拉更多消息,查看更多历史消息。
  1076. if (self.conversationDataRepository.count>100) {
  1077. // NSRange range = NSMakeRange(0, 1);
  1078. KSRCMessageModel *message = self.conversationDataRepository[0];
  1079. [[RCIMClient sharedRCIMClient]deleteMessages:@[@(message.messageId)]];
  1080. [self.conversationDataRepository removeObjectAtIndex:0];
  1081. [self.conversationMessageTableView reloadData];
  1082. }
  1083. [self.conversationDataRepository addObject:model];
  1084. return YES;
  1085. }
  1086. /**
  1087. * 判断消息是否在collectionView的底部
  1088. *
  1089. * @return 是否在底部
  1090. */
  1091. - (BOOL)isAtTheBottomOfTableView {
  1092. if (self.conversationMessageTableView.contentSize.height <= self.conversationMessageTableView.frame.size.height) {
  1093. return YES;
  1094. }
  1095. if(self.conversationMessageTableView.contentOffset.y +200 >= (self.conversationMessageTableView.contentSize.height - self.conversationMessageTableView.frame.size.height)) {
  1096. return YES;
  1097. }else{
  1098. return NO;
  1099. }
  1100. }
  1101. /**
  1102. * 消息滚动到底部
  1103. *
  1104. * @param animated 是否开启动画效果
  1105. */
  1106. - (void)scrollToBottomAnimated:(BOOL)animated {
  1107. if ([self.conversationMessageTableView numberOfSections] == 0) {
  1108. return;
  1109. }
  1110. NSUInteger finalRow = MAX(0, [self.conversationMessageTableView numberOfRowsInSection:0] - 1);
  1111. if (0 == finalRow) {
  1112. return;
  1113. }
  1114. NSIndexPath *finalIndexPath =
  1115. [NSIndexPath indexPathForItem:finalRow inSection:0];
  1116. [self.conversationMessageTableView scrollToRowAtIndexPath:finalIndexPath atScrollPosition:UITableViewScrollPositionTop animated:animated];
  1117. }
  1118. /*
  1119. #pragma mark - Navigation
  1120. // In a storyboard-based application, you will often want to do a little preparation before navigation
  1121. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  1122. // Get the new view controller using [segue destinationViewController].
  1123. // Pass the selected object to the new view controller.
  1124. }
  1125. */
  1126. #pragma mark ----- table data source
  1127. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  1128. return self.conversationDataRepository.count;
  1129. }
  1130. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  1131. KSRCMessageModel *model = [self.conversationDataRepository objectAtIndex:indexPath.row];
  1132. RCMessageContent *messageContent = model.content;
  1133. KSChatroomTextCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KSChatroomTextCell"];
  1134. if(!cell){
  1135. cell = [[KSChatroomTextCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"KSChatroomTextCell"];
  1136. }
  1137. if ([messageContent isMemberOfClass:[RCTextMessage class]] || [messageContent isMemberOfClass:[KSLiveChatroomWelcome class]] || [messageContent isMemberOfClass:[KSLiveChatroomLike class]] || [messageContent isMemberOfClass:[KSLiveChatroomSeatApply class]] || [messageContent isMemberOfClass:[KSLiveChatroomSeatResponse class]]){
  1138. [cell setDataModel:model createrId:self.createrId];
  1139. }
  1140. return cell;
  1141. }
  1142. - (void)sendSeatMessageApply:(BOOL)isApply {
  1143. SEATHANDLE type = isApply ? SEATHANDLE_APPLY : SEATHANDLE_CANCELAPPLY;
  1144. KSLiveChatroomSeatApply *applyMessage = [[KSLiveChatroomSeatApply alloc] init];
  1145. applyMessage.type = type;
  1146. applyMessage.audienceId = UserDefault(UIDKey);
  1147. applyMessage.audienceName = UserDefault(NicknameKey);
  1148. applyMessage.audienceAvatar = UserDefault(AvatarUrlKey);
  1149. MJWeakSelf;
  1150. [self sendMessage:applyMessage displayMessage:YES callback:^(BOOL success) {
  1151. if (success) {
  1152. weakSelf.micStatus = isApply ? MICSTATUS_WAITING : MICSTATUS_NOMAL;
  1153. }
  1154. }];
  1155. }
  1156. /**
  1157. 点赞
  1158. */
  1159. - (void)praiseBtnPressed {
  1160. NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
  1161. __weak __typeof(&*self)weakSelf = self;
  1162. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.21 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1163. if ([[NSDate date] timeIntervalSince1970] - self.lastClickPraiseTime >= 0.2) {
  1164. KSLiveChatroomLike *praiseSendMessage = [[KSLiveChatroomLike alloc] init];
  1165. praiseSendMessage.counts = clickPraiseBtnTimes;
  1166. [weakSelf sendMessage:praiseSendMessage displayMessage:YES callback:^(BOOL success) {
  1167. }];
  1168. clickPraiseBtnTimes = 0;
  1169. self.likeCount += praiseSendMessage.counts;
  1170. [self countLikeMessageCount];
  1171. }
  1172. });
  1173. KSLiveChatroomLike *praiseMessage = [[KSLiveChatroomLike alloc] init];
  1174. clickPraiseBtnTimes++;
  1175. self.lastClickPraiseTime = currentTime;
  1176. [self presentLikeMessageAnimation:praiseMessage];
  1177. }
  1178. /**
  1179. 赞动画
  1180. @param likeMessage 赞消息
  1181. */
  1182. - (void)presentLikeMessageAnimation:(KSLiveChatroomLike *)likeMessage {
  1183. LiveRoomLikeLayer *layer = [[LiveRoomLikeLayer alloc] initWithName:@"Like"];
  1184. [self.view.layer addSublayer:layer];
  1185. CGRect tempFrame = [self.view convertRect:self.bottomView.likeButton.frame fromView:self.bottomView];
  1186. CGFloat width = tempFrame.size.width;
  1187. CGFloat height = tempFrame.size.height;
  1188. CGFloat dx = CGRectGetMidX(tempFrame) - width *0.5;
  1189. CGFloat dy = CGRectGetMidY(tempFrame) - 8 - height;
  1190. layer.frame = CGRectMake(dx, dy, width, height);
  1191. [layer startAnimation];
  1192. }
  1193. #pragma mark - setter & getter
  1194. - (void)setMicStatus:(MICSTATUS)micStatus {
  1195. _micStatus = micStatus;
  1196. if (self.bottomView) {
  1197. switch (micStatus) {
  1198. case MICSTATUS_NOMAL:
  1199. {
  1200. [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_nomal"] forState:UIControlStateNormal];
  1201. }
  1202. break;
  1203. case MICSTATUS_WAITING:
  1204. case MICSTATUS_CONNECTING:
  1205. {
  1206. [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_image"] forState:UIControlStateNormal];
  1207. }
  1208. break;
  1209. default:
  1210. break;
  1211. }
  1212. }
  1213. }
  1214. #pragma mark ----- RCRTCEngineEventDelegate
  1215. - (void)didRTCConnectionStateChanged:(RCRTCConnectionState)state {
  1216. if (state == RCRTCConnectionStateConnecting) {
  1217. NSLog(@"--------RCRTCConnectionStateConnecting");
  1218. }
  1219. else if (state == RCRTCConnectionStateConnected) {
  1220. NSLog(@"--------RCRTCConnectionStateConnected");
  1221. }
  1222. }
  1223. #pragma mark ----- RCRTCStatusReportDelegate
  1224. - (void)didReportStatusForm:(RCRTCStatusForm *)form {
  1225. NSLog(@"%@",form.description);
  1226. }
  1227. #pragma mark --- lazying
  1228. - (KSChatVideoView *)videoView {
  1229. if (!_videoView) {
  1230. _videoView = [[KSChatVideoView alloc] initWithFrame:self.view.bounds];
  1231. _videoView.backgroundColor = [UIColor clearColor];
  1232. }
  1233. return _videoView;
  1234. }
  1235. - (UIView *)videoContainerView {
  1236. if (!_videoContainerView) {
  1237. _videoContainerView = [[UIView alloc] initWithFrame:CGRectZero];
  1238. _videoContainerView.backgroundColor = [UIColor clearColor];
  1239. }
  1240. return _videoContainerView;
  1241. }
  1242. - (LiveRoomHeadView *)headView {
  1243. if (!_headView) {
  1244. _headView = [LiveRoomHeadView shareInstance];
  1245. MJWeakSelf;
  1246. [_headView quitAction:^{
  1247. [weakSelf quitAction];
  1248. }];
  1249. }
  1250. return _headView;
  1251. }
  1252. - (LiveRoomBottomView *)bottomView {
  1253. if (!_bottomView) {
  1254. _bottomView = [LiveRoomBottomView shareInstance];
  1255. MJWeakSelf;
  1256. [_bottomView bottomClickAction:^(LIVEROOMACTION action) {
  1257. [weakSelf bottomViewAction:action];
  1258. }];
  1259. }
  1260. return _bottomView;
  1261. }
  1262. - (void)bottomViewAction:(LIVEROOMACTION)action {
  1263. switch (action) {
  1264. case LIVEROOMACTION_CHAT: // 聊天
  1265. {
  1266. [_inputBar setHidden:NO];
  1267. [_inputBar setInputBarStatus:KSBottomBarStatusKeyboard];
  1268. }
  1269. break;
  1270. case LIVEROOMACTION_SEAT: // // 连麦按钮
  1271. { // 连麦按钮
  1272. // 判断是否是连麦状态
  1273. if (self.micStatus == MICSTATUS_CONNECTING) { // 连麦中
  1274. // 是否取消连麦
  1275. MJWeakSelf;
  1276. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"连麦中,确认取消连麦吗?" leftTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{
  1277. } confirm:^{
  1278. [weakSelf connectHostWithStatus:NO];
  1279. }];
  1280. }
  1281. else if (self.micStatus == MICSTATUS_WAITING) { // 连麦申请中
  1282. // 弹窗取消申请
  1283. MJWeakSelf;
  1284. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"连麦申请中,确认取消连麦申请吗?" leftTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{
  1285. } confirm:^{
  1286. [weakSelf sendSeatMessageApply:NO];
  1287. }];
  1288. }
  1289. else {
  1290. if (self.isCreaterInRoom == NO) {
  1291. [self MBPShow:@"主讲人不在直播间"];
  1292. return;
  1293. }
  1294. else if (self.isCreaterVideoEnable == NO && self.isCreaterAudioEnable == NO) {
  1295. [self MBPShow:@"主讲人未开始直播"];
  1296. return;
  1297. }
  1298. [self MBPShow:@"已发起连麦申请"];
  1299. [self sendSeatMessageApply:YES];
  1300. }
  1301. }
  1302. break;
  1303. case LIVEROOMACTION_SHOP:
  1304. {
  1305. [self showCartAlert];
  1306. }
  1307. break;
  1308. case LIVEROOMACTION_LIKE:
  1309. {
  1310. if (self.isCreaterInRoom) {
  1311. [self praiseBtnPressed];
  1312. }
  1313. else {
  1314. [self MBPShow:@"主讲人不在直播间"];
  1315. }
  1316. }
  1317. break;
  1318. default:
  1319. break;
  1320. }
  1321. }
  1322. - (void)showCartAlert {
  1323. [self.cardView showViewInView:self.view];
  1324. }
  1325. - (void)quitAction {
  1326. if (self.micStatus == MICSTATUS_CONNECTING) {
  1327. MJWeakSelf;
  1328. self.alertView = [KSNormalAlertView ks_showAlertWithTitle:@"连麦中,是否退出房间?" leftTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{
  1329. } confirm:^{
  1330. [weakSelf quitRoom];
  1331. }];
  1332. }
  1333. else {
  1334. [self quitRoom];
  1335. }
  1336. }
  1337. - (void)quitRoom {
  1338. // 清理视图
  1339. [self clearRemoteContainer];
  1340. // 退出房间
  1341. [self exitRoom];
  1342. [self.navigationController dismissViewControllerAnimated:YES completion:nil];
  1343. }
  1344. - (RCRTCEngine *)engine {
  1345. if (!_engine) {
  1346. _engine = [RCRTCEngine sharedInstance];
  1347. [_engine setStatusReportDelegate:self];
  1348. [_engine setDelegate:self];
  1349. }
  1350. return _engine;
  1351. }
  1352. - (NSMutableArray<KSLiveStreamVideo *> *)streamVideos {
  1353. if (!_streamVideos) {
  1354. _streamVideos = [NSMutableArray array];
  1355. }
  1356. return _streamVideos;
  1357. }
  1358. - (KSLiveStreamVideo *)localVideo {
  1359. if (!_localVideo) {
  1360. _localVideo = [KSLiveStreamVideo LocalStreamVideo];
  1361. }
  1362. return _localVideo;
  1363. }
  1364. - (NSMutableArray *)remoteMemberArray {
  1365. if (!_remoteMemberArray) {
  1366. _remoteMemberArray = [NSMutableArray array];
  1367. }
  1368. return _remoteMemberArray;
  1369. }
  1370. - (UIView *)messageContentView {
  1371. if (!_messageContentView) {
  1372. _messageContentView = [[UIView alloc] init];
  1373. [_messageContentView setBackgroundColor: [UIColor clearColor]];
  1374. }
  1375. return _messageContentView;
  1376. }
  1377. - (NSMutableArray<KSRCMessageModel *> *)conversationDataRepository {
  1378. if (!_conversationDataRepository) {
  1379. _conversationDataRepository = [NSMutableArray array];
  1380. }
  1381. return _conversationDataRepository;
  1382. }
  1383. - (KSChatInputBarControl *)inputBar {
  1384. if (!_inputBar) {
  1385. _inputBar = [[KSChatInputBarControl alloc] initWithStatus:KSBottomBarStatusDefault];
  1386. [_inputBar setDelegate:self];
  1387. }
  1388. return _inputBar;
  1389. }
  1390. - (UITableView *)conversationMessageTableView {
  1391. if (!_conversationMessageTableView) {
  1392. _conversationMessageTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
  1393. _conversationMessageTableView.backgroundColor = [UIColor clearColor];
  1394. _conversationMessageTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  1395. _conversationMessageTableView.dataSource = self;
  1396. _conversationMessageTableView.delegate = self;
  1397. _conversationMessageTableView.rowHeight = UITableViewAutomaticDimension;
  1398. _conversationMessageTableView.estimatedRowHeight = 40.0f;
  1399. _conversationMessageTableView.showsVerticalScrollIndicator = NO;
  1400. [_conversationMessageTableView registerClass:[KSChatroomTextCell class] forCellReuseIdentifier:@"KSChatroomTextCell"];
  1401. }
  1402. return _conversationMessageTableView;
  1403. }
  1404. - (SeatContentView *)seatContainer {
  1405. if (!_seatContainer) {
  1406. _seatContainer = [[SeatContentView alloc] init];
  1407. }
  1408. return _seatContainer;
  1409. }
  1410. - (void)sendDownSeatMessage {
  1411. KSLiveChatroomDownSeat *downSeatMessage = [[KSLiveChatroomDownSeat alloc] init];
  1412. downSeatMessage.audienceId = UserDefault(UIDKey);
  1413. downSeatMessage.audienceName = UserDefault(NicknameKey);
  1414. MJWeakSelf;
  1415. [self sendMessage:downSeatMessage displayMessage:NO callback:^(BOOL success) {
  1416. weakSelf.micStatus = MICSTATUS_NOMAL;
  1417. }];
  1418. }
  1419. - (LiveroomTimeManager *)timeManager {
  1420. if (!_timeManager) {
  1421. _timeManager = [[LiveroomTimeManager alloc] initWithDelegate:self];
  1422. }
  1423. return _timeManager;
  1424. }
  1425. - (NSInteger)getCloseTime {
  1426. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  1427. [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  1428. formatter.timeZone = [NSTimeZone systemTimeZone];
  1429. NSDate *currentDate = [NSDate date];
  1430. NSDate *expireDate = [formatter dateFromString:self.liveEndTime];
  1431. NSTimeInterval timeInterval = [expireDate timeIntervalSinceDate:currentDate];
  1432. return self.expiredMinute * 60 + timeInterval;
  1433. }
  1434. - (KSShopCardView *)cardView {
  1435. if (!_cardView) {
  1436. _cardView = [KSShopCardView shareInstance];
  1437. MJWeakSelf;
  1438. [_cardView clickAction:^(CART_TYPE type) {
  1439. [weakSelf displayTeacherInfo:type];
  1440. }];
  1441. }
  1442. return _cardView;
  1443. }
  1444. // tabs= practice | 陪练课 live | 直播课 video | 视频课 music | 乐谱 跳转到对应tab页
  1445. - (void)displayTeacherInfo:(CART_TYPE)type {
  1446. RCRTCVideoView *videoView = nil;
  1447. for (UIView *view in self.videoView.subviews) {
  1448. if ([view isKindOfClass:[RCRTCVideoView class]]) {
  1449. videoView = (RCRTCVideoView *)view;
  1450. }
  1451. }
  1452. if (videoView) {
  1453. _hasShowSuspendView = YES;
  1454. KSDragWindowManager *manager = [KSDragWindowManager sharedManager];
  1455. [manager initDragWindow];
  1456. manager.rootVC = (CustomNavViewController *)self.navigationController;
  1457. [videoView removeFromSuperview];
  1458. [manager.dragWindow.containerView addSubview:videoView];
  1459. [videoView mas_updateConstraints:^(MASConstraintMaker *make) {
  1460. make.left.right.top.bottom.mas_equalTo(manager.dragWindow.containerView);
  1461. }];
  1462. [manager.dragWindow bringSubviewToFront:manager.dragWindow.cancelButton];
  1463. }
  1464. NSString *tabs = @"";
  1465. switch (type) {
  1466. case CART_TYPE_ACCOMPANY:
  1467. {
  1468. tabs = @"practice";
  1469. }
  1470. break;
  1471. case CART_TYPE_LIVE:
  1472. {
  1473. tabs = @"live";
  1474. }
  1475. break;
  1476. case CART_TYPE_VIDEO:
  1477. {
  1478. tabs = @"video";
  1479. }
  1480. break;
  1481. case CART_TYPE_MUSIC:
  1482. {
  1483. tabs = @"music";
  1484. }
  1485. break;
  1486. default:
  1487. break;
  1488. }
  1489. NSString *url = [NSString stringWithFormat:@"%@%@%@&tabs=%@", WEBHOST,@"/#/teacherHome?teacherId=",self.createrId,tabs];
  1490. KSLiveWebViewController *ctrl = [[KSLiveWebViewController alloc] init];
  1491. ctrl.url = url;
  1492. [self.navigationController pushViewController:ctrl animated:YES];
  1493. }
  1494. - (void)dealloc {
  1495. [[NSNotificationCenter defaultCenter] removeObserver:self];
  1496. }
  1497. @end