// // RoomAnimationView.m // YuMi // // Created by P on 2025/1/13. // //banner 手势处理会有遮挡其他视图操作的问题 #import "RoomAnimationView.h" #import #import #import #import #import #import "UIView+VAP.h" #import "ClientConfig.h" #import "XPGiftStorage.h" #import "UserInfoModel.h" #import "RoomInfoModel.h" #import "MicroQueueModel.h" #import "AttachmentModel.h" #import "QGVAPConfigModel.h" #import "ActivityInfoModel.h" #import "GiftReceiveInfoModel.h" #import "XPRoomEnterHideTipView.h" #import "XPMessageRemoteExtModel.h" #import "PIBaseAnimationViewModel.h" #import "XPRoomGiftAnimationParser.h" #import "GiftComboManager.h" #import "GiftAnimationManager.h" #import "BannerScheduler.h" #import "TouchAreaCacheManager.h" #import "GestureOptimizer.h" #import "MSRoomGameWebVC.h" #import "XPRoomViewController.h" #import "PIUniversalBannerView.h" #import "PIUniversalBannerModel.h" #import "CPGiftBanner.h" #import "CPLevelUpAnimation.h" #import "CPBindingAnimation.h" #import "GameUniversalBannerView.h" #import "LuckyGiftWinningFlagView.h" #import "LuckyGiftWinningBannerView.h" #import "RoomHighValueGiftBannerAnimation.h" #import "LuckyPackageBannerView.h" #import "BravoGiftBannerView.h" #import "XCCurrentVCStackManager.h" #import "BravoGiftWinningFlagView.h" #import "RoomEnterModel.h" // Old Methods #import "DatingInfoModel.h" #import "XPWebViewController.h" #import "XPRoomCandyGiftView.h" #import "XPSailingAnimationView.h" #import "AcrossRoomPKPrizeModel.h" #import "XPRoomNobleLevelUpView.h" #import "XPRoomGiftCompoundView.h" #import "XPRoomGiftBroadcastView.h" #import "XPRoomLuckyBigPrizeView.h" #import "XPTreasureFairyGiftView.h" #import "XPAcrossRoomPKPrizeView.h" #import "PIRoomGiftBroadcastWindow.h" #import "XPRoomDatingAnimationView.h" #import "XPRoomAnchorRankBannerView.h" #import "XPRoomGraffitiGiftAnimationView.h" #import "XPRoomFunctionContainerView.h" #import "XPRoomRankEntranceView.h" #import "XPRoomAnchorRankEnterView.h" #import "MSRoomOnLineView.h" #import "BannerScheduler.h" static const CGFloat kTipViewStayDuration = 3.0; //static const CGFloat kTipViewMoveDuration = 0.5; @interface RoomAnimationView () < PAGViewListener, SVGAPlayerDelegate, HWDMP4PlayDelegate, GiftAnimationDelegate, NIMBroadcastManagerDelegate, PIUniversalBannerViewDelegate, XPRoomGiftBroadcastViewDelegate, XPRoomLuckyBigPrizeViewDelegate, PIRoomGiftBroadcastWindowDelegate, XPRoomAnchorRankBannerViewDelegate, XPRoomGraffitiGiftAnimationViewDelegate, UIGestureRecognizerDelegate, BannerSchedulerDelegate > @property (nonatomic, weak) idhostDelegate; ///最底层 @property (nonatomic, strong) XPRoomAnimationHitView *bottomContainer; ///中层 @property (nonatomic, strong) XPRoomAnimationHitView *middleContainer; ///顶层 @property (nonatomic, strong) XPRoomAnimationHitView *topContainer; @property (nonatomic, strong) XPRoomAnimationHitView *bannerContainer; @property (nonatomic, strong) UIView *bannerSwipeGestureContainer; @property (nonatomic, strong) UIView *bannerLeftTapGestureContainer; @property (nonatomic, strong) UIView *bannerRightTapGestureContainer; /// --- 进场 /// 存放进房待播放动画的"队列"(这里用数组模拟队列 FIFO) @property (nonatomic, strong) NSMutableArray *enterRoomAnimationQueue; /// 标记当前是否有进房动画在执行 @property (nonatomic, assign) BOOL isEnterRoomAnimating; @property (nonatomic, strong) SVGAImageView *enterEffectView; /// --- 礼物 @property(nonatomic, strong) PAGView *pagGiftEffectView; @property(nonatomic, strong) SVGAParser *vggParser; @property(nonatomic, assign) BOOL isPlayingGiftEffect; @property(nonatomic, strong) NSMutableArray *svgaQueue; @property(nonatomic, strong) VAPView *vapGiftEffectView; @property(nonatomic, strong) SVGAImageView *vggGiftEffectView; @property(nonatomic, strong) NSMutableArray *mp4AvatarLoaders; @property(nonatomic, copy) NSString *GiftDynamicEffectListPath; @property(nonatomic, strong) dispatch_source_t giftEffectTimer; @property(nonatomic, strong) dispatch_queue_t giftEffectsQueue; @property(nonatomic, strong) XPRoomGiftAnimationParser *vapParser; @property(nonatomic, strong) GiftAnimationManager *giftAnimationManager; @property(nonatomic, strong) GiftReceiveInfoModel *mp4TempReceiveInfoModel; /// --- 飘屏 @property (nonatomic, strong) NSMutableArray *roomBannertModelsQueueV2; // 特效播放队列,包括幸运礼物 banner, CP banner 和 CP 特效 @property (nonatomic, assign) BOOL isRoomBannerV2Displaying; @property (nonatomic, strong) BannerScheduler *bannerScheduler; // 统一的 Banner 播放调度器 // --- Brove 金币动画 @property (nonatomic, strong) SVGAVideoEntity *broveSVGAEntity_lv_1; @property (nonatomic, strong) SVGAVideoEntity *broveSVGAEntity_lv_2; @property (nonatomic, strong) SVGAVideoEntity *broveSVGAEntity_lv_3; @property (nonatomic, strong) NSMutableArray *broveSVGAQueue; @property (nonatomic, assign) BOOL isBroveSVGAPlaying; @property (nonatomic, assign) NSInteger viewIndex; /// --- 座驾 ///座驾pag动效 @property(nonatomic, strong) PAGView *carPagEffectView; ///座驾VAP特效 @property(nonatomic, strong) VAPView *carVapEffectView; @property(nonatomic, strong) SVGAImageView *carSVGAEffectView; @property(nonatomic, strong) NSMutableArray *carEffectQueue; /// --- 旧逻辑,有需要再重构 @property(nonatomic, assign) BOOL isPlayOfB; @property(nonatomic, strong) NSMutableArray *animationListB; // 玩法 @property(nonatomic, assign) CGFloat broadCastHieght; @property (nonatomic, strong) NetImageView *imageLoader; @property (nonatomic, strong) SVGAImageView *datingEffectView; @property (nonatomic, strong) NSMutableArray *sailingQueue; @property (nonatomic, strong) NSMutableArray *datingEffectQueue; @property (nonatomic, strong) NSMutableArray *graffitiGiftQueue; @property (nonatomic, strong) NSMutableArray *acrossRoomPKQueue; @property (nonatomic,strong) SVGAImageView *luckyGiftEffectView; ///福袋礼物送礼物的特效 @property (nonatomic, strong) VAPView *luckyVapGiftEffectView; ///福袋礼物VAP特效 @property(nonatomic, strong) NSMutableArray * universalBannerViewCaches; // Banner 手势相关属性 @property(nonatomic, assign) CGPoint savedTapPoint; @property(nonatomic, assign) BOOL hasSavedTapPoint; @end @implementation RoomAnimationView - (void)setIsPlayOfB:(BOOL)isPlayOfB { _isPlayOfB = isPlayOfB; } - (void)dealloc { NSLog(@" RoomAnimationView: dealloc 开始"); [self removeItSelf]; NSLog(@" RoomAnimationView: dealloc 完成"); } - (void)removeItSelf { NSLog(@"�� RoomAnimationView: 开始销毁"); // 🔧 新增:取消所有动画,防止异步回调 [self cancelAllAnimations]; // 移除广播代理 [[NIMSDK sharedSDK].broadcastManager removeDelegate:self]; // 取消所有延迟执行 [NSObject cancelPreviousPerformRequestsWithTarget:self]; // 清理所有 POP 动画 [self pop_removeAllAnimations]; // 清理定时器 if (_giftEffectTimer && !dispatch_source_testcancel(_giftEffectTimer)) { dispatch_source_cancel(_giftEffectTimer); _giftEffectTimer = nil; } // 清理队列 if (_giftEffectsQueue) { self.giftEffectsQueue = NULL; } // �� 新增:清理所有 banner 视图,防止 block 强引用 [self cleanupAllSubviews]; // 🔧 新增:清理 BannerScheduler if (self.bannerScheduler) { NSLog(@"�� 清理 BannerScheduler"); [self.bannerScheduler clearQueue]; [self.bannerScheduler pause]; self.bannerScheduler = nil; } // 清理手势识别器 [self cleanupGestureRecognizers]; // 清理缓存管理器 [self cleanupCacheManagers]; // 移除通知监听 [self removeNotificationObservers]; NSLog(@"�� RoomAnimationView: 销毁完成"); } - (void)cancelAllAnimations { NSLog(@" 取消所有动画"); // 取消所有 POP 动画 [self pop_removeAllAnimations]; // 取消所有子视图的 POP 动画 NSArray *containers = @[self.bannerContainer, self.topContainer, self.middleContainer, self.bottomContainer]; for (UIView *container in containers) { if (!container) continue; for (UIView *subview in container.subviews) { [subview pop_removeAllAnimations]; } } // 取消所有 UIView 动画 [UIView setAnimationsEnabled:NO]; NSLog(@"🔄 所有动画取消完成"); } - (void)cleanupAllSubviews { NSLog(@" 清理所有子视图"); // 清理所有容器的子视图 NSArray *containers = @[self.bannerContainer, self.topContainer, self.middleContainer, self.bottomContainer]; for (UIView *container in containers) { if (!container) continue; NSMutableArray *viewsToRemove = [NSMutableArray array]; for (UIView *subview in container.subviews) { [viewsToRemove addObject:subview]; NSLog(@" 标记移除视图: %@ (从容器: %@)", NSStringFromClass([subview class]), NSStringFromClass([container class])); } for (UIView *view in viewsToRemove) { // 取消视图上的所有动画 [view pop_removeAllAnimations]; // 清理视图上的手势识别器 if (view.gestureRecognizers.count > 0) { NSLog(@" 清理视图 %@ 上的 %lu 个手势识别器", NSStringFromClass([view class]), (unsigned long)view.gestureRecognizers.count); for (UIGestureRecognizer *gesture in view.gestureRecognizers.copy) { [view removeGestureRecognizer:gesture]; } } // 强制移除视图 [view removeFromSuperview]; } } NSLog(@"🔄 所有子视图清理完成"); } - (void)__cleanupAllBannerViews { NSLog(@"�� 清理所有 Banner 视图"); // 清理 bannerContainer 中的所有子视图 NSMutableArray *viewsToRemove = [NSMutableArray array]; for (UIView *subview in self.bannerContainer.subviews) { [viewsToRemove addObject:subview]; NSLog(@"🔄 标记移除 banner: %@", NSStringFromClass([subview class])); } for (UIView *view in viewsToRemove) { [view removeFromSuperview]; } // 清理 topContainer 中的所有子视图(可能包含 CP 相关的 banner) viewsToRemove = [NSMutableArray array]; for (UIView *subview in self.topContainer.subviews) { if ([subview isKindOfClass:[CPBindingAnimation class]] || [subview isKindOfClass:[CPLevelUpAnimation class]]) { [viewsToRemove addObject:subview]; NSLog(@"�� 标记移除 CP banner: %@", NSStringFromClass([subview class])); } } for (UIView *view in viewsToRemove) { [view removeFromSuperview]; } NSLog(@"🔄 Banner 视图清理完成"); } -(void)resumeTimer{ if(self.giftEffectTimer != nil){ dispatch_source_cancel(self.giftEffectTimer); self.giftEffectTimer = nil; } [self.pagGiftEffectView removeListener:self]; self.hostDelegate = nil; [[GiftComboManager sharedManager] removeComboFlag]; } - (instancetype)initWithHostDelegate:(id)hDelegate { if (self = [super init]) { self.hostDelegate = hDelegate; [[NIMSDK sharedSDK].broadcastManager addDelegate:self]; // 确保RoomAnimationView可以接收用户交互 self.userInteractionEnabled = YES; [self setup]; } return self; } #pragma mark - SETUP - (void)setup { [self setupUI]; [self setupEnterRoom]; [self setupGiftEffects]; [self setupBanner]; [self setupCar]; [self setupOldMethods]; [self addBnnerContainGesture]; } - (void)setupUI { [self addSubview:self.bottomContainer]; [self insertSubview:self.middleContainer aboveSubview:self.bottomContainer]; [self insertSubview:self.topContainer aboveSubview:self.middleContainer]; [self insertSubview:self.bannerContainer aboveSubview:self.topContainer]; [self.bottomContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; [self.middleContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; [self.topContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self); }]; [self.bannerContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(self).offset(kNavigationHeight); make.leading.trailing.mas_equalTo(self); make.height.mas_equalTo(80); }]; } - (void)setupEnterRoom { self.enterRoomAnimationQueue = [NSMutableArray array]; } - (void)setupGiftEffects { self.GiftDynamicEffectListPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) xpSafeObjectAtIndex:0] stringByAppendingPathComponent:@"GiftDynamicEffectList"]; self.giftAnimationManager = [[GiftAnimationManager alloc] initWithContainerView:self.bottomContainer]; self.giftAnimationManager.delegate = self; _giftEffectsQueue = dispatch_queue_create("com.RoomGiftEffects.queue", DISPATCH_QUEUE_SERIAL); } - (void)setupBanner { _roomBannertModelsQueueV2 = [NSMutableArray array]; // 初始化 Banner 调度器 self.bannerScheduler = [[BannerScheduler alloc] initWithDelegate:self]; } #pragma mark - Legacy Support - (void)setupCar { _carEffectQueue = [NSMutableArray array]; } - (void)setupOldMethods { _broadCastHieght = 190 - 150 + kGetScaleWidth(69); _sailingQueue = @[].mutableCopy; _datingEffectQueue = @[].mutableCopy; _graffitiGiftQueue = @[].mutableCopy; _acrossRoomPKQueue = @[].mutableCopy; _animationListB = @[].mutableCopy; } #pragma mark - Method: Enter Roooooom - (void)enterRoom:(NIMMessage *)message content:(NIMChatroomNotificationContent *)content { NIMMessageChatroomExtension *messageExt = (NIMMessageChatroomExtension *)message.messageExt; NSDictionary * dic = [(NSDictionary *)messageExt.roomExt.toJSONObject objectForKey:message.from]; XPMessageRemoteExtModel *extModel = [XPMessageRemoteExtModel modelWithJSON:dic]; if (extModel.platformRole == 1) { return; } if (extModel.enterHide) { //隐身进房 if ([message.from isEqualToString:[AccountInfoStorage instance].getUid]) { [self createEnterHideAnimation]; } } else { [self userEnterRoom:content ext:extModel]; } } ///自己的隐身进房提示 - (void)createEnterHideAnimation { NSDictionary * dic= @{@"title":@"隐身进房", @"isMe":@"", @"experLevelSeq":@"", @"effectPath" : @""}; [self.enterRoomAnimationQueue addObject:dic]; [self tryDequeueAnimation]; } - (void)userEnterRoom:(NIMChatroomNotificationContent *)content ext:(XPMessageRemoteExtModel *)extModel { NSString *userName = [self _resolveUserNameFromContent:content extModel:extModel]; NSString *title = [NSString stringWithFormat:YMLocalizedString(@"XPRoomAnimationView0"), userName]; NSDictionary * dic= @{@"title":title, @"experLevelSeq":[NSString stringWithFormat:@"%ld", extModel.experLevelSeq], @"effectPath" : extModel.enterRoomEffects.length ? extModel.enterRoomEffects : @""}; [self.enterRoomAnimationQueue addObject:dic]; [self tryDequeueAnimation]; } - (void)tryDequeueAnimation { // 如果当前正有动画在播放,或者队列里啥都没有,直接return if (self.isEnterRoomAnimating || self.enterRoomAnimationQueue.count == 0) { return; } self.isEnterRoomAnimating = YES; // 取出队列头部的动画信息并移除 NSDictionary *dic = [self.enterRoomAnimationQueue firstObject]; if (self.enterRoomAnimationQueue.count > 0) { [self.enterRoomAnimationQueue xpSafeRemoveObjectAtIndex:0]; } NSString *title = [dic objectForKey:@"title"]; if ([title isEqualToString:@"隐身进房"]) { [self playHideEnterRoomAnimation]; } else { // 开始执行动画 [self playUserEnterRoomAnimation:[dic objectForKey:@"title"] experLevelSeq:[dic objectForKey:@"experLevelSeq"] effectPath:[dic objectForKey:@"effectPath"] isCPEnter:[[dic objectForKey:@"isCP"] boolValue]]; } } - (void)playHideEnterRoomAnimation { XPRoomEnterHideTipView *enterHideTipView = [[XPRoomEnterHideTipView alloc] initWithFrame:CGRectMake(KScreenWidth, (KScreenHeight - 48) * 0.5, KScreenWidth, 48)]; [self.bottomContainer addSubview:enterHideTipView]; @kWeakify(self); [self enterRoomAnimationFor:enterHideTipView startFrom:enterHideTipView.center startTo:CGPointMake(enterHideTipView.frame.size.width / 2, enterHideTipView.center.y) endFrom:CGPointMake(0, enterHideTipView.center.y) endTo:CGPointMake(-KScreenWidth / 2, enterHideTipView.center.y) duration:CACurrentMediaTime() + kTipViewStayDuration completion:^(BOOL finished) { @kStrongify(self); [self endAnimationEnterEffect]; }]; } - (void)playUserEnterRoomAnimation:(NSString *)title experLevelSeq:(NSString *)experLevelSeq effectPath:(NSString *)effectPath isCPEnter:(BOOL)isCP { NSDictionary *attributes = @{ NSFontAttributeName:kFontRegular(26), NSForegroundColorAttributeName:[UIColor whiteColor] }; NSMutableAttributedString *attributeString = [[NSMutableAttributedString alloc] initWithString:title attributes:attributes]; [attributeString setYy_alignment:NSTextAlignmentLeft]; BOOL hasEffectPath = ![NSString isEmpty:effectPath]; [self addEnterEffectView: hasEffectPath]; if (isCP) { [self playCPPPPEnterRoomSvgaWith:attributeString]; } else if (hasEffectPath) { [self playEnterRoomSvga:effectPath content:attributeString needMoveAnimation:YES]; } else { [self playEnterRoomSvgaWith:experLevelSeq content:attributeString]; } } - (void)addEnterEffectView:(BOOL)hasEffectPath { if (self.enterEffectView.superview == nil) { [self.bottomContainer addSubview:self.enterEffectView]; } self.enterEffectView.loops = hasEffectPath ? 0 : 1; self.enterEffectView.frame = CGRectMake(hasEffectPath ? KScreenWidth : 0, 340 + kSafeAreaTopHeight, KScreenWidth, hasEffectPath ? 75 : 50); } - (void)playEnterRoomSvga:(NSString *)path content:(NSAttributedString *)content needMoveAnimation:(BOOL)isNeed { @kWeakify(self); SVGAParser *parser = [[SVGAParser alloc] init]; [parser parseWithURL:[NSURL URLWithString:path] completionBlock:^(SVGAVideoEntity * _Nullable videoItem) { @kStrongify(self); self.enterEffectView.alpha = 1; self.enterEffectView.hidden = NO; self.enterEffectView.videoItem = videoItem; [self.enterEffectView setAttributedText:content forKey:@"room_text"]; [self.enterEffectView startAnimation]; if (isNeed) { [self popAnimationEnterEffect]; } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); [self endAnimationEnterEffect]; }]; } - (void)playEnterRoomSvgaWith:(NSString *)seq content:(NSAttributedString *)content { NSInteger seqNum = [NSString isEmpty:seq] ? 0 : [seq integerValue]; NSString *path = [self _mapExperLevelSeqToLocalKey:seqNum]; if ([NSString isEmpty:path]) { [self endAnimationEnterEffect]; return; } NSString *targetPath = [NSString stringWithFormat:@"%@/%@.svga", @"https://image.molistar.xyz", path]; [self playEnterRoomSvga:targetPath content:content needMoveAnimation:NO]; } - (void)playCPPPPEnterRoomSvgaWith:(NSAttributedString *)content { if ([NSString isEmpty:content.string]) { [self endAnimationEnterEffect]; return; } NSString *path = @"CP进场"; @kWeakify(self); SVGAParser *parser = [[SVGAParser alloc] init]; [parser parseWithNamed:path inBundle:[NSBundle mainBundle] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { @kStrongify(self); self.enterEffectView.loops = 0; self.enterEffectView.alpha = 1; self.enterEffectView.hidden = NO; self.enterEffectView.videoItem = videoItem; [self.enterEffectView setAttributedText:content forKey:@"room_text"]; [self.enterEffectView startAnimation]; [self popAnimationEnterEffect]; } failureBlock:^(NSError * _Nonnull error) { @kStrongify(self); [self endAnimationEnterEffect]; }]; } - (void)popAnimationEnterEffect { @kWeakify(self); [self enterRoomAnimationFor:self.enterEffectView startFrom:self.enterEffectView.center startTo:CGPointMake(self.enterEffectView.frame.size.width / 2, self.enterEffectView.center.y) endFrom:CGPointMake(0, self.enterEffectView.center.y) endTo:CGPointMake(-KScreenWidth / 2, self.enterEffectView.center.y) duration:CACurrentMediaTime() + kTipViewStayDuration completion:^(BOOL finished) { @kStrongify(self); [self endAnimationEnterEffect]; }]; } - (void)endAnimationEnterEffect { self.isEnterRoomAnimating = NO; [self.enterEffectView stopAnimation]; self.enterEffectView.hidden = YES; [self.enterEffectView removeFromSuperview]; [self tryDequeueAnimation]; } #pragma mark - Method: Banner - (void)inserBannerModelToQueue:(AttachmentModel *)obj { // 参数验证 if (!obj || ![obj isKindOfClass:[AttachmentModel class]]) { NSLog(@"⚠️ 警告: inserBannerModelToQueue 接收到无效参数: %@", obj); return; } #if 0 // DEBUG环境:复制banner数据用于测试 BOOL enableBannerCopy = obj.second == Custom_Message_Sub_Super_Gift_Banner;// YES; // 设置为NO可以禁用复制功能 NSInteger copyCount = 10; // 可以调整这个数字来改变复制数量 if (enableBannerCopy) { NSLog(@"🧪 DEBUG环境:收到banner数据,复制%ld份用于测试", (long)copyCount); NSLog(@"🧪 原始banner类型: %@", NSStringFromClass([obj class])); // 添加原始数据到新调度器 [self.bannerScheduler enqueueBanner:obj]; // 复制指定份数 for (int i = 1; i < copyCount; i++) { AttachmentModel *copiedObj = [[AttachmentModel alloc] init]; // 安全复制data if ([obj.data respondsToSelector:@selector(mutableCopy)]) { copiedObj.data = [obj.data mutableCopy]; } else if ([obj.data respondsToSelector:@selector(copy)]) { copiedObj.data = [obj.data copy]; } else { copiedObj.data = obj.data; // 直接引用,注意这可能不是深拷贝 } copiedObj.first = obj.first; copiedObj.second = obj.second; copiedObj.isBroadcast = obj.isBroadcast; copiedObj.seq = obj.seq; NSLog(@"🧪 复制第%d份banner数据", i + 1); [self.bannerScheduler enqueueBanner:copiedObj]; } NSLog(@"🧪 队列中banner总数: %ld", (long)self.bannerScheduler.queueCount); // 显示队列中banner类型的分布 NSLog(@"🧪 队列状态: %@", [self.bannerScheduler queueStatusDescription]); } else { // 禁用复制功能,正常添加 NSLog(@"🧪 DEBUG环境:复制功能已禁用,正常添加banner"); [self.bannerScheduler enqueueBanner:obj]; } #else // 生产环境:正常添加 [self.bannerScheduler enqueueBanner:obj]; #endif } - (void)sortBannerQueue { // 保持先进先出(FIFO)策略,不需要排序 // 队列顺序就是添加顺序,确保公平性 NSLog(@"🔄 RoomAnimationView: 使用先进先出策略,保持队列原有顺序"); } - (void)processNextRoomEffectAttachment { NSLog(@"🔄 processNextRoomEffectAttachment 被调用 - 当前状态: isRoomBannerV2Displaying=%@, 队列数量: %ld", self.isRoomBannerV2Displaying ? @"YES" : @"NO", (long)self.roomBannertModelsQueueV2.count); // 检查队列是否有元素 if (self.roomBannertModelsQueueV2.count == 0) { // 如果队列为空,停止处理 self.isRoomBannerV2Displaying = NO; NSLog(@"🔄 队列为空,停止处理"); return; } if (self.isRoomBannerV2Displaying) { NSLog(@"🔄 已有banner正在显示,跳过处理"); return; } [self sortBannerQueue]; // 从队列中取出第一个元素并移出队列 AttachmentModel *nextAttachment = [self.roomBannertModelsQueueV2 firstObject]; if (self.roomBannertModelsQueueV2.count > 0) { [self.roomBannertModelsQueueV2 xpSafeRemoveObjectAtIndex:0]; } // 设置为正在显示的状态 self.isRoomBannerV2Displaying = YES; NSLog(@"🔄 设置状态为正在显示: %@", NSStringFromClass([nextAttachment class])); // 清理之前的banner,防止多个banner同时显示(保留debug视图) NSMutableArray *viewsToRemove = [NSMutableArray array]; for (UIView *subview in self.bannerContainer.subviews) { [viewsToRemove addObject:subview]; NSLog(@"🔄 标记移除banner: %@", NSStringFromClass([subview class])); } for (UIView *view in viewsToRemove) { [view removeFromSuperview]; } switch (nextAttachment.second) { case Custom_Message_Sub_General_Floating_Screen_One_Room: case Custom_Message_Sub_General_Floating_Screen_All_Room: // 通用飘屏 [self playGameBanner:nextAttachment]; break; case Custom_Message_Sub_Super_Gift_Winning_Coins_ALL_Room: [self playLuckyWinningBanner:nextAttachment]; break; case Custom_Message_Sub_CP_Gift: [self playCPGiftBanner:nextAttachment]; break; case Custom_Message_Sub_CP_Upgrade: [self playCPLevelUp:nextAttachment]; break; case Custom_Message_Sub_CP_Binding: [self playCPBinding:nextAttachment]; break; case Custom_Message_Sub_Gift_ChannelNotify: [self playRoomGiftBanner:nextAttachment]; break; case Custom_Message_Sub_LuckyPackage: [self playLuckyPackageBanner:nextAttachment]; break; case Custom_Message_Sub_Super_Gift_Banner: [self playBroveBanner:nextAttachment]; break; default: break; } } - (void)playBroveBanner:(AttachmentModel *)obj { if (!obj.data) { self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; return; } self.isRoomBannerV2Displaying = YES; @kWeakify(self); RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; // �� 新增:检查是否正在销毁 if (!self || !self.superview) { NSLog(@"⚠️ RoomAnimationView 正在销毁,跳过 banner 播放"); return; } [BravoGiftBannerView display:self.bannerContainer inRoomUid:roomInfo.uid with:obj complete:^{ @kStrongify(self); if (!self || !self.superview) { NSLog(@"⚠️ RoomAnimationView 已被释放,跳过 complete 回调"); return; } NSLog(@"🔄 BravoGiftBannerView complete 回调被调用"); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; } exitCurrentRoom:^{ @kStrongify(self); if (!self || !self.superview) { NSLog(@"⚠️ RoomAnimationView 已被释放,跳过 exitCurrentRoom 回调"); return; } [self.hostDelegate exitRoom]; }]; } - (void)playLuckyPackageBanner:(AttachmentModel *)obj { if (!obj.data) { self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; return; } self.isRoomBannerV2Displaying = YES; @kWeakify(self); RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; [LuckyPackageBannerView display:self.bannerContainer inRoomUid:roomInfo.uid with:obj complete:^{ @kStrongify(self); NSLog(@"🔄 LuckyPackageBannerView complete 回调被调用"); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; } exitCurrentRoom:^{ @kStrongify(self); [self.hostDelegate exitRoom]; }]; } - (void)receiveRoomGiftBanner:(AttachmentModel *)obj { [self inserBannerModelToQueue:obj]; } - (void)playRoomGiftBanner:(AttachmentModel *)obj { if (!obj.data) { self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; return; } self.isRoomBannerV2Displaying = YES; @kWeakify(self); [RoomHighValueGiftBannerAnimation display:self.bannerContainer with:obj complete:^{ @kStrongify(self); NSLog(@"🔄 RoomHighValueGiftBannerAnimation complete 回调被调用"); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; }]; } - (void)receiveCPEvent:(AttachmentModel *)attachment { [self inserBannerModelToQueue:attachment]; } - (void)playCPBinding:(AttachmentModel *)attachment { @kWeakify(self); [CPBindingAnimation display:self.topContainer with:attachment complete:^{ @kStrongify(self); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; }]; } - (void)playCPGiftBanner:(AttachmentModel *)attachMent { @kWeakify(self); [CPGiftBanner display:self.bannerContainer with:attachMent complete:^{ @kStrongify(self); NSLog(@"🔄 CPGiftBanner complete 回调被调用"); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; }]; } - (void)playCPLevelUp:(AttachmentModel *)attachMent { @kWeakify(self); [CPLevelUpAnimation display:self.topContainer with:attachMent complete:^{ @kStrongify(self); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; }]; } - (void)receiveLuckGiftWinning:(AttachmentModel *)attachment { RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; [LuckyGiftWinningFlagView display:self.topContainer with:attachment roomID:roomInfo.roomId uID:[AccountInfoStorage instance].getUid]; } - (void)receiveBravoGiftWinning:(AttachmentModel *)attachment { RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; BravoGiftWinningFlagViewModel *flagModel = [BravoGiftWinningFlagView display:self.topContainer with:attachment roomID:roomInfo.roomId uID:[AccountInfoStorage instance].getUid]; if (flagModel.roomId != roomInfo.roomId || flagModel.uid != [AccountInfoStorage instance].getUid.integerValue) { return; } if ([flagModel.receiverUidList containsObject:@([AccountInfoStorage instance].getUid.integerValue)]) { // 数据同步到 combo [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveLuckGiftWinning" object:[NSString stringByRemovingRedundantZeros:@(flagModel.receiverProfit).stringValue]]; } if (!self.hostDelegate.getRoomInfo.hasAnimationEffect) { return; } NSDictionary *tip = [attachment.data objectForKey:@"tip"]; if (tip) { NSNumber *level = [tip objectForKey:@"level"]; NSNumber *coins = [tip objectForKey:@"coins"]; if (level) { [self loadBroveSVGAVideoItem:level.integerValue]; } if (coins) { [[NSNotificationCenter defaultCenter] postNotificationName:@"receiveLuckGiftWinning" object:[NSString stringByRemovingRedundantZeros:coins.stringValue]]; } } } - (void)loadBroveSVGAVideoItem:(NSInteger)level { if (level < 3) { return; } NSString *svgaItemName = @""; SVGAVideoEntity *entity; svgaItemName = @"大"; entity = self.broveSVGAEntity_lv_3; /* switch (level) { case 1: svgaItemName = @"小"; entity = self.broveSVGAEntity_lv_1; break; case 2: svgaItemName = @"中"; entity = self.broveSVGAEntity_lv_2; break; case 3: svgaItemName = @"大"; entity = self.broveSVGAEntity_lv_3; break; default: break; } */ if ([NSString isEmpty:svgaItemName]) { return; } if (self.broveSVGAQueue.count >=3) { return; } SVGAImageView *svgaView = [[SVGAImageView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)]; svgaView.backgroundColor = [UIColor clearColor]; svgaView.contentMode = UIViewContentModeScaleAspectFill; svgaView.userInteractionEnabled = NO; if (!entity) { @kWeakify(self); SVGAParser *parser = [[SVGAParser alloc] init]; [parser parseWithNamed:svgaItemName inBundle:[NSBundle mainBundle] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { @kStrongify(self); self.broveSVGAEntity_lv_3 = videoItem; /* switch (level) { case 1: self.broveSVGAEntity_lv_1 = videoItem; break; case 2: self.broveSVGAEntity_lv_2 = videoItem; break; case 3: self.broveSVGAEntity_lv_3 = videoItem; break; default: break; } */ [self startPlayBroveSVGA:svgaView level:level]; } failureBlock:^(NSError * _Nonnull error) { }]; } else { [self startPlayBroveSVGA:svgaView level:level]; } } - (void)startPlayBroveSVGA:(SVGAImageView *)svgaView level:(NSInteger)level { if (!_broveSVGAQueue) { _broveSVGAQueue = [[NSMutableArray alloc] init]; self.viewIndex = 0; } if (![self.broveSVGAQueue containsObject:svgaView]) { [self.broveSVGAQueue addObject:svgaView]; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((0.3 + (self.viewIndex * 0.1)) * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ SVGAVideoEntity *entity; switch (level) { case 1: entity = self.broveSVGAEntity_lv_1; break; case 2: entity = self.broveSVGAEntity_lv_2; break; case 3: entity = self.broveSVGAEntity_lv_3; break; default: break; } // [self insertSubview:svgaView atIndex:self.viewIndex]; [self _addSubviewToMiddleContainer:svgaView]; svgaView.tag = 913; svgaView.delegate = self; svgaView.videoItem = entity; svgaView.loops = 1; [svgaView startAnimation]; self.viewIndex += 1; }); } - (void)receiveLuckGiftBanner:(AttachmentModel *)attachment { [self inserBannerModelToQueue:attachment]; } - (void)playLuckyWinningBanner:(AttachmentModel *)nextAttachment { RoomInfoModel *roomInfo = self.hostDelegate.getRoomInfo; @kWeakify(self); [LuckyGiftWinningBannerView display:self.bannerContainer inRoomUid:roomInfo.uid with:nextAttachment complete:^{ @kStrongify(self); NSLog(@"🔄 LuckyGiftWinningBannerView complete 回调被调用"); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; } exitCurrentRoom:^{ @kStrongify(self); [self.hostDelegate exitRoom]; }]; } -(void)receiveRoomGeneralFloatingScreen:(AttachmentModel *)attachment{ PIBaseAnimationViewModel *roomGraffiti = [PIBaseAnimationViewModel new]; roomGraffiti.data = attachment.data; roomGraffiti.type = GiftBannerType_General_Floating_Screen; roomGraffiti.first = attachment.first; roomGraffiti.second = attachment.second; PIUniversalBannerModel *model = [PIUniversalBannerModel modelWithDictionary:attachment.data]; if (model.skipType == 7 && (attachment.second == Custom_Message_Sub_General_Floating_Screen_One_Room || attachment.second == Custom_Message_Sub_General_Floating_Screen_All_Room)) { [self receiveGameBanner:attachment]; } else { if(self.animationListB.count == 0 && self.isPlayOfB == NO){ [self createGeneralFloatingScreenAnimation:roomGraffiti bannerModel:model]; } [self.animationListB addObject:roomGraffiti]; } } - (void)receiveGameBanner:(AttachmentModel *)attachment { [self inserBannerModelToQueue:attachment]; } - (void)playGameBanner:(AttachmentModel *)attachment { @kWeakify(self); [GameUniversalBannerView display:self.bannerContainer with:attachment complete:^{ @kStrongify(self); NSLog(@"🔄 GameUniversalBannerView complete 回调被调用"); self.isRoomBannerV2Displaying = NO; [self.bannerScheduler markBannerFinished]; } goToGame:^(NSInteger gameID) { @kStrongify(self); NSArray *baishunList = [self.hostDelegate getPlayList]; for (ActivityInfoModel *model in baishunList) { if (model.id == gameID) { MSRoomGameWebVC *vc = [[MSRoomGameWebVC alloc]initWithDelegate:self.hostDelegate gameModel:model]; vc.view.frame = CGRectMake(0, 0, KScreenWidth, KScreenHeight); XPRoomViewController *roomVC = (XPRoomViewController *)self.hostDelegate; [roomVC addChildViewController:vc]; RoomAnimationView *animationView; for (id obj in self.hostDelegate.getSuperView.subviews) { if ([obj isKindOfClass:[RoomAnimationView class]]){ animationView = obj; break; } } [self.hostDelegate.getSuperView addSubview:vc.view]; vc.view.tag = 913; break; } } }]; } #pragma mark - Method: Car - (void)handleCarEnter:(AttachmentModel *)attachment { if (attachment.second == Custom_Message_Sub_Car_EnterRoom) { if (self.isPlayingGiftEffect) { return; } if ([self _isInSudGame]) { return; } if (!self.hostDelegate.getRoomInfo.hasAnimationEffect) { return; } if (self.hostDelegate.getUserInfo.platformRole == 1) { return; } NSInteger otherViewType = [attachment.data[@"otherViewType"] integerValue]; NSString *viewUrl = attachment.data[@"viewUrl"]; NSString *effect = attachment.data[@"effect"]; NSDictionary *dic = nil; if (![NSString isEmpty:viewUrl]) { dic = @{@"otherViewType":@(otherViewType), @"viewUrl":viewUrl}; } else if(![NSString isEmpty:effect]) { dic = @{@"effect":effect}; } [self starHandleCarEffect:dic]; } } - (void)starHandleCarEffect:(NSDictionary *)dic { if (!dic) { return; } [self.carEffectQueue addObject:dic]; [self playCarEffect:dic]; } - (void)nextCarEffect { if (self.carEffectQueue.count > 0) { if (self.carEffectQueue.count > 0) { [self.carEffectQueue xpSafeRemoveObjectAtIndex:0]; } } if (self.carEffectQueue.count > 0) { [self playCarEffect:self.carEffectQueue.firstObject]; } } - (void)playCarEffect:(NSDictionary *)effectDict { NSString *viewUrl = [effectDict objectForKey:@"viewUrl"]; NSString *carEffect = [effectDict objectForKey:@"effect"]; @kWeakify(self); if (![NSString isEmpty:viewUrl]) { [self.vapParser parseWithURL:viewUrl completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if (![NSString isEmpty:videoUrl]) { if([videoUrl.lowercaseString containsString:@".pag"]){ self.carPagEffectView.hidden = NO; [self _addSubviewToMiddleContainer:self.carPagEffectView]; [self.carPagEffectView setPath:videoUrl]; [self.carPagEffectView play]; } else { self.carVapEffectView.hidden = NO; [self _addSubviewToMiddleContainer:self.carVapEffectView]; [self.carVapEffectView setMute:NO]; [self.carVapEffectView playHWDMP4:videoUrl repeatCount:1 delegate:self]; } } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); [self nextCarEffect]; }]; } else if (![NSString isEmpty:carEffect]) { [self.vggParser parseWithURL:[NSURL URLWithString:carEffect] completionBlock:^(SVGAVideoEntity * _Nullable videoItem) { @kStrongify(self); if (videoItem) { [self _addSubviewToMiddleContainer:self.carSVGAEffectView]; [self _updateGiftEffectContentMode:self.carSVGAEffectView size:videoItem.videoSize]; self.carSVGAEffectView.videoItem = videoItem; [self.carSVGAEffectView startAnimation]; } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); [self nextCarEffect]; }]; } } #pragma mark - Method: Send Gifts - (void)receiveGiftHandleSendGiftAnimationWith:(GiftReceiveInfoModel *)receiveInfo attachment:(AttachmentModel *)attachment { if (!self.hostDelegate.getRoomInfo.hasAnimationEffect) { return; } GiftInfoModel *giftInfo = receiveInfo.gift; if (attachment.second == Custom_Message_Sub_AllMicroLuckySend || attachment.second == Custom_Message_Sub_AllBatchMicroLuckySend || attachment.second == Custom_Message_Sub_Gift_LuckySend) { NSString * giftId = [NSString stringWithFormat:@"%ld", receiveInfo.luckyGiftList.giftList.firstObject.giftId]; NSString *roomUid = receiveInfo.roomUid == 0 ? receiveInfo.uid : @(receiveInfo.roomUid).stringValue; giftInfo = [[XPGiftStorage shareStorage] findGiftInfo:giftId inRoom:roomUid]; } if (giftInfo == nil) { giftInfo = [[XPGiftStorage shareStorage] findGiftInfo:receiveInfo.giftId inRoom:@(receiveInfo.roomUid).stringValue]; } if (giftInfo == nil) { giftInfo = [receiveInfo.displayGift xpSafeObjectAtIndex:0]; } receiveInfo.gift = giftInfo; RoomInfoModel * roomInfo = [self.hostDelegate getRoomInfo]; if ((giftInfo.otherViewType == GiftOtherViewTypeMp4 || giftInfo.otherViewType == GiftOtherViewTypePag) && giftInfo.viewUrl.length > 0 && roomInfo.hasAnimationEffect) { receiveInfo.mp4Url = giftInfo.viewUrl; } else if (giftInfo.hasVggPic && giftInfo.vggUrl.length > 0 && roomInfo.hasAnimationEffect) {///SVGA动画 receiveInfo.vggUrl = giftInfo.vggUrl; } if (attachment.second != Custom_Message_Sub_Gift_EmbeddedStyle) { // Custom_Message_Sub_Gift_EmbeddedStyle 只需播放特效 [self.giftAnimationManager enqueueGift:receiveInfo]; } } - (void)receiveGift:(GiftReceiveInfoModel *)receiveInfo { if (receiveInfo.isHomeShow) { return; } // 处理 combo [[GiftComboManager sharedManager] receiveGiftInfoForDisplayComboFlags:receiveInfo container:self]; if (receiveInfo.isLuckyBagGift) { if (receiveInfo.isShowAnimation) { [self handleLuckyBagGifts:receiveInfo]; } } else { if (![NSString isEmpty:receiveInfo.mp4Url] || ![NSString isEmpty:receiveInfo.vggUrl]) { [self addToSvgaQueue:receiveInfo]; } } dispatch_async(self.giftEffectsQueue, ^{ if (self.giftEffectTimer == nil && self.svgaQueue.count > 0) { [self startHandleGiftEffectTimer]; } }); } - (void)handleLuckyBagGifts:(GiftReceiveInfoModel *)receiveInfo { //如果是福袋礼物的话 就不用看他的价值了 只要有SVGA的话 就播放 // MARK: 此代码从 XPRoomAnimationView copy 而来,暂时无法确认其正确性 RoomInfoModel * roomInfo = [self.hostDelegate getRoomInfo]; if (receiveInfo.displayGift.count > 0) { [receiveInfo.displayGift enumerateObjectsUsingBlock:^(GiftInfoModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ((obj.otherViewType == GiftOtherViewTypeMp4 || obj.otherViewType == GiftOtherViewTypePag) && obj.viewUrl && obj.viewUrl.length > 0 && roomInfo.hasAnimationEffect) { GiftReceiveInfoModel * model = [[GiftReceiveInfoModel alloc] init]; model.mp4Url = obj.viewUrl; model.avatar = receiveInfo.sendUserAvatar; model.gift = obj; model.giftNum = [obj.giftNum integerValue];; model.nick = receiveInfo.nick; model.targetUids = receiveInfo.targetUids; model.isBatch = receiveInfo.isBatch; model.targetAvatar = receiveInfo.targetAvatar; model.targetNick = receiveInfo.targetNick; [self addToSvgaQueue:model]; } else if (obj.hasVggPic && roomInfo.hasAnimationEffect) { GiftReceiveInfoModel * model = [[GiftReceiveInfoModel alloc] init]; model.mp4Url = obj.viewUrl; model.avatar = receiveInfo.sendUserAvatar; model.gift = obj; model.giftNum = [obj.giftNum integerValue];; model.nick = receiveInfo.nick; model.targetUids = receiveInfo.targetUids; model.isBatch = receiveInfo.isBatch; model.targetAvatar = receiveInfo.targetAvatar; model.targetNick = receiveInfo.targetNick; [self addToSvgaQueue:model]; } }]; } } - (void)startHandleGiftEffectTimer { NSTimeInterval period = 2.0; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行 @kWeakify(self); dispatch_source_set_event_handler(timer, ^{ @kStrongify(self) if (!self) { return; } dispatch_async(self.giftEffectsQueue, ^{ NSLog(@"******************* 当前队列个数: %@,image loader 个数: %@", @(self.svgaQueue.count), @(self.mp4AvatarLoaders.count)); if (self.svgaQueue.count == 0) { dispatch_source_cancel(timer); dispatch_async(dispatch_get_main_queue(), ^{ self.giftEffectTimer = nil; }); } else { if (self.isPlayingGiftEffect == NO) { self.isPlayingGiftEffect = YES; GiftReceiveInfoModel *receiveModel = [self.svgaQueue xpSafeObjectAtIndex:0]; if ([receiveModel isKindOfClass:[GiftReceiveInfoModel class]]) { dispatch_async(dispatch_get_main_queue(), ^{ [self createGiftSvgaAnimation:receiveModel]; }); }else { [self removeFromSvgaQueueAtIndex:0]; self.isPlayingGiftEffect = NO; } } } }); }); self.giftEffectTimer = timer; dispatch_resume(timer); } -(void)createGiftSvgaAnimation:(GiftReceiveInfoModel *)receiveInfo { GiftInfoModel *giftInfo = receiveInfo.gift; NSString *targetURL = @""; if (receiveInfo.isLuckyBagGift) { // 播放背包礼物动画 if (![NSString isEmpty:receiveInfo.mp4Url]) { [self stopCarEffect:giftInfo.goldPrice]; targetURL = receiveInfo.mp4Url; [self playLuckyGiftEffectWithVapUrl:receiveInfo.mp4Url]; } else if (![NSString isEmpty:receiveInfo.luckyGiftSvgaUrl]) { [self stopCarEffect:giftInfo.goldPrice]; targetURL = receiveInfo.luckyGiftSvgaUrl; [self playGiftEffectWithVapUrl:receiveInfo.luckyGiftSvgaUrl]; } else if (![NSString isEmpty:giftInfo.luckyGiftSvgaUrl]) { [self stopCarEffect:giftInfo.goldPrice]; targetURL = giftInfo.luckyGiftSvgaUrl; [self playGiftEffectWithVapUrl:giftInfo.luckyGiftSvgaUrl]; } } else { [self stopCarEffect:giftInfo.goldPrice]; if (giftInfo.otherViewType == GiftOtherViewTypePag || giftInfo.otherViewType == GiftOtherViewTypeMp4) { if (giftInfo.otherViewType == GiftOtherViewTypePag) { [self playGiftEffectWithPagUrl:giftInfo.viewUrl]; targetURL = giftInfo.viewUrl; } else { self.mp4TempReceiveInfoModel = receiveInfo; [self playGiftEffectWithVapUrl:giftInfo.viewUrl]; targetURL = giftInfo.viewUrl; } } // 兜底,同时判断是否可以执行 svga if ([NSString isEmpty:targetURL] && giftInfo.hasVggPic && giftInfo.vggUrl.length > 0) { [self playGiftEffectWithVggUrl:giftInfo.vggUrl]; targetURL = giftInfo.vggUrl; } } // NSLog(@"******************* 执行播放, 是否MP4:%@,URL: %@, self.svgaQueue num: %@", @(giftInfo.otherViewType), targetURL, @(self.svgaQueue.count)); [[NSNotificationCenter defaultCenter] postNotificationName:@"kExchangeRoomAnimationViewAndGameViewIndex" object:nil]; @kWeakify(self); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ @kStrongify(self); [self removeFromSvgaQueueAtIndex:0]; }); } - (void)stopCarEffect:(double)goldPrice { if ([ClientConfig shareConfig].configInfo.hideCarEffectGiftPrice <= goldPrice) { [self.carEffectQueue removeAllObjects]; [self.carSVGAEffectView stopAnimation]; [self.carVapEffectView stopHWDMP4]; [self.carPagEffectView stop]; } } - (void)playGiftEffectWithPagUrl:(NSString *)pagUrl { @kWeakify(self); [self.vapParser parseWithURL:pagUrl completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if ([NSString isEmpty:videoUrl]) { [self playPAGFailure]; } else { [self _addSubviewToMiddleContainer:self.pagGiftEffectView]; [self.pagGiftEffectView setPath:videoUrl]; [self.pagGiftEffectView play]; } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); [self playPAGFailure]; }]; } - (void)playGiftEffectWithVapUrl:(NSString *)vapUrl { NSString *encodingUrl = [vapUrl pureURLString]; NSString *fullPath = [self _findFullPath:vapUrl]; self.mp4AvatarLoaders = @[].mutableCopy; if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]){ [self playVAG:fullPath]; }else { @kWeakify(self); [self.vapParser parseWithURL:encodingUrl completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if ([NSString isEmpty:videoUrl]) { [self playVAGFailure]; } else { [self playVAG:videoUrl]; } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); [self playVAGFailure]; }]; } } - (void)playGiftEffectWithVggUrl:(NSString *)vggUrl { NSString *encodingUrl = [vggUrl pureURLString]; NSString *fullPath = [self _findFullPath:vggUrl]; // NSLog(@"******************* 解释 SVGA : %@ | %@", encodingUrl, fullPath); if (![NSString isEmpty:fullPath] && [[NSFileManager defaultManager] fileExistsAtPath:fullPath]) { NSData *data = [NSData dataWithContentsOfFile:fullPath options:0 error:NULL]; NSString *fileName = [[encodingUrl componentsSeparatedByString:@"/"] lastObject]; @kWeakify(self); [self.vggParser parseWithData:data cacheKey:fileName completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { @kStrongify(self); if (videoItem) { [self playVGG:videoItem]; } else { [self playVGGFailure]; } } failureBlock:^(NSError * _Nonnull error) { @kStrongify(self); [self playVGGFailure]; }]; } else { @kWeakify(self); [self.vggParser parseWithURL:[NSURL URLWithString:encodingUrl] completionBlock:^(SVGAVideoEntity * _Nullable videoItem) { @kStrongify(self); // NSLog(@"******************* 解释 SVGA 成功"); if (videoItem) { [self playVGG:videoItem]; } else { [self playVGGFailure]; } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); // NSLog(@"******************* 解释 SVGA 失败:%@", error); [self playVGGFailure]; }]; } } - (void)playVGG:(SVGAVideoEntity *)videoItem { [self.vggGiftEffectView stopAnimation]; [self _addSubviewToMiddleContainer:self.vggGiftEffectView]; [self _updateGiftEffectContentMode:self.vggGiftEffectView size:videoItem.videoSize]; self.vggGiftEffectView.videoItem = videoItem; [self.vggGiftEffectView startAnimation]; } - (void)playVGGFailure { [self playVGGEnd]; } - (void)playVGGEnd { self.isPlayingGiftEffect = NO; self.vggGiftEffectView.hidden = YES; } - (void)playVAG:(NSString *)url { @kWeakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @kStrongify(self); [self _addSubviewToMiddleContainer:self.vapGiftEffectView]; [self.vapGiftEffectView setMute:NO]; [self.vapGiftEffectView playHWDMP4:url repeatCount:1 delegate:self]; }); } - (void)playVAGFailure { [self playVAGEnd]; } - (void)playVAGEnd { self.isPlayingGiftEffect = NO; self.vapGiftEffectView.hidden = YES; [self.vapGiftEffectView setMute:YES]; [self.vapGiftEffectView stopHWDMP4]; [self.vapGiftEffectView removeFromSuperview]; self.vapGiftEffectView = nil; } - (void)playPAGFailure { self.isPlayingGiftEffect = NO; self.pagGiftEffectView.hidden = YES; } #pragma mark - Delegates #pragma mark - SVGA delegate - (void)svgaPlayerDidFinishedAnimation:(SVGAPlayer *)player { if (player.tag == 913) { [player clear]; player.delegate = nil; [player removeFromSuperview]; self.viewIndex -= 1; if ([self.broveSVGAQueue containsObject:player]) { [self.broveSVGAQueue removeObject:player]; } return; } if (player == self.enterEffectView && player.loops == 1) { [self endAnimationEnterEffect]; } else if (player == self.vggGiftEffectView) { [self playVGGEnd]; } else if (player == self.carSVGAEffectView) { self.carSVGAEffectView.hidden = YES; [self.carSVGAEffectView removeFromSuperview]; [self nextCarEffect]; } } #pragma mark - HWDMP4PlayDelegate - (BOOL)shouldStartPlayMP4:(VAPView *)container config:(QGVAPConfigModel *)config { CGFloat width = config.info.size.width; CGFloat height = config.info.size.height; container.center = self.center; [container mas_updateConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(KScreenWidth); make.height.mas_equalTo(KScreenWidth * height / width); }]; return YES; } - (void)viewDidFinishPlayMP4:(NSInteger)totalFrameCount view:(VAPView *)container { dispatch_async(dispatch_get_main_queue(), ^{ container.hidden = YES; if (container == self.carVapEffectView) { [self.carVapEffectView removeFromSuperview]; self.carVapEffectView = nil; [self nextCarEffect]; } else if (container == self.vapGiftEffectView) { [self playVAGEnd]; } else if(container == self.luckyVapGiftEffectView) { [self.luckyVapGiftEffectView removeFromSuperview]; self.luckyVapGiftEffectView = nil; } }); } - (void)viewDidStopPlayMP4:(NSInteger)lastFrameIndex view:(VAPView *)container { dispatch_async(dispatch_get_main_queue(), ^{ container.hidden = YES; if (container == self.carVapEffectView) { [self.carVapEffectView removeFromSuperview]; [self nextCarEffect]; } else if (container == self.vapGiftEffectView) { [self playVAGEnd]; } else if(container == self.luckyVapGiftEffectView) { [self.luckyVapGiftEffectView removeFromSuperview]; self.luckyVapGiftEffectView = nil; } }); } - (void)viewDidFailPlayMP4:(NSError *)error{ [self playVAGFailure]; } - (void)loadVapImageWithURL:(NSString *)urlStr context:(NSDictionary *)context completion:(VAPImageCompletionBlock)completionBlock { if (self.mp4TempReceiveInfoModel) { dispatch_async(dispatch_get_main_queue(), ^{ QGVAPSourceInfo *info = (QGVAPSourceInfo *)context[@"resource"]; // NSLog(@" MP4 的 key - info.contentTag : %@", info.contentTag); [self _loadMP4AvatarURL:urlStr info:info completion:completionBlock]; }); } } - (void)_loadMP4AvatarURL:(NSString *)urlStr info:(QGVAPSourceInfo *)info completion:(VAPImageCompletionBlock)completionBlock { NSString *path = @""; switch (self.mp4TempReceiveInfoModel.gift.showAvatarType) { case 1: // 只使用 avatar case 3: path = self.mp4TempReceiveInfoModel.avatar; if ([NSString isEmpty:path]) { path = self.mp4TempReceiveInfoModel.sendUserAvatar; } break; case 2: // 只使用 target avatar path = self.mp4TempReceiveInfoModel.targetAvatar; if ([NSString isEmpty:path]) { path = self.mp4TempReceiveInfoModel.recvUserAvatar; } break; default: break; } if ([NSString isEmpty:path]) { completionBlock(kImage(@"common_avatar"), nil, @"dafeult image"); } else { NetImageView *avatar_Loader = [[NetImageView alloc] init]; [avatar_Loader loadImageWithUrl:path completion:^(UIImage * _Nullable image, NSURL * _Nonnull url) { completionBlock(image, nil, urlStr); }]; [self.mp4AvatarLoaders addObject:avatar_Loader]; } } #pragma mark - PAGViewListener - (void)onAnimationEnd:(PAGView*)pagView{ dispatch_async(dispatch_get_main_queue(), ^{ pagView.hidden = YES; if (pagView == self.pagGiftEffectView) { self.isPlayingGiftEffect = NO; } else if(pagView == self.carPagEffectView){ [self.carPagEffectView removeFromSuperview]; self.carPagEffectView = nil; [self nextCarEffect]; } }); } - (void)onAnimationCancel:(PAGView*)pagView{ dispatch_async(dispatch_get_main_queue(), ^{ pagView.hidden = YES; if (pagView == self.pagGiftEffectView) { self.isPlayingGiftEffect = NO; } }); } #pragma mark - RoomHostDelegate #pragma mark - RoomGuestDelegate: NIMCustomMessage - (void)handleNIMCustomMessage:(NIMMessage *)message { if (![message.messageObject isKindOfClass:[NIMCustomObject class]]) { return; } NIMCustomObject *obj = (NIMCustomObject *)message.messageObject; if (obj.attachment == nil || ![obj.attachment isKindOfClass:[AttachmentModel class]]) { return; } AttachmentModel *attachment = (AttachmentModel *)obj.attachment; { // 自定义消息处理入口排查日志:first/second、payload size、线程 NSData *payloadJSON = nil; @try { payloadJSON = [NSJSONSerialization dataWithJSONObject:attachment.data ?: @{} options:0 error:nil]; } @catch (__unused NSException *e) {} NSLog(@"[Combo effect][Anim] 🎞 handleNIMCustomMessage | first=%ld second=%ld | payload=%lub | main=%@ | ts=%.3f", (long)attachment.first, (long)attachment.second, (unsigned long)payloadJSON.length, [NSThread isMainThread] ? @"YES" : @"NO", [[NSDate date] timeIntervalSince1970]); } switch (attachment.first) { case CustomMessageType_User_Enter_Room: [self _handleEnterRoomMessage:message]; break; case CustomMessageType_RedPacket: [self _handleLuckyPackageMessage:attachment]; break; case CustomMessageType_Gift: [self _handleGiftMessage:attachment]; break; case CustomMessageType_AllMicroSend: [self _handleGiftMessage:attachment]; break; case CustomMessageType_Car_Notify: [self handleCarEnter:attachment]; break; case CustomMessageType_CP: [self receiveCPEvent:attachment]; break; case CustomMessageType_Super_Gift: [self _handleSuperGift:attachment]; break; case CustomMessageType_General_Floating_Screen: [self _handleCommonBanner:attachment]; break; case CustomMessageType_LuckyBag: [self handleLuckyBag:attachment]; break; case CustomMessageType_Look_Love: [self handle98:attachment]; break; case CustomMessageType_RoomPlay_Dating: [self handleDating:attachment]; break; case CustomMessageType_Across_Room_PK: [self handleAcrossRoomPK:attachment]; break; case CustomMessageType_Anchor_FansTeam: [self handleAnchorFansTeam:attachment]; break; case CustomMessageType_Anchor_Hour_Rank: [self handleAnchorHourRank:attachment]; break; case CustomMessageType_License_Hour_Rank: [self handleLicenseHourRank:attachment]; break; case CustomMessageType_Gift_Compound: [self handle93:attachment]; break; case CustomMessageType_Room_Sailing: [self handle81:attachment]; break; case CustomMessageType_Graffiti_Gift: { [self receiveRoomGraffitiGift:attachment]; } break; case CustomMessageType_Graffiti_Star_Kitchen: [self handle104:attachment]; break; case CustomMessageType_Treasure_Fairy: [self handleFairy:attachment]; break; case CustomMessageType_Tarot: [self handleTarot:attachment]; break; case CustomMessageType_Common_H5: [self handleCommonH5:attachment]; break; default: break; } } - (void)_handleEnterRoomMessage:(NIMMessage *)message { NIMCustomObject *obj = (NIMCustomObject *)message.messageObject; if (obj.attachment == nil || ![obj.attachment isKindOfClass:[AttachmentModel class]]) { return; } AttachmentModel *attachment = (AttachmentModel *)obj.attachment; switch (attachment.second) { case Custom_Message_Sub_User_Enter_Room: { RoomEnterModel *model = [RoomEnterModel modelWithJSON:attachment.data]; if (model.platformRole == 1) { return; } if (model.enterHide) { //隐身进房 if (model.uid == [AccountInfoStorage instance].getUid.integerValue) { [self createEnterHideAnimation]; } } else { NSString *userName = model.nick; if (model.cpList.count > 0 && model.screenType < 3) { NSString *title = [NSString stringWithFormat:YMLocalizedString(@"XPRoomAnimationView0"), userName]; NSDictionary * dic= @{@"title":title, @"experLevelSeq":[NSString stringWithFormat:@"%ld", model.experLevelSeq], @"effectPath" : @"", @"isCP":@(YES) }; [self.enterRoomAnimationQueue addObject:dic]; } else { NSString *title = [NSString stringWithFormat:YMLocalizedString(@"XPRoomAnimationView0"), userName]; NSDictionary * dic= @{@"title":title, @"experLevelSeq":[NSString stringWithFormat:@"%ld", model.experLevelSeq], @"effectPath" : model.enterRoomEffects.length ? model.enterRoomEffects : @""}; [self.enterRoomAnimationQueue addObject:dic]; } [self tryDequeueAnimation]; } } break; default: break; } } - (void)_handleLuckyPackageMessage:(AttachmentModel *)attachment { if ([self _isInSudGame]) { return; } switch (attachment.second) { case Custom_Message_Sub_LuckyPackage: [self receiveLuckGiftBanner:attachment]; break; default: return; break; } } - (void)_handleGiftMessage:(AttachmentModel *)attachment { if ([self _isInSudGame]) { return; } NSLog(@"[Combo effect] 📨 收到礼物消息 - second: %ld", (long)attachment.second); GiftReceiveInfoModel * receiveInfo = [GiftReceiveInfoModel modelWithJSON:attachment.data]; [receiveInfo giftDataAlignment]; // 检查并修复连击计数 if (receiveInfo.comboCount < 1) { NSLog(@"[Combo effect] 🚨 收到云信消息中连击计数异常 - comboCount: %ld", (long)receiveInfo.comboCount); // 尝试从 attachment.data 中获取正确的连击计数 NSNumber *dataComboCount = attachment.data[@"comboCount"]; if (dataComboCount && [dataComboCount integerValue] >= 1) { NSLog(@"[Combo effect] 🔧 从 attachment.data 修复连击计数 - 修复为: %@", dataComboCount); receiveInfo.comboCount = [dataComboCount integerValue]; } else { NSLog(@"[Combo effect] 🔧 设置默认连击计数为 1"); receiveInfo.comboCount = 1; } } NSLog(@"[Combo effect] 📨 礼物消息解析完成 - giftId: %ld, combo: %ld, uid: %@", (long)receiveInfo.gift.giftId, (long)receiveInfo.comboCount, receiveInfo.uid); receiveInfo.isLuckyBagGift = (attachment.second == Custom_Message_Sub_Gift_LuckySend || attachment.second == Custom_Message_Sub_AllMicroLuckySend || attachment.second == Custom_Message_Sub_AllBatchMicroLuckySend); receiveInfo.isBatch = (attachment.second == Custom_Message_Sub_AllBatchSend || attachment.second == Custom_Message_Sub_AllBatchMicroLuckySend); receiveInfo.isComboBatch = (attachment.second == Custom_Message_Sub_AllMicroSend); switch (attachment.second) { case Custom_Message_Sub_Gift_Send: [self _handleGift:receiveInfo attachment:attachment]; break; case Custom_Message_Sub_AllMicroSend: case Custom_Message_Sub_AllBatchSend: [self _handleGift:receiveInfo attachment:attachment]; break; case Custom_Message_Sub_Gift_ChannelNotify: //处理原广播通用飘屏 banner 逻辑 [self receiveRoomGiftBanner:attachment]; return; break; case Custom_Message_Sub_Gift_EmbeddedStyle: [self _handleGiftEmbeddedStyle:receiveInfo attachment:attachment]; break; case 607: [self receiveLuckGiftBanner:attachment]; break; case Custom_Message_Sub_Gift_LuckySend: case Custom_Message_Sub_AllMicroLuckySend: case Custom_Message_Sub_AllBatchMicroLuckySend: { [self _handleGift:receiveInfo attachment:attachment]; // if (receiveInfo.mp4Url.length > 0) { // [self playLuckyGiftEffectWithVapUrl:receiveInfo.mp4Url]; // } else { // NSString * svgaString = receiveInfo.luckyGiftSvgaUrl.length > 0 ? receiveInfo.luckyGiftSvgaUrl : receiveInfo.gift.luckyGiftSvgaUrl; // NSURL * luckyGiftSvgaUrl = [NSURL URLWithString:svgaString]; // if (luckyGiftSvgaUrl.absoluteString.length > 0) { // [self play:luckyGiftSvgaUrl]; // } // } // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // [self receiveGift:receiveInfo]; // }); } break; default: break; } if (receiveInfo.isLuckyBagGift) { NSLog(@"[Combo effect] 🎁 处理福袋礼物"); [self receiveGiftHandleSendGiftAnimationWith:receiveInfo attachment:attachment]; [self _handleBagGift:receiveInfo]; } else { if (receiveInfo.gift.notifyFull == 1 && attachment.second == Custom_Message_Sub_Gift_Send) { // 全服礼物,但由自己发送,不在此逻辑播放 NSLog(@"[Combo effect] 🎁 全服礼物由自己发送,跳过播放"); return; } // 处理从位置 发送者 到 接受者 的礼物移动动画 NSLog(@"[Combo effect] 🎁 处理普通礼物动画"); [self receiveGiftHandleSendGiftAnimationWith:receiveInfo attachment:attachment]; // 播放礼物动画(svga/mp4)(如果有的话) [self receiveGift:receiveInfo]; } } - (void)_handleBagGift:(GiftReceiveInfoModel *)receiveInfo { if (!self.hostDelegate.getRoomInfo.hasAnimationEffect) { return; } [self receiveGift:receiveInfo]; } - (void)_handleGift:(GiftReceiveInfoModel *)receiveInfo attachment:(AttachmentModel *)attachment { // 确认接受者有 uid if (receiveInfo.targetUsers.count == 0) { GiftReceiveUserInfoModel *model = [[GiftReceiveUserInfoModel alloc] init]; model.nick = receiveInfo.targetNick; model.avatar = receiveInfo.targetAvatar; model.uid = receiveInfo.uid.integerValue; receiveInfo.targetUsers = @[model]; } // 确认接受者有 uid if (receiveInfo.targetUsers.count > 0 && receiveInfo.targetUids.count <= 0) { receiveInfo.targetUids = [receiveInfo.targetUsers valueForKeyPath:@"uid"]; } } - (void)_handleGiftEmbeddedStyle:(GiftReceiveInfoModel *)receiveInfo attachment:(AttachmentModel *)attachment { // 数据对齐 receiveInfo.targetUid = [[attachment.data objectForKey:@"recvUserUid"] stringValue]; receiveInfo.targetNick = [attachment.data objectForKey:@"recvUserNick"]; receiveInfo.targetAvatar = [attachment.data objectForKey:@"recvUserAvatar"]; receiveInfo.avatar = [attachment.data objectForKey:@"sendUserAvatar"]; receiveInfo.nick = [attachment.data objectForKey:@"sendUserNick"]; receiveInfo.uid = [[attachment.data objectForKey:@"sendUserUid"] stringValue]; // 确认接受者有 uid if (receiveInfo.targetUsers.count > 0 && receiveInfo.targetUids.count <= 0) { receiveInfo.targetUids = [receiveInfo.targetUsers valueForKeyPath:@"uid"]; } } - (void)_handleSuperGift:(AttachmentModel *)attachment { switch (attachment.second) { case Custom_Message_Sub_Super_Gift_Winning_Coins: [self receiveLuckGiftWinning:attachment]; break; case Custom_Message_Sub_Super_Gift_Winning_Coins_ALL_Room: [self receiveLuckGiftBanner:attachment]; break; case Custom_Message_Sub_Super_Gift_Banner: [self handleBroveGiftBanner:attachment]; break; case Custom_Message_Sub_Super_Gift_UI_Rffect: [self receiveBravoGiftWinning:attachment]; default: break; } } - (void)_handleCommonBanner:(AttachmentModel *)attachment { switch (attachment.second) { case Custom_Message_Sub_General_Floating_Screen_One_Room: case Custom_Message_Sub_General_Floating_Screen_All_Room: [self receiveRoomGeneralFloatingScreen:attachment]; break; default: break; } } - (NSString *)_findFullPath:(NSString *)url { NSString *encodingUrl = [url pureURLString]; NSString *fileName = [[encodingUrl componentsSeparatedByString:@"/"] lastObject]; NSString *fullPath = [self.GiftDynamicEffectListPath stringByAppendingPathComponent:fileName]; return fullPath; } - (void)_updateGiftEffectContentMode:(UIView *)view size:(CGSize)size { CGFloat width = size.width; CGFloat height = size.height; if (width > height) { view.contentMode = UIViewContentModeScaleAspectFit; } else {//高大于宽 CGFloat resizeH = KScreenWidth * height / width;//按照屏幕的宽度去缩放,获得高度 if (resizeH > KScreenHeight) {//如果大于屏幕高度,填充 view.contentMode = UIViewContentModeScaleAspectFill; } else {//小于屏幕高度, view.contentMode = UIViewContentModeScaleAspectFit; } } } - (void)handleCommonH5:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { default: break; } } - (void)handleTarot:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { default: break; } } - (void)handleFairy:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Treasure_Fairy_Draw_Gift_L4: case Custom_Message_Sub_Treasure_Fairy_Draw_Gift_L5: case Custom_Message_Sub_Treasure_Fairy_Convert_L1: case Custom_Message_Sub_Treasure_Fairy_Convert_L2: case Custom_Message_Sub_Treasure_Fairy_Convert_L3: [self receiveTreasureFairyGiftHighLevel:attachment]; break; default: break; } } - (void)handle98:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Look_Love_InRoom_NeedAllMicSend: [self receiveCandyTreeGiftHighLevle:attachment]; break; default: break; } } - (void)handle104:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { default: break; } } - (void)handleLuckyBag:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Room_Gift_LuckBag_FullScree: case Custom_Message_Sub_Room_Gift_LuckBag_Server: [self receiveLuckyGiftBigPrize:attachment]; break; default: break; } } - (void)handleVIPLevelUp:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Room_Noble_LevelUp_Suspend: [self receiveNobleLevelUp:attachment]; break; default: break; } } - (void)handleDating:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Room_Play_Dating_Public_Result: [self roomDatingPublicResult:attachment]; break; default: break; } } - (void)handleAcrossRoomPK:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_AcrossRoomPK_Result: [self acrossRoomPKBannerAnimation:attachment]; break; default: break; } } - (void)handleAnchorFansTeam:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_FansTeam_Join_Success: { GiftReceiveInfoModel *receiveInfo = [[GiftReceiveInfoModel alloc] init]; GiftInfoModel *giftInfo = [GiftInfoModel modelWithJSON:attachment.data[@"giftVo"]]; receiveInfo.gift = giftInfo; receiveInfo.giftInfo = giftInfo; [self receiveFansTeamGiftHandleSendGiftAnimation:attachment]; [self receiveGift:receiveInfo]; } break; default: break; } } - (void)handleAnchorHourRank:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Anchor_Hour_Rank: [self receiveAnchorHourRank:attachment]; break; default: break; } } - (void)handleLicenseHourRank:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_License_Hour_Rank: [self receiveAnchorHourRank:attachment]; break; default: break; } } - (void)handle93:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Gift_Compound: [self receiveGiftCompound:attachment]; break; default: break; } } - (void)handle81:(AttachmentModel *)attachment { if (!attachment) { return; } switch (attachment.second) { case Custom_Message_Sub_Sailing_AllRoom_Notify: case Custom_Message_Sub_Sailing_InRoom_NeedAllMicSend: [self receiveRoomSailing:attachment]; break; default: break; } } - (void)handleBroveGiftBanner:(AttachmentModel *)attachment { [self inserBannerModelToQueue:attachment]; } #pragma mark - RoomGuestDelegate: NIMNotificationMessageDelegate - (void)handleNIMNotificationMessage:(NIMMessage *)message { return; // NIMNotificationObject *notiMsg = (NIMNotificationObject *)message.messageObject; // NIMChatroomNotificationContent *content = (NIMChatroomNotificationContent *)notiMsg.content; // if (!content) { // return; // } // // switch (content.eventType) { // case NIMChatroomEventTypeEnter: // [self enterRoom:message content:content]; // break; // // default: // break; // } } #pragma mark - NIMBroadcastManagerDelegate - (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.hostDelegate.getUserInfo.partitionId]){ return; } switch (attachment.first) { case CustomMessageType_General_Floating_Screen: [self _handleCommonBanner:attachment]; break; case CustomMessageType_Noble_VIP: [self handleVIPLevelUp:attachment]; break; case CustomMessageType_LuckyBag: [self handleLuckyBag:attachment]; break; case CustomMessageType_Graffiti_Star_Kitchen: [self handle104:attachment]; break; case CustomMessageType_Look_Love: [self handle98:attachment]; break; case CustomMessageType_Treasure_Fairy: [self handleFairy:attachment]; break; case CustomMessageType_Tarot: [self handleTarot:attachment]; break; case CustomMessageType_Common_H5: [self handleCommonH5:attachment]; break; default: break; } } } #pragma mark - GiftAnimationManagerDelegate - (CGPoint)animationPointAtStageViewByUid:(NSString *)uid { return [self.hostDelegate animationPointAtStageViewByUid:uid]; } #pragma mark - Gesture - (void)addBnnerContainGesture { [self insertSubview:self.bannerSwipeGestureContainer aboveSubview:self.bannerContainer]; [self insertSubview:self.bannerLeftTapGestureContainer aboveSubview:self.bannerContainer]; [self insertSubview:self.bannerRightTapGestureContainer aboveSubview:self.bannerContainer]; // 设置容器 tag 用于手势优化器识别 self.bannerSwipeGestureContainer.tag = 1001; // 中央容器 self.bannerLeftTapGestureContainer.tag = 1002; // 左侧容器 self.bannerRightTapGestureContainer.tag = 1003; // 右侧容器 [self.bannerSwipeGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.center.mas_equalTo(self.bannerContainer); make.top.bottom.mas_equalTo(self.bannerContainer); make.width.mas_equalTo(self.bannerContainer.mas_width).multipliedBy(2.0/3.0); }]; [self.bannerLeftTapGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.top.leading.bottom.mas_equalTo(self.bannerContainer); make.trailing.mas_equalTo(self.bannerSwipeGestureContainer.mas_leading); }]; [self.bannerRightTapGestureContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.top.trailing.bottom.mas_equalTo(self.bannerContainer); make.leading.mas_equalTo(self.bannerSwipeGestureContainer.mas_trailing); }]; // 创建中央区域的 swipe 手势(2/3 宽度) UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe)]; if (isMSRTL()) { swipe.direction = UISwipeGestureRecognizerDirectionRight; } else { swipe.direction = UISwipeGestureRecognizerDirectionLeft; } swipe.delegate = self; // 创建中央区域的 tap 手势(2/3 宽度) UITapGestureRecognizer *centerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBannerTap:)]; centerTap.delegate = self; // 创建左侧区域的 tap 手势(1/6 宽度) UITapGestureRecognizer *leftTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBannerTap:)]; leftTap.delegate = self; // 创建右侧区域的 tap 手势(1/6 宽度) UITapGestureRecognizer *rightTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBannerTap:)]; rightTap.delegate = self; // 只有当 swipe 判定失败后,tap 才允许成功 [centerTap requireGestureRecognizerToFail:swipe]; // 添加手势识别器 [self.bannerSwipeGestureContainer addGestureRecognizer:swipe]; [self.bannerSwipeGestureContainer addGestureRecognizer:centerTap]; [self.bannerLeftTapGestureContainer addGestureRecognizer:leftTap]; [self.bannerRightTapGestureContainer addGestureRecognizer:rightTap]; NSLog(@"🎯 手势识别器设置完成 - 中央容器: %@, 左侧容器: %@, 右侧容器: %@", NSStringFromClass([self.bannerSwipeGestureContainer class]), NSStringFromClass([self.bannerLeftTapGestureContainer class]), NSStringFromClass([self.bannerRightTapGestureContainer class])); } - (void)handleSwipe { // 只对当前显示的 banner 发送 swipe 通知 // 通过检查当前 bannerContainer 中是否有可见的 banner 来决定是否发送通知 UIView *currentVisibleBanner = nil; for (UIView *subview in self.bannerContainer.subviews) { if (!subview.hidden && subview.alpha > 0.01) { currentVisibleBanner = subview; break; } } if (currentVisibleBanner) { NSLog(@"🎯 检测到 swipe 手势,当前有可见 banner: %@,发送 SwipeOutBanner 通知", NSStringFromClass([currentVisibleBanner class])); // 传递当前 banner 信息,让监听者知道是哪个 banner 被 swipe [[NSNotificationCenter defaultCenter] postNotificationName:@"SwipeOutBanner" object:currentVisibleBanner]; } else { NSLog(@"🎯 检测到 swipe 手势,但当前没有可见 banner,忽略此手势"); } } - (void)handleBannerTap:(UITapGestureRecognizer *)tapGesture { CGPoint tapPoint = [tapGesture locationInView:self.bannerContainer]; // 检查当前显示的 banner 是否在 tap 位置可以响应事件 if ([self isPointInBannerInteractiveArea:tapPoint]) { // banner 可以响应,不处理,让 banner 继续原有逻辑 NSLog(@"🎯 Banner tap 位置在可交互区域,banner 将处理此事件"); [[NSNotificationCenter defaultCenter] postNotificationName:@"TapBanner" object:nil userInfo:@{@"point": [NSValue valueWithCGPoint:tapPoint]}]; } else { // banner 不能响应,保存 tap 位置 self.savedTapPoint = tapPoint; self.hasSavedTapPoint = YES; NSLog(@"💾 Banner tap 位置不在可交互区域,已保存位置: %@", NSStringFromCGPoint(tapPoint)); // 将 bannerContainer 中的点转换为屏幕坐标系 CGPoint screenPoint = [self.bannerContainer convertPoint:tapPoint toView:nil]; // UIView *tappedView = tapGesture.view; [[NSNotificationCenter defaultCenter] postNotificationName:@"BannerTapToFunctionContainer" object:nil userInfo:@{@"point": [NSValue valueWithCGPoint:screenPoint]}]; } } - (BOOL)isPointInBannerInteractiveArea:(CGPoint)point { // 使用缓存管理器进行快速检查 TouchAreaCacheManager *cacheManager = [TouchAreaCacheManager sharedManager]; // 首先尝试使用缓存 if ([cacheManager isPointInInteractiveArea:point]) { NSLog(@"🎯 isPointInBannerInteractiveArea: 缓存命中 - 点 %@ 在可交互区域内", NSStringFromCGPoint(point)); return YES; } // 缓存未命中,使用传统方法检查 NSLog(@"🎯 isPointInBannerInteractiveArea: 缓存未命中,使用传统方法检查"); for (UIView *subview in self.bannerContainer.subviews) { if (subview.hidden || subview.alpha <= 0.01) { continue; } // 检查点是否在子视图范围内 if (CGRectContainsPoint(subview.bounds, point)) { // 检查子视图是否支持用户交互 if (subview.userInteractionEnabled) { // 进一步检查子视图是否有可点击的元素 CGPoint subviewPoint = [subview convertPoint:point fromView:self.bannerContainer]; UIView *hitView = [subview hitTest:subviewPoint withEvent:nil]; if (hitView && hitView.userInteractionEnabled) { // 更新缓存 CGRect bannerBounds = subview.bounds; CGRect interactiveBounds = [self calculateInteractiveBoundsForView:subview]; [cacheManager updateBannerBounds:bannerBounds interactiveBounds:interactiveBounds forBanner:subview]; NSLog(@"🎯 isPointInBannerInteractiveArea: 传统方法命中,已更新缓存"); return YES; } } } } NSLog(@"🎯 isPointInBannerInteractiveArea: 点 %@ 不在可交互区域内", NSStringFromCGPoint(point)); return NO; } #pragma mark - UIGestureRecognizerDelegate - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { // 允许手势同时识别 return YES; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { CGPoint touchPoint = [touch locationInView:self.bannerContainer]; CGFloat containerWidth = self.bannerContainer.bounds.size.width; // 使用手势优化器进行优化 GestureOptimizer *optimizer = [GestureOptimizer sharedOptimizer]; BOOL shouldReceive = [optimizer shouldGestureRecognizer:gestureRecognizer receiveTouchAtPoint:touchPoint containerWidth:containerWidth]; // 调试信息 if (shouldReceive) { [optimizer debugGestureAreaForPoint:touchPoint containerWidth:containerWidth]; } return shouldReceive; } #pragma mark - Animations - (void)enterRoomAnimationFor:(UIView *)targetView startFrom:(CGPoint)sfPoint startTo:(CGPoint)stPoint endFrom:(CGPoint)efPoint endTo:(CGPoint)etPoint duration:(CFTimeInterval)duration completion:(void (^)(BOOL finished))completion { @kWeakify(self); [self enterRoomMoveInAnimationFor:targetView fromValue:sfPoint toValue:stPoint completion:^(BOOL finished) { if (finished) { @kStrongify(self); @kWeakify(self); [self enterRoomMoveOutAnimationFor:targetView fromValue:efPoint toValue:etPoint beginTime:duration completion:^(BOOL finished) { if (finished) { @kStrongify(self); [self cleanupAnimationsForView:targetView]; if (completion) { completion(finished); } } }]; } }]; } - (void)enterRoomMoveInAnimationFor:(UIView *)view fromValue:(CGPoint)fromValue toValue:(CGPoint)toValue completion:(void (^)(BOOL finished))completion { POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:fromValue]; springAnimation.toValue = [NSValue valueWithCGPoint:toValue]; [springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { if (completion) completion(finished); }]; [view pop_addAnimation:springAnimation forKey:@"amationSpringIn"]; } - (void)enterRoomMoveOutAnimationFor:(UIView *)view fromValue:(CGPoint)fromValue toValue:(CGPoint)toValue beginTime:(CFTimeInterval)beginTime completion:(void (^)(BOOL finished))completion { POPSpringAnimation *moveAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:fromValue]; moveAnimation.toValue = [NSValue valueWithCGPoint:toValue]; moveAnimation.beginTime = beginTime; moveAnimation.springSpeed = 8; moveAnimation.springBounciness = 15.f; [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { if (completion) completion(finished); }]; [view pop_addAnimation:moveAnimation forKey:@"animetionMoveOut"]; } - (void)cleanupAnimationsForView:(UIView *)view { [view pop_removeAllAnimations]; [view removeFromSuperview]; } #pragma mark - Common methods - (NSString *)_resolveUserNameFromContent:(NIMChatroomNotificationContent *)content extModel:(XPMessageRemoteExtModel *)extModel { // 1) extModel.nick 优先 if (![NSString isEmpty:extModel.nick]) { return extModel.nick; } // 2) 如果 extModel.nick 为空,看 content.source.nick if (![NSString isEmpty:content.source.nick]) { return content.source.nick; } // 3) 如果依然空,并且是自己,则取本地 nick if (content.source.userId.intValue == self.hostDelegate.getUserInfo.uid && ![NSString isEmpty:self.hostDelegate.getUserInfo.nick]) { return self.hostDelegate.getUserInfo.nick; } // 4) 兜底 return @""; } - (NSString *)_mapExperLevelSeqToLocalKey:(NSInteger)seq { if (seq >= 30 && seq <= 39) { return @"experience_entre_effect_30"; } else if (seq >= 40 && seq <= 49) { return @"experience_entre_effect_40"; } else if (seq >= 50 && seq <= 59) { return @"experience_entre_effect_50"; } else if (seq >= 60 && seq <= 69) { return @"experience_entre_effect_60"; } else if (seq >= 70 && seq <= 79) { return @"experience_entre_effect_70"; } else if (seq >= 80 && seq <= 89) { return @"experience_entre_effect_80"; } else if (seq >= 90) { return @"experience_entre_effect_90"; } return @""; } - (BOOL)_isInSudGame { BOOL isGamePlaying = NO; if (self.hostDelegate.getRoomInfo.type == RoomType_MiniGame) { NSInteger micCount = self.hostDelegate.getRoomInfo.mgMicNum; for (int i = -1; i 0) { [self.animationListB xpSafeRemoveObjectAtIndex:0]; } self.isPlayOfB = NO; [self playAnimationWithModel]; } }]; [luckyGiftEffectView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; }); [luckyGiftEffectView pop_addAnimation:springAnimation forKey:@"nobleLevelUpspingOutAnimation"]; } - (void)receiveCandyTreeGiftHighLevle:(AttachmentModel *)attatchment { if ([self _isInSudGame]) {return;} PIBaseAnimationViewModel *giftModel = [PIBaseAnimationViewModel new]; giftModel.data = attatchment.data; giftModel.second = attatchment.second; giftModel.first = attatchment.first; giftModel.type = GiftBannerType_Love; if (self.animationListB.count == 0 && self.isPlayOfB == NO) { [self createCandyTreeBannerAnimation:giftModel]; } [self.animationListB addObject:giftModel]; } - (void)createCandyTreeBannerAnimation:(PIBaseAnimationViewModel *)attatchment { CGFloat kscale = (CGFloat)60 / (CGFloat)375; self.isPlayOfB = YES; CGFloat top = (kNavigationHeight + 15); XPRoomCandyGiftView *candyTreeView = [[XPRoomCandyGiftView alloc] initWithFrame:CGRectMake(KScreenWidth, top, KScreenWidth , KScreenWidth * kscale)]; candyTreeView.isMaxLargeGift = attatchment.second == Custom_Message_Sub_Look_Love_InRoom_NeedAllMicSend; candyTreeView.candyInfo = attatchment.data; [self.middleContainer addSubview:candyTreeView]; POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:candyTreeView.center]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(candyTreeView.frame.size.width / 2, candyTreeView.center.y)]; @kWeakify(self); [springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { @kStrongify(self); if (finished) { POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, candyTreeView.center.y)]; moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, candyTreeView.center.y)]; moveAnimation.beginTime = CACurrentMediaTime() + 3; moveAnimation.duration = 0.5; moveAnimation.repeatCount = 1; moveAnimation.removedOnCompletion = YES; @kWeakify(self); [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { @kStrongify(self); if (finished) { [candyTreeView removeFromSuperview]; self.isPlayOfB = NO; if (self.animationListB.count > 0) { [self.animationListB xpSafeRemoveObjectAtIndex:0]; } [self playAnimationWithModel]; } }]; [candyTreeView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; } }]; [candyTreeView pop_addAnimation:springAnimation forKey:@"candyTreespingOutAnimation"]; } - (void)receiveNobleLevelUp:(AttachmentModel *)attachment { @kWeakify(self); [[NetImageView new]loadImageWithUrl:attachment.data[@"avatar"] completion:^(UIImage * _Nonnull image, NSURL * _Nonnull url) { @kStrongify(self); PIBaseAnimationViewModel *nobleModel = [PIBaseAnimationViewModel new]; nobleModel.data = attachment.data; nobleModel.type = GiftBannerType_Nobleman; if (self.animationListB.count == 0 && self.isPlayOfB == NO) { [self createNobleLevelUpBannerAnimation:nobleModel]; } [self.animationListB addObject:nobleModel]; }]; } - (void)createNobleLevelUpBannerAnimation:(PIBaseAnimationViewModel *)model { self.isPlayOfB = YES; CGFloat top = (kNavigationHeight + 15); XPRoomNobleLevelUpView *nobleLevelUpView = [[XPRoomNobleLevelUpView alloc] initWithFrame:CGRectMake(KScreenWidth, top, KScreenWidth, 90)]; nobleLevelUpView.nobleInfo = model.data; [self.topContainer addSubview:nobleLevelUpView]; @kWeakify(self); @kWeakify(nobleLevelUpView); nobleLevelUpView.completionBlock = ^{ @kStrongify(self); @kStrongify(nobleLevelUpView); POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:nobleLevelUpView.center]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(nobleLevelUpView.frame.size.width / 2, nobleLevelUpView.center.y)]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, nobleLevelUpView.center.y)]; moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, nobleLevelUpView.center.y)]; moveAnimation.beginTime = CACurrentMediaTime() + 3; moveAnimation.duration = 0.5; moveAnimation.repeatCount = 1; moveAnimation.removedOnCompletion = YES; @kWeakify(self); [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { @kStrongify(self); if (finished) { self.isPlayOfB = NO; [nobleLevelUpView removeFromSuperview]; if (self.animationListB.count > 0) { [self.animationListB xpSafeRemoveObjectAtIndex:0]; } [self playAnimationWithModel]; } }]; [nobleLevelUpView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; }); [nobleLevelUpView pop_addAnimation:springAnimation forKey:@"nobleLevelUpspingOutAnimation"]; }; } - (void)createAnchorHourRankAnimation:(PIBaseAnimationViewModel *)attachment { self.isPlayOfB = YES; CGFloat top = (kNavigationHeight + 15); XPRoomAnchorRankBannerView *anchorRankView = [[XPRoomAnchorRankBannerView alloc] initWithFrame:CGRectMake(KScreenWidth,top, KScreenWidth, kGetScaleWidth(55))]; anchorRankView.anchorRankInfo = [RoomHalfHourRankModel modelWithDictionary:attachment.data]; anchorRankView.delegate = self; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gotoTopRankAnchorRoom:)]; [anchorRankView addGestureRecognizer:tap]; [self.topContainer addSubview:anchorRankView]; POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:anchorRankView.center]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(anchorRankView.frame.size.width / 2, anchorRankView.center.y)]; [springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { if (finished) { POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, anchorRankView.center.y)]; moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, anchorRankView.center.y)]; moveAnimation.beginTime = CACurrentMediaTime() + 3; moveAnimation.duration = 0.5; moveAnimation.repeatCount = 1; moveAnimation.removedOnCompletion = YES; [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { [anchorRankView removeFromSuperview]; self.isPlayOfB = NO; if(self.animationListB.count > 0){ [self.animationListB xpSafeRemoveObjectAtIndex:0]; [self playAnimationWithModel]; } }]; [anchorRankView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; } }]; [anchorRankView pop_addAnimation:springAnimation forKey:@"nobleLevelUpspingOutAnimation"]; } - (void)gotoTopRankAnchorRoom:(UITapGestureRecognizer *)tap { XPRoomAnchorRankBannerView * view = (XPRoomAnchorRankBannerView *)tap.view; if (view.anchorRankInfo.uid.integerValue > 0) { [self.hostDelegate exitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:view.anchorRankInfo.uid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); } } - (void)receiveTreasureFairyGiftHighLevel:(AttachmentModel *)attatchment { if ([self _isInSudGame]) {return;} PIBaseAnimationViewModel *giftModel = [PIBaseAnimationViewModel new]; giftModel.data = attatchment.data; giftModel.second = attatchment.second; giftModel.first = attatchment.first; giftModel.type = GiftBannerType_Fairy; if (self.animationListB.count == 0 && self.isPlayOfB == NO) { [self createTreasureFairyBannerAnimation:giftModel]; } [self.animationListB addObject:giftModel]; } - (void)createTreasureFairyBannerAnimation:(PIBaseAnimationViewModel *)attatchment { self.isPlayOfB = YES; CGFloat kscale = (CGFloat)60 / (CGFloat)375; CGFloat top = (kNavigationHeight + 15); XPTreasureFairyGiftView *treasureView = [[XPTreasureFairyGiftView alloc] initWithFrame:CGRectMake(KScreenWidth, top, KScreenWidth , KScreenWidth * kscale)]; //最大礼物 if ((attatchment.second == Custom_Message_Sub_Treasure_Fairy_Draw_Gift_L5) || (attatchment.second == Custom_Message_Sub_Treasure_Fairy_Convert_L3)) { treasureView.isMaxLargeGift = YES; }else{ treasureView.isMaxLargeGift = NO; } if ((attatchment.second == Custom_Message_Sub_Treasure_Fairy_Draw_Gift_L4) || (attatchment.second == Custom_Message_Sub_Treasure_Fairy_Draw_Gift_L5)) { treasureView.isDrawGift = YES; //抽奖 }else{ treasureView.isDrawGift = NO; //召唤 } treasureView.treasureInfo = attatchment.data; [self.middleContainer addSubview:treasureView]; POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:treasureView.center]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(treasureView.frame.size.width / 2, treasureView.center.y)]; [springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { if (finished) { POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, treasureView.center.y)]; moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, treasureView.center.y)]; moveAnimation.beginTime = CACurrentMediaTime() + 3; moveAnimation.duration = 0.5; moveAnimation.repeatCount = 1; moveAnimation.removedOnCompletion = YES; @kWeakify(self); [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { @kStrongify(self); if (finished) { [treasureView removeFromSuperview]; self.isPlayOfB = NO; if (self.animationListB.count > 0) { [self.animationListB xpSafeRemoveObjectAtIndex:0]; } [self playAnimationWithModel]; } }]; [treasureView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; } }]; [treasureView pop_addAnimation:springAnimation forKey:@"candyTreespingOutAnimation"]; } - (void)createGiftCompoundBannerAnimation:(PIBaseAnimationViewModel *)attachment { self.isPlayOfB = YES; XPRoomGiftCompoundView *compoundGiftView = [[XPRoomGiftCompoundView alloc] initWithFrame:CGRectMake(KScreenWidth, kNavigationHeight + 15, KScreenWidth, 45)]; compoundGiftView.compoundGiftInfo = attachment.data; [self.middleContainer addSubview:compoundGiftView]; POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:compoundGiftView.center]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(compoundGiftView.frame.size.width / 2, compoundGiftView.center.y)]; [springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { if (finished) { POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, compoundGiftView.center.y)]; moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, compoundGiftView.center.y)]; moveAnimation.beginTime = CACurrentMediaTime() + 3; moveAnimation.duration = 0.5; moveAnimation.repeatCount = 1; moveAnimation.removedOnCompletion = YES; @kWeakify(self); [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { @kStrongify(self); if (finished) { [compoundGiftView removeFromSuperview]; self.isPlayOfB = NO; if(self.animationListB.count > 0){ [self.animationListB xpSafeRemoveObjectAtIndex:0]; } [self playAnimationWithModel]; } }]; [compoundGiftView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; } }]; [compoundGiftView pop_addAnimation:springAnimation forKey:@"compoundGiftSpingOutAnimation"]; } - (void)createGeneralFloatingScreenAnimation:(PIBaseAnimationViewModel *)attachment bannerModel:(PIUniversalBannerModel *)model { self.isPlayOfB = YES; CGFloat top = (kNavigationHeight + 15); if (!model) { model = [PIUniversalBannerModel modelWithDictionary:attachment.data]; } BOOL isSvga = [model.resourceType.uppercaseString isEqualToString:@"SVGA"]; __block PIUniversalBannerView *bannerView; @kWeakify(self); if (isSvga == YES) { SVGAParser *parser = [SVGAParser new]; [parser parseWithURL:[NSURL URLWithString:model.resourceContent] completionBlock:^(SVGAVideoEntity * _Nonnull videoItem) { @kStrongify(self); model.videoItem = videoItem; CGFloat height = kGetScaleWidth(60); if(videoItem.videoSize.width > 0){ height = KScreenWidth * videoItem.videoSize.height / videoItem.videoSize.width; } bannerView = [[PIUniversalBannerView alloc]initWithFrame:CGRectMake(KScreenWidth, top, KScreenWidth, height)]; [self showGeneralFloatingScreenView:bannerView model:model]; } failureBlock:^(NSError * _Nonnull error) { @kStrongify(self); [bannerView removeFromSuperview]; self.isPlayOfB = NO; if(self.animationListB.count > 0){ [self.animationListB xpSafeRemoveObjectAtIndex:0]; } [self playAnimationWithModel]; }]; }else{ if (!_imageLoader) { _imageLoader = [[NetImageView alloc] init]; } [self.imageLoader loadImageWithUrl:model.resourceContent completion:^(UIImage * _Nonnull image, NSURL * _Nonnull url) { @kStrongify(self); model.image = image; CGFloat width = image.size.width <= 0 ? kGetScaleWidth(60) : image.size.width; CGFloat height = image.size.height ; CGFloat getHeight = KScreenWidth * height / width; if (getHeight > 100) { getHeight = 100; } bannerView = [[PIUniversalBannerView alloc]initWithFrame:CGRectMake(KScreenWidth, top, KScreenWidth, getHeight) ]; [self showGeneralFloatingScreenView:bannerView model:model]; } fail:^(NSError * _Nonnull error) { @kStrongify(self); [bannerView removeFromSuperview]; self.isPlayOfB = NO; if(self.animationListB.count > 0){ [self.animationListB xpSafeRemoveObjectAtIndex:0]; } [self playAnimationWithModel]; }]; } } -(void)showGeneralFloatingScreenView:(PIUniversalBannerView *)bannerView model:(PIUniversalBannerModel *)model{ BOOL isSvga = [model.resourceType isEqualToString:@"SVGA"]; bannerView.isSvga = isSvga; bannerView.delegate = self; if (!_universalBannerViewCaches) { _universalBannerViewCaches = @[].mutableCopy; } [self.universalBannerViewCaches addObject:bannerView]; @kWeakify(bannerView); @kWeakify(self); [bannerView setAllowToPlay:^{ @kStrongify(self); @kStrongify(bannerView); [self.topContainer addSubview:bannerView]; POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:bannerView.center]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(bannerView.frame.size.width / 2, bannerView.center.y)]; @kWeakify(self); [springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { if (finished) { POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, bannerView.center.y)]; moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, bannerView.center.y)]; moveAnimation.beginTime = CACurrentMediaTime() + 5; moveAnimation.duration = 0.5; moveAnimation.repeatCount = 1; moveAnimation.removedOnCompletion = YES; [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { @kStrongify(self); if (finished) { [bannerView removeFromSuperview]; [self.universalBannerViewCaches removeObject:bannerView]; self.isPlayOfB = NO; if(self.animationListB.count > 0){ [self.animationListB xpSafeRemoveObjectAtIndex:0]; } [self playAnimationWithModel]; } }]; [bannerView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; } }]; [bannerView pop_addAnimation:springAnimation forKey:@"starKitchenOutAnimation"]; }]; bannerView.model = model; } - (void)roomDatingPublicResult:(AttachmentModel *)attachment { self.datingEffectQueue = [NSMutableArray array]; ///心动结果公布的话 结果是一个数组 NSArray * results = [DatingInfoModel modelsWithArray:attachment.data[@"list"]]; [self.datingEffectQueue addObjectsFromArray:results]; if (self.datingEffectQueue.count > 0) { [self startDatingAnimation:self.datingEffectQueue.firstObject]; } } - (void)startDatingAnimation:(DatingInfoModel *)datingModel { [[NSNotificationCenter defaultCenter] postNotificationName:@"message" object:[self createRoomDatingResultMessage:datingModel]]; NSString * targetUid= [NSString stringWithFormat:@"%ld", datingModel.targetUid]; NSString * uid= [NSString stringWithFormat:@"%ld", datingModel.uid]; if (datingModel.hasHeart || datingModel.hasSelectUser) { datingModel.originPoint = [self.hostDelegate animationPointAtStageViewByUid:uid]; datingModel.targetPoint = [self.hostDelegate animationPointAtStageViewByUid:targetUid]; } XPRoomDatingAnimationView *datingView = [[XPRoomDatingAnimationView alloc] init]; [self.topContainer addSubview:datingView]; [datingView startAnimationWithModel:datingModel finishBlock:^(BOOL finish) { [datingView removeFromSuperview]; [self.datingEffectQueue removeObject:datingModel]; if (self.datingEffectQueue.count > 0) { DatingInfoModel * datingModel = [self.datingEffectQueue firstObject]; [self startDatingAnimation:datingModel]; } }]; } - (NIMMessage *)createRoomDatingResultMessage:(DatingInfoModel *)datingModel { NIMMessage * message = [[NIMMessage alloc] init]; NIMSession * session = [NIMSession session:[NSString stringWithFormat:@"%ld", self.hostDelegate.getRoomInfo.roomId] type:NIMSessionTypeChatroom]; [message setValue:session forKey:@"session"]; AttachmentModel * attach = [[AttachmentModel alloc] init]; attach.first = CustomMessageType_RoomPlay_Dating; if (datingModel.hasHeart) {///如果是互选的话 attach.second = Custom_Message_Sub_Room_Play_Dating_Result_Mutual; } else { attach.second = Custom_Message_Sub_Room_Play_Dating_Result_Not_Mutual; } attach.data = [datingModel model2dictionary]; NIMCustomObject * object = [[NIMCustomObject alloc] init]; object.attachment = attach; message.messageObject = object; return message; } - (void)acrossRoomPKBannerAnimation:(AttachmentModel *)attacment { AcrossRoomPKPrizeModel * prizeModel = [AcrossRoomPKPrizeModel modelWithJSON:attacment.data]; if (attacment.second == Custom_Message_Sub_AnchorPK_Result) { prizeModel.pkType = 1; } else { prizeModel.pkType = 0; } [self.acrossRoomPKQueue addObject:prizeModel]; if (self.acrossRoomPKQueue.count == 1 ) {//判断为1个时开始播放,防止多条消息回来后重叠播放 [self startAcrossRoomPKAnimation:self.acrossRoomPKQueue.firstObject]; } } - (void)startAcrossRoomPKAnimation:(AcrossRoomPKPrizeModel *)model { XPAcrossRoomPKPrizeView *wishEffectView = [[XPAcrossRoomPKPrizeView alloc] initWithFrame:CGRectMake(KScreenWidth, kNavigationHeight+40, KScreenWidth, 87)]; wishEffectView.data = model; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gotoPkWinRoom:)]; [wishEffectView addGestureRecognizer:tap]; [self.topContainer addSubview:wishEffectView]; [UIView animateWithDuration:0.5 animations:^{ wishEffectView.frame = CGRectMake(0, kNavigationHeight+40, KScreenWidth, 87); } completion:^(BOOL finished) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.5 animations:^{ wishEffectView.hidden = YES; } completion:^(BOOL finished) { [wishEffectView removeFromSuperview]; [self.acrossRoomPKQueue removeObject:model]; if (self.acrossRoomPKQueue.count > 0) { [self startAcrossRoomPKAnimation:self.acrossRoomPKQueue.firstObject]; } }]; }); }]; } - (void)gotoPkWinRoom:(UITapGestureRecognizer *)tap { XPAcrossRoomPKPrizeView * view = (XPAcrossRoomPKPrizeView *)tap.view; if (view.data.winUid.length > 0) { [self.hostDelegate exitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:view.data.winUid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); } } - (void)receiveFansTeamGiftHandleSendGiftAnimation:(AttachmentModel *)attachment { if ([self _isInSudGame]) {return;} if (!self.hostDelegate.getRoomInfo.hasAnimationEffect) {return;} GiftReceiveInfoModel *receiveInfo = [[GiftReceiveInfoModel alloc] init]; GiftInfoModel *gift = [GiftInfoModel modelWithJSON:attachment.data[@"giftVo"]]; receiveInfo.gift = gift; receiveInfo.giftInfo = gift; receiveInfo.targetUid = [NSString stringWithFormat:@"%ld", self.hostDelegate.getRoomInfo.uid]; GiftInfoModel * giftInfo = receiveInfo.gift ? receiveInfo.gift : receiveInfo.giftInfo; if (attachment.second == Custom_Message_Sub_AllMicroLuckySend || attachment.second == Custom_Message_Sub_AllBatchMicroLuckySend || attachment.second == Custom_Message_Sub_Gift_LuckySend) { NSString * giftId = [NSString stringWithFormat:@"%ld", receiveInfo.luckyGiftList.giftList.firstObject.giftId]; giftInfo = [[XPGiftStorage shareStorage] findGiftInfo:giftId inRoom:@(receiveInfo.roomUid).stringValue]; } if (giftInfo == nil) { giftInfo = [[XPGiftStorage shareStorage] findGiftInfo:receiveInfo.giftId inRoom:@(receiveInfo.roomUid).stringValue]; } ///在最外面判断是否可以拿到那个礼物 后面使用就不用判断了 receiveInfo.gift = giftInfo; receiveInfo.isLuckyBagGift = (attachment.second == Custom_Message_Sub_AllMicroLuckySend || attachment.second == Custom_Message_Sub_AllBatchMicroLuckySend || attachment.second == Custom_Message_Sub_Gift_LuckySend); [self.giftAnimationManager enqueueGift:receiveInfo]; } - (void)receiveAnchorHourRank:(AttachmentModel *)attachment { PIBaseAnimationViewModel *anchorModel =[PIBaseAnimationViewModel new]; anchorModel.data = attachment.data; anchorModel.type = GiftBannerType_AnchorHour; if(self.animationListB.count == 0 && self.isPlayOfB == NO){ [self createAnchorHourRankAnimation:anchorModel]; } [self.animationListB addObject:anchorModel]; } - (void)receiveGiftCompound:(AttachmentModel *)attachment { if ([self _isInSudGame]) {return;} PIBaseAnimationViewModel *magicModel =[PIBaseAnimationViewModel new]; magicModel.data = attachment.data; magicModel.type = GiftBannerType_Magic_House; if(self.animationListB.count == 0 && self.isPlayOfB == NO){ [self createGiftCompoundBannerAnimation:magicModel]; } [self.animationListB addObject:magicModel]; } - (void)receiveRoomSailing:(AttachmentModel *)attachment { if ([self _isInSudGame]) {return;} if (self.sailingQueue.count == 0) { [self createSailingBannerAnimation:attachment]; } [self.sailingQueue addObject:attachment]; } - (void)createSailingBannerAnimation:(AttachmentModel *)attatchment { CGFloat kscale = (CGFloat)60 / (CGFloat)375; XPSailingAnimationView *sailingView = [[XPSailingAnimationView alloc] initWithFrame:CGRectMake(KScreenWidth, kNavigationHeight + 15, KScreenWidth, KScreenWidth * kscale)]; sailingView.prizeInfo = attatchment.data; [self.middleContainer addSubview:sailingView]; POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter]; springAnimation.springSpeed = 12; springAnimation.springBounciness = 10.f; springAnimation.fromValue = [NSValue valueWithCGPoint:sailingView.center]; springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(sailingView.frame.size.width / 2, sailingView.center.y)]; [springAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { if (finished) { POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, sailingView.center.y)]; moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, sailingView.center.y)]; moveAnimation.beginTime = CACurrentMediaTime() + 3; moveAnimation.duration = 0.5; moveAnimation.repeatCount = 1; moveAnimation.removedOnCompletion = YES; @kWeakify(self); [moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) { @kStrongify(self); if (finished) { [sailingView removeFromSuperview]; if (self.sailingQueue.count > 0) { [self.sailingQueue xpSafeRemoveObjectAtIndex:0]; } if (self.sailingQueue.count > 0) { [self createSailingBannerAnimation:self.sailingQueue.firstObject]; } } }]; [sailingView pop_addAnimation:moveAnimation forKey:@"moveOutAnimation"]; } }]; [sailingView pop_addAnimation:springAnimation forKey:@"candyTreespingOutAnimation"]; } - (void)receiveRoomGraffitiGift:(AttachmentModel *)attachment { if (self.graffitiGiftQueue.count == 0) { [self startGraffitiGiftAnimation:attachment]; } [self.graffitiGiftQueue addObject:attachment]; } - (void)startGraffitiGiftAnimation:(AttachmentModel *)model { NSDictionary * dic = model.data; NSNumber * giftId = dic[@"giftId"]; GiftInfoModel * giftInfo = [[XPGiftStorage shareStorage] findGiftInfo:giftId.stringValue inRoom:@""]; NSArray * array = dic[@"drawFixedArray"]; if (giftInfo.giftUrl.length > 0 && array.count > 0) { [[SDWebImageManager sharedManager] loadImageWithURL:[NSURL URLWithString:giftInfo.giftUrl] options:SDWebImageProgressiveLoad progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { if (error == nil && image) { XPRoomGraffitiGiftAnimationView *graffitiView = [[XPRoomGraffitiGiftAnimationView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)]; graffitiView.giftImage = image; graffitiView.delegate = self; graffitiView.model = model; graffitiView.pointArray = array; [graffitiView beginDrawAnimation]; [self.topContainer addSubview:graffitiView]; } else { [self.graffitiGiftQueue removeObject:model]; if (self.graffitiGiftQueue.count > 0) { [self startGraffitiGiftAnimation:self.graffitiGiftQueue.firstObject]; } } }]; } else { [self.graffitiGiftQueue removeObject:model]; if (self.graffitiGiftQueue.count > 0) { [self startGraffitiGiftAnimation:self.graffitiGiftQueue.firstObject]; } } } - (void)playLuckyGiftEffectWithVapUrl:(NSString *)vapUrl { NSString *encodingUrl = [vapUrl pureURLString]; encodingUrl = [encodingUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; NSString *fileName = [[encodingUrl componentsSeparatedByString:@"/"] lastObject]; NSString *fullPath = [self.GiftDynamicEffectListPath stringByAppendingPathComponent:fileName]; if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]){ vapUrl = fullPath; [self _addSubviewToMiddleContainer:self.luckyVapGiftEffectView]; [self.luckyVapGiftEffectView setMute:NO]; [self.luckyVapGiftEffectView playHWDMP4:vapUrl repeatCount:1 delegate:self]; } else { @kWeakify(self); [self.vapParser parseWithURL:encodingUrl completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if (videoUrl.length) { [self _addSubviewToMiddleContainer:self.luckyVapGiftEffectView]; [self.luckyVapGiftEffectView setMute:NO]; [self.luckyVapGiftEffectView playHWDMP4:videoUrl repeatCount:1 delegate:self]; } } failureBlock:^(NSError * _Nullable error) { self.luckyVapGiftEffectView.hidden = YES; }]; } } -(void)playAnimationWithModel{ if(self.animationListB.count <= 0){ return; } if(self.isPlayOfB == YES) { return; } PIBaseAnimationViewModel *model = self.animationListB.firstObject; switch (model.type) { case GiftBannerType_Lucky: [self createBigPrizeAnimation:model]; break; case GiftBannerType_Love: [self createCandyTreeBannerAnimation:model]; break; case GiftBannerType_Nobleman: [self createNobleLevelUpBannerAnimation:model]; break; case GiftBannerType_AnchorHour: [self createAnchorHourRankAnimation:model]; break; case GiftBannerType_LicneseHour: [self createAnchorHourRankAnimation:model]; break; case GiftBannerType_Fairy: [self createTreasureFairyBannerAnimation:model]; break; case GiftBannerType_Magic_House: [self createGiftCompoundBannerAnimation:model]; break; case GiftBannerType_General_Floating_Screen: [self createGeneralFloatingScreenAnimation:model bannerModel:nil]; break; default: // 其他未处理的枚举值类型 // 可能包括但不限于:GiftBannerType_Unknown, GiftBannerType_Normal, // GiftBannerType_Special, GiftBannerType_Event 等 break; } } - (SVGAImageView *)luckyGiftEffectView { if (!_luckyGiftEffectView) { _luckyGiftEffectView = [[SVGAImageView alloc]init]; _luckyGiftEffectView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; _luckyGiftEffectView.userInteractionEnabled = NO; _luckyGiftEffectView.delegate = self; _luckyGiftEffectView.hidden = YES; } return _luckyGiftEffectView; } - (VAPView *)luckyVapGiftEffectView { if (!_luckyVapGiftEffectView) { _luckyVapGiftEffectView = [[VAPView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)]; _luckyVapGiftEffectView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; _luckyVapGiftEffectView.contentMode = UIViewContentModeScaleAspectFill; _luckyVapGiftEffectView.hidden = YES; } return _luckyVapGiftEffectView; } #pragma mark - Old Methods Delegate: XPRoomGiftBroadcastViewDelegate - (void)xPRoomGiftBroadcastView:(XPRoomGiftBroadcastView *)view enterRoom:(NSString *)roomUid roomName:(NSString *)roomName{ id isShowBroadcastView = [[NSUserDefaults standardUserDefaults]valueForKey:@"kSaveBrooadcastSelectState"]; if(isShowBroadcastView == nil){ PIRoomGiftBroadcastWindow *broadcastView = [[PIRoomGiftBroadcastWindow alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)]; broadcastView.roodUid = roomUid; broadcastView.roomName = roomName; broadcastView.delegate = self; [kWindow addSubview:broadcastView]; return; } if (roomUid.length > 0 && self.hostDelegate.getRoomInfo.uid != roomUid.integerValue) { [self.hostDelegate exitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:roomUid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); } } - (void)xPRoomLuckyBigPrizeView:(XPRoomLuckyBigPrizeView *)view luckyGiftInfo:(PIBaseAnimationViewModel *)giftInfo { id isShowBroadcastView = [[NSUserDefaults standardUserDefaults]valueForKey:@"kSaveLuckSelectState"]; if(isShowBroadcastView == nil){ PIRoomGiftBroadcastWindow *broadcastView = [[PIRoomGiftBroadcastWindow alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)]; broadcastView.isLuck = YES; broadcastView.roodUid = giftInfo.roomUid; broadcastView.roomName = giftInfo.roomTitle; broadcastView.delegate = self; [kWindow addSubview:broadcastView]; return; } if (!giftInfo.isInRoomVisable && giftInfo.roomUid.length > 0 && giftInfo.roomUid.integerValue != self.hostDelegate.getRoomInfo.uid) { [self.hostDelegate exitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:giftInfo.roomUid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); } } - (void)xPRoomAnchorRankBannerView:(XPRoomAnchorRankBannerView *)view rankInfo:(RoomHalfHourRankModel *)rankInfo{ if (rankInfo.uid.integerValue > 0 && self.hostDelegate.getRoomInfo.uid != rankInfo.uid.integerValue) { [self.hostDelegate exitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:rankInfo.uid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); } } -(void)confirmLeaveForTheRoom:(NSString *_Nonnull)roomUid{ if (roomUid.length > 0 && self.hostDelegate.getRoomInfo.uid != roomUid.integerValue) { [self.hostDelegate exitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:roomUid viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); } } - (void)pIUniversalBannerView:(PIUniversalBannerView *)view didClick:(PIUniversalBannerModel *)model{ if (model.skipType == 2){ [self.hostDelegate exitRoom]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [XPRoomViewController openRoom:model.skipContent viewController:[XCCurrentVCStackManager shareManager].getCurrentVC]; }); }else if (model.skipType == 3){ XPWebViewController * webVC = [[XPWebViewController alloc] initWithRoomUID:nil]; webVC.isPush = YES; webVC.url = model.skipContent; [self.hostDelegate.getCurrentNav pushViewController:webVC animated:YES]; } } - (void)xPRoomGraffitiGiftAnimationViewCompletion:(XPRoomGraffitiGiftAnimationView *)view attachment:(nonnull AttachmentModel *)attachment{ [view removeFromSuperview]; if ([self.graffitiGiftQueue containsObject:attachment]) { [self.graffitiGiftQueue removeObject:attachment]; } if (self.graffitiGiftQueue.count > 0) { [self startGraffitiGiftAnimation:self.graffitiGiftQueue.firstObject]; } } #pragma mark - Touch Event Handling // 移除pointInside重写,让触摸事件正常传递 #pragma mark - Public Methods - (CGPoint)getSavedTapPoint { return self.savedTapPoint; } - (BOOL)hasSavedTapPointAvailable { return self.hasSavedTapPoint; } - (void)clearSavedTapPoint { self.hasSavedTapPoint = NO; self.savedTapPoint = CGPointZero; } - (void)debugBannerSchedulerStatus { NSLog(@"🔍 BannerScheduler 调试信息:"); NSLog(@"%@", [self.bannerScheduler debugStatus]); NSLog(@"🔍 RoomAnimationView 状态:"); NSLog(@" - isRoomBannerV2Displaying: %@", self.isRoomBannerV2Displaying ? @"YES" : @"NO"); NSLog(@" - bannerContainer.subviews.count: %ld", (long)self.bannerContainer.subviews.count); } - (CGRect)calculateInteractiveBoundsForView:(UIView *)view { // 计算视图的可交互区域 // 这里可以根据具体的 Banner 类型来调整可交互区域 CGRect bounds = view.bounds; // 如果是通用 Banner,可能需要调整可交互区域 // 例如:排除某些不可点击的区域 if ([view isKindOfClass:[PIUniversalBannerView class]]) { // 可以根据需要调整可交互区域 // 这里暂时使用整个视图区域 return bounds; } // 默认使用整个视图区域作为可交互区域 return bounds; } - (void)testBannerScheduler { NSLog(@"🧪 开始测试 BannerScheduler"); // 测试添加 Banner AttachmentModel *testBanner = [[AttachmentModel alloc] init]; testBanner.second = Custom_Message_Sub_Super_Gift_Banner; testBanner.data = @{@"test": @"data"}; [self.bannerScheduler enqueueBanner:testBanner]; NSLog(@"🧪 测试完成"); } - (void)debugTouchAreaCache { NSLog(@"🎯 触摸区域缓存调试信息:"); TouchAreaCacheManager *cacheManager = [TouchAreaCacheManager sharedManager]; [cacheManager debugPrintCache]; } - (void)debugGestureOptimizer { NSLog(@"🎯 手势优化器调试信息:"); GestureOptimizer *optimizer = [GestureOptimizer sharedOptimizer]; // 测试不同区域的点 CGFloat containerWidth = self.bannerContainer.bounds.size.width; CGPoint leftPoint = CGPointMake(containerWidth * 0.1, 40); // 左侧区域 CGPoint centerPoint = CGPointMake(containerWidth * 0.5, 40); // 中央区域 CGPoint rightPoint = CGPointMake(containerWidth * 0.9, 40); // 右侧区域 NSLog(@"🎯 测试左侧区域点:"); [optimizer debugGestureAreaForPoint:leftPoint containerWidth:containerWidth]; NSLog(@"🎯 测试中央区域点:"); [optimizer debugGestureAreaForPoint:centerPoint containerWidth:containerWidth]; NSLog(@"🎯 测试右侧区域点:"); [optimizer debugGestureAreaForPoint:rightPoint containerWidth:containerWidth]; } #pragma mark - Cleanup Methods - (void)cleanupGestureRecognizers { NSLog(@"🔄 清理手势识别器"); // 清理中央容器的手势识别器 if (self.bannerSwipeGestureContainer) { for (UIGestureRecognizer *gesture in self.bannerSwipeGestureContainer.gestureRecognizers.copy) { [self.bannerSwipeGestureContainer removeGestureRecognizer:gesture]; } } // 清理左侧容器的手势识别器 if (self.bannerLeftTapGestureContainer) { for (UIGestureRecognizer *gesture in self.bannerLeftTapGestureContainer.gestureRecognizers.copy) { [self.bannerLeftTapGestureContainer removeGestureRecognizer:gesture]; } } // 清理右侧容器的手势识别器 if (self.bannerRightTapGestureContainer) { for (UIGestureRecognizer *gesture in self.bannerRightTapGestureContainer.gestureRecognizers.copy) { [self.bannerRightTapGestureContainer removeGestureRecognizer:gesture]; } } NSLog(@"🔄 手势识别器清理完成"); } - (void)cleanupCacheManagers { NSLog(@"🔄 清理缓存管理器"); // 清理 BannerScheduler [self cleanupBannerScheduler]; // 清理触摸区域缓存 TouchAreaCacheManager *touchCacheManager = [TouchAreaCacheManager sharedManager]; [touchCacheManager clearCache]; // 清理手势优化器缓存 GestureOptimizer *gestureOptimizer = [GestureOptimizer sharedOptimizer]; [gestureOptimizer clearCache]; NSLog(@"🔄 缓存管理器清理完成"); } - (void)cleanupBannerScheduler { if (self.bannerScheduler) { NSLog(@"�� 清理 BannerScheduler"); [self.bannerScheduler clearQueue]; [self.bannerScheduler pause]; self.bannerScheduler = nil; } } - (void)removeNotificationObservers { NSLog(@"🔄 移除通知监听"); // 移除所有相关的通知监听 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"SwipeOutBanner" object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:@"TapBanner" object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:@"BannerTapToFunctionContainer" object:nil]; // 移除其他可能添加的通知监听 [[NSNotificationCenter defaultCenter] removeObserver:self]; NSLog(@"🔄 通知监听移除完成"); } #pragma mark - BannerSchedulerDelegate - (void)bannerScheduler:(BannerScheduler *)scheduler shouldPlayBanner:(id)banner { NSLog(@"🎯 BannerSchedulerDelegate: 收到播放 Banner 请求"); NSLog(@"🎯 Banner 类型: %@", [banner class]); // 将 Banner 数据转换为 AttachmentModel 并播放 if ([banner isKindOfClass:[AttachmentModel class]]) { AttachmentModel *attachment = (AttachmentModel *)banner; NSLog(@"🎯 AttachmentModel 类型: %ld", (long)attachment.second); NSLog(@"🎯 AttachmentModel 数据: %@", attachment.data); [self _playBannerWithAttachment:attachment]; } else { NSLog(@"⚠️ BannerSchedulerDelegate: Banner 不是 AttachmentModel 类型"); } } - (void)bannerSchedulerDidFinishPlaying:(BannerScheduler *)scheduler { // Banner 播放完成,可以在这里进行清理工作 NSLog(@"🔄 BannerScheduler: Banner 播放完成"); } - (void)bannerScheduler:(BannerScheduler *)scheduler didStartPlayingBanner:(id)banner { // Banner 开始播放 NSLog(@"🔄 BannerScheduler: Banner 开始播放 - 类型: %@", [banner class]); } #pragma mark - Private Methods - (void)_playBannerWithAttachment:(AttachmentModel *)attachment { NSLog(@"🎯 _playBannerWithAttachment: 开始处理 Banner - 类型: %ld", (long)attachment.second); // 清理之前的 banner,防止多个banner同时显示 NSMutableArray *viewsToRemove = [NSMutableArray array]; for (UIView *subview in self.bannerContainer.subviews) { [viewsToRemove addObject:subview]; NSLog(@"🔄 标记移除banner: %@", NSStringFromClass([subview class])); // 从缓存中移除旧的 Banner TouchAreaCacheManager *cacheManager = [TouchAreaCacheManager sharedManager]; [cacheManager removeBannerFromCache:subview]; } for (UIView *view in viewsToRemove) { [view removeFromSuperview]; } // 根据 Banner 类型分发到不同的播放方法 switch (attachment.second) { case Custom_Message_Sub_General_Floating_Screen_One_Room: case Custom_Message_Sub_General_Floating_Screen_All_Room: NSLog(@"🎯 分发到 playGameBanner"); [self playGameBanner:attachment]; break; case Custom_Message_Sub_Super_Gift_Winning_Coins_ALL_Room: NSLog(@"🎯 分发到 playLuckyWinningBanner"); [self playLuckyWinningBanner:attachment]; break; case Custom_Message_Sub_CP_Gift: NSLog(@"🎯 分发到 playCPGiftBanner"); [self playCPGiftBanner:attachment]; break; case Custom_Message_Sub_CP_Upgrade: NSLog(@"🎯 分发到 playCPLevelUp"); [self playCPLevelUp:attachment]; break; case Custom_Message_Sub_CP_Binding: NSLog(@"🎯 分发到 playCPBinding"); [self playCPBinding:attachment]; break; case Custom_Message_Sub_Gift_ChannelNotify: NSLog(@"🎯 分发到 playRoomGiftBanner"); [self playRoomGiftBanner:attachment]; break; case Custom_Message_Sub_LuckyPackage: NSLog(@"🎯 分发到 playLuckyPackageBanner"); [self playLuckyPackageBanner:attachment]; break; case Custom_Message_Sub_Super_Gift_Banner: NSLog(@"🎯 分发到 playBroveBanner"); [self playBroveBanner:attachment]; break; default: NSLog(@"⚠️ 未知的 Banner 类型: %ld", (long)attachment.second); // 标记播放完成,继续下一个 [self.bannerScheduler markBannerFinished]; break; } } @end