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

365 lines
10 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];
// 隐藏 mp4 视图
self.mp4View.hidden = YES;
self.imageView.hidden = NO;
// 重置状态
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 {
// 如果是相同的 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 播放控制
- (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 startMP4PlaybackWithURL:videoUrl];
}
} failureBlock:^(NSError * _Nullable error) {
@kStrongify(self);
// MP4 恢复播放失败,降级使用 picURL
[self handleMP4FailureWithFallback];
}];
} else {
[self.mp4View resumeHWDMP4];
}
}
}
#pragma mark - 可见性管理
- (void)willDisplay {
self.isVisible = YES;
// 如果有准备好的 MP4立即开始播放
if (self.mp4View.tag == 1 && !self.mp4View.hidden && self.mp4Path) {
[self resumeMP4Playback];
} else if (!self.mp4View.hidden) {
// 恢复之前暂停的播放
[self resumeMP4Playback];
}
NSLog(@"[MedalsCyclePagerCell] willDisplay - isVisible: %@, mp4Path: %@",
self.isVisible ? @"YES" : @"NO", self.mp4Path ?: @"nil");
}
- (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(@"MedalsCyclePagerCell dealloc");
}
#pragma mark - Lazy load
- (VAPView *)mp4View {
if (!_mp4View) {
_mp4View = [[VAPView alloc] init];
_mp4View.contentMode = UIViewContentModeScaleAspectFit;
#if DEBUG
_mp4View.backgroundColor = [UIColor redColor];
#endif
}
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