diff --git a/YuMi/Modules/YMRoom/View/StageView/AnchorPKStageView.m b/YuMi/Modules/YMRoom/View/StageView/AnchorPKStageView.m index 9ec9a589..c85f5e25 100644 --- a/YuMi/Modules/YMRoom/View/StageView/AnchorPKStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/AnchorPKStageView.m @@ -157,9 +157,14 @@ CGRect r = [self rectForViewAtIndex:right]; if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } - (void)onRoomEntered { diff --git a/YuMi/Modules/YMRoom/View/StageView/AnchorStageView.m b/YuMi/Modules/YMRoom/View/StageView/AnchorStageView.m index e14bd5f7..fbdc8307 100644 --- a/YuMi/Modules/YMRoom/View/StageView/AnchorStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/AnchorStageView.m @@ -121,9 +121,14 @@ if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } - (void)onRoomEntered { diff --git a/YuMi/Modules/YMRoom/View/StageView/DatingStageView.m b/YuMi/Modules/YMRoom/View/StageView/DatingStageView.m index 599c17fa..dda72122 100644 --- a/YuMi/Modules/YMRoom/View/StageView/DatingStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/DatingStageView.m @@ -169,9 +169,14 @@ CGRect r = [self rectForViewAtIndex:right]; if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } /** diff --git a/YuMi/Modules/YMRoom/View/StageView/FifteenMicStageView.m b/YuMi/Modules/YMRoom/View/StageView/FifteenMicStageView.m index 1e4300d4..796068cc 100644 --- a/YuMi/Modules/YMRoom/View/StageView/FifteenMicStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/FifteenMicStageView.m @@ -148,9 +148,14 @@ if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } @end diff --git a/YuMi/Modules/YMRoom/View/StageView/LittleGameScrollStageView.m b/YuMi/Modules/YMRoom/View/StageView/LittleGameScrollStageView.m index 9d7e0033..67b7ed3f 100644 --- a/YuMi/Modules/YMRoom/View/StageView/LittleGameScrollStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/LittleGameScrollStageView.m @@ -148,10 +148,15 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey; CGRect r = [self rectForViewAtIndex:right]; if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; // 坐标在自身(scrollView 内),返回给调用方在自身坐标使用 - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } - (void)didSelectAtIndex:(NSInteger)index { diff --git a/YuMi/Modules/YMRoom/View/StageView/LittleGameStageView.m b/YuMi/Modules/YMRoom/View/StageView/LittleGameStageView.m index 04dbc60f..9ae36f96 100644 --- a/YuMi/Modules/YMRoom/View/StageView/LittleGameStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/LittleGameStageView.m @@ -92,9 +92,14 @@ UIKIT_EXTERN NSString * const kRoomRoomLittleGameMiniStageNotificationKey; CGRect r = [self rectForViewAtIndex:right]; if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } - (void)didSelectAtIndex:(NSInteger)index { diff --git a/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m b/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m index 5ca18094..69ac1128 100644 --- a/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m +++ b/YuMi/Modules/YMRoom/View/StageView/MicMidpointRectManager.m @@ -94,7 +94,11 @@ @"rightUid": @(rightUid) }; - if (leftUid <= 0 || rightUid <= 0 || cpList.count == 0) { + if (cpList.count < 2) { + return; + } + + if (leftUid <= 0 && rightUid <= 0) { return; } // 遍历匹配并播放对应等级的SVGA diff --git a/YuMi/Modules/YMRoom/View/StageView/NineteenMicStageView.m b/YuMi/Modules/YMRoom/View/StageView/NineteenMicStageView.m index 42a7cdb3..91219b86 100644 --- a/YuMi/Modules/YMRoom/View/StageView/NineteenMicStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/NineteenMicStageView.m @@ -193,9 +193,14 @@ static const NSInteger kMicCountPerRow = 5; if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } @end diff --git a/YuMi/Modules/YMRoom/View/StageView/StageView.m b/YuMi/Modules/YMRoom/View/StageView/StageView.m index 8dbd237c..b00fd0c4 100644 --- a/YuMi/Modules/YMRoom/View/StageView/StageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/StageView.m @@ -1223,7 +1223,7 @@ if (CGRectIsEmpty(leftRect) || CGRectIsEmpty(rightRect)) { return CGRectZero; } - // 判断是否同一“行”:中心 y 差值小于高度的三分之一 + // 判断是否同一"行":中心 y 差值小于高度的三分之一 CGFloat leftCenterY = CGRectGetMidY(leftRect); CGFloat rightCenterY = CGRectGetMidY(rightRect); if (fabs(leftCenterY - rightCenterY) > (CGRectGetHeight(leftRect) / 3.0)) { @@ -1233,9 +1233,14 @@ CGFloat leftCenterX = CGRectGetMidX(leftRect); CGFloat rightCenterX = CGRectGetMidX(rightRect); CGFloat midX = (leftCenterX + rightCenterX) / 2.0; - CGFloat midY = (leftCenterY + rightCenterY) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(leftRect); + CGFloat rightTopY = CGRectGetMinY(rightRect); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } #pragma mark - StageViewProtocol - 基本上都是工具方法 diff --git a/YuMi/Modules/YMRoom/View/StageView/TenMicStageView.m b/YuMi/Modules/YMRoom/View/StageView/TenMicStageView.m index dec91100..dd9ffe56 100644 --- a/YuMi/Modules/YMRoom/View/StageView/TenMicStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/TenMicStageView.m @@ -115,9 +115,14 @@ if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } @end diff --git a/YuMi/Modules/YMRoom/View/StageView/TwentyMicStageView.m b/YuMi/Modules/YMRoom/View/StageView/TwentyMicStageView.m index 751abf29..feaa81a3 100644 --- a/YuMi/Modules/YMRoom/View/StageView/TwentyMicStageView.m +++ b/YuMi/Modules/YMRoom/View/StageView/TwentyMicStageView.m @@ -111,9 +111,14 @@ static const NSInteger kMicCountPerRow = 5; CGRect r = [self rectForViewAtIndex:right]; if (CGRectIsEmpty(l) || CGRectIsEmpty(r)) return CGRectZero; CGFloat midX = (CGRectGetMidX(l) + CGRectGetMidX(r)) / 2.0; - CGFloat midY = (CGRectGetMidY(l) + CGRectGetMidY(r)) / 2.0; + + // 🔧 修改:使用两个麦位矩形的顶部对齐,而不是中心对齐 + CGFloat leftTopY = CGRectGetMinY(l); + CGFloat rightTopY = CGRectGetMinY(r); + CGFloat midTopY = (leftTopY + rightTopY) / 2.0; + CGFloat size = 75.0; - return CGRectMake(midX - size / 2.0, midY - size / 2.0, size, size); + return CGRectMake(midX - size / 2.0, midTopY, size, size); } diff --git a/YuMi/Modules/YMRoom/View/XPRoomViewController.m b/YuMi/Modules/YMRoom/View/XPRoomViewController.m index 36a07b08..40ed1ca5 100644 --- a/YuMi/Modules/YMRoom/View/XPRoomViewController.m +++ b/YuMi/Modules/YMRoom/View/XPRoomViewController.m @@ -1422,6 +1422,9 @@ XPCandyTreeInsufficientBalanceViewDelegate> [self.functionView onRoomMiniEntered]; [self.functionView onRoomEntered]; [self.menuContainerView onRoomMiniEntered]; + + // 🔧 最小化进房:初始化当前用户麦位状态 + [self initializeCurrentUserMicStatusForMiniEnter]; } [[XPRoomMiniManager shareManager] configRoomInfo:nil]; [[XPRoomMiniManager shareManager] configUserInfo:nil]; @@ -1500,6 +1503,9 @@ XPCandyTreeInsufficientBalanceViewDelegate> [self.functionView onRoomMiniEntered]; [self.functionView onRoomEntered]; [self.menuContainerView onRoomMiniEntered]; + + // 🔧 最小化进房:初始化当前用户麦位状态 + [self initializeCurrentUserMicStatusForMiniEnter]; } [[XPRoomMiniManager shareManager] configRoomInfo:nil]; [[XPRoomMiniManager shareManager] configUserInfo:nil]; @@ -1600,6 +1606,9 @@ XPCandyTreeInsufficientBalanceViewDelegate> } [self.messageContainerView onRoomMiniEntered]; + + // 🔧 最小化进房:初始化当前用户麦位状态 + [self initializeCurrentUserMicStatusForMiniEnter]; } [self cleanMiniRoomStatues]; } @@ -1936,6 +1945,9 @@ XPCandyTreeInsufficientBalanceViewDelegate> [self.functionView onRoomMiniEntered]; [self.functionView onRoomEntered]; [self.menuContainerView onRoomMiniEntered]; + + // 🔧 最小化进房:初始化当前用户麦位状态 + [self initializeCurrentUserMicStatusForMiniEnter]; } [self cleanMiniRoomStatues]; @@ -2139,9 +2151,76 @@ XPCandyTreeInsufficientBalanceViewDelegate> 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 *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 *currentQueue = [self.stageView getMicroQueue]; + if (currentQueue) { + [self handleMicChangeForCP:currentQueue]; + } + }); + } + } + } } break; case NIMChatroomEventTypeAddManager: { @@ -2482,40 +2561,56 @@ XPCandyTreeInsufficientBalanceViewDelegate> } } +/// 处理麦位变化相关的CP逻辑 +- (void)handleMicChangeForCP:(NSMutableDictionary *)queue { + NSLog(@"🔧 处理麦位变化相关的CP逻辑"); + + // 更新当前用户的麦位状态 + [self updateCurrentUserMicStatus:queue]; + + // 更新麦位快照 + [self updateMicMidpointRectManagerSnapshot]; + + // 只有在完成进房初始化后才处理CP相关逻辑 + if (self.hasCompletedRoomInitialization) { + // 检测下麦用户并处理CP关系缓存 + [self handleDownMicEventIfNeeded:queue]; + + // 🔧 新增:处理用户切换mic场景,确保所有CP SVGA状态正确更新 + [self handleMicSwitchScenarioIfNeeded:queue]; + + // 调用CP API + [self callMicCpListByUidListOnMicChangeWithQueue:queue]; + } else { + NSLog(@"🔧 进房初始化中,跳过CP相关处理"); + } +} + /// 处理麦位关系CP消息 - (void)handleMicRelationshipCPMessage:(AttachmentModel *)attachment { NSLog(@"🔧 接收到麦位关系CP消息"); - if (!attachment.data || ![attachment.data isKindOfClass:[NSString class]]) { + if (!attachment.data) { NSLog(@"⚠️ 麦位关系CP消息:data格式错误,跳过处理"); return; } - NSString *jsonString = (NSString *)attachment.data; + NSDictionary *jsonDic = (NSDictionary *)attachment.data; + NSString *jsonString = [jsonDic objectForKey:@"data"]; NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; - NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + NSArray *dataArray = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if (error) { NSLog(@"❌ 麦位关系CP消息:JSON解析失败 - %@", error.localizedDescription); return; } - // 验证消息格式 - NSNumber *first = jsonDict[@"first"]; - NSNumber *second = jsonDict[@"second"]; - NSArray *dataArray = jsonDict[@"data"]; - - if (!first || !second || ![dataArray isKindOfClass:[NSArray class]]) { + if (![dataArray isKindOfClass:[NSArray class]]) { NSLog(@"⚠️ 麦位关系CP消息:消息格式错误,跳过处理"); return; } - if (first.integerValue != MicRelationship_Type || second.integerValue != MicRelationship_CP) { - NSLog(@"⚠️ 麦位关系CP消息:消息类型不匹配,跳过处理"); - return; - } - // 解析CP数据 NSMutableArray *cpList = [NSMutableArray array]; for (NSDictionary *cpDict in dataArray) { @@ -3070,9 +3165,6 @@ XPCandyTreeInsufficientBalanceViewDelegate> [self.functionView onRoomUpdate]; [self.functionView onMicroQueueUpdate:queue]; - // 更新当前用户的麦位状态 - [self updateCurrentUserMicStatus:queue]; - // 获取当前用户麦位状态 NSDictionary *currentStatus = [self getCurrentUserMicStatus:queue]; BOOL isOnMic = [currentStatus[@"isOnMic"] boolValue]; @@ -3083,18 +3175,8 @@ XPCandyTreeInsufficientBalanceViewDelegate> self.anchorScrollView.scrollEnabled = YES; } - // 🔧 新增:只有在完成进房初始化后才调用mic位变动API - if (self.hasCompletedRoomInitialization) { - // 检测下麦用户并处理CP关系缓存 - [self handleDownMicEventIfNeeded:queue]; - - // 🔧 :更新麦位快照 - [self updateMicMidpointRectManagerSnapshot]; - - [self callMicCpListByUidListOnMicChangeWithQueue:queue]; - } else { - NSLog(@"🔧 进房初始化中,跳过 micCpListByUidList 调用"); - } + // 🔧 注意:CP相关的处理逻辑已迁移到 NIMChatroomEventTypeQueueChange 中 + // 这里只处理UI相关的更新,不再处理CP逻辑 } - (CGPoint)animationPointAtStageViewByUid:(NSString *)uid { @@ -3116,6 +3198,8 @@ XPCandyTreeInsufficientBalanceViewDelegate> - (void)getMicCpListByRoomUidSuccess:(NSArray *)cpList { self.currentCpList = cpList; + // 写入 MicMidpointRectManager 缓存后再刷新绘制 + [self updateMicMidpointRectManagerCache:cpList]; // 刷新绘制,按CP关系播放对应SVGA [self drawSocialStageMidpointRects]; } @@ -3228,6 +3312,9 @@ XPCandyTreeInsufficientBalanceViewDelegate> [self.functionView onRoomMiniEntered]; [self.functionView onRoomEntered]; [self.menuContainerView onRoomMiniEntered]; + + // 🔧 最小化进房:初始化当前用户麦位状态 + [self initializeCurrentUserMicStatusForMiniEnter]; } [[XPRoomMiniManager shareManager] configRoomInfo:nil]; [[XPRoomMiniManager shareManager] configUserInfo:nil]; @@ -3718,6 +3805,8 @@ XPCandyTreeInsufficientBalanceViewDelegate> NSInteger currentUserMicPosition = self.currentUserMicPosition; if (currentUserMicPosition == -1) { NSLog(@"🔧 获取mic用户列表:当前用户不在麦上,返回空数据"); + // 通知 manager 移除该用户 UID 的相关数据 + [self removeCurrentUserCpDataFromManager]; return micUserUids; } @@ -3776,6 +3865,8 @@ XPCandyTreeInsufficientBalanceViewDelegate> // 如果只有当前用户一个人,返回空数据 if (micUserUids.count == 1) { NSLog(@"🔧 获取mic用户列表:只有当前用户一个人,返回空数据"); + // 通知 manager 移除该用户 UID 的相关数据 + [self removeCurrentUserCpDataFromManager]; return [NSArray array]; } @@ -3784,6 +3875,120 @@ XPCandyTreeInsufficientBalanceViewDelegate> return micUserUids; } +/// 处理其他用户mic变化(包括切换场景) +- (void)handleOtherUserMicChange:(UserInfoModel *)userInfo changeType:(NSInteger)changeType { + if (!self.stageView || ![self.stageView respondsToSelector:@selector(midpointRectManager)]) { + NSLog(@"🔧 处理其他用户mic变化:stageView 不支持 midpointRectManager,跳过处理"); + return; + } + + MicMidpointRectManager *midpointRectManager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)]; + if (!midpointRectManager) { + NSLog(@"🔧 处理其他用户mic变化:无法获取 midpointRectManager,跳过处理"); + return; + } + + NSInteger userUid = userInfo.uid; + NSArray *userUids = @[@(userUid)]; + + if (changeType == 2) { + // 其他用户下麦 + NSLog(@"🔧 处理其他用户下麦:UID %ld", (long)userUid); + + // 更新快照 + [self updateMicMidpointRectManagerSnapshot]; + + // 清除与该用户相关的cp与SVGA + [midpointRectManager removeCpEntriesForUids:userUids]; + [midpointRectManager removeMidpointRectsForUids:userUids]; + + // 重绘(根据现有缓存) + [self drawSocialStageMidpointRects]; + + NSLog(@"🔧 其他用户下麦处理完成:UID %ld", (long)userUid); + } else if (changeType == 1) { + // 其他用户上麦 + NSLog(@"🔧 处理其他用户上麦:UID %ld", (long)userUid); + + // 更新快照 + [self updateMicMidpointRectManagerSnapshot]; + + // 重新绘制所有CP关系(包括新上麦用户可能产生的CP关系) + [self drawSocialStageMidpointRects]; + + NSLog(@"🔧 其他用户上麦处理完成:UID %ld", (long)userUid); + } +} + +/// 移除当前用户的CP数据从manager中 +- (void)removeCurrentUserCpDataFromManager { + if (!self.stageView || ![self.stageView respondsToSelector:@selector(midpointRectManager)]) { + NSLog(@"🔧 移除当前用户CP数据:stageView 不支持 midpointRectManager,跳过处理"); + return; + } + + NSInteger currentUid = [AccountInfoStorage instance].getUid.integerValue; + NSArray *removedUids = @[@(currentUid)]; + + MicMidpointRectManager *midpointRectManager = (MicMidpointRectManager *)[self.stageView performSelector:@selector(midpointRectManager)]; + if (midpointRectManager) { + // 从缓存移除 + [midpointRectManager removeCpEntriesForUids:removedUids]; + // 从UI移除 + [midpointRectManager removeMidpointRectsForUids:removedUids]; + NSLog(@"🔧 移除当前用户CP数据完成:UID %ld", (long)currentUid); + } +} + +/// 处理用户切换mic场景,确保所有CP SVGA状态正确更新 +- (void)handleMicSwitchScenarioIfNeeded:(NSMutableDictionary *)queue { + if (!self.stageView || ![self.stageView respondsToSelector:@selector(midpointRectManager)]) { + NSLog(@"🔧 处理切换mic场景:stageView 不支持 midpointRectManager,跳过处理"); + return; + } + + // 🔧 改进:基于当前用户麦位状态变化来检测切换场景 + // 检查当前用户是否发生了麦位变化 + BOOL hasMicPositionChanged = self.currentUserMicStatusChanged; + + if (hasMicPositionChanged) { + NSLog(@"🔧 检测到当前用户麦位状态变化,可能是切换mic场景"); + + // 获取当前舞台类型的麦位总数 + NSInteger micCount = 0; + if (self.roomInfo) { + switch (self.roomInfo.type) { + case RoomType_Game: 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) { + // 对于麦位变化场景,需要重新构建所有CP关系 + // 1. 清除所有现有的中点矩形和SVGA + [midpointRectManager removeAllMidpointRects]; + + // 2. 重新构建麦位快照 + [midpointRectManager rebuildMicSnapshotWithStageView:self.stageView micCount:micCount]; + + // 3. 重新绘制所有CP关系 + [self drawSocialStageMidpointRects]; + + NSLog(@"🔧 用户麦位变化场景处理完成:重新构建了所有CP关系"); + } + + // 🔧 注意:不在这里重置状态标志,等待API成功回调后再重置 + // 状态标志将在 getMicCpListByUidListSuccess 中重置,确保能发送云信消息 + } else { + NSLog(@"🔧 当前用户麦位状态未变化,跳过切换场景处理"); + } +} + /// 检测并处理下麦事件 - (void)handleDownMicEventIfNeeded:(NSMutableDictionary *)queue { if (!self.stageView || ![self.stageView respondsToSelector:@selector(midpointRectManager)]) { @@ -3874,6 +4079,37 @@ XPCandyTreeInsufficientBalanceViewDelegate> }); } +/// 最小化进房时初始化当前用户麦位状态 +- (void)initializeCurrentUserMicStatusForMiniEnter { + NSLog(@"🔧 最小化进房:初始化当前用户麦位状态"); + + // 重置状态 + self.currentUserMicStatusChanged = NO; + self.currentUserWasOnMic = NO; + self.currentUserMicPosition = -1; + self.hasCompletedRoomInitialization = NO; // 标记为未完成初始化 + + // 获取当前麦位状态 + NSMutableDictionary *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 *)queue { NSInteger currentUid = [AccountInfoStorage instance].getUid.integerValue; @@ -3961,12 +4197,10 @@ XPCandyTreeInsufficientBalanceViewDelegate> [cpDataArray addObject:cpDict]; } - NSDictionary *finalData = @{@"first": @(MicRelationship_Type), - @"second":@(MicRelationship_CP), - @"data":cpDataArray.copy}; - NSError *error = nil; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:finalData options:0 error:&error]; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:cpDataArray.copy + options:0 + error:&error]; if (error) { NSLog(@"❌ 发送麦位关系 NIM message:JSON序列化失败 - %@", error.localizedDescription); return; @@ -3975,11 +4209,15 @@ XPCandyTreeInsufficientBalanceViewDelegate> NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; NSLog(@"🔧 发送麦位关系 NIM message:JSON数据 - %@", 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 = jsonString; + attachment.data = finalData; // 创建 NIM message NIMMessage *message = [[NIMMessage alloc] init]; diff --git a/YuMi/Resources/cp/mic_cp_lv1.svga b/YuMi/Resources/cp/mic_cp_lv1.svga index 62e75916..a16a35b5 100644 Binary files a/YuMi/Resources/cp/mic_cp_lv1.svga and b/YuMi/Resources/cp/mic_cp_lv1.svga differ diff --git a/YuMi/Resources/cp/mic_cp_lv2.svga b/YuMi/Resources/cp/mic_cp_lv2.svga index 56a1f0ff..00ee051a 100644 Binary files a/YuMi/Resources/cp/mic_cp_lv2.svga and b/YuMi/Resources/cp/mic_cp_lv2.svga differ diff --git a/YuMi/Resources/cp/mic_cp_lv3.svga b/YuMi/Resources/cp/mic_cp_lv3.svga index f17e1445..c7fc65b5 100644 Binary files a/YuMi/Resources/cp/mic_cp_lv3.svga and b/YuMi/Resources/cp/mic_cp_lv3.svga differ diff --git a/YuMi/Resources/cp/mic_cp_lv4.svga b/YuMi/Resources/cp/mic_cp_lv4.svga index 083b6c18..1cd1d01b 100644 Binary files a/YuMi/Resources/cp/mic_cp_lv4.svga and b/YuMi/Resources/cp/mic_cp_lv4.svga differ diff --git a/YuMi/Resources/cp/mic_cp_lv5.svga b/YuMi/Resources/cp/mic_cp_lv5.svga index 4e80a739..7dd82a71 100644 Binary files a/YuMi/Resources/cp/mic_cp_lv5.svga and b/YuMi/Resources/cp/mic_cp_lv5.svga differ diff --git a/YuMi/Tools/File/UploadFile.m b/YuMi/Tools/File/UploadFile.m index 69a52936..878b29da 100644 --- a/YuMi/Tools/File/UploadFile.m +++ b/YuMi/Tools/File/UploadFile.m @@ -178,7 +178,6 @@ static UploadFile* manager; }]; [[QCloudCOSTransferMangerService defaultCOSTransferManager] UploadObject:put]; - } /// 上传一个Image diff --git a/current_user_mic_switch_flow.md b/current_user_mic_switch_flow.md new file mode 100644 index 00000000..3b20eb7a --- /dev/null +++ b/current_user_mic_switch_flow.md @@ -0,0 +1,98 @@ +# 当前用户切换 mic 时云信消息发送流程 + +## 修复后的完整流程 + +``` +1. 用户切换 mic (位置A → 位置B) + ↓ +2. 接收云信通知 NIMChatroomEventTypeQueueChange + - 下麦通知 (changeType == 2) + - 上麦通知 (changeType == 1) + ↓ +3. 调用 handleMicChangeForCP:queue + ↓ +4. 在 handleMicChangeForCP 中执行: + a) updateCurrentUserMicStatus:queue + - 检测到麦位变化 + - 设置 currentUserMicStatusChanged = YES + b) updateMicMidpointRectManagerSnapshot + c) handleDownMicEventIfNeeded:queue + d) handleMicSwitchScenarioIfNeeded:queue + - 检测到 currentUserMicStatusChanged = YES + - 清除所有CP关系 + - 重新构建CP关系 + - 🔧 不重置状态标志(关键修复) + e) callMicCpListByUidListOnMicChangeWithQueue:queue + - 调用 micCpListByUidList API + ↓ +5. API 成功回调 getMicCpListByUidListSuccess:cpList + ↓ +6. 在 getMicCpListByUidListSuccess 中: + a) 更新 CP 缓存 + b) 刷新绘制 + c) 检查 currentUserMicStatusChanged = YES + d) 🔧 发送云信消息 sendMicRelationshipNIMessage:cpList + e) 重置状态标志 currentUserMicStatusChanged = NO + ↓ +7. 其他用户收到云信消息,更新CP关系 +``` + +## 关键修复点 + +### 修复前的问题 +- `handleMicSwitchScenarioIfNeeded` 中提前重置 `currentUserMicStatusChanged = NO` +- API 成功回调时状态标志已经是 `NO` +- 无法发送云信消息 + +### 修复后的逻辑 +- `handleMicSwitchScenarioIfNeeded` 中不重置状态标志 +- 状态标志保持 `YES` 直到 API 成功回调 +- API 成功回调时正确发送云信消息 +- 发送消息后重置状态标志 + +## 代码修改 + +### 修改前 +```objc +// 在 handleMicSwitchScenarioIfNeeded 中 +if (hasMicPositionChanged) { + // 处理麦位变化场景 + [midpointRectManager removeAllMidpointRects]; + [midpointRectManager rebuildMicSnapshotWithStageView:self.stageView micCount:micCount]; + [self drawSocialStageMidpointRects]; + + // ❌ 提前重置状态标志 + self.currentUserMicStatusChanged = NO; +} +``` + +### 修改后 +```objc +// 在 handleMicSwitchScenarioIfNeeded 中 +if (hasMicPositionChanged) { + // 处理麦位变化场景 + [midpointRectManager removeAllMidpointRects]; + [midpointRectManager rebuildMicSnapshotWithStageView:self.stageView micCount:micCount]; + [self drawSocialStageMidpointRects]; + + // ✅ 不在这里重置状态标志,等待API成功回调后再重置 + // 状态标志将在 getMicCpListByUidListSuccess 中重置,确保能发送云信消息 +} +``` + +## 验证要点 + +1. **当前用户切换mic时**: + - ✅ 正确检测到麦位状态变化 + - ✅ 重新构建所有CP关系 + - ✅ 调用 micCpListByUidList API + - ✅ API成功回调时发送云信消息 + - ✅ 状态标志在发送消息后正确重置 + +2. **其他用户切换mic时**: + - ✅ 通过 handleOtherUserMicChange 处理 + - ✅ 不发送云信消息(由切换用户发送) + +3. **边界情况**: + - ✅ 当前用户不在麦上时清理CP数据 + - ✅ 只有当前用户一个人时清理CP数据