// // MedalMediaDisplayManager.m // YuMi // // Created by AI on 2025/1/20. // #import "MedalMediaDisplayManager.h" #import #import "XPRoomGiftAnimationParser.h" #import "NSString+XPExtension.h" #import "UIImageConstant.h" @interface MedalMediaDisplayManager () @property (nonatomic, copy, readwrite, nullable) NSString *currentImagePath; @property (nonatomic, copy, readwrite, nullable) NSString *currentMP4Path; @property (nonatomic, strong) XPRoomGiftAnimationParser *mp4Parser; @property (nonatomic, assign) NSInteger mp4PlaybackTag; // 播放状态标记 @end @implementation MedalMediaDisplayManager #pragma mark - 初始化 - (instancetype)initWithDelegate:(id)delegate { self = [super init]; if (self) { _delegate = delegate; _isVisible = YES; _mp4PlaybackTag = 0; [self setupNotifications]; } return self; } - (void)dealloc { [self cleanupResources]; [[NSNotificationCenter defaultCenter] removeObserver:self]; NSLog(@"MedalMediaDisplayManager dealloc"); } #pragma mark - 公共接口 - (void)updateDisplayWithModel:(id)model { self.currentModel = model; [self handleMediaDisplayWithModel:model]; } - (void)willDisplay { self.isVisible = YES; [self resumeMP4PlaybackIfNeeded]; } - (void)didEndDisplaying { self.isVisible = NO; [self pauseMP4Playback]; } - (void)stopMP4Playback { VAPView *mp4View = [self.delegate getMP4View]; if (mp4View) { [mp4View stopHWDMP4]; self.mp4PlaybackTag = 0; } } - (void)pauseMP4Playback { VAPView *mp4View = [self.delegate getMP4View]; if (mp4View && !mp4View.hidden) { [mp4View pauseHWDMP4]; } } - (void)resumeMP4Playback { [self resumeMP4PlaybackIfNeeded]; } - (void)cleanupResources { [self stopMP4Playback]; self.mp4Parser = nil; self.currentImagePath = nil; self.currentMP4Path = nil; self.currentModel = nil; } #pragma mark - 核心媒体处理逻辑 - (void)handleMediaDisplayWithModel:(id)model { if (!model || !self.delegate) { [self showDefaultPlaceholder]; return; } NSString *mp4Url = [self.delegate getMP4UrlFromModel:model]; NSString *picUrl = [self.delegate getPicUrlFromModel:model]; // 优先判断 MP4 URL if (![NSString isEmpty:mp4Url] && [self isValidMP4URL:mp4Url]) { [self setMp4Path:mp4Url]; return; } // MP4 URL 无效,检查图片 URL if (![NSString isEmpty:picUrl]) { if ([self isValidMP4URL:picUrl]) { // picUrl 实际上是 MP4(兼容旧逻辑) [self setMp4Path:picUrl]; return; } else if ([NSString isValidImageURL:picUrl]) { // 有效的图片 URL [self setImagePath:picUrl]; return; } } // 都无效,显示默认占位图 [self showDefaultPlaceholder]; } - (void)setImagePath:(NSString *)imagePath { [self stopMP4Playback]; self.currentImagePath = imagePath; self.currentMP4Path = nil; VAPView *mp4View = [self.delegate getMP4View]; NetImageView *imageView = [self.delegate getImageView]; mp4View.hidden = YES; imageView.hidden = NO; imageView.imageUrl = imagePath ?: @""; [self notifyDisplayUpdated:NO success:![NSString isEmpty:imagePath]]; } - (void)setMp4Path:(NSString *)mp4Path { if ([NSString isEmpty:mp4Path]) { [self showDefaultPlaceholder]; return; } [self stopMP4Playback]; self.currentMP4Path = mp4Path; self.currentImagePath = nil; VAPView *mp4View = [self.delegate getMP4View]; NetImageView *imageView = [self.delegate getImageView]; mp4View.hidden = NO; imageView.hidden = YES; if (!self.mp4Parser) { self.mp4Parser = [[XPRoomGiftAnimationParser alloc] init]; } @kWeakify(self); [self.mp4Parser parseWithURL:mp4Path completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if (![NSString isEmpty:videoUrl]) { self.mp4PlaybackTag = 1; // 标记已准备好播放 if (self.isVisible) { [self startMP4PlaybackWithURL:videoUrl]; } } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); NSLog(@"[MedalMediaDisplayManager] Failed to parse mp4: %@", error); [self handleMP4FailureWithFallback]; }]; } - (void)startMP4PlaybackWithURL:(NSString *)videoUrl { if ([NSString isEmpty:videoUrl] || !self.delegate) { return; } VAPView *mp4View = [self.delegate getMP4View]; if (mp4View && !mp4View.hidden) { [mp4View playHWDMP4:videoUrl repeatCount:-1 delegate:self]; [self notifyDisplayUpdated:YES success:YES]; NSLog(@"[MedalMediaDisplayManager] Started MP4 playback: %@", videoUrl); } } - (void)handleMP4FailureWithFallback { if (!self.delegate || !self.currentModel) { [self showDefaultPlaceholder]; return; } // MP4 失败,尝试降级到图片 NSString *picUrl = [self.delegate getPicUrlFromModel:self.currentModel]; if (![NSString isEmpty:picUrl] && [NSString isValidImageURL:picUrl]) { [self setImagePath:picUrl]; } else { [self showDefaultPlaceholder]; } } - (void)showDefaultPlaceholder { [self stopMP4Playback]; VAPView *mp4View = [self.delegate getMP4View]; NetImageView *imageView = [self.delegate getImageView]; mp4View.hidden = YES; imageView.hidden = NO; imageView.imageUrl = @""; // 获取默认占位图 UIImage *placeholderImage = nil; if ([self.delegate respondsToSelector:@selector(getDefaultPlaceholderImage)]) { placeholderImage = [self.delegate getDefaultPlaceholderImage]; } if (!placeholderImage) { placeholderImage = [UIImageConstant defaultEmptyPlaceholder]; } imageView.image = placeholderImage; [self notifyDisplayUpdated:NO success:NO]; } - (void)resumeMP4PlaybackIfNeeded { if (!self.isVisible || [NSString isEmpty:self.currentMP4Path]) { return; } VAPView *mp4View = [self.delegate getMP4View]; if (mp4View && !mp4View.hidden) { if (self.mp4PlaybackTag == 1) { // 已准备好但尚未播放,重新解析并播放 @kWeakify(self); [self.mp4Parser parseWithURL:self.currentMP4Path completionBlock:^(NSString * _Nullable videoUrl) { @kStrongify(self); if (![NSString isEmpty:videoUrl] && self.isVisible) { [self startMP4PlaybackWithURL:videoUrl]; } } failureBlock:^(NSError * _Nullable error) { @kStrongify(self); [self handleMP4FailureWithFallback]; }]; } else { // 恢复暂停的播放 [mp4View resumeHWDMP4]; } } } #pragma mark - 辅助方法 - (BOOL)isValidMP4URL:(NSString *)url { if ([NSString isEmpty:url]) { return NO; } NSString *lowercaseUrl = [url lowercaseString]; return [lowercaseUrl hasSuffix:@".mp4"] || [lowercaseUrl containsString:@"mp4"] || [lowercaseUrl containsString:@"video"]; } - (void)notifyDisplayUpdated:(BOOL)isMP4 success:(BOOL)success { if ([self.delegate respondsToSelector:@selector(onMediaDisplayUpdated:success:)]) { [self.delegate onMediaDisplayUpdated:isMP4 success:success]; } } #pragma mark - 通知处理 - (void)setupNotifications { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppDidEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAppWillEnterForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } - (void)handleAppDidEnterBackground { [self pauseMP4Playback]; } - (void)handleAppWillEnterForeground { if (self.isVisible) { [self resumeMP4PlaybackIfNeeded]; } } - (void)handleMemoryWarning { if (!self.isVisible) { [self stopMP4Playback]; } } #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(@"[MedalMediaDisplayManager] MP4 播放失败: %@", error); [self handleMP4FailureWithFallback]; } @end