Files
peko-ios/YuMi/Modules/YMRoom/View/XPRoomViewController.m

4347 lines
186 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// YMRoomViewController.m
// YUMI
//
// Created by YUMI on 2021/10/11.
//
#import "XPRoomViewController.h"
///Third
#import <Masonry/Masonry.h>
#import <NIMSDK/NIMSDK.h>
///Tool
#import "YUMIMacroUitls.h"
#import "YUMIConstant.h"
#import "AccountInfoStorage.h"
#import "RtcManager.h"
#import "XPRoomMiniManager.h"
#import "TTPopup.h"
#import "TurboModeStateManager.h"
#import "Api+ArrangeMic.h"
#import "Api+Room.h"
#import "DESEncrypt.h"
#import "NSArray+Safe.h"
#import "NSMutableDictionary+Saft.h"
#import "XPSkillCardPlayerManager.h"
#import "XCCurrentVCStackManager.h"
#import "CountDownHelper.h"
///Model
#import "RoomInfoModel.h"
#import "UserInfoModel.h"
#import "AttachmentModel.h"
#import "ArrangeMicModel.h"
#import "XPReleaseRadioModel.h"
#import "GuildSuperAdminInfoModel.h"
#import "XPKickUserModel.h"
#import "ContentLevelUpgradeModel.h"
#import "XPReceiveRedPacketModel.h"
#import "XPRedPacketModel.h"
#import "XPFreeGiftModel.h"
#import "XPUserCardInfoModel.h"
///View
#import "BaseNavigationController.h"
#import "XPRoomBackContainerView.h"
#import "XPRoomMenuContainerView.h"
#import "XPRoomQuickMessageContainView.h"
#import "MsRoomMessageMainView.h"
#import "RoomHeaderView.h"
#import "SocialStageView.h"
#import "TenMicStageView.h"
#import "FifteenMicStageView.h"
#import "NineteenMicStageView.h"
#import "TwentyMicStageView.h"
#import "DatingStageView.h"
#import "AnchorStageView.h"
#import "AnchorPkStageView.h"
//#import "XPRoomAnimationView.h"
#import "RoomAnimationView.h"
#import "XPRoomFunctionContainerView.h"
#import "XPArrangeMicViewController.h"
#import "XPRoomSettingInputView.h"
#import "AnchorRoomScrollView.h"
#import "AnchorRoomSrollTipView.h"
#import "XPReceiveRedPacketView.h"
#import "XPWebViewController.h"
#import "SessionViewController.h"
#import "XPFreeGiftsObtainView.h"
///P
#import "XPRoomPresenter.h"
#import "XPRoomProtocol.h"
#import "XPWeakTimer.h"
#import "RoomHostDelegate.h"
#import "RoomGuestDelegate.h"
#import "Api+RoomSetting.h"
#import "XNDJTDDLoadingTool.h"
#import "ClientConfig.h"
#import "LittleGameStageView.h"
#import "LittleGameScrollStageView.h"
#import "XPRoomLittleGameContainerView.h"
#import "StageViewManager.h"
#import "PIRoomEnterRedPacketView.h"
#import "XPIAPRechargeViewController.h"
#import "XPCandyTreeInsufficientBalanceView.h"
#import "GiftComboManager.h"
#import "LuckyGiftWinningFlagView.h"
#import "RoomBoomResultView.h"
#import "RoomBoomManager.h"
#import "RoomBoomExplosionView.h"
#import "RoomBoomResultView.h"
#import "XPSendGiftView.h"
#import "RoomSideMenu.h"
#import "RoomResourceManager.h"
#import "LuckyPackageLogicManager.h"
#import "MicCpInfoModel.h"
#import "XPMessageRemoteExtModel.h"
#import "MicMidpointRectManager.h"
// 🔧 新增Turbo Mode Tips 相关
#import "XPTurboModeTipsManager.h"
#import "BuglyManager.h"
//#import "XPMineHallAnchorIncomeStatisViewController.h"
UIKIT_EXTERN NSString * const kRoomGiftEffectUpdateNotificationKey;
UIKIT_EXTERN NSString * const kRoomMiniNotificationKey;
UIKIT_EXTERN NSString * kNewUserRechargeKey;
UIKIT_EXTERN NSString * const kMessageFromPublicRoomWithAttachmentNotification;
UIKIT_EXTERN NSString * const kFreeGiftCountdownNotification;
NSString * const kHadShowAnchorRoomTipKey = @"kHadShowAnchorRoomTipKey";//是否展示过个播房上划用户引导
NSString * const kHadQuitOtherRoomKey = @"kHadQuitOtherRoomKey";//是否退出过非自己房间
@interface XPRoomViewController ()<
XPRoomProtocol,
RoomHostDelegate,
NIMChatroomManagerDelegate,
NIMChatManagerDelegate,
NIMConversationManagerDelegate,
NIMLoginManagerDelegate,
XPRoomSettingInputViewDelegate,
AnchorRoomScrollViewDelegate,
NIMBroadcastManagerDelegate,
XPRoomLittleGameContainerViewDelegate,
CountDownHelperDelegate,
PIRoomEnterRedPacketViewDelegate,
XPReceiveRedPacketViewDelegate,
XPCandyTreeInsufficientBalanceViewDelegate>
///背景
@property (nonatomic,strong) XPRoomBackContainerView *backContainerView;
///个播上下切换view
@property (nonatomic, strong) AnchorRoomScrollView *anchorScrollView;
///房间信息
@property (nonatomic,strong) RoomHeaderView *roomHeaderView;
///坑位信息
@property (nonatomic,strong) StageView *stageView;
@property (nonatomic,strong) StageViewManager *stageViewManager;
@property (nonatomic,strong) TenMicStageView *tenMicStageView;
@property (nonatomic,strong) FifteenMicStageView *fifteenMicStageView;
@property (nonatomic,strong) TwentyMicStageView *twentyMicStageView;
@property(nonatomic, strong) NineteenMicStageView *nineteenMicStageView;
///公屏
@property (nonatomic,strong) MsRoomMessageMainView *messageContainerView;
///快捷发言
@property (nonatomic, strong) XPRoomQuickMessageContainView *quickMessageContainerView;
///底部操作栏
@property (nonatomic,strong) XPRoomMenuContainerView *menuContainerView;
///活动
@property(nonatomic, strong) RoomSideMenu *sideMenu;
///动画的view
@property (nonatomic,strong) RoomAnimationView *animationView;
///功能view的的容器
@property (nonatomic,strong) XPRoomFunctionContainerView *functionView;
///小游戏的容器
@property (nonatomic,strong) XPRoomLittleGameContainerView *littleGameView;
///房间的Uid
@property (nonatomic,copy) NSString * roomUid;
///小游戏的id
@property (nonatomic,copy) NSString *mgId;
///礼物id
@property (nonatomic, copy) NSString *giftId;
///用户信息
@property (nonatomic,strong) UserInfoModel *userInfo;
///房间信息
@property (nonatomic,strong) RoomInfoModel *roomInfo;
///本地是否修改过礼物特效显示 当前房间如果设置过礼物特效是否显示的话 就不再跟随房间礼物特效的更新而更新
@property (nonatomic,assign) BOOL hasAnimationEffect;
///跟随的
@property (nonatomic,copy) NSString *fromNick;
@property (nonatomic,assign) UserEnterRoomFromType fromType;
@property (nonatomic,copy) NSString *fromUid;
///超管列表
@property (nonatomic,strong) NSArray<GuildSuperAdminInfoModel *> *superMangerList;
///是否请求过了超管
@property (nonatomic,assign) BOOL isRequestSuperAdmin;
///是否是最小化进房
@property (nonatomic,assign) BOOL isMiniEnter;
///个播上划引导
@property (nonatomic, strong) AnchorRoomSrollTipView *anchorScrollTipView;
///滚动过的个播列表
@property (nonatomic,strong) NSMutableArray<NSString *> *anchorRoomList;
///序号
@property (nonatomic,assign) NSInteger anchorIndex;
///免费礼物数量
@property (nonatomic,strong) XPFreeGiftModel *freeModel;
///免费礼物弹窗
@property (nonatomic,strong) XPFreeGiftsObtainView *freeView;
///全服红包id
@property(nonatomic,copy) NSString *redEnvelopeId;
///红包
@property(nonatomic,strong) PIRoomEnterRedPacketView *redPacketView;
///是否正在显示红包弹窗,防止显示多个弹窗
@property(nonatomic,assign) BOOL isShowRedPacket;
@property(nonatomic,copy) NSString *releaseCoins;
// 🔧 防护机制:房间状态标志位
@property (atomic, assign) BOOL isExitingRoom; // 是否正在退出房间
@property (atomic, assign) BOOL isViewActive; // VC 是否可见/活跃
@property(nonatomic,copy) NSString *myCoins;
@property(nonatomic,strong) UIButton *exitGameButton;
/// 上麦请求弹窗定时器用于10秒后自动移除弹窗
@property(nonatomic,strong) NSTimer *upMicAskTimer;
/// 🔧 修复:保存 block 形式的通知观察者,防止内存泄漏
@property(nonatomic,strong) id<NSObject> exchangeRoomAnimationViewObserver;
/// 当前房间的CP关系列表用于在中点关系位播放对应cp等级的SVGA
@property (nonatomic, strong) NSArray<MicCpInfoModel *> *currentCpList;
@property (nonatomic, assign) BOOL currentUserMicStatusChanged; // 当前用户麦位状态是否发生变化(上麦、下麦、换位)
@property (nonatomic, assign) BOOL currentUserWasOnMic; // 当前用户之前是否在麦上
@property (nonatomic, assign) NSInteger currentUserMicPosition; // 当前用户的麦位位置(-1表示不在麦上
@property (nonatomic, assign) BOOL hasCompletedRoomInitialization; // 是否已完成进房初始化
@end
@implementation XPRoomViewController
+ (BOOL)openRoom:(NSString *)roomUid viewController:(UIViewController *)viewController {
return [self openRoom:roomUid fromNick:nil fromType:0 fromUid:nil viewController:viewController];
}
+ (BOOL)openRoom:(NSString *)roomUid viewController:(UIViewController *)viewController redEnvelopeId:(NSString *)redEnvelopeId{
XPRoomViewController * roomVC = [[self alloc] init];
roomVC.roomUid = roomUid;
roomVC.fromUid = nil;
roomVC.fromType = 0;
roomVC.fromNick = nil;
roomVC.redEnvelopeId = redEnvelopeId;
BaseNavigationController * baseNav = [[BaseNavigationController alloc] initWithRootViewController:roomVC];
baseNav.modalPresentationStyle = UIModalPresentationFullScreen;
[viewController presentViewController:baseNav animated:YES completion:nil];
return YES;
}
+ (BOOL)openRoom:(NSString*)roomUid fromNick:(NSString * __nullable)fromNick fromType:(UserEnterRoomFromType)fromType fromUid:(NSString * __nullable)fromUid viewController:(UIViewController*)viewController {
XPRoomViewController * roomVC = [[self alloc] init];
roomVC.roomUid = roomUid;
roomVC.fromUid = fromUid;
roomVC.fromType = fromType;
roomVC.fromNick = fromNick;
BaseNavigationController * baseNav = [[BaseNavigationController alloc] initWithRootViewController:roomVC];
baseNav.modalPresentationStyle = UIModalPresentationFullScreen;
[viewController presentViewController:baseNav animated:YES completion:nil];
return YES;
}
/**最小化进房
* @params roomUid 房主uid
* @params viewController 启动方
*/
+ (BOOL)openMiniRoom:(NSString*)roomUid viewController:(UIViewController*)viewController {
XPRoomViewController * roomVC = [[self alloc] init];
roomVC.roomUid = roomUid;
roomVC.isMiniEnter = YES;
BaseNavigationController * baseNav = [[BaseNavigationController alloc] initWithRootViewController:roomVC];
baseNav.modalPresentationStyle = UIModalPresentationFullScreen;
[viewController presentViewController:baseNav animated:YES completion:nil];
return roomVC;
}
/**最小化进房
* @params roomUid 房主uid
* @params viewController 启动方
* @params redEnvelopeId 全服红包id
*/
+ (BOOL)openMiniRoom:(NSString*)roomUid viewController:(UIViewController*)viewController redEnvelopeId:(NSString *)redEnvelopeId{
XPRoomViewController * roomVC = [[self alloc] init];
roomVC.roomUid = roomUid;
roomVC.isMiniEnter = YES;
roomVC.redEnvelopeId = redEnvelopeId;
BaseNavigationController * baseNav = [[BaseNavigationController alloc] initWithRootViewController:roomVC];
baseNav.modalPresentationStyle = UIModalPresentationFullScreen;
[viewController presentViewController:baseNav animated:YES completion:nil];
return roomVC;
}
/** XPRoomViewController 不允许外部 init ,请通过该方法启动房间。
* @params roomUid 房主uid
* @params viewController 启动方
* @params mgId 小游戏的id
*/
+ (BOOL)openRoom:(NSString*)roomUid mgId:(NSString *)mgId viewController:(UIViewController*)viewController {
XPRoomViewController * roomVC = [[self alloc] init];
roomVC.roomUid = roomUid;
roomVC.mgId = mgId;
BaseNavigationController * baseNav = [[BaseNavigationController alloc] initWithRootViewController:roomVC];
baseNav.modalPresentationStyle = UIModalPresentationFullScreen;
[viewController presentViewController:baseNav animated:YES completion:nil];
return YES;
}
/// h5进房并弹出礼物面板
/// @param roomUid 房主Uid
/// @param giftId 礼物id
/// @param viewController 启动方
+ (BOOL)openRoom:(NSString *)roomUid giftId:(NSString *)giftId viewController:(UIViewController *)viewController {
XPRoomViewController * roomVC = [[self alloc] init];
roomVC.roomUid = roomUid;
roomVC.giftId = giftId;
BaseNavigationController * baseNav = [[BaseNavigationController alloc] initWithRootViewController:roomVC];
baseNav.modalPresentationStyle = UIModalPresentationFullScreen;
[viewController presentViewController:baseNav animated:YES completion:nil];
return YES;
}
- (void)dealloc {
NSLog(@"🔄 XPRoomViewController: 开始销毁");
// 🔧 防护:强制清理 TRTC 连接,防止资源泄漏
[[RtcManager instance] exitRoom];
// 🔧 防护:标记房间正在退出,防止后续异步回调
self.isExitingRoom = YES;
self.isViewActive = NO;
[[RoomBoomManager sharedManager] leaveRoom];
[XPSkillCardPlayerManager shareInstance].photoIdList = nil;
[XPSkillCardPlayerManager shareInstance].isInRoomVC = NO;
[XPSkillCardPlayerManager shareInstance].isInRoomFirstRecharge = NO;
[XPSkillCardPlayerManager shareInstance].isInRoom = NO;
[XPSkillCardPlayerManager shareInstance].roomUid = @"";
if([[XPRoomMiniManager shareManager] getRoomInfo]==nil){
[XPSkillCardPlayerManager shareInstance].isMineInMic = NO;
}
[[CountDownHelper shareHelper] stopCountDown];
[CountDownHelper shareHelper].delegate = nil;
[[NIMSDK sharedSDK].chatroomManager removeDelegate:self];
[[NIMSDK sharedSDK].chatManager removeDelegate:self];
[[NIMSDK sharedSDK].loginManager removeDelegate:self];
[[NIMSDK sharedSDK].conversationManager removeDelegate:self];
[[NIMSDK sharedSDK].broadcastManager removeDelegate:self];
[[RoomBoomManager sharedManager] removeEventListenerForTarget:self];
// 🔧 修复:移除 block 形式的通知观察者
if (self.exchangeRoomAnimationViewObserver) {
[[NSNotificationCenter defaultCenter] removeObserver:self.exchangeRoomAnimationViewObserver];
self.exchangeRoomAnimationViewObserver = nil;
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 🔧 修复:清理 RoomAnimationView
if (_animationView) {
NSLog(@"🔄 XPRoomViewController: 清理 RoomAnimationView");
[_animationView removeItSelf];
[_animationView removeFromSuperview];
_animationView = nil;
}
// 清理上麦请求弹窗定时器
if (self.upMicAskTimer) {
[self.upMicAskTimer invalidate];
self.upMicAskTimer = nil;
}
NSLog(@"🔄 XPRoomViewController: 销毁完成");
}
- (XPRoomPresenter *)createPresenter {
return [[XPRoomPresenter alloc] init];
}
- (BOOL)isHiddenNavBar {
return YES;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self exitOldRoom];
[self preLoadGifts];
[self initSubViews];
[self initSubViewConstraints];
[self setupNimSDK];
[self loadRoomDataAndUsers];
[self setupNotifications];
[self handleActivityContainerViewEvents];
///获取房间超管列表
[self.presenter getRoomSuperAdmin:self.roomUid];
[XPSkillCardPlayerManager shareInstance].isInRoom = YES;
[CountDownHelper shareHelper].delegate = self;
[self setupForBoom];
[self handleGiftComboCallBack];
[self setupStageViewManager];
// 🔧 新增:启动 Turbo Mode Tips 监听
[[XPTurboModeTipsManager sharedManager] startTipsMonitoringInRoom];
}
//- (void)test {
// XPMineHallAnchorIncomeStatisViewController *vc = [[XPMineHallAnchorIncomeStatisViewController alloc] init];
// [self.navigationController pushViewController:vc animated:YES];
//}
- (void)handleGiftComboCallBack {
@kWeakify(self);
[[GiftComboManager sharedManager] setHandleRoomUIChanged:^(BOOL comboViewDisplay) {
@kStrongify(self);
dispatch_async(dispatch_get_main_queue(), ^{
self.sideMenu.hidden = comboViewDisplay;
self.menuContainerView.hidden = comboViewDisplay;
// 添加状态验证如果要隐藏UI确保连击确实在进行
if (comboViewDisplay && ![[GiftComboManager sharedManager] isActive]) {
NSLog(@"⚠️ 检测到UI隐藏请求但连击未进行执行强制重置");
[self forceBoomStateReset];
}
});
}];
}
- (void)setupForBoom {
@kWeakify(self);
[[RoomBoomManager sharedManager] registerBoomExplosion:^(id _Nonnull sth) {
@kStrongify(self);
[self.presenter getBoomDetail:self.roomUid];
if (![sth isKindOfClass:[NSArray class]]) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[RoomBoomExplosionView display:self.view with:sth complete:^{ }];
});
} target:self];
[[RoomBoomManager sharedManager] registerBoomGiftDisplay:^(id _Nonnull sth) {
@kStrongify(self);
dispatch_async(dispatch_get_main_queue(), ^{
[RoomBoomResultView display:self.view attachment:sth];
});
} target:self];
[[RoomBoomManager sharedManager] registerBoomEnterRoomExplosion:^(id _Nonnull sth) {
@kStrongify(self);
if (![sth isKindOfClass:[NSArray class]]) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
[RoomBoomExplosionView display:self.view with:sth complete:^{
}];
});
} target:self];
}
- (void)requestBoomData {
[self.presenter getBoomRocketAnimationInfo:self.roomUid];
[self.presenter getBoomDetail:self.roomUid];
}
- (void)loadRoomDataAndUsers {
[XNDJTDDLoadingTool showAnchorLoading:self.navigationController.view];
[self.presenter initEnterRoom:self.roomUid user:[AccountInfoStorage instance].getUid];
}
- (void)setupNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myGiftEffectUpdate:) name:kRoomGiftEffectUpdateNotificationKey object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(openRedPacketNotification:) name:@"kOpenRedPacketNotification" object:nil];
// 添加公共房间消息转发通知监听
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handlePublicRoomMessageForward:)
name:@"MessageFromPublicRoomWithAttachmentNotification"
object:nil];
// 添加应用生命周期保护
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
@kWeakify(self);
// 🔧 修复:保存 block 观察者的返回值,防止内存泄漏
self.exchangeRoomAnimationViewObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"kExchangeRoomAnimationViewAndGameViewIndex"
object:nil
queue:NSOperationQueue.mainQueue
usingBlock:^(NSNotification * _Nonnull notification) {
@kStrongify(self);
if (self.view.subviews.count > 0) {
UIView *topView = self.view.subviews.lastObject;
UIView *secondView = [self.view.subviews xpSafeObjectAtIndex:self.view.subviews.count-2];
if (secondView && secondView.tag == 913) {
NSInteger index1 = [self.view.subviews indexOfObject:topView];
NSInteger index2 = [self.view.subviews indexOfObject:secondView];
[self.view exchangeSubviewAtIndex:index1 withSubviewAtIndex:index2];
}
}
}];
}
- (void)setupNimSDK {
//监听云信消息
[[NIMSDK sharedSDK].chatroomManager addDelegate:self];
[[NIMSDK sharedSDK].chatManager addDelegate:self];
[[NIMSDK sharedSDK].loginManager addDelegate:self];
[[NIMSDK sharedSDK].conversationManager addDelegate:self];
[[NIMSDK sharedSDK].broadcastManager addDelegate:self];
}
/// 禮物面板緩存-2: 進房後,用當前的 roomUid 加載最新數據,並緩存
- (void)preLoadGifts {
[self.presenter getNormalGiftList:self.roomUid];
///获取免费礼物
[self.presenter getFreeGiftData];
if(![NSString isEmpty:self.redEnvelopeId]){
[self.presenter getRedPacketInft:self.redEnvelopeId];
}
}
#pragma mark - MSSessionReleaseHeadlinesViewDelegate
-(void)releaseHeadlinesWithText:(NSString *)text{
XPCandyTreeInsufficientBalanceView *balanceView = [[XPCandyTreeInsufficientBalanceView alloc]initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
balanceView.delegate = self;
[self.view addSubview:balanceView];
}
#pragma mark - XPCandyTreeInsufficientBalanceViewDelegate
- (void)payBalanceAction{
XPIAPRechargeViewController * webVC =[[XPIAPRechargeViewController alloc] init];
webVC.type = @"4";
[self.navigationController pushViewController:webVC animated:YES];
}
///打开红包通知
-(void)openRedPacketNotification:(NSNotification *)not{
NSDictionary *redPacketDic = not.userInfo;
NSInteger type = [redPacketDic[@"type"] integerValue];
NSInteger userId = [redPacketDic[@"uid"] integerValue];
// NSString *roomUid = redPacketDic[@"roomUId"];
if(type == 2){
[self.messageContainerView showUserCard:userId];
}else if (type == 3){
// [self.roomHeaderView showSharePanel];
}else if (type == 4){
NSString *scrolling = redPacketDic[@"scrolling"];
[self.menuContainerView showInputView:scrolling];
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.menuContainerView menuResignFirstResponder];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
[self.menuContainerView recheckMicState];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
self.freeView.hidden = YES;
// 🔧 防护:标记 VC 即将不可见
self.isViewActive = NO;
// 🔧 防护:如果 VC 被移除或 dismiss强制清理 TRTC
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
[[RtcManager instance] exitRoom];
self.isExitingRoom = YES;
}
// 如果连击正在进行,强制重置
if ([[GiftComboManager sharedManager] isActive]) {
NSLog(@"📱 房间即将退出,检查连击状态");
[self forceBoomStateReset];
}
}
- (void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
[XPSkillCardPlayerManager shareInstance].isInRoomVC = NO;
// 🔧 防护:确保标记为不可见
self.isViewActive = NO;
// 🔧 修复:发送房间退出通知,让 BuglyManager 知道用户已退出房间
[[NSNotificationCenter defaultCenter] postNotificationName:@"RoomDidExit"
object:nil
userInfo:@{@"roomId": self.roomUid ?: @"unknown"}];
NSLog(@"🎮 房间退出通知已发送 - RoomID: %@", self.roomUid);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.freeView.hidden = NO;
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
[XPSkillCardPlayerManager shareInstance].isInRoomVC = YES;
// 🔧 防护:标记 VC 可见
self.isViewActive = YES;
// 🔧 修复:发送房间进入通知,让 BuglyManager 知道用户已进入房间
[[NSNotificationCenter defaultCenter] postNotificationName:@"RoomDidEnter"
object:nil
userInfo:@{@"roomId": self.roomUid ?: @"unknown"}];
NSLog(@"🎮 房间进入通知已发送 - RoomID: %@", self.roomUid);
}
#pragma mark - 连击状态管理
// 强制重置连击状态和恢复UI
- (void)forceBoomStateReset {
NSLog(@"🔧 XPRoomViewController 强制重置连击状态");
// 1. 直接重置连击管理器(会自动发送通知给所有相关组件)
[[GiftComboManager sharedManager] forceBoomStateReset];
// 2. 确保UI恢复双重保障
dispatch_async(dispatch_get_main_queue(), ^{
self.sideMenu.hidden = NO;
self.menuContainerView.hidden = NO;
NSLog(@"🔄 强制恢复底部操作栏和侧栏显示");
});
}
// 应用生命周期保护
- (void)applicationDidEnterBackground:(NSNotification *)notification {
if ([[GiftComboManager sharedManager] isActive]) {
NSLog(@"📱 应用进入后台,检查连击状态");
[self forceBoomStateReset];
}
}
// 内存警告保护
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
if ([[GiftComboManager sharedManager] isActive]) {
NSLog(@"⚠️ 收到内存警告,检查连击状态");
[self forceBoomStateReset];
}
}
#pragma mark - Private Method
- (void)initSubViews {
self.view.backgroundColor = [UIColor darkGrayColor];
[self.view addSubview:self.backContainerView];
[self.view addSubview:self.littleGameView];
[self.view addSubview:self.messageContainerView];
[self.view addSubview:self.quickMessageContainerView];
[self.view addSubview:self.menuContainerView];
[self.view addSubview:self.sideMenu];
[self.view addSubview:self.redPacketView];
[self.view addSubview:self.roomHeaderView];
[self.view addSubview:self.functionView];
[self.view addSubview:self.animationView];
}
- (void)__layoutTwentyMicStage {
[self.stageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
}
- (void)__layoutNineteenMicStage {
[self.stageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
}
- (void)__layoutFifteenMicStage {
[self.stageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
}
- (void)__layoutTenMicStage {
[self.stageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
}
- (void)initSubViewConstraints {
[self.backContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
[self.littleGameView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
[self.roomHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.top.mas_equalTo(self.view);
make.height.mas_equalTo(kNavigationHeight);
}];
[self.messageContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.roomHeaderView.mas_bottom);
make.bottom.equalTo(self.quickMessageContainerView.mas_top).offset(-5);
make.leading.equalTo(self.view);
make.trailing.equalTo(self.sideMenu.mas_leading).offset(-10);
}];
CGFloat quickMsgHeight = 30;
//记录最后关闭时间
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *closeCount = [defaults objectForKey:kRoomQuickMessageCloseCount];
if (closeCount.intValue > 3) {
quickMsgHeight = 0;
} else {
quickMsgHeight = 30;
}
[self.quickMessageContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(self.view).offset(- 52 - kSafeAreaBottomHeight);
make.height.mas_equalTo(0);
make.leading.trailing.mas_equalTo(self.view);
}];
[self.menuContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
if (iPhoneXSeries) {
make.bottom.mas_equalTo(self.view);
} else {
make.bottom.mas_equalTo(-10);
}
make.height.mas_equalTo(52 + kSafeAreaBottomHeight);
}];
[self.sideMenu mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.messageContainerView);
make.trailing.mas_equalTo(self.view);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).offset(-5);
make.width.mas_equalTo(87);
}];
[self.redPacketView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.mas_equalTo(kGetScaleWidth(58));
make.trailing.mas_equalTo(-kGetScaleWidth(6));
make.bottom.mas_equalTo(-160);
}];
[self.animationView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
[self.functionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
}
- (void)updateViewConstraintsOnAnchorRoom {
[self.anchorScrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.view);
}];
[self.backContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.anchorScrollView.middleImageView);
}];
[self.roomHeaderView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.top.mas_equalTo(self.anchorScrollView.middleImageView);
make.height.mas_equalTo(kNavigationHeight);
}];
[self.stageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.anchorScrollView.middleImageView);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
[self.messageContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.stageView.mas_bottom);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).mas_offset(-5);
make.leading.equalTo(self.anchorScrollView.middleImageView);
make.trailing.equalTo(self.sideMenu.mas_leading).offset(-10);
}];
[self.quickMessageContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(0);
make.bottom.mas_equalTo(self.backContainerView).mas_offset(-5-40-8-kSafeAreaBottomHeight);
make.leading.equalTo(self.anchorScrollView.middleImageView);
make.trailing.equalTo(self.sideMenu.mas_leading).offset(-10);
}];
[self.menuContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.anchorScrollView.middleImageView);
if (iPhoneXSeries) {
make.bottom.mas_equalTo(self.view);
} else {
make.bottom.mas_equalTo(self.view).offset(-10);
}
make.height.mas_equalTo(52 + kSafeAreaBottomHeight);
}];
[self.sideMenu mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.messageContainerView);
make.trailing.mas_equalTo(self.anchorScrollView.middleImageView);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).mas_offset(-5);
make.width.mas_equalTo(87);
}];
[self.functionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.anchorScrollView.middleImageView);
}];
}
- (void)handleActivityContainerViewEvents {
@kWeakify(self);
self.sideMenu.openRedPacketHandle = ^(XPRedPacketModel * _Nullable redModel,RoomType type,BOOL isChangeRoom) {
@kStrongify(self);
if(isChangeRoom == YES){
self.redPacketView.type = type;
self.redPacketView.redPacketList = self.sideMenu.redPacketList;
return;
}
XPReceiveRedPacketView *view = [[XPReceiveRedPacketView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
view.delegate = self;
self.isShowRedPacket = YES;
view.receiveModel = redModel;
for (UIView *subView in self.view.subviews) {
if([subView isKindOfClass:[XPReceiveRedPacketView class]]) {
view.alphaView.backgroundColor = [UIColor clearColor];
break;
}
}
[self.view addSubview:view];
[self.view bringSubviewToFront:view];
};
self.sideMenu.showSendGiftView = ^{
@kStrongify(self);
NSString * roomUid = [NSString stringWithFormat:@"%ld", [self getRoomInfo].uid];
XPSendGiftView * giftView = [[XPSendGiftView alloc] initWithType:SendGiftType_Room uid:roomUid];
giftView.delegate = self;
NSArray * giftUses = [self configGiftUsers:[self getMicroQueue]];
[giftView configGiftUsers:giftUses];
[self.getCurrentNav presentViewController:giftView animated:YES completion:nil];
};
}
- (NSArray<XPGiftUserInfoModel *> *)configGiftUsers:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
NSMutableArray * array = [NSMutableArray array];
for (MicroQueueModel * microModel in queue.allValues) {
if (microModel.userInfo && microModel.userInfo.uid >0) {
UserInfoModel * userInfo = microModel.userInfo;
XPGiftUserInfoModel * userModel = [[XPGiftUserInfoModel alloc] init];
userModel.avatar = userInfo.avatar;
userModel.vipMic = userInfo.vipMic;
userModel.position = [NSString stringWithFormat:@"%d", microModel.microState.position];
userModel.uid = userInfo.uid;
if (self.getRoomInfo.type == RoomType_Anchor && microModel.microState.position == -1) {///个播房一直为离开模式,不需要添加房主位
continue;
}
[array addObject:userModel];
}
}
if (self.getRoomInfo.leaveMode) {
RoomInfoModel * roomInfo= self.getRoomInfo;
XPGiftUserInfoModel * userModel = [[XPGiftUserInfoModel alloc] init];
userModel.avatar = roomInfo.avatar;
userModel.position = @"-1";
userModel.uid = roomInfo.uid;
[array addObject:userModel];
} else if (self.getRoomInfo.type == RoomType_Anchor) {
RoomInfoModel * roomInfo= self.getRoomInfo;
BOOL hadContainerOwner = NO;
for (UserInfoModel *userModel in array) {
if (userModel.uid == roomInfo.uid) {
hadContainerOwner = YES;
break;
}
}
if (!hadContainerOwner) {
XPGiftUserInfoModel * userModel = [[XPGiftUserInfoModel alloc] init];
userModel.avatar = roomInfo.avatar;
userModel.position = @"-1";
userModel.uid = roomInfo.uid;
[array addObject:userModel];
}
}
return array;
}
- (void)exitOldRoom {
NSString * roomUid = [NSString stringWithFormat:@"%ld", [XPRoomMiniManager shareManager].getRoomInfo.uid];
if (roomUid.integerValue > 0) {
if ([roomUid isEqualToString:self.roomUid]) {
self.isMiniEnter = YES;
} else {
//有最小化房间 并且要进入的房间和最小化的房间不一样的话 就先退掉 最小化的房间
NSString * roomId = [NSString stringWithFormat:@"%ld", [XPRoomMiniManager shareManager].getRoomInfo.roomId];
[self.presenter exitNIMRoom:roomId];
[[RtcManager instance] exitRoom];
[[XPRoomMiniManager shareManager] resetLocalMessage];
[self.presenter reportUserOutRoom:roomUid];
[XPSkillCardPlayerManager shareInstance].micState = MICState_None;
}
}
}
-(BOOL)getIsMiniEnter{
return self.isMiniEnter;
}
- (void)userEnterRoomSuccess {
AttachmentModel *attachment = [[AttachmentModel alloc]init];
attachment.first = CustomMessageType_Car_Notify;
attachment.second = Custom_Message_Sub_Car_EnterRoom;
NSMutableDictionary *att = [NSMutableDictionary dictionary];
if (self.userInfo.userVipInfoVO.enterHide) {///VIP隐身进房,不发座驾消息
return;
}
if (self.userInfo.platformRole == 1) {
return;
}
if (self.userInfo.viewUrl.length > 0) {
[att safeSetObject:self.userInfo.nick forKey:@"nick"];
[att safeSetObject:self.userInfo.viewUrl forKey:@"viewUrl"];
[att safeSetObject:@(self.userInfo.otherViewType) forKey:@"otherViewType"];
} else if (self.userInfo.carEffect.length > 0) {
[att safeSetObject:self.userInfo.carEffect forKey:@"effect"];
[att safeSetObject:self.userInfo.nick forKey:@"nick"];
} else {
return;
}
attachment.data = att;
NSString *sessionID = [NSString stringWithFormat:@"%ld",self.roomInfo.roomId];
NIMMessage *message = [[NIMMessage alloc]init];
NIMCustomObject *object = [[NIMCustomObject alloc] init];
object.attachment = attachment;
message.messageObject = object;
//构造会话
NIMSession *session = [NIMSession session:sessionID type:NIMSessionTypeChatroom];
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil];
}
- (BOOL)updateStageView {
NSLog(@"🔄 updateStageView: 开始更新 stageView当前 alpha = %.2f", self.stageView.alpha);
// 优先使用 StageViewManager 来管理 stageView
if (self.stageViewManager) {
NSLog(@"🔧 updateStageView: 使用 StageViewManager");
BOOL success = [self.stageViewManager updateStageViewForRoomType:self.roomInfo.type
roomInfo:self.roomInfo
container:self.view
delegate:self];
if (success) {
self.stageView = self.stageViewManager.currentStageView;
if (self.stageView) {
NSLog(@"🔧 updateStageView: StageViewManager 设置 alpha = 0 (之前 = %.2f)", self.stageView.alpha);
self.stageView.alpha = 0;
NSLog(@"✅ StageViewManager 成功更新 stageView: %@", NSStringFromClass([self.stageView class]));
return YES;
}
}
NSLog(@"⚠️ StageViewManager 更新失败,降级到原有逻辑");
}
// 降级处理:原有逻辑
// NSLog(@"🔧 updateStageView: 使用降级逻辑");
// Class stageViewClass = [self stageViewClassForRoomInfo:self.roomInfo];
// if (stageViewClass) { //&& ![self.stageView isKindOfClass:stageViewClass]) {
// NSLog(@"🔧 updateStageView: 创建新的 stageView: %@", NSStringFromClass(stageViewClass));
// [self.stageView removeFromSuperview];
// self.stageView = nil;
// self.stageView = [[stageViewClass alloc] initWithDelegate:self];
// NSLog(@"🔧 updateStageView: 降级逻辑设置 alpha = 0 (之前 = %.2f)", self.stageView.alpha);
// self.stageView.alpha = 0;
// NSLog(@"✅ 降级逻辑成功更新 stageView: %@", NSStringFromClass([self.stageView class]));
// return YES;
// }
NSLog(@"❌ 所有 stageView 更新方法都失败了");
return NO;
}
- (Class)stageViewClassForRoomInfo:(RoomInfoModel *)roomInfo {
if (roomInfo.roomModeType == RoomModeType_Open_Blind) {
return [DatingStageView class];
}
if (roomInfo.datingState == RoomDatingStateChangeType_Close &&
![self.stageView isKindOfClass:[SocialStageView class]]) {
return [SocialStageView class];
}
if (roomInfo.type == RoomType_Anchor) {
if (roomInfo.roomModeType == RoomModeType_Open_AcrossRoomPK_mode &&
![self.stageView isKindOfClass:[AnchorPKStageView class]]) {
return [AnchorPKStageView class];
} else if (roomInfo.roomModeType == RoomModeType_Normal_Mode &&
![self.stageView isKindOfClass:[AnchorStageView class]]) {
return [AnchorStageView class];
}
} else if (roomInfo.type == RoomType_MiniGame) {
return [LittleGameScrollStageView class];
} else if (roomInfo.type == RoomType_19Mic) {
return [NineteenMicStageView class];
} else if (roomInfo.type == RoomType_20Mic) {
return [TwentyMicStageView class];
} else if (roomInfo.type == RoomType_15Mic) {
return [FifteenMicStageView class];
} else if (roomInfo.type == RoomType_10Mic) {
return [TenMicStageView class];
} else if (roomInfo.hadChangeRoomType && ![self.stageView isKindOfClass:[SocialStageView class]]) {
return [SocialStageView class];
}
return [SocialStageView class];
}
- (void)changeStageViewOnRoomUpdate {
NSLog(@"🔄 changeStageViewOnRoomUpdate: 开始更新 stageView");
if (![self updateStageView]) {
NSLog(@"❌ changeStageViewOnRoomUpdate: updateStageView 失败");
return;
}
NSLog(@"✅ changeStageViewOnRoomUpdate: updateStageView 成功,当前 alpha = %.2f", self.stageView.alpha);
// 🔧 新增:发送房间类型变化通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"RoomTypeChanged"
object:nil
userInfo:@{@"roomType": @(self.roomInfo.type)}];
if (!self.stageView.superview) {
NSLog(@"🔧 changeStageViewOnRoomUpdate: 添加 stageView 到视图层级");
[self.view insertSubview:self.stageView
belowSubview:self.roomHeaderView];
// 防止小屏机麦位挡住按钮
NSInteger index_sideMenu = -1;
NSInteger index_stageView = -1;
for (UIView *view in self.view.subviews) {
if (index_sideMenu == -1 || index_stageView == -1) {
if (view == self.sideMenu) {
index_sideMenu = [self.view.subviews indexOfObject:view];
}
if (view == self.stageView) {
index_stageView = [self.view.subviews indexOfObject:view];
}
} else {
[self.view exchangeSubviewAtIndex:index_sideMenu withSubviewAtIndex:index_stageView];
break;
}
}
}
NSLog(@"🔧 changeStageViewOnRoomUpdate: 设置 stageView.alpha = 1 (之前 = %.2f)", self.stageView.alpha);
self.stageView.alpha = 1;
NSLog(@"✅ changeStageViewOnRoomUpdate: stageView.alpha 已设置为 1");
// 调试:检查最终状态
[self debugStageViewStatus];
// 🔧 新增:如果是 SocialStageView绘制中点矩形
[self drawSocialStageMidpointRects];
// 🔧 新增进房成功后调用CP相关API
[self callMicCpListByRoomUidAfterRoomEntered];
// 🔧 新增stage view类型变化时调用CP相关API
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
[self callMicCpListByUidListWithQueue:currentQueue];
// 🔧 新增检查19 mic房间中央位置position 6用户资格
[self checkCentralPositionUserQualification:currentQueue];
[self addExitGameButton];
self.quickMessageContainerView.hidden = YES;
[self.quickMessageContainerView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(0);
}];
switch (self.roomInfo.type) {
case RoomType_MiniGame: {
[self.stageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom).offset(26 + 4);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
CGFloat height = 80;
if (iPhoneXSeries) {
height = 100;
}
[self.messageContainerView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(height);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).offset(-5);
make.leading.mas_equalTo(self.view);
make.trailing.mas_equalTo(self.sideMenu.mas_leading).offset(-10);
}];
[self.sideMenu displayForMiniGame];
[self.sideMenu mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(self.view);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).offset(-5);
make.width.mas_equalTo(87);
}];
}
break;
default: {
[self.stageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
[self.messageContainerView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.stageView.mas_bottom);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).offset(-5);
make.leading.mas_equalTo(self.view);
make.trailing.mas_equalTo(self.sideMenu.mas_leading).offset(-10);
}];
if (self.roomInfo.type == RoomType_19Mic || self.roomInfo.type == RoomType_20Mic) {
[self.sideMenu mas_remakeConstraints:^(MASConstraintMaker *make) {
make.trailing.mas_equalTo(self.view);
make.height.mas_equalTo(160);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).offset(-5);
make.width.mas_equalTo(87);
}];
} else {
[self.sideMenu mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.messageContainerView);
make.trailing.mas_equalTo(self.view);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).offset(-5);
make.width.mas_equalTo(87);
}];
}
}
break;
}
}
- (void)changeStageViewOnAnchorRoom {
if (self.roomInfo.roomModeType == RoomModeType_Open_AcrossRoomPK_mode && ![self.stageView isKindOfClass:[AnchorPKStageView class]]) {
[self.stageView removeFromSuperview];
self.stageView = nil;
self.stageView = [[AnchorPKStageView alloc] initWithDelegate:self];
} else if(self.roomInfo.roomModeType == RoomModeType_Normal_Mode && ![self.stageView isKindOfClass:[AnchorStageView class]]) {
[self.stageView removeFromSuperview];
self.stageView = nil;
self.stageView = [[AnchorStageView alloc] initWithDelegate:self];
}
if (!self.stageView.superview) {
[self.anchorScrollView insertSubview:self.stageView belowSubview:self.roomHeaderView];
}
[self.stageView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.anchorScrollView.middleImageView);
make.top.mas_equalTo(self.roomHeaderView.mas_bottom);
make.height.mas_equalTo(self.stageView.hightForStageView);
}];
[self.messageContainerView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.stageView.mas_bottom);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).mas_offset(-5);
make.leading.mas_equalTo(self.anchorScrollView.middleImageView);
make.trailing.mas_equalTo(self.sideMenu.mas_leading).offset(-10);
}];
CGFloat quickMsgHeight = 30;
//记录最后关闭时间
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *closeCount = [defaults objectForKey:kRoomQuickMessageCloseCount];
if (closeCount.intValue > 3 || self.roomInfo.speakTemplate.count <=0) {
quickMsgHeight = 0;
} else {
quickMsgHeight = 30;
}
self.quickMessageContainerView.titleArray = self.roomInfo.speakTemplate;
[self.quickMessageContainerView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(0);
make.bottom.mas_equalTo(self.backContainerView).mas_offset(-5-40-8-kSafeAreaBottomHeight);
make.leading.trailing.mas_equalTo(self.anchorScrollView.middleImageView);
}];
[self.sideMenu mas_remakeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.messageContainerView);
make.trailing.mas_equalTo(self.anchorScrollView.middleImageView);
make.bottom.mas_equalTo(self.quickMessageContainerView.mas_top).mas_offset(-5);
make.width.mas_equalTo(87);
}];
}
- (void)setRoomInfo:(RoomInfoModel *)roomInfo {
if (_roomInfo == roomInfo) {
return;
}
_roomInfo = roomInfo;
NSString *roomId = @(self.roomInfo.roomId).stringValue;
NSLog(@"🎮 设置房间ID: %@", roomId);
[[TurboModeStateManager sharedManager] setCurrentRoomId:roomId];
[[LuckyPackageLogicManager sharedInstance] setRoomUid:@(roomInfo.uid).stringValue];
[[RoomResourceManager sharedManager] updateCurrentSkinID:roomInfo.usedMicSkinId
effectID:roomInfo.usedMicEffectId];
[self.sideMenu updateView];
}
#pragma mark - Game Switch
- (void)addExitGameButton {
[self removeExitGameButton];
if (self.roomInfo.type != RoomType_MiniGame) {
return;
}
if (_exitGameButton == nil) {
_exitGameButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_exitGameButton setTitle:@"切换" forState:UIControlStateNormal];
[_exitGameButton setImage:[UIImage imageNamed:@"ms_room_game_switch_button"] forState:UIControlStateNormal];
[_exitGameButton addTarget:self action:@selector(didTapExitGameButton) forControlEvents:UIControlEventTouchUpInside];
}
[self.view addSubview:self.exitGameButton];
[self.exitGameButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.stageView.mas_bottom).offset(10);
make.leading.mas_equalTo(self.view).offset(12);
make.width.height.equalTo(@21);
}];
}
- (void)removeExitGameButton {
[self.exitGameButton removeFromSuperview];
}
- (void)didTapExitGameButton {
if ([self.littleGameView isInSudGame]) {
[XNDJTDDLoadingTool showErrorWithMessage:YMLocalizedString(@"XPRoomFunctionContainerView19")];
} else {
@kWeakify(self);
[TTPopup alertWithMessage:YMLocalizedString(@"XPRoomFunctionContainerView20") confirmHandler:^{
@kStrongify(self);
[XNDJTDDLoadingTool showLoading];
[self exitLittleGame];
} cancelHandler:^{ }];
}
}
- (void)exitLittleGame {
RoomInfoModel * roomInfo = self.getRoomInfo;
NSMutableDictionary * params = [NSMutableDictionary dictionary];
NSString * ticket = [AccountInfoStorage instance].getTicket;
[params setObject:ticket forKey:@"ticket"];
[params setObject:@(roomInfo.uid).stringValue forKey:@"uid"];
[params setObject:[NSString stringWithFormat:@"%ld", roomInfo.uid] forKey:@"roomUid"];
if (roomInfo.title.length > 0) {
[params setObject:roomInfo.title forKey:@"title"];
}
if (roomInfo.roomPwd.length > 0) {
[params setObject:roomInfo.roomPwd forKey:@"roomPwd"];
} else{
[params setObject:@"" forKey:@"roomPwd"];
}
if (roomInfo.tagId > 0) {
[params setObject:[NSString stringWithFormat:@"%ld", roomInfo.tagId] forKey:@"tagId"];
}
if (self.roomInfo.oldType == 0) {
self.roomInfo.oldType = RoomType_Room;
}
[params setObject:@(self.roomInfo.oldType) forKey:@"type"];
[params setObject:@"0" forKey:@"mgId"];
[params setObject:@(roomInfo.hasAnimationEffect) forKey:@"hasAnimationEffect"];
[Api ownerUpdateRoomInfo:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
[XNDJTDDLoadingTool hideHUD];
} else {
[XNDJTDDLoadingTool showErrorWithMessage:msg];
}
} params:params];
[self.littleGameView handleSelfInExitEvent];
[self.littleGameView destroyMG];
}
#pragma mark- XPReceiveRedPacketViewDelegate
- (void)closeViewAction{
self.isShowRedPacket = NO;
}
#pragma mark- PIRoomEnterRedPacketViewDelegate
- (void)openRedPacketWithModel:(XPRedPacketModel *)redModel{
XPReceiveRedPacketView *view = [[XPReceiveRedPacketView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
view.receiveModel = redModel;
view.delegate = self;
self.isShowRedPacket = YES;
for (UIView *subView in self.view.subviews) {
if([subView isKindOfClass:[XPReceiveRedPacketView class]]) {
view.alphaView.backgroundColor = [UIColor clearColor];
break;
}
}
[self.view addSubview:view];
[self.view bringSubviewToFront:view];
}
#pragma mark - CountDownHelperDelegate
- (void)onCountdownFinish {
[[NSNotificationCenter defaultCenter]postNotificationName:kFreeGiftCountdownNotification object:nil userInfo:@{@"isCountdownFinish":@(YES)}];
if(self.freeModel.curStage.integerValue < self.freeModel.maxStage.integerValue){
[self.presenter getFreeGiftData];
}
if(self.freeModel.curStage.intValue == 0){
self.freeView.freeModel = self.freeModel;
if(!self.freeView.superview){
[kWindow addSubview:self.freeView];
}
}
}
- (void)onCountdownOpen:(int)time {
NSInteger seconds = time;
int hours = (seconds)%(24*3600)/3600;
int minutes = (seconds)%3600/60;
int second = (seconds)%60;
[[NSNotificationCenter defaultCenter] postNotificationName:kFreeGiftCountdownNotification
object:nil
userInfo:@{@"text": [NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, second],
@"isCountdownFinish": @(NO)}];
}
- (void)cancelRoomArrangeMic {
///退出排麦
if (self.roomInfo.roomModeType == RoomModeType_Open_Micro_Mode || self.roomInfo.roomModeType == RoomModeType_Open_Blind) {
NSString * roomUid = [NSString stringWithFormat:@"%ld", self.roomInfo.uid];
NSString * uid = [AccountInfoStorage instance].getUid;
[Api getRoomArrangeMicList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
ArrangeMicModel * arrangeMicModel= [ArrangeMicModel modelWithJSON:data.data];
if (arrangeMicModel.myPos.intValue > 0) {
__block NSString * grouptype = @"0";
if (self.roomInfo.roomModeType == RoomModeType_Open_Blind) {
[arrangeMicModel.queue enumerateObjectsUsingBlock:^(ArrangeMicUserModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.uid.integerValue == [AccountInfoStorage instance].getUid.integerValue) {
grouptype = [NSString stringWithFormat:@"%ld", obj.groupType];
*stop = YES;
}
}];
}
[Api cancelArrangeMic:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {} roomUid:roomUid operUid:uid groupType:grouptype];
}
}
} roomUid:roomUid operUid:uid page:@"1" pageSize:@"50"];
}else if(self.roomInfo.roomModeType == RoomModeType_Open_PK_Mode) {
NSString * roomUid = [NSString stringWithFormat:@"%ld", self.roomInfo.uid];
NSString * uid = [AccountInfoStorage instance].getUid;
[Api getRoomPKUserList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
ArrangeMicModel * arrangeMicModel= [ArrangeMicModel modelWithJSON:data.data];
if (arrangeMicModel.myPos.intValue > 0) {
__block NSString * grouptype = @"0";
[arrangeMicModel.queue enumerateObjectsUsingBlock:^(ArrangeMicUserModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.uid.integerValue == [AccountInfoStorage instance].getUid.integerValue) {
grouptype = [NSString stringWithFormat:@"%ld", obj.groupType];
*stop = YES;
}
}];
[Api cancelRoomPKArrangeMic:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
} roomUid:roomUid operUid:uid groupType:grouptype];
}
}
} roomUid:roomUid operUid:uid page:@"1" pageSize:@"50"];
}
}
///超管进入密码房间
- (void)superAdminEnterPwdRoom:(RoomInfoModel *)roomInfo {
__block BOOL isSuperAdmin = NO;
[self.superMangerList enumerateObjectsUsingBlock:^(GuildSuperAdminInfoModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj.uid isEqualToString:[AccountInfoStorage instance].getUid]) {
isSuperAdmin = YES;
*stop = YES;
}
}];
if (!isSuperAdmin) {
isSuperAdmin = self.userInfo.platformRole == 1;
}
if (!isSuperAdmin) {
XPRoomSettingInputView * titleView = [[XPRoomSettingInputView alloc] init];
titleView.maxCount = 15;
titleView.delegate = self;
titleView.type = RoomSettingInputType_Input_Pwd;
TTPopupConfig * config = [[TTPopupConfig alloc] init];
config.contentView = titleView;
config.maskBackgroundAlpha = 1;
config.shouldDismissOnBackgroundTouch = NO;
[TTPopup popupWithConfig:config];
} else {
self.roomInfo.datingState = roomInfo.roomModeType == RoomModeType_Open_Blind ? RoomDatingStateChangeType_Open : RoomDatingStateChangeType_Normal;
[self changeStageViewOnRoomUpdate];
[self.roomHeaderView onRoomEntered];
[self.sideMenu onRoomEntered];
[self.backContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
if ([XPRoomMiniManager shareManager].getRoomInfo.uid != self.roomUid.integerValue) {// 最小化进房 还是原来的房间的话 不需要重新进入云信 因为压根没退
// 🔧 防护:检查是否允许进房
if (!self.isExitingRoom && self.isViewActive) {
[self.presenter enterNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId] user:self.userInfo];
}
[self.functionView onRoomEntered];
[self.messageContainerView onRoomEntered];
[self.menuContainerView onRoomEntered];
} else {
//虽然没退出房间 但是队列还是要拿的
[self.stageView onRoomMiniEntered];
[self.messageContainerView onRoomMiniEntered];
[self.functionView onRoomMiniEntered];
[self.functionView onRoomEntered];
[self.menuContainerView onRoomMiniEntered];
// 🔧 最小化进房:初始化当前用户麦位状态
[self initializeCurrentUserMicStatusForMiniEnter];
}
[[XPRoomMiniManager shareManager] configRoomInfo:nil];
[[XPRoomMiniManager shareManager] configUserInfo:nil];
[[XPRoomMiniManager shareManager] configCurrentMusic:nil isPlaying:NO];
}
}
///超管进个播有密码房间
- (void)handleSuperManagerEnterAnchorRoom:(RoomInfoModel *)roomInfo {
__block BOOL isSuperAdmin = NO;
[self.superMangerList enumerateObjectsUsingBlock:^(GuildSuperAdminInfoModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj.uid isEqualToString:[AccountInfoStorage instance].getUid]) {
isSuperAdmin = YES;
*stop = YES;
}
}];
if (!isSuperAdmin) {
isSuperAdmin = self.userInfo.platformRole == 1;
}
if (!isSuperAdmin) {
XPRoomSettingInputView * titleView = [[XPRoomSettingInputView alloc] init];
titleView.maxCount = 15;
titleView.delegate = self;
titleView.type = RoomSettingInputType_Input_Pwd;
TTPopupConfig * config = [[TTPopupConfig alloc] init];
config.contentView = titleView;
config.maskBackgroundAlpha = 1;
config.shouldDismissOnBackgroundTouch = NO;
[TTPopup popupWithConfig:config];
} else {
self.roomInfo.datingState = roomInfo.roomModeType == RoomModeType_Open_Blind ? RoomDatingStateChangeType_Open : RoomDatingStateChangeType_Normal;
if (!self.anchorScrollView.superview) {
[self.view insertSubview:self.anchorScrollView belowSubview:self.menuContainerView];
}
[self.backContainerView removeFromSuperview];
[self.stageView removeFromSuperview];
[self.messageContainerView removeFromSuperview];
[self.quickMessageContainerView removeFromSuperview];
[self.sideMenu removeFromSuperview];
[self.roomHeaderView removeFromSuperview];
[self.functionView removeFromSuperview];
[self.anchorScrollView addSubview:self.backContainerView];
[self.anchorScrollView addSubview:self.stageView];
[self.anchorScrollView addSubview:self.messageContainerView];
[self.anchorScrollView addSubview:self.quickMessageContainerView];
[self.anchorScrollView addSubview:self.sideMenu];
[self.anchorScrollView addSubview:self.roomHeaderView];
if (!self.menuContainerView.superview) {
[self.view addSubview:self.menuContainerView];
}
[self.anchorScrollView addSubview:self.functionView];
[self.view bringSubviewToFront:self.animationView];
[self updateViewConstraintsOnAnchorRoom];
[self changeStageViewOnAnchorRoom];
[self.roomHeaderView onRoomEntered];
[self.sideMenu onRoomEntered];
[self.backContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
if ([XPRoomMiniManager shareManager].getRoomInfo.uid != self.roomUid.integerValue) {// 最小化进房 还是原来的房间的话 不需要重新进入云信 因为压根没退
// 🔧 防护:检查是否允许进房
if (!self.isExitingRoom && self.isViewActive) {
[self.presenter enterNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId] user:self.userInfo];
}
[self.functionView onRoomEntered];
[self.messageContainerView onRoomEntered];
[self.menuContainerView onRoomEntered];
} else {
//虽然没退出房间 但是队列还是要拿的
[self.stageView onRoomMiniEntered];
[self.messageContainerView onRoomMiniEntered];
[self.functionView onRoomMiniEntered];
[self.functionView onRoomEntered];
[self.menuContainerView onRoomMiniEntered];
// 🔧 最小化进房:初始化当前用户麦位状态
[self initializeCurrentUserMicStatusForMiniEnter];
}
[[XPRoomMiniManager shareManager] configRoomInfo:nil];
[[XPRoomMiniManager shareManager] configUserInfo:nil];
[[XPRoomMiniManager shareManager] configCurrentMusic:nil isPlaying:NO];
}
}
///展示个播上划引导
- (void)showAnchorScrollTipView {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *anchoScrollTip = [defaults objectForKey:kHadShowAnchorRoomTipKey];
if (!anchoScrollTip) {
UIWindow *window = [UIApplication sharedApplication].delegate.window;
[window addSubview:self.anchorScrollTipView];
[window bringSubviewToFront:self.anchorScrollTipView];
}
}
-(void)requesstShieldingAction{
[self.presenter requestShieldingWithType:@"1" objId:[NSString stringWithFormat:@"%ld",self.roomInfo.uid]];
}
-(void)requestShieldingSuccess{
[self showErrorToast:YMLocalizedString(@"RoomHeaderView11")];
[self exitRoom];
}
#pragma mark - 本地礼物特效更新
- (void)myGiftEffectUpdate:(NSNotification *)notification {
NSDictionary * dic = notification.object;
self.roomInfo.hasAnimationEffect = ((NSNumber *)dic[@"hasAnimationEffect"]).boolValue;
self.hasAnimationEffect = self.roomInfo.hasAnimationEffect;
[self.roomHeaderView onRoomUpdate];
}
#pragma mark - XPRoomProtocol
///获取已解锁照片id列表
-(void)getUnlockRoomAlbumPhotoListSuccessWithList:(NSArray *)list{}
///获取踢人房间列表
-(void)getKickUserListSuccessWithList:(NSArray *)list{
NSString *uid = [[AccountInfoStorage instance]getUid];
for (id obj in list) {
if([obj integerValue] == [uid integerValue]){
[self exitRoom];
[XNDJTDDLoadingTool showErrorWithMessage:YMLocalizedString(@"XPRoomViewController0")];
break;
}
}
}
- (void)handleRoomWithPasswordAfterInitRoom {
if (!self.isRequestSuperAdmin) {
@kWeakify(self);
[Api getRoomSuperAdminList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
@kStrongify(self);
NSArray<GuildSuperAdminInfoModel *> * array = [GuildSuperAdminInfoModel modelsWithArray:data.data];
self.superMangerList = array;
[self superAdminEnterPwdRoom:self.roomInfo];
} roomUid:self.roomUid];
} else {
[self superAdminEnterPwdRoom:self.roomInfo];
}
}
- (void)handleRoomWithoutPasswordAfterInitRoom {
NSLog(@"🔄 handleRoomWithoutPasswordAfterInitRoom: 开始处理无密码房间");
self.roomInfo.datingState = (self.roomInfo.roomModeType == RoomModeType_Open_Blind) ?
RoomDatingStateChangeType_Open :
RoomDatingStateChangeType_Normal;
NSLog(@"🔧 handleRoomWithoutPasswordAfterInitRoom: 调用 changeStageViewOnRoomUpdate");
[self changeStageViewOnRoomUpdate];
NSLog(@"✅ handleRoomWithoutPasswordAfterInitRoom: changeStageViewOnRoomUpdate 完成");
[self.roomHeaderView onRoomEntered];
[self.sideMenu onRoomEntered];
[self.backContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
self.quickMessageContainerView.titleArray = self.roomInfo.speakTemplate;
if ([XPRoomMiniManager shareManager].getRoomInfo.uid != self.roomUid.integerValue) {
[self.functionView onRoomEntered];
[self.messageContainerView onRoomEntered];
[self.menuContainerView onRoomEntered];
// 通知云信重新进房
[self.presenter enterNIMRoom:@(self.roomInfo.roomId).stringValue
user:self.userInfo];
} else {
//虽然没退出房间 但是队列还是要拿的
[self.stageView onRoomMiniEntered];
[self.functionView onRoomMiniEntered];
[self.sideMenu onRoomMiniEntered];
[self.functionView onRoomEntered];
[self.menuContainerView onRoomMiniEntered];
// PK 状态下,从最小化进房需要模仿初始化房间,丢 2 次 roominfo 进去 functionView
if (self.roomInfo.roomModeType == RoomModeType_Open_AcrossRoomPK_mode ||
self.roomInfo.roomModeType == RoomModeType_Open_PK_Mode) {
[self.functionView onRoomEntered];
}
[self.messageContainerView onRoomMiniEntered];
// 🔧 最小化进房:初始化当前用户麦位状态
[self initializeCurrentUserMicStatusForMiniEnter];
}
[self cleanMiniRoomStatues];
}
- (void)enterMyRoom {
if (self.roomInfo.roomId > 0) { // 存在房间信息,直接使用房间信息开房
if (self.roomInfo.type == RoomType_MiniGame &&
self.roomInfo.mgId > 0) {
self.mgId = [NSString stringWithFormat:@"%lld", self.roomInfo.mgId];
}
[self.presenter openRoom:self.roomInfo.title
type:self.roomInfo.type
roomPwd:self.roomInfo.roomPwd
roomDesc:self.roomInfo.roomDesc
backPic:@""
mgId:self.mgId];
} else { // 不存在房间信息,全新开房
NSString* title = [NSString stringWithFormat:YMLocalizedString(@"XPRoomViewController1"), self.userInfo.nick];
RoomType type = RoomType_Room;
if (self.mgId.length > 0) {
type = RoomType_MiniGame;
}
[self.presenter openRoom:title
type:type
roomPwd:@""
roomDesc:@""
backPic:@""
mgId:self.mgId];
}
}
- (void)cleanMiniRoomStatues {
[[XPRoomMiniManager shareManager] configRoomInfo:nil];
[[XPRoomMiniManager shareManager] configUserInfo:nil];
[[XPRoomMiniManager shareManager] configCurrentMusic:nil isPlaying:NO];
}
- (void)initEnterRoomSuccess:(RoomInfoModel *)roomInfo user:(UserInfoModel *)userInfo {
[XNDJTDDLoadingTool hideHUDInView:self.navigationController.view];
[XPSkillCardPlayerManager shareInstance].roomUid = @(roomInfo.uid).stringValue;
userInfo.fromUid = self.fromUid;
userInfo.fromType = self.fromType;
userInfo.fromNick = self.fromNick;
self.hasAnimationEffect = roomInfo.hasAnimationEffect;
self.roomInfo = roomInfo;
self.userInfo = userInfo;
// 修复首次获取roominfo成功后同步礼物特效状态到开关
NSString *roomId = @(roomInfo.roomId).stringValue;
[[TurboModeStateManager sharedManager] updateGiftEffectsForRoom:roomId
enabled:roomInfo.hasAnimationEffect];
[self requestBoomData];
@kWeakify(self);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
@kStrongify(self);
//获取一下红包信息
[self.presenter getRedPacket:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
[self getOnlineCount];
});
switch (roomInfo.type) {
case RoomType_Anchor:
[self handleInitAnchorRoom];
[self showAnchorScrollTipView];
break;
default: {
if (roomInfo.valid) { // 房间有效,直接进入房间
if (roomInfo.roomPwd.length > 0 &&
roomInfo.uid != [AccountInfoStorage instance].getUid.integerValue &&
[XPRoomMiniManager shareManager].getRoomInfo.uid != self.roomUid.integerValue) {//进房密码的情况
[self handleRoomWithPasswordAfterInitRoom];
} else {
[self handleRoomWithoutPasswordAfterInitRoom];
}
} else { // 房间无效,分是否为自己房间进行处理
if ([self.roomUid isEqualToString:[NSString stringWithFormat:@"%ld", userInfo.uid]]) {
// 当前用户进入自己房间
[self enterMyRoom];
} else { // 当前用户进入别人房间
[self showSuccessToast:YMLocalizedString(@"XPRoomViewController2")];
[self enterRoomFail:0];
}
} }
break;
}
[self.menuContainerView recheckMicState];
}
- (void)openRoomSuccess:(RoomInfoModel *)roomInfo {
[XNDJTDDLoadingTool hideHUDInView:self.navigationController.view];
self.roomInfo = roomInfo;
[XPSkillCardPlayerManager shareInstance].is9Mic = roomInfo.type == RoomType_Room;
if (roomInfo.type == RoomType_Anchor) {
[self handleInitAnchorRoom];
[self showAnchorScrollTipView];
return;
}
[self changeStageViewOnRoomUpdate];
[self.roomHeaderView onRoomEntered];
[self.sideMenu onRoomEntered];
[self.backContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
[self.functionView onRoomEntered];
[self.messageContainerView onRoomEntered];
self.quickMessageContainerView.titleArray = self.roomInfo.speakTemplate;
// 🔧 防护:检查是否允许进房
if (!self.isExitingRoom && self.isViewActive) {
[self.presenter enterNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId] user:self.userInfo];
}
}
- (void)enterRoomSuccess:(NIMChatroom *)chatRoom {
[XNDJTDDLoadingTool hideHUDInView:self.navigationController.view];
// 🔧 防护:检查 VC 是否仍然有效
if (self.isExitingRoom || !self.isViewActive) {
NSLog(@"🔧 enterRoomSuccess: VC 已退出或不可见,忽略回调");
return;
}
[self.stageView onRoomEntered];
[self.functionView onRoomEntered];
// 🔧 新增:初始化当前用户的麦位状态
[self initializeCurrentUserMicStatus];
//上报进房
if (self.roomInfo != nil) {
[self.presenter reportUserInterRoom:[NSString stringWithFormat:@"%zd", self.roomInfo.uid]];
}
if (self.giftId.length) {
///有的时候 拉取麦序的话 可能会有点延迟 导致送礼物的人 没有拉取到
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"kShowGiftView" object:@{@"giftId": self.giftId}];
});
}
}
- (void)enterRoomFail:(NSInteger)code {
[XNDJTDDLoadingTool hideHUDInView:self.navigationController.view];
[self hideHUD];
// 🔧 防护:检查 VC 是否仍然有效
if (self.isExitingRoom || !self.isViewActive) {
NSLog(@"🔧 enterRoomFail: VC 已退出或不可见,忽略回调 (code: %ld)", (long)code);
return;
}
if (code == 13003) {
[self showErrorToast:YMLocalizedString(@"XPRoomViewController3")];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)getOnlineCount{
@kWeakify(self);
[Api requestRoomOnlineUserList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
@kStrongify(self);
if(code == 200){
NSMutableArray *temp = @[].mutableCopy;
NSArray *list = [XPMessageRemoteExtModel modelsWithArray:data.data];
for (XPMessageRemoteExtModel *model in list) {
if (model.platformRole != 1) {
[temp addObject:model];
}
}
self.roomInfo.onlineNum = temp.count;
[self.functionView updateOnlineCount:[NSString stringWithFormat:@"%ld",temp.count] countList:temp.copy isUpdateCount:NO];
}
// 更安全的輪詢
@kWeakify(self);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@kStrongify(self);
if (self) {
[self getOnlineCount];
}
});
} roomUid:self.roomUid];
}
///请求房间超管成功
///这个接口本来是想和roominfo 和userinfo 一起请求的 但是 进房的同步操作 只有超管进入密码房间才会有同步的问题
- (void)getRoomSuperAdminSuccess:(NSArray *)list {
self.isRequestSuperAdmin = YES;
self.superMangerList = list;
}
///获取推荐滚动的个播房列表(只有第一次进房获取)
- (void)getNextAnchorRoomSuccess:(RoomInfoModel *)roomInfo {
[self hideHUD];
if (roomInfo) { //获取下一个房间成功
if (![self.anchorRoomList containsObject:self.roomUid]) {
[self.anchorRoomList addObject:self.roomUid];
}
self.anchorIndex = self.anchorRoomList.count -1;
//1、退出房间操作
self.anchorScrollView.isCanScrollTop = YES;
self.anchorScrollView.contentOffset = CGPointMake(0, KScreenHeight);
self.isRequestSuperAdmin = NO;
[self exitOldRoom];
[self.presenter exitNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId]];
[[XPRoomMiniManager shareManager] resetLocalMessage];
[[RtcManager instance] exitRoom];
[self.presenter reportUserOutRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
[self.functionView onRoomChange];
//2、移除房间子控件
[self.stageView removeFromSuperview];
[self.messageContainerView removeFromSuperview];
[self.quickMessageContainerView removeFromSuperview];
[self.sideMenu removeFromSuperview];
[self.roomHeaderView removeFromSuperview];
[self.functionView removeFromSuperview];
self.functionView = nil;
self.messageContainerView = nil;
self.stageView = nil;
[self.menuContainerView removeFromSuperview];
self.menuContainerView = nil;
//3、重新进入房间
self.roomUid = [NSString stringWithFormat:@"%zd", roomInfo.uid];
[self.presenter initEnterRoom:self.roomUid user:[AccountInfoStorage instance].getUid];
/// 公屏 福袋礼物爆出的礼物 需要依赖 当前房间中的礼物列表 需要提前获取礼物列表 可以拿到礼物 没有想到别的方法去解决这个问题 有需要改的在修改吧
[self.presenter getNormalGiftList:self.roomUid];
///获取房间超管列表
[self.presenter getRoomSuperAdmin:self.roomUid];
} else {
if (self.anchorScrollView.isCanScrollTop) {
self.anchorScrollView.contentOffset = CGPointMake(0, KScreenHeight);
} else {
self.anchorScrollView.contentOffset = CGPointMake(0, 0);
}
[self showErrorToast:YMLocalizedString(@"XPRoomViewController4")];
}
}
- (void)getCurrentRoomInfoSuccess:(RoomInfoModel *)roomInfo {
[self hideHUD];
self.anchorScrollView.contentOffset = CGPointMake(0, KScreenHeight);
if (roomInfo && roomInfo.valid) {
//1、退出房间操作
self.isRequestSuperAdmin = NO;
[self exitOldRoom];
[self.presenter exitNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId]];
[[XPRoomMiniManager shareManager] resetLocalMessage];
[[RtcManager instance] exitRoom];
[self.presenter reportUserOutRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
//2、移除房间子控件
// [self.backContainerView removeFromSuperview];
[self.stageView removeFromSuperview];
[self.messageContainerView removeFromSuperview];
[self.quickMessageContainerView removeFromSuperview];
[self.sideMenu removeFromSuperview];
[self.roomHeaderView removeFromSuperview];
[self.functionView removeFromSuperview];
self.functionView = nil;
self.messageContainerView = nil;
self.stageView = nil;
[self.menuContainerView removeFromSuperview];
self.menuContainerView = nil;
//3、重新进入房间
self.roomUid = [NSString stringWithFormat:@"%zd", roomInfo.uid];
[self.presenter initEnterRoom:self.roomUid user:[AccountInfoStorage instance].getUid];
/// 公屏 福袋礼物爆出的礼物 需要依赖 当前房间中的礼物列表 需要提前获取礼物列表 可以拿到礼物 没有想到别的方法去解决这个问题 有需要改的在修改吧
[self.presenter getNormalGiftList:self.roomUid];
///获取房间超管列表
[self.presenter getRoomSuperAdmin:self.roomUid];
} else {
[self showErrorToast:YMLocalizedString(@"XPRoomViewController5")];
}
}
///个播房初始化一些操作
- (void)handleInitAnchorRoom {
if (self.roomInfo.valid) { // 房间有效,直接进入房间
if (self.roomInfo.roomPwd.length > 0 &&
self.roomInfo.uid != [AccountInfoStorage instance].getUid.integerValue &&
[XPRoomMiniManager shareManager].getRoomInfo.uid != self.roomUid.integerValue) {//进房密码的情况
if (!self.isRequestSuperAdmin) {
@kWeakify(self);
[Api getRoomSuperAdminList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
@kStrongify(self);
NSArray<GuildSuperAdminInfoModel *> * array = [GuildSuperAdminInfoModel modelsWithArray:data.data];
self.superMangerList = array;
[self handleSuperManagerEnterAnchorRoom:self.roomInfo];
} roomUid:self.roomUid];
} else {
[self handleSuperManagerEnterAnchorRoom:self.roomInfo];
}
} else {
self.roomInfo.datingState = self.roomInfo.roomModeType == RoomModeType_Open_Blind ? RoomDatingStateChangeType_Open : RoomDatingStateChangeType_Normal;
[self __removeAllViews];
if (!self.anchorScrollView.superview) {
[self.view addSubview:self.anchorScrollView];
}
[self.anchorScrollView addSubview:self.backContainerView];
[self.anchorScrollView addSubview:self.stageView];
[self.anchorScrollView addSubview:self.messageContainerView];
[self.anchorScrollView addSubview:self.quickMessageContainerView];
[self.anchorScrollView addSubview:self.sideMenu];
[self.anchorScrollView addSubview:self.roomHeaderView];
[self.anchorScrollView addSubview:self.menuContainerView];
[self.anchorScrollView addSubview:self.functionView];
[self.view bringSubviewToFront:self.animationView];
[self updateViewConstraintsOnAnchorRoom];
[self changeStageViewOnAnchorRoom];
[self.roomHeaderView onRoomEntered];
[self.sideMenu onRoomEntered];
[self.backContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
if ([XPRoomMiniManager shareManager].getRoomInfo.uid != self.roomUid.integerValue) {// 最小化进房 还是原来的房间的话 不需要重新进入云信 因为压根没退
// 🔧 防护:检查是否允许进房
if (!self.isExitingRoom && self.isViewActive) {
[self.presenter enterNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId] user:self.userInfo];
}
[self.functionView onRoomEntered];
[self.messageContainerView onRoomEntered];
[self.menuContainerView onRoomEntered];
} else {
//虽然没退出房间 但是队列还是要拿的
[self.stageView onRoomMiniEntered];
[self.messageContainerView onRoomMiniEntered];
[self.functionView onRoomMiniEntered];
[self.functionView onRoomEntered];
[self.menuContainerView onRoomMiniEntered];
// 🔧 最小化进房:初始化当前用户麦位状态
[self initializeCurrentUserMicStatusForMiniEnter];
}
[self cleanMiniRoomStatues];
}
} else { // 房间无效,分是否为自己房间进行处理
if ([self.roomUid isEqualToString:[NSString stringWithFormat:@"%ld", self.userInfo.uid]]) { // 当前用户进入自己房间
if (self.roomInfo.roomId > 0) { // 存在房间信息,直接使用房间信息开房
if (self.roomInfo.type == RoomType_MiniGame && self.roomInfo.mgId > 0) {
self.mgId = [NSString stringWithFormat:@"%lld", self.roomInfo.mgId];
}
[self.presenter openRoom:self.roomInfo.title type:self.roomInfo.type roomPwd:self.roomInfo.roomPwd roomDesc:self.roomInfo.roomDesc backPic:@"" mgId:self.mgId];
} else { // 不存在房间信息,全新开房
NSString* title = [NSString stringWithFormat:YMLocalizedString(@"XPRoomViewController6"), self.userInfo.nick];
RoomType type = RoomType_Room;
if (self.mgId.length > 0) {
type = RoomType_MiniGame;
}
[self.presenter openRoom:title type:type roomPwd:@"" roomDesc:@"" backPic:@"" mgId:self.mgId];
}
} else { // 当前用户进入别人房间
[self showSuccessToast:YMLocalizedString(@"XPRoomViewController7")];
[self enterRoomFail:0];
}
}
}
- (void)getRedPacketInfoSuccess:(XPRedPacketModel *)redInfo{
XPReceiveRedPacketView *view = [[XPReceiveRedPacketView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
view.delegate = self;
self.isShowRedPacket = YES;
redInfo.validityType = 0;
redInfo.kind = 1;
view.receiveModel = redInfo;
view.inAllPacketRoom = YES;
for (UIView *subView in self.view.subviews) {
if([subView isKindOfClass:[XPReceiveRedPacketView class]]) {
view.alphaView.backgroundColor = [UIColor clearColor];
break;
}
}
[self.view addSubview:view];
[self.view bringSubviewToFront:view];
}
- (void)getRedPacketSuccess:(NSArray *)list {
if(list.count > 0){
self.sideMenu.redPacketList = [NSMutableArray arrayWithArray:list];
}
}
-(void)getFreeGiftDataSuccess:(XPFreeGiftModel *)freeModel{
self.freeModel = freeModel;
self.menuContainerView.freeModel = freeModel;
if(self.freeModel.curStage.integerValue < self.freeModel.maxStage.integerValue){
[CountDownHelper shareHelper].isCountdownFinish = NO;
[[CountDownHelper shareHelper] openCountdownWithTime:freeModel.curStageSecond.intValue];
}else{
if([CountDownHelper shareHelper].isCountdownFinish == NO){
[CountDownHelper shareHelper].isCountdownFinish = YES;
}
[[NSNotificationCenter defaultCenter]postNotificationName:kFreeGiftCountdownNotification object:nil userInfo:@{@"isHiddenCountdown":@(YES)}];
[[CountDownHelper shareHelper] stopCountDown];
}
if(self.freeModel.curStage.intValue > 0 && [[XPRoomMiniManager shareManager].curState isEqualToString:@"0"]){
self.freeView.freeModel = self.freeModel;
if(!self.freeView.superview){
[kWindow addSubview:self.freeView];
}
[XPRoomMiniManager shareManager].curState = nil;
}
}
#pragma mark - NIMChatroomManagerDelegate
- (void)chatroomBeKicked:(NIMChatroomBeKickedResult *)result {
if (result.reason == 5) {
[self showErrorToast:YMLocalizedString(@"XPRoomViewController9")];
}
[self.presenter exitNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId]];
[[XPRoomMiniManager shareManager] resetLocalMessage];
[self cancelRoomArrangeMic];
[[RtcManager instance] exitRoom];
[self.presenter reportUserOutRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
[self.littleGameView handleSelfInExitEvent];
[self.littleGameView destroyMG];
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - NIMChatManagerDelegate
- (void)__removeAllViews {
[self.anchorScrollView removeFromSuperview];
[self.backContainerView removeFromSuperview];
[self.stageView removeFromSuperview];
[self.messageContainerView removeFromSuperview];
[self.quickMessageContainerView removeFromSuperview];
[self.sideMenu removeFromSuperview];
[self.roomHeaderView removeFromSuperview];
[self.functionView removeFromSuperview];
[self.menuContainerView removeFromSuperview];
}
- (void)onRecvMessages:(NSArray<NIMMessage *> *)messages {
// 入口排查日志:批次大小、线程和时间戳
for (NIMMessage * message in messages) {
///房间内收到p2p的消息 比如升级消息
if (message.session.sessionType == NIMSessionTypeP2P) {
if(message.messageType == NIMMessageTypeCustom) {
NIMCustomObject *obj = (NIMCustomObject *)message.messageObject;
if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) {
AttachmentModel * attachment = (AttachmentModel *)obj.attachment;
if (attachment.first == CustomMessageType_User_UpGrade) {
ContentLevelUpgradeModel * levelInfo = [ContentLevelUpgradeModel modelWithDictionary:attachment.data];
if (attachment.second == Custom_Message_Sub_User_UpGrade_Exper) {
self.userInfo.userLevelVo.experLevelSeq = levelInfo.levelSeq;
} else if(attachment.second == Custom_Message_Sub_User_UpGrade_Charm) {
self.userInfo.userLevelVo.charmLevelSeq = levelInfo.levelSeq;
}
}
}
}
}
// 非房间内消息不处理(记录过滤原因)
if (message.session.sessionType != NIMSessionTypeChatroom) {
NSLog(@"[Recv] ⛔️ 过滤:非聊天室消息 | type=%ld | sid=%@",
(long)message.session.sessionType, message.session.sessionId);
continue;
}
if (![message.session.sessionId isEqualToString:@(self.roomInfo.roomId).stringValue]) {
NSLog(@"[Recv] ⛔️ 过滤:房间不匹配 | msg.sid=%@ | curRoomId=%@",
message.session.sessionId, @(self.roomInfo.roomId).stringValue);
continue;
}
NSLog(@"[Recv] --- Message Raw Attach Content: %@, %@, %ld", @(message.senderClientType), message.rawAttachContent, (long)message.messageType);
// 全服礼物/游戏飘屏按 TurboModeStateManager 的房间开关控制
NSString *currentRoomIdForTurbo = @(self.roomInfo.roomId).stringValue;
BOOL containsAllRoomMsg = [message.rawAttachContent containsString:@"\"allRoomMsg\":1"]; // 全服礼物飘屏标记
BOOL containsAllGameMsg = [message.rawAttachContent containsString:@"\"allGameMsg\":1"]; // 预留:全服游戏飘屏标记
if (containsAllRoomMsg || containsAllGameMsg) {
BOOL allowGiftScreen = [[TurboModeStateManager sharedManager] isGlobalGiftScreenEnabledForRoom:currentRoomIdForTurbo];
BOOL allowGameScreen = [[TurboModeStateManager sharedManager] isGlobalGameScreenEnabledForRoom:currentRoomIdForTurbo];
if ((containsAllRoomMsg && !allowGiftScreen) || (containsAllGameMsg && !allowGameScreen)) {
NSLog(@"[Recv] ⛔️ 按 TurboMode 屏蔽全服飘屏 | gift=%@ game=%@ | roomId=%@",
allowGiftScreen ? @"ON" : @"OFF",
allowGameScreen ? @"ON" : @"OFF",
currentRoomIdForTurbo);
continue;
}
}
if (message.messageType == NIMMessageTypeNotification) {
[self handleNIMNotificationTypeMessage:message];
} else if (message.messageType == NIMMessageTypeCustom) {
[self handleNimCustomTypeMessage:message];
} else if(message.messageType == NIMMessageTypeText) {
[self.messageContainerView handleNIMTextMessage:message];
[self.littleGameView handleNIMTextMessage:message];
} else if(message.messageType == NIMMessageTypeTip) {
[self.messageContainerView handleNIMTextMessage:message];
}else if(message.messageType == NIMMessageTypeImage){
[self.messageContainerView handleNIMImageMessage:message];
}
}
}
- (void)handleNIMNotificationTypeMessage:(NIMMessage *)message {
NIMNotificationObject *notiMsg = (NIMNotificationObject *)message.messageObject;
NIMChatroomNotificationContent *content = (NIMChatroomNotificationContent *)notiMsg.content;
if (![notiMsg.content isKindOfClass:[NIMChatroomNotificationContent class]]) {
// content 可能为其他类型,不处理
return;
}
NSInteger onLineNumber = self.roomInfo.onlineNum;
switch (content.eventType) {
case NIMChatroomEventTypeInfoUpdated: // 麦序状态更新
{
NSDictionary *data = [content.notifyExt toJSONObject];
[self handleSwitchRoomType:data];
}
break;
case NIMChatroomEventTypeEnter:
{
if (content.source.userId.integerValue == [AccountInfoStorage instance].getUid.integerValue){
[self.presenter getKickUserListWithRoomUid:@(self.roomInfo.uid).stringValue];
}
if (content.source.userId.integerValue != [AccountInfoStorage instance].getUid.integerValue && self.userInfo.platformRole != 1) {
onLineNumber += 1;
}else {
///如果是自己的话 如果有坐骑的话
[self userEnterRoomSuccess];
///在活动页面 判断是否显示 相亲加入的按钮 所以需要等用户进房成功之后才能获取 房间角色
[self.sideMenu onRoomUpdate];
[self.menuContainerView onRoomUpdate];
}
break;
}
case NIMChatroomEventTypeAddBlack:
case NIMChatroomEventTypeKicked:
case NIMChatroomEventTypeExit:
onLineNumber -= 1;
// 🔧 新增若退出用户与CP相关移除其CP SVGA并重绘
if (content.source && content.source.userId.length > 0) {
NSInteger exitUid = content.source.userId.integerValue;
if (exitUid > 0) {
[self removeCpDataForUids:@[@(exitUid)]];
// 构造剩余在麦用户列表,触发一次 CP 刷新,等价于补充一次“下麦”的逻辑
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (currentQueue) {
NSMutableArray<NSString *> *remainingUids = [NSMutableArray array];
for (NSString *positionKey in currentQueue.allKeys) {
MicroQueueModel *micModel = currentQueue[positionKey];
NSInteger uid = micModel.userInfo.uid;
if (uid > 0 && uid != exitUid) {
[remainingUids addObject:[NSString stringWithFormat:@"%ld", (long)uid]];
}
}
if (remainingUids.count > 0) {
[self.presenter micCpListByUidList:remainingUids];
}
}
[self drawSocialStageMidpointRects];
}
}
break;
case NIMChatroomEventTypeQueueChange: // 麦序上下麦
{
NSDictionary* data = (NSDictionary *)content.ext;
UserInfoModel* userInfo = [UserInfoModel modelWithJSON:[data objectForKey:NIMChatroomEventInfoQueueChangeItemValueKey]];
NSInteger changeType = [data[NIMChatroomEventInfoQueueChangeTypeKey] integerValue];
NSLog(@"🔧 接收到麦序变化通知:用户 %ld变化类型 %ld", (long)userInfo.uid, (long)changeType);
// 处理排麦场景
if (changeType == 1 && userInfo.uid == [AccountInfoStorage instance].getUid.integerValue) {
[self cancelRoomArrangeMic];
}
// 他人上麦通过自定义消息同步CP直接返回
if (changeType == 1 && userInfo.uid != [AccountInfoStorage instance].getUid.integerValue) {
// 🔧 改进:处理其他用户上麦场景
[self handleOtherUserMicChange:userInfo changeType:changeType];
} else {
// 他人下麦:仅更新缓存与显示,跳过 API
if (changeType == 2 && userInfo.uid != [AccountInfoStorage instance].getUid.integerValue) {
// 🔧 改进处理其他用户切换mic场景
[self handleOtherUserMicChange:userInfo changeType:changeType];
} else {
NSNumber *key = nil;
if ([notiMsg respondsToSelector:@selector(attachContent)]) {
NSString *jsonString = (NSString *)[notiMsg performSelector:@selector(attachContent)];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *attachData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!error) {
NSDictionary *dataDic = [attachData objectForKey:@"data"];
NSString *queueChange = [dataDic objectForKey:@"queueChange"];
if (![NSString isEmpty:queueChange]) {
jsonData = [queueChange dataUsingEncoding:NSUTF8StringEncoding];
NSError *e = nil;
NSDictionary *queueChangeData = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!e) {
key = [queueChangeData objectForKey:@"key"];
}
}
}
}
if (key) {
// 通过 key 更新麦位队列
NSInteger position = key.integerValue;
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (currentQueue) {
for (NSString *key in [currentQueue allKeys]) {
if (key.integerValue == position) {
MicroQueueModel *model = [currentQueue objectForKey:key];
if (changeType == 2) {
// 用户下 mic
model.userInfo = nil;
} else {
// 用户上 mic
model.userInfo = userInfo;
}
break;
}
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self handleMicChangeForCP:currentQueue];
});
}
} else {
// 获取最新的麦位队列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (currentQueue) {
[self handleMicChangeForCP:currentQueue];
}
});
}
}
}
}
break;
case NIMChatroomEventTypeAddManager: {
NIMChatroomNotificationMember *targets = [content.targets firstObject];
if (targets.userId.integerValue == self.userInfo.uid) {
[XNDJTDDLoadingTool showSuccessWithMessage:YMLocalizedString(@"1.0.34_text_15")];
}
}
break;
case NIMChatroomEventTypeRemoveManager: {
NIMChatroomNotificationMember *targets = [content.targets firstObject];
if (targets.userId.integerValue == self.userInfo.uid) {
[XNDJTDDLoadingTool showSuccessWithMessage:YMLocalizedString(@"1.0.34_text_16")];
}
}
break;
default:
break;
}
self.roomInfo.onlineNum = onLineNumber;
[self.functionView updateOnlineCount:[NSString stringWithFormat:@"%ld",onLineNumber] countList:@[] isUpdateCount:YES];
[self.roomHeaderView onRoomUpdate];
[self.stageView handleNIMNotificationMessage:message];
[self.animationView handleNIMNotificationMessage:message];
[self.messageContainerView handleNIMNotificationMessage:message];
[self.sideMenu handleNIMNotificationMessage:message];
[self.menuContainerView handleNIMNotificationMessage:message];
[self.functionView handleNIMNotificationMessage:message];
[self.littleGameView handleNIMNotificationMessage:message];
if([self.stageView findMicroViewByUid:@(self.userInfo.uid).stringValue] != nil){
[XPSkillCardPlayerManager shareInstance].isMineInMic = YES;
}else{
[XPSkillCardPlayerManager shareInstance].isMineInMic = NO;
};
}
- (void)handleSwitchRoomType:(NSDictionary *)data {
int type = [data[@"type"] intValue];
switch (type) {
case 1: // 房间信息变化
case 3: // 房间信息 & 麦位信息变化
{
NSMutableDictionary *lastRoomInfoDic = [NSMutableDictionary dictionaryWithDictionary:[self.roomInfo model2dictionary]];
[lastRoomInfoDic addEntriesFromDictionary: ((NSString *)data[@"roomInfo"]).toJSONObject];
RoomInfoModel *newRoomInfo = [RoomInfoModel modelWithJSON:lastRoomInfoDic];
if (newRoomInfo.type != RoomType_MiniGame) {
newRoomInfo.mgId = 0;
}
newRoomInfo.hasAnimationEffect = self.hasAnimationEffect;
if (newRoomInfo.roomModeType == RoomModeType_Open_Blind && self.roomInfo.roomModeType != RoomModeType_Open_Blind) {
///普通房 切换为相亲房
newRoomInfo.datingState = RoomDatingStateChangeType_Open;
} else if(newRoomInfo.roomModeType != RoomModeType_Open_Blind && self.roomInfo.roomModeType == RoomModeType_Open_Blind) {
///关闭了相亲房
newRoomInfo.datingState = RoomDatingStateChangeType_Close;
} else {
newRoomInfo.datingState = RoomDatingStateChangeType_Normal;
}
//房间类型是否变更了(从个播->普通,个播->小游戏等)
newRoomInfo.hadChangeRoomType = self.roomInfo.type != newRoomInfo.type;
BOOL anchorToOther = newRoomInfo.type != RoomType_Anchor && self.roomInfo.type == RoomType_Anchor;//个播变其他房
self.roomInfo = newRoomInfo;
[self.backContainerView onRoomUpdate];
self.quickMessageContainerView.titleArray = self.roomInfo.speakTemplate;
if (self.roomInfo.type == RoomType_Anchor && self.roomInfo.hadChangeRoomType) {///从其他房变为个播房
[self handleInitAnchorRoom];
[self showAnchorScrollTipView];
} else if (anchorToOther) {
[self __removeAllViews];
[self.view addSubview:self.backContainerView];
[self.view addSubview:self.stageView];
[self.view addSubview:self.messageContainerView];
[self.view addSubview:self.quickMessageContainerView];
[self.view addSubview:self.menuContainerView];
[self.view addSubview:self.sideMenu];
[self.view addSubview:self.roomHeaderView];
[self.view bringSubviewToFront:self.menuContainerView];
[self.view addSubview:self.functionView];
[self.view bringSubviewToFront:self.animationView];
[self initSubViewConstraints];
[self changeStageViewOnRoomUpdate];
[self.stageView onRoomUpdate];
[self.menuContainerView onRoomUpdate];
[self.functionView onRoomUpdate];
[self.sideMenu onRoomUpdate];
[self.messageContainerView onRoomUpdate];
[self.littleGameView onRoomUpdate];
} else {
if (self.roomInfo.type == RoomType_Anchor) {
[self changeStageViewOnAnchorRoom];
} else if (self.roomInfo.type == RoomType_20Mic) {
[self __layoutTwentyMicStage];
[self changeStageViewOnRoomUpdate];
[self.twentyMicStageView onRoomUpdate];
} else if (self.roomInfo.type == RoomType_19Mic) {
[self __layoutNineteenMicStage];
[self changeStageViewOnRoomUpdate];
[self.nineteenMicStageView onRoomUpdate];
} else if (self.roomInfo.type == RoomType_15Mic) {
[self __layoutFifteenMicStage];
[self changeStageViewOnRoomUpdate];
[self.fifteenMicStageView onRoomUpdate];
} else if (self.roomInfo.type == RoomType_10Mic) {
[self __layoutTenMicStage];
[self changeStageViewOnRoomUpdate];
[self.tenMicStageView onRoomUpdate];
} else {
[self changeStageViewOnRoomUpdate];
}
[self.stageView onRoomUpdate];
[self.menuContainerView onRoomUpdate];
[self.functionView onRoomUpdate];
[self.sideMenu onRoomUpdate];
[self.messageContainerView onRoomUpdate];
[self.littleGameView onRoomUpdate];
}
}
break;
}
}
- (void)handleNimCustomTypeMessage:(NIMMessage *)message {
NIMCustomObject *obj = (NIMCustomObject *)message.messageObject;
if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) {
AttachmentModel * attachment = (AttachmentModel *)obj.attachment;
[self.stageView handleNIMCustomMessage:message];
[self.animationView handleNIMCustomMessage:message];
[self.menuContainerView handleNIMCustomMessage:message];
[self.sideMenu handleNIMCustomMessage:message];
[self.functionView handleNIMCustomMessage:message];
[self.littleGameView handleNIMCustomMessage:message];
[self.messageContainerView handleNIMCustomMessage:message];
switch (attachment.first) {
case ClientMessage_Type:
switch (attachment.second) {
case ClientMessage_UpMic_Ask:
[self handleUpMicAsk:attachment];
break;
case ClientMessage_UpMic_Agree:
[self handleUpMicAgree:attachment];
break;
case ClientMessage_UpMic_Reject:
[XNDJTDDLoadingTool showErrorWithMessage:YMLocalizedString(@"20.20.62_text_4")];
break;
}
break;
case CustomMessageType_First_Recharge_Reward:
switch (attachment.second) {
case Custom_Message_Sub_Room_First_Recharge_Reward:
self.userInfo.isFirstCharge = NO;
[self.menuContainerView onRoomUpdate];
[self.sideMenu onRoomUpdate];
break;
}
break;
case CustomMessageType_Update_RoomInfo:
switch (attachment.second) {
case Custom_Message_Sub_Update_RoomInfo_MessageState: {
///兼容安卓和iOS 协议不同的问题
NSDictionary * dic= attachment.data[@"roomInfo"];
if (dic.allKeys.count <=0) {
dic = attachment.data;
}
RoomInfoModel * roomInfo = [RoomInfoModel modelWithDictionary:dic];
self.roomInfo.isCloseScreen = roomInfo.isCloseScreen;
[self.menuContainerView onRoomUpdate];
}
break;
case Custom_Message_Sub_Update_RoomInfo_AnimateEffect: {
NSDictionary * dic= attachment.data[@"roomInfo"];
if (dic.allKeys.count <=0) {
dic = attachment.data;
}
RoomInfoModel * roomInfo = [RoomInfoModel modelWithDictionary:dic];
self.roomInfo.hasAnimationEffect = roomInfo.hasAnimationEffect;
[self.roomHeaderView onRoomUpdate];
}
break;
}
break;
case CustomMessageType_Queue:
switch (attachment.second) {
case Custom_Message_Sub_Queue_Invite: {
NSDictionary *dic = attachment.data;
NSString *uid = dic[@"uid"];
if (uid.integerValue == [AccountInfoStorage instance].getUid.integerValue) {
[self cancelRoomArrangeMic];
}
}
break;
}
break;
case CustomMessageType_Hall_Super_Admin:
switch (attachment.second) {
case Custom_Message_Sub_Hall_Super_Admin_Kick_Manager_Out_Room: {
XPKickUserModel * userInfo = [XPKickUserModel modelWithJSON:attachment.data];
if (userInfo.targetUid == [AccountInfoStorage instance].getUid.integerValue) {
[self.presenter exitNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId]];
[[XPRoomMiniManager shareManager] resetLocalMessage];
[self cancelRoomArrangeMic];
[[RtcManager instance] exitRoom];
[self.presenter reportUserOutRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
[self.littleGameView handleSelfInExitEvent];
[self.littleGameView destroyMG];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
break;
}
break;
case CustomMessageType_RedPacket:
switch (attachment.second) {
case Custom_Message_Sub_RoomDiamandRedPacket: {
XPReceiveRedPacketView *view = [[XPReceiveRedPacketView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
XPRedPacketModel *data = [XPRedPacketModel modelWithJSON:attachment.data];
view.delegate = self;
self.isShowRedPacket = YES;
data.validityType = 0;
data.kind = 1;
data.redEnvelopeId = attachment.data[@"redEnvelopeId"];
view.receiveModel = data;
for (UIView *subView in self.view.subviews) {
if([subView isKindOfClass:[XPReceiveRedPacketView class]]) {
view.alphaView.backgroundColor = [UIColor clearColor];
break;
}
}
[self.view addSubview:view];
[self.view bringSubviewToFront:view];
if(self.sideMenu.redPacketList == nil){
self.sideMenu.redPacketList = [NSMutableArray array];
}
NSMutableArray *redPacketList = [NSMutableArray arrayWithArray:self.sideMenu.redPacketList];
[redPacketList insertObject:data atIndex:0];
self.sideMenu.redPacketList = redPacketList;
}
break;
case Custom_Message_Sub_NewRoomDiamandRedPacket: {
XPRedPacketModel *curData = [XPRedPacketModel modelWithJSON:attachment.data];
if(self.sideMenu.redPacketList == nil){
self.sideMenu.redPacketList = [NSMutableArray array];
}
NSMutableArray *redPacketList = [NSMutableArray arrayWithArray:self.sideMenu.redPacketList];
[redPacketList insertObject:curData atIndex:0];
if(self.roomInfo.uid > 0 && curData.redEnvelopeNum.integerValue != redPacketList.count){
[self.presenter getRedPacket:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
return;
}
curData.redEnvelopeId = attachment.data[@"redEnvelopeId"];
if(curData.validityType == 0){
XPReceiveRedPacketView *view = [[XPReceiveRedPacketView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
view.delegate = self;
self.isShowRedPacket = YES;
view.receiveModel = curData;
for (UIView *subView in self.view.subviews) {
if([subView isKindOfClass:[XPReceiveRedPacketView class]]) {
view.alphaView.backgroundColor = [UIColor clearColor];
break;
}
}
[self.view addSubview:view];
[self.view bringSubviewToFront:view];
}
self.sideMenu.redPacketList = redPacketList;
}
break;
}
break;
case CustomMessageType_Free_Gift_Star_Reset_Time:
switch (attachment.second) {
case Custom_Message_Sub_Free_Gift_Star_Reset_Time:
[self.presenter getFreeGiftData];
[[NSNotificationCenter defaultCenter]postNotificationName:kFreeGiftCountdownNotification object:nil userInfo:@{@"isReset":@(YES)}];
break;
case Custom_Message_Sub_Update_Gift_Information:
[self.presenter getFreeGiftData];
[[NSNotificationCenter defaultCenter]postNotificationName:kFreeGiftCountdownNotification object:nil userInfo:@{@"updateGiftInfo":attachment.data ?: @""}];
break;
}
break;
case CustomMessageType_Custom_Room_Background:
switch (attachment.second) {
case Custom_Message_Sub_Custom_Room_Background:
if ([attachment.data isKindOfClass:[NSDictionary class]]) {
NSString *url = attachment.data[@"url"];
if (![NSString isEmpty:url]) {
self.roomInfo.backPic = url;//@"https://tva1.moyu.im/mw600/9f0b0dd5ly1hvacrep5fhj20jg0t60w4.jpg";
[self.backContainerView onRoomUpdate];
}
}
break;
}
break;
case CustomMessageType_RoomLevel_Update:
switch (attachment.second) {
case Custom_Message_Sub_RoomLevel_Update:
if ([attachment.data isKindOfClass:[NSDictionary class]]) {
NSString *url = attachment.data[@"roomLevelIcon"];
NSNumber *roomLevel = attachment.data[@"roomLevel"];
if (![NSString isEmpty:url] && roomLevel.integerValue > self.roomHeaderView.tag) {
self.roomHeaderView.tag = roomLevel.integerValue;
[self.roomHeaderView updateLevel:url];
self.roomInfo.roomLevelIcon = url;
}
}
break;
}
break;
case MicRelationship_Type:
switch (attachment.second) {
case MicRelationship_CP:
[self handleMicRelationshipCPMessage:attachment];
break;
}
break;
case CustomMessageType_CP:
switch (attachment.second) {
case Custom_Message_Sub_CP_Binding:
case Custom_Message_Sub_CP_Upgrade:
[self handleCPBindingOrUpgradeMessage:attachment];
break;
}
break;
}
}
}
/// 处理CP绑定或升级消息
- (void)handleCPBindingOrUpgradeMessage:(AttachmentModel *)attachment {
NSLog(@"🔧 处理CP绑定或升级消息first=%ld, second=%ld", (long)attachment.first, (long)attachment.second);
// 只有在完成进房初始化后才处理CP相关逻辑
if (!self.hasCompletedRoomInitialization) {
NSLog(@"🔧 进房初始化中跳过CP绑定/升级处理");
return;
}
// 获取当前麦位队列
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (!currentQueue) {
NSLog(@"⚠️ 无法获取当前麦位队列跳过CP绑定/升级处理");
return;
}
// 使用 MicMidpointRectManager 处理CP绑定/升级消息
MicMidpointRectManager *manager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (manager) {
// 计算麦位总数
NSInteger micCount = [self getMicCountForRoomType:self.roomInfo.type];
[manager handleCPBindingOrUpgradeMessageWithStageView:self.stageView
micCount:micCount
queue:currentQueue];
}
// 调用CP API更新状态
[self callMicCpListByUidListWithQueue:currentQueue];
NSLog(@"✅ CP绑定/升级消息处理完成");
}
/// 处理麦位变化相关的CP逻辑
- (void)handleMicChangeForCP:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
NSLog(@"🔧 处理麦位变化相关的CP逻辑");
// 更新当前用户的麦位状态
[self updateCurrentUserMicStatus:queue];
// 只有在完成进房初始化后才处理CP相关逻辑
if (self.hasCompletedRoomInitialization) {
// 使用 MicMidpointRectManager 处理CP逻辑
MicMidpointRectManager *manager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (manager) {
// 计算麦位总数
NSInteger micCount = [self getMicCountForRoomType:self.roomInfo.type];
[manager handleMicChangeForCPWithStageView:self.stageView
micCount:micCount
queue:queue
currentUserUid:[AccountInfoStorage instance].getUid.integerValue
roomType:self.roomInfo.type];
}
// 调用CP API
[self callMicCpListByUidListWithQueue:queue];
// 🔧 新增检查19 mic房间中央位置用户资格
[self checkCentralPositionUserQualification:queue];
} else {
NSLog(@"🔧 进房初始化中跳过CP相关处理");
}
}
/// 处理麦位关系CP消息
- (void)handleMicRelationshipCPMessage:(AttachmentModel *)attachment {
NSLog(@"🔧 接收到麦位关系CP消息");
if (!attachment.data) {
NSLog(@"⚠️ 麦位关系CP消息data格式错误跳过处理");
return;
}
// 兼容多种数据结构:
// 1) attachment.data 为 NSDictionary包含 key "data",其值可能为 NSString(JSON) 或 NSArray
// 2) attachment.data 直接为 NSArray
// 3) attachment.data 直接为 NSString(JSON)
NSArray *dataArray = nil;
id raw = attachment.data;
if ([raw isKindOfClass:[NSDictionary class]]) {
id inner = [(NSDictionary *)raw objectForKey:@"data"];
if ([inner isKindOfClass:[NSArray class]]) {
dataArray = (NSArray *)inner;
} else if ([inner isKindOfClass:[NSString class]]) {
NSData *jsonData = [(NSString *)inner dataUsingEncoding:NSUTF8StringEncoding];
if (jsonData) {
NSError *err = nil;
id parsed = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&err];
if (!err && [parsed isKindOfClass:[NSArray class]]) {
dataArray = (NSArray *)parsed;
}
}
}
} else if ([raw isKindOfClass:[NSArray class]]) {
dataArray = (NSArray *)raw;
} else if ([raw isKindOfClass:[NSString class]]) {
NSData *jsonData = [(NSString *)raw dataUsingEncoding:NSUTF8StringEncoding];
if (jsonData) {
NSError *err = nil;
id parsed = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&err];
if (!err && [parsed isKindOfClass:[NSArray class]]) {
dataArray = (NSArray *)parsed;
}
}
}
if (![dataArray isKindOfClass:[NSArray class]]) {
NSLog(@"⚠️ 麦位关系CP消息未能解析为数组跳过处理");
return;
}
// 解析CP数据
NSMutableArray<MicCpInfoModel *> *cpList = [NSMutableArray array];
for (id item in dataArray) {
if ([item isKindOfClass:[NSDictionary class]]) {
NSDictionary *cpDict = (NSDictionary *)item;
MicCpInfoModel *cpInfo = [[MicCpInfoModel alloc] init];
cpInfo.uid = [cpDict[@"uid"] integerValue];
cpInfo.loverUid = [cpDict[@"loverUid"] integerValue];
cpInfo.cpLevel = [cpDict[@"cpLevel"] integerValue];
[cpList addObject:cpInfo];
}
}
NSLog(@"🔧 麦位关系CP消息解析到 %lu 条CP数据", (unsigned long)cpList.count);
// 更新当前CP列表
self.currentCpList = cpList.copy;
// 使用 MicMidpointRectManager 更新缓存并重绘
MicMidpointRectManager *manager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (manager) {
NSInteger micCount = [self getMicCountForRoomType:self.roomInfo.type];
[manager updateCpListCacheAndRedraw:cpList
stageView:self.stageView
micCount:micCount
roomType:self.roomInfo.type];
} else {
// 退化:仅更新本地并重绘
[self drawSocialStageMidpointRects];
}
// 🔧 补强为了确保不遗漏其他CP例如 a-b 与 b-c 同时存在在接收10011后再拉一次当前麦上的完整CP
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (currentQueue) {
[self callMicCpListByUidListWithQueue:currentQueue];
}
NSLog(@"🔧 麦位关系CP消息处理完成");
}
- (void)handleUpMicAsk:(AttachmentModel *)attachment {
NSNumber *targetUid = [attachment.data objectForKey:@"targetUid"];
if (!targetUid || ![targetUid.stringValue isEqualToString:[AccountInfoStorage instance].getUid]) {
return;
}
// 先清理之前的定时器(如果存在)
if (self.upMicAskTimer) {
[self.upMicAskTimer invalidate];
self.upMicAskTimer = nil;
}
TTAlertConfig *config = [[TTAlertConfig alloc] init];
config.title = YMLocalizedString(@"20.20.62_text_1");
config.message = YMLocalizedString(@"20.20.62_text_2");
config.actionStyle = TTAlertActionBothStyle;
// 自定义按钮
config.confirmButtonConfig.title = YMLocalizedString(@"MessageContentGuildView5");
config.cancelButtonConfig.title = YMLocalizedString(@"MessageContentGuildView4");
// 再觸發的話clean 舊內容
[TTPopup dismiss];
[self removeUpMicAskTimer];
@kWeakify(self);
[TTPopup alertWithConfig:config
confirmHandler:^{
@kStrongify(self);
// 用户点击确认,清理定时器
[self removeUpMicAskTimer];
[self sendResponseUpMicMessage:attachment.data isAgree:YES];
} cancelHandler:^{
@kStrongify(self);
// 用户点击取消,清理定时器
[self removeUpMicAskTimer];
[self sendResponseUpMicMessage:attachment.data isAgree:NO];
}];
// 设置10秒后自动移除弹窗的定时器
self.upMicAskTimer = [NSTimer scheduledTimerWithTimeInterval:10.0
target:self
selector:@selector(autoDismissUpMicAskPopup)
userInfo:nil
repeats:NO];
}
- (void)removeUpMicAskTimer {
if (self.upMicAskTimer) {
[self.upMicAskTimer invalidate];
self.upMicAskTimer = nil;
}
}
/**
自动移除上麦请求弹窗
10秒后自动调用移除弹窗并清理定时器
*/
- (void)autoDismissUpMicAskPopup {
// 移除弹窗
[TTPopup dismiss];
// 清理定时器
if (self.upMicAskTimer) {
[self.upMicAskTimer invalidate];
self.upMicAskTimer = nil;
}
NSLog(@"⏰ 上麦请求弹窗已自动移除10秒超时");
}
- (void)handleUpMicAgree:(AttachmentModel *)attachment {
NSDictionary *originalData = attachment.data;
NSNumber *position = [originalData objectForKey:@"position"];
NSNumber *targetUid = [originalData objectForKey:@"targetUid"];
if (!position || !targetUid) {
return;
}
NSNumber *sendTime = [originalData objectForKey:@"sendTime"];
if (!sendTime) {
return;
} else {
int64_t nowMillis = (int64_t)([[NSDate date] timeIntervalSince1970] * 1000);
if (nowMillis - sendTime.doubleValue > 10 * 1000) {
// 已经超过 10 秒
return;
}
}
NSMutableDictionary * dic = [NSMutableDictionary dictionary];
[dic setValue:position forKey:@"micPosition"];
[dic setValue:targetUid forKey:@"uid"];
AttachmentModel *attachement = [[AttachmentModel alloc]init];
attachement.first = CustomMessageType_Queue;
attachement.second = Custom_Message_Sub_Queue_Invite;
attachement.data = dic;
NIMMessage *message = [[NIMMessage alloc]init];
NIMCustomObject *object = [[NIMCustomObject alloc] init];
object.attachment = attachement;
message.messageObject = object;
//构造会话
NIMSession *session = [NIMSession session: @(self.roomInfo.roomId).stringValue
type:NIMSessionTypeChatroom];
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session error:nil];
}
- (void)sendResponseUpMicMessage:(NSDictionary *)originalData isAgree:(BOOL)isAgree {
AttachmentModel *attachment = [[AttachmentModel alloc] init];
attachment.first = ClientMessage_Type;
attachment.second = isAgree ? ClientMessage_UpMic_Agree : ClientMessage_UpMic_Reject;
attachment.data = originalData;
NSString *sessionID = @(self.roomInfo.roomId).stringValue;
NIMMessage *message = [[NIMMessage alloc]init];
NIMCustomObject *object = [[NIMCustomObject alloc] init];
object.attachment = attachment;
message.messageObject = object;
NIMSessionType sessionType = NIMSessionTypeChatroom;
//构造会话
NIMSession *session = [NIMSession session:sessionID type:sessionType];
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session completion:^(NSError * _Nullable error) {
BOOL onMain = [NSThread isMainThread];
if (error) {
NSLog(@"[[Send] ❌ 发送失败 | sessionId=%@ | code=%ld | desc=%@ | main=%@ | ts=%.3f",
sessionID, (long)error.code, error.localizedDescription, onMain ? @"YES" : @"NO", [[NSDate date] timeIntervalSince1970]);
} else {
NSLog(@"[Send] ✅ 发送成功 | sessionId=%@ | main=%@ | ts=%.3f",
sessionID, onMain ? @"YES" : @"NO", [[NSDate date] timeIntervalSince1970]);
}
}];
}
#pragma mark - NIMBroadcastDelegate
// 广播消息
- (void)onReceiveBroadcastMessage:(NIMBroadcastMessage *)broadcastMessage{
if (broadcastMessage.content) {
NSDictionary *msgDictionary = [broadcastMessage.content toJSONObject];
AttachmentModel *attachment = [AttachmentModel modelWithJSON:msgDictionary[@"body"]];
NSString *partitionId = [NSString stringWithFormat:@"%@",attachment.data[@"partitionId"]];
if(![partitionId isEqualToString:self.getUserInfo.partitionId]){
return;
}
BOOL isHave = NO;
if(attachment.first == CustomMessageType_Look_Love && attachment.second == Custom_Message_Sub_Look_Love_InRoom_NeedAllMicSend){
isHave = YES;
}else if(attachment.first == CustomMessageType_Super_Gift &&
(attachment.second == Custom_Message_Sub_Super_Gift || attachment.second == Custom_Message_Sub_Super_Gift_Room_Message)){
isHave = YES;
}else if(attachment.first == CustomMessageType_General_Public_Screen && attachment.second == Custom_Message_Sub_General_Public_Screen_All_Room){
isHave = YES;
}else if (attachment.first == CustomMessageType_Tarot && (attachment.second == Custom_Message_Sub_Tarot_Advanced || attachment.second == Custom_Message_Sub_Tarot_Intermediate)){
isHave = YES;
}else if(attachment.first == CustomMessageType_LuckyBag && attachment.second == Custom_Message_Sub_Room_Gift_LuckBag_FullScree){
isHave = YES;
}
// 对全服飘屏按 TurboModeStateManager 的房间开关做二次过滤
if (isHave && (attachment.first == CustomMessageType_General_Public_Screen && attachment.second == Custom_Message_Sub_General_Public_Screen_All_Room)) {
NSString *currentRoomIdForTurbo = @(self.roomInfo.roomId).stringValue;
BOOL allowGiftScreen = [[TurboModeStateManager sharedManager] isGlobalGiftScreenEnabledForRoom:currentRoomIdForTurbo];
if (!allowGiftScreen) {
NSLog(@"[Broadcast] ⛔️ 按 TurboMode 屏蔽全服礼物飘屏 | roomId=%@", currentRoomIdForTurbo);
return;
}
}
if (attachment.first == CustomMessageType_RedPacket && attachment.second == Custom_Message_Sub_AllDiamandRedPacket) {
if(self.isShowRedPacket == YES)return;
XPRedPacketModel *data = [XPRedPacketModel modelWithDictionary:attachment.data];
data.validityType = 0;
data.kind = 1;
data.redEnvelopeId = attachment.data[@"redEnvelopeId"];
RoomInfoModel *roomInfo = self.roomInfo;
XPReceiveRedPacketView *view = [[XPReceiveRedPacketView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
view.delegate = self;
self.isShowRedPacket = YES;
if (roomInfo.uid == data.roomUid.integerValue) {
view.inAllPacketRoom = YES;
} else {
view.inAllPacketRoom = NO;
}
view.receiveModel = data;
[self.view addSubview:view];
[self.view bringSubviewToFront:view];
}
if(isHave == NO)return;
attachment.isBroadcast = YES;
NIMMessage *message = [NIMMessage new];
NIMCustomObject *object = [NIMCustomObject new];
object.attachment = attachment;
message.messageObject = object;
[self.messageContainerView handleNIMTextMessage:message];
}
}
//发送消息成功回调
- (void)sendMessage:(NIMMessage *)message didCompleteWithError:(NSError *)error {
if (message.yidunAntiSpamRes) {
NSDictionary * spamRes = message.yidunAntiSpamRes.toJSONObject;
id spamResExt = ((NSString *)spamRes[@"ext"]).toJSONObject;
NSDictionary *antispamDic = nil;
if ([spamResExt isKindOfClass:[NSArray class]]) {
antispamDic = [spamResExt xpSafeObjectAtIndex:0];
} else if ([spamResExt isKindOfClass:[NSDictionary class]]) {
antispamDic = spamResExt[@"antispam"];
}
NSDictionary *realAntiDic = nil;
if ([[antispamDic allKeys] containsObject:@"antispam"]) {
realAntiDic = antispamDic[@"antispam"];;
} else {
realAntiDic = antispamDic;
}
if (realAntiDic) {
NSInteger suggestion = [realAntiDic[@"suggestion"] integerValue];
if (suggestion == 2) {
[self showErrorToast:YMLocalizedString(@"XPRoomViewController10")];
}
}
}
if (![message.session.sessionId isEqualToString:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId]]) {
return;
}
if (error) {
NSLog(@"%@", error);
return;
};
if (message.messageType == NIMMessageTypeCustom) {
NIMCustomObject *obj = (NIMCustomObject *)message.messageObject;
if (obj.attachment != nil && [obj.attachment isKindOfClass:[AttachmentModel class]]) {
AttachmentModel * attachment = (AttachmentModel *)obj.attachment;
[self.stageView handleNIMCustomMessage:message];
[self.animationView handleNIMCustomMessage:message];
[self.functionView handleNIMCustomMessage:message];
if (attachment.first == CustomMessageType_Update_RoomInfo && attachment.second == Custom_Message_Sub_Update_RoomInfo_MessageState) {
NSDictionary * dic= attachment.data[@"roomInfo"];
if (dic.allKeys.count <=0) {
dic = attachment.data;
}
RoomInfoModel * roomInfo = [RoomInfoModel modelWithDictionary:dic];
self.roomInfo.isCloseScreen = roomInfo.isCloseScreen;
[self.menuContainerView onRoomUpdate];
}else if(attachment.first == CustomMessageType_Update_RoomInfo && attachment.second == Custom_Message_Sub_Update_RoomInfo_AnimateEffect){
NSDictionary * dic= attachment.data[@"roomInfo"];
if (dic.allKeys.count <= 0) {
dic = attachment.data;
}
RoomInfoModel * roomInfo = [RoomInfoModel modelWithDictionary:dic];
self.roomInfo.hasAnimationEffect = roomInfo.hasAnimationEffect;
}
[self.roomHeaderView onRoomUpdate];
[self.messageContainerView handleNIMCustomMessage:message];
}
}else if(message.messageType == NIMMessageTypeText) {
[self.messageContainerView handleNIMTextMessage:message];;
}
}
#pragma mark - NIMLoginManagerDelegate
- (void)onKickout:(NIMLoginKickoutResult *)result {
// [self exitRoom];
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - NIMConversationManagerDelegate
- (void)didAddRecentSession:(NIMRecentSession *)recentSession
totalUnreadCount:(NSInteger)totalUnreadCount {
[self.menuContainerView addNIMRecentSession:recentSession];
}
- (void)didUpdateRecentSession:(NIMRecentSession *)recentSession
totalUnreadCount:(NSInteger)totalUnreadCount {
[self.menuContainerView addNIMRecentSession:recentSession];
}
- (void)didRemoveRecentSession:(NIMRecentSession *)recentSession
totalUnreadCount:(NSInteger)totalUnreadCount {
[self.menuContainerView removeNIMRecentSession:recentSession];
}
#pragma mark - RoomDelegate
- (RoomInfoModel *)getRoomInfo {
return self.roomInfo;
}
- (UserInfoModel *)getUserInfo {
return self.userInfo;
}
- (BOOL)isManagerOrOwner {
return self.roomUid.integerValue == self.userInfo.uid ||
[self.superMangerList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"uid == %d", self.userInfo.uid]] > 0;
}
-(NSMutableArray *)getPlayList{
return self.sideMenu.playList;
}
-(NSMutableArray *)getLittleGameList {
return self.sideMenu.littleGameList;
}
- (void)exitRoom {
[XNDJTDDLoadingTool showLoading];
// 🔧 防护:标记正在退出房间
self.isExitingRoom = YES;
[XPSkillCardPlayerManager shareInstance].micState = MICState_None;
[self.stageView exitNIMRoom];
[self.menuContainerView menuResignFirstResponder];
[self.animationView resumeTimer];
switch (self.roomInfo.roomModeType) {
case RoomModeType_Open_Micro_Mode:
case RoomModeType_Open_Blind:
[self exitRoomAndCancelArrangeMic];
break;
case RoomModeType_Open_PK_Mode:
[self exitRoomWhenPKing];
break;
case RoomType_MiniGame:
[self exitRoomWhenPlayMiniGame];
break;
default:
[self exitRoomPage];
break;
}
}
- (BOOL)isSameRoom:(NSString *)targetUid {
return [self.roomUid isEqualToString:targetUid];
}
- (void)exitRoomAndCancelArrangeMic {
NSString * roomUid = [NSString stringWithFormat:@"%ld", self.roomInfo.uid];
NSString * uid = [AccountInfoStorage instance].getUid;
[Api getRoomArrangeMicList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
ArrangeMicModel * arrangeMicModel= [ArrangeMicModel modelWithJSON:data.data];
if (arrangeMicModel.myPos.intValue > 0) {
[TTPopup alertWithMessage:YMLocalizedString(@"XPRoomViewController11") confirmHandler:^{
__block NSString * grouptype = @"0";
if (self.roomInfo.roomModeType == RoomModeType_Open_Blind) {
[arrangeMicModel.queue enumerateObjectsUsingBlock:^(ArrangeMicUserModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.uid.integerValue == [AccountInfoStorage instance].getUid.integerValue) {
grouptype = [NSString stringWithFormat:@"%ld", obj.groupType];
*stop = YES;
}
}];
}
[Api cancelArrangeMic:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
} roomUid:roomUid operUid:uid groupType:grouptype];
[self exitRoomPage];
} cancelHandler:^{
}];
return;
}
}
[self exitRoomPage];
} roomUid:roomUid operUid:uid page:@"1" pageSize:@"50"];
}
- (void)exitRoomWhenPKing {
NSString * roomUid = [NSString stringWithFormat:@"%ld", self.roomInfo.uid];
NSString * uid = [AccountInfoStorage instance].getUid;
[Api getRoomPKUserList:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
ArrangeMicModel * arrangeMicModel= [ArrangeMicModel modelWithJSON:data.data];
if (arrangeMicModel.myPos.intValue > 0) {
[TTPopup alertWithMessage:YMLocalizedString(@"XPRoomViewController11") confirmHandler:^{
__block NSString * grouptype = @"0";
[arrangeMicModel.queue enumerateObjectsUsingBlock:^(ArrangeMicUserModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (obj.uid.integerValue == [AccountInfoStorage instance].getUid.integerValue) {
grouptype = [NSString stringWithFormat:@"%ld", obj.groupType];
*stop = YES;
}
}];
[Api cancelRoomPKArrangeMic:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
} roomUid:roomUid operUid:uid groupType:grouptype];
[self exitRoomPage];
} cancelHandler:^{
}];
return;
}
}
[self exitRoomPage];
} roomUid:roomUid operUid:uid page:@"1" pageSize:@"50"];
}
- (void)exitRoomWhenPlayMiniGame {
if ([self.littleGameView isInSudGame]) {
TTAlertConfig *config = [[TTAlertConfig alloc] init];
config.cancelButtonConfig.title = YMLocalizedString(@"XPRoomSettingInputView4");
config.confirmButtonConfig.title = YMLocalizedString(@"TTAlertConfig0");
config.message = YMLocalizedString(@"XPRoomViewController14");
@kWeakify(self);
[TTPopup alertWithConfig:config confirmHandler:^{ // 知道了
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@kStrongify(self);
[self exitRoomPage];
});
} cancelHandler:^{
}];
} else {
[self exitRoomPage];
}
}
- (void)exitRoomPage {
if (self.roomInfo.type == RoomType_MiniGame) {
[self.littleGameView handleSelfInExitEvent];
[self.littleGameView destroyMG];
}
[[XPRoomMiniManager shareManager] resetLocalMessage];
// 🔧 防护:确保 TRTC 退出
[[RtcManager instance] exitRoom];
// 🔧 防护:标记房间已退出
self.isExitingRoom = YES;
self.isViewActive = NO;
[self.presenter exitNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId]];
[self.presenter reportUserOutRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
[self handleFirstOutRoom];
[self dismissViewControllerAnimated:YES completion:^{
[XNDJTDDLoadingTool hideHUD];
}];
}
- (void)miniRoom {
if(self.freeModel != nil){
[XPRoomMiniManager shareManager].curState = self.freeModel.curStage;
}
[self.menuContainerView menuResignFirstResponder];
NSDictionary *roomInfoDic = self.roomInfo.model2dictionary != nil ? self.roomInfo.model2dictionary : @{};
NSDictionary *userInfoDic = self.userInfo.model2dictionary != nil ? self.userInfo.model2dictionary : @{};
NSMutableDictionary *queueDic = self.getMicroQueue != nil ? self.getMicroQueue : [@{} mutableCopy];
if(self.roomInfo.type == RoomType_MiniGame) {
if ([self.littleGameView isInSudGame]) {
TTAlertConfig *config = [[TTAlertConfig alloc] init];
config.cancelButtonConfig.title = YMLocalizedString(@"XPRoomViewController15");
config.confirmButtonConfig.title = YMLocalizedString(@"XPRoomViewController16");
config.message = YMLocalizedString(@"XPRoomViewController17");
@kWeakify(self);
[TTPopup alertWithConfig:config confirmHandler:^{ // 知道了
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@kStrongify(self);
[[XPRoomMiniManager shareManager] configRoomInfo:self.roomInfo];
[[XPRoomMiniManager shareManager] configUserInfo:self.userInfo];
[[XPRoomMiniManager shareManager] configCurrentMusic:self.functionView.getCurrentMusic isPlaying:self.functionView.isPlaying];
NSDictionary * dic = @{@"roomInfo":roomInfoDic, @"userInfo":userInfoDic, @"microQueue":queueDic};
[[NSNotificationCenter defaultCenter] postNotificationName:kRoomMiniNotificationKey object:nil userInfo:dic];
[self.view endEditing:YES];
[self.littleGameView handleSelfInExitEvent];
[self.littleGameView destroyMG];
[self handleFirstOutRoom];
[self dismissViewControllerAnimated:YES completion:nil];
});
} cancelHandler:^{
}];
} else {
[[XPRoomMiniManager shareManager] configRoomInfo:self.roomInfo];
[[XPRoomMiniManager shareManager] configUserInfo:self.userInfo];
[[XPRoomMiniManager shareManager] configCurrentMusic:self.functionView.getCurrentMusic isPlaying:self.functionView.isPlaying];
NSDictionary * dic = @{@"roomInfo":roomInfoDic, @"userInfo":userInfoDic, @"microQueue":queueDic};
[[NSNotificationCenter defaultCenter] postNotificationName:kRoomMiniNotificationKey object:nil userInfo:dic];
[self.view endEditing:YES];
[self.littleGameView handleSelfInExitEvent];
[self.littleGameView destroyMG];
[self handleFirstOutRoom];
[self dismissViewControllerAnimated:YES completion:nil];
}
}else {
[[XPRoomMiniManager shareManager] configRoomInfo:self.roomInfo];
[[XPRoomMiniManager shareManager] configUserInfo:self.userInfo];
[[XPRoomMiniManager shareManager] configCurrentMusic:self.functionView.getCurrentMusic isPlaying:self.functionView.isPlaying];
NSDictionary * dic = @{@"roomInfo":roomInfoDic, @"userInfo":userInfoDic, @"microQueue":queueDic};
[[NSNotificationCenter defaultCenter] postNotificationName:kRoomMiniNotificationKey object:nil userInfo:dic];
[self.view endEditing:YES];
[self handleFirstOutRoom];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
- (void)showPKPanel {
[self.functionView showPKPanelView];
}
- (UINavigationController *)getCurrentNav {
return self.navigationController;
}
-(UIView *)getSuperView{
return self.view;
}
- (NSMutableDictionary<NSString *,MicroQueueModel *> *)getMicroQueue {
return [self.stageView getMicroQueue];
}
- (NSArray *)getRoomSuperAdminList {
return self.superMangerList;
}
- (NSArray *)getRoomPKGroupTeamList {
return [self.functionView getRoomPKGroupTeamList];
}
- (BOOL)isRoomPKPlaying {
return [self.functionView isRoomPKPlaying];
}
- (void)onMicroQueueUpdate:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
[self.menuContainerView onMicroQueueUpdate:queue];
[self.functionView onRoomUpdate];
[self.functionView onMicroQueueUpdate:queue];
// 获取当前用户麦位状态
NSDictionary *currentStatus = [self getCurrentUserMicStatus:queue];
BOOL isOnMic = [currentStatus[@"isOnMic"] boolValue];
if (isOnMic) {
self.anchorScrollView.scrollEnabled = NO;
} else {
self.anchorScrollView.scrollEnabled = YES;
}
// 🔧 注意CP相关的处理逻辑已迁移到 NIMChatroomEventTypeQueueChange 中
// 这里只处理UI相关的更新不再处理CP逻辑
}
- (CGPoint)animationPointAtStageViewByUid:(NSString *)uid {
return [self.stageView animationPointAtStageViewByUid:uid];
}
- (CGPoint)animationPointAtStageViewByIndex:(NSInteger)index {
return [self.stageView animationPointAtStageViewByIndex:index];
}
- (void)getRoomBoomInfoSuccess:(NSArray <BoomDetailModel*> *)models {
[[RoomBoomManager sharedManager] updateBoomDetailArray:models];
[self.sideMenu updateForBoomDetailArray:models];
}
- (void)getRoomBoomExplosionSuccess:(BoomInfoModel *)model {
[[RoomBoomManager sharedManager] receiveEnterRoomBoom:model];
}
/// 统一的CP API成功回调方法
- (void)handleMicCpListSuccess:(NSArray<MicCpInfoModel *> *)cpList {
NSLog(@"🔧 CP API成功回调接收到 %lu 条CP数据", (unsigned long)cpList.count);
self.currentCpList = cpList;
// 使用 MicMidpointRectManager 更新CP列表缓存并重绘
MicMidpointRectManager *manager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (manager) {
// 计算麦位总数
NSInteger micCount = [self getMicCountForRoomType:self.roomInfo.type];
[manager updateCpListCacheAndRedraw:cpList
stageView:self.stageView
micCount:micCount
roomType:self.roomInfo.type];
}
// 如果当前用户麦位状态发生变化(上麦、下麦、换位),发送 NIM message
if (self.currentUserMicStatusChanged) {
[self sendMicRelationshipNIMessage:cpList];
self.currentUserMicStatusChanged = NO; // 重置标志
}
NSLog(@"🔧 CP API成功回调处理完成");
}
/// 按房间UID获取CP列表成功回调
- (void)getMicCpListByRoomUidSuccess:(NSArray <MicCpInfoModel *> *)cpList {
NSLog(@"🔧 按房间UID获取CP列表成功接收到 %lu 条CP数据", (unsigned long)cpList.count);
[self handleMicCpListSuccess:cpList];
}
/// 按用户UID列表获取CP列表成功回调
- (void)getMicCpListByUidListSuccess:(NSArray<MicCpInfoModel *> *)cpList {
NSLog(@"🔧 按用户UID列表获取CP列表成功接收到 %lu 条CP数据", (unsigned long)cpList.count);
[self handleMicCpListSuccess:cpList];
}
#pragma mark - 首次退出非自己的房间,处理是否需要弹新用户充值优惠
- (void)handleFirstOutRoom {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults boolForKey:kHadQuitOtherRoomKey] && self.userInfo.uid != self.roomInfo.uid && self.userInfo.showLimitCharge) {
[defaults setBool:YES forKey:kHadQuitOtherRoomKey];
[defaults synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName:kNewUserRechargeKey object:nil];
}
}
#pragma mark - XPRoomLittleGameContainerViewDelegate
- (void)hiddenSudGamePostionView {
[self.functionView hiddenSudGamePostionView];
}
#pragma mark - AnchorRoomScrollViewDelegate
- (void)anchorScrollViewScrollToNext:(AnchorRoomScrollView *)anchorScrollView {
if (!self) {
return;
}
[self.view endEditing:YES];
[self showAnchorLoading];
[self.presenter getCycleAnchorRoomList:[NSString stringWithFormat:@"%ld", self.roomInfo.uid]];
}
- (void)anchorScrollViewScrollToPrevious:(AnchorRoomScrollView *)anchorScrollView {
if (!self || self.anchorRoomList.count <= 0) {
return;
}
[self.view endEditing:YES];
[self showAnchorLoading];
if (self.anchorIndex < self.anchorRoomList.count) {
NSString * roomUid = [self.anchorRoomList xpSafeObjectAtIndex:self.anchorIndex];
[self.presenter getCurrentRoomInfo:roomUid];
self.anchorIndex --;
}else {
[self showErrorToast:YMLocalizedString(@"XPRoomViewController13")];
self.anchorScrollView.contentOffset = CGPointMake(0, KScreenHeight);
}
}
#pragma mark - XPRoomSettingInputViewDelegate
///点击了完成
- (void)xPRoomSettingInputView:(XPRoomSettingInputView *)view didClickConfirm:(NSString *)text type:(RoomSettingInputType)type {
NSString *pwdDes = [DESEncrypt encryptUseDES:text key:KeyWithType(KeyType_PasswordEncode)];
if ([pwdDes isEqualToString:self.roomInfo.roomPwd]) {
[TTPopup dismiss];
view.isPwdError = NO;
if (self.roomInfo.type == RoomType_Anchor) {
[self __removeAllViews];
if (!self.anchorScrollView.superview) {
[self.view insertSubview:self.anchorScrollView belowSubview:self.menuContainerView];
}
[self.anchorScrollView addSubview:self.backContainerView];
[self.anchorScrollView addSubview:self.stageView];
[self.anchorScrollView addSubview:self.messageContainerView];
[self.anchorScrollView addSubview:self.quickMessageContainerView];
[self.anchorScrollView addSubview:self.sideMenu];
[self.anchorScrollView addSubview:self.roomHeaderView];
if (!self.menuContainerView.superview) {
[self.view addSubview:self.menuContainerView];
}
[self.anchorScrollView addSubview:self.functionView];
[self.view bringSubviewToFront:self.animationView];
[self updateViewConstraintsOnAnchorRoom];
[self changeStageViewOnAnchorRoom];
[self.roomHeaderView onRoomEntered];
[self.sideMenu onRoomEntered];
[self.backContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
[self.littleGameView onRoomEntered];
if ([XPRoomMiniManager shareManager].getRoomInfo.uid != self.roomUid.integerValue) {// 最小化进房 还是原来的房间的话 不需要重新进入云信 因为压根没退
// 🔧 防护:检查是否允许进房
if (!self.isExitingRoom && self.isViewActive) {
[self.presenter enterNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId] user:self.userInfo];
}
[self.functionView onRoomEntered];
[self.messageContainerView onRoomEntered];
[self.menuContainerView onRoomEntered];
} else {
//虽然没退出房间 但是队列还是要拿的
[self.stageView onRoomMiniEntered];
[self.messageContainerView onRoomMiniEntered];
[self.functionView onRoomMiniEntered];
[self.functionView onRoomEntered];
[self.menuContainerView onRoomMiniEntered];
// 🔧 最小化进房:初始化当前用户麦位状态
[self initializeCurrentUserMicStatusForMiniEnter];
}
[[XPRoomMiniManager shareManager] configRoomInfo:nil];
[[XPRoomMiniManager shareManager] configUserInfo:nil];
[[XPRoomMiniManager shareManager] configCurrentMusic:nil isPlaying:NO];
} else {
[self.roomHeaderView onRoomEntered];
[self.sideMenu onRoomEntered];
[self.menuContainerView onRoomEntered];
[self changeStageViewOnRoomUpdate];
[self.backContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
[self.functionView onRoomEntered];
// 🔧 防护:检查是否允许进房
if (!self.isExitingRoom && self.isViewActive) {
[self.presenter enterNIMRoom:[NSString stringWithFormat:@"%ld", self.roomInfo.roomId] user:self.userInfo];
}
[self.messageContainerView onRoomEntered];
[self.littleGameView onRoomEntered];
[[XPRoomMiniManager shareManager] configRoomInfo:nil];
[[XPRoomMiniManager shareManager] configUserInfo:nil];
[[XPRoomMiniManager shareManager] configCurrentMusic:nil isPlaying:NO];
}
} else {
view.isPwdError = YES;
}
}
- (void)didClickCancel:(RoomSettingInputType)type {
if (type == RoomSettingInputType_Input_Pwd) {
[self enterRoomFail:0];
}
}
-(NSInteger)getPublicScreenType{
return self.messageContainerView.type;
}
- (void)displayMusicPanel {
[self.functionView showMusicPanel];
}
- (void)updateScreenMessageState:(BOOL)isClose {
self.roomInfo.isCloseScreen = isClose;
}
#pragma mark - XPFirstRechargeViewDelegate
-(void)rechargeHandle{}
-(void)contactCustomerService{
TTAlertConfig *config = [[TTAlertConfig alloc]init];
config.title = YMLocalizedString(@"XPIAPRechargeViewController7");
config.message = YMLocalizedString(@"XPIAPRechargeViewController8");
TTAlertButtonConfig *confirmButtonConfig = [[TTAlertButtonConfig alloc]init];
confirmButtonConfig.title = YMLocalizedString(@"XPIAPRechargeViewController9");
UIImage *image = [UIImage gradientColorImageFromColors:@[UIColorFromRGB(0x13E2F5),UIColorFromRGB(0x9DB4FF),UIColorFromRGB(0xCC67FF)] gradientType:GradientTypeLeftToRight imgSize:CGSizeMake(200, 200)];
confirmButtonConfig.backgroundColor = [UIColor colorWithPatternImage:image];
confirmButtonConfig.cornerRadius = 38/2;
config.confirmButtonConfig = confirmButtonConfig;
@kWeakify(self);
[TTPopup alertWithConfig:config confirmHandler:^{
@kStrongify(self);
[self requestContactCustomerService];
} cancelHandler:^{
}];
}
-(void)requestContactCustomerService{
[XNDJTDDLoadingTool showLoading];
[Api requestContactCustomerServiceCompletion:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
[XNDJTDDLoadingTool hideHUD];
if(code == 200){
NSString *uid = [NSString stringWithFormat:@"%@",data.data];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NIMSession * session = [NIMSession session:uid type:NIMSessionTypeP2P];
SessionViewController * sessionVC = [[SessionViewController alloc] initWithSession:session];
[self.getCurrentNav pushViewController:sessionVC animated:YES];
});
}
}];
}
- (XPRoomBackContainerView *)backContainerView {
if (!_backContainerView) {
_backContainerView = [[XPRoomBackContainerView alloc] initWithdelegate:self];
}
return _backContainerView;
}
- (RoomHeaderView *)roomHeaderView {
if (!_roomHeaderView) {
_roomHeaderView = [[RoomHeaderView alloc] initWithDelegate:self];
}
return _roomHeaderView;
}
- (MsRoomMessageMainView *)messageContainerView {
if (!_messageContainerView) {
_messageContainerView = [[MsRoomMessageMainView alloc] initWithDelegate:self];
}
return _messageContainerView;
}
- (XPRoomQuickMessageContainView *)quickMessageContainerView {
if(!_quickMessageContainerView) {
_quickMessageContainerView = [[XPRoomQuickMessageContainView alloc] initWithDelegate:self];
}
return _quickMessageContainerView;
}
- (XPRoomMenuContainerView *)menuContainerView {
if (!_menuContainerView) {
_menuContainerView = [[XPRoomMenuContainerView alloc] initWithDelegate:self];
@kWeakify(self);
[_menuContainerView setUpdateHeight:^(CGFloat height) {
@kStrongify(self);
[self.menuContainerView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.leading.trailing.mas_equalTo(self.view);
if (iPhoneXSeries) {
make.bottom.mas_equalTo(self.view);
} else {
make.bottom.mas_equalTo(self.view).offset(-10);
}
make.height.mas_equalTo(height + kSafeAreaBottomHeight);
}];
}];
}
return _menuContainerView;
}
- (RoomSideMenu *)sideMenu {
if (!_sideMenu) {
_sideMenu = [[RoomSideMenu alloc] initWithDelegate:self];
}
return _sideMenu;
}
- (RoomAnimationView *)animationView {
if (!_animationView) {
_animationView = [[RoomAnimationView alloc] initWithHostDelegate:self];
}
return _animationView;
}
- (XPRoomFunctionContainerView *)functionView {
if (!_functionView) {
_functionView = [[XPRoomFunctionContainerView alloc] initWithDelegate:self];
}
return _functionView;
}
- (StageView *)stageView {
// 🔧 修改:如果 StageViewManager 已初始化,返回其管理的 stageView
if (self.stageViewManager && self.stageViewManager.currentStageView) {
return self.stageViewManager.currentStageView;
}
// 🔧 降级:只有在 StageViewManager 不可用时才创建默认的 SocialStageView
if (!_stageView) {
_stageView = [[SocialStageView alloc] initWithDelegate:self];
_stageView.alpha = 0;
NSLog(@"⚠️ 使用降级 stageView: SocialStageView");
}
return _stageView;
}
- (TenMicStageView *)tenMicStageView {
if (!_tenMicStageView) {
_tenMicStageView = [[TenMicStageView alloc] initWithDelegate:self];
}
return _tenMicStageView;
}
- (NineteenMicStageView *)nineteenMicStageView {
if (!_nineteenMicStageView) {
_nineteenMicStageView = [[NineteenMicStageView alloc] initWithDelegate:self];
}
return _nineteenMicStageView;
}
- (TwentyMicStageView *)twentyMicStageView {
if (!_twentyMicStageView) {
_twentyMicStageView = [[TwentyMicStageView alloc] initWithDelegate:self];
}
return _twentyMicStageView;
}
- (FifteenMicStageView *)fifteenMicStageView {
if (!_fifteenMicStageView) {
_fifteenMicStageView = [[FifteenMicStageView alloc] initWithDelegate:self];
}
return _fifteenMicStageView;
}
- (XPRoomLittleGameContainerView *)littleGameView {
if (!_littleGameView) {
_littleGameView = [[XPRoomLittleGameContainerView alloc] initWithDelegate:self];
_littleGameView.delegate = self;
}
return _littleGameView;
}
- (AnchorRoomScrollView *)anchorScrollView {
if (!_anchorScrollView) {
_anchorScrollView = [[AnchorRoomScrollView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
_anchorScrollView.anchorScrollDelegate = self;
_anchorScrollView.hostDelegate = self;
if (@available(iOS 11.0, *)) {
_anchorScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
}
return _anchorScrollView;
}
- (AnchorRoomSrollTipView *)anchorScrollTipView {
if (!_anchorScrollTipView) {
_anchorScrollTipView = [[AnchorRoomSrollTipView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
}
return _anchorScrollTipView;
}
- (NSMutableArray<NSString *> *)anchorRoomList {
if (!_anchorRoomList) {
_anchorRoomList = [NSMutableArray array];
}
return _anchorRoomList;
}
-(XPFreeGiftsObtainView *)freeView{
if (!_freeView){
_freeView = [[XPFreeGiftsObtainView alloc]initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
}
return _freeView;
}
- (PIRoomEnterRedPacketView *)redPacketView{
if(!_redPacketView){
_redPacketView = [[PIRoomEnterRedPacketView alloc]initWithFrame:CGRectZero];
_redPacketView.hidden = YES;
_redPacketView.isInRoom = YES;
_redPacketView.delegate = self;
}
return _redPacketView;
}
#pragma mark - 公共房间消息转发处理
- (void)handlePublicRoomMessageForward:(NSNotification *)notification {
NIMMessage *message = notification.object;
if (![message isKindOfClass:[NIMMessage class]]) {
NSLog(@"XPRoomViewController: 收到无效的公共房间转发消息");
return;
}
// 检查房间是否处于活跃状态
if (!self.roomInfo || !self.messageContainerView) {
NSLog(@"XPRoomViewController: 房间未就绪,忽略公共房间转发消息");
return;
}
if ([message.messageObject isKindOfClass:[NIMNotificationObject class]]) {
NIMNotificationObject *notiMsg = (NIMNotificationObject *)message.messageObject;
if ([notiMsg respondsToSelector:@selector(content)]) {
NIMChatroomNotificationContent *content = (NIMChatroomNotificationContent *)notiMsg.content;
if (content.eventType == NIMChatroomEventTypeExit || content.eventType == NIMChatroomEventTypeKicked) {
return;
}
}
}
switch (message.messageType) {
case NIMMessageTypeNotification:
break;
case NIMMessageTypeCustom:
[self handleNimCustomTypeMessage:message];
break;
case NIMMessageTypeText:
[self.messageContainerView handleNIMTextMessage:message];
[self.littleGameView handleNIMTextMessage:message];
break;
case NIMMessageTypeTip:
[self.messageContainerView handleNIMTextMessage:message];
break;
case NIMMessageTypeImage:
[self.messageContainerView handleNIMImageMessage:message];
break;
default:
break;
}
}
// 新增StageViewManager 懒加载
- (StageViewManager *)stageViewManager {
if (!_stageViewManager) {
_stageViewManager = [[StageViewManager alloc] init];
NSLog(@"✅ StageViewManager 懒加载初始化成功");
}
return _stageViewManager;
}
// 新增:设置 StageViewManager
- (void)setupStageViewManager {
// 触发懒加载
[self stageViewManager];
NSLog(@"✅ StageViewManager 设置完成");
}
// 调试方法:检查 stageView 状态
- (void)debugStageViewStatus {
NSLog(@"🔍 DEBUG: stageView 状态检查");
NSLog(@" - stageView: %@", self.stageView);
NSLog(@" - stageView.alpha: %.2f", self.stageView.alpha);
NSLog(@" - stageView.hidden: %@", self.stageView.hidden ? @"YES" : @"NO");
NSLog(@" - stageView.superview: %@", self.stageView.superview);
NSLog(@" - stageView.frame: %@", NSStringFromCGRect(self.stageView.frame));
NSLog(@" - stageView.class: %@", NSStringFromClass([self.stageView class]));
}
/// 在 StageView 上绘制符合条件的相邻麦位中点矩形
- (void)drawSocialStageMidpointRects {
if (!self.stageView) {
NSLog(@"🔧 当前没有 stageView跳过中点矩形绘制");
return;
}
// 使用 MicMidpointRectManager 绘制社交舞台中点矩形
MicMidpointRectManager *manager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (manager) {
// 计算麦位总数
NSInteger micCount = [self getMicCountForRoomType:self.roomInfo.type];
[manager drawSocialStageMidpointRectsWithStageView:self.stageView
micCount:micCount
roomType:self.roomInfo.type];
} else {
NSLog(@"🔧 无法获取 midpointRectManager跳过中点矩形绘制");
}
}
/// 进房成功后调用CP相关API
- (void)callMicCpListByRoomUidAfterRoomEntered {
NSLog(@"🔧 进房成功:开始调用 micCpListByRoomUid API");
if (!self.roomInfo) {
NSLog(@"⚠️ 进房成功roomInfo为空跳过CP API调用");
return;
}
NSString *roomUid = [NSString stringWithFormat:@"%ld", (long)self.roomInfo.uid];
NSLog(@"🔧 进房成功房间UID: %@, 房间类型: %ld", roomUid, (long)self.roomInfo.type);
// 调用 micCpListByRoomUid 方法
if ([self.presenter respondsToSelector:@selector(micCpListByRoomUid:)]) {
NSLog(@"🔧 进房成功:调用 micCpListByRoomUid: %@", roomUid);
[self.presenter micCpListByRoomUid:roomUid];
} else {
NSLog(@"⚠️ 进房成功presenter不支持micCpListByRoomUid方法");
}
NSLog(@"🔧 进房成功micCpListByRoomUid API调用完成");
}
/// 获取当前用户及其左右相邻用户的uid列表最多3个
- (NSArray<NSString *> *)getAllMicUserUidsWithQueue:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
NSMutableArray<NSString *> *micUserUids = [NSMutableArray array];
if (!self.roomInfo) {
NSLog(@"⚠️ 获取mic用户列表roomInfo为空");
return micUserUids;
}
// 获取当前用户的麦位位置
NSInteger currentUserMicPosition = self.currentUserMicPosition;
if (currentUserMicPosition == -1) {
NSLog(@"🔧 获取mic用户列表当前用户不在麦上返回空数据");
// 通知 manager 移除该用户 UID 的相关数据
NSInteger currentUid = [AccountInfoStorage instance].getUid.integerValue;
[self removeCpDataForUids:@[@(currentUid)]];
return micUserUids;
}
// 获取最大麦位数
NSInteger maxMicCount = 0;
switch (self.roomInfo.type) {
case RoomType_Room:
maxMicCount = 9;
break;
case RoomType_10Mic:
maxMicCount = 10;
break;
case RoomType_15Mic:
maxMicCount = 15;
break;
case RoomType_19Mic:
maxMicCount = 19;
break;
case RoomType_20Mic:
maxMicCount = 20;
break;
default:
maxMicCount = 9; // 默认9麦
break;
}
// 获取当前用户及其左右相邻用户的UID
NSInteger leftPosition = currentUserMicPosition - 1;
NSInteger rightPosition = currentUserMicPosition + 1;
// 获取左边用户
if (leftPosition >= 0) {
NSString *leftPositionKey = [NSString stringWithFormat:@"%ld", (long)leftPosition];
MicroQueueModel *leftMicModel = queue[leftPositionKey];
if (leftMicModel && leftMicModel.userInfo && leftMicModel.userInfo.uid > 0) {
[micUserUids addObject:[NSString stringWithFormat:@"%ld", (long)leftMicModel.userInfo.uid]];
}
}
// 获取当前用户
NSString *currentPositionKey = [NSString stringWithFormat:@"%ld", (long)currentUserMicPosition];
MicroQueueModel *currentMicModel = queue[currentPositionKey];
if (currentMicModel && currentMicModel.userInfo && currentMicModel.userInfo.uid > 0) {
[micUserUids addObject:[NSString stringWithFormat:@"%ld", (long)currentMicModel.userInfo.uid]];
}
// 获取右边用户
if (rightPosition < maxMicCount) {
NSString *rightPositionKey = [NSString stringWithFormat:@"%ld", (long)rightPosition];
MicroQueueModel *rightMicModel = queue[rightPositionKey];
if (rightMicModel && rightMicModel.userInfo && rightMicModel.userInfo.uid > 0) {
[micUserUids addObject:[NSString stringWithFormat:@"%ld", (long)rightMicModel.userInfo.uid]];
}
}
// 🔧 修复即使只有当前用户一个人也返回当前用户的UID确保能触发NIM message
if (micUserUids.count == 1) {
NSLog(@"🔧 获取mic用户列表只有当前用户一个人但仍需发送NIM message");
// 不返回空数组保持当前用户UID确保能触发CP API和NIM message
}
NSLog(@"🔧 获取mic用户列表当前用户麦位 %ld找到 %lu 个相关用户: %@",
(long)currentUserMicPosition, (unsigned long)micUserUids.count, micUserUids);
return micUserUids;
}
/// 处理其他用户mic变化包括切换场景
- (void)handleOtherUserMicChange:(UserInfoModel *)userInfo changeType:(NSInteger)changeType {
if (!self.stageView || ![self.stageView respondsToSelector:@selector(midpointRectManager)]) {
NSLog(@"🔧 处理其他用户mic变化stageView 不支持 midpointRectManager跳过处理");
return;
}
// 获取当前麦位队列
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (!currentQueue) {
NSLog(@"🔧 处理其他用户mic变化无法获取当前麦位队列跳过处理");
return;
}
// 使用 MicMidpointRectManager 处理其他用户mic变化
MicMidpointRectManager *manager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (manager) {
// 计算麦位总数
NSInteger micCount = [self getMicCountForRoomType:self.roomInfo.type];
[manager handleOtherUserMicChange:userInfo
changeType:changeType
stageView:self.stageView
micCount:micCount
queue:currentQueue
roomType:self.roomInfo.type];
}
// 处理API调用
if (changeType == 2) { // 其他用户下麦
// 获取剩余用户列表
NSMutableArray<NSString *> *remainingUids = [NSMutableArray array];
for (NSString *positionKey in currentQueue.allKeys) {
MicroQueueModel *micModel = currentQueue[positionKey];
if (micModel && micModel.userInfo && micModel.userInfo.uid > 0) {
[remainingUids addObject:[NSString stringWithFormat:@"%ld", (long)micModel.userInfo.uid]];
}
}
// 调用API更新剩余用户的CP状态
if (remainingUids.count > 0) {
[self.presenter micCpListByUidList:remainingUids];
}
// 重绘
[self drawSocialStageMidpointRects];
} else if (changeType == 1) { // 其他用户上麦
// 调用CP API更新状态
[self callMicCpListByUidListWithQueue:currentQueue];
// 重新绘制所有CP关系
[self drawSocialStageMidpointRects];
}
}
/// 通用的CP数据移除方法
- (void)removeCpDataForUids:(NSArray<NSNumber *> *)uids {
if (!self.stageView || ![self.stageView respondsToSelector:@selector(midpointRectManager)]) {
NSLog(@"🔧 移除CP数据stageView 不支持 midpointRectManager跳过处理");
return;
}
if (uids.count == 0) {
NSLog(@"🔧 移除CP数据没有需要移除的用户跳过处理");
return;
}
// 使用 MicMidpointRectManager 移除CP数据
MicMidpointRectManager *manager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (manager) {
[manager removeCpDataForUids:uids];
NSLog(@"🔧 移除CP数据完成UIDs %@", uids);
}
}
/// 统一的CP API调用方法
- (void)callMicCpListByUidListWithQueue:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
NSLog(@"🔧 调用CP API开始获取mic用户列表");
// 直接从queue获取当前所有在mic上的用户uid列表确保数据是最新的
NSMutableArray<NSString *> *micUserUids = [NSMutableArray array];
if (queue) {
for (NSString *positionKey in queue.allKeys) {
MicroQueueModel *micModel = queue[positionKey];
if (micModel && micModel.userInfo && micModel.userInfo.uid > 0) {
[micUserUids addObject:[NSString stringWithFormat:@"%ld", (long)micModel.userInfo.uid]];
}
}
}
if (micUserUids.count == 0) {
NSLog(@"🔧 调用CP API没有用户在mic上跳过API调用");
return;
}
// 调用 micCpListByUidList 方法
NSLog(@"🔧 调用CP APImicCpListByUidList: %@", micUserUids);
[self.presenter micCpListByUidList:micUserUids];
NSLog(@"🔧 调用CP API完成");
}
/// 初始化当前用户的麦位状态
- (void)initializeCurrentUserMicStatus {
NSLog(@"🔧 初始化当前用户麦位状态");
// 重置状态
self.currentUserMicStatusChanged = NO;
self.currentUserWasOnMic = NO;
self.currentUserMicPosition = -1;
self.hasCompletedRoomInitialization = NO; // 标记为未完成初始化
// 获取当前麦位状态
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (currentQueue && currentQueue.count > 0) {
NSDictionary *currentStatus = [self getCurrentUserMicStatus:currentQueue];
BOOL isOnMic = [currentStatus[@"isOnMic"] boolValue];
NSInteger micPosition = [currentStatus[@"micPosition"] integerValue];
self.currentUserWasOnMic = isOnMic;
self.currentUserMicPosition = micPosition;
NSLog(@"🔧 初始化完成 - 当前用户是否在麦上: %@, 麦位: %ld",
isOnMic ? @"" : @"", (long)micPosition);
} else {
NSLog(@"🔧 初始化完成 - 当前没有麦位数据");
}
// 延迟设置初始化完成标志确保进房时的麦位更新不会触发API调用
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.hasCompletedRoomInitialization = YES;
NSLog(@"🔧 进房初始化完成,后续麦位变动将触发 micCpListByUidList 调用");
});
}
/// 最小化进房时初始化当前用户麦位状态
- (void)initializeCurrentUserMicStatusForMiniEnter {
NSLog(@"🔧 最小化进房:初始化当前用户麦位状态");
// 重置状态
self.currentUserMicStatusChanged = NO;
self.currentUserWasOnMic = NO;
self.currentUserMicPosition = -1;
self.hasCompletedRoomInitialization = NO; // 标记为未完成初始化
// 获取当前麦位状态
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
if (currentQueue && currentQueue.count > 0) {
NSDictionary *currentStatus = [self getCurrentUserMicStatus:currentQueue];
BOOL isOnMic = [currentStatus[@"isOnMic"] boolValue];
NSInteger micPosition = [currentStatus[@"micPosition"] integerValue];
self.currentUserWasOnMic = isOnMic;
self.currentUserMicPosition = micPosition;
NSLog(@"🔧 最小化进房初始化完成 - 当前用户是否在麦上: %@, 麦位: %ld",
isOnMic ? @"" : @"", (long)micPosition);
} else {
NSLog(@"🔧 最小化进房初始化完成 - 当前没有麦位数据");
}
// 最小化进房时立即设置初始化完成标志,因为云信已经连接
self.hasCompletedRoomInitialization = YES;
NSLog(@"🔧 最小化进房初始化完成,后续麦位变动将触发 micCpListByUidList 调用");
}
/// 获取当前用户的麦位状态
- (NSDictionary *)getCurrentUserMicStatus:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
NSInteger currentUid = [AccountInfoStorage instance].getUid.integerValue;
BOOL isOnMic = NO;
NSInteger micPosition = -1;
for (NSString *position in queue.allKeys) {
MicroQueueModel *micModel = queue[position];
if (micModel.userInfo.uid > 0 && micModel.userInfo.uid == currentUid) {
isOnMic = YES;
micPosition = position.integerValue;
break;
}
}
return @{
@"isOnMic": @(isOnMic),
@"micPosition": @(micPosition)
};
}
/// 检查当前用户麦位状态是否发生变化(上麦、下麦、换位)
- (BOOL)checkCurrentUserMicStatusChanged:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
NSDictionary *currentStatus = [self getCurrentUserMicStatus:queue];
BOOL currentUserOnMic = [currentStatus[@"isOnMic"] boolValue];
NSInteger currentMicPosition = [currentStatus[@"micPosition"] integerValue];
// 情况1之前不在麦上现在在麦上上麦
if (currentUserOnMic && !self.currentUserWasOnMic) {
NSLog(@"🔧 检测到当前用户刚刚上麦,麦位: %ld", (long)currentMicPosition);
return YES;
}
// 情况2之前在麦上现在不在麦上下麦
if (!currentUserOnMic && self.currentUserWasOnMic) {
NSLog(@"🔧 检测到当前用户刚刚下麦,之前麦位: %ld", (long)self.currentUserMicPosition);
return YES;
}
// 情况3之前在麦上现在也在麦上但位置不同换位
if (currentUserOnMic && self.currentUserWasOnMic &&
currentMicPosition != self.currentUserMicPosition) {
NSLog(@"🔧 检测到当前用户换麦位,从 %ld 换到 %ld",
(long)self.currentUserMicPosition, (long)currentMicPosition);
return YES;
}
return NO;
}
/// 更新当前用户的麦位状态
- (void)updateCurrentUserMicStatus:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
NSDictionary *currentStatus = [self getCurrentUserMicStatus:queue];
BOOL currentUserOnMic = [currentStatus[@"isOnMic"] boolValue];
NSInteger micPosition = [currentStatus[@"micPosition"] integerValue];
// 检查麦位状态是否发生变化(上麦、下麦、换位)
if ([self checkCurrentUserMicStatusChanged:queue]) {
self.currentUserMicStatusChanged = YES;
}
// 更新状态
self.currentUserWasOnMic = currentUserOnMic;
self.currentUserMicPosition = micPosition;
}
/// 发送麦位关系 NIM message
- (void)sendMicRelationshipNIMessage:(NSArray<MicCpInfoModel *> *)cpList {
NSLog(@"🔧 发送麦位关系 NIM messageCP数据条数: %ld当前用户麦位: %ld",
(long)cpList.count, (long)self.currentUserMicPosition);
if (!self.roomInfo) {
NSLog(@"⚠️ 发送麦位关系 NIM messageroomInfo为空跳过发送");
return;
}
// 将 CP 数据转换为 JSON string
NSMutableArray *cpDataArray = [NSMutableArray array];
for (MicCpInfoModel *cpInfo in cpList) {
NSDictionary *cpDict = @{
@"uid": @(cpInfo.uid),
@"loverUid": @(cpInfo.loverUid),
@"cpLevel": @(cpInfo.cpLevel)
};
[cpDataArray addObject:cpDict];
}
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:cpDataArray.copy
options:0
error:&error];
if (error) {
NSLog(@"❌ 发送麦位关系 NIM messageJSON序列化失败 - %@", error.localizedDescription);
return;
}
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(@"🔧 发送麦位关系 NIM messageJSON数据 - %@", jsonString);
NSDictionary *finalData = @{@"first": @(MicRelationship_Type),
@"second":@(MicRelationship_CP),
@"data":jsonString};
// 创建 AttachmentModel
AttachmentModel *attachment = [[AttachmentModel alloc] init];
attachment.first = MicRelationship_Type; // 1001
attachment.second = MicRelationship_CP; // 10011
attachment.data = finalData;
// 创建 NIM message
NIMMessage *message = [[NIMMessage alloc] init];
NIMCustomObject *object = [[NIMCustomObject alloc] init];
object.attachment = attachment;
message.messageObject = object;
// 设置用户信息到 remoteExt
UserInfoModel *userInfo = [self getUserInfo];
XPMessageRemoteExtModel *extModel = [[XPMessageRemoteExtModel alloc] init];
extModel.androidBubbleUrl = userInfo.androidBubbleUrl;
extModel.iosBubbleUrl = userInfo.iosBubbleUrl;
extModel.fromSayHelloChannel = userInfo.fromSayHelloChannel;
extModel.platformRole = userInfo.platformRole;
NSMutableDictionary *remoteExt = [NSMutableDictionary dictionaryWithObject:extModel.model2dictionary forKey:[NSString stringWithFormat:@"%ld", userInfo.uid]];
message.remoteExt = remoteExt;
// 构造会话
NSString *sessionID = [NSString stringWithFormat:@"%ld", self.roomInfo.roomId];
NIMSession *session = [NIMSession session:sessionID type:NIMSessionTypeChatroom];
// 发送消息
[[NIMSDK sharedSDK].chatManager sendMessage:message toSession:session completion:^(NSError * _Nullable error) {
if (error) {
NSLog(@"❌ 发送麦位关系 NIM message 失败 - %@", error.localizedDescription);
} else {
NSLog(@"✅ 发送麦位关系 NIM message 成功");
}
}];
}
/// 更新 MicMidpointRectManager 的缓存
- (void)updateMicMidpointRectManagerCache:(NSArray<MicCpInfoModel *> *)cpList {
if (!self.stageView) {
NSLog(@"<EFBFBD><EFBFBD> 更新缓存stageView 为空,跳过缓存更新");
return;
}
MicMidpointRectManager *midpointRectManager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (midpointRectManager) {
// 设置房间类型
midpointRectManager.roomType = self.roomInfo.type;
// 获取当前用户及其相邻用户的UID列表
NSMutableDictionary<NSString *,MicroQueueModel *> *currentQueue = [self.stageView getMicroQueue];
NSArray<NSString *> *micUserUids = [self getAllMicUserUidsWithQueue:currentQueue];
// 转换为 NSNumber 数组
NSMutableArray<NSNumber *> *uidNumbers = [NSMutableArray array];
for (NSString *uidString in micUserUids) {
[uidNumbers addObject:@(uidString.integerValue)];
}
// 更新缓存:先移除相关用户的旧数据,再合并新数据
[midpointRectManager mergeCpListCache:cpList replaceForUids:uidNumbers];
NSLog(@"<EFBFBD><EFBFBD> 更新缓存完成CP数据条数 %lu相关用户 %@",
(unsigned long)cpList.count, uidNumbers);
}
}
/// 更新 MicMidpointRectManager 的麦位快照
- (void)updateMicMidpointRectManagerSnapshot {
if (!self.stageView) {
NSLog(@"<EFBFBD><EFBFBD> 更新快照stageView 为空,跳过快照更新");
return;
}
// 获取当前舞台类型的麦位总数
NSInteger micCount = 0;
if (self.roomInfo) {
switch (self.roomInfo.type) {
case RoomType_Room: micCount = 9; break;
case RoomType_10Mic: micCount = 10; break;
case RoomType_15Mic: micCount = 15; break;
case RoomType_19Mic: micCount = 19; break;
case RoomType_20Mic: micCount = 20; break;
default: micCount = 9; break;
}
}
MicMidpointRectManager *midpointRectManager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)];
if (midpointRectManager) {
// 设置房间类型
midpointRectManager.roomType = self.roomInfo.type;
[midpointRectManager rebuildMicSnapshotWithStageView:self.stageView micCount:micCount];
NSLog(@"🔧 更新麦位快照完成:麦位总数 %ld", (long)micCount);
}
}
/// 根据房间类型获取麦位总数
- (NSInteger)getMicCountForRoomType:(NSInteger)roomType {
switch (roomType) {
case RoomType_Room: return 9;
case RoomType_10Mic: return 10;
case RoomType_15Mic: return 15;
case RoomType_19Mic: return 19;
case RoomType_20Mic: return 20;
default: return 9;
}
}
/// 检查19 mic房间中央位置position 6用户资格
- (void)checkCentralPositionUserQualification:(NSMutableDictionary<NSString *,MicroQueueModel *> *)queue {
// 只检查19 mic房间
if (self.roomInfo.type != RoomType_19Mic) {
return;
}
NSLog(@"🔧 检查19 mic房间中央位置用户资格");
// 查找中央位置position 6的用户
MicroQueueModel *centralMicModel = nil;
for (NSString *positionKey in queue.allKeys) {
MicroQueueModel *micModel = queue[positionKey];
if (micModel.microState.position == 6) {
centralMicModel = micModel;
break;
}
}
// 如果中央位置有用户,检查其资格
if (centralMicModel && centralMicModel.userInfo) {
NSString *userId = [NSString stringWithFormat:@"%ld", (long)centralMicModel.userInfo.uid];
NSLog(@"🔧 中央位置用户:%@,检查资格", userId);
// 调用Boss位置资格检查API
[Api requestBossMicUp:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
NSLog(@"✅ 中央位置用户资格检查通过:%@", userId);
} else {
NSLog(@"❌ 中央位置用户资格检查失败:%@,错误:%@", userId, msg);
// 不满足条件的用户需要下mic
[self forceDownMicForUser:userId atPosition:6];
}
} roomUid:@(self.roomInfo.uid).stringValue uid:userId];
} else {
NSLog(@"🔧 中央位置无用户,无需检查");
}
}
/// 强制用户下mic
- (void)forceDownMicForUser:(NSString *)userId atPosition:(NSInteger)position {
NSLog(@"🔧 强制用户下mic%@,位置:%ld", userId, (long)position);
// 使用 NIM 的 API 强制下mic
NIMChatroomQueueRemoveRequest *request = [[NIMChatroomQueueRemoveRequest alloc] init];
request.key = [NSString stringWithFormat:@"%ld", (long)position];
request.roomId = @(self.roomInfo.roomId).stringValue;
[[NIMSDK sharedSDK].chatroomManager removeChatroomQueueObject:request completion:^(NSError * _Nullable error, NSDictionary<NSString *,NSString *> * _Nullable element) {
if (error) {
NSLog(@"❌ 强制下mic失败%@,错误:%@", userId, error.localizedDescription);
[XNDJTDDLoadingTool showErrorWithMessage:@"强制下mic失败"];
} else {
NSLog(@"✅ 强制下mic成功%@", userId);
[XNDJTDDLoadingTool showSuccessWithMessage:@"中央位置需要满足贡献值要求已自动下mic"];
}
}];
}
@end