flutter视频播放器video_player_avfoundation之FVPVideoPlayer(二)

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],
    };
}
相关推荐
文心快码BaiduComate2 小时前
一人即团队,SubAgent引爆开发者新范式
前端·后端·程序员
掘金一周2 小时前
2025年还有前端不会Nodejs ?| 掘金一周 9.25
android·前端·后端
Sailing2 小时前
前端拖拽,看似简单,其实处处是坑
前端·javascript·面试
RoyLin2 小时前
前端·后端·node.js
RoyLin3 小时前
C++ 基础与核心概念
前端·后端·node.js
记得坚持3 小时前
vue2插槽
前端·vue.js
臣臣臣臣臣什么臣3 小时前
uni-app 多文件上传:直接循环调用 uni.uploadFile 实现(并行 / 串行双模式)
android·前端
带只拖鞋去流浪3 小时前
Vue.js响应式API
前端·javascript·vue.js
Coder_R3 小时前
如何 把 Mac 上的 APK(app) 安装到安卓手机上?
前端·面试