// // MedalsWearingListCollectionViewCell.m // YuMi // // Created by P on 2025/6/19. // #import "MedalsWearingListCollectionViewCell.h" #import #import "XPRoomGiftAnimationParser.h" #import "MedalsModel.h" @interface MedalsWearingListCollectionViewCell () @property(nonatomic, copy) NSString *imagePath; @property(nonatomic, copy) NSString *mp4Path; @property(nonatomic, strong) NetImageView *imageView; @property(nonatomic, strong) VAPView *mp4View; @property(nonatomic, strong) XPRoomGiftAnimationParser *mp4Parser; @property (nonatomic, strong) UIImageView *selectedImageView; @property (nonatomic, assign) BOOL isVisible; // 跟踪 cell 是否可见 // 新增属性:用于降级处理 @property (nonatomic, strong) MedalVo *currentMedalModel; // 当前显示的 medal 模型 @end @implementation MedalsWearingListCollectionViewCell + (NSString *)cellID { return NSStringFromClass([MedalsWearingListCollectionViewCell class]); } + (void)registerTo:(UICollectionView *)collectionView { [collectionView registerClass:[self class] forCellWithReuseIdentifier:[self cellID]]; } + (instancetype)cellFor:(UICollectionView *)collectionView atIndexPath:(NSIndexPath *)index { MedalsWearingListCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[self cellID] forIndexPath:index]; cell.isVisible = YES; return cell; } - (void)updateCell:(MedalVo *)medalModel { self.selectedImageView.hidden = medalModel.useStatus==NO; // 保存当前模型,用于降级处理 self.currentMedalModel = medalModel; // 按照新的显示逻辑处理 [self handleMedalDisplay:medalModel]; } #pragma mark - 新的显示逻辑 - (void)handleMedalDisplay:(MedalVo *)medalModel { if (!medalModel) { [self setImagePath:@""]; return; } #ifdef DEBUG // Debug 环境:如果数据不满足新逻辑条件,使用现有逻辑 if ([NSString isEmpty:medalModel.mp4Url] && [NSString isEmpty:medalModel.picUrl]) { // 都为空,使用旧逻辑 [self handleLegacyDisplay:medalModel]; return; } if (![NSString isEmpty:medalModel.picUrl] && [NSString isEmpty:medalModel.mp4Url]) { // 只有 picUrl 有值,mp4Url 为空,检查是否符合新逻辑 if (![NSString isImageFormat:medalModel.picUrl] && ![medalModel.picUrl.lowercaseString hasSuffix:@".mp4"]) { // picUrl 既不是图片也不是 mp4,使用旧逻辑 [self handleLegacyDisplay:medalModel]; return; } } #endif // 1. 优先判断 mp4Url 是否有内容 if (![NSString isEmpty:medalModel.mp4Url]) { [self setMp4Path:medalModel.mp4Url]; return; } // 2. mp4Url 为空,使用 picUrl if (![NSString isEmpty:medalModel.picUrl]) { // 3. 使用 picUrl 时,判断内容是否为图片 if ([NSString isImageFormat:medalModel.picUrl]) { [self setImagePath:medalModel.picUrl]; } else { // 不是图片格式,显示默认占位图 [self setImagePath:@""]; } return; } // 4. 都为空,显示默认占位图 [self setImagePath:@""]; } #pragma mark - mp4 失败降级处理 - (void)handleMp4FailureFallback { if (!self.currentMedalModel) { [self setImagePath:@""]; return; } // mp4 下载失败,降级使用 picUrl if (![NSString isEmpty:self.currentMedalModel.picUrl]) { if ([NSString isImageFormat:self.currentMedalModel.picUrl]) { [self setImagePath:self.currentMedalModel.picUrl]; } else { // picUrl 也不是图片格式,显示默认占位图 [self setImagePath:@""]; } } else { // picUrl 为空,显示默认占位图 [self setImagePath:@""]; } } #pragma mark - 旧版显示逻辑(用于 Debug 环境兼容) - (void)handleLegacyDisplay:(MedalVo *)medalModel { if ([medalModel.picUrl hasSuffix:@"mp4"]) { [self setMp4Path:medalModel.picUrl]; } else { [self setImagePath:medalModel.picUrl]; } } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setupUI]; [self setupNotifications]; } return self; } - (void)setupUI { [self.contentView addGradientBackgroundWithColors:@[ UIColorFromRGB(0x41007b), UIColorFromRGB(0x290858) ] startPoint:CGPointMake(0.5, 0) endPoint:CGPointMake(0.5, 1) cornerRadius:8]; [self.contentView setAllCornerRadius:8 borderWidth:1 borderColor:UIColorFromRGB(0xa166bf)]; self.imageView = [[NetImageView alloc] init]; self.imageView.contentMode = UIViewContentModeScaleAspectFill; [self.contentView addSubview:self.imageView]; [self.imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.mas_equalTo(self.contentView); make.top.mas_equalTo(13); make.leading.trailing.mas_equalTo(self.contentView).inset(13); make.height.mas_equalTo(self.imageView.mas_width); }]; [self.contentView addSubview:self.mp4View]; [self.mp4View mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(self.imageView); }]; [self.contentView addSubview:self.selectedImageView]; [self.selectedImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.mas_equalTo(7); make.trailing.mas_equalTo(-7); make.size.mas_equalTo(CGSizeMake(21, 21)); }]; } - (void)setImagePath:(NSString *)imagePath { // 停止之前的 mp4 播放 [self stopMP4Playback]; _imagePath = imagePath; self.mp4View.hidden = YES; self.imageView.hidden = NO; self.imageView.imageUrl = imagePath; } - (void)setMp4Path:(NSString *)mp4Path { // 如果是相同的 mp4 路径,不需要重新加载 if ([_mp4Path isEqualToString:mp4Path]) { return; } // 停止之前的 mp4 播放 [self stopMP4Playback]; _mp4Path = mp4Path; self.mp4View.hidden = NO; self.imageView.hidden = YES; if (!_mp4Parser) { self.mp4Parser = [[XPRoomGiftAnimationParser alloc] init]; } @kWeakify(self); [self.mp4Parser parseWithURL:mp4Path completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if (![NSString isEmpty:videoUrl]) { // 只有当 cell 可见时才播放 if (self.isVisible) { [self.mp4View playHWDMP4:videoUrl repeatCount:-1 delegate:nil]; } else { // 存储 URL,但不立即播放 self.mp4View.tag = 1; // 标记已准备好播放 } } } failureBlock:^(NSError * _Nullable error) { NSLog(@"Failed to parse mp4: %@", error); // mp4 下载失败时,降级使用 picUrl [self handleMp4FailureFallback]; }]; } #pragma mark - MP4 播放控制 - (void)stopMP4Playback { if (self.mp4View) { [self.mp4View stopHWDMP4]; self.mp4View.tag = 0; // 重置播放状态标记 } } - (void)pauseMP4Playback { if (self.mp4View && !self.mp4View.hidden) { [self.mp4View pauseHWDMP4]; } } - (void)resumeMP4Playback { if (self.mp4View && !self.mp4View.hidden && self.mp4Path) { if (self.mp4View.tag == 1) { // 已准备好但尚未播放 @kWeakify(self); [self.mp4Parser parseWithURL:self.mp4Path completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if (![NSString isEmpty:videoUrl] && self.isVisible) { [self.mp4View playHWDMP4:videoUrl repeatCount:-1 delegate:nil]; } } failureBlock:nil]; } else { [self.mp4View resumeHWDMP4]; } } } #pragma mark - 可见性管理 - (void)willDisplay { self.isVisible = YES; [self resumeMP4Playback]; } - (void)didEndDisplaying { self.isVisible = NO; [self pauseMP4Playback]; } #pragma mark - 通知处理 - (void)appDidEnterBackground { [self pauseMP4Playback]; } - (void)appWillEnterForeground { if (self.isVisible) { [self resumeMP4Playback]; } } - (void)didReceiveMemoryWarning { // 内存警告时停止播放 if (!self.isVisible) { [self stopMP4Playback]; } } #pragma mark - 生命周期 - (void)dealloc { // 停止播放 [self stopMP4Playback]; // 移除通知观察者 [[NSNotificationCenter defaultCenter] removeObserver:self]; // 清理资源 self.mp4Parser = nil; NSLog(@"MedalsWearingListCollectionViewCell dealloc"); } #pragma mark - - (VAPView *)mp4View { if (!_mp4View) { _mp4View = [[VAPView alloc] init]; _mp4View.contentMode = UIViewContentModeScaleAspectFit; } return _mp4View; } - (UIImageView *)selectedImageView { if (!_selectedImageView) { _selectedImageView = [[UIImageView alloc] initWithImage:kImage(@"medals_selected")]; _selectedImageView.hidden = YES; } return _selectedImageView; } - (void)setupNotifications { // 监听应用进入后台和恢复前台的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; // 监听内存警告通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } - (void)prepareForReuse { [super prepareForReuse]; // 停止播放 [self stopMP4Playback]; // 隐藏 mp4 视图 self.mp4View.hidden = YES; self.imageView.hidden = NO; // 重置状态 self.mp4Path = nil; self.imagePath = nil; self.isVisible = NO; self.currentMedalModel = nil; // 重置当前模型 } @end