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],
    };
}
相关推荐
2013编程爱好者31 分钟前
Vue工程结构分析
前端·javascript·vue.js·typescript·前端框架
小满zs1 小时前
Next.js第十一章(渲染基础概念)
前端
不羁的fang少年2 小时前
前端常见问题(vue,css,html,js等)
前端·javascript·css
change_fate3 小时前
el-menu折叠后文字下移
前端·javascript·vue.js
yivifu3 小时前
CSS Grid 布局详解(2025最新标准)
前端·css
o***Z4484 小时前
前端性能优化案例
前端
张拭心4 小时前
前端没有实际的必要了?结合今年工作内容,谈谈我的看法
前端·ai编程
姜太小白4 小时前
【前端】CSS媒体查询响应式设计详解:@media (max-width: 600px) {……}
前端·css·媒体
HIT_Weston5 小时前
39、【Ubuntu】【远程开发】拉出内网 Web 服务:构建静态网页(二)
linux·前端·ubuntu
百***06015 小时前
SpringMVC 请求参数接收
前端·javascript·算法