Files
peko-ios/YuMi/Modules/YMMine/View/Medals/MedalsCyclePagerCell.m

405 lines
12 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.

//
// MedalsCyclePagerCell.m
// YuMi
//
// Created by P on 2025/1/7.
//
#import "MedalsCyclePagerCell.h"
#import "MedalsModel.h"
#import <QGVAPWrapView.h>
#import "XPRoomGiftAnimationParser.h"
@interface MedalsCyclePagerCell () <HWDMP4PlayDelegate>
@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) MedalVo *displayModel;
@property (nonatomic, assign) BOOL isVisible; // 跟踪 cell 是否可见
@end
@implementation MedalsCyclePagerCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setupUI];
[self setupNotifications];
}
return self;
}
- (instancetype)init {
self = [super init];
if (self) {
[self setupUI];
[self setupNotifications];
}
return self;
}
- (void)setupUI {
self.isVisible = YES;
self.backgroundColor = [UIColor clearColor];
self.contentView.backgroundColor = [UIColor clearColor];
// 添加图片视图
self.imageView = [[NetImageView alloc] init];
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:self.imageView];
[self.imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.mas_equalTo(self.contentView);
make.size.mas_equalTo(CGSizeMake(184, 184));
}];
// 添加 MP4 视图
[self.contentView addSubview:self.mp4View];
[self.mp4View mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.imageView);
}];
}
- (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];
// 更彻底地重置 mp4View
[self resetMP4View];
// 隐藏视图
self.mp4View.hidden = YES;
self.imageView.hidden = YES;
// 重置状态
self.mp4Path = nil;
self.imagePath = nil;
self.isVisible = YES;
self.displayModel = nil; // 重置数据模型
// 清空图片
self.imageView.image = nil;
self.imageView.imageUrl = @"";
}
- (void)updateCell:(MedalVo *)model {
self.displayModel = model;
[self updateDisplayWithCurrentModel];
}
- (void)updateDisplayWithCurrentModel {
if (!self.displayModel) {
[self showDefaultPlaceholder];
return;
}
// 优化后的判断逻辑:更严格的 MP4 URL 验证
NSString *mp4Url = self.displayModel.mp4Url;
NSString *picUrl = self.displayModel.picUrl;
// 首先检查是否有明确的 MP4 URL
if (![NSString isEmpty:mp4Url] && [self isValidMP4URL:mp4Url]) {
[self setMp4Path:mp4Url];
return;
}
// 检查 picUrl 是否实际上是 MP4兼容旧逻辑
if (![NSString isEmpty:picUrl]) {
if ([self isValidMP4URL:picUrl]) {
[self setMp4Path:picUrl];
return;
} else if ([NSString isValidImageURL:picUrl]) {
[self setImagePath:picUrl];
return;
}
}
// 都不满足,显示默认占位图
[self showDefaultPlaceholder];
}
#pragma mark - 私有方法
/// 验证是否为有效的 MP4 URL
- (BOOL)isValidMP4URL:(NSString *)url {
if ([NSString isEmpty:url]) {
return NO;
}
// 检查 URL 是否以 mp4 结尾或包含 mp4 标识
NSString *lowercaseUrl = [url lowercaseString];
return [lowercaseUrl hasSuffix:@".mp4"] ||
[lowercaseUrl containsString:@"mp4"] ||
[lowercaseUrl containsString:@"video"];
}
/// 显示默认占位图
- (void)showDefaultPlaceholder {
[self stopMP4Playback];
self.mp4View.hidden = YES;
self.imageView.hidden = NO;
// 设置默认占位图,如果没有可以使用透明图片或者空白
self.imageView.imageUrl = @"";
self.imageView.image = [UIImageConstant defaultEmptyPlaceholder];
}
- (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 {
if ([NSString isEmpty:mp4Path]) {
self.mp4View.hidden = YES;
[self handleMP4FailureWithFallback];
return;
}
// 如果是相同的 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]) {
// 延迟播放机制:先标记为准备就绪,等待明确的播放信号
self.mp4View.tag = 1; // 标记已准备好播放
// 如果当前 Cell 可见,立即播放
if (self.isVisible) {
[self startMP4PlaybackWithURL:videoUrl];
}
}
} failureBlock:^(NSError * _Nullable error) {
@kStrongify(self);
NSLog(@"[MedalsCyclePagerCell] Failed to parse mp4: %@, fallback to image", error);
// MP4 解析失败,降级使用 picURL
[self handleMP4FailureWithFallback];
}];
}
/// 开始 MP4 播放
- (void)startMP4PlaybackWithURL:(NSString *)videoUrl {
if (![NSString isEmpty:videoUrl] && self.mp4View && !self.mp4View.hidden) {
[self.mp4View playHWDMP4:videoUrl repeatCount:-1 delegate:self];
NSLog(@"[MedalsCyclePagerCell] Started MP4 playback: %@", videoUrl);
}
}
/// 处理 MP4 播放失败,降级使用 picURL
- (void)handleMP4FailureWithFallback {
if (![NSString isEmpty:self.displayModel.picUrl]) {
if ([NSString isValidImageURL:self.displayModel.picUrl]) {
[self setImagePath:self.displayModel.picUrl];
} else {
[self showDefaultPlaceholder];
}
} else {
[self showDefaultPlaceholder];
}
}
#pragma mark - MP4 播放控制
/// 重置 MP4 视图,清理所有缓存内容
- (void)resetMP4View {
if (self.mp4View) {
// 方案1: 基础重置(推荐)
[self.mp4View stopHWDMP4];
self.mp4View.tag = 0;
self.mp4View.hidden = YES;
// 方案2: 完全重置(如果遇到严重的缓存问题才启用)
if (NO) { // 根据需要设置为 NO 来禁用完全重置
[self.mp4View removeFromSuperview];
_mp4View = nil;
// 重新添加 mp4View
[self.contentView addSubview:self.mp4View];
[self.mp4View mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(self.imageView);
}];
}
// 方案3: 强制清理内存(备用方案)
// 通过设置新的frame来触发内部清理
if (NO) { // 可以启用此方案作为替代
CGRect currentFrame = self.mp4View.frame;
self.mp4View.frame = CGRectZero;
self.mp4View.frame = currentFrame;
}
}
}
- (void)stopMP4Playback {
if (self.mp4View) {
[self.mp4View stopHWDMP4];
self.mp4View.tag = 0; // 重置播放状态标记
}
}
#pragma mark - 可见性管理
- (void)willDisplay {
self.isVisible = YES;
// 重新开始播放,而不是恢复暂停的播放
if (!self.mp4View.hidden && self.mp4Path) {
// 彻底停止当前播放
[self stopMP4Playback];
// 重新开始播放
if (!_mp4Parser) {
self.mp4Parser = [[XPRoomGiftAnimationParser alloc] init];
}
@kWeakify(self);
[self.mp4Parser parseWithURL:self.mp4Path
completionBlock:^(NSString * _Nullable videoUrl) {
@kStrongify(self);
if (![NSString isEmpty:videoUrl] && self.isVisible) {
[self startMP4PlaybackWithURL:videoUrl];
}
} failureBlock:^(NSError * _Nullable error) {
@kStrongify(self);
NSLog(@"[MedalsCyclePagerCell] Failed to restart mp4 in willDisplay: %@", error);
[self handleMP4FailureWithFallback];
}];
}
NSLog(@"[MedalsCyclePagerCell] willDisplay - isVisible: %@, mp4Path: %@",
self.isVisible ? @"YES" : @"NO", self.mp4Path ?: @"nil");
}
- (void)didEndDisplaying {
self.isVisible = NO;
// 彻底停止播放,而不是暂停
[self stopMP4Playback];
}
#pragma mark - 通知处理
- (void)appDidEnterBackground {
[self stopMP4Playback];
}
- (void)appWillEnterForeground {
if (self.isVisible && !self.mp4View.hidden && self.mp4Path) {
// 重新开始播放
if (!_mp4Parser) {
self.mp4Parser = [[XPRoomGiftAnimationParser alloc] init];
}
@kWeakify(self);
[self.mp4Parser parseWithURL:self.mp4Path
completionBlock:^(NSString * _Nullable videoUrl) {
@kStrongify(self);
if (![NSString isEmpty:videoUrl] && self.isVisible) {
[self startMP4PlaybackWithURL:videoUrl];
}
} failureBlock:^(NSError * _Nullable error) {
@kStrongify(self);
[self handleMP4FailureWithFallback];
}];
}
}
- (void)didReceiveMemoryWarning {
// 内存警告时停止播放
if (!self.isVisible) {
[self stopMP4Playback];
}
}
#pragma mark - 生命周期
- (void)dealloc {
// 停止播放
[self stopMP4Playback];
// 移除通知观察者
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 清理资源
self.mp4Parser = nil;
NSLog(@"MedalsCyclePagerCell dealloc");
}
#pragma mark - Lazy load
- (VAPView *)mp4View {
if (!_mp4View) {
_mp4View = [[VAPView alloc] init];
_mp4View.contentMode = UIViewContentModeScaleAspectFit;
// [_mp4View enableOldVersion:YES];
}
return _mp4View;
}
#pragma mark - HWDMP4PlayDelegate
- (BOOL)shouldStartPlayMP4:(VAPView *)container config:(QGVAPConfigModel *)config {
return YES;
}
- (void)viewDidFinishPlayMP4:(NSInteger)totalFrameCount view:(VAPView *)container {
// MP4 播放完成,循环播放会自动重新开始
}
- (void)viewDidStopPlayMP4:(NSInteger)lastFrameIndex view:(VAPView *)container {
// MP4 播放停止
}
- (void)viewDidFailPlayMP4:(NSError *)error {
NSLog(@"MP4 播放失败: %@", error);
// MP4 播放失败,降级使用 picURL
[self handleMP4FailureWithFallback];
}
@end