markdown
/**
* FVPVideoPlayer.m
*
* 视频播放器核心实现类
*
* 功能概述:
* - 基于AVFoundation框架实现的视频播放器
* - 支持本地和网络视频播放
* - 提供完整的播放控制功能(播放、暂停、跳转、音量、播放速度等)
* - 支持视频旋转和变换处理
* - 实现KVO观察者模式监听播放状态变化
* - 提供事件回调机制与Flutter层通信
*
* 主要职责:
* 1. 视频播放器生命周期管理
* 2. 播放状态监控和事件分发
* 3. 视频输出和渲染配置
* 4. 播放参数控制(音量、速度、循环等)
* 5. 错误处理和异常管理
*
* 核心组件:
* - AVPlayer: 底层播放器实例
* - AVPlayerItem: 播放项目和媒体资源
* - AVPlayerItemVideoOutput: 视频输出配置
* - FVPVideoEventListener: 事件监听器接口
*
*/
FVPVideoPlayer
objectivec
- (instancetype)initWithPlayerItem:(AVPlayerItem *)item
avFactory:(id <FVPAVFactory>)avFactory
viewProvider:(NSObject <FVPViewProvider> *)viewProvider {
self = [super init];
NSAssert(self, @"super init cannot be nil");
_viewProvider = viewProvider;
AVAsset *asset = [item asset];
void (^assetCompletionHandler)(void) = ^{
NSLog(@"🩷 FVPVideoPlayer: 开始处理资源轨道信息");
if ([asset statusOfValueForKey:@"tracks" error:nil] == AVKeyValueStatusLoaded) {
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if ([tracks count] > 0) {
NSLog(@"🩷 FVPVideoPlayer: 发现视频轨道,开始处理变换信息");
AVAssetTrack *videoTrack = tracks[0];
void (^trackCompletionHandler)(void) = ^{
if (self->_disposed) return;
if ([videoTrack statusOfValueForKey:@"preferredTransform"
error:nil] == AVKeyValueStatusLoaded) {
NSLog(@"🩷 FVPVideoPlayer: 应用视频变换矩阵");
// Rotate the video by using a videoComposition and the preferredTransform
self->_preferredTransform = FVPGetStandardizedTransformForTrack(videoTrack);
// Do not use video composition when it is not needed.
if (CGAffineTransformIsIdentity(self->_preferredTransform)) {
NSLog(@"🩷 FVPVideoPlayer: 视频无需变换,使用原始方向");
return;
}
NSLog(@"🩷 FVPVideoPlayer: 创建视频合成配置");
// Note:
// https://developer.apple.com/documentation/avfoundation/avplayeritem/1388818-videocomposition
// Video composition can only be used with file-based media and is not supported for
// use with media served using HTTP Live Streaming.
AVMutableVideoComposition *videoComposition =
[self getVideoCompositionWithTransform:self->_preferredTransform
withAsset:asset
withVideoTrack:videoTrack];
item.videoComposition = videoComposition;
NSLog(@"🩷 FVPVideoPlayer: 视频合成配置应用完成");
}
};
[videoTrack loadValuesAsynchronouslyForKeys:@[@"preferredTransform"]
completionHandler:trackCompletionHandler];
}
}
};
NSLog(@"🩷 FVPVideoPlayer: 创建AVPlayer实例");
_player = [avFactory playerWithPlayerItem:item];
_player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
NSLog(@"🩷 FVPVideoPlayer: 配置视频输出格式");
// Configure output.
NSDictionary *pixBuffAttributes = @{
(id) kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
(id) kCVPixelBufferIOSurfacePropertiesKey: @{}
};
_videoOutput = [avFactory videoOutputWithPixelBufferAttributes:pixBuffAttributes];
NSLog(@"🩷 FVPVideoPlayer: 开始异步加载资源轨道");
[asset loadValuesAsynchronouslyForKeys:@[@"tracks"] completionHandler:assetCompletionHandler];
return self;
}
视频初始化完成后, 会立即调用setEventListener
方法设置事件监听器
。
setEventListener
objectivec
- (void)setEventListener:(NSObject <FVPVideoEventListener> *)eventListener {
_eventListener = eventListener;
// The first time an event listener is set, set up video event listeners to relay status changes
// 第一次设置事件监听器时,设置视频事件监听器以中继状态变化
// changes to the event listener.
// 变化到事件监听器。
if (eventListener && !_listenersRegistered) {
AVPlayerItem *item = self.player.currentItem;
// If the item is already ready to play, ensure that the intialized event is sent first.
// 如果项目已经准备好播放,确保首先发送初始化事件。
[self reportStatusForPlayerItem:item];
// Set up all necessary observers to report video events.
// 设置所有必要的观察者以报告视频事件。
NSLog(@"🩷 FVPVideoPlayer: 注册播放项KVO观察者");
FVPRegisterKeyValueObservers(self, FVPGetPlayerItemObservations(), item);
NSLog(@"🩷 FVPVideoPlayer: 注册播放器KVO观察者");
FVPRegisterKeyValueObservers(self, FVPGetPlayerObservations(), _player);
NSLog(@"🩷 FVPVideoPlayer: 注册播放结束通知监听器");
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(itemDidPlayToEndTime:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:item];
_listenersRegistered = YES;
}
}
这里首先会调用reportStatusForPlayerItem
方法如果项目已经准备好播放,确保首先发送初始化事件
。
然后添加注册播放项KVO观察者
、注册播放器KVO观察者
、注册播放结束通知监听器
。
reportStatusForPlayerItem
objectivec
- (void)reportStatusForPlayerItem:(AVPlayerItem *)item {
NSLog(@"🩷 FVPVideoPlayer: 播放项状态: %ld", (long) item.status);
switch (item.status) {
case AVPlayerItemStatusFailed:
NSLog(@"🩷 FVPVideoPlayer: 播放项状态为失败,发送加载失败事件");
[self sendFailedToLoadVideoEvent];
break;
case AVPlayerItemStatusUnknown:
NSLog(@"🩷 FVPVideoPlayer: 播放项状态未知,等待状态更新");
break;
case AVPlayerItemStatusReadyToPlay:
[item addOutput:_videoOutput];
NSLog(@"🩷 FVPVideoPlayer: 视频输出已添加,检查初始化状态");
[self reportInitializedIfReadyToPlay];
break;
}
}
markdown
* 报告播放项状态
*
* @param item 播放项对象
*
* 功能说明:
* 根据播放项的状态执行相应的处理逻辑
* - Failed: 发送加载失败事件
* - Unknown: 暂不处理,等待状态更新
* - ReadyToPlay: 添加视频输出并检查是否可以报告初始化完成
FVPGetPlayerItemObservations
objectivec
/// Returns a mapping of KVO keys to NSValue-wrapped observer context pointers for observations that
/// 返回KVO键到NSValue包装的观察者上下文指针的映射,用于应该为AVPlayerItem实例设置的观察。
/// should be set for AVPlayerItem instances.
/// 应该为AVPlayerItem实例设置的观察。
static NSDictionary<NSString *, NSValue *> *FVPGetPlayerItemObservations(void) {
return @{
@"loadedTimeRanges": [NSValue valueWithPointer:timeRangeContext],
@"status": [NSValue valueWithPointer:statusContext],
@"presentationSize": [NSValue valueWithPointer:presentationSizeContext],
@"duration": [NSValue valueWithPointer:durationContext],
@"playbackLikelyToKeepUp": [NSValue valueWithPointer:playbackLikelyToKeepUpContext],
};
}
FVPGetPlayerObservations
objectivec
/// Returns a mapping of KVO keys to NSValue-wrapped observer context pointers for observations that
/// 返回KVO键到NSValue包装的观察者上下文指针的映射,用于应该为AVPlayer实例设置的观察。
/// should be set for AVPlayer instances.
/// 应该为AVPlayer实例设置的观察。
static NSDictionary<NSString *, NSValue *> *FVPGetPlayerObservations(void) {
return @{
@"rate": [NSValue valueWithPointer:rateContext],
};
}