LiveVideoRoomViewController.m 88 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491
  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 "KSLiveStreamVideo.h"
  17. #import "SeatContentView.h"
  18. #import "LiveroomTimeManager.h"
  19. #import "KSLiveEmptyView.h"
  20. #import "KSChatVideoView.h"
  21. #import "LiveRoomLikeLayer.h"
  22. #import "KSDragWindowManager.h"
  23. #import "KSLiveBadNetView.h"
  24. #import <Lottie/Lottie.h>
  25. #import "KSShopCardView.h"
  26. #import "KSLiveWebViewController.h"
  27. #import "KSLiveEndView.h"
  28. #import "LiveAnimationView.h"
  29. #import "LiveRoomAlertView.h"
  30. #import "LiveApplyView.h"
  31. #import "LiveApplyingView.h"
  32. #import "ShareLiveDisplayView.h"
  33. #import "UIImage+KSScreenShot.h"
  34. #import "TZImageManager.h"
  35. #import "RecordCheckManager.h"
  36. #import "KSPremissionAlert.h"
  37. #import "KSUMShareManager.h"
  38. #define AUTOHIDE_TIME (10.0f)
  39. #define PACKAGE_LOST (0.30f)
  40. typedef NS_ENUM(NSInteger, MICSTATUS) {
  41. MICSTATUS_NOMAL, // 正常状态
  42. MICSTATUS_WAITING, // 申请中
  43. MICSTATUS_CONNECTING, // 连麦中
  44. };
  45. @interface LiveVideoRoomViewController ()<RCRTCEngineEventDelegate,RCRTCRoomEventDelegate,RCRTCStatusReportDelegate,UIGestureRecognizerDelegate,KSChatInputBarControlDelegate,UITableViewDataSource,UITableViewDelegate,LiveroomTimeManagerDelegate>
  46. @property (nonatomic, strong) LiveroomTimeManager *timeManager;
  47. // 音频配置
  48. @property(strong, nonatomic) RCRTCEngine *engine;
  49. @property (nonatomic, strong) RCRTCRoom *room;
  50. @property (nonatomic, strong) KSLiveStreamVideo *localVideo;
  51. @property (nonatomic) NSMutableArray <KSLiveStreamVideo *>*streamVideos;
  52. // 视频主窗口容器
  53. @property (nonatomic, strong) UIView *videoContainerView;
  54. @property (nonatomic, strong) KSChatVideoView *videoView;
  55. @property (nonatomic, strong) LiveRoomHeadView *headView;
  56. @property (nonatomic, copy) LiveRoomBottomView *bottomView;
  57. /**
  58. 上次点赞按钮点击时间
  59. */
  60. @property(nonatomic, assign) NSTimeInterval lastClickPraiseTime;
  61. // 连麦状态
  62. @property (nonatomic, assign) MICSTATUS micStatus;
  63. /// 提示窗
  64. @property (nonatomic, strong) LiveRoomAlertView *alertView;
  65. /// 连麦的视图
  66. @property (nonatomic, strong) SeatContentView *seatContainer;
  67. // 聊天UI
  68. /*!
  69. 消息列表CollectionView和输入框都在这个view里
  70. */
  71. @property(nonatomic, strong) UIView *messageContentView;
  72. /*!
  73. 会话页面的TableView
  74. */
  75. @property (nonatomic, strong) UITableView *conversationMessageTableView;
  76. /**
  77. 输入工具栏
  78. */
  79. @property(nonatomic,strong) KSChatInputBarControl *inputBar;
  80. /**
  81. * 是否需要滚动到底部
  82. */
  83. @property(nonatomic, assign) BOOL isNeedScrollToButtom;
  84. /*!
  85. 聊天内容的消息Cell数据模型的数据源
  86. @discussion 数据源中存放的元素为消息Cell的数据模型,即RCDLiveMessageModel对象。
  87. */
  88. @property(nonatomic, strong) NSMutableArray<KSRCMessageModel *> *conversationDataRepository;
  89. @property (nonatomic, assign) BOOL isImConnected;
  90. /// 老师是否在房间
  91. @property (nonatomic, assign) BOOL isCreaterInRoom;
  92. @property (nonatomic, assign) BOOL isCreaterVideoEnable;
  93. @property (nonatomic, assign) BOOL isCreaterAudioEnable;
  94. @property (nonatomic, strong) KSLiveEmptyView *emptyView;
  95. /// 是否在切换身份
  96. @property (nonatomic, assign) BOOL isTransferRole;
  97. @property (nonatomic, assign) BOOL hasSendWelcomeMessage;
  98. @property (nonatomic, assign) BOOL hasShowSuspendView;
  99. @property (nonatomic, assign) BOOL isPackageLostTips;
  100. @property (nonatomic, assign) NSTimeInterval lostStartTime; // 开始丢包事件
  101. @property (nonatomic, assign) NSTimeInterval bitErrorTime; // video bit zero start
  102. @property (nonatomic, strong) KSLiveBadNetView *netBadView;
  103. @property (nonatomic, assign) BOOL isLostPackage; // 严重丢包
  104. @property (nonatomic, assign) BOOL isBitError; // bit error
  105. @property (nonatomic, strong) LOTAnimationView *animationView;
  106. @property (nonatomic, assign) BOOL isCloseRoom; // 是否直播结束
  107. @property (nonatomic, strong) KSLiveEndView *endView; // 直播结束显示的画面
  108. @property (nonatomic, strong) KSShopCardView *cardView;
  109. @property (nonatomic, strong) LiveAnimationView *enterAnimationView;
  110. @property (nonatomic, assign) NSTimeInterval lastSendMsgTime;
  111. @property (nonatomic, assign) BOOL isOtherLogin; // 是否被顶掉
  112. @property (nonatomic, strong) LiveApplyView *applyView; // 未申请
  113. @property (nonatomic, strong) LiveApplyingView *applyingView; // 申请中
  114. @property (nonatomic, assign) BOOL isPauseLive;
  115. @property (nonatomic, strong) ShareLiveDisplayView *shareView;
  116. @end
  117. // 用于记录点赞消息连续点击的次数
  118. static int clickPraiseBtnTimes = 0;
  119. @implementation LiveVideoRoomViewController
  120. - (instancetype)init {
  121. self = [super init];
  122. if (self) {
  123. [self defaultConfig];
  124. }
  125. return self;
  126. }
  127. - (void)defaultConfig {
  128. self.enableChat = YES;
  129. self.enableSeat = YES;
  130. self.enableLike = YES;
  131. self.micStatus = MICSTATUS_NOMAL;
  132. self.isCreaterInRoom = NO; // 默认老师不在房间
  133. self.isImConnected = [USER_MANAGER checkIMConnected];
  134. self.isCloseRoom = NO;
  135. self.isOtherLogin = NO;
  136. }
  137. - (void)viewDidLoad {
  138. [super viewDidLoad];
  139. // Do any additional setup after loading the view.
  140. self.ks_prefersNavigationBarHidden = YES;
  141. [self registerNotification];
  142. [self setupUI];
  143. if (self.isImConnected) {
  144. [self connectService];
  145. }
  146. else {
  147. MJWeakSelf;
  148. [USER_MANAGER connectionIMCallback:^(BOOL isSuccess) {
  149. dispatch_main_async_safe(^{
  150. [weakSelf connectService];
  151. });
  152. }];
  153. }
  154. [self countUMEvent];
  155. [self judgeAutoClose];
  156. }
  157. - (void)countUMEvent {
  158. [USER_MANAGER sendUMEvent:@"klx_live"];
  159. }
  160. - (void)muteAllAudio {
  161. if (self.room) {
  162. [self.room muteAllRemoteAudio:YES];
  163. }
  164. }
  165. - (void)IMConnetedCallback {
  166. dispatch_main_async_safe(^{
  167. // IM 连接成功回调
  168. if (self.hasSendWelcomeMessage) { // 如果已经发送了进入消息
  169. // 查询是否直播间开启
  170. [KSNetworkingManager liveRoomJoinRoomRequest:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) {
  171. if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
  172. // 回调信息
  173. MJWeakSelf;
  174. [self notiferJoinSuccessToServiceCallback:^{
  175. [weakSelf MBPShow:@"IM连接成功"];
  176. [weakSelf sendWelcomeMessage];
  177. }];
  178. }
  179. else {
  180. [self MBPShow:MESSAGEKEY];
  181. [self quitRoom];
  182. }
  183. } faliure:^(NSError * _Nonnull error) {
  184. }];
  185. }
  186. });
  187. }
  188. - (void)sendWelcomeMessage {
  189. [[RCIMClient sharedRCIMClient] joinExistChatRoom:self.roomId messageCount:-1 success:^{
  190. KSLiveChatroomWelcome *joinChatroomMessage = [[KSLiveChatroomWelcome alloc] init];
  191. [joinChatroomMessage setMsgId:[RCIM sharedRCIM].currentUserInfo.userId];
  192. MJWeakSelf;
  193. [self sendMessage:joinChatroomMessage displayMessage:NO callback:^(BOOL success) {
  194. [weakSelf showEnterMessageAndJoinRTCRoom];
  195. }];
  196. } error:^(RCErrorCode status) {
  197. NSLog(@"error code %zd" ,status);
  198. }];
  199. }
  200. - (void)showEnterMessageAndJoinRTCRoom {
  201. NSString *contentString = [NSString stringWithFormat:@"%@ 进入直播间",UserDefault(NicknameKey)];
  202. [self showAnimationView:YES showMessag:contentString];
  203. [self joinRTCRoom];
  204. }
  205. - (void)leaveRTCRoomCallback:(void(^)(BOOL success))callback {
  206. [[RCRTCEngine sharedInstance] leaveRoom:^(BOOL isSuccess, RCRTCCode code) {
  207. callback(isSuccess);
  208. }];
  209. }
  210. - (void)joinRTCRoom {
  211. // 如果当前加入了房间 只刷新或者退出
  212. if (self.room && self.liveRoleType == RCRTCLiveRoleTypeBroadcaster) {
  213. [self connectHostWithStatus:NO];
  214. }
  215. else {
  216. [self setRoleType];
  217. }
  218. }
  219. - (void)judgeAutoClose {
  220. if (self.isTempRoom == NO) {
  221. self.timeManager.autoCloseNetworkRoomTime = [self getCloseTime];
  222. }
  223. }
  224. - (void)liveroomLogOut {
  225. if (self.isOtherLogin) {
  226. return;
  227. }
  228. self.isOtherLogin = YES;
  229. [self MBPShow:@"该账号在其他设备上登录"];
  230. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  231. [self quitRoom];
  232. [[NSNotificationCenter defaultCenter] postNotificationName:@"liveroomBackRefresh" object:nil];
  233. });
  234. }
  235. - (void)liveroomQuit {
  236. if (self.isOtherLogin) {
  237. return;
  238. }
  239. self.isOtherLogin = YES;
  240. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  241. [self quitRoom];
  242. [[NSNotificationCenter defaultCenter] postNotificationName:@"liveroomBackRefresh" object:nil];
  243. });
  244. }
  245. - (void)registerNotification {
  246. [KSChatroomMessageCenter registerMessageTypes];
  247. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMessageNotification:) name:OnReceiveChatroomMessageNotification object:nil];
  248. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomLogOut) name:@"liveroomLogout" object:nil];
  249. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomQuit) name:@"liveroomQuit" object:nil];
  250. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(IMConnetedCallback) name:@"RongIMConnected" object:nil];
  251. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(muteAllAudio) name:@"muteLiveAudio" object:nil];
  252. }
  253. - (void)setupUI {
  254. CGSize size = self.view.bounds.size;
  255. self.view.backgroundColor = HexRGB(0x25292e);
  256. [self.view addSubview:self.emptyView];
  257. [self.view addSubview:self.endView];
  258. // 视图主容器
  259. [self.view addSubview:self.videoContainerView];
  260. [self.videoContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
  261. make.left.top.bottom.right.mas_equalTo(self.view);
  262. }];
  263. [self.videoContainerView addSubview:self.videoView];
  264. [self.videoContainerView addSubview:self.netBadView];
  265. [self.videoView mas_makeConstraints:^(MASConstraintMaker *make) {
  266. make.left.top.bottom.right.mas_equalTo(self.view);
  267. }];
  268. [self.emptyView mas_makeConstraints:^(MASConstraintMaker *make) {
  269. make.left.right.top.bottom.mas_equalTo(self.videoView);
  270. }];
  271. self.emptyView.hidden = YES;
  272. // 直播结束的画面
  273. [self.endView mas_makeConstraints:^(MASConstraintMaker *make) {
  274. make.left.right.top.bottom.mas_equalTo(self.videoView);
  275. }];
  276. self.endView.hidden = YES;
  277. [self.netBadView mas_makeConstraints:^(MASConstraintMaker *make) {
  278. make.left.right.top.bottom.mas_equalTo(self.videoView);
  279. }];
  280. self.netBadView.hidden = YES;
  281. [self.view addSubview:self.headView];
  282. self.headView.boardcastName.text = [NSString returnNoNullStringWithString:self.createrName];
  283. if (![NSString isEmptyString:self.createrAvatal]) {
  284. [self.headView.boardcastAvatal sd_setImageWithURL:[NSURL URLWithString:[self.createrAvatal getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:@"teacher_logo"]];
  285. }
  286. [self countLikeMessageCount];
  287. [self countMemberCount];
  288. [self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
  289. make.left.right.mas_equalTo(self.view);
  290. make.height.mas_equalTo(80);
  291. if (@available(iOS 11.0, *)) {
  292. make.top.mas_equalTo(self.view.mas_safeAreaLayoutGuideTop).offset(0);
  293. } else {
  294. // Fallback on earlier versions
  295. make.top.mas_equalTo(self.view.mas_top).offset(0);
  296. }
  297. }];
  298. [self.view addSubview:self.messageContentView];
  299. if (self.hideCartButton == NO) {
  300. [self.videoContainerView addSubview:self.animationView];
  301. [self.animationView mas_makeConstraints:^(MASConstraintMaker *make) {
  302. if (@available(iOS 11.0, *)) {
  303. make.bottom.mas_equalTo(self.videoContainerView.mas_safeAreaLayoutGuideBottom).offset(-14);
  304. } else {
  305. // Fallback on earlier versions
  306. make.bottom.mas_equalTo(self.videoContainerView.mas_bottom).offset(-14);
  307. }
  308. make.centerX.mas_equalTo(self.videoContainerView.mas_right).offset(-84);
  309. make.width.mas_equalTo(48);
  310. make.height.mas_equalTo(56);
  311. }];
  312. [self.animationView play];
  313. }
  314. [self.view addSubview:self.bottomView];
  315. self.bottomView.hideCartVButton = self.hideCartButton;
  316. [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
  317. make.left.right.mas_equalTo(self.view);
  318. if (@available(iOS 11.0, *)) {
  319. make.bottom.mas_equalTo(self.view.mas_safeAreaLayoutGuideBottom).offset(-10);
  320. } else {
  321. // Fallback on earlier versions
  322. make.bottom.mas_equalTo(self.view.mas_bottom).offset(-10);
  323. }
  324. make.height.mas_equalTo(44);
  325. }];
  326. [self.messageContentView mas_makeConstraints:^(MASConstraintMaker *make) {
  327. make.left.mas_equalTo(self.view.mas_left);
  328. make.right.mas_equalTo(self.view.mas_right);
  329. make.bottom.mas_equalTo(self.bottomView.mas_top).offset(-10);
  330. make.height.mas_equalTo(180);
  331. }];
  332. [self.view addSubview:self.inputBar];
  333. [_inputBar setBackgroundColor: [UIColor whiteColor]];
  334. [_inputBar setFrame:CGRectMake(0, kScreenHeight, size.width , 50)];
  335. [_inputBar setHidden:YES];
  336. [self.messageContentView addSubview:self.conversationMessageTableView];
  337. [self.conversationMessageTableView mas_makeConstraints:^(MASConstraintMaker *make) {
  338. make.left.top.mas_equalTo(self.messageContentView);
  339. make.width.mas_equalTo(KPortraitWidth);
  340. make.bottom.mas_equalTo(self.messageContentView.mas_bottom);
  341. }];
  342. UITapGestureRecognizer *resetBottomTapGesture =[[UITapGestureRecognizer alloc]
  343. initWithTarget:self
  344. action:@selector(resetBottomGesture:)];
  345. resetBottomTapGesture.delegate = self;
  346. [self.view addGestureRecognizer:resetBottomTapGesture];
  347. // if (self.isTempRoom) {
  348. // self.bottomView.hideShareButton = NO;
  349. // }
  350. // else {
  351. // self.bottomView.hideShareButton = YES;
  352. // }
  353. }
  354. - (void)connectService {
  355. [self setRoleType];
  356. [self joinChatRoom];
  357. }
  358. - (void)joinChatRoom {
  359. [[RCIMClient sharedRCIMClient] joinChatRoom:self.roomId messageCount:-1 success:^{
  360. dispatch_async(dispatch_get_main_queue(), ^{
  361. [self addConstMessage];
  362. });
  363. KSLiveChatroomWelcome *joinChatroomMessage = [[KSLiveChatroomWelcome alloc] init];
  364. [joinChatroomMessage setMsgId:[RCIM sharedRCIM].currentUserInfo.userId];
  365. MJWeakSelf;
  366. [self sendMessage:joinChatroomMessage displayMessage:NO callback:^(BOOL success) {
  367. weakSelf.hasSendWelcomeMessage = YES;
  368. NSString *contentString = [NSString stringWithFormat:@"%@ 进入直播间",UserDefault(NicknameKey)];;
  369. [weakSelf showAnimationView:YES showMessag:contentString];
  370. }];
  371. // 加入成功发送消息
  372. } error:^(RCErrorCode status) {
  373. if (status == RC_CHATROOM_NOT_EXIST || status == KICKED_FROM_CHATROOM || status == RC_PARAMETER_INVALID_CHATROOM) {
  374. dispatch_async(dispatch_get_main_queue(), ^{
  375. [self MBPShow:@"加入聊天室失败"];
  376. });
  377. }
  378. }];
  379. }
  380. - (void)viewWillAppear:(BOOL)animated {
  381. [super viewWillAppear:animated];
  382. [UIApplication sharedApplication].idleTimerDisabled = YES;
  383. [IQKeyboardManager sharedManager].enableAutoToolbar = NO;
  384. [IQKeyboardManager sharedManager].enable = NO;
  385. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
  386. // 重新渲染view
  387. KSDragWindowManager *manager = [KSDragWindowManager sharedManager];
  388. if (manager.hasShowWindow) {
  389. [manager resignDragWindow];
  390. }
  391. if (self.hasShowSuspendView) {
  392. KSLiveStreamVideo *mainVideo = nil;
  393. if (self.videoView.streamId) {
  394. NSMutableArray *steamArray = [self.streamVideos mutableCopy];
  395. for (KSLiveStreamVideo *streamVideo in steamArray) {
  396. if ([streamVideo.streamId isEqualToString:self.videoView.streamId]) {
  397. mainVideo = streamVideo;
  398. break;
  399. }
  400. }
  401. }
  402. if (mainVideo) {
  403. [self.videoView addSubview:mainVideo.canvesView];
  404. [mainVideo.canvesView mas_updateConstraints:^(MASConstraintMaker *make) {
  405. make.left.right.bottom.top.mas_equalTo(self.videoView);
  406. }];
  407. }
  408. _hasShowSuspendView = NO;
  409. }
  410. if (self.room) {
  411. [self.room muteAllRemoteAudio:NO];
  412. }
  413. }
  414. - (void)viewWillDisappear:(BOOL)animated {
  415. [super viewWillDisappear:animated];
  416. [UIApplication sharedApplication].idleTimerDisabled = NO;
  417. [IQKeyboardManager sharedManager].enableAutoToolbar = YES;
  418. [IQKeyboardManager sharedManager].enable = YES;
  419. if (@available(iOS 13.0, *)) {
  420. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDarkContent;
  421. } else {
  422. // Fallback on earlier versions
  423. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
  424. }
  425. // 退出房间才停止定时器
  426. if (_timeManager) {
  427. [_timeManager stopDurationTimer];
  428. }
  429. }
  430. // 刷新seatView
  431. - (void)renderSeatView {
  432. NSMutableArray *seatArray = [NSMutableArray array];
  433. NSArray *remoteUserArray = [self.room.remoteUsers mutableCopy];
  434. for (RCRTCRemoteUser *user in remoteUserArray) {
  435. if (![user.userId isEqualToString:self.videoView.streamUserId] && ![user.userId isEqualToString:self.createrId] && ![user.userId containsString:@"rc_mc_"]) {
  436. [seatArray addObject:user.userId];
  437. }
  438. }
  439. if (self.micStatus == MICSTATUS_CONNECTING && ![seatArray containsObject:UserDefault(IM_USERID)]) {
  440. [seatArray addObject:UserDefault(IM_USERID)];
  441. }
  442. if (seatArray.count) {
  443. if (![self.videoContainerView.subviews containsObject:self.seatContainer]) {
  444. [self.videoContainerView addSubview:self.seatContainer];
  445. }
  446. [self updateSeatContainerConstraint];
  447. self.seatContainer.seatMemberArray = [NSMutableArray arrayWithArray:seatArray];
  448. [self.seatContainer refreshSeatUI];
  449. }
  450. else {
  451. [self removeSeatContainer];
  452. }
  453. }
  454. - (void)updateSeatContainerConstraint {
  455. [self.seatContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
  456. make.left.right.mas_equalTo(self.videoContainerView);
  457. make.top.mas_equalTo(self.headView.mas_bottom).offset(0);
  458. make.height.mas_equalTo(70);
  459. }];
  460. }
  461. // 移除连麦视图
  462. - (void)removeSeatContainer {
  463. if ([self.videoContainerView.subviews containsObject:self.seatContainer]) {
  464. [self.seatContainer removeFromSuperview];
  465. }
  466. }
  467. - (void)countLikeMessageCount {
  468. NSInteger count = self.likeCount;
  469. NSString *desc = @"";
  470. if (count > 10000) {
  471. count = count / 10000;
  472. desc = [NSString stringWithFormat:@"本场点赞%zd万+",count];
  473. }
  474. else {
  475. desc = [NSString stringWithFormat:@"本场点赞%zd",count];
  476. }
  477. self.headView.likeCount.text = desc;
  478. }
  479. - (void)setRoleType {
  480. self.micStatus = MICSTATUS_NOMAL;
  481. [self hideApplyingView];
  482. // 1.设置切换听筒为扬声器
  483. [self.engine setDefaultAudioRouteToSpeaker:YES];
  484. // 2.加入RTC房间
  485. [self joinLiveRoom];
  486. }
  487. // 观众上下麦
  488. - (void)connectHostWithStatus:(BOOL)isConnect {
  489. // 转换中,不再处理转换身份
  490. if (self.isTransferRole) {
  491. return;
  492. }
  493. // 如果需要切换的身份和当前身份相同 拦截
  494. if (isConnect && self.liveRoleType == RCRTCLiveRoleTypeBroadcaster) {
  495. return;
  496. }
  497. else if (isConnect == NO && self.liveRoleType == RCRTCLiveRoleTypeAudience) {
  498. return;
  499. }
  500. self.isTransferRole = YES;
  501. // 清理当前视图
  502. [self removeSeatContainer];
  503. [self clearVideoViewSource];
  504. if (isConnect) {
  505. // 开启麦克风采集
  506. [self.engine.defaultAudioStream setMicrophoneDisable:NO];
  507. NSArray *streams = @[self.engine.defaultAudioStream];
  508. MJWeakSelf;
  509. [self.room.localUser switchToBroadcaster:streams onSucceed:^(RCRTCLiveInfo * _Nonnull liveInfo) {
  510. weakSelf.isTransferRole = NO;
  511. dispatch_main_async_safe(^{
  512. __strong typeof(weakSelf) strongSelf = weakSelf;
  513. //3.切换成功, 订阅远端用户流
  514. strongSelf.liveRoleType = RCRTCLiveRoleTypeBroadcaster;
  515. NSMutableArray *streamArray = [NSMutableArray array];
  516. NSArray *remoteUserArray = [strongSelf.room.remoteUsers mutableCopy];
  517. for (RCRTCRemoteUser *user in remoteUserArray) {
  518. if (remoteUserArray.count) {
  519. [streamArray addObjectsFromArray:user.remoteStreams];
  520. }
  521. }
  522. if (streamArray.count) {
  523. [strongSelf subscribeRemoteResource:streamArray];
  524. }
  525. else {
  526. [strongSelf updateVideoViewContainer];
  527. }
  528. strongSelf.micStatus = MICSTATUS_CONNECTING;
  529. // 刷新麦位状态
  530. });
  531. } onFailed:^(RCRTCCode code) {
  532. NSLog(@"------- up error code %ld", code);
  533. weakSelf.isTransferRole = NO;
  534. dispatch_main_async_safe(^{
  535. if (code == RCRTCCodeSwitchRoleSameRoleError) {
  536. weakSelf.liveRoleType = RCRTCLiveRoleTypeBroadcaster;
  537. // 失败
  538. weakSelf.micStatus = MICSTATUS_CONNECTING;
  539. // 刷新麦位状态
  540. [weakSelf renderSeatView];
  541. }
  542. else {
  543. // 失败
  544. [weakSelf MBPShow:@"上麦失败"];
  545. weakSelf.micStatus = MICSTATUS_NOMAL;
  546. [weakSelf renderSeatView];
  547. }
  548. });
  549. } onKicked:^{
  550. weakSelf.isTransferRole = NO;
  551. dispatch_main_async_safe(^{
  552. // 被踢出
  553. [weakSelf kickOutLiveRoom];
  554. });
  555. }];
  556. }
  557. else { // 观众下麦
  558. MJWeakSelf;
  559. [self.room.localUser switchToAudienceOnSucceed:^{
  560. weakSelf.isTransferRole = NO;
  561. dispatch_main_async_safe(^{
  562. __strong typeof(weakSelf) strongSelf = weakSelf;
  563. strongSelf.liveRoleType = RCRTCLiveRoleTypeAudience;
  564. [strongSelf.engine.defaultAudioStream setMicrophoneDisable:YES];
  565. NSMutableArray *streamArray = [NSMutableArray array];
  566. NSArray *remoteUserArray = [strongSelf.room.remoteUsers mutableCopy];
  567. for (RCRTCRemoteUser *user in remoteUserArray) {
  568. if (user.remoteStreams.count) {
  569. [streamArray addObjectsFromArray:user.remoteStreams];
  570. }
  571. }
  572. strongSelf.micStatus = MICSTATUS_NOMAL;
  573. if (streamArray.count) {
  574. [strongSelf subscribeRemoteResource:streamArray];
  575. }
  576. else {
  577. [strongSelf updateVideoViewContainer];
  578. }
  579. // 刷新麦位状态
  580. // 发送下麦消息
  581. [weakSelf sendDownSeatMessage];
  582. });
  583. } onFailed:^(RCRTCCode code) {
  584. weakSelf.isTransferRole = NO;
  585. NSLog(@"------- down error code %ld", code);
  586. dispatch_main_async_safe(^{
  587. if (code == RCRTCCodeSwitchRoleSameRoleError) {
  588. weakSelf.liveRoleType = RCRTCLiveRoleTypeAudience;
  589. weakSelf.micStatus = MICSTATUS_NOMAL;
  590. }
  591. else {
  592. // 失败
  593. [weakSelf MBPShow:@"下麦失败"];
  594. weakSelf.micStatus = MICSTATUS_CONNECTING;
  595. }
  596. // 刷新麦位状态
  597. [weakSelf renderSeatView];
  598. });
  599. } onKicked:^{
  600. weakSelf.isTransferRole = NO;
  601. dispatch_main_async_safe(^{
  602. // 被踢出
  603. [weakSelf kickOutLiveRoom];
  604. });
  605. }];
  606. }
  607. }
  608. - (void)kickOutLiveRoom {
  609. [self MBPShow:@"您已被踢出直播间"];
  610. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  611. [self quitRoom];
  612. });
  613. }
  614. - (void)countMemberCount {
  615. NSInteger count = self.totalCount;
  616. NSString *desc = @"";
  617. if (count > 10000) {
  618. count = count / 10000;
  619. desc = [NSString stringWithFormat:@"%zd万+人",count];
  620. }
  621. else {
  622. desc = [NSString stringWithFormat:@"%zd人",count];
  623. }
  624. self.headView.roomMemberCount.text = desc;
  625. }
  626. - (void)clearVideoViewSource {
  627. self.videoView.streamUserId = @"";
  628. self.videoView.streamTag = @"";
  629. self.videoView.streamId = @"";
  630. }
  631. // 清空视图
  632. - (void)clearRemoteContainer {
  633. [self.streamVideos removeAllObjects];
  634. [self.videoView removeAllSubViews];
  635. [self clearVideoViewSource];
  636. [self removeSeatContainer];
  637. }
  638. // 更新视图
  639. - (void)updateVideoViewContainer {
  640. NSMutableArray *steamArray = [self.streamVideos mutableCopy];
  641. if (steamArray.count > 0) {
  642. // 渲染主播的流 优先显示主播的共享流
  643. KSLiveStreamVideo *mainVideo = nil;
  644. for (KSLiveStreamVideo *videoSource in steamArray) {
  645. if ([videoSource.userId isEqualToString:self.createrId]) { // 主屏只渲染老师
  646. if ([videoSource.streamTag isEqualToString:@"screenshare"]) { // 如果是共享流
  647. if (![self.videoView.streamId isEqualToString:videoSource.streamId]) { // 主屏渲染不同 则重新渲染
  648. mainVideo = videoSource;
  649. }
  650. break;
  651. }
  652. else if ([videoSource.streamTag isEqualToString:@"RongCloudRTC"]) { // 默认流
  653. if (![self.videoView.streamId isEqualToString:videoSource.streamId]) { // 主屏渲染不同 则重新渲染
  654. mainVideo = videoSource;
  655. }
  656. }
  657. else { // 其他流
  658. if (mainVideo == nil) { // 如果暂未遍历出其他视频流 先取一个作为主屏展示流
  659. if (![self.videoView.streamId isEqualToString:videoSource.streamId]) { // 主屏渲染不同 则重新渲染
  660. mainVideo = videoSource;
  661. }
  662. }
  663. }
  664. }
  665. }
  666. // 如果需要渲染主屏
  667. if (mainVideo) {
  668. [self.videoView removeAllSubViews];
  669. self.videoView.streamUserId = mainVideo.userId;
  670. self.videoView.streamId = mainVideo.streamId;
  671. self.videoView.streamTag = mainVideo.streamTag;
  672. mainVideo.canvesView.tag = 10000;
  673. [self.videoView addSubview:mainVideo.canvesView];
  674. [mainVideo.canvesView mas_makeConstraints:^(MASConstraintMaker *make) {
  675. make.left.right.bottom.top.mas_equalTo(self.videoView);
  676. }];
  677. }
  678. }
  679. [self changeEmptyStatusView];
  680. // 刷新 seatView
  681. [self renderSeatView];
  682. }
  683. - (void)removeViewSubViews:(UIView *)container {
  684. while (container.subviews.count) {
  685. [container.subviews.lastObject removeFromSuperview];
  686. }
  687. }
  688. - (void)changeEmptyStatusView {
  689. if (self.isCreaterInRoom) {
  690. if (self.isCreaterVideoEnable) {
  691. self.emptyView.hidden = YES;
  692. }
  693. else if (self.isCreaterVideoEnable == NO && self.isCreaterAudioEnable == YES) {
  694. // 主播关闭了摄像头
  695. [self showEmptyViewDisplay:YES];
  696. }
  697. else { // 休息一下
  698. [self showEmptyViewDisplay:NO];
  699. }
  700. }
  701. else {
  702. // 无主播
  703. [self.videoView removeAllSubViews];
  704. [self showEmptyViewDisplay:NO];
  705. }
  706. }
  707. #pragma mark ---- RCRTCRoomEventDelegate
  708. // 远端发布流
  709. - (void)didPublishStreams:(NSArray<RCRTCInputStream *> *)streams {
  710. [self subscribeRemoteResource:streams];
  711. NSLog(@"didPublishStreams --------");
  712. }
  713. // 订阅远端流
  714. - (void)subscribeRemoteResource:(NSArray<RCRTCInputStream *> *)streams {
  715. [self subscribeRemoteResource:streams isTiny:NO];
  716. }
  717. - (void)subscribeRemoteResource:(NSArray<RCRTCInputStream *> *)streams isTiny:(BOOL)isTiny {
  718. // 订阅房间中远端用户音视频流资源
  719. NSArray *tinyStream = isTiny ? streams : @[];
  720. NSArray *ordinaryStream = isTiny ? @[] : streams;
  721. [self.room.localUser subscribeStream:ordinaryStream
  722. tinyStreams:tinyStream
  723. completion:^(BOOL isSuccess, RCRTCCode desc) {
  724. if (desc != RCRTCCodeSuccess) {
  725. NSString *errorStr = [NSString stringWithFormat:@"订阅远端流失败:%ld", (long) desc];
  726. [self MBPShow:errorStr];
  727. return;
  728. }
  729. // 创建并设置远端视频预览视图
  730. for (RCRTCInputStream *stream in streams) {
  731. if ([stream.userId isEqualToString:self.createrId]) {
  732. self.isPauseLive = NO;
  733. }
  734. if (stream.mediaType == RTCMediaTypeVideo) {
  735. if ([stream.userId isEqualToString:self.createrId]) { // 只渲染老师的流
  736. self.isCreaterVideoEnable = YES;
  737. [self setupRemoteViewWithStream:stream];
  738. }
  739. }
  740. if (stream.mediaType == RTCMediaTypeAudio && [stream.userId isEqualToString:self.createrId]) {
  741. self.isCreaterAudioEnable = YES;
  742. }
  743. }
  744. [self updateVideoViewContainer];
  745. }];
  746. }
  747. // 创建并设置远端视频预览视图
  748. - (KSLiveStreamVideo *)setupRemoteViewWithStream:(RCRTCInputStream *)stream {
  749. KSLiveStreamVideo *sVideo = [self creatStreamVideoWithStreamId:stream.streamId userId:stream.userId tag:stream.tag];
  750. sVideo.userId = stream.userId;
  751. RCRTCVideoView *remoteView = (RCRTCVideoView *) sVideo.canvesView;
  752. //设置视频流的渲染视图
  753. [(RCRTCVideoInputStream *) stream setVideoView:remoteView];
  754. return sVideo;
  755. }
  756. // 远端用户取消发布资源
  757. - (void)didUnpublishStreams:(NSArray<RCRTCInputStream *> *)streams {
  758. [self unsubscribeRemoteResource:streams orStreamId:nil];
  759. }
  760. - (void)stream:(RCRTCInputStream *)stream didVideoEnable:(BOOL)enable {
  761. NSLog(@"----- video --- staus %d", enable);
  762. }
  763. - (void)stream:(RCRTCInputStream *)stream didAudioMute:(BOOL)mute {
  764. NSLog(@"----- audio --- staus %d", mute);
  765. if ([stream.userId isEqualToString:self.createrId]) {
  766. if (mute) {
  767. self.isCreaterAudioEnable = NO;
  768. }
  769. else {
  770. self.isCreaterAudioEnable = YES;
  771. }
  772. [self changeEmptyStatusView];
  773. }
  774. }
  775. - (void)didOfflineUser:(RCRTCRemoteUser *)user {
  776. [self removeRemoteVideoWithUserId:user.userId];
  777. [self renderSeatView];
  778. }
  779. // 判断是否已有预览视图
  780. - (KSLiveStreamVideo *)creatStreamVideoWithStreamId:(NSString *)streamId userId:(NSString *)userId tag:(NSString *)streamTag {
  781. KSLiveStreamVideo *sVideo = [self fetchStreamVideoWithStreamId:streamId];
  782. if (!sVideo) {
  783. sVideo = [[KSLiveStreamVideo alloc] initWithStreamId:streamId streamUserId:userId streamTag:streamTag];
  784. [self.streamVideos insertObject:sVideo atIndex:0];
  785. }
  786. return sVideo;
  787. }
  788. // 根据 streamId 确认唯一的音视频流
  789. - (KSLiveStreamVideo *)fetchStreamVideoWithStreamId:(NSString *)streamId {
  790. NSMutableArray *streamArray = [self.streamVideos mutableCopy];
  791. for (KSLiveStreamVideo *sVideo in streamArray) {
  792. if ([streamId isEqualToString:sVideo.streamId]) {
  793. return sVideo;
  794. }
  795. }
  796. return nil;
  797. }
  798. // 远端掉线/离开回掉调用,删除远端用户的所有音视频流
  799. - (void)fetchStreamVideoOffLineWithStreamId:(NSString *)streamId {
  800. NSArray *arr = [NSArray arrayWithArray:self.streamVideos];
  801. for (KSLiveStreamVideo *sVideo in arr) {
  802. if ([streamId isEqualToString:sVideo.streamId]) {
  803. if (sVideo) {
  804. [sVideo.canvesView removeFromSuperview];
  805. [self.streamVideos removeObject:sVideo];
  806. }
  807. }
  808. }
  809. [self updateVideoViewContainer];
  810. }
  811. // 远端用户切换身份(主播接收)
  812. - (void)didSwitchRoleWithUser:(RCRTCRemoteUser *)user roleType:(RCRTCLiveRoleType)roleType {
  813. if (roleType == RCRTCLiveRoleTypeAudience) {
  814. [self renderSeatView];
  815. }
  816. NSLog(@"didSwitchRoleWithUser --------");
  817. }
  818. - (void)judgeRefreshSeatView:(NSString *)userId {
  819. BOOL needRefresh = NO;
  820. if (_seatContainer) {
  821. if (self.seatContainer.seatMemberArray.count && [self.videoContainerView.subviews containsObject:self.seatContainer]) {
  822. NSMutableArray *memerArray = [self.seatContainer.seatMemberArray mutableCopy];
  823. for (NSString *memberId in memerArray) {
  824. if ([userId isEqualToString:memberId]) {
  825. needRefresh = YES;
  826. break;
  827. }
  828. }
  829. }
  830. }
  831. if (needRefresh) {
  832. [self renderSeatView];
  833. }
  834. }
  835. // 主播加入房间 (观众接收)
  836. - (void)didJoinUser:(RCRTCRemoteUser *)user {
  837. if ([user.userId isEqualToString:self.createrId]) { // 当主播重新进入房间需要重置连麦状态
  838. self.isCreaterInRoom = YES;
  839. // 重置连麦状态
  840. // [self resetMicWaitToNomal];
  841. // 刷新顶部视图
  842. }
  843. else {
  844. // [self renderSeatView];
  845. }
  846. NSLog(@"didJoinUser --------");
  847. }
  848. // 主播离开房间 (观众接收)
  849. - (void)didLeaveUser:(RCRTCRemoteUser *)user {
  850. [self removeRemoteVideoWithUserId:user.userId]; // 移除对应的视频
  851. if ([user.userId isEqualToString:self.createrId]) { // 如果是老师
  852. self.isCreaterInRoom = NO;
  853. [self resetMicWaitToNomal];
  854. [self tipsTeacherLeaveRoom];
  855. [self clearVideoView];
  856. [self hideTipsView];
  857. [self showEmptyViewDisplay:NO];
  858. }
  859. else {
  860. [self renderSeatView];
  861. }
  862. }
  863. - (void)removeRemoteVideoWithUserId:(NSString *)userId {
  864. NSArray *arr = [NSArray arrayWithArray:self.streamVideos];
  865. for (KSLiveStreamVideo *sVideo in arr) {
  866. if ([userId isEqualToString:sVideo.userId]) {
  867. if (sVideo) {
  868. [sVideo.canvesView removeFromSuperview];
  869. [self.streamVideos removeObject:sVideo];
  870. }
  871. }
  872. }
  873. }
  874. - (void)clearVideoView {
  875. [self clearVideoViewSource];
  876. [self.videoView removeAllSubViews];
  877. }
  878. - (void)resetConnectionStatus {
  879. if (self.micStatus == MICSTATUS_CONNECTING) {
  880. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  881. [self connectHostWithStatus:NO];
  882. });
  883. }
  884. else {
  885. [self removeSeatContainer];
  886. }
  887. self.micStatus = MICSTATUS_NOMAL;
  888. }
  889. - (void)resetMicWaitToNomal {
  890. if (self.micStatus == MICSTATUS_WAITING) {
  891. self.micStatus = MICSTATUS_NOMAL;
  892. }
  893. }
  894. - (void)tipsTeacherLeaveRoom {
  895. [self MBPShow:@"老师已离开房间"];
  896. }
  897. #pragma mark - KSChatInputBarControlDelegate
  898. // 根据inputBar 回调来修改页面布局
  899. - (void)onInputBarControlContentSizeChanged:(CGRect)frame withAnimationDuration:(CGFloat)duration andAnimationCurve:(UIViewAnimationCurve)curve {
  900. CGRect originFrame = _inputBar.frame;
  901. __weak __typeof(&*self)weakSelf = self;
  902. // 目前只使用y值即可 -- 只修改messageContentView高度,让展示消息view和输入框随之移动
  903. [UIView animateWithDuration:duration animations:^{
  904. [UIView setAnimationCurve:curve];
  905. [weakSelf.inputBar setFrame:CGRectMake(0, frame.origin.y - originFrame.size.height, originFrame.size.width, originFrame.size.height)];
  906. [UIView commitAnimations];
  907. }];
  908. }
  909. // 发送消息
  910. - (void)onTouchSendButton:(NSString *)text {
  911. if (self.blacklistFlag == YES) {
  912. [self setDefaultBottomViewStatus];
  913. return;
  914. }
  915. NSDate *date = [NSDate date];
  916. NSTimeInterval currentInterval = [date timeIntervalSince1970];
  917. if (currentInterval - self.lastSendMsgTime < 3) {
  918. [self MBPShow:@"您说话太快啦"];
  919. return;
  920. }
  921. if (self.lastSendMsgTime == 0) {
  922. self.lastSendMsgTime = currentInterval;
  923. }
  924. if (text.length > 40) {
  925. [self MBPShow:@"聊天消息需在40个字以内哦"];
  926. return;
  927. }
  928. self.lastSendMsgTime = currentInterval;
  929. [self touristSendMessage:text];
  930. }
  931. - (void)touristSendMessage:(NSString *)text {
  932. RCTextMessage *rcTextMessage = [RCTextMessage messageWithContent:text];
  933. MJWeakSelf;
  934. [self sendMessage:rcTextMessage displayMessage:YES callback:^(BOOL success) {
  935. [weakSelf setDefaultBottomViewStatus];
  936. }];
  937. }
  938. #pragma mark ---- RTC
  939. - (void)joinLiveRoom {
  940. // 1.配置房间
  941. RCRTCRoomConfig *config = [[RCRTCRoomConfig alloc] init];
  942. config.roomType = RCRTCRoomTypeLive;
  943. config.liveType = RCRTCLiveTypeAudioVideo;
  944. config.roleType = RCRTCLiveRoleTypeAudience;
  945. __weak typeof(self) weakSelf = self;
  946. [self.engine joinRoom:self.roomId config:config completion:^(RCRTCRoom * _Nullable room, RCRTCCode code) {
  947. dispatch_main_async_safe(^{
  948. __strong typeof(weakSelf) strongSelf = weakSelf;
  949. if (code == RCRTCCodeSignalServerNotConnect || code == RCRTCCodeParameterError || code == RCRTCCodeNotInRTCRoom || code == RCRTCCodeRTCTokenIsNull || code == RCRTCCodeHttpTimeoutError || code == RCRTCCodeHttpError || code == RCRTCCodeVoIPNotAvailable) {
  950. NSLog(@"%@",[NSString stringWithFormat:@"加入失败 code %ld",code]);
  951. }
  952. else { // 加入直播间成功
  953. [strongSelf notiferJoinSuccessToServiceCallback:^{
  954. }];
  955. if (room) {
  956. strongSelf.room = room;
  957. strongSelf.room.delegate = self;
  958. }
  959. [strongSelf countMemberCount];
  960. if (room.remoteUsers.count) {
  961. NSMutableArray *streamArray = [NSMutableArray array];
  962. NSMutableArray *remoteUserArray = [room.remoteUsers mutableCopy];
  963. for (RCRTCRemoteUser *user in remoteUserArray) {
  964. if ([user.userId isEqualToString:strongSelf.createrId]) {
  965. strongSelf.isCreaterInRoom = YES;
  966. }
  967. if (user.remoteStreams.count) {
  968. [streamArray addObjectsFromArray:user.remoteStreams];
  969. [strongSelf subscribeRemoteResource:streamArray];
  970. }
  971. }
  972. }
  973. [self changeEmptyStatusView];
  974. }
  975. });
  976. }];
  977. }
  978. - (void)closeAlertView {
  979. if (self.alertView && self.alertView.isShow) {
  980. [self.alertView removeFromSuperview];
  981. self.alertView = nil;
  982. }
  983. }
  984. - (void)displayCloseView {
  985. self.isCloseRoom = YES;
  986. [self hideTipsView];
  987. // 移除连麦视图
  988. [self clearRemoteContainer];
  989. if (self.micStatus != MICSTATUS_NOMAL) {
  990. self.micStatus = MICSTATUS_NOMAL;
  991. self.liveRoleType = RCRTCLiveRoleTypeAudience;
  992. [self.engine.defaultAudioStream setMicrophoneDisable:YES];
  993. }
  994. [self.engine leaveRoom:^(BOOL isSuccess, RCRTCCode code) {
  995. if (code != RCRTCCodeSuccess) {
  996. // 退出失败
  997. }
  998. }];
  999. self.endView.hidden = NO;
  1000. [self setDefaultBottomViewStatus];
  1001. [[NSNotificationCenter defaultCenter] postNotificationName:@"LiveroomClose" object:nil];
  1002. }
  1003. - (void)insertMessage:(RCMessage *)rcMessage userInfo:(NSDictionary *)userInfo {
  1004. // left
  1005. NSDictionary *leftDic = userInfo;
  1006. if (leftDic && [leftDic[@"left"] isEqual:@(0)]) {
  1007. self.isNeedScrollToButtom = YES;
  1008. }
  1009. [self appendAndDisplayMessage:rcMessage];
  1010. }
  1011. - (void)kickSeatWithMessage:(NSString *)message {
  1012. [self MBPShow:message];
  1013. self.micStatus = MICSTATUS_NOMAL;
  1014. [self connectHostWithStatus:NO];
  1015. }
  1016. - (void)showInviteAlertMessage:(NSString *)tipsMessage {
  1017. if (self.alertView && self.alertView.isShow) {
  1018. [self.alertView removeFromSuperview];
  1019. self.alertView = nil;
  1020. }
  1021. MJWeakSelf;
  1022. self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:tipsMessage leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{
  1023. weakSelf.micStatus = MICSTATUS_NOMAL;
  1024. [weakSelf sendSeatResponseMessage:NO];
  1025. } confirm:^{
  1026. [weakSelf sendSeatResponseMessage:YES];
  1027. }];
  1028. }
  1029. - (void)sendSeatResponseMessage:(BOOL)isApprove {
  1030. SEATRESPONSE type = isApprove ? SEATRESPONSE_AUDIENCEAPPROVE : SEATRESPONSE_AUDIENCEREJECT;
  1031. KSLiveChatroomSeatResponse *responseMessage = [[KSLiveChatroomSeatResponse alloc] init];
  1032. responseMessage.type = type;
  1033. responseMessage.audienceId = UserDefault(IM_USERID);
  1034. responseMessage.audienceName = UserDefault(NicknameKey);
  1035. MJWeakSelf;
  1036. [self sendMessage:responseMessage displayMessage:YES callback:^(BOOL success) {
  1037. if (success) {
  1038. if (isApprove) {
  1039. [weakSelf connectHostWithStatus:YES];
  1040. }
  1041. }
  1042. }];
  1043. }
  1044. - (void)showEmptyViewDisplay:(BOOL)isPublish {
  1045. NSString *imageName = @"";
  1046. NSString *descMessage = @"";
  1047. if (isPublish) {
  1048. imageName = @"liveVideo_close";
  1049. descMessage = @"老师已关闭画面!";
  1050. }
  1051. else {
  1052. imageName = @"liveroom_empty";
  1053. descMessage = @"休息一下,马上回来!";
  1054. }
  1055. [self.emptyView configViewWithImageName:imageName desc:descMessage];
  1056. self.emptyView.hidden = NO;
  1057. }
  1058. - (void)exitRoom {
  1059. [self.engine.defaultAudioStream setMicrophoneDisable:YES];
  1060. __weak typeof(self) weakSelf = self;
  1061. [self.engine leaveRoom:^(BOOL isSuccess, RCRTCCode code) {
  1062. dispatch_main_async_safe(^{
  1063. __strong typeof(weakSelf) strongSelf = weakSelf;
  1064. [strongSelf sendLeaveMessageCallback:^(BOOL success) {
  1065. // quit 接口
  1066. [weakSelf quitNotiferService];
  1067. [weakSelf quitChatRoom];
  1068. }];
  1069. });
  1070. }];
  1071. }
  1072. // 取消订阅流 Id 所对应的 View
  1073. - (void)unsubscribeRemoteResource:(NSArray<RCRTCInputStream *> *)streams orStreamId:(NSString *)streamId {
  1074. for (RCRTCInputStream *stream in streams) {
  1075. if (stream.mediaType == RTCMediaTypeVideo) {
  1076. streamId = stream.streamId;
  1077. if ([stream.userId isEqualToString:self.createrId]) {
  1078. [self clearVideoViewSource];
  1079. self.isCreaterVideoEnable = NO;
  1080. // 主讲老师停止发布视频流
  1081. NSLog(@"----- 停止发布视频流");
  1082. }
  1083. [self fetchStreamVideoOffLineWithStreamId:streamId];
  1084. }
  1085. else if (stream.mediaType == RTCMediaTypeAudio && [stream.userId isEqualToString:self.createrId]) {
  1086. // 主讲老师停止发布音频流
  1087. NSLog(@"----- 停止发布音频流");
  1088. self.isCreaterAudioEnable = NO;
  1089. }
  1090. }
  1091. }
  1092. #pragma mark -- 加入直播间和退出直播间回到服务
  1093. - (void)notiferJoinSuccessToServiceCallback:(void(^)(void))callback {
  1094. callback();
  1095. [KSNetworkingManager liveRoomJoinRoomRequest:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) {
  1096. callback();
  1097. } faliure:^(NSError * _Nonnull error) {
  1098. callback();
  1099. }];
  1100. }
  1101. #pragma mark -- 退出直播间回掉服务
  1102. - (void)quitNotiferService {
  1103. [KSNetworkingManager LiveroomQuit:KS_POST success:^(NSDictionary * _Nonnull dic) {
  1104. if ([dic ks_integerValueForKey:@"errCode"] == 0) {
  1105. NSLog(@"success");
  1106. }
  1107. else {
  1108. NSLog(@"----- error %@", [dic ks_stringValueForKey:@"errMsg"]);
  1109. }
  1110. } faliure:^(NSError * _Nonnull error) {
  1111. }];
  1112. }
  1113. - (void)sendLeaveMessageCallback:(void(^)(BOOL success))callback {
  1114. RCUserInfo *currentUserInfo = [RCIM sharedRCIM].currentUserInfo;
  1115. KSLiveChatroomLeave *leaveMessage = [[KSLiveChatroomLeave alloc] init];
  1116. leaveMessage.userId = currentUserInfo.userId;
  1117. leaveMessage.userName = currentUserInfo.name;
  1118. [self sendMessage:leaveMessage displayMessage:NO callback:^(BOOL success) {
  1119. callback(success);
  1120. }];
  1121. }
  1122. - (void)quitChatRoom {
  1123. [[RCIMClient sharedRCIMClient] quitChatRoom:self.roomId success:^{
  1124. NSLog(@"sucess log out--");
  1125. } error:^(RCErrorCode status) {
  1126. NSLog(@"RCErrorCode------%ld ", (long)status);
  1127. }];
  1128. }
  1129. - (void)addConstMessage { // 固定欢迎词语
  1130. RCTextMessage *statement = [RCTextMessage messageWithContent:@"欢迎进入直播课堂,请遵守相关法规,禁止传播低俗、暴力等不良信息。为孩子创造健康绿色的学习环境。"];
  1131. statement.extra = @"statement";
  1132. RCMessage *constMessage = [[RCMessage alloc] initWithType:ConversationType_CHATROOM targetId:self.roomId direction:MessageDirection_SEND content:statement];
  1133. constMessage.content.senderUserInfo = [RCIM sharedRCIM].currentUserInfo;
  1134. [self appendAndDisplayMessage:constMessage];
  1135. }
  1136. #pragma mark - views init
  1137. /**
  1138. * 接收到消息的回调
  1139. */
  1140. - (void)didReceiveMessageNotification:(NSNotification *)notification {
  1141. NSDictionary *dic = notification.object;
  1142. __block RCMessage *rcMessage = dic[@"message"];
  1143. KSRCMessageModel *model = [[KSRCMessageModel alloc] initWithMessage:rcMessage];
  1144. model.userInfo = rcMessage.content.senderUserInfo;
  1145. if (model.conversationType == ConversationType_CHATROOM && [model.targetId isEqualToString:self.roomId]) {
  1146. __weak typeof(&*self) __blockSelf = self;
  1147. dispatch_main_async_safe((^{
  1148. // 对赞消息拦截 ,展示动画,不插入数据源 ,对于seat消息 和踢出消息,弹出alert
  1149. if (rcMessage) {
  1150. // 👍🏻消息
  1151. if ([rcMessage.content isMemberOfClass:[KSLiveChatroomLike class]]) {
  1152. return;
  1153. }
  1154. else if ([rcMessage.content isMemberOfClass:[RCChatroomLikeCount class]]) {
  1155. RCChatroomLikeCount *likeCountMessage = (RCChatroomLikeCount *)rcMessage.content;
  1156. __blockSelf.likeCount = likeCountMessage.count;
  1157. [__blockSelf countLikeMessageCount];
  1158. return;
  1159. }
  1160. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomWelcome class]]) {
  1161. // 过滤自己发送的欢迎消息
  1162. if ([rcMessage.senderUserId isEqualToString:UserDefault(IM_USERID)]) {
  1163. return;
  1164. }
  1165. // if ([rcMessage.senderUserId isEqual:self.createrId]) { // 如果是老师 重置连麦申请状态
  1166. // [__blockSelf resetMicWaitToNomal];
  1167. // }
  1168. RCUserInfo *userInfo = rcMessage.content.senderUserInfo;
  1169. NSString *userName = [userInfo.name stringByAppendingString:@""];
  1170. NSString *contentMsg = [NSString stringWithFormat:@"%@ 进入直播间",userName];
  1171. [__blockSelf showAnimationView:YES showMessag:contentMsg];
  1172. }
  1173. // 用户退出消息
  1174. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomLeave class]]) {
  1175. return;
  1176. }
  1177. // 用户退出消息
  1178. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomUserQuit class]]) {
  1179. return;
  1180. }
  1181. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomKickOut class]]) {
  1182. KSLiveChatroomKickOut *kickMessage = (KSLiveChatroomKickOut *)rcMessage.content;
  1183. NSString *currentUserId = UserDefault(IM_USERID);
  1184. if ([kickMessage.targetId isEqualToString:currentUserId]) {
  1185. __blockSelf.micStatus = MICSTATUS_NOMAL;
  1186. [__blockSelf kickOutLiveRoom];
  1187. }
  1188. return;
  1189. }
  1190. // 人数同步
  1191. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomMemberUp class]]) {
  1192. KSLiveChatroomMemberUp *member = (KSLiveChatroomMemberUp *)rcMessage.content;
  1193. __blockSelf.totalCount = member.count;
  1194. [__blockSelf countMemberCount];
  1195. return;
  1196. }
  1197. // 连麦控制消息
  1198. else if ([rcMessage.content isMemberOfClass:[RCChatroomSeatsControl class]]) {
  1199. RCChatroomSeatsControl *seatControlMessage = (RCChatroomSeatsControl *)rcMessage.content;
  1200. if (seatControlMessage.seatBan) { // 禁止连麦
  1201. __blockSelf.enableSeat = NO;
  1202. // 未连麦情况下才重置
  1203. if (__blockSelf.micStatus != MICSTATUS_CONNECTING) {
  1204. __blockSelf.micStatus = MICSTATUS_NOMAL;
  1205. }
  1206. [__blockSelf hideApplyView];
  1207. [__blockSelf hideApplyingView];
  1208. }
  1209. else { // 开启连麦
  1210. __blockSelf.enableSeat = YES;
  1211. }
  1212. [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo];
  1213. }
  1214. // 连麦申请消息
  1215. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomSeatApply class]]) {
  1216. KSLiveChatroomSeatApply *seatApplyMessage = (KSLiveChatroomSeatApply *)rcMessage.content;
  1217. // 只处理和自己相关的消息
  1218. if (![seatApplyMessage.audienceId isEqualToString:UserDefault(IM_USERID)]) {
  1219. // 刷新麦序
  1220. return;
  1221. }
  1222. if (seatApplyMessage.type == SEATHANDLE_INVITE) { // 老师发起申请
  1223. NSString *titles = [NSString stringWithFormat:@"%@邀请您连麦",[NSString returnNoNullStringWithString:seatApplyMessage.teacherName]];
  1224. [__blockSelf showInviteAlertMessage:titles];
  1225. }
  1226. else if (seatApplyMessage.type == SEATHANDLE_DISINVITE) { // 老师撤回邀请
  1227. [__blockSelf MBPShow:@"老师撤回了连麦邀请"];
  1228. if (__blockSelf.alertView && __blockSelf.alertView.isShow) {
  1229. [__blockSelf.alertView dismissAlertView];
  1230. }
  1231. }
  1232. else if (seatApplyMessage.type == SEATHANDLE_KICKSEAT) { // 老师将麦上观众抱下麦
  1233. [__blockSelf kickSeatWithMessage:@"您已被抱下麦"];
  1234. }
  1235. [__blockSelf hideApplyingView];
  1236. [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo];
  1237. }
  1238. // 连麦回复消息(暂不处理)
  1239. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomSeatResponse class]]) {
  1240. KSLiveChatroomSeatResponse *seatResponseMessage = (KSLiveChatroomSeatResponse *)rcMessage.content;
  1241. if (![seatResponseMessage.audienceId isEqualToString:UserDefault(IM_USERID)]) { // 只处理和自己相关的消息
  1242. return;
  1243. }
  1244. if (seatResponseMessage.type == SEATRESPONSE_TEACHERAPPROVE && __blockSelf.micStatus == MICSTATUS_WAITING) { //
  1245. [__blockSelf hideApplyingView];
  1246. [__blockSelf connectHostWithStatus:YES];
  1247. [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo];
  1248. }
  1249. else if (seatResponseMessage.type == SEATRESPONSE_TEACHERREJECT) {
  1250. [__blockSelf MBPShow:@"老师拒绝了您的连麦申请"];
  1251. __blockSelf.micStatus = MICSTATUS_NOMAL;
  1252. [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo];
  1253. }
  1254. }
  1255. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomDownSeat class]]) { // 成员下麦消息
  1256. return;
  1257. }
  1258. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomDownSeat class]]) { // 成员主动下麦消息
  1259. return;
  1260. }
  1261. else if ([rcMessage.content isMemberOfClass:[KSLiveChatroomClose class]]) { // 直播间已关闭
  1262. [__blockSelf MBPShow:@"直播已结束"];
  1263. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1264. // 显示直播间关闭状态
  1265. [__blockSelf closeAlertView];
  1266. [__blockSelf displayCloseView];
  1267. [__blockSelf hideApplyingView];
  1268. });
  1269. return;
  1270. }
  1271. else if ([rcMessage.content isMemberOfClass:[RCTextMessage class]]) {
  1272. [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo];
  1273. }
  1274. else if ([rcMessage.content isMemberOfClass:[KSRCPauseLiveMessage class]]) { // 老师暂停直播
  1275. __blockSelf.isPauseLive = YES;
  1276. [__blockSelf MBPShow:@"老师已暂停直播"];
  1277. if (__blockSelf.micStatus == MICSTATUS_WAITING) {
  1278. [__blockSelf sendSeatMessageApply:NO];
  1279. }
  1280. [__blockSelf resetConnectionStatus];
  1281. [__blockSelf resetNetTips];
  1282. [__blockSelf closeAlertView];
  1283. [__blockSelf hideApplyingView];
  1284. return;
  1285. }
  1286. else if ([rcMessage.content isMemberOfClass:[KSRCShopRushMessage class]]) { //
  1287. KSRCShopRushMessage *rushMsg = (KSRCShopRushMessage *)rcMessage.content;
  1288. NSString *contentMsg = [NSString stringWithFormat:@"%@ 正在抢购",[NSString returnNoNullStringWithString:rushMsg.userName]];
  1289. [__blockSelf showAnimationView:NO showMessag:contentMsg];
  1290. }
  1291. else if ([rcMessage.content isMemberOfClass:[KSLiveBlockUser class]]) { // 黑名单
  1292. KSLiveBlockUser *blockMessage = (KSLiveBlockUser *)rcMessage.content;
  1293. if ([blockMessage.userId isEqualToString:UserDefault(IM_USERID)]) {
  1294. __blockSelf.blacklistFlag = YES;
  1295. [__blockSelf resetConnectionStatus];
  1296. }
  1297. }
  1298. else if ([rcMessage.content isMemberOfClass:[KSLiveUnBlockUser class]]) {
  1299. KSLiveUnBlockUser *unBlockMessage = (KSLiveUnBlockUser *)rcMessage.content;
  1300. if ([unBlockMessage.userId isEqualToString:UserDefault(IM_USERID)]) {
  1301. __blockSelf.blacklistFlag = NO;
  1302. }
  1303. }
  1304. else if ([rcMessage.content isMemberOfClass:[KSDownSeatAllMessage class]]) { // 所有麦上用户下麦
  1305. if (__blockSelf.micStatus == MICSTATUS_CONNECTING) {
  1306. [__blockSelf kickSeatWithMessage:@"您已被抱下麦"];
  1307. [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo];
  1308. }
  1309. }
  1310. else if ([rcMessage.content isMemberOfClass:[KSRejectAllSeatMessage class]]) { // 拒绝所有连麦申请
  1311. if (__blockSelf.micStatus == MICSTATUS_WAITING) {
  1312. [__blockSelf MBPShow:@"老师拒绝了您的连麦申请"];
  1313. // 处理连麦状态
  1314. __blockSelf.micStatus = MICSTATUS_NOMAL;
  1315. [__blockSelf insertMessage:rcMessage userInfo:notification.userInfo];
  1316. [__blockSelf hideApplyingView];
  1317. }
  1318. }
  1319. }
  1320. }));
  1321. }
  1322. }
  1323. - (void)hideApplyView {
  1324. if (self.applyView.isShow) {
  1325. [self.applyView hideView];
  1326. }
  1327. }
  1328. - (void)hideApplyingView {
  1329. if (self.applyingView.isShow) {
  1330. [self.applyingView hideView];
  1331. }
  1332. }
  1333. // 发送人数同步消息
  1334. - (void)sendMemberCountMessage {
  1335. KSLiveChatroomMemberCount *syncMessage = [[KSLiveChatroomMemberCount alloc] init];
  1336. syncMessage.count = self.totalCount;
  1337. [self sendMessage:syncMessage displayMessage:NO callback:^(BOOL success) {
  1338. }];
  1339. }
  1340. #pragma mark ----- time manager delegate
  1341. - (void)quitClassroomNotifer {
  1342. [self MBPShow:@"直播课已结束!"];
  1343. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1344. [self quitAction];
  1345. });
  1346. }
  1347. #pragma mark - gesture and button action
  1348. - (void)resetBottomGesture:(UIGestureRecognizer *)gestureRecognizer {
  1349. if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
  1350. [self setDefaultBottomViewStatus];
  1351. }
  1352. }
  1353. - (void)setDefaultBottomViewStatus {
  1354. [self.inputBar setInputBarStatus:KSBottomBarStatusDefault];
  1355. [self.inputBar setHidden:YES];
  1356. }
  1357. #pragma mark ---- sendMessage/showMessage
  1358. - (void)sendMessage:(RCMessageContent *)messageContent displayMessage:(BOOL)displayMessage callback:(void(^)(BOOL success))callback {
  1359. if (_roomId == nil) {
  1360. return;
  1361. }
  1362. messageContent.senderUserInfo = [RCIM sharedRCIM].currentUserInfo;
  1363. if (messageContent == nil) {
  1364. return;
  1365. }
  1366. __weak typeof(&*self) __weakself = self;
  1367. [KSChatroomMessageCenter sendChatMessage:self.roomId content:messageContent success:^(long messageId) {
  1368. dispatch_async(dispatch_get_main_queue(), ^{
  1369. RCMessage *message = [[RCMessage alloc] initWithType:ConversationType_CHATROOM targetId:__weakself.roomId direction:MessageDirection_SEND content:messageContent];
  1370. message.content.senderUserInfo = [RCIM sharedRCIM].currentUserInfo;
  1371. message.senderUserId = UserDefault(IM_USERID);
  1372. if (displayMessage) {
  1373. [__weakself appendAndDisplayMessage:message];
  1374. }
  1375. [__weakself.inputBar clearInputView];
  1376. callback(YES);
  1377. });
  1378. } error:^(RCErrorCode errorCode, long messageId) {
  1379. if (errorCode == RC_CHATROOM_NOT_EXIST) {
  1380. dispatch_async(dispatch_get_main_queue(), ^{
  1381. [__weakself MBPShow:@"聊天已被解散,请退出后重进。"];
  1382. });
  1383. }
  1384. dispatch_async(dispatch_get_main_queue(), ^{
  1385. [__weakself.inputBar clearInputView];
  1386. NSLog(@"发送失败,errorcode is: %ld",(long)errorCode);
  1387. callback(NO);
  1388. });
  1389. }];
  1390. }
  1391. /**
  1392. * 将消息加入本地数组
  1393. */
  1394. - (void)appendAndDisplayMessage:(RCMessage *)rcMessage {
  1395. if (!rcMessage) {
  1396. return;
  1397. }
  1398. KSRCMessageModel *model = [[KSRCMessageModel alloc] initWithMessage:rcMessage];
  1399. model.userInfo = rcMessage.content.senderUserInfo;
  1400. if (!model.userInfo) {
  1401. model.userInfo = [RCIMClient sharedRCIMClient].currentUserInfo;
  1402. }
  1403. if ([self appendMessageModel:model]) {
  1404. NSIndexPath *indexPath =
  1405. [NSIndexPath indexPathForItem:self.conversationDataRepository.count - 1
  1406. inSection:0];
  1407. if ([self.conversationMessageTableView numberOfRowsInSection:0] !=
  1408. self.conversationDataRepository.count - 1) {
  1409. return;
  1410. }
  1411. // view刷新
  1412. [self.conversationMessageTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
  1413. if ([self isAtTheBottomOfTableView] || self.isNeedScrollToButtom) {
  1414. [self scrollToBottomAnimated:YES];
  1415. self.isNeedScrollToButtom=NO;
  1416. }
  1417. }
  1418. return;
  1419. }
  1420. /**
  1421. * 如果当前会话没有这个消息id,把消息加入本地数组
  1422. */
  1423. - (BOOL)appendMessageModel:(KSRCMessageModel *)model {
  1424. if (!model.content) {
  1425. return NO;
  1426. }
  1427. //这里可以根据消息类型来决定是否显示,如果不希望显示直接return NO
  1428. //数量不可能无限制的大,这里限制收到消息过多时,就对显示消息数量进行限制。
  1429. //用户可以手动下拉更多消息,查看更多历史消息。
  1430. if (self.conversationDataRepository.count>500) {
  1431. // NSRange range = NSMakeRange(0, 1);
  1432. KSRCMessageModel *message = self.conversationDataRepository[0];
  1433. [[RCIMClient sharedRCIMClient]deleteMessages:@[@(message.messageId)]];
  1434. [self.conversationDataRepository removeObjectAtIndex:0];
  1435. [self.conversationMessageTableView reloadData];
  1436. }
  1437. [self.conversationDataRepository addObject:model];
  1438. return YES;
  1439. }
  1440. /**
  1441. * 判断消息是否在collectionView的底部
  1442. *
  1443. * @return 是否在底部
  1444. */
  1445. - (BOOL)isAtTheBottomOfTableView {
  1446. if (self.conversationMessageTableView.contentSize.height <= self.conversationMessageTableView.frame.size.height) {
  1447. return YES;
  1448. }
  1449. if(self.conversationMessageTableView.contentOffset.y +200 >= (self.conversationMessageTableView.contentSize.height - self.conversationMessageTableView.frame.size.height)) {
  1450. return YES;
  1451. }else{
  1452. return NO;
  1453. }
  1454. }
  1455. /**
  1456. * 消息滚动到底部
  1457. *
  1458. * @param animated 是否开启动画效果
  1459. */
  1460. - (void)scrollToBottomAnimated:(BOOL)animated {
  1461. if ([self.conversationMessageTableView numberOfSections] == 0) {
  1462. return;
  1463. }
  1464. NSUInteger finalRow = MAX(0, [self.conversationMessageTableView numberOfRowsInSection:0] - 1);
  1465. if (0 == finalRow) {
  1466. return;
  1467. }
  1468. NSIndexPath *finalIndexPath =
  1469. [NSIndexPath indexPathForItem:finalRow inSection:0];
  1470. [self.conversationMessageTableView scrollToRowAtIndexPath:finalIndexPath atScrollPosition:UITableViewScrollPositionTop animated:animated];
  1471. }
  1472. /*
  1473. #pragma mark - Navigation
  1474. // In a storyboard-based application, you will often want to do a little preparation before navigation
  1475. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
  1476. // Get the new view controller using [segue destinationViewController].
  1477. // Pass the selected object to the new view controller.
  1478. }
  1479. */
  1480. #pragma mark ----- table data source
  1481. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  1482. return self.conversationDataRepository.count;
  1483. }
  1484. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  1485. KSRCMessageModel *model = [self.conversationDataRepository objectAtIndex:indexPath.row];
  1486. RCMessageContent *messageContent = model.content;
  1487. KSChatroomTextCell *cell = [tableView dequeueReusableCellWithIdentifier:@"KSChatroomTextCell"];
  1488. if(!cell){
  1489. cell = [[KSChatroomTextCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"KSChatroomTextCell"];
  1490. }
  1491. if ([messageContent isMemberOfClass:[RCTextMessage class]] || [messageContent isMemberOfClass:[KSLiveChatroomWelcome class]] || [messageContent isMemberOfClass:[KSLiveChatroomLike class]] || [messageContent isMemberOfClass:[KSLiveChatroomSeatApply class]] || [messageContent isMemberOfClass:[KSLiveChatroomSeatResponse class]] || [messageContent isMemberOfClass:[KSDownSeatAllMessage class]] || [messageContent isMemberOfClass:[KSRejectAllSeatMessage class]] || [messageContent isMemberOfClass:[RCChatroomSeatsControl class]]){
  1492. [cell setDataModel:model createrId:self.createrId];
  1493. }
  1494. return cell;
  1495. }
  1496. - (void)sendSeatMessageApply:(BOOL)isApply {
  1497. if (self.micStatus == MICSTATUS_CONNECTING) {
  1498. [self MBPShow:@"您已上麦"];
  1499. return;
  1500. }
  1501. SEATHANDLE type = isApply ? SEATHANDLE_APPLY : SEATHANDLE_CANCELAPPLY;
  1502. KSLiveChatroomSeatApply *applyMessage = [[KSLiveChatroomSeatApply alloc] init];
  1503. applyMessage.type = type;
  1504. applyMessage.audienceId = UserDefault(IM_USERID);
  1505. applyMessage.audienceName = UserDefault(NicknameKey);
  1506. applyMessage.audienceAvatar = UserDefault(AvatarUrlKey);
  1507. applyMessage.teacherId = self.createrId;
  1508. applyMessage.teacherName = self.createrName;
  1509. MJWeakSelf;
  1510. [self sendMessage:applyMessage displayMessage:YES callback:^(BOOL success) {
  1511. if (success) {
  1512. weakSelf.micStatus = isApply ? MICSTATUS_WAITING : MICSTATUS_NOMAL;
  1513. }
  1514. }];
  1515. }
  1516. /**
  1517. 点赞
  1518. */
  1519. - (void)praiseBtnPressed {
  1520. NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
  1521. __weak __typeof(&*self)weakSelf = self;
  1522. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.21 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  1523. if ([[NSDate date] timeIntervalSince1970] - self.lastClickPraiseTime >= 0.2) {
  1524. KSLiveChatroomLike *praiseSendMessage = [[KSLiveChatroomLike alloc] init];
  1525. praiseSendMessage.counts = clickPraiseBtnTimes;
  1526. [weakSelf sendMessage:praiseSendMessage displayMessage:NO callback:^(BOOL success) {
  1527. }];
  1528. clickPraiseBtnTimes = 0;
  1529. // self.likeCount += praiseSendMessage.counts;
  1530. // [self countLikeMessageCount];
  1531. }
  1532. });
  1533. KSLiveChatroomLike *praiseMessage = [[KSLiveChatroomLike alloc] init];
  1534. clickPraiseBtnTimes++;
  1535. self.lastClickPraiseTime = currentTime;
  1536. [self presentLikeMessageAnimation:praiseMessage];
  1537. }
  1538. /**
  1539. 赞动画
  1540. @param likeMessage 赞消息
  1541. */
  1542. - (void)presentLikeMessageAnimation:(KSLiveChatroomLike *)likeMessage {
  1543. LiveRoomLikeLayer *layer = [[LiveRoomLikeLayer alloc] initWithName:@"Like"];
  1544. [self.view.layer addSublayer:layer];
  1545. CGRect tempFrame = [self.view convertRect:self.bottomView.likeButton.frame fromView:self.bottomView];
  1546. CGFloat width = tempFrame.size.width;
  1547. CGFloat height = tempFrame.size.height;
  1548. CGFloat dx = CGRectGetMidX(tempFrame) - width *0.5;
  1549. CGFloat dy = CGRectGetMidY(tempFrame) - 8 - height;
  1550. layer.frame = CGRectMake(dx, dy, width, height);
  1551. [layer startAnimation];
  1552. }
  1553. #pragma mark - setter & getter
  1554. - (void)setMicStatus:(MICSTATUS)micStatus {
  1555. _micStatus = micStatus;
  1556. if (self.bottomView) {
  1557. switch (micStatus) {
  1558. case MICSTATUS_NOMAL:
  1559. {
  1560. [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_nomal"] forState:UIControlStateNormal];
  1561. }
  1562. break;
  1563. case MICSTATUS_WAITING:
  1564. {
  1565. [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seating_image"] forState:UIControlStateNormal];
  1566. }
  1567. break;
  1568. case MICSTATUS_CONNECTING:
  1569. {
  1570. [self.bottomView.seatButton setImage:[UIImage imageNamed:@"seat_image"] forState:UIControlStateNormal];
  1571. }
  1572. break;
  1573. default:
  1574. break;
  1575. }
  1576. }
  1577. }
  1578. #pragma mark ----- RCRTCEngineEventDelegate
  1579. - (void)didRTCConnectionStateChanged:(RCRTCConnectionState)state {
  1580. if (state == RCRTCConnectionStateConnecting) {
  1581. NSLog(@"--------RCRTCConnectionStateConnecting");
  1582. }
  1583. else if (state == RCRTCConnectionStateConnected) {
  1584. NSLog(@"--------RCRTCConnectionStateConnected");
  1585. }
  1586. }
  1587. #pragma mark ----- RCRTCStatusReportDelegate
  1588. - (void)didReportStatusForm:(RCRTCStatusForm *)form {
  1589. // 获取丢包率
  1590. for (RCRTCStreamStat *stat in form.recvStats) {
  1591. if ([stat.mediaType isEqualToString:@"video"]) { // 接收视频会出现视频黑的情况 bitRate = 0
  1592. if (stat.packetLoss >= PACKAGE_LOST) { // 丢包
  1593. if (self.lostStartTime == 0) { // 如果之前没有丢包
  1594. self.lostStartTime = [[NSDate date] timeIntervalSince1970];
  1595. }
  1596. else {
  1597. NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
  1598. if (currentTime - self.lostStartTime >= 8) {
  1599. self.isLostPackage = YES;
  1600. [self showBadNetTips];
  1601. }
  1602. }
  1603. }
  1604. else {
  1605. self.lostStartTime = 0;
  1606. self.isLostPackage = NO;
  1607. [self hideTipsView];
  1608. }
  1609. if (stat.bitRate == 0) {
  1610. if (self.bitErrorTime == 0) {
  1611. self.bitErrorTime = [[NSDate date] timeIntervalSince1970];
  1612. }
  1613. else {
  1614. NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970];
  1615. if (currentTime - self.bitErrorTime >= 5) {
  1616. self.isBitError = YES;
  1617. [self showBadNetTips];
  1618. }
  1619. }
  1620. }
  1621. else {
  1622. self.isBitError = NO;
  1623. self.bitErrorTime = 0;
  1624. [self hideTipsView];
  1625. }
  1626. }
  1627. }
  1628. // NSLog(@"%@",form.description);
  1629. }
  1630. - (void)showBadNetTips {
  1631. dispatch_main_async_safe(^{
  1632. if (self.isCloseRoom == NO) {
  1633. if (self.netBadView.hidden == YES) {
  1634. self.netBadView.hidden = NO;
  1635. self.videoView.hidden = YES;
  1636. }
  1637. }
  1638. });
  1639. }
  1640. - (void)hideTipsView {
  1641. dispatch_main_async_safe(^{
  1642. if (self.netBadView.hidden == NO) {
  1643. if (self.isBitError == NO && self.isLostPackage == NO) {
  1644. self.netBadView.hidden = YES;
  1645. self.videoView.hidden = NO;
  1646. }
  1647. }
  1648. })
  1649. }
  1650. #pragma mark --- lazying
  1651. - (KSChatVideoView *)videoView {
  1652. if (!_videoView) {
  1653. _videoView = [[KSChatVideoView alloc] initWithFrame:self.view.bounds];
  1654. _videoView.backgroundColor = [UIColor clearColor];
  1655. }
  1656. return _videoView;
  1657. }
  1658. - (UIView *)videoContainerView {
  1659. if (!_videoContainerView) {
  1660. _videoContainerView = [[UIView alloc] initWithFrame:CGRectZero];
  1661. _videoContainerView.backgroundColor = [UIColor clearColor];
  1662. }
  1663. return _videoContainerView;
  1664. }
  1665. - (LiveRoomHeadView *)headView {
  1666. if (!_headView) {
  1667. _headView = [LiveRoomHeadView shareInstance];
  1668. MJWeakSelf;
  1669. [_headView quitAction:^{
  1670. [weakSelf showQuitAlert];
  1671. }];
  1672. // [_headView quitAction:^(BOOL isQuit) {
  1673. // if (isQuit) {
  1674. // [weakSelf showQuitAlert];
  1675. // }
  1676. // else {
  1677. // [weakSelf displayTeacherInfo:CART_TYPE_NONE];
  1678. // }
  1679. // }];
  1680. }
  1681. return _headView;
  1682. }
  1683. - (void)showTeacherCenter {
  1684. }
  1685. - (void)showQuitAlert {
  1686. if (self.isCloseRoom) { // 如果直播结束 直接点击退出房间
  1687. [self quitRoom];
  1688. }
  1689. else if (self.micStatus == MICSTATUS_CONNECTING) {
  1690. [self quitAction];
  1691. }
  1692. else {
  1693. MJWeakSelf;
  1694. self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"是否退出直播间" leftButtonTitle:@"取消" rightTitle:@"退出" inView:self.view cancel:^{
  1695. } confirm:^{
  1696. [weakSelf quitAction];
  1697. }];
  1698. }
  1699. }
  1700. - (LiveRoomBottomView *)bottomView {
  1701. if (!_bottomView) {
  1702. _bottomView = [LiveRoomBottomView shareInstance];
  1703. MJWeakSelf;
  1704. [_bottomView bottomClickAction:^(LIVEROOMACTION action) {
  1705. [weakSelf bottomViewAction:action];
  1706. }];
  1707. }
  1708. return _bottomView;
  1709. }
  1710. - (void)bottomViewAction:(LIVEROOMACTION)action {
  1711. if (self.isCloseRoom) {
  1712. [self MBPShow:@"直播已结束"];
  1713. return;
  1714. }
  1715. switch (action) {
  1716. case LIVEROOMACTION_CHAT: // 聊天
  1717. {
  1718. if (self.blacklistFlag == YES) {
  1719. [self MBPShow:@"您已被老师禁言"];
  1720. return;
  1721. }
  1722. [_inputBar setHidden:NO];
  1723. [_inputBar setInputBarStatus:KSBottomBarStatusKeyboard];
  1724. }
  1725. break;
  1726. case LIVEROOMACTION_SEAT: // // 连麦按钮
  1727. { // 连麦按钮
  1728. // 判断是否是连麦状态
  1729. if (self.micStatus == MICSTATUS_CONNECTING) { // 连麦中
  1730. // 是否取消连麦
  1731. MJWeakSelf;
  1732. self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"连麦中,确认取消连麦吗?" leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{
  1733. } confirm:^{
  1734. [weakSelf connectHostWithStatus:NO];
  1735. }];
  1736. }
  1737. else if (self.micStatus == MICSTATUS_WAITING) { // 连麦申请中
  1738. // 弹窗取消申请
  1739. [self displayApplyingView];
  1740. }
  1741. else {
  1742. if (self.blacklistFlag == YES) {
  1743. [self MBPShow:@"您已被老师禁言"];
  1744. return;
  1745. }
  1746. if (self.isCreaterInRoom == NO) {
  1747. [self MBPShow:@"老师不在直播间"];
  1748. return;
  1749. }
  1750. if (self.isPauseLive == YES) {
  1751. [self MBPShow:@"老师暂停中,暂时无法接受邀请"];
  1752. return;
  1753. }
  1754. if (self.enableSeat) {
  1755. // 显示弹窗
  1756. [self displaySeatApplyView];
  1757. }
  1758. else {
  1759. [self MBPShow:@"老师关闭连麦申请"];
  1760. }
  1761. }
  1762. }
  1763. break;
  1764. case LIVEROOMACTION_SHOP:
  1765. {
  1766. [self showCartAlert];
  1767. }
  1768. break;
  1769. case LIVEROOMACTION_LIKE:
  1770. {
  1771. if (self.isCreaterInRoom) {
  1772. [self praiseBtnPressed];
  1773. }
  1774. else {
  1775. [self MBPShow:@"老师不在直播间"];
  1776. }
  1777. }
  1778. break;
  1779. // case LIVEROOMACTION_SHARE:
  1780. // {
  1781. // [self showShareAlert];
  1782. // }
  1783. // break;
  1784. default:
  1785. break;
  1786. }
  1787. }
  1788. - (void)showShareAlert {
  1789. NSString *qrUrl = [NSString stringWithFormat:@"%@%@?id=%@&userId=%@",WEBHOST,@"/#/shareLiveRoom",self.roomId,UserDefault(UIDKey)];
  1790. self.shareView = [ShareLiveDisplayView shareInstance];
  1791. [self.shareView configWithTeacherAvatar:self.createrAvatal teacherName:self.createrName qrUrl:qrUrl];
  1792. MJWeakSelf;
  1793. [self.shareView shareActionCallback:^(SHARETYPE type) {
  1794. UIImage *image = [UIImage getCurrentViewShot:weakSelf.shareView.containerView];
  1795. if (type == SHARETYPE_SAVE) {
  1796. [weakSelf savePicWithImage:image];
  1797. }
  1798. else {
  1799. [weakSelf showShareUI:image];
  1800. }
  1801. }];
  1802. [self.shareView showShareViewInView:self.view];
  1803. }
  1804. - (void)displaySeatApplyView {
  1805. [self.applyView showInView:self.view];
  1806. }
  1807. - (void)displayApplyingView {
  1808. [self.applyingView showInView:self.view];
  1809. }
  1810. - (void)showCartAlert {
  1811. [self.cardView showViewInView:self.view];
  1812. }
  1813. - (void)quitAction {
  1814. if (self.micStatus == MICSTATUS_CONNECTING) {
  1815. MJWeakSelf;
  1816. self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"连麦中,是否退出房间?" leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{
  1817. } confirm:^{
  1818. [weakSelf quitRoom];
  1819. }];
  1820. }
  1821. else {
  1822. [self quitRoom];
  1823. }
  1824. }
  1825. - (void)quitRoom {
  1826. KSDragWindowManager *manager = [KSDragWindowManager sharedManager];
  1827. if (manager.hasShowWindow) {
  1828. [manager resignDragWindow];
  1829. }
  1830. // 清理视图
  1831. [self clearRemoteContainer];
  1832. // 退出房间
  1833. [self exitRoom];
  1834. [self.navigationController dismissViewControllerAnimated:YES completion:nil];
  1835. }
  1836. - (RCRTCEngine *)engine {
  1837. if (!_engine) {
  1838. _engine = [RCRTCEngine sharedInstance];
  1839. [_engine setStatusReportDelegate:self];
  1840. [_engine setDelegate:self];
  1841. }
  1842. return _engine;
  1843. }
  1844. - (NSMutableArray<KSLiveStreamVideo *> *)streamVideos {
  1845. if (!_streamVideos) {
  1846. _streamVideos = [NSMutableArray array];
  1847. }
  1848. return _streamVideos;
  1849. }
  1850. - (KSLiveStreamVideo *)localVideo {
  1851. if (!_localVideo) {
  1852. _localVideo = [KSLiveStreamVideo LocalStreamVideo];
  1853. }
  1854. return _localVideo;
  1855. }
  1856. - (UIView *)messageContentView {
  1857. if (!_messageContentView) {
  1858. _messageContentView = [[UIView alloc] init];
  1859. [_messageContentView setBackgroundColor: [UIColor clearColor]];
  1860. }
  1861. return _messageContentView;
  1862. }
  1863. - (NSMutableArray<KSRCMessageModel *> *)conversationDataRepository {
  1864. if (!_conversationDataRepository) {
  1865. _conversationDataRepository = [NSMutableArray array];
  1866. }
  1867. return _conversationDataRepository;
  1868. }
  1869. - (KSChatInputBarControl *)inputBar {
  1870. if (!_inputBar) {
  1871. _inputBar = [[KSChatInputBarControl alloc] initWithStatus:KSBottomBarStatusDefault];
  1872. [_inputBar setDelegate:self];
  1873. }
  1874. return _inputBar;
  1875. }
  1876. - (UITableView *)conversationMessageTableView {
  1877. if (!_conversationMessageTableView) {
  1878. _conversationMessageTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
  1879. _conversationMessageTableView.backgroundColor = [UIColor clearColor];
  1880. _conversationMessageTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
  1881. _conversationMessageTableView.dataSource = self;
  1882. _conversationMessageTableView.delegate = self;
  1883. _conversationMessageTableView.rowHeight = UITableViewAutomaticDimension;
  1884. _conversationMessageTableView.estimatedRowHeight = 40.0f;
  1885. _conversationMessageTableView.showsVerticalScrollIndicator = NO;
  1886. [_conversationMessageTableView registerClass:[KSChatroomTextCell class] forCellReuseIdentifier:@"KSChatroomTextCell"];
  1887. }
  1888. return _conversationMessageTableView;
  1889. }
  1890. - (SeatContentView *)seatContainer {
  1891. if (!_seatContainer) {
  1892. _seatContainer = [[SeatContentView alloc] init];
  1893. }
  1894. return _seatContainer;
  1895. }
  1896. - (KSLiveEmptyView *)emptyView {
  1897. if (!_emptyView) {
  1898. _emptyView = [[KSLiveEmptyView alloc] init];
  1899. _emptyView.backgroundColor = [UIColor clearColor];
  1900. BOOL isPhoneLive = YES;
  1901. [_emptyView dispPlayViewIsPhoneLive:isPhoneLive];
  1902. }
  1903. return _emptyView;
  1904. }
  1905. - (KSLiveEndView *)endView {
  1906. if (!_endView) {
  1907. _endView = [[KSLiveEndView alloc] init];
  1908. _endView.backgroundColor = HexRGB(0x25292e);
  1909. BOOL isPhoneLive = YES;
  1910. [_endView dispPlayViewIsPhoneLive:isPhoneLive];
  1911. }
  1912. return _endView;
  1913. }
  1914. - (KSLiveBadNetView *)netBadView {
  1915. if (!_netBadView) {
  1916. _netBadView = [[KSLiveBadNetView alloc] init];
  1917. BOOL isPhoneLive = YES;
  1918. [_netBadView dispPlayViewIsPhoneLive:isPhoneLive];
  1919. MJWeakSelf;
  1920. [_netBadView configViewWithImageName:@"liveVideo_close" desc:@"当前网络状态不佳" callback:^{
  1921. [weakSelf resetAllConnection];
  1922. }];
  1923. }
  1924. return _netBadView;
  1925. }
  1926. - (LOTAnimationView *)animationView {
  1927. if (!_animationView) {
  1928. _animationView = [LOTAnimationView animationWithFilePath:[[NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"LiveCard" ofType:@"bundle"]] pathForResource:@"cardAnimation" ofType:@"json"]];
  1929. _animationView.loopAnimation = YES;
  1930. }
  1931. return _animationView;
  1932. }
  1933. - (void)resetNetTips {
  1934. self.lostStartTime = 0;
  1935. self.isBitError = NO;
  1936. self.bitErrorTime = 0;
  1937. self.isLostPackage = NO;
  1938. [self hideTipsView];
  1939. }
  1940. // 黑名单
  1941. - (void)setBlacklistFlag:(BOOL)blacklistFlag {
  1942. _blacklistFlag = blacklistFlag;
  1943. [self refreshBottomChatStatus];
  1944. }
  1945. - (void)refreshBottomChatStatus {
  1946. if (self.bottomView) {
  1947. NSString *tipsTitle = self.blacklistFlag ? @"您已被老师禁言" : @"快来互动吧!";
  1948. self.bottomView.tipsLabel.text = tipsTitle;
  1949. }
  1950. }
  1951. - (void)resetAllConnection {
  1952. [self resetNetTips];
  1953. // 清理视图
  1954. [self.streamVideos removeAllObjects];
  1955. [self clearVideoViewSource];
  1956. [self removeSeatContainer];
  1957. // 立刻房间后重新加入房间
  1958. // 退出房间
  1959. [self.engine.defaultAudioStream setMicrophoneDisable:YES];
  1960. __weak typeof(self) weakSelf = self;
  1961. [self.engine leaveRoom:^(BOOL isSuccess, RCRTCCode code) {
  1962. __strong typeof(weakSelf) strongSelf = weakSelf;
  1963. if (code != RCRTCCodeSuccess) {
  1964. // 退出失败
  1965. }
  1966. // 3.加入RTC房间
  1967. [strongSelf joinLiveRoom];
  1968. }];
  1969. }
  1970. - (void)sendDownSeatMessage {
  1971. KSLiveChatroomDownSeat *downSeatMessage = [[KSLiveChatroomDownSeat alloc] init];
  1972. downSeatMessage.audienceId = UserDefault(IM_USERID);
  1973. downSeatMessage.audienceName = UserDefault(NicknameKey);
  1974. MJWeakSelf;
  1975. [self sendMessage:downSeatMessage displayMessage:NO callback:^(BOOL success) {
  1976. weakSelf.micStatus = MICSTATUS_NOMAL;
  1977. }];
  1978. }
  1979. - (LiveroomTimeManager *)timeManager {
  1980. if (!_timeManager) {
  1981. _timeManager = [[LiveroomTimeManager alloc] initWithDelegate:self];
  1982. }
  1983. return _timeManager;
  1984. }
  1985. - (NSInteger)getCloseTime {
  1986. NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
  1987. [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  1988. formatter.timeZone = [NSTimeZone systemTimeZone];
  1989. NSDate *currentDate = [NSDate date];
  1990. NSDate *expireDate = [formatter dateFromString:self.liveEndTime];
  1991. NSTimeInterval timeInterval = [expireDate timeIntervalSinceDate:currentDate];
  1992. return self.expiredMinute * 60 + timeInterval;
  1993. }
  1994. - (KSShopCardView *)cardView {
  1995. if (!_cardView) {
  1996. _cardView = [KSShopCardView shareInstance];
  1997. MJWeakSelf;
  1998. [_cardView clickAction:^(CART_TYPE type) {
  1999. [weakSelf displayTeacherInfo:type];
  2000. }];
  2001. }
  2002. return _cardView;
  2003. }
  2004. // tabs= practice | 陪练课 live | 直播课 video | 视频课 music | 乐谱 跳转到对应tab页
  2005. - (void)displayTeacherInfo:(CART_TYPE)type {
  2006. [self sendRushMessage];
  2007. RCRTCVideoView *videoView = nil;
  2008. for (UIView *view in self.videoView.subviews) {
  2009. if ([view isKindOfClass:[RCRTCVideoView class]]) {
  2010. videoView = (RCRTCVideoView *)view;
  2011. }
  2012. }
  2013. if (videoView) {
  2014. _hasShowSuspendView = YES;
  2015. KSDragWindowManager *manager = [KSDragWindowManager sharedManager];
  2016. [manager initDragWindowWithPhoneLive:YES];
  2017. manager.rootVC = (CustomNavViewController *)self.navigationController;
  2018. [videoView removeFromSuperview];
  2019. [manager.dragWindow.containerView addSubview:videoView];
  2020. [videoView mas_updateConstraints:^(MASConstraintMaker *make) {
  2021. make.left.right.top.bottom.mas_equalTo(manager.dragWindow.containerView);
  2022. }];
  2023. [manager.dragWindow bringSubviewToFront:manager.dragWindow.cancelButton];
  2024. }
  2025. NSString *tabs = @"";
  2026. switch (type) {
  2027. case CART_TYPE_NONE:
  2028. {
  2029. tabs = @"";
  2030. }
  2031. break;
  2032. case CART_TYPE_ACCOMPANY:
  2033. {
  2034. tabs = @"practice";
  2035. }
  2036. break;
  2037. case CART_TYPE_LIVE:
  2038. {
  2039. tabs = @"live";
  2040. }
  2041. break;
  2042. case CART_TYPE_VIDEO:
  2043. {
  2044. tabs = @"video";
  2045. }
  2046. break;
  2047. case CART_TYPE_MUSIC:
  2048. {
  2049. tabs = @"music";
  2050. }
  2051. break;
  2052. default:
  2053. break;
  2054. }
  2055. NSString *url = @"";
  2056. if ([NSString isEmptyString:tabs]) {
  2057. url = [NSString stringWithFormat:@"%@%@%@", WEBHOST,@"/#/teacherHome?teacherId=",self.createrId];
  2058. }
  2059. else {
  2060. url = [NSString stringWithFormat:@"%@%@%@&tabs=%@", WEBHOST,@"/#/teacherHome?teacherId=",self.createrId,tabs];
  2061. }
  2062. KSLiveWebViewController *ctrl = [[KSLiveWebViewController alloc] init];
  2063. ctrl.url = url;
  2064. [self.navigationController pushViewController:ctrl animated:YES];
  2065. }
  2066. - (void)sendRushMessage {
  2067. // KSRCShopRushMessage *rushMessage = [[KSRCShopRushMessage alloc] init];
  2068. // rushMessage.userId = UserDefault(UIDKey);
  2069. // rushMessage.userName = UserDefault(NicknameKey);
  2070. // [self sendMessage:rushMessage displayMessage:NO callback:^(BOOL success) {
  2071. //
  2072. // }];
  2073. }
  2074. - (void)showAnimationView:(BOOL)isJoinRoom showMessag:(NSString *)message {
  2075. if (self.enterAnimationView && self.enterAnimationView.isShow) {
  2076. return;
  2077. }
  2078. else {
  2079. ANIMATIONTYPE type = isJoinRoom ? ANIMATIONTYPE_JOIN : ANIMATIONTYPE_RUSH;
  2080. self.enterAnimationView = [[LiveAnimationView alloc] initWithTitle:message animationType:type];
  2081. [self.view addSubview:self.enterAnimationView];
  2082. [self.enterAnimationView mas_makeConstraints:^(MASConstraintMaker *make) {
  2083. make.left.right.mas_equalTo(self.view);
  2084. make.width.mas_equalTo(KPortraitWidth);
  2085. make.height.mas_equalTo(24.0f);
  2086. make.bottom.mas_equalTo(self.messageContentView.mas_top).offset(-6);
  2087. }];
  2088. [self.view bringSubviewToFront:self.enterAnimationView];
  2089. MJWeakSelf;
  2090. [self.enterAnimationView startAnimationEndCallback:^{
  2091. weakSelf.enterAnimationView.isShow = NO;
  2092. weakSelf.enterAnimationView = nil;
  2093. }];
  2094. }
  2095. }
  2096. - (void)dealloc {
  2097. [[NSNotificationCenter defaultCenter] removeObserver:self];
  2098. }
  2099. - (LiveApplyView *)applyView {
  2100. if (!_applyView) {
  2101. _applyView = [LiveApplyView shareInstance];
  2102. MJWeakSelf;
  2103. [_applyView applySeatCallback:^{
  2104. [weakSelf applySeatAction];
  2105. }];
  2106. }
  2107. return _applyView;
  2108. }
  2109. - (void)applySeatAction {
  2110. [self MBPShow:@"已发起连麦申请"];
  2111. [self sendSeatMessageApply:YES];
  2112. }
  2113. - (LiveApplyingView *)applyingView {
  2114. if (!_applyingView) {
  2115. _applyingView = [LiveApplyingView shareInstance];
  2116. NSString *avatar = UserDefaultObjectForKey(AvatarUrlKey);
  2117. [_applyingView.myAvatal sd_setImageWithURL:[NSURL URLWithString:[avatar getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:USERDEFAULT_LOGO]];
  2118. [_applyingView.teacherAvatal sd_setImageWithURL:[NSURL URLWithString:[self.createrAvatal getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:USERDEFAULT_LOGO]];
  2119. MJWeakSelf;
  2120. [_applyingView cancleApplyCallback:^{
  2121. [weakSelf cancleApplyAction];
  2122. }];
  2123. }
  2124. return _applyingView;
  2125. }
  2126. - (void)cancleApplyAction {
  2127. MJWeakSelf;
  2128. self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"连麦申请中,确认取消连麦申请吗?" leftButtonTitle:@"取消" rightTitle:@"确定" inView:self.view cancel:^{
  2129. } confirm:^{
  2130. [weakSelf.applyingView hideView];
  2131. [weakSelf sendSeatMessageApply:NO];
  2132. }];
  2133. }
  2134. - (void)showShareUI:(UIImage *)image {
  2135. [KSUMShareManager shareInstanceWithImage:image url:@"" shareTitle:@"" descMessage:@"" shareType:KSSHARETYPE_IMAGE showInView:self callback:^(BOOL isSuccess, NSString * _Nonnull descMessage) {
  2136. if (isSuccess) {
  2137. [self.shareView hideView];
  2138. }
  2139. else {
  2140. }
  2141. [self MBPShow:descMessage];
  2142. }];
  2143. }
  2144. - (void)savePicWithImage:(UIImage *)image {
  2145. // 判断相册权限
  2146. PREMISSIONTYPE albumEnable = [RecordCheckManager checkPhotoLibraryPremissionAvaiable:NO showInView:nil];
  2147. if (albumEnable == PREMISSIONTYPE_YES) { // 如果有权限
  2148. [[TZImageManager manager] savePhotoWithImage:image completion:^(PHAsset *asset, NSError *error) {
  2149. if (!error) {
  2150. [self .shareView hideView];
  2151. [self MBPShow:@"保存成功"];
  2152. }
  2153. else {
  2154. [self MBPShow:@"保存图片失败"];
  2155. }
  2156. }];
  2157. }
  2158. else {
  2159. if (albumEnable == PREMISSIONTYPE_NO) {
  2160. [self showAlertWithMessage:@"请开启相册访问权限" type:CHECKDEVICETYPE_CAMREA];
  2161. }
  2162. }
  2163. }
  2164. - (void)showAlertWithMessage:(NSString *)message type:(CHECKDEVICETYPE)deviceType {
  2165. [KSPremissionAlert shareInstanceDisplayImage:deviceType message:message showInView:self.view cancel:^{
  2166. } confirm:^{
  2167. [self openSettingView];
  2168. }];
  2169. }
  2170. - (void)openSettingView {
  2171. if (@available(iOS 10, *)) {
  2172. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString] options:@{} completionHandler:nil];
  2173. } else {
  2174. [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
  2175. }
  2176. }
  2177. @end