Files
peko-ios/YuMi/Modules/YMRoom/View/AnimationView/RoomAnimationView.m

4477 lines
175 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

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

//
// RoomAnimationView.m
// YuMi
//
// Created by P on 2025/1/13.
//
//banner 手势处理会有遮挡其他视图操作的问题
#import "RoomAnimationView.h"
#import <POP.h>
#import <SVGA.h>
#import <libpag/libpag.h>
#import <NIMSDK/NIMSDK.h>
#import <UIImageView+WebCache.h>
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#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 "XPAcrossRoomPKPrizeView.h"
#import "PIRoomGiftBroadcastWindow.h"
#import "XPRoomDatingAnimationView.h"
#import "XPRoomAnchorRankBannerView.h"
#import "XPRoomGraffitiGiftAnimationView.h"
#import "XPRoomFunctionContainerView.h"
#import "XPRoomRankEntranceView.h"
#import "XPRoomAnchorRankEnterView.h"
#import "SocialStageView.h"
#import "MSRoomOnLineView.h"
#import "XPTurboModeConstants.h"
#import "TurboModeStateManager.h"
#import "BannerScheduler.h"
#import "GameBannerGestureManager.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) id<RoomHostDelegate>hostDelegate;
///最底层
@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<NSDictionary *> *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<NSDictionary *> *carEffectQueue;
/// --- 旧逻辑,有需要再重构
@property(nonatomic, assign) BOOL isPlayOfB;
@property(nonatomic, strong) NSMutableArray<PIBaseAnimationViewModel *> *animationListB; // 玩法
@property (nonatomic, assign) CGFloat broadCastHieght;
@property (nonatomic, strong) NetImageView *imageLoader;
@property (nonatomic, strong) SVGAImageView *datingEffectView;
@property (nonatomic, strong) NSMutableArray<AttachmentModel *> *sailingQueue;
@property (nonatomic, strong) NSMutableArray<DatingInfoModel *> *datingEffectQueue;
@property (nonatomic, strong) NSMutableArray<AttachmentModel *> *graffitiGiftQueue;
@property (nonatomic, strong) NSMutableArray<AcrossRoomPKPrizeModel *> *acrossRoomPKQueue;
@property (nonatomic,strong) SVGAImageView *luckyGiftEffectView; ///福袋礼物送礼物的特效
@property (nonatomic, strong) VAPView *luckyVapGiftEffectView; ///福袋礼物VAP特效
@property(nonatomic, strong) NSMutableArray <PIUniversalBannerView *>* universalBannerViewCaches;
// Banner 手势相关属性
@property(nonatomic, assign) CGPoint savedTapPoint;
@property(nonatomic, assign) BOOL hasSavedTapPoint;
@property (nonatomic, strong) GameBannerGestureManager *gameGestureManager;
@property (nonatomic, assign) BOOL isGameModeActive;
@property (nonatomic, strong) UISwipeGestureRecognizer *swipeWhenPlayGame;
// 🔧 移除:本地开关状态,改为使用 TurboModeStateManager
@property (nonatomic, strong) NSString *currentRoomId; // 当前房间 ID
// 移除本地开关缓存:直接向 TurboModeStateManager 询问实时值
@end
@implementation RoomAnimationView
- (void)setIsPlayOfB:(BOOL)isPlayOfB {
_isPlayOfB = isPlayOfB;
}
- (void)dealloc
{
NSLog(@" RoomAnimationView: dealloc 开始");
[self removeItSelf];
NSLog(@" RoomAnimationView: dealloc 完成");
}
- (void)removeItSelf {
NSLog(@"<EFBFBD><EFBFBD> RoomAnimationView: 开始销毁");
[self smartDestroy];
}
- (void)cancelAllAnimations {
NSLog(@"<EFBFBD><EFBFBD> 取消所有动画");
// <20><> 新增:检查是否有正在播放的重要动画
BOOL hasImportantAnimation = [self hasImportantAnimationPlaying];
if (hasImportantAnimation) {
NSLog(@"⚠️ 检测到重要动画正在播放,延迟清理");
// 延迟清理,给动画完成的时间
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self cancelAllAnimations];
});
return;
}
// 执行实际的取消动画逻辑
[self pop_removeAllAnimations];
// 取消所有容器的子视图动画
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];
}
}
}
- (void)cleanupAllSubviews {
NSLog(@"🔄 清理所有子视图");
// <20><> 新增:检查是否有正在播放的重要动画
BOOL hasImportantAnimation = [self hasImportantAnimationPlaying];
if (hasImportantAnimation) {
NSLog(@"⚠️ 检测到重要动画正在播放,延迟清理子视图");
// 延迟清理,给动画完成的时间
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self cleanupAllSubviews];
});
return;
}
// 执行实际的清理逻辑
[self forceCleanupAllSubviews];
}
- (void)forceCleanupAllSubviews {
NSLog(@"<EFBFBD><EFBFBD> 强制清理所有子视图");
// 清理所有容器的子视图
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(@"<EFBFBD><EFBFBD> 标记移除视图: %@ (从容器: %@)", NSStringFromClass([subview class]), NSStringFromClass([container class]));
}
for (UIView *view in viewsToRemove) {
// 取消视图上的所有动画
[view pop_removeAllAnimations];
// 清理视图上的手势识别器
if (view.gestureRecognizers.count > 0) {
NSLog(@"<EFBFBD><EFBFBD> 清理视图 %@ 上的 %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(@"<EFBFBD><EFBFBD> 清理所有 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(@"<EFBFBD><EFBFBD> 标记移除 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<RoomHostDelegate>)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];
// 🔧 新增:监听房间类型变化
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRoomTypeChanged:)
name:@"RoomTypeChanged"
object:nil];
// 🔧 新增监听combo状态变化
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleComboStateChanged:)
name:@"GiftComboStateChanged"
object:nil];
// 🎮 新增:初始化小游戏手势管理器
self.gameGestureManager = [[GameBannerGestureManager alloc] init];
self.gameGestureManager.bannerContainer = self.bannerContainer;
// 🎮 新增:监听房间类型变化
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleRoomTypeChanged:)
name:@"RoomTypeChanged"
object:nil];
// 🔧 新增初始化Turbo Mode开关状态
[self setupTurboModeSwitches];
// 🔧 新增监听Turbo Mode开关变化
[self setupTurboModeNotifications];
}
- (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;
}
// 🔧 新增初始化Turbo Mode开关状态
- (void)setupTurboModeSwitches {
// 🔧 修改不再在这里设置房间ID等待 TurboModeStateManager 设置
// 如果 TurboModeStateManager 已经有当前房间ID则使用它
NSString *roomId = [[TurboModeStateManager sharedManager] loadCurrentRoomId];
if (roomId && roomId.length > 0) {
self.currentRoomId = roomId;
// 更新礼物特效开关状态(基于房间信息)
[[TurboModeStateManager sharedManager] updateGiftEffectsForRoom:roomId
enabled:self.hostDelegate.getRoomInfo.hasAnimationEffect];
NSLog(@"🎮 RoomAnimationView Turbo Mode开关初始化完成 - 房间ID: %@", roomId);
} else {
NSLog(@"🎮 RoomAnimationView: 等待 TurboModeStateManager 设置房间ID");
}
}
// 🔧 新增设置Turbo Mode通知监听
- (void)setupTurboModeNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleTurboGiftEffectsChanged:)
name:kTurboGiftEffectsEnabledChanged
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleTurboGlobalGiftScreenChanged:)
name:kTurboGlobalGiftScreenEnabledChanged
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleTurboGlobalGameScreenChanged:)
name:kTurboGlobalGameScreenEnabledChanged
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleTurboCpMicChanged:)
name:kTurboCpMicEnabledChanged
object:nil];
// 🔧 新增监听当前房间ID设置通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleCurrentRoomIdSet:)
name:kTurboCurrentRoomIdSet
object:nil];
// 🔧 新增:监听 Turbo Mode 状态变化通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleTurboModeStateChanged:)
name:@"TurboModeStateChanged"
object:nil];
NSLog(@"🎮 Turbo Mode通知监听设置完成");
}
#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 {
// 🔧 修复:实时查询开关:礼物特效,使用正确的公开接口
NSString *roomIdForTurbo = self.currentRoomId ?: @([self.hostDelegate getRoomInfo].roomId).stringValue;
BOOL giftEffectsEnabled = [[TurboModeStateManager sharedManager] isGiftEffectsEnabledForRoom:roomIdForTurbo];
if (!giftEffectsEnabled) {
NSLog(@"🎮 Turbo Mode进房特效已关闭跳过进房动画 - 房间ID: %@", roomIdForTurbo);
return;
}
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;
}
// 🔧 修复:统一在 inserBannerModelToQueue 中检查所有开关,使用正确的公开接口
NSString *roomIdForTurbo = self.currentRoomId ?: @([self.hostDelegate getRoomInfo].roomId).stringValue;
if (obj.second == Custom_Message_Sub_General_Floating_Screen_One_Room ||
obj.second == Custom_Message_Sub_General_Floating_Screen_All_Room) {
// 游戏相关banner
BOOL allowGameScreen = [[TurboModeStateManager sharedManager] isGlobalGameScreenEnabledForRoom:roomIdForTurbo];
if (!allowGameScreen) {
NSLog(@"🎮 Turbo Mode全局游戏屏幕已关闭跳过游戏banner - 房间ID: %@", roomIdForTurbo);
return;
}
} else if (obj.second == Custom_Message_Sub_Super_Gift_Banner ||
obj.second == Custom_Message_Sub_Gift_ChannelNotify ||
obj.second == Custom_Message_Sub_LuckyPackage ||
obj.second == Custom_Message_Sub_Super_Gift_Winning_Coins_ALL_Room ||
obj.second == Custom_Message_Sub_Room_Gift_LuckBag ||
obj.second == Custom_Message_Sub_Room_Gift_LuckBag_Server ||
obj.second == Custom_Message_Sub_Room_Gift_LuckBag_FullScree) {
// 礼物相关banner
BOOL allowGiftScreen = [[TurboModeStateManager sharedManager] isGlobalGiftScreenEnabledForRoom:roomIdForTurbo];
if (!allowGiftScreen) {
NSLog(@"🎮 Turbo Mode全局礼物屏幕已关闭跳过礼物banner - 房间ID: %@", roomIdForTurbo);
return;
}
} else if (obj.second == Custom_Message_Sub_CP_Gift ||
obj.second == Custom_Message_Sub_CP_Upgrade ||
obj.second == Custom_Message_Sub_CP_Binding) {
// 🔧 修复CP相关banner - 需要检查礼物特效开关,使用正确的公开接口
NSString *roomIdForTurbo = self.currentRoomId ?: @([self.hostDelegate getRoomInfo].roomId).stringValue;
BOOL giftEffectsEnabled = [[TurboModeStateManager sharedManager] isGiftEffectsEnabledForRoom:roomIdForTurbo];
if (!giftEffectsEnabled) {
NSLog(@"🎮 Turbo Mode礼物特效已关闭跳过CP banner - 房间ID: %@", roomIdForTurbo);
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;
// <20><> 新增:检查是否正在销毁
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];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
// 🔧 新增:重置触摸状态
[self resetTouchState];
} 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];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
} exitCurrentRoom:^{
@kStrongify(self);
[self.hostDelegate exitRoom];
}];
}
- (void)receiveRoomGiftBanner:(AttachmentModel *)obj {
// 🔧 优化:开关检查已移至 inserBannerModelToQueue 统一处理
[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];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
}];
}
- (void)receiveCPEvent:(AttachmentModel *)attachment {
// 🔧 优化:开关检查已移至 inserBannerModelToQueue 统一处理
[self inserBannerModelToQueue:attachment];
}
- (void)playCPBinding:(AttachmentModel *)attachment {
@kWeakify(self);
[CPBindingAnimation display:self.topContainer
with:attachment
complete:^{
@kStrongify(self);
self.isRoomBannerV2Displaying = NO;
[self.bannerScheduler markBannerFinished];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
}];
}
- (void)playCPGiftBanner:(AttachmentModel *)attachMent {
@kWeakify(self);
[CPGiftBanner display:self.bannerContainer
with:attachMent
complete:^{
@kStrongify(self);
NSLog(@"🔄 CPGiftBanner complete 回调被调用");
self.isRoomBannerV2Displaying = NO;
[self.bannerScheduler markBannerFinished];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
}];
}
- (void)playCPLevelUp:(AttachmentModel *)attachMent {
@kWeakify(self);
[CPLevelUpAnimation display:self.topContainer
with:attachMent
complete:^{
@kStrongify(self);
self.isRoomBannerV2Displaying = NO;
[self.bannerScheduler markBannerFinished];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
}];
}
- (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 {
// 🔧 优化:开关检查已移至 inserBannerModelToQueue 统一处理
[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];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
} 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 {
// 🔧 优化:开关检查已移至 inserBannerModelToQueue 统一处理
[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];
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
} 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 {
// 🔧 修复:实时查询开关:礼物特效,使用正确的公开接口
{
NSString *roomIdForTurbo = self.currentRoomId ?: @([self.hostDelegate getRoomInfo].roomId).stringValue;
BOOL giftEffectsEnabled = [[TurboModeStateManager sharedManager] isGiftEffectsEnabledForRoom:roomIdForTurbo];
if (!giftEffectsEnabled) {
NSLog(@"🎮 RoomAnimationView 礼物特效已关闭,跳过动画 - 房间ID: %@", roomIdForTurbo);
return;
}
}
if (!self.hostDelegate.getRoomInfo.hasAnimationEffect) {
return;
}
// 🔧 新增:更新用户送礼时间
[self.giftAnimationManager updateUserGiftTime:receiveInfo.uid];
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;
#if DEBUG
// 自定义消息处理入口排查日志first/second、payload size、线程
NSData *payloadJSON = nil;
@try { payloadJSON = [NSJSONSerialization dataWithJSONObject:attachment.data ?: @{} options:0 error:nil]; } @catch (__unused NSException *e) {}
#endif
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_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;
}
GiftReceiveInfoModel * receiveInfo = [GiftReceiveInfoModel modelWithJSON:attachment.data];
[receiveInfo giftDataAlignment];
// 检查并修复连击计数
if (receiveInfo.comboCount < 1) {
// 尝试从 attachment.data 中获取正确的连击计数
NSNumber *dataComboCount = attachment.data[@"comboCount"];
if (dataComboCount && [dataComboCount integerValue] >= 1) {
receiveInfo.comboCount = [dataComboCount integerValue];
} else {
receiveInfo.comboCount = 1;
}
}
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) {
[self receiveGiftHandleSendGiftAnimationWith:receiveInfo
attachment:attachment];
[self _handleBagGift:receiveInfo];
} else {
if (receiveInfo.gift.notifyFull == 1 &&
attachment.second == Custom_Message_Sub_Gift_Send) {
// 全服礼物,但由自己发送,不在此逻辑播放
return;
}
// 处理从位置 发送者 到 接受者 的礼物移动动画
[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)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_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
BOOL hasVisibleBanner = NO;
for (UIView *subview in self.bannerContainer.subviews) {
if (!subview.hidden && subview.alpha > 0.01) {
hasVisibleBanner = YES;
break;
}
}
// 如果没有可见的 banner直接转发点击事件到下层
if (!hasVisibleBanner) {
NSLog(@"🎯 没有可见 banner直接转发点击事件到下层");
self.savedTapPoint = tapPoint;
self.hasSavedTapPoint = YES;
CGPoint screenPoint = [self.bannerContainer convertPoint:tapPoint toView:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"BannerTapToFunctionContainer"
object:nil
userInfo:@{@"point": [NSValue valueWithCGPoint:screenPoint]}];
return;
}
// 有可见 banner 时的原有逻辑
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];
[[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<micCount; i++) {
NSMutableDictionary * micQueue = self.hostDelegate.getMicroQueue;
MicroQueueModel *micSequence = [micQueue objectForKey:[NSString stringWithFormat:@"%d", i]];
if (micSequence == nil || micSequence.userInfo == nil) {
continue;
}
if (micSequence.userInfo.uid == [AccountInfoStorage instance].getUid.integerValue &&
micSequence.userInfo.gameStatus == LittleGamePlayStatus_Plying) {
isGamePlaying = YES;
break;
}
}
}
return isGamePlaying;
}
- (void)_addSubviewToMiddleContainer:(UIView *)view {
if (view.superview == nil) {
[self.middleContainer addSubview:view];
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.middleContainer);
make.width.mas_equalTo(KScreenWidth);
make.height.mas_equalTo(KScreenHeight);
}];
}
view.hidden = NO;
}
#pragma mark - Lazy init
- (XPRoomAnimationHitView *)bottomContainer {
if (!_bottomContainer) {
_bottomContainer = [[XPRoomAnimationHitView alloc] init];
}
return _bottomContainer;
}
- (XPRoomAnimationHitView *)middleContainer {
if (!_middleContainer) {
_middleContainer = [[XPRoomAnimationHitView alloc] init];
}
return _middleContainer;
}
- (XPRoomAnimationHitView *)topContainer {
if (!_topContainer) {
_topContainer = [[XPRoomAnimationHitView alloc] init];
}
return _topContainer;
}
- (XPRoomAnimationHitView *)bannerContainer {
if (!_bannerContainer) {
_bannerContainer = [[XPRoomAnimationHitView alloc] init];
}
return _bannerContainer;
}
- (UIView *)bannerSwipeGestureContainer {
if (!_bannerSwipeGestureContainer) {
_bannerSwipeGestureContainer = [[UIView alloc] init];
_bannerSwipeGestureContainer.userInteractionEnabled = YES;
}
return _bannerSwipeGestureContainer;
}
- (UIView *)bannerLeftTapGestureContainer {
if (!_bannerLeftTapGestureContainer) {
_bannerLeftTapGestureContainer = [[UIView alloc] init];
_bannerLeftTapGestureContainer.userInteractionEnabled = YES;
}
return _bannerLeftTapGestureContainer;
}
- (UIView *)bannerRightTapGestureContainer {
if (!_bannerRightTapGestureContainer) {
_bannerRightTapGestureContainer = [[UIView alloc] init];
_bannerRightTapGestureContainer.userInteractionEnabled = YES;
}
return _bannerRightTapGestureContainer;
}
- (SVGAImageView *)enterEffectView {
if (_enterEffectView == nil) {
_enterEffectView = [[SVGAImageView alloc]init];
_enterEffectView.delegate = self;
_enterEffectView.contentMode = UIViewContentModeScaleAspectFill;
_enterEffectView.frame = CGRectMake(0, 0, KScreenWidth, 50);
_enterEffectView.backgroundColor = [UIColor clearColor];
_enterEffectView.alpha = 0;
_enterEffectView.userInteractionEnabled = NO;
}
return _enterEffectView;
}
- (NSMutableArray *)svgaQueue{
if(!_svgaQueue){
_svgaQueue = [NSMutableArray array];
}
return _svgaQueue;
}
- (void)addToSvgaQueue:(GiftReceiveInfoModel *)model {
dispatch_async(self.giftEffectsQueue, ^{
[self.svgaQueue addObject:model];
});
}
- (void)removeFromSvgaQueueAtIndex:(NSInteger)index {
dispatch_async(self.giftEffectsQueue, ^{
if (index < self.svgaQueue.count) {
if (index < self.svgaQueue.count) {
[self.svgaQueue xpSafeRemoveObjectAtIndex:index];
}
}
});
}
- (XPRoomGiftAnimationParser *)vapParser {
if (!_vapParser) {
_vapParser = [[XPRoomGiftAnimationParser alloc] init];
}
return _vapParser;
}
- (PAGView *)pagGiftEffectView{
if(!_pagGiftEffectView){
_pagGiftEffectView = [[PAGView alloc]init];
[_pagGiftEffectView addListener:self];
_pagGiftEffectView.backgroundColor = [UIColor clearColor];
_pagGiftEffectView.userInteractionEnabled = NO;
_pagGiftEffectView.repeatCount = 1;
_pagGiftEffectView.scaleMode = PAGScaleModeStretch;
}
return _pagGiftEffectView;
}
- (VAPView *)vapGiftEffectView {
if (!_vapGiftEffectView) {
_vapGiftEffectView = [[VAPView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
_vapGiftEffectView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
_vapGiftEffectView.contentMode = UIViewContentModeScaleAspectFill;
_vapGiftEffectView.hidden = YES;
_vapGiftEffectView.userInteractionEnabled = NO;
}
return _vapGiftEffectView;
}
- (SVGAParser *)vggParser {
if (!_vggParser) {
_vggParser = [[SVGAParser alloc] init];
}
return _vggParser;
}
- (SVGAImageView *)vggGiftEffectView {
if (!_vggGiftEffectView) {
_vggGiftEffectView = [[SVGAImageView alloc]init];
_vggGiftEffectView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
_vggGiftEffectView.userInteractionEnabled = NO;
_vggGiftEffectView.delegate = self;
_vggGiftEffectView.hidden = YES;
_vggGiftEffectView.loops = 1;
_vggGiftEffectView.clearsAfterStop = YES;
}
return _vggGiftEffectView;
}
- (VAPView *)carVapEffectView {
if (!_carVapEffectView) {
_carVapEffectView = [[VAPView alloc] initWithFrame:CGRectMake(0, 0, KScreenWidth, KScreenHeight)];
_carVapEffectView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.3];
_carVapEffectView.contentMode = UIViewContentModeScaleAspectFill;
_carVapEffectView.hidden = YES;
}
return _carVapEffectView;
}
- (SVGAImageView *)carSVGAEffectView {
if (_carSVGAEffectView == nil) {
_carSVGAEffectView = [[SVGAImageView alloc]init];
_carSVGAEffectView.delegate = self;
_carSVGAEffectView.contentMode = UIViewContentModeScaleAspectFit;
_carSVGAEffectView.frame = CGRectMake(0, 0, KScreenWidth, KScreenHeight);
_carSVGAEffectView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.3];
_carSVGAEffectView.hidden = YES;
_carSVGAEffectView.userInteractionEnabled = NO;
_carSVGAEffectView.loops = 1;
_carSVGAEffectView.clearsAfterStop = YES;
}
return _carSVGAEffectView;
}
- (PAGView *)carPagEffectView{
if(!_carPagEffectView){
_carPagEffectView = [[PAGView alloc]init];
[_carPagEffectView addListener:self];
_carPagEffectView.backgroundColor = [UIColor clearColor];
_carPagEffectView.userInteractionEnabled = NO;
_carPagEffectView.repeatCount = 1;
_carPagEffectView.scaleMode = PAGScaleModeStretch;
}
return _carPagEffectView;
}
#pragma mark - OLD METHODs
- (void)receiveLuckyGiftBigPrize:(AttachmentModel *)attachment {
if ([self _isInSudGame]) {return;}
PIBaseAnimationViewModel * prizeModel = [PIBaseAnimationViewModel modelWithDictionary:attachment.data];
prizeModel.type = GiftBannerType_Lucky;
prizeModel.isInRoomVisable = attachment.second == Custom_Message_Sub_Room_Gift_LuckBag;
if(self.animationListB.count == 0 && self.isPlayOfB == NO){
[self createBigPrizeAnimation:prizeModel];
}
[self.animationListB addObject:prizeModel];
}
- (void)createBigPrizeAnimation:(PIBaseAnimationViewModel *)prizeModel {
self.isPlayOfB = YES;
CGFloat top = (kNavigationHeight + 15);
XPRoomLuckyBigPrizeView * luckyGiftEffectView = [[XPRoomLuckyBigPrizeView alloc] initWithFrame:CGRectMake(KScreenWidth, top, 375, 71)];
luckyGiftEffectView.delegate = self;
[self.topContainer addSubview:luckyGiftEffectView];
luckyGiftEffectView.giftInfo = prizeModel;
POPSpringAnimation *springAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
springAnimation.springSpeed = 12;
springAnimation.springBounciness = 10.f;
springAnimation.fromValue = [NSValue valueWithCGPoint:luckyGiftEffectView.center];
springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(KScreenWidth / 2, luckyGiftEffectView.center.y)];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 7 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
POPBasicAnimation *moveAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter];
moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, luckyGiftEffectView.center.y)];
moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(-KScreenWidth/2, luckyGiftEffectView.center.y)];
moveAnimation.beginTime = CACurrentMediaTime();
moveAnimation.duration = 0.5;
moveAnimation.repeatCount = 1;
moveAnimation.removedOnCompletion = YES;
@kWeakify(self);
[moveAnimation setCompletionBlock:^(POPAnimation *anim, BOOL finished) {
@kStrongify(self);
if (finished) {
[luckyGiftEffectView removeFromSuperview];
if (self.animationListB.count > 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)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_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;
}
/// 调试方法在界面上绘制所有符合条件横向相邻1-4 或 5-8的麦位中点 75x75 半透明矩形
- (void)debug_drawMicMidpointRects {
// 移除旧的调试矩形
NSArray<UIView *> *subviews = [self.topContainer.subviews copy];
for (UIView *view in subviews) {
if (view.tag == 56001) {
[view removeFromSuperview];
}
}
// 在宿主视图层级中查找 SocialStageView
SocialStageView *stageView = nil;
for (UIView *obj in self.hostDelegate.getSuperView.subviews) {
if ([obj isKindOfClass:[SocialStageView class]]) {
stageView = (SocialStageView *)obj;
break;
}
}
if (!stageView) {
return;
}
// 定义所有横向相邻的有效麦位对
NSArray<NSArray<NSNumber *> *> *pairs = @[
@[@1, @2], @[@2, @3], @[@3, @4],
@[@5, @6], @[@6, @7], @[@7, @8]
];
for (NSArray<NSNumber *> *pair in pairs) {
NSInteger left = pair.firstObject.integerValue;
NSInteger right = pair.lastObject.integerValue;
CGRect stageRect = [stageView rectForMidpointBetweenMicAtIndex:left andIndex:right];
if (CGRectIsEmpty(stageRect)) {
continue;
}
// 转换到当前视图坐标系
CGRect rectInSelf = [self convertRect:stageRect fromView:stageView];
UIView *overlay = [[UIView alloc] initWithFrame:rectInSelf];
overlay.backgroundColor = [[UIColor redColor] colorWithAlphaComponent:0.25];
overlay.layer.borderColor = [UIColor.redColor colorWithAlphaComponent:0.6].CGColor;
overlay.layer.borderWidth = 1.0;
overlay.layer.cornerRadius = 8.0;
overlay.clipsToBounds = YES;
overlay.tag = 56001;
[self.topContainer addSubview:overlay];
}
}
- (CGRect)calculateInteractiveBoundsForView:(UIView *)view {
// 计算视图的可交互区域
// 这里可以根据具体的 Banner 类型来调整可交互区域
CGRect bounds = view.bounds;
// 如果是通用 Banner可能需要调整可交互区域
// 例如:排除某些不可点击的区域
if ([view isKindOfClass:[PIUniversalBannerView class]]) {
// 可以根据需要调整可交互区域
// 这里暂时使用整个视图区域
return bounds;
}
// 默认使用整个视图区域作为可交互区域
return bounds;
}
#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)cleanupGiftEffects {
NSLog(@"🎮 清理礼物特效");
// 停止当前播放的礼物特效
if (self.isPlayingGiftEffect) {
self.isPlayingGiftEffect = NO;
}
// 清理礼物特效视图
if (self.pagGiftEffectView && self.pagGiftEffectView.superview) {
[self.pagGiftEffectView stop];
[self.pagGiftEffectView removeFromSuperview];
}
if (self.vapGiftEffectView && self.vapGiftEffectView.superview) {
[self.vapGiftEffectView stopHWDMP4];
[self.vapGiftEffectView removeFromSuperview];
}
if (self.vggGiftEffectView && self.vggGiftEffectView.superview) {
[self.vggGiftEffectView stopAnimation];
[self.vggGiftEffectView removeFromSuperview];
}
// 清空礼物队列
[self.svgaQueue removeAllObjects];
NSLog(@"🎮 礼物特效清理完成");
}
// 🔧 新增清理礼物相关banner
- (void)cleanupGiftBanners {
NSLog(@"🎮 清理礼物相关banner");
// 清理礼物相关的banner
NSMutableArray *viewsToRemove = [NSMutableArray array];
for (UIView *subview in self.bannerContainer.subviews) {
if ([subview isKindOfClass:[BravoGiftBannerView class]] ||
[subview isKindOfClass:[LuckyGiftWinningBannerView class]] ||
[subview isKindOfClass:[RoomHighValueGiftBannerAnimation class]] ||
[subview isKindOfClass:[LuckyPackageBannerView class]]) {
[viewsToRemove addObject:subview];
}
}
for (UIView *view in viewsToRemove) {
[view removeFromSuperview];
}
// 清空队列中礼物相关的banner包括超级礼物banner、礼物频道通知、福袋banner
if (self.bannerScheduler) {
// 逆序移除以避免索引移动问题
for (NSInteger i = self.bannerScheduler.queueCount - 1; i >= 0; i--) {
id queued = [self.bannerScheduler bannerAtIndex:i];
if ([queued isKindOfClass:[AttachmentModel class]]) {
AttachmentModel *att = (AttachmentModel *)queued;
if (att.second == Custom_Message_Sub_Super_Gift_Banner ||
att.second == Custom_Message_Sub_Gift_ChannelNotify ||
att.second == Custom_Message_Sub_LuckyPackage) {
[self.bannerScheduler removeBannerAtIndex:i];
}
}
}
}
// 标记banner播放完成继续下一个
if (self.isRoomBannerV2Displaying) {
self.isRoomBannerV2Displaying = NO;
[self.bannerScheduler markBannerFinished];
}
NSLog(@"🎮 礼物相关banner清理完成");
}
// 🔧 新增清理游戏相关banner
- (void)cleanupGameBanners {
NSLog(@"🎮 清理游戏相关banner");
// 清理游戏相关的banner
NSMutableArray *viewsToRemove = [NSMutableArray array];
for (UIView *subview in self.bannerContainer.subviews) {
if ([subview isKindOfClass:[GameUniversalBannerView class]]) {
[viewsToRemove addObject:subview];
}
}
for (UIView *view in viewsToRemove) {
[view removeFromSuperview];
}
// 清空队列中游戏相关的banner通用飘屏单房/全房)
if (self.bannerScheduler) {
for (NSInteger i = self.bannerScheduler.queueCount - 1; i >= 0; i--) {
id queued = [self.bannerScheduler bannerAtIndex:i];
if ([queued isKindOfClass:[AttachmentModel class]]) {
AttachmentModel *att = (AttachmentModel *)queued;
if (att.second == Custom_Message_Sub_General_Floating_Screen_One_Room ||
att.second == Custom_Message_Sub_General_Floating_Screen_All_Room) {
[self.bannerScheduler removeBannerAtIndex:i];
}
}
}
}
// 标记banner播放完成继续下一个
if (self.isRoomBannerV2Displaying) {
self.isRoomBannerV2Displaying = NO;
[self.bannerScheduler markBannerFinished];
}
NSLog(@"🎮 游戏相关banner清理完成");
}
- (void)cleanupBannerScheduler {
if (self.bannerScheduler) {
NSLog(@"<EFBFBD><EFBFBD> 清理 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 name:@"RoomTypeChanged" object:nil];
// 🔧 新增移除Turbo Mode通知监听
[[NSNotificationCenter defaultCenter] removeObserver:self name:kTurboGiftEffectsEnabledChanged object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kTurboGlobalGiftScreenEnabledChanged object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:kTurboGlobalGameScreenEnabledChanged object:nil];
// 移除其他可能添加的通知监听
[[NSNotificationCenter defaultCenter] removeObserver:self];
NSLog(@"🔄 通知监听移除完成");
}
#pragma mark - BannerSchedulerDelegate
- (void)bannerScheduler:(BannerScheduler *)scheduler shouldPlayBanner:(id)banner {
// 将 Banner 数据转换为 AttachmentModel
if (![banner isKindOfClass:[AttachmentModel class]]) {
NSLog(@"⚠️ BannerSchedulerDelegate: Banner 不是 AttachmentModel 类型");
[self.bannerScheduler markBannerFinished];
return;
}
AttachmentModel *attachment = (AttachmentModel *)banner;
// 二次校验:出队播放前按 TurboMode 再过滤一次,防止进房竞态导致漏过滤
NSString *roomIdForTurbo = self.currentRoomId ?: @([self.hostDelegate getRoomInfo].roomId).stringValue;
BOOL allowed = YES;
if (attachment.second == Custom_Message_Sub_General_Floating_Screen_One_Room ||
attachment.second == Custom_Message_Sub_General_Floating_Screen_All_Room) {
allowed = [[TurboModeStateManager sharedManager] isGlobalGameScreenEnabledForRoom:roomIdForTurbo];
} else if (attachment.second == Custom_Message_Sub_Super_Gift_Banner ||
attachment.second == Custom_Message_Sub_Gift_ChannelNotify ||
attachment.second == Custom_Message_Sub_LuckyPackage) {
allowed = [[TurboModeStateManager sharedManager] isGlobalGiftScreenEnabledForRoom:roomIdForTurbo];
} else if (attachment.second == Custom_Message_Sub_CP_Gift ||
attachment.second == Custom_Message_Sub_CP_Upgrade ||
attachment.second == Custom_Message_Sub_CP_Binding) {
allowed = [[TurboModeStateManager sharedManager] isGiftEffectsEnabledForRoom:roomIdForTurbo];
}
if (!allowed) {
NSLog(@"🎮 TurboMode 二次校验拒绝播放 banner(second=%ld) - 房间ID:%@", (long)attachment.second, roomIdForTurbo);
[self.bannerScheduler markBannerFinished];
return;
}
[self _playBannerWithAttachment:attachment];
}
- (void)bannerSchedulerDidFinishPlaying:(BannerScheduler *)scheduler {
// Banner 播放完成,可以在这里进行清理工作
// 🔧 新增:确保手势容器状态正确
[self ensureBannerGestureContainersEnabled];
[self resetTouchState];
}
- (void)bannerScheduler:(BannerScheduler *)scheduler didStartPlayingBanner:(id)banner {
// Banner 开始播放
}
#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;
}
}
#pragma mark - 房间类型手势容器控制
// 处理房间类型变化
- (void)handleRoomTypeChanged:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
NSInteger roomType = [userInfo[@"roomType"] integerValue];
if (roomType == RoomType_MiniGame) {
NSLog(@" RoomAnimationView: 检测到小游戏房间,设置 banner 手势穿透模式 + 启用 swipe 手势");
[self setBannerGesturePassthroughMode];
self.isGameModeActive = YES;
} else {
NSLog(@" RoomAnimationView: 检测到非小游戏房间,恢复 banner 手势正常模式 + 移除 swipe 手势");
[self restoreBannerGestureNormalMode];
self.isGameModeActive = NO;
}
}
// 重构手势穿透模式设置
- (void)setBannerGesturePassthroughMode {
// 隐藏和禁用所有手势容器
[self hideAllGestureContainers];
// 完全禁用 bannerContainer 的用户交互
self.bannerContainer.userInteractionEnabled = YES;
// 启用小游戏手势管理器(包含 swipe 手势)
[self.gameGestureManager enableGestureForGameMode];
NSLog(@"<EFBFBD><EFBFBD> Banner 手势已设置为穿透模式swipe 手势已启用");
}
// 重构手势正常模式恢复
- (void)restoreBannerGestureNormalMode {
// 显示和启用所有手势容器
[self showAllGestureContainers];
// 恢复 bannerContainer 的用户交互
self.bannerContainer.userInteractionEnabled = YES;
// 禁用小游戏手势管理器(移除 swipe 手势)
[self.gameGestureManager disableGestureForGameMode];
NSLog(@"🎮 Banner 手势已恢复正常模式swipe 手势已移除");
}
- (void)hideAllGestureContainers {
self.bannerSwipeGestureContainer.hidden = YES;
self.bannerLeftTapGestureContainer.hidden = YES;
self.bannerRightTapGestureContainer.hidden = YES;
self.bannerSwipeGestureContainer.userInteractionEnabled = NO;
self.bannerLeftTapGestureContainer.userInteractionEnabled = NO;
self.bannerRightTapGestureContainer.userInteractionEnabled = NO;
}
- (void)showAllGestureContainers {
self.bannerSwipeGestureContainer.hidden = NO;
self.bannerLeftTapGestureContainer.hidden = NO;
self.bannerRightTapGestureContainer.hidden = NO;
self.bannerSwipeGestureContainer.userInteractionEnabled = YES;
self.bannerLeftTapGestureContainer.userInteractionEnabled = YES;
self.bannerRightTapGestureContainer.userInteractionEnabled = YES;
}
// 优化手势状态确保方法
- (void)ensureBannerGestureContainersEnabled {
if (self.isGameModeActive) {
// 小游戏模式下,确保手势管理器正常工作(包含 swipe 手势)
if (!self.gameGestureManager.isEnabled) {
NSLog(@"🔧 检测到小游戏手势管理器未启用,重新启用(包含 swipe 手势)");
[self.gameGestureManager enableGestureForGameMode];
}
return;
}
// 非小游戏模式下的原有逻辑(不包含 swipe 手势)
[self showAllGestureContainers];
// 确保小游戏手势管理器已禁用
if (self.gameGestureManager.isEnabled) {
NSLog(@"<EFBFBD><EFBFBD> 检测到小游戏手势管理器仍启用,禁用(移除 swipe 手势)");
[self.gameGestureManager disableGestureForGameMode];
}
}
// 优化触摸状态重置
- (void)resetTouchState {
self.savedTapPoint = CGPointZero;
self.hasSavedTapPoint = NO;
if (self.isGameModeActive) {
[self.gameGestureManager resetState];
[self.gameGestureManager enableGestureForGameMode];
NSLog(@" 小游戏模式触摸状态已重置swipe 手势已重新启用");
} else {
NSLog(@" 非小游戏模式触摸状态已重置swipe 手势保持禁用");
}
}
// 🔧 新增处理combo状态变化
- (void)handleComboStateChanged:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
NSString *uid = userInfo[@"uid"];
BOOL isCombo = [userInfo[@"isCombo"] boolValue];
// 通知动画管理器更新combo状态
if (isCombo) {
[self.giftAnimationManager setUserComboState:YES forUser:uid];
} else {
[self.giftAnimationManager clearUserComboState:uid];
}
}
// 🔧 修改处理Turbo Mode礼物特效开关变化
- (void)handleTurboGiftEffectsChanged:(NSNotification *)notification {
BOOL enabled = [notification.userInfo[@"on"] boolValue];
// 如果关闭,清理当前效果
if (!enabled) {
[self cleanupGiftEffects];
// 同时清理 CP 类 Banner礼物特效开关关闭时不应再展示 CP 相关)
if (self.currentRoomId.length > 0) {
[self cleanupCpBanners];
} else {
[self cleanupCpBanners];
}
}
NSLog(@"🎮 RoomAnimationView 礼物特效状态变化: %@", enabled ? @"开启" : @"关闭");
}
// 🔧 修改处理Turbo Mode全局礼物屏幕开关变化
- (void)handleTurboGlobalGiftScreenChanged:(NSNotification *)notification {
BOOL enabled = [notification.userInfo[@"on"] boolValue];
// 如果关闭,清理礼物相关 banner
if (!enabled) {
[self cleanupGiftBanners];
}
NSLog(@"🎮 RoomAnimationView 全局礼物屏幕状态变化: %@", enabled ? @"开启" : @"关闭");
}
// 🔧 修改处理Turbo Mode全局游戏屏幕开关变化
- (void)handleTurboGlobalGameScreenChanged:(NSNotification *)notification {
BOOL enabled = [notification.userInfo[@"on"] boolValue];
// 如果关闭,清理游戏相关 banner
if (!enabled) {
[self cleanupGameBanners];
}
NSLog(@"🎮 RoomAnimationView 全局游戏屏幕状态变化: %@", enabled ? @"开启" : @"关闭");
}
// 🔧 新增处理Turbo Mode CP麦位开关变化
- (void)handleTurboCpMicChanged:(NSNotification *)notification {
BOOL enabled = [notification.userInfo[@"on"] boolValue];
// CP麦位开关的变化由 MicMidpointRectManager 处理,这里只记录日志
NSLog(@"🎮 RoomAnimationView CP麦位开关状态变化: %@", enabled ? @"开启" : @"关闭");
}
// 🔧 新增:清理 CP 相关 banner受礼物特效开关影响
- (void)cleanupCpBanners {
NSLog(@"🎮 清理 CP 相关banner");
// 清理可见的 CP 相关视图
NSMutableArray *viewsToRemove = [NSMutableArray array];
for (UIView *subview in self.bannerContainer.subviews) {
if ([subview isKindOfClass:[CPGiftBanner class]]) {
[viewsToRemove addObject:subview];
}
}
for (UIView *subview in self.topContainer.subviews) {
if ([subview isKindOfClass:[CPBindingAnimation class]] ||
[subview isKindOfClass:[CPLevelUpAnimation class]]) {
[viewsToRemove addObject:subview];
}
}
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
// 清空队列中 CP 相关的 bannerCP 礼物/升级/绑定)
if (self.bannerScheduler) {
for (NSInteger i = self.bannerScheduler.queueCount - 1; i >= 0; i--) {
id queued = [self.bannerScheduler bannerAtIndex:i];
if ([queued isKindOfClass:[AttachmentModel class]]) {
AttachmentModel *att = (AttachmentModel *)queued;
if (att.second == Custom_Message_Sub_CP_Gift ||
att.second == Custom_Message_Sub_CP_Upgrade ||
att.second == Custom_Message_Sub_CP_Binding) {
[self.bannerScheduler removeBannerAtIndex:i];
}
}
}
}
if (self.isRoomBannerV2Displaying) {
self.isRoomBannerV2Displaying = NO;
[self.bannerScheduler markBannerFinished];
}
NSLog(@"🎮 CP 相关banner清理完成");
}
// 🔧 新增处理当前房间ID设置通知
- (void)handleCurrentRoomIdSet:(NSNotification *)notification {
NSString *roomId = notification.userInfo[@"roomId"];
if (roomId && roomId.length > 0) {
self.currentRoomId = roomId;
// 更新礼物特效开关状态(基于房间信息)
[[TurboModeStateManager sharedManager] updateGiftEffectsForRoom:roomId
enabled:self.hostDelegate.getRoomInfo.hasAnimationEffect];
NSLog(@"🎮 RoomAnimationView: 收到房间ID设置通知房间ID: %@", roomId);
}
}
// 🔧 新增:更新本地开关状态缓存
// 移除本地缓存更新:统一直接查 TurboModeStateManager
// 🔧 新增:处理 Turbo Mode 状态变化通知
- (void)handleTurboModeStateChanged:(NSNotification *)notification {
BOOL turboModeEnabled = [notification.userInfo[@"enabled"] boolValue];
if (turboModeEnabled) {
// Turbo Mode 开启时,停止所有 banner 和动画
[self stopAllBannersAndAnimations];
NSLog(@"🎮 RoomAnimationView: Turbo Mode 已开启,停止所有 banner 和动画");
} else {
// Turbo Mode 关闭时,恢复正常的 banner 和动画控制
if (self.bannerScheduler) {
// 关键:恢复调度器,清除 isPaused 并在空闲时自动触发下一条
[self.bannerScheduler resume];
}
NSLog(@"🎮 RoomAnimationView: Turbo Mode 已关闭,恢复 banner 调度");
}
}
// 🔧 新增:停止所有 banner 和动画
- (void)stopAllBannersAndAnimations {
// 停止所有 banner
if (self.bannerScheduler) {
[self.bannerScheduler clearQueue];
[self.bannerScheduler pause];
}
// 注意:礼物动画清理由 cleanupGiftEffects 专门处理,避免重复
// 停止其他可能的动画
for (UIView *subview in self.topContainer.subviews) {
if ([subview respondsToSelector:@selector(stopAnimation)]) {
[subview performSelector:@selector(stopAnimation)];
}
}
// 停止中间容器的动画
for (UIView *subview in self.middleContainer.subviews) {
if ([subview respondsToSelector:@selector(stopAnimation)]) {
[subview performSelector:@selector(stopAnimation)];
}
}
// 停止底部容器的动画
for (UIView *subview in self.bottomContainer.subviews) {
if ([subview respondsToSelector:@selector(stopAnimation)]) {
[subview performSelector:@selector(stopAnimation)];
}
}
}
// 🔧 新增:检查重要动画状态
- (BOOL)hasImportantAnimationPlaying {
// 检查 topContainer 中是否有正在播放的重要动画
for (UIView *subview in self.topContainer.subviews) {
if ([subview isKindOfClass:[BravoGiftWinningFlagView class]] ||
[subview isKindOfClass:[LuckyGiftWinningFlagView class]]) {
NSLog(@"🎯 检测到重要动画正在播放: %@", NSStringFromClass([subview class]));
return YES;
}
}
// 检查 middleContainer 中是否有正在播放的礼物动画
for (UIView *subview in self.middleContainer.subviews) {
if ([subview isKindOfClass:[SVGAImageView class]] ||
[subview isKindOfClass:[VAPView class]] ||
[subview isKindOfClass:[PAGView class]]) {
NSLog(@"🎯 检测到礼物动画正在播放: %@", NSStringFromClass([subview class]));
return YES;
}
}
return NO;
}
// <20><> 新增:智能销毁方法
- (void)smartDestroy {
// 检查是否有正在播放的重要动画
BOOL hasImportantAnimation = [self hasImportantAnimationPlaying];
if (hasImportantAnimation) {
NSLog(@"⚠️ 检测到重要动画正在播放,延迟销毁");
// 延迟销毁,给动画完成的时间
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self forceDestroy];
});
return;
}
[self forceDestroy];
}
- (void)forceDestroy {
NSLog(@"<EFBFBD><EFBFBD> RoomAnimationView: 强制销毁");
// 移除广播代理
[[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(@"<EFBFBD><EFBFBD> 清理 BannerScheduler");
[self.bannerScheduler clearQueue];
[self.bannerScheduler pause];
self.bannerScheduler = nil;
}
// 清理手势识别器
[self cleanupGestureRecognizers];
// 清理缓存管理器
[self cleanupCacheManagers];
// 移除通知监听
[self removeNotificationObservers];
NSLog(@"<EFBFBD><EFBFBD> RoomAnimationView: 销毁完成");
}
@end