|
@@ -0,0 +1,2474 @@
|
|
|
+//
|
|
|
+// TXLiveRoomViewController.m
|
|
|
+// TeacherDaya
|
|
|
+//
|
|
|
+// Created by 王智 on 2023/3/6.
|
|
|
+// Copyright © 2023 DayaMusic. All rights reserved.
|
|
|
+//
|
|
|
+
|
|
|
+#import "TXLiveRoomViewController.h"
|
|
|
+#import "LivePreviewBodyView.h"
|
|
|
+#import "KSEnterLiveroomManager.h"
|
|
|
+#import "UserInfoManager.h"
|
|
|
+#import "KSChatInputBarControl.h"
|
|
|
+#import "TXLiveRoomHeadView.h"
|
|
|
+#import "LiveRoomBottomView.h"
|
|
|
+#import "SeatTipsView.h"
|
|
|
+#import "TXLiveChatListCell.h"
|
|
|
+#import "LiveRoomAlertView.h"
|
|
|
+#import "LiveMoreDisplayView.h"
|
|
|
+#import "LiveAnimationView.h"
|
|
|
+#import "TXLiveSeatActionView.h"
|
|
|
+
|
|
|
+#import "TXLiteAVSDK_Professional/TXLiteAVSDK.h"
|
|
|
+#import <ImSDK_Plus/ImSDK_Plus.h>
|
|
|
+#import "TXIMLinsenter.h"
|
|
|
+
|
|
|
+#import "TXLiveMessageModel.h"
|
|
|
+#import "TXLiveURLUtils.h"
|
|
|
+#import "TXLiveMessageCenter.h"
|
|
|
+#import "TXSeatContainerView.h"
|
|
|
+#import "TXUISeatMember.h"
|
|
|
+#import "TXLiveRoomTimeManager.h"
|
|
|
+#import "LiveRoomConfirmAlert.h"
|
|
|
+#import "LiveModuleService.h"
|
|
|
+#import "KSShareChooseViewController.h"
|
|
|
+
|
|
|
+typedef NS_ENUM(NSInteger, LIVEPAGE) {
|
|
|
+ LIVEPAGE_PREVIEW,
|
|
|
+ LIVEPAGE_LIVE,
|
|
|
+};
|
|
|
+
|
|
|
+@interface TXLiveRoomViewController ()<KSChatInputBarControlDelegate,UITableViewDataSource,UITableViewDelegate,TRTCCloudDelegate,UIGestureRecognizerDelegate,TXLiveRoomTimeManagerDelegate>
|
|
|
+
|
|
|
+@property (nonatomic, assign) LIVEPAGE pageType;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL isPauseLive; // 是否暂停直播
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL isOtherLogin; // 是否被顶掉
|
|
|
+
|
|
|
+@property (nonatomic, strong) LivePreviewBodyView *previewPageView;
|
|
|
+
|
|
|
+@property (nonatomic, strong) UIView *preVideoView;
|
|
|
+
|
|
|
+// rtc
|
|
|
+@property (nonatomic, strong) TRTCCloud *trtcCloud;
|
|
|
+
|
|
|
+#pragma mark ------- 直播房间信息
|
|
|
+
|
|
|
+/// 主讲人id
|
|
|
+@property (nonatomic, strong) NSString *createrId;
|
|
|
+/// 主讲人名称
|
|
|
+@property (nonatomic, strong) NSString *createrName;
|
|
|
+/// 主讲人头像
|
|
|
+@property (nonatomic, strong) NSString *createrAvatal;
|
|
|
+
|
|
|
+// 是否禁止连麦
|
|
|
+@property (nonatomic, assign) BOOL enableSeat;
|
|
|
+// 是否禁止聊天
|
|
|
+@property (nonatomic, assign) BOOL enableChat;
|
|
|
+// 是否允许点赞
|
|
|
+@property (nonatomic, assign) BOOL enableLike;
|
|
|
+
|
|
|
+@property (nonatomic, assign) NSInteger likeCount;
|
|
|
+/// 房间人数
|
|
|
+@property (nonatomic, assign) NSInteger totalCount;
|
|
|
+
|
|
|
+#pragma mark ---- Live page
|
|
|
+
|
|
|
+@property (nonatomic, strong) UIView *livePageView;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL isImConnected;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL hasJoinRoomSuccess;
|
|
|
+
|
|
|
+@property (nonatomic, strong) UIView *localVideoView;
|
|
|
+
|
|
|
+@property (nonatomic, strong) UIView *liveVideoView;
|
|
|
+
|
|
|
+@property (nonatomic, strong) TXLiveRoomHeadView *headView;
|
|
|
+
|
|
|
+@property (nonatomic, copy) LiveRoomBottomView *bottomView;
|
|
|
+
|
|
|
+/// 连麦的视图
|
|
|
+@property (nonatomic, strong) TXSeatContainerView *seatContainer;
|
|
|
+// 聊天UI
|
|
|
+/*!
|
|
|
+ 消息列表CollectionView和输入框都在这个view里
|
|
|
+ */
|
|
|
+@property(nonatomic, strong) UIView *messageContentView;
|
|
|
+
|
|
|
+/*!
|
|
|
+ 会话页面的TableView
|
|
|
+ */
|
|
|
+@property (nonatomic, strong) UITableView *conversationMessageTableView;
|
|
|
+
|
|
|
+/*!
|
|
|
+ 聊天内容的消息Cell数据模型的数据源
|
|
|
+
|
|
|
+ @discussion 数据源中存放的元素为消息Cell的数据模型,即RCDLiveMessageModel对象。
|
|
|
+ */
|
|
|
+@property(nonatomic, strong) NSMutableArray<TXLiveMessageModel *> *conversationDataRepository;
|
|
|
+/**
|
|
|
+ 输入工具栏
|
|
|
+ */
|
|
|
+@property(nonatomic,strong) KSChatInputBarControl *inputBar;
|
|
|
+/**
|
|
|
+ * 是否需要滚动到底部
|
|
|
+ */
|
|
|
+@property(nonatomic, assign) BOOL isNeedScrollToButtom;
|
|
|
+
|
|
|
+@property (nonatomic, strong) UIView *seatCtrlView;
|
|
|
+
|
|
|
+@property (nonatomic, strong) TXLiveSeatActionView *seatActionView; // 连麦控制view
|
|
|
+
|
|
|
+@property (nonatomic, strong) NSMutableArray *seatApplyArray; // 申请连麦数组
|
|
|
+
|
|
|
+// 远端连麦用户
|
|
|
+@property (nonatomic, strong) NSMutableArray *remoteMemberArray;
|
|
|
+
|
|
|
+/// 连麦申请统计数据
|
|
|
+@property (nonatomic, strong) SeatTipsView *seatApplyTips;
|
|
|
+
|
|
|
+/// 提示窗
|
|
|
+@property (nonatomic, strong) LiveRoomAlertView *alertView;
|
|
|
+
|
|
|
+
|
|
|
+// SEI消息定时器
|
|
|
+@property (nonatomic, strong) dispatch_source_t SEITimer;
|
|
|
+
|
|
|
+@property (nonatomic, strong) LiveMoreDisplayView *moreView;
|
|
|
+
|
|
|
+@property (nonatomic, strong) LiveAnimationView *animationView;
|
|
|
+
|
|
|
+@property (nonatomic, strong) UITapGestureRecognizer *gesture;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL needPublishStream;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL micEnable;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL needSendOpenMsg;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL muteRoomMic;
|
|
|
+
|
|
|
+@property (nonatomic, strong) TXLiveRoomTimeManager *timeManager;
|
|
|
+
|
|
|
+@property (nonatomic, assign) BOOL isQuitRoom; // 是否正在退出房间
|
|
|
+
|
|
|
+@property (nonatomic, strong) LiveRoomConfirmAlert *closeAlert;
|
|
|
+
|
|
|
+@property (nonatomic, assign) NSTimeInterval lastSendMsgTime;
|
|
|
+
|
|
|
+@property (nonatomic, copy) TXLiveRoomBackAction callback;
|
|
|
+
|
|
|
+@end
|
|
|
+
|
|
|
+@implementation TXLiveRoomViewController
|
|
|
+
|
|
|
+- (void)backRefreshCallback:(TXLiveRoomBackAction)callback {
|
|
|
+ if (callback) {
|
|
|
+ self.callback = callback;
|
|
|
+ }
|
|
|
+}
|
|
|
+- (instancetype)init {
|
|
|
+ self = [super init];
|
|
|
+ if (self) {
|
|
|
+ [self default3AConfig];
|
|
|
+ }
|
|
|
+ return self;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)default3AConfig {
|
|
|
+ self.ANS = 0;
|
|
|
+ self.AEC = 100;
|
|
|
+ self.AGC = 0;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)viewDidLoad {
|
|
|
+ [super viewDidLoad];
|
|
|
+ // Do any additional setup after loading the view.
|
|
|
+ self.ks_prefersNavigationBarHidden = YES;
|
|
|
+ self.isOtherLogin = NO;
|
|
|
+ [self registerOtherLoginNotice];
|
|
|
+ [self configUI];
|
|
|
+ [self configEngine];
|
|
|
+ self.pageType = LIVEPAGE_PREVIEW;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)registerOtherLoginNotice {
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomLogOut) name:@"liveroomLogout" object:nil];
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(liveroomQuit) name:@"liveroomQuit" object:nil];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewWillAppear:(BOOL)animated {
|
|
|
+ [super viewWillAppear:animated];
|
|
|
+ [UIApplication sharedApplication].idleTimerDisabled = YES;
|
|
|
+ [IQKeyboardManager sharedManager].enableAutoToolbar = NO;
|
|
|
+ [IQKeyboardManager sharedManager].enable = NO;
|
|
|
+ [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)viewWillDisappear:(BOOL)animated {
|
|
|
+ [super viewWillDisappear:animated];
|
|
|
+ [UIApplication sharedApplication].idleTimerDisabled = NO;
|
|
|
+ [IQKeyboardManager sharedManager].enableAutoToolbar = YES;
|
|
|
+ [IQKeyboardManager sharedManager].enable = YES;
|
|
|
+ if (@available(iOS 13.0, *)) {
|
|
|
+ [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDarkContent;
|
|
|
+ } else {
|
|
|
+ // Fallback on earlier versions
|
|
|
+ [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
|
|
|
+ }
|
|
|
+ if (_timeManager) {
|
|
|
+ [self.timeManager stopDurationTimer];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)configUI {
|
|
|
+ self.previewPageView = [LivePreviewBodyView shareInstance];
|
|
|
+ [self.view addSubview:self.previewPageView];
|
|
|
+ [self.previewPageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.bottom.top.mas_equalTo(self.view);
|
|
|
+ }];
|
|
|
+ MJWeakSelf;
|
|
|
+ [self.previewPageView previewOperationCallback:^(PREVIEWLIVEACTION action) {
|
|
|
+ [weakSelf previewOperationAction:action];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)configEngine {
|
|
|
+ if (!self.preVideoView) {
|
|
|
+ self.preVideoView = [[UIView alloc] init];
|
|
|
+ [self.previewPageView.videoView addSubview:self.preVideoView];
|
|
|
+ [self.preVideoView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.top.bottom.right.mas_equalTo(self.previewPageView.videoView);
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ [self.trtcCloud startLocalPreview:YES view:self.preVideoView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)previewOperationAction:(PREVIEWLIVEACTION)action {
|
|
|
+ switch (action) {
|
|
|
+ case PREVIEWLIVEACTION_BACK: // 返回
|
|
|
+ {
|
|
|
+ if (self.isPauseLive) { // 退出RTC房间
|
|
|
+ [self quitRoomBackPreView:NO];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self.trtcCloud stopLocalPreview];
|
|
|
+ self.preVideoView = nil;
|
|
|
+ [self.trtcCloud stopPublishing];
|
|
|
+ [self.trtcCloud exitRoom];
|
|
|
+ [self.navigationController dismissViewControllerAnimated:YES completion:nil];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case PREVIEWLIVEACTION_SWITCH: // 切换
|
|
|
+ {
|
|
|
+ [self switchCamera];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case PREVIEWLIVEACTION_BEAUTY: // 美颜
|
|
|
+ {
|
|
|
+
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case PREVIEWLIVEACTION_OPEN: // 进入直播间
|
|
|
+ {
|
|
|
+ [self showLiveView];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case PREVIEWLIVEACTION_SHARE:
|
|
|
+ {
|
|
|
+ [self shareLiveRoomMessage];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)shareLiveRoomMessage {
|
|
|
+ // 选择群组分享
|
|
|
+
|
|
|
+ NSMutableDictionary *parm = [NSMutableDictionary dictionary];
|
|
|
+ [parm setValue:@"TC_CHATSHARE_LIVE" forKey:@"businessID"];
|
|
|
+ [parm setValue:@"TXLiveShareCell" forKey:@"TMessageCell_Name"];
|
|
|
+ [parm setValue:@"TXLiveShareMessage" forKey:@"TMessageCell_Name"];
|
|
|
+
|
|
|
+ NSString *teacherName = UserDefaultObjectForKey(NicknameKey);
|
|
|
+ if ([NSString isEmptyString:teacherName]) {
|
|
|
+ teacherName = [NSString stringWithFormat:@"游客%@",UserDefaultObjectForKey(UIDKey)];
|
|
|
+ }
|
|
|
+ [parm setValue:self.roomId forKey:@"roomUID"];
|
|
|
+ [parm setValue:teacherName forKey:@"teacherName"];
|
|
|
+ [parm setValue:UserDefaultObjectForKey(AvatarUrlKey) forKey:@"teacherAvatar"];
|
|
|
+ [parm setValue:self.liveContent forKey:@"liveDescMessage"];
|
|
|
+
|
|
|
+ NSData *data = [NSJSONSerialization dataWithJSONObject:parm options:0 error:nil];
|
|
|
+
|
|
|
+
|
|
|
+ KSShareChooseViewController *chooseCtrl = [[KSShareChooseViewController alloc] init];
|
|
|
+ chooseCtrl.msgData = data;
|
|
|
+ [self.navigationController pushViewController:chooseCtrl animated:YES];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showLiveView {
|
|
|
+ if (self.isPauseLive) { // 直接切换页面
|
|
|
+ self.pageType = LIVEPAGE_LIVE;
|
|
|
+ [self.view addSubview:self.livePageView];
|
|
|
+ [self.livePageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.top.bottom.mas_equalTo(self.view);
|
|
|
+ }];
|
|
|
+ // 开启推流
|
|
|
+ [self startPublishStream];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self showhud];
|
|
|
+ MJWeakSelf;
|
|
|
+ [KSEnterLiveroomManager queryLiveroomConfig:self.roomId callback:^(NSDictionary * _Nullable parm) {
|
|
|
+ [weakSelf removehub];
|
|
|
+ if (parm != nil) {
|
|
|
+ [weakSelf setupLiveroomConfig:parm];
|
|
|
+ [weakSelf createLivePageViewDisplay];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [weakSelf previewBackAction];
|
|
|
+ }
|
|
|
+ }];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)startPublishStream {
|
|
|
+ self.pageType = LIVEPAGE_LIVE;
|
|
|
+ self.isPauseLive = NO;
|
|
|
+ [self publishLocalStream];
|
|
|
+ [self countMemberCount];
|
|
|
+ [self.timeManager requestCousreTimeConfig];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)publishLocalStream {
|
|
|
+
|
|
|
+ TRTCParams *parms = [[TRTCParams alloc] init];
|
|
|
+ parms.sdkAppId = CONFIG_TXSDKAPPID;
|
|
|
+ parms.userSig = self.userSig;
|
|
|
+ parms.role = TRTCRoleAnchor;
|
|
|
+ parms.strRoomId = self.roomId;
|
|
|
+ parms.userId = UserDefault(IM_USERID);
|
|
|
+ self.trtcCloud.delegate = self;
|
|
|
+ [self config3AParams];
|
|
|
+ [self.trtcCloud startLocalPreview:YES view:self.liveVideoView];
|
|
|
+ [self.trtcCloud enterRoom:parms appScene:TRTCAppSceneLIVE];
|
|
|
+ [self.trtcCloud muteLocalAudio:!self.micEnable];
|
|
|
+ [self configVideoAndAudioSetting];
|
|
|
+ NSString *streamId = [NSString stringWithFormat:@"%@_%@", self.roomId, UserDefault(IM_USERID)];
|
|
|
+ [self.trtcCloud startPublishing:streamId type:TRTCVideoStreamTypeBig];
|
|
|
+ [self sendOpenLiveMessage];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)extracted:(TXLiveMessageOpenLive *)openLiveMsg {
|
|
|
+ [self sendCustomMessage:openLiveMsg priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)sendOpenLiveMessage {
|
|
|
+ if (TXIM_LINSENTER.loginIMSuccess) {
|
|
|
+ // 发送开播消息
|
|
|
+ TXLiveMessageOpenLive *openLiveMsg = [[TXLiveMessageOpenLive alloc] init];
|
|
|
+ openLiveMsg.userInfo = [self getCurrentUser];
|
|
|
+ [self extracted:openLiveMsg];
|
|
|
+
|
|
|
+ [self syncLiveStatus:YES];
|
|
|
+
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.needSendOpenMsg = YES;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)configVideoAndAudioSetting {
|
|
|
+ [self.trtcCloud startLocalAudio:TRTCAudioQualityMusic];
|
|
|
+
|
|
|
+ TRTCVideoEncParam *videoEncParam = [[TRTCVideoEncParam alloc] init];
|
|
|
+ videoEncParam.videoFps = 15;
|
|
|
+ videoEncParam.resMode = TRTCVideoResolutionModePortrait;
|
|
|
+ videoEncParam.videoResolution = TRTCVideoResolution_960_540;
|
|
|
+ [self.trtcCloud setVideoEncoderParam:videoEncParam];
|
|
|
+}
|
|
|
+
|
|
|
+// 配置3A参数
|
|
|
+- (void)config3AParams {
|
|
|
+ [self configANSParm:self.ANS];
|
|
|
+ [self configAECParm:self.AEC];
|
|
|
+ [self configAGCParm:self.AGC];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 背景音抑制
|
|
|
+- (void)configANSParm:(NSInteger)number {
|
|
|
+ NSMutableDictionary *dic = [NSMutableDictionary dictionary];
|
|
|
+ [dic setValue:@"enableAudioANS" forKey:@"api"];
|
|
|
+ NSMutableDictionary *subDic = [NSMutableDictionary dictionary];
|
|
|
+ if (number > 0) {
|
|
|
+ [subDic setValue:@(1) forKey:@"enable"];
|
|
|
+ [subDic setValue:@(number) forKey:@"level"];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [subDic setValue:@(0) forKey:@"enable"];
|
|
|
+ [subDic setValue:@(0) forKey:@"level"];
|
|
|
+ }
|
|
|
+ [dic setValue:subDic forKey:@"params"];
|
|
|
+ NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:nil];
|
|
|
+ NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
|
+ [self.trtcCloud callExperimentalAPI:jsonString];
|
|
|
+}
|
|
|
+
|
|
|
+// 回声消除
|
|
|
+- (void)configAECParm:(NSInteger)number {
|
|
|
+ NSMutableDictionary *dic = [NSMutableDictionary dictionary];
|
|
|
+ [dic setValue:@"enableAudioAEC" forKey:@"api"];
|
|
|
+ NSMutableDictionary *subDic = [NSMutableDictionary dictionary];
|
|
|
+ if (number > 0) {
|
|
|
+ [subDic setValue:@(1) forKey:@"enable"];
|
|
|
+ [subDic setValue:@(number) forKey:@"level"];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [subDic setValue:@(0) forKey:@"enable"];
|
|
|
+ [subDic setValue:@(0) forKey:@"level"];
|
|
|
+ }
|
|
|
+ [dic setValue:subDic forKey:@"params"];
|
|
|
+ NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:nil];
|
|
|
+ NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
|
+ [self.trtcCloud callExperimentalAPI:jsonString];
|
|
|
+}
|
|
|
+
|
|
|
+// 自动增益
|
|
|
+- (void)configAGCParm:(NSInteger)number {
|
|
|
+ NSMutableDictionary *dic = [NSMutableDictionary dictionary];
|
|
|
+ [dic setValue:@"enableAudioAGC" forKey:@"api"];
|
|
|
+ NSMutableDictionary *subDic = [NSMutableDictionary dictionary];
|
|
|
+ if (number > 0) {
|
|
|
+ [subDic setValue:@(1) forKey:@"enable"];
|
|
|
+ [subDic setValue:@(number) forKey:@"level"];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [subDic setValue:@(0) forKey:@"enable"];
|
|
|
+ [subDic setValue:@(0) forKey:@"level"];
|
|
|
+ }
|
|
|
+ [dic setValue:subDic forKey:@"params"];
|
|
|
+ NSData *data = [NSJSONSerialization dataWithJSONObject:dic options:kNilOptions error:nil];
|
|
|
+ NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
|
+ [self.trtcCloud callExperimentalAPI:jsonString];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)setupLiveroomConfig:(NSDictionary *)source {
|
|
|
+ self.createrId = [source ks_stringValueForKey:@"speakerId"];
|
|
|
+ self.createrName = [source ks_stringValueForKey:@"speakerName"];
|
|
|
+ self.createrAvatal = [source ks_stringValueForKey:@"speakerPic"];
|
|
|
+// self.likeCount = [source ks_integerValueForKey:@"likeNum"]; // 点赞数
|
|
|
+ NSInteger lookCount = [source ks_integerValueForKey:@"lookNum"];
|
|
|
+ self.totalCount = lookCount; // 观看人数
|
|
|
+ NSString *roomConfig = [source ks_stringValueForKey:@"roomConfig"];
|
|
|
+ NSData *jsonData = [roomConfig dataUsingEncoding:NSUTF8StringEncoding];
|
|
|
+ NSError *err;
|
|
|
+ NSDictionary *configDic = [NSJSONSerialization JSONObjectWithData:jsonData
|
|
|
+ options:NSJSONReadingMutableContainers
|
|
|
+ error:&err];
|
|
|
+ if (configDic) {
|
|
|
+ self.enableChat = ![configDic ks_boolValueForKey:@"whether_chat"];
|
|
|
+ self.enableSeat = ![configDic ks_boolValueForKey:@"whether_mic"];
|
|
|
+ self.enableLike = ![configDic ks_boolValueForKey:@"whether_like"];
|
|
|
+ }
|
|
|
+ self.userSig = [source ks_stringValueForKey:@"userSig"];
|
|
|
+
|
|
|
+ NSDictionary *liveRoomConfig = [source ks_dictionaryValueForKey:@"liveRoomConfig"];
|
|
|
+ if (liveRoomConfig) {
|
|
|
+ self.pushDomain = [liveRoomConfig ks_stringValueForKey:@"pushUrl"];
|
|
|
+ self.pushUrlKey = [liveRoomConfig ks_stringValueForKey:@"pushSecret"];
|
|
|
+ self.playDomain = [liveRoomConfig ks_stringValueForKey:@"playUrl"];
|
|
|
+ self.liveUrlKey = [liveRoomConfig ks_stringValueForKey:@"playSecret"];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)createLivePageViewDisplay {
|
|
|
+ self.micEnable = YES;
|
|
|
+ self.pageType = LIVEPAGE_LIVE;
|
|
|
+ self.isImConnected = TXIM_LINSENTER.isIMConnected;
|
|
|
+ [self registerNotification];
|
|
|
+ [self setupUI];
|
|
|
+ [self connectionService];
|
|
|
+ // 开启SEL消息定时器
|
|
|
+ [self startSEITimer];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ------- Live page
|
|
|
+
|
|
|
+- (void)startSEITimer {
|
|
|
+ if (self.SEITimer) {
|
|
|
+ dispatch_resume(self.SEITimer);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)StopSEITimer {
|
|
|
+ if (self.SEITimer) {
|
|
|
+ dispatch_suspend(self.SEITimer);
|
|
|
+ }
|
|
|
+}
|
|
|
+- (void)registerNotification {
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMessageNotification:) name:OnReceiveTXLiveMessageNotification object:nil];
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(IMConnetedCallback) name:@"TXIMConnected" object:nil];
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnterBackground) name:@"appEnterBackground" object:nil];
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appBecomeActive) name:@"appBecomeActive" object:nil];
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveGroupMessage:) name:OnReceiveTXLiveGroupNotification object:nil];
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveGroupCountMessage:) name:OnReceiveTXLiveGroupCountNotification object:nil];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)IMConnetedCallback {
|
|
|
+ if (self.isOtherLogin) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ dispatch_main_async_safe(^{
|
|
|
+ if (self.hasJoinRoomSuccess) { // 如果已发送进入消息
|
|
|
+ // 查询是否直播间开启
|
|
|
+ [LiveModuleService queryRoomMessage:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) {
|
|
|
+ if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
|
|
|
+ // 回调信息
|
|
|
+ MJWeakSelf;
|
|
|
+ [self notiferJoinSuccessToServiceCallback:^{
|
|
|
+ [weakSelf joinChatRoomAndLiveRoom];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self MBPShow:MESSAGEKEY];
|
|
|
+ [self quitRoomBackPreView:NO];
|
|
|
+ }
|
|
|
+ } faliure:^(NSError * _Nonnull error) {
|
|
|
+
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ if (self.needPublishStream) {
|
|
|
+ [self startPublishStream];
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+- (void)joinChatRoomAndLiveRoom {
|
|
|
+ [[V2TIMManager sharedInstance] joinGroup:self.roomId msg:nil succ:^{
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ [self delyJoinRTCRoom];
|
|
|
+ [self getGroupInfo];
|
|
|
+ });
|
|
|
+
|
|
|
+ } fail:^(int code, NSString *desc) {
|
|
|
+ NSLog(@"");
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)delyJoinRTCRoom {
|
|
|
+ [self resetSeatContainer];
|
|
|
+ [self.trtcCloud exitRoom];
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ [self publishLocalStream];
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ------ enter back ground
|
|
|
+- (void)appEnterBackground {
|
|
|
+ self.isEnterBackground = YES;
|
|
|
+ if (self.isPauseLive == NO) { // 暂停推流
|
|
|
+ [self pauseLiveActionBack:NO];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)appBecomeActive {
|
|
|
+ if (self.isEnterBackground && self.pageType == LIVEPAGE_LIVE && self.isOtherLogin == NO) {
|
|
|
+ if (TXIM_LINSENTER.isIMConnected) {
|
|
|
+ // 开启推流
|
|
|
+ [self startPublishStream];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.needPublishStream = YES;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ self.isEnterBackground = NO;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setupUI {
|
|
|
+ CGSize size = self.view.bounds.size;
|
|
|
+ [self.view addSubview:self.livePageView];
|
|
|
+ [self.livePageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.top.bottom.mas_equalTo(self.view);
|
|
|
+ }];
|
|
|
+
|
|
|
+ [self.livePageView addSubview:self.liveVideoView];
|
|
|
+ [self.liveVideoView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.top.bottom.right.mas_equalTo(self.livePageView);
|
|
|
+ }];
|
|
|
+
|
|
|
+ [self.livePageView addSubview:self.headView];
|
|
|
+
|
|
|
+ CGFloat headHeight = [TXLiveRoomHeadView getViewHeight];
|
|
|
+ [self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.mas_equalTo(self.livePageView);
|
|
|
+ make.height.mas_equalTo(headHeight);
|
|
|
+ if (@available(iOS 11.0, *)) {
|
|
|
+ make.top.mas_equalTo(self.livePageView.mas_safeAreaLayoutGuideTop).offset(10);
|
|
|
+ } else {
|
|
|
+ // Fallback on earlier versions
|
|
|
+ make.top.mas_equalTo(self.livePageView.mas_top).offset(10);
|
|
|
+ }
|
|
|
+ }];
|
|
|
+ [self.livePageView addSubview:self.messageContentView];
|
|
|
+ [self.livePageView addSubview:self.bottomView];
|
|
|
+
|
|
|
+ self.headView.boardcastName.text = [NSString returnNoNullStringWithString:self.createrName];
|
|
|
+ if (![NSString isEmptyString:self.createrAvatal]) {
|
|
|
+ [self.headView.boardcastAvatal sd_setImageWithURL:[NSURL URLWithString:[self.createrAvatal getUrlEndcodeString]] placeholderImage:[UIImage imageNamed:@"teacher_logo"]];
|
|
|
+ }
|
|
|
+ [self countLikeMessageCount];
|
|
|
+
|
|
|
+ [self.livePageView addSubview:self.messageContentView];
|
|
|
+ [self.livePageView addSubview:self.bottomView];
|
|
|
+
|
|
|
+ [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.mas_equalTo(self.livePageView);
|
|
|
+ if (@available(iOS 11.0, *)) {
|
|
|
+ make.bottom.mas_equalTo(self.livePageView.mas_safeAreaLayoutGuideBottom).offset(-5);
|
|
|
+ } else {
|
|
|
+ // Fallback on earlier versions
|
|
|
+ make.bottom.mas_equalTo(self.livePageView.mas_bottom).offset(-5);
|
|
|
+ }
|
|
|
+ make.height.mas_equalTo(44);
|
|
|
+ }];
|
|
|
+
|
|
|
+ [self.messageContentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.mas_equalTo(self.livePageView.mas_left);
|
|
|
+ make.right.mas_equalTo(self.livePageView.mas_right);
|
|
|
+ make.bottom.mas_equalTo(self.bottomView.mas_top).offset(-5);
|
|
|
+ make.height.mas_equalTo(180);
|
|
|
+ }];
|
|
|
+ [self.livePageView addSubview:self.inputBar];
|
|
|
+ [_inputBar setBackgroundColor: [UIColor whiteColor]];
|
|
|
+
|
|
|
+ [_inputBar setFrame:CGRectMake(0, kScreenHeight, size.width , 50)];
|
|
|
+ [_inputBar setHidden:YES];
|
|
|
+
|
|
|
+ [self.messageContentView addSubview:self.conversationMessageTableView];
|
|
|
+ [self.conversationMessageTableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.top.mas_equalTo(self.messageContentView);
|
|
|
+ make.width.mas_equalTo(KPortraitWidth);
|
|
|
+ make.bottom.mas_equalTo(self.messageContentView.mas_bottom);
|
|
|
+ }];
|
|
|
+
|
|
|
+ [self.livePageView addSubview:self.seatApplyTips];
|
|
|
+ [self.seatApplyTips hideView];
|
|
|
+ [self.seatApplyTips mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.bottom.mas_equalTo(self.bottomView.mas_top).offset(2);
|
|
|
+ make.height.mas_equalTo(20);
|
|
|
+ make.left.mas_equalTo(self.bottomView.mas_right).offset(-116);
|
|
|
+ }];
|
|
|
+
|
|
|
+ UITapGestureRecognizer *resetBottomTapGesture =[[UITapGestureRecognizer alloc]
|
|
|
+ initWithTarget:self
|
|
|
+ action:@selector(resetBottomGesture:)];
|
|
|
+ resetBottomTapGesture.delegate = self;
|
|
|
+ [self.livePageView addGestureRecognizer:resetBottomTapGesture];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)joinChatRoom {
|
|
|
+ if (TXIM_LINSENTER.loginIMSuccess == NO) {
|
|
|
+ [TXIM_LINSENTER TXIMLoginWithUserId:UserDefault(IM_USERID) sig:self.userSig callback:^(BOOL isSuccess, NSString * _Nullable msg) {
|
|
|
+ [self liveConversationJoin];
|
|
|
+ if (self.needSendOpenMsg) {
|
|
|
+ self.needSendOpenMsg = NO;
|
|
|
+ [self sendOpenLiveMessage];
|
|
|
+ }
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self liveConversationJoin];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)liveConversationJoin {
|
|
|
+ [[V2TIMManager sharedInstance] joinGroup:self.roomId msg:nil succ:^{
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ [self addConstMessage];
|
|
|
+ self.hasJoinRoomSuccess = YES;
|
|
|
+ [self getGroupInfo];
|
|
|
+ });
|
|
|
+
|
|
|
+ } fail:^(int code, NSString *desc) {
|
|
|
+ NSLog(@"");
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)getGroupInfo {
|
|
|
+ [self getGroupAttrInfo];
|
|
|
+ [self getGroupCount];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)getGroupAttrInfo {
|
|
|
+ [[V2TIMManager sharedInstance] getGroupAttributes:self.roomId keys:nil succ:^(NSMutableDictionary<NSString *,NSString *> *groupAttributeList) {
|
|
|
+ [self evaluateGroupInfo:groupAttributeList];
|
|
|
+ } fail:^(int code, NSString *desc) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)getGroupCount {
|
|
|
+ [[V2TIMManager sharedInstance] getGroupCounters:self.roomId keys:nil succ:^(NSDictionary<NSString *,NSNumber *> *groupCounters) {
|
|
|
+ [self evaluateGroupCount:groupCounters];
|
|
|
+ } fail:^(int code, NSString *desc) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)addConstMessage {
|
|
|
+ TXConstMessage *statement = [[TXConstMessage alloc] init];
|
|
|
+ statement.userInfo = [self getCurrentUser];
|
|
|
+ statement.text = @"欢迎进入直播课堂,请遵守相关法规,禁止传播低俗、暴力等不良信息。为孩子创造健康绿色的学习环境。";
|
|
|
+ [self appendAndDisplayMessage:statement];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)sendWelcomeMessage {
|
|
|
+ TXLiveMessageWelcome *welcomeMessage = [[TXLiveMessageWelcome alloc] init];
|
|
|
+ welcomeMessage.userInfo = [self getCurrentUser];
|
|
|
+ [self sendCustomMessage:welcomeMessage priority:V2TIM_PRIORITY_DEFAULT displayMessage:NO callback:^(BOOL success) {
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)joinLiveRoom {
|
|
|
+ [self notiferJoinSuccessToServiceCallback:^{
|
|
|
+
|
|
|
+ }];
|
|
|
+ [self startPublishStream];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)liveroomLogOut {
|
|
|
+ if (self.isOtherLogin == NO) {
|
|
|
+ self.isOtherLogin = YES;
|
|
|
+ [self MBPShow:@"该账号在其他设备上登录"];
|
|
|
+ [self quitRoomBackPreView:NO];
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil];
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)liveroomQuit {
|
|
|
+ if (self.isOtherLogin == NO) {
|
|
|
+ self.isOtherLogin = YES;
|
|
|
+ [self quitRoomBackPreView:NO];
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"backLoginView" object:nil];
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)connectionService {
|
|
|
+ [self configLiveVideo];
|
|
|
+ [self joinChatRoom];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)configLiveVideo {
|
|
|
+ [self.trtcCloud setAudioRoute:TRTCAudioModeSpeakerphone];
|
|
|
+ [self setupLocalVideoView];
|
|
|
+ [self joinLiveRoom];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)countLikeMessageCount {
|
|
|
+ NSInteger count = self.likeCount;
|
|
|
+ NSString *desc = @"";
|
|
|
+ if (count > 10000) {
|
|
|
+ count = count / 10000;
|
|
|
+ desc = [NSString stringWithFormat:@"本场点赞%zd万+",count];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ desc = [NSString stringWithFormat:@"本场点赞%zd",count];
|
|
|
+ }
|
|
|
+ self.headView.likeCount.text = desc;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)countMemberCount {
|
|
|
+ NSInteger count = self.totalCount;
|
|
|
+ NSString *desc = @"";
|
|
|
+ if (count > 10000) {
|
|
|
+ count = count / 10000;
|
|
|
+ desc = [NSString stringWithFormat:@"%zd万+人",count];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ desc = [NSString stringWithFormat:@"%zd人",count];
|
|
|
+ }
|
|
|
+ self.headView.roomMemberCount.text = desc;
|
|
|
+}
|
|
|
+
|
|
|
+// 刷新seatView
|
|
|
+- (void)renderSeatView {
|
|
|
+ NSMutableArray *seatArray = [NSMutableArray array];
|
|
|
+ NSArray *remoteUserArray = [self.remoteMemberArray mutableCopy];
|
|
|
+ for (TXUISeatMember *member in remoteUserArray) {
|
|
|
+ [seatArray addObject:member];
|
|
|
+ }
|
|
|
+ if (seatArray.count) {
|
|
|
+ if (![self.livePageView.subviews containsObject:self.seatContainer]) {
|
|
|
+ [self.livePageView addSubview:self.seatContainer];
|
|
|
+ }
|
|
|
+
|
|
|
+ [self.seatContainer mas_remakeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.mas_equalTo(self.livePageView);
|
|
|
+ make.top.mas_equalTo(self.headView.mas_bottom).offset(10);
|
|
|
+ make.height.mas_equalTo(80);
|
|
|
+ }];
|
|
|
+
|
|
|
+ [self.seatContainer refreshSeatUIWithMember:[NSMutableArray arrayWithArray:seatArray]];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self removeSeatContainer];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)removeSeatContainer {
|
|
|
+ if ([self.livePageView.subviews containsObject:self.seatContainer]) {
|
|
|
+ [self.seatContainer removeFromSuperview];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 添加本地采集预览页面
|
|
|
+- (void)setupLocalVideoView {
|
|
|
+
|
|
|
+ [self.trtcCloud startLocalPreview:YES view:self.liveVideoView];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark --- publish
|
|
|
+
|
|
|
+- (void)onStopPublishing:(int)err errMsg:(NSString *)errMsg {
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+- (void)onStartPublishing:(int)err errMsg:(NSString *)errMsg {
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark ----- trtc delegate
|
|
|
+
|
|
|
+- (void)sendSEIMessage {
|
|
|
+ if (self.isOtherLogin || self.isPauseLive) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ NSMutableArray *memberArray = [self.remoteMemberArray mutableCopy];
|
|
|
+ NSMutableArray *seatUser = [NSMutableArray array];
|
|
|
+ for (TXUISeatMember *member in memberArray) {
|
|
|
+ NSMutableDictionary *memberDic = [NSMutableDictionary dictionary];
|
|
|
+ [memberDic setValue:member.userId forKey:@"userId"];
|
|
|
+ [memberDic setValue:@(member.muteMic) forKey:@"micStatus"];
|
|
|
+ [seatUser addObject:memberDic];
|
|
|
+ }
|
|
|
+ NSMutableDictionary *parm = [NSMutableDictionary dictionary];
|
|
|
+ [parm setValue:seatUser forKey:@"seatUser"];
|
|
|
+ [parm setValue:@"sync" forKey:@"api"];
|
|
|
+ BOOL isSuccess = [self.trtcCloud sendSEIMsg:[parm mj_JSONData] repeatCount:1];
|
|
|
+ NSLog(@"%d",isSuccess);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 远端主播加入房间
|
|
|
+- (void)onRemoteUserEnterRoom:(NSString *)userId {
|
|
|
+ if (![self judgeContainerRemoteUser:userId]) { // 如果不包含当前用户 添加 和刷新连麦
|
|
|
+ TXUISeatMember *member = [[TXUISeatMember alloc] init];
|
|
|
+ member.userId = userId;
|
|
|
+ member.muteMic = NO;
|
|
|
+ [self.remoteMemberArray addObject:member];
|
|
|
+ }
|
|
|
+ [self renderSeatView];
|
|
|
+ // 刷新连麦成员状态数据
|
|
|
+ if ([self judgeContainMember:userId]) {
|
|
|
+ // 更新状态
|
|
|
+ [self updateMemberStatusConnecting:userId];
|
|
|
+ [self refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ [self mixRemoteStream];
|
|
|
+}
|
|
|
+
|
|
|
+// 混流操作
|
|
|
+- (void)mixRemoteStream {
|
|
|
+ TRTCTranscodingConfig *config = [[TRTCTranscodingConfig alloc] init];
|
|
|
+ // 设置分辨率为720 × 1280, 码率为1500kbps,帧率为20FPS
|
|
|
+ config.videoWidth = 540;
|
|
|
+ config.videoHeight = 960;
|
|
|
+ config.videoBitrate = 1500;
|
|
|
+ config.videoFramerate = 20;
|
|
|
+ config.videoGOP = 2;
|
|
|
+ config.audioSampleRate = 48000;
|
|
|
+ config.audioBitrate = 64;
|
|
|
+ config.audioChannels = 2;
|
|
|
+ // 采用全手动模式
|
|
|
+ config.mode = TRTCTranscodingConfigMode_Manual;
|
|
|
+
|
|
|
+ NSMutableArray *mixUsers = [NSMutableArray new];
|
|
|
+ // 主播摄像头的画面位置
|
|
|
+ TRTCMixUser* local = [TRTCMixUser new];
|
|
|
+ local.userId = UserDefault(IM_USERID);
|
|
|
+ local.zOrder = 0; // zOrder 为0代表主播画面位于最底层
|
|
|
+ local.rect = CGRectMake(0, 0, 540, 960);
|
|
|
+ local.roomID = nil; // 本地用户不用填写 roomID,远程需要
|
|
|
+ [mixUsers addObject:local];
|
|
|
+ if (self.remoteMemberArray.count) {
|
|
|
+ NSMutableArray *userArray = [self.remoteMemberArray mutableCopy];
|
|
|
+
|
|
|
+ for (int index = 0; index < userArray.count; index++) {
|
|
|
+ TXUISeatMember *member = userArray[index];
|
|
|
+ // 连麦者的画面位置
|
|
|
+ TRTCMixUser* remoteUser = [TRTCMixUser new];
|
|
|
+ remoteUser.userId = member.userId;
|
|
|
+ remoteUser.zOrder = index+1;
|
|
|
+ remoteUser.inputType = TRTCMixInputTypePureAudio;
|
|
|
+ remoteUser.rect = CGRectMake(0, 0, 0, 0); //仅供参考
|
|
|
+ remoteUser.roomID = self.roomId; // 本地用户不用填写 roomID,远程需要
|
|
|
+ [mixUsers addObject:remoteUser];
|
|
|
+ }
|
|
|
+ config.mixUsers = mixUsers;
|
|
|
+ [self.trtcCloud setMixTranscodingConfig:config];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self.trtcCloud setMixTranscodingConfig:nil];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 远端主播离开
|
|
|
+- (void)onRemoteUserLeaveRoom:(NSString *)userId reason:(NSInteger)reason {
|
|
|
+ if ([self judgeContainerRemoteUser:userId]) { // 如果包含当前用户 移除当前用和刷新连麦
|
|
|
+ [self removeMemberUserId:userId];
|
|
|
+ }
|
|
|
+ [self renderSeatView];
|
|
|
+
|
|
|
+ // 刷新连麦成员数据
|
|
|
+ if ([self judgeContainMember:userId]) {
|
|
|
+ [self removeMember:userId];
|
|
|
+ [self refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ [self mixRemoteStream];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)onUserAudioAvailable:(NSString *)userId available:(BOOL)available {
|
|
|
+
|
|
|
+ [self changeSeatMemberStatus:userId muteMic:!available];
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)judgeContainerRemoteUser:(NSString *)userId {
|
|
|
+ BOOL containUser = NO;
|
|
|
+ NSMutableArray *remoteMemArray = [self.remoteMemberArray mutableCopy];
|
|
|
+ for (TXUISeatMember *member in remoteMemArray) {
|
|
|
+ if ([member.userId isEqualToString:userId]) {
|
|
|
+ containUser = YES;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return containUser;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)removeMemberUserId:(NSString *)userId {
|
|
|
+ NSMutableArray *remoteMemArray = [self.remoteMemberArray mutableCopy];
|
|
|
+ for (TXUISeatMember *member in remoteMemArray) {
|
|
|
+ if ([member.userId isEqualToString:userId]) {
|
|
|
+ [self.remoteMemberArray removeObject:member];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 音视频技术指标的实时统计回调
|
|
|
+- (void)onStatistics:(TRTCStatistics *)statistics {
|
|
|
+ BOOL isConnectedFailed = NO;
|
|
|
+
|
|
|
+ if (statistics.upLoss == 1.0) {
|
|
|
+ isConnectedFailed = YES;
|
|
|
+ }
|
|
|
+ [self showNetStatus:statistics.rtt networkLost:isConnectedFailed];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showNetStatus:(NSInteger)rttValue networkLost:(BOOL)isFailed {
|
|
|
+ dispatch_main_async_safe((^{
|
|
|
+ if (isFailed) {
|
|
|
+ [self.headView.rttImage setImage:[UIImage imageNamed:@"live_networking_bad"]];
|
|
|
+ self.headView.msLabel.text = @"网络已断开";
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.headView.msLabel.text = [NSString stringWithFormat:@"%zdms",rttValue];
|
|
|
+ if (rttValue < 100) {
|
|
|
+ [self.headView.rttImage setImage:[UIImage imageNamed:@"live_networking_good"]];
|
|
|
+ }
|
|
|
+ else if (rttValue < 200) {
|
|
|
+ [self.headView.rttImage setImage:[UIImage imageNamed:@"live_networking_nomal"]];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self.headView.rttImage setImage:[UIImage imageNamed:@"live_networking_bad"]];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }));
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - KSChatInputBarControlDelegate
|
|
|
+// 根据inputBar 回调来修改页面布局
|
|
|
+- (void)onInputBarControlContentSizeChanged:(CGRect)frame withAnimationDuration:(CGFloat)duration andAnimationCurve:(UIViewAnimationCurve)curve {
|
|
|
+ CGRect originFrame = _inputBar.frame;
|
|
|
+ __weak __typeof(&*self)weakSelf = self;
|
|
|
+ // 目前只使用y值即可 -- 只修改messageContentView高度,让展示消息view和输入框随之移动
|
|
|
+ [UIView animateWithDuration:duration animations:^{
|
|
|
+ [UIView setAnimationCurve:curve];
|
|
|
+ [weakSelf.inputBar setFrame:CGRectMake(0, frame.origin.y - originFrame.size.height, originFrame.size.width, originFrame.size.height)];
|
|
|
+ [UIView commitAnimations];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+// 发送消息
|
|
|
+- (void)onTouchSendButton:(NSString *)text {
|
|
|
+ NSDate *date = [NSDate date];
|
|
|
+ NSTimeInterval currentInterval = [date timeIntervalSince1970];
|
|
|
+ if (currentInterval - self.lastSendMsgTime < 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (self.lastSendMsgTime == 0) {
|
|
|
+ self.lastSendMsgTime = currentInterval;
|
|
|
+ }
|
|
|
+ [self touristSendMessage:text];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)touristSendMessage:(NSString *)text {
|
|
|
+
|
|
|
+ TXLiveTextMessage *message = [[TXLiveTextMessage alloc] init];
|
|
|
+ message.userInfo = [self getCurrentUser];
|
|
|
+ message.text = text;
|
|
|
+ MJWeakSelf;
|
|
|
+ [self sendTextMessage:message priority:V2TIM_PRIORITY_NORMAL displayMessage:YES callback:^(BOOL success) {
|
|
|
+ [weakSelf setDefaultBottomViewStatus];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (TXLiveUser *)getCurrentUser {
|
|
|
+ TXLiveUser *user = [[TXLiveUser alloc] init];
|
|
|
+ user.sendUserId = UserDefault(IM_USERID);
|
|
|
+ user.sendUserName = UserDefault(NicknameKey);
|
|
|
+ user.avatarUrl = UserDefault(AvatarUrlKey);
|
|
|
+ return user;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)syncLiveStatus:(BOOL)isOnLive {
|
|
|
+
|
|
|
+ [LiveModuleService updateRoomStatusRequest:KS_POST pushStatus:isOnLive roomId:self.roomId success:^(NSDictionary * _Nonnull dic) {
|
|
|
+
|
|
|
+ } faliure:^(NSError * _Nonnull error) {
|
|
|
+
|
|
|
+ }];
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)exitRoomCallback:(void(^)(BOOL success))callback {
|
|
|
+ [self.trtcCloud stopPublishing];
|
|
|
+ [self.trtcCloud stopLocalPreview];
|
|
|
+ [self.trtcCloud exitRoom];
|
|
|
+ [self sendLeaveMessageCallback:^(BOOL success) {
|
|
|
+ callback(success);
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+// 销毁房间
|
|
|
+- (void)distoryRoomCallback:(void(^)(BOOL success))callback {
|
|
|
+ [self.trtcCloud stopLocalPreview];
|
|
|
+ [self.trtcCloud exitRoom];
|
|
|
+ [self sendLeaveMessageCallback:^(BOOL success) {
|
|
|
+ callback(success);
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark -- 加入直播间和退出直播间回到服务
|
|
|
+- (void)notiferJoinSuccessToServiceCallback:(void(^)(void))callback {
|
|
|
+ [LiveModuleService speakerJoinRoomRequest:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) {
|
|
|
+ callback();
|
|
|
+ } faliure:^(NSError * _Nonnull error) {
|
|
|
+ callback();
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark -- 退出直播间回掉服务
|
|
|
+- (void)quitNotiferService {
|
|
|
+ [LiveModuleService LiveroomQuit:KS_POST success:^(NSDictionary * _Nonnull dic) {
|
|
|
+ if ([dic ks_integerValueForKey:@"errCode"] == 0) {
|
|
|
+ NSLog(@"success");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ NSLog(@"----- error %@", [dic ks_stringValueForKey:@"errMsg"]);
|
|
|
+ }
|
|
|
+ } faliure:^(NSError * _Nonnull error) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)closeRoomNotiferService {
|
|
|
+ [LiveModuleService roomDestroyRequest:KS_GET roomUid:self.roomId success:^(NSDictionary * _Nonnull dic) {
|
|
|
+ if ([dic ks_integerValueForKey:@"errCode"] == 0) {
|
|
|
+ NSLog(@"success");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ NSLog(@"----- error %@", [dic ks_stringValueForKey:@"errMsg"]);
|
|
|
+ }
|
|
|
+ } faliure:^(NSError * _Nonnull error) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)sendLeaveMessageCallback:(void(^)(BOOL success))callback {
|
|
|
+ TXLiveMessageLeave *leaveMessage = [[TXLiveMessageLeave alloc] init];
|
|
|
+ leaveMessage.userInfo = [self getCurrentUser];
|
|
|
+ [self sendCustomMessage:leaveMessage priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ callback(success);
|
|
|
+ }];
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+- (void)quitChatRoom {
|
|
|
+ [[V2TIMManager sharedInstance] quitGroup:self.roomId succ:^{
|
|
|
+
|
|
|
+ } fail:^(int code, NSString *desc) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark -------- live message handle
|
|
|
+
|
|
|
+- (NSString *)getMsgClassName:(NSString *)messageObjectName {
|
|
|
+ TXLiveMessageModel *configModel = [[TXLiveMessageModel alloc] init];
|
|
|
+ NSString *className = [configModel getMessageClassName:messageObjectName];
|
|
|
+ return className;
|
|
|
+}
|
|
|
+/**
|
|
|
+ * 接收到消息的回调
|
|
|
+ */
|
|
|
+- (void)didReceiveMessageNotification:(NSNotification *)notification {
|
|
|
+ NSDictionary *dic = notification.object; /// __block TXLiveMessageModel *liveMessage
|
|
|
+ NSDictionary *content = [dic ks_dictionaryValueForKey:@"message"];
|
|
|
+ NSString *objectName = [content ks_stringValueForKey:@"objectName"];
|
|
|
+ NSString *className = [self getMsgClassName:objectName];
|
|
|
+
|
|
|
+ __block TXLiveMessageModel *message = [[NSClassFromString(className) alloc] init];
|
|
|
+ message.messageId = [dic ks_stringValueForKey:@"msgID"];
|
|
|
+ message.groupId = [dic ks_stringValueForKey:@"groupID"];
|
|
|
+ [message evaluateSource:content];
|
|
|
+ if ([message.groupId isEqualToString:self.roomId]) {
|
|
|
+ __weak typeof(&*self) __blockSelf = self;
|
|
|
+ dispatch_main_async_safe((^{
|
|
|
+ if ([message isMemberOfClass:[TXLiveMessageLike class]]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageLikeCount class]]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageWelcome class]]) {
|
|
|
+ TXLiveMessageWelcome *welcomeMessage = (TXLiveMessageWelcome *)message;
|
|
|
+ // 过滤自己发送的欢迎消息
|
|
|
+ if ([welcomeMessage.userInfo.sendUserId isEqualToString:UserDefault(IM_USERID)]) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ NSString *userName = [welcomeMessage.userInfo.sendUserName stringByAppendingString:@""];
|
|
|
+ NSString *contentMsg = [NSString stringWithFormat:@"%@ 进入直播间",userName];
|
|
|
+ [__blockSelf showAnimationView:YES showMessag:contentMsg];
|
|
|
+
|
|
|
+ if ([__blockSelf judgeContainMember:welcomeMessage.userInfo.sendUserId]) {
|
|
|
+ [__blockSelf removeMember:welcomeMessage.userInfo.sendUserId];
|
|
|
+ [__blockSelf refreshSeatApplyView];
|
|
|
+ [__blockSelf renderSeatView];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageMemberUp class]]) {
|
|
|
+ TXLiveMessageMemberUp *member = (TXLiveMessageMemberUp *)message;
|
|
|
+ __blockSelf.totalCount = member.count;
|
|
|
+ [__blockSelf countMemberCount];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageStatSync class]]) { // 数据同步 不取点赞数
|
|
|
+ // 暂不使用
|
|
|
+// TXLiveMessageStatSync *syncStatMsg = (TXLiveMessageStatSync *)message;
|
|
|
+// __blockSelf.totalCount = syncStatMsg.onlineUsers;
|
|
|
+// __blockSelf.likeCount = syncStatMsg.likes;
|
|
|
+// [__blockSelf countMemberCount];
|
|
|
+ }
|
|
|
+ // 用户退出消息
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageLeave class]]) {
|
|
|
+ TXLiveMessageLeave *memberLeaveMsg = (TXLiveMessageLeave *)message;
|
|
|
+ if ([__blockSelf judgeContainMember:memberLeaveMsg.userInfo.sendUserId]) {
|
|
|
+ [__blockSelf removeMember:memberLeaveMsg.userInfo.sendUserId];
|
|
|
+ [__blockSelf refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageUserQuit class]]) {
|
|
|
+ TXLiveMessageUserQuit *quitMsg = (TXLiveMessageUserQuit *)message;
|
|
|
+ if ([__blockSelf judgeContainMember:quitMsg.targetId]) {
|
|
|
+ [__blockSelf removeMember:quitMsg.targetId];
|
|
|
+ [__blockSelf refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 连麦申请消息
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageSeatApply class]]) {
|
|
|
+
|
|
|
+ TXLiveMessageSeatApply *seatApplyMessage = (TXLiveMessageSeatApply *)message;
|
|
|
+
|
|
|
+ if (seatApplyMessage.type == SEATHANDLE_APPLY) { // 观众申请 加入申请中
|
|
|
+ LiveSeatMember *member = [[LiveSeatMember alloc] init];
|
|
|
+ member.avatar = seatApplyMessage.audienceAvatar;
|
|
|
+ member.name = seatApplyMessage.audienceName;
|
|
|
+ member.userId = seatApplyMessage.audienceId;
|
|
|
+ member.isConnected = NO;
|
|
|
+ if (![__blockSelf judgeContainMember:member.userId]) {
|
|
|
+ [__blockSelf.seatApplyArray addObject:member];
|
|
|
+ [__blockSelf refreshSeatApplyView];
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ else if (seatApplyMessage.type == SEATHANDLE_CANCELAPPLY) { // 观众取消申请 从申请中剔除
|
|
|
+ if ([__blockSelf judgeContainMember:seatApplyMessage.audienceId]) {
|
|
|
+ // 如果在申请中的状态
|
|
|
+ BOOL connectedStatus = [__blockSelf getUserConnectStatus:seatApplyMessage.audienceId];
|
|
|
+ if (connectedStatus == NO) {
|
|
|
+ [__blockSelf removeMember:seatApplyMessage.audienceId];
|
|
|
+ [__blockSelf refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+ // 连麦回复消息(暂不处理)
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageSeatResponse class]]) {
|
|
|
+
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageDownSeat class]]) { // 观众下麦,从列表中移除
|
|
|
+
|
|
|
+ TXLiveMessageDownSeat *downSeatMessage = (TXLiveMessageDownSeat *)message;
|
|
|
+ if ([__blockSelf judgeContainMember:downSeatMessage.audienceId]) {
|
|
|
+ [__blockSelf removeMember:downSeatMessage.audienceId];
|
|
|
+ [__blockSelf refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+ // 禁言控制
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageChatBan class]]) {
|
|
|
+
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageBlockUser class]]) { // 禁言用户操作 (用户无法发生下麦消息)
|
|
|
+ TXLiveMessageBlockUser *blockMessage = (TXLiveMessageBlockUser *)message;
|
|
|
+ if ([__blockSelf judgeContainMember:blockMessage.userId]) {
|
|
|
+ [__blockSelf removeMember:blockMessage.userId];
|
|
|
+ [__blockSelf refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageShopRush class]]) {
|
|
|
+ TXLiveMessageShopRush *rushMsg = (TXLiveMessageShopRush *)message;
|
|
|
+
|
|
|
+ NSString *contentMsg = [NSString stringWithFormat:@"%@ 正在抢购",[NSString returnNoNullStringWithString:rushMsg.userInfo.sendUserName]];
|
|
|
+ [__blockSelf showAnimationView:NO showMessag:contentMsg];
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveTextMessage class]]) {
|
|
|
+ TXLiveTextMessage *rushMsg = (TXLiveTextMessage *)message;
|
|
|
+ [__blockSelf insertMessage:rushMsg];
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveMessageClose class]]) { // 直播间已关闭
|
|
|
+ [__blockSelf MBPShow:@"直播已结束"];
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ // 退出直播间
|
|
|
+ [__blockSelf quitRoomBackPreView:NO];
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveForceKickMsg class]]) { // 踢出信息
|
|
|
+ TXLiveForceKickMsg *changeMsg = (TXLiveForceKickMsg *)message;
|
|
|
+ if (changeMsg.targetIds.count == 0 || changeMsg.targetIds == nil) {
|
|
|
+ [__blockSelf courseChangeTips:changeMsg];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXLiveCourseTimeChange class]]) {
|
|
|
+ [__blockSelf.timeManager requestCousreTimeConfig];
|
|
|
+ }
|
|
|
+ else if ([message isMemberOfClass:[TXMicStatusSync class]]) {
|
|
|
+// TXMicStatusSync *syncMsg = (TXMicStatusSync *)message;
|
|
|
+// [__blockSelf changeSeatMemberStatus:syncMsg.userId muteMic:syncMsg.muteMic];
|
|
|
+ }
|
|
|
+ }));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)courseChangeTips:(TXLiveForceKickMsg *)msg {
|
|
|
+ MJWeakSelf;
|
|
|
+ [self exitRoomCallback:^(BOOL success) {
|
|
|
+ // quit 接口
|
|
|
+ [weakSelf quitNotiferService];
|
|
|
+ [weakSelf quitChatRoom];
|
|
|
+ [weakSelf.moreView hideView];
|
|
|
+ }];
|
|
|
+ // 退出房间
|
|
|
+ NSString *tipsMsg = [NSString isEmptyString:msg.reason] ? @"当前课程已调整,请退出直播间" : msg.reason;
|
|
|
+ self.closeAlert = [LiveRoomConfirmAlert liveroomAlertWithTitle:tipsMsg sureTitle:@"确定" inView:self.view confirm:^{
|
|
|
+ [weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+// 修改mic状态
|
|
|
+- (void)changeSeatMemberStatus:(NSString *)userId muteMic:(BOOL)muteMic {
|
|
|
+ // 刷新连麦成员状态数据
|
|
|
+ if ([self judgeContainMember:userId]) {
|
|
|
+ // 更新状态
|
|
|
+ [self updateMemberMicStatus:userId isMicMute:muteMic];
|
|
|
+ [self refreshSeatApplyView];
|
|
|
+ }
|
|
|
+ // 修改seat container
|
|
|
+ if ([self judgeContainerRemoteUser:userId]) {
|
|
|
+ [self updateSeatMember:userId isMute:muteMic];
|
|
|
+ [self renderSeatView];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateSeatMember:(NSString *)userId isMute:(BOOL)isMute {
|
|
|
+ NSMutableArray *applyArray = [self.remoteMemberArray mutableCopy];
|
|
|
+ for (TXUISeatMember *obj in applyArray) {
|
|
|
+ if ([obj.userId isEqualToString:userId]) {
|
|
|
+ obj.muteMic = isMute;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)insertMessage:(TXLiveMessageModel *)liveMessage {
|
|
|
+ [self appendAndDisplayMessage:liveMessage];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (BOOL)judgeContainMember:(NSString *)userId {
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ for (LiveSeatMember *obj in applyArray) {
|
|
|
+ if ([obj.userId isEqualToString:userId]) {
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return NO;
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)getUserConnectStatus:(NSString *)userId {
|
|
|
+ BOOL isConnected = NO;
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ for (LiveSeatMember *member in applyArray) {
|
|
|
+ if ([member.userId isEqualToString:userId]) {
|
|
|
+ return member.isConnected;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return isConnected;
|
|
|
+}
|
|
|
+
|
|
|
+- (NSInteger)queryConnectingCount {
|
|
|
+ NSInteger count = 0;
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ for (LiveSeatMember *member in applyArray) {
|
|
|
+ if (member.isConnected) {
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return count;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)removeMember:(NSString *)memberId {
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ for (LiveSeatMember *member in applyArray) {
|
|
|
+ if ([member.userId isEqualToString:memberId]) {
|
|
|
+ [self.seatApplyArray removeObject:member];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateMemberStatusWaiting:(NSString *)userId {
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ for (LiveSeatMember *obj in applyArray) {
|
|
|
+ if ([obj.userId isEqualToString:userId]) {
|
|
|
+ obj.isConnected = NO;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateMemberStatusConnecting:(NSString *)userId {
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ for (LiveSeatMember *obj in applyArray) {
|
|
|
+ if ([obj.userId isEqualToString:userId]) {
|
|
|
+ obj.isConnected = YES;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)updateMemberMicStatus:(NSString *)userId isMicMute:(BOOL)muteMic {
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ for (LiveSeatMember *obj in applyArray) {
|
|
|
+ if ([obj.userId isEqualToString:userId]) {
|
|
|
+ obj.isMicMute = muteMic;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)refreshSeatApplyView {
|
|
|
+ NSMutableArray *applyArray = [self.seatApplyArray mutableCopy];
|
|
|
+ if (applyArray.count) {
|
|
|
+ NSInteger count = 0;
|
|
|
+ for (LiveSeatMember *obj in applyArray) {
|
|
|
+ if (obj.isConnected == NO) {
|
|
|
+ count++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (count > 0) {
|
|
|
+ [self showSeatTips:count];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self hideSeatTips];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self hideSeatTips];
|
|
|
+ }
|
|
|
+ [self.seatActionView refreshSeatApplyTable:applyArray];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showSeatTips:(NSInteger)count {
|
|
|
+ NSString *countDesc = count >= 100 ? @"99+" : [NSString stringWithFormat:@"%zd",count];
|
|
|
+ [self.seatApplyTips configCountMessage:countDesc];
|
|
|
+ [self.seatApplyTips showTipsView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)hideSeatTips {
|
|
|
+ [self.seatApplyTips hideView];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark ----- 群计数器
|
|
|
+// 群属性变更信息
|
|
|
+- (void)didReceiveGroupCountMessage:(NSNotification *)notification {
|
|
|
+ NSDictionary *dic = notification.object; /// __block TXLiveMessageModel *liveMessage
|
|
|
+
|
|
|
+ NSString *groupId = [dic ks_stringValueForKey:@"groupID"];
|
|
|
+ NSDictionary *content = [dic ks_dictionaryValueForKey:@"message"];
|
|
|
+ if ([groupId isEqualToString:self.roomId]) {
|
|
|
+ [self evaluateGroupCount:content];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)evaluateGroupCount:(NSDictionary *)groupCount {
|
|
|
+ // 点赞数据
|
|
|
+ if ([[groupCount allKeys] containsObject:@"LIKES"]) {
|
|
|
+ self.likeCount = [groupCount ks_integerValueForKey:@"LIKES"];
|
|
|
+ [self countLikeMessageCount];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ------ 群属性
|
|
|
+- (void)didReceiveGroupMessage:(NSNotification *)notification {
|
|
|
+
|
|
|
+ NSDictionary *dic = notification.object; /// __block TXLiveMessageModel *liveMessage
|
|
|
+
|
|
|
+ NSString *groupId = [dic ks_stringValueForKey:@"groupID"];
|
|
|
+ NSDictionary *content = [dic ks_dictionaryValueForKey:@"message"];
|
|
|
+ if ([groupId isEqualToString:self.roomId]) {
|
|
|
+ [self evaluateGroupInfo:content];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)evaluateGroupInfo:(NSDictionary *)groupInfo {
|
|
|
+ // 在线人数
|
|
|
+ if ([[groupInfo allKeys] containsObject:@"MEMBER_ONLINE"]) {
|
|
|
+ self.totalCount = [groupInfo ks_integerValueForKey:@"MEMBER_ONLINE"];
|
|
|
+ [self countMemberCount];
|
|
|
+ }
|
|
|
+ // 主播全员闭麦状态
|
|
|
+ if ([[groupInfo allKeys] containsObject:@"ANCHOR_MIC"]) {
|
|
|
+ if ([[groupInfo ks_stringValueForKey:@"ANCHOR_MIC"] isEqualToString:@"ON"]) { // 禁麦
|
|
|
+ self.muteRoomMic = YES;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.muteRoomMic = NO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.muteRoomMic = NO;
|
|
|
+ }
|
|
|
+ if (self.seatActionView) {
|
|
|
+ self.seatActionView.isMuteMic = self.muteRoomMic;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark - gesture and button action
|
|
|
+- (void)resetBottomGesture:(UIGestureRecognizer *)gestureRecognizer {
|
|
|
+ if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
|
|
|
+ [self setDefaultBottomViewStatus];
|
|
|
+ }
|
|
|
+}
|
|
|
+- (void)setDefaultBottomViewStatus {
|
|
|
+ [self.inputBar setInputBarStatus:KSBottomBarStatusDefault];
|
|
|
+ [self.inputBar setHidden:YES];
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ----- table data source
|
|
|
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
|
|
|
+ return self.conversationDataRepository.count;
|
|
|
+}
|
|
|
+
|
|
|
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
|
|
+ TXLiveMessageModel *model = [self.conversationDataRepository objectAtIndex:indexPath.row];
|
|
|
+ TXLiveChatListCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TXLiveChatListCell"];
|
|
|
+ if (!cell) {
|
|
|
+ cell = [[TXLiveChatListCell appearance] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"TXLiveChatListCell"];
|
|
|
+ }
|
|
|
+ if ([model isMemberOfClass:[TXConstMessage class]] || [model isMemberOfClass:[TXLiveTextMessage class]] || [model isMemberOfClass:[TXLiveMessageWelcome class]] || [model isMemberOfClass:[TXLiveMessageLike class]] || [model isMemberOfClass:[TXLiveMessageSeatControl class]] ||[model isMemberOfClass:[TXLiveMessageChatBan class]] || [model isMemberOfClass:[TXLiveMessageSeatApply class]] || [model isMemberOfClass:[TXLiveMessageSeatResponse class]] || [model isMemberOfClass:[TXLiveMessageShopRush class]] || [model isMemberOfClass:[TXLiveMessageDownSeatAll class]] || [model isMemberOfClass:[TXLiveMessageRejectAllSeat class]] ){
|
|
|
+ [cell setDataModel:model createrId:self.createrId];
|
|
|
+ }
|
|
|
+
|
|
|
+ return cell;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ----- live page lazying
|
|
|
+
|
|
|
+- (UIView *)livePageView {
|
|
|
+ if (!_livePageView) {
|
|
|
+ _livePageView = [[UIView alloc] init];
|
|
|
+ _livePageView.backgroundColor = HexRGB(0x25292e);
|
|
|
+ }
|
|
|
+ return _livePageView;
|
|
|
+}
|
|
|
+
|
|
|
+- (UIView *)liveVideoView {
|
|
|
+ if (!_liveVideoView) {
|
|
|
+ _liveVideoView = [[UIView alloc] init];
|
|
|
+ _liveVideoView.backgroundColor = [UIColor clearColor];
|
|
|
+ }
|
|
|
+ return _liveVideoView;
|
|
|
+}
|
|
|
+
|
|
|
+- (TXLiveRoomHeadView *)headView {
|
|
|
+ if (!_headView) {
|
|
|
+ _headView = [TXLiveRoomHeadView shareInstance];
|
|
|
+ MJWeakSelf;
|
|
|
+ [_headView liveHeaderAction:^{
|
|
|
+ [weakSelf switchCamera];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ return _headView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)switchCamera {
|
|
|
+ // 获取当前摄像头状态
|
|
|
+ BOOL isFrontCamera = [[self.trtcCloud getDeviceManager] isFrontCamera];
|
|
|
+ [[self.trtcCloud getDeviceManager] switchCamera:!isFrontCamera];
|
|
|
+}
|
|
|
+
|
|
|
+- (LiveRoomBottomView *)bottomView {
|
|
|
+ if (!_bottomView) {
|
|
|
+ _bottomView = [LiveRoomBottomView shareInstance];
|
|
|
+ if (self.isLiveCourse) {
|
|
|
+ [_bottomView.moreButton setImage:[UIImage imageNamed:@"liveroom_pause"] forState:UIControlStateNormal];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [_bottomView.moreButton setImage:[UIImage imageNamed:@"liveroom_quit"] forState:UIControlStateNormal];
|
|
|
+ }
|
|
|
+ MJWeakSelf;
|
|
|
+ [_bottomView bottomClickAction:^(LIVEROOMACTION action) {
|
|
|
+ [weakSelf bottomViewAction:action];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ return _bottomView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)bottomViewAction:(LIVEROOMACTION)action {
|
|
|
+ switch (action) {
|
|
|
+ case LIVEROOMACTION_CHAT: // 聊天
|
|
|
+ {
|
|
|
+ [_inputBar setHidden:NO];
|
|
|
+ [_inputBar setInputBarStatus:KSBottomBarStatusKeyboard];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case LIVEROOMACTION_SEAT: // 呼出连麦页面
|
|
|
+ {
|
|
|
+ [self displaySeatView];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case LIVEROOMACTION_MORE: // 更多
|
|
|
+ {
|
|
|
+ if (self.isLiveCourse) {
|
|
|
+ [self pauseAction];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self showMoreView];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case LIVEROOMACTION_MICACTION:
|
|
|
+ {
|
|
|
+ self.micEnable = !self.micEnable;
|
|
|
+ [self.trtcCloud muteLocalAudio:!self.micEnable];
|
|
|
+ NSString *desc = @"";
|
|
|
+ if (self.micEnable) {
|
|
|
+ desc = @"你已开启麦克风";
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ desc = @"你已关闭麦克风";
|
|
|
+ }
|
|
|
+ [self MBPShow:desc];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setMicEnable:(BOOL)micEnable {
|
|
|
+ _micEnable = micEnable;
|
|
|
+ self.bottomView.isMicOn = micEnable;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)showMoreView {
|
|
|
+ [self.view addSubview:self.moreView];
|
|
|
+}
|
|
|
+
|
|
|
+- (SeatTipsView *)seatApplyTips {
|
|
|
+ if (!_seatApplyTips) {
|
|
|
+ _seatApplyTips = [[SeatTipsView alloc] init];
|
|
|
+ }
|
|
|
+ return _seatApplyTips;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)displaySeatView {
|
|
|
+ [self.view addSubview:self.seatCtrlView];
|
|
|
+ [self refreshSeatApplyView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)closeRoomAction {
|
|
|
+ MJWeakSelf;
|
|
|
+ self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"结束直播后,不可再次开启" leftButtonTitle:@"取消" rightTitle:@"结束直播" inView:self.view cancel:^{
|
|
|
+
|
|
|
+ } confirm:^{
|
|
|
+ [weakSelf distoryRoomAction];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)pauseAction {
|
|
|
+ MJWeakSelf;
|
|
|
+ self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"暂停后观众将无法看到视频画面" leftButtonTitle:@"取消" rightTitle:@"暂停直播" inView:self.view cancel:^{
|
|
|
+
|
|
|
+ } confirm:^{
|
|
|
+ [weakSelf quitRoomBackPreView:YES];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)distoryRoomAction {
|
|
|
+ MJWeakSelf;
|
|
|
+ [self distoryRoomCallback:^(BOOL success) {
|
|
|
+ [weakSelf closeRoomNotiferService];
|
|
|
+ [weakSelf quitChatRoom];
|
|
|
+ [weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
|
|
|
+ }];
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+- (void)pauseLiveActionBack:(BOOL)backPreView {
|
|
|
+ self.needPublishStream = NO;
|
|
|
+ [self resetSeatContainer];
|
|
|
+ TXLiveMessagePauseLive *pauserMsg = [[TXLiveMessagePauseLive alloc] init];
|
|
|
+ pauserMsg.userInfo = [self getCurrentUser];
|
|
|
+ MJWeakSelf;
|
|
|
+ [self sendCustomMessage:pauserMsg priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ if (backPreView) {
|
|
|
+ [weakSelf pauseLiveReset];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [weakSelf pauseLive];
|
|
|
+ }
|
|
|
+ }];
|
|
|
+ [self syncLiveStatus:NO];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)pauseLive {
|
|
|
+ [self.trtcCloud stopPublishing];
|
|
|
+ [self.trtcCloud exitRoom];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)pauseLiveReset {
|
|
|
+ [self.moreView hideView];
|
|
|
+ self.isPauseLive = YES;
|
|
|
+ self.pageType = LIVEPAGE_PREVIEW;
|
|
|
+ [self.livePageView removeFromSuperview];
|
|
|
+ [self.trtcCloud stopPublishing];
|
|
|
+ [self.trtcCloud exitRoom];
|
|
|
+ [self configEngine];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)resetSeatContainer {
|
|
|
+ self.remoteMemberArray = [NSMutableArray array];
|
|
|
+ [self removeSeatContainer];
|
|
|
+ self.seatApplyArray = [NSMutableArray array];
|
|
|
+ [self refreshSeatApplyView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)quitRoomBackPreView:(BOOL)backPreView {
|
|
|
+ if (backPreView) { // 暂停
|
|
|
+ [self pauseLiveActionBack:YES];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // 未连接IM直接退出
|
|
|
+ if (self.pageType == LIVEPAGE_PREVIEW && self.isPauseLive == NO) {
|
|
|
+ [self previewBackAction];
|
|
|
+ }
|
|
|
+ else { // 如果连接了IM
|
|
|
+ MJWeakSelf;
|
|
|
+ [self exitRoomCallback:^(BOOL success) {
|
|
|
+ // quit 接口
|
|
|
+ [weakSelf quitNotiferService];
|
|
|
+ [weakSelf quitChatRoom];
|
|
|
+ [weakSelf.moreView hideView];
|
|
|
+ if (weakSelf.callback) {
|
|
|
+ weakSelf.callback();
|
|
|
+ }
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ [weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
|
|
|
+ });
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)previewBackAction {
|
|
|
+ self.preVideoView = nil;
|
|
|
+ [self.trtcCloud stopLocalPreview];
|
|
|
+ if (self.callback) {
|
|
|
+ self.callback();
|
|
|
+ }
|
|
|
+ [self.navigationController dismissViewControllerAnimated:YES completion:nil];
|
|
|
+}
|
|
|
+
|
|
|
+- (NSMutableArray *)remoteMemberArray {
|
|
|
+ if (!_remoteMemberArray) {
|
|
|
+ _remoteMemberArray = [NSMutableArray array];
|
|
|
+ }
|
|
|
+ return _remoteMemberArray;
|
|
|
+}
|
|
|
+
|
|
|
+- (UIView *)messageContentView {
|
|
|
+ if (!_messageContentView) {
|
|
|
+ _messageContentView = [[UIView alloc] init];
|
|
|
+ [_messageContentView setBackgroundColor: [UIColor clearColor]];
|
|
|
+ }
|
|
|
+ return _messageContentView;
|
|
|
+}
|
|
|
+
|
|
|
+- (NSMutableArray<TXLiveMessageModel *> *)conversationDataRepository {
|
|
|
+ if (!_conversationDataRepository) {
|
|
|
+ _conversationDataRepository = [NSMutableArray array];
|
|
|
+ }
|
|
|
+ return _conversationDataRepository;
|
|
|
+}
|
|
|
+
|
|
|
+- (KSChatInputBarControl *)inputBar {
|
|
|
+ if (!_inputBar) {
|
|
|
+ _inputBar = [[KSChatInputBarControl alloc] initWithStatus:KSBottomBarStatusDefault];
|
|
|
+ [_inputBar setDelegate:self];
|
|
|
+ }
|
|
|
+ return _inputBar;
|
|
|
+}
|
|
|
+
|
|
|
+- (UITableView *)conversationMessageTableView {
|
|
|
+ if (!_conversationMessageTableView) {
|
|
|
+ _conversationMessageTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
|
|
|
+ _conversationMessageTableView.backgroundColor = [UIColor clearColor];
|
|
|
+ _conversationMessageTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
|
|
+ _conversationMessageTableView.dataSource = self;
|
|
|
+ _conversationMessageTableView.delegate = self;
|
|
|
+ _conversationMessageTableView.rowHeight = UITableViewAutomaticDimension;
|
|
|
+ _conversationMessageTableView.estimatedRowHeight = 40.0f;
|
|
|
+ _conversationMessageTableView.showsVerticalScrollIndicator = NO;
|
|
|
+ [_conversationMessageTableView registerClass:[TXLiveChatListCell class] forCellReuseIdentifier:@"TXLiveChatListCell"];
|
|
|
+ }
|
|
|
+ return _conversationMessageTableView;
|
|
|
+}
|
|
|
+
|
|
|
+- (TXSeatContainerView *)seatContainer {
|
|
|
+ if (!_seatContainer) {
|
|
|
+ _seatContainer = [[TXSeatContainerView alloc] init];
|
|
|
+ MJWeakSelf;
|
|
|
+ [_seatContainer seatActionCallback:^(NSString * _Nonnull userId, BOOL muteMic) {
|
|
|
+ [weakSelf muteUserAction:userId muteStatus:muteMic];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ return _seatContainer;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)muteUserAction:(NSString *)userId muteStatus:(BOOL)muteMic {
|
|
|
+ TXControlMemberMic *ctrlMsg = [[TXControlMemberMic alloc] init];
|
|
|
+ ctrlMsg.userInfo = [self getCurrentUser];
|
|
|
+ ctrlMsg.userId = userId;
|
|
|
+ ctrlMsg.muteMic = muteMic;
|
|
|
+ [self sendCustomMessage:ctrlMsg priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (UIView *)seatCtrlView {
|
|
|
+ if (!_seatCtrlView) {
|
|
|
+ _seatCtrlView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, KPortraitWidth, KPortraitHeight)];
|
|
|
+ _seatCtrlView.backgroundColor = [UIColor clearColor];
|
|
|
+ self.gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideSeatCtrlView)];
|
|
|
+ self.gesture.delegate = self;
|
|
|
+ [_seatCtrlView addGestureRecognizer:self.gesture];
|
|
|
+ [_seatCtrlView addSubview:self.seatActionView];
|
|
|
+ UIView *headView = [[UIView alloc] initWithFrame:CGRectMake(0, KPortraitHeight - 300 - iPhoneXSafeBottomMargin - 16, KPortraitWidth, 16)];
|
|
|
+ headView.backgroundColor = [UIColor whiteColor];
|
|
|
+ [_seatCtrlView addSubview:headView];
|
|
|
+ if (@available(iOS 11.0, *)) {
|
|
|
+ headView.layer.cornerRadius = 8;
|
|
|
+ headView.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner; // 左上圆角
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:headView.bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(8, 8)];
|
|
|
+ CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
|
|
|
+ maskLayer.frame = headView.bounds;
|
|
|
+ maskLayer.path = path.CGPath;
|
|
|
+ headView.layer.mask = maskLayer;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ return _seatCtrlView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)hideSeatCtrlView {
|
|
|
+ if (_seatCtrlView) {
|
|
|
+ [_seatCtrlView removeFromSuperview];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
|
|
|
+ if (gestureRecognizer == self.gesture) {
|
|
|
+ if ([touch.view isDescendantOfView:self.seatActionView]) {
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return YES;
|
|
|
+}
|
|
|
+
|
|
|
+- (TXLiveSeatActionView *)seatActionView {
|
|
|
+ if (!_seatActionView) {
|
|
|
+ _seatActionView = [[TXLiveSeatActionView alloc] initWithFrame:CGRectMake(0, kScreenHeight - 300 - iPhoneXSafeBottomMargin, kScreenWidth, 300 + iPhoneXSafeBottomMargin)];
|
|
|
+ MJWeakSelf;
|
|
|
+ [_seatActionView opreationCallback:^(TXLIVESECONTROL action, LiveSeatMember * _Nonnull member) {
|
|
|
+ [weakSelf operationSeatApplyAction:action member:member];
|
|
|
+ }];
|
|
|
+ _seatActionView.isForbiddenApply = !self.enableSeat;
|
|
|
+ _seatActionView.isMuteMic = self.muteRoomMic;
|
|
|
+ }
|
|
|
+ return _seatActionView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)operationSeatApplyAction:(TXLIVESECONTROL)control member:(LiveSeatMember *)member {
|
|
|
+ switch (control) {
|
|
|
+ case TXLIVESECONTROL_ALLOW: // 允许连麦
|
|
|
+ {
|
|
|
+ [self sendMicStatusRequest:0];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_FORBIDDEN: // 禁止连麦
|
|
|
+ {
|
|
|
+ [self sendMicStatusRequest:1];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_APPROVE: // 上麦
|
|
|
+ {
|
|
|
+ if (member) {
|
|
|
+ // 判断连麦人数超过4人 无法点击
|
|
|
+ NSInteger count = [self queryConnectingCount];
|
|
|
+ if (count >= 4) {
|
|
|
+ [self MBPShow:@"最多可连麦4人"];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ TXLiveMessageSeatResponse *responseMessage = [[TXLiveMessageSeatResponse alloc] init];
|
|
|
+ responseMessage.type = SEATRESPONSE_TEACHERAPPROVE;
|
|
|
+ responseMessage.teacherId = self.createrId;
|
|
|
+ responseMessage.teacherName = self.createrName;
|
|
|
+ responseMessage.audienceId = member.userId;
|
|
|
+ responseMessage.audienceName = member.name;
|
|
|
+ responseMessage.userInfo = [self getCurrentUser];
|
|
|
+ MJWeakSelf;
|
|
|
+ [self sendCustomMessage:responseMessage priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ [weakSelf refreshSeatArrayRemoveMember:NO member:member];
|
|
|
+ }];
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_DOWNSEAT: // 下麦
|
|
|
+ {
|
|
|
+ if (member) {
|
|
|
+ TXLiveMessageSeatApply *kickSeatMessage = [[TXLiveMessageSeatApply alloc] init];
|
|
|
+ kickSeatMessage.userInfo = [self getCurrentUser];
|
|
|
+ kickSeatMessage.type = SEATHANDLE_KICKSEAT;
|
|
|
+ kickSeatMessage.teacherId = self.createrId;
|
|
|
+ kickSeatMessage.teacherName = self.createrName;
|
|
|
+ kickSeatMessage.audienceId = member.userId;
|
|
|
+ kickSeatMessage.audienceName = member.name;
|
|
|
+ MJWeakSelf;
|
|
|
+ [self sendCustomMessage:kickSeatMessage priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ [weakSelf refreshSeatArrayRemoveMember:YES member:member];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_DOWNSEATALL: // 下麦
|
|
|
+ {
|
|
|
+ MJWeakSelf;
|
|
|
+ self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"确认后所有连麦中学员都将下麦" leftButtonTitle:@"取消" rightTitle:@"确认" inView:self.view cancel:^{
|
|
|
+
|
|
|
+ } confirm:^{
|
|
|
+ [weakSelf downSeatAll];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_REFUSEALL:
|
|
|
+ {
|
|
|
+ MJWeakSelf;
|
|
|
+ self.alertView = [LiveRoomAlertView liveroomAlertWithTitle:@"确认将正在申请连麦的学员清空?" leftButtonTitle:@"取消" rightTitle:@"确认" inView:self.view cancel:^{
|
|
|
+
|
|
|
+ } confirm:^{
|
|
|
+ [weakSelf refuseAllSeatApply];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_MUTEALL: // 全员闭麦
|
|
|
+ {
|
|
|
+ [self changeMuteMicStatus:YES];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_OPNEALL: // 全员开麦
|
|
|
+ {
|
|
|
+ [self changeMuteMicStatus:NO];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_MUTESINGLE: // 闭麦
|
|
|
+ {
|
|
|
+ TXControlMemberMic *ctrlMsg = [[TXControlMemberMic alloc] init];
|
|
|
+ ctrlMsg.userInfo = [self getCurrentUser];
|
|
|
+ ctrlMsg.userId = member.userId;
|
|
|
+ ctrlMsg.muteMic = YES;
|
|
|
+ [self sendCustomMessage:ctrlMsg priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case TXLIVESECONTROL_OPENSINGLE: // 开启
|
|
|
+ {
|
|
|
+ TXControlMemberMic *ctrlMsg = [[TXControlMemberMic alloc] init];
|
|
|
+ ctrlMsg.userInfo = [self getCurrentUser];
|
|
|
+ ctrlMsg.userId = member.userId;
|
|
|
+ ctrlMsg.muteMic = NO;
|
|
|
+ [self sendCustomMessage:ctrlMsg priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+// 全体连麦人麦克风状态
|
|
|
+- (void)changeMuteMicStatus:(BOOL)isMute {
|
|
|
+ [LiveModuleService updateRoomMicStatusRequest:KS_POST isMuteMic:isMute roomId:self.roomId success:^(NSDictionary * _Nonnull dic) {
|
|
|
+
|
|
|
+ } faliure:^(NSError * _Nonnull error) {
|
|
|
+
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+// 是否允许连麦 0:是 1否
|
|
|
+- (void)sendMicStatusRequest:(NSInteger)whetherMic {
|
|
|
+ [self showhud];
|
|
|
+ [KSNetworkingManager liveRoomSetMicApplyEnable:KS_GET roomUid:self.roomId whetherMic:whetherMic success:^(NSDictionary * _Nonnull dic) {
|
|
|
+ [self removehub];
|
|
|
+ if ([dic ks_integerValueForKey:@"code"] == 200 && [dic ks_boolValueForKey:@"status"]) {
|
|
|
+ if (whetherMic == 0) {
|
|
|
+ self.seatActionView.isForbiddenApply = NO;
|
|
|
+ [self enableSeatAction];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.seatActionView.isForbiddenApply = YES;
|
|
|
+ [self forceSeatAction];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self MBPShow:MESSAGEKEY];
|
|
|
+ }
|
|
|
+ } faliure:^(NSError * _Nonnull error) {
|
|
|
+ [self removehub];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)enableSeatAction {
|
|
|
+
|
|
|
+ TXLiveMessageSeatControl *message = [[TXLiveMessageSeatControl alloc] init];
|
|
|
+ message.userInfo = [self getCurrentUser];
|
|
|
+ message.seatBan = NO;
|
|
|
+ [self sendCustomMessage:message priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+// 禁止连麦
|
|
|
+- (void)forceSeatAction {
|
|
|
+ TXLiveMessageSeatControl *message = [[TXLiveMessageSeatControl alloc] init];
|
|
|
+ message.userInfo = [self getCurrentUser];
|
|
|
+ message.seatBan = YES;
|
|
|
+ MJWeakSelf;
|
|
|
+ [self sendCustomMessage:message priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ [weakSelf removeSeatMember:YES];
|
|
|
+ }];
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+// 全员下麦
|
|
|
+- (void)downSeatAll {
|
|
|
+ TXLiveMessageDownSeatAll *message = [[TXLiveMessageDownSeatAll alloc] init];
|
|
|
+ message.userInfo = [self getCurrentUser];
|
|
|
+ MJWeakSelf;
|
|
|
+ [self sendCustomMessage:message priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ [weakSelf removeSeatMember:NO];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)refuseAllSeatApply {
|
|
|
+ TXLiveMessageRejectAllSeat *message = [[TXLiveMessageRejectAllSeat alloc] init];
|
|
|
+ message.userInfo = [self getCurrentUser];
|
|
|
+ MJWeakSelf;
|
|
|
+ [self sendCustomMessage:message priority:V2TIM_PRIORITY_HIGH displayMessage:NO callback:^(BOOL success) {
|
|
|
+ [weakSelf removeSeatMember:YES];
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)removeSeatMember:(BOOL)isApply {
|
|
|
+ NSMutableArray *seatArray = [self.seatApplyArray mutableCopy];
|
|
|
+
|
|
|
+ if (isApply) {
|
|
|
+ NSMutableArray *array = [NSMutableArray array];
|
|
|
+ for (LiveSeatMember *member in seatArray) {
|
|
|
+ if (member.isConnected == YES) {
|
|
|
+ [array addObject:member];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ self.seatApplyArray = [NSMutableArray arrayWithArray:array];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ NSMutableArray *array = [NSMutableArray array];
|
|
|
+ for (LiveSeatMember *member in seatArray) {
|
|
|
+ if (member.isConnected == NO) {
|
|
|
+ [array addObject:member];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ self.seatApplyArray = [NSMutableArray arrayWithArray:array];
|
|
|
+ }
|
|
|
+ [self refreshSeatApplyView];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)refreshSeatArrayRemoveMember:(BOOL)isRemove member:(LiveSeatMember *)member {
|
|
|
+ if (isRemove) {
|
|
|
+ [self.seatApplyArray removeObject:member];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ member.isConnected = YES;
|
|
|
+ if (![self judgeContainMember:member.userId]) {
|
|
|
+ [self.seatApplyArray addObject:member];
|
|
|
+ NSLog(@"hah--------");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ [self refreshSeatApplyView];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (NSMutableArray *)seatApplyArray {
|
|
|
+ if (!_seatApplyArray) {
|
|
|
+ _seatApplyArray = [NSMutableArray array];
|
|
|
+ }
|
|
|
+ return _seatApplyArray;
|
|
|
+}
|
|
|
+
|
|
|
+- (LiveMoreDisplayView *)moreView {
|
|
|
+ if (!_moreView) {
|
|
|
+ _moreView = [LiveMoreDisplayView shareInstance];
|
|
|
+ _moreView.frame = CGRectMake(0, 0, KPortraitWidth, KPortraitHeight);
|
|
|
+ MJWeakSelf;
|
|
|
+ [_moreView operationQuitAction:^(BOOL isCloseRoom) {
|
|
|
+ [weakSelf leaveRoom:isCloseRoom];
|
|
|
+ }];
|
|
|
+ }
|
|
|
+ return _moreView;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)leaveRoom:(BOOL)closeRoom {
|
|
|
+ if (closeRoom) {
|
|
|
+ [self closeRoomAction];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ [self pauseAction];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)dealloc {
|
|
|
+ NSLog(@"---- live room dealloc");
|
|
|
+
|
|
|
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
|
+
|
|
|
+ if (_SEITimer) {
|
|
|
+ dispatch_source_cancel(_SEITimer);
|
|
|
+ _SEITimer = nil;
|
|
|
+ }
|
|
|
+ if (_trtcCloud) {
|
|
|
+ [self.trtcCloud stopLocalAudio];
|
|
|
+ [self.trtcCloud stopLocalPreview];
|
|
|
+ [self.trtcCloud stopPublishing];
|
|
|
+ [self.trtcCloud exitRoom];
|
|
|
+ _trtcCloud = nil;
|
|
|
+ [TRTCCloud destroySharedIntance];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (dispatch_source_t)SEITimer {
|
|
|
+ if (!_SEITimer) {
|
|
|
+ _SEITimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
|
|
|
+ uint64_t interval = (uint64_t)(2 * NSEC_PER_SEC);
|
|
|
+ dispatch_source_set_timer(_SEITimer, DISPATCH_TIME_NOW, interval, 0);
|
|
|
+// @weakify(self);
|
|
|
+ dispatch_source_set_event_handler(_SEITimer, ^{
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+// @strongify(self);
|
|
|
+ [self sendSEIMessage];
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return _SEITimer;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+#pragma mark ---- sendMessage/showMessage
|
|
|
+// 文本消息
|
|
|
+- (void)sendTextMessage:(TXLiveMessageModel *)messageContent priority:(V2TIMMessagePriority)priority displayMessage:(BOOL)displayMessage callback:(void(^)(BOOL success))callback {
|
|
|
+ NSData *msgData = [messageContent encodeMessage];
|
|
|
+ if (msgData == nil) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ __weak typeof(&*self) __weakself = self;
|
|
|
+ [TXLiveMessageCenter sendLiveChatMessage:msgData groupId:self.roomId priority:priority success:^{
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+
|
|
|
+ if (displayMessage) {
|
|
|
+ [__weakself appendAndDisplayMessage:messageContent];
|
|
|
+ }
|
|
|
+ [__weakself.inputBar clearInputView];
|
|
|
+ callback(YES);
|
|
|
+ });
|
|
|
+ } failer:^(int code, NSString * _Nonnull msg) {
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ [__weakself.inputBar clearInputView];
|
|
|
+ NSLog(@"发送失败,errorcode is: %d message: %@",code, msg);
|
|
|
+ callback(NO);
|
|
|
+ });
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+// 其他消息
|
|
|
+- (void)sendCustomMessage:(TXLiveMessageModel *)messageContent priority:(V2TIMMessagePriority)priority displayMessage:(BOOL)displayMessage callback:(void(^)(BOOL success))callback {
|
|
|
+ NSData *msgData = [messageContent encodeMessage];
|
|
|
+ if (msgData == nil) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ __weak typeof(&*self) __weakself = self;
|
|
|
+ [TXLiveMessageCenter sendLiveChatMessage:msgData groupId:self.roomId priority:priority success:^{
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+
|
|
|
+ if (displayMessage) {
|
|
|
+ [__weakself appendAndDisplayMessage:messageContent];
|
|
|
+ }
|
|
|
+ callback(YES);
|
|
|
+ });
|
|
|
+ } failer:^(int code, NSString * _Nonnull msg) {
|
|
|
+ dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
+ NSLog(@"发送失败,errorcode is: %d message: %@",code, msg);
|
|
|
+ callback(NO);
|
|
|
+ });
|
|
|
+ }];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将消息加入本地数组
|
|
|
+ */
|
|
|
+- (void)appendAndDisplayMessage:(TXLiveMessageModel *)liveMessage {
|
|
|
+ if (!liveMessage) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ([self appendMessageModel:liveMessage]) {
|
|
|
+ NSIndexPath *indexPath =
|
|
|
+ [NSIndexPath indexPathForItem:self.conversationDataRepository.count - 1
|
|
|
+ inSection:0];
|
|
|
+ if ([self.conversationMessageTableView numberOfRowsInSection:0] !=
|
|
|
+ self.conversationDataRepository.count - 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // view刷新
|
|
|
+ [self.conversationMessageTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
|
|
|
+ if ([self isAtTheBottomOfTableView] || self.isNeedScrollToButtom) {
|
|
|
+ [self scrollToBottomAnimated:NO];
|
|
|
+ self.isNeedScrollToButtom=NO;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 如果当前会话没有这个消息id,把消息加入本地数组
|
|
|
+ */
|
|
|
+- (BOOL)appendMessageModel:(TXLiveMessageModel *)model {
|
|
|
+
|
|
|
+ if (!model) {
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+ //这里可以根据消息类型来决定是否显示,如果不希望显示直接return NO
|
|
|
+
|
|
|
+ //数量不可能无限制的大,这里限制收到消息过多时,就对显示消息数量进行限制。
|
|
|
+ //用户可以手动下拉更多消息,查看更多历史消息。
|
|
|
+ if (self.conversationDataRepository.count>500) {
|
|
|
+ [self.conversationDataRepository removeObjectAtIndex:0];
|
|
|
+ [self.conversationMessageTableView reloadData];
|
|
|
+ }
|
|
|
+
|
|
|
+ [self.conversationDataRepository addObject:model];
|
|
|
+ return YES;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 判断消息是否在collectionView的底部
|
|
|
+ *
|
|
|
+ * @return 是否在底部
|
|
|
+ */
|
|
|
+- (BOOL)isAtTheBottomOfTableView {
|
|
|
+ if (self.conversationMessageTableView.contentSize.height <= self.conversationMessageTableView.frame.size.height) {
|
|
|
+ return YES;
|
|
|
+ }
|
|
|
+ if(self.conversationMessageTableView.contentOffset.y +200 >= (self.conversationMessageTableView.contentSize.height - self.conversationMessageTableView.frame.size.height)) {
|
|
|
+ return YES;
|
|
|
+ }else{
|
|
|
+ return NO;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 消息滚动到底部
|
|
|
+ *
|
|
|
+ * @param animated 是否开启动画效果
|
|
|
+ */
|
|
|
+- (void)scrollToBottomAnimated:(BOOL)animated {
|
|
|
+ if ([self.conversationMessageTableView numberOfSections] == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ NSUInteger finalRow = MAX(0, [self.conversationMessageTableView numberOfRowsInSection:0] - 1);
|
|
|
+ if (0 == finalRow) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ NSIndexPath *finalIndexPath =
|
|
|
+ [NSIndexPath indexPathForItem:finalRow inSection:0];
|
|
|
+ [self.conversationMessageTableView scrollToRowAtIndexPath:finalIndexPath atScrollPosition:UITableViewScrollPositionTop animated:animated];
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)showAnimationView:(BOOL)isJoinRoom showMessag:(NSString *)message {
|
|
|
+ if (self.animationView && self.animationView.isShow) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ if (self.pageType == LIVEPAGE_PREVIEW) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ANIMATIONTYPE type = isJoinRoom ? ANIMATIONTYPE_JOIN : ANIMATIONTYPE_RUSH;
|
|
|
+ self.animationView = [[LiveAnimationView alloc] initWithTitle:message animationType:type];
|
|
|
+ [self.view addSubview:self.animationView];
|
|
|
+ [self.animationView mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
+ make.left.right.mas_equalTo(self.view);
|
|
|
+ make.width.mas_equalTo(KPortraitWidth);
|
|
|
+ make.height.mas_equalTo(24.0f);
|
|
|
+ make.bottom.mas_equalTo(self.messageContentView.mas_top).offset(-6);
|
|
|
+ }];
|
|
|
+ [self.view bringSubviewToFront:self.animationView];
|
|
|
+ NSLog(@"------- start animation ");
|
|
|
+ MJWeakSelf;
|
|
|
+ [self.animationView startAnimationEndCallback:^{
|
|
|
+ NSLog(@"----- hide ");
|
|
|
+ weakSelf.animationView.isShow = YES;
|
|
|
+ weakSelf.animationView = nil;
|
|
|
+ }];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (TRTCCloud *)trtcCloud {
|
|
|
+ if (!_trtcCloud) {
|
|
|
+ _trtcCloud = [TRTCCloud sharedInstance];
|
|
|
+ _trtcCloud.delegate = self;
|
|
|
+ }
|
|
|
+ return _trtcCloud;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ---- 3A config
|
|
|
+- (void)setSubjectId:(NSString *)subjectId {
|
|
|
+ _subjectId = subjectId;
|
|
|
+ // 设置 3A参数
|
|
|
+ self.ANS = 0;
|
|
|
+ self.AEC = 100;
|
|
|
+ // 长号14和萨克斯5 AGC 一样
|
|
|
+ if ([subjectId isEqualToString:@"5"] || [subjectId isEqualToString:@"14"]) {
|
|
|
+ self.AGC = 45;
|
|
|
+ }
|
|
|
+ else if ([subjectId isEqualToString:@"16"]) { // 次中音号
|
|
|
+ self.AGC = 80;
|
|
|
+ }
|
|
|
+ else if ([subjectId isEqualToString:@"12"]) {
|
|
|
+ self.AGC = 50;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.AGC = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (TXLiveRoomTimeManager *)timeManager {
|
|
|
+ if (!_timeManager) {
|
|
|
+ _timeManager = [[TXLiveRoomTimeManager alloc] initWithDelegate:self];
|
|
|
+ _timeManager.roomId = self.roomId;
|
|
|
+ _timeManager.duration = self.surplusTime;
|
|
|
+ _timeManager.autoCloseNetworkRoomTime = self.autoCloseNetworkRoomTime;
|
|
|
+ }
|
|
|
+ return _timeManager;
|
|
|
+}
|
|
|
+
|
|
|
+#pragma mark ---- time manager
|
|
|
+- (void)formatTime:(NSString *)timeString timeType:(LIVETIMETYPE)type {
|
|
|
+ self.headView.timeLabel.text = timeString;
|
|
|
+ switch (type) {
|
|
|
+ case LIVETIMETYPE_NOTSTART:
|
|
|
+ {
|
|
|
+ self.headView.timeView.hidden = YES;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case LIVETIMETYPE_COURSE:
|
|
|
+ {
|
|
|
+ self.headView.timeView.hidden = NO;
|
|
|
+ self.headView.tipsLabel.text = @"课程时长: ";
|
|
|
+ self.headView.timeLabel.textColor = HexRGB(0x00FFF0);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case LIVETIMETYPE_REST:
|
|
|
+ {
|
|
|
+ self.headView.timeView.hidden = NO;
|
|
|
+ self.headView.tipsLabel.text = @"课间时间: ";
|
|
|
+ self.headView.timeLabel.textColor = HexRGB(0x00FFF0);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case LIVETIMETYPE_CLOSECOUNT:
|
|
|
+ {
|
|
|
+ self.headView.timeView.hidden = NO;
|
|
|
+ self.headView.tipsLabel.text = @"直播即将关闭: ";
|
|
|
+ self.headView.timeLabel.textColor = HexRGB(0xFF605E);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case LIVETIMETYPE_CLOSE:
|
|
|
+ {
|
|
|
+ self.headView.timeView.hidden = NO;
|
|
|
+ self.headView.tipsLabel.text = @"直播即将关闭:";
|
|
|
+ self.headView.timeLabel.textColor = HexRGB(0xFF605E);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)timeLabelAddAnimation:(CABasicAnimation *)animation {
|
|
|
+ [self.headView.timeLabel.layer addAnimation:animation forKey:nil];
|
|
|
+}
|
|
|
+
|
|
|
+- (void)quitClassroomNotifer {
|
|
|
+ if (self.isLiveCourse) {
|
|
|
+ [self.timeManager stopDurationTimer];
|
|
|
+ [self MBPShow:@"直播已结束"];
|
|
|
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
|
+ // 退出直播间
|
|
|
+ [self quitRoomBackPreView:NO];
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+- (void)setIsLiveCourse:(BOOL)isLiveCourse {
|
|
|
+ _isLiveCourse = isLiveCourse;
|
|
|
+ if (isLiveCourse) {
|
|
|
+ self.headView.timeView.hidden = NO;
|
|
|
+ [self configCountTimer];
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ self.headView.timeView.hidden = YES;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (void)configCountTimer {
|
|
|
+ self.timeManager.duration = self.surplusTime;
|
|
|
+}
|
|
|
+/*
|
|
|
+#pragma mark - Navigation
|
|
|
+
|
|
|
+// In a storyboard-based application, you will often want to do a little preparation before navigation
|
|
|
+- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
|
|
|
+ // Get the new view controller using [segue destinationViewController].
|
|
|
+ // Pass the selected object to the new view controller.
|
|
|
+}
|
|
|
+*/
|
|
|
+
|
|
|
+@end
|