flutter视频播放器video_player_avfoundation之FVPVideoPlayerPlugin(一)

FVPVideoPlayerPlugin

less 复制代码
@interface FVPVideoPlayerPlugin ()
@property(readonly, strong, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;
@property(nonatomic, strong) id<FVPDisplayLinkFactory> displayLinkFactory;
@property(nonatomic, strong) id<FVPAVFactory> avFactory;
@property(nonatomic, strong) NSObject<FVPViewProvider> *viewProvider;
@property(nonatomic, assign) int64_t nextPlayerIdentifier;
@end
  • registrar:Flutter 的 插件注册器,负责和 Flutter Engine 通信(比如注册 channel、view factory 等)。
  • displayLinkFactory:封装 CADisplayLink,主要用于 定时刷新视频帧
  • avFactory:封装 AVPlayer 的工厂,创建播放器实例。
  • viewProvider:封装 FlutterPlatformView 的提供者,用于生成原生 video view。
  • nextPlayerIdentifier:自增的播放器 ID,用于标识不同的播放器实例。

initWithRegistrar

swift 复制代码
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
  return [self initWithAVFactory:[[FVPDefaultAVFactory alloc] init]
              displayLinkFactory:[[FVPDefaultDisplayLinkFactory alloc] init]
                    viewProvider:[[FVPDefaultViewProvider alloc] initWithRegistrar:registrar]
                       registrar:registrar];
}
ini 复制代码
- (instancetype)initWithAVFactory:(id<FVPAVFactory>)avFactory
               displayLinkFactory:(id<FVPDisplayLinkFactory>)displayLinkFactory
                     viewProvider:(NSObject<FVPViewProvider> *)viewProvider
                        registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
  self = [super init];
  NSAssert(self, @"super init cannot be nil");
  _registrar = registrar;
  _viewProvider = viewProvider;
  _displayLinkFactory = displayLinkFactory ?: [[FVPDefaultDisplayLinkFactory alloc] init];
  _avFactory = avFactory ?: [[FVPDefaultAVFactory alloc] init];
  _viewProvider = viewProvider ?: [[FVPDefaultViewProvider alloc] initWithRegistrar:registrar];
  _playersByIdentifier = [NSMutableDictionary dictionaryWithCapacity:1];
  _nextPlayerIdentifier = 1;
  return self;
}

要点:

  • 提供依赖注入的能力(可替换工厂),同时有默认实现。
  • 初始化 _playersByIdentifier(存放所有播放器实例的字典)。
  • _nextPlayerIdentifier 从 1 开始,自增管理。

SetUpFVPAVFoundationVideoPlayerApi

markdown 复制代码
/**
 * 【入口】设置AVFoundation视频播放器API
 * 
 * 此函数是设置AVFoundation视频播放器API的主入口点:
 * 1. 作为便捷方法,使用空字符串作为默认消息通道后缀
 * 2. 内部调用SetUpFVPAVFoundationVideoPlayerApiWithSuffix进行实际配置
 * 3. 用于初始化全局的视频播放器API通信通道
 * 4. 建立Flutter与原生iOS/macOS视频播放器之间的基础通信桥梁
 * 
 * @param binaryMessenger Flutter二进制消息传递器,用于与Flutter引擎通信
 * @param api 实现FVPAVFoundationVideoPlayerApi协议的对象,处理具体的视频播放逻辑
 */
void SetUpFVPAVFoundationVideoPlayerApi(id<FlutterBinaryMessenger> binaryMessenger,
                                        NSObject<FVPAVFoundationVideoPlayerApi> *api) {
  SetUpFVPAVFoundationVideoPlayerApiWithSuffix(binaryMessenger, api, @"");
}

SetUpFVPAVFoundationVideoPlayerApiWithSuffix

markdown 复制代码
* 【重要】设置AVFoundation视频播放器API与指定后缀
* 
* 此函数负责配置Flutter与原生iOS/macOS视频播放器之间的通信通道。
* 它为每个API方法创建消息通道,并设置相应的消息处理器。

initialize

less 复制代码
{
  FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
         initWithName:[NSString stringWithFormat:@"%@%@",                                                 @"dev.flutter.pigeon.video_player_avfoundation."                                                 @"AVFoundationVideoPlayerApi.initialize",                                                 messageChannelSuffix]
      binaryMessenger:binaryMessenger
                codec:FVPGetMessagesCodec()];
  if (api) {
    NSCAssert([api respondsToSelector:@selector(initialize:)],
              @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(initialize:)",
              api);
    [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {      FlutterError *error;      [api initialize:&error];
      NSLog(@"🔴 🔧 Initialize通道处理器设置完成,错误状态: %@", error ? error.message : @"无错误");
      callback(wrapResult(nil, error));
    }];
    NSLog(@"🔴 ✅ Initialize消息通道已成功配置");
  } else {
    [channel setMessageHandler:nil];
    NSLog(@"🔴 ⚠️ Initialize消息通道处理器已清空(API为空)");
  }
}

初始化方法, flutter端调用initialize()方法后,iOS端会执行 [api initialize:&error];方法,完成视频播放器的初始化。

initialize

objectivec 复制代码
- (void)initialize:(FlutterError *__autoreleasing *)error {
#if TARGET_OS_IOS
  // Allow audio playback when the Ring/Silent switch is set to silent
  upgradeAudioSessionCategory(AVAudioSessionCategoryPlayback, 0, 0);
  NSLog(@"🔊 Initialize: iOS音频会话已配置为播放模式");
#endif

  FlutterError *disposeError;
  // Disposing a player removes it from the dictionary, so iterate over a copy.
  NSArray<FVPVideoPlayer *> *players = [self.playersByIdentifier.allValues copy];
  NSLog(@"🧹 Initialize: 开始清理现有播放器,当前播放器数量: %lu", (unsigned long)players.count);
  
  for (FVPVideoPlayer *player in players) {
    [player disposeWithError:&disposeError];
    if (disposeError) {
      NSLog(@"⚠️ Initialize: 播放器清理过程中出现错误: %@", disposeError.message);
    }
  }
  [self.playersByIdentifier removeAllObjects];
  NSLog(@"✅ Initialize: 所有播放器已清理完成,播放器字典已重置");
}

🟢 【核心】初始化视频播放器插件

此方法负责初始化视频播放器插件的核心功能:

  1. 在iOS平台上配置音频会话,允许在静音模式下播放音频
  2. 清理所有现有的视频播放器实例,释放相关资源
  3. 确保插件处于干净的初始状态

error 错误指针,用于返回初始化过程中可能出现的错误

createTexturePlayerWithOptions

less 复制代码
{
  FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
         initWithName:[NSString                          stringWithFormat:@"%@%@",                                           @"dev.flutter.pigeon.video_player_avfoundation."                                           @"AVFoundationVideoPlayerApi.createForTextureView",                                           messageChannelSuffix]
      binaryMessenger:binaryMessenger
                codec:FVPGetMessagesCodec()];
  if (api) {
    NSCAssert([api respondsToSelector:@selector(createTexturePlayerWithOptions:error:)],
              @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to "
              @"@selector(createTexturePlayerWithOptions:error:)",
              api);
    [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {      NSArray<id> *args = message;      FVPCreationOptions *arg_creationOptions = GetNullableObjectAtIndex(args, 0);      FlutterError *error;      FVPTexturePlayerIds *output = [api createTexturePlayerWithOptions:arg_creationOptions                                                                  error:&error];
      NSLog(@"🎨 CreateTexturePlayer通道处理器执行完成,播放器ID: %@,纹理ID: %@,错误状态: %@",
            output ? @(output.playerId) : @"无", output ? @(output.textureId) : @"无", error ? error.message : @"无错误");
      callback(wrapResult(output, error));
    }];
    NSLog(@"🔴 ✅ CreateTexturePlayer消息通道已成功配置");
  } else {
    [channel setMessageHandler:nil];
    NSLog(@"⚠️ CreateTexturePlayer消息通道处理器已清空(API为空)");
  }
}

创建纹理播放通道处理器。

flutter初始化成功后,会立即执行createForTextureView方法。iOS端的纹理播放通道处理器收到消息后,会立即执行createTexturePlayerWithOptions方法。

createTexturePlayerWithOptions

ini 复制代码
- (nullable FVPTexturePlayerIds *)createTexturePlayerWithOptions:
                                      (nonnull FVPCreationOptions *)options
                                                           error:(FlutterError **)error {
    NSLog(@"🎬 createTexturePlayerWithOptions, 创建纹理播放器,URL: %@", options.uri);

    @try {
    AVPlayerItem *item = [self playerItemWithCreationOptions:options];
    NSLog(@"🎬 CreateTexturePlayer: AVPlayerItem创建成功,URL: %@", options.uri);

    FVPFrameUpdater *frameUpdater =
        [[FVPFrameUpdater alloc] initWithRegistry:self.registrar.textures];
    NSLog(@"🖼️ CreateTexturePlayer: 帧更新器创建成功");

    NSObject<FVPDisplayLink> *displayLink =
        [self.displayLinkFactory displayLinkWithRegistrar:_registrar
                                                 callback:^() {
                                                   [frameUpdater displayLinkFired];
                                                 }];
    NSLog(@"🔗 CreateTexturePlayer: 显示链接创建成功");

    FVPTextureBasedVideoPlayer *player =
        [[FVPTextureBasedVideoPlayer alloc] initWithPlayerItem:item
                                                  frameUpdater:frameUpdater
                                                   displayLink:displayLink
                                                     avFactory:self.avFactory
                                                  viewProvider:self.viewProvider];
    NSLog(@"🎮 CreateTexturePlayer: FVPTextureBasedVideoPlayer实例创建成功");

    int64_t textureIdentifier = [self.registrar.textures registerTexture:player];
    [player setTextureIdentifier:textureIdentifier];
    NSLog(@"🖼️ CreateTexturePlayer: 纹理注册成功,纹理ID: %lld", textureIdentifier);

    __weak typeof(self) weakSelf = self;
    int64_t playerIdentifier = [self configurePlayer:player
                             withExtraDisposeHandler:^() {
                               [weakSelf.registrar.textures unregisterTexture:textureIdentifier];
                               NSLog(@"🗑️ CreateTexturePlayer: 纹理ID %lld 已注销", textureIdentifier);
                             }];

    FVPTexturePlayerIds *result = [FVPTexturePlayerIds makeWithPlayerId:playerIdentifier textureId:textureIdentifier];
    NSLog(@"✅ CreateTexturePlayer: 纹理播放器创建完成,播放器ID: %lld,纹理ID: %lld", playerIdentifier, textureIdentifier);
    return result;
  } @catch (NSException *exception) {
    NSLog(@"❌ CreateTexturePlayer: 创建失败,异常: %@", exception.reason);
    *error = [FlutterError errorWithCode:@"video_player" message:exception.reason details:nil];
    return nil;
  }
}
  • 🟡 【核心】创建纹理播放器
  • 此方法用于创建基于纹理的视频播放器实例:
    1. 根据创建选项构建AVPlayerItem
    1. 初始化帧更新器和显示链接,用于纹理渲染
    1. 创建FVPTextureBasedVideoPlayer实例,包含纹理渲染所需的所有逻辑
    1. 注册纹理并配置播放器,返回播放器ID和纹理ID
    1. 处理创建过程中可能出现的异常
  • @param options 视频播放器创建选项,包含视频URL、HTTP头等配置
  • @param error 错误指针,用于返回创建过程中可能出现的错误
  • @return 包含播放器ID和纹理ID的对象,创建失败时返回nil

playerItemWithCreationOptions

objectivec 复制代码
- (nonnull AVPlayerItem *)playerItemWithCreationOptions:(nonnull FVPCreationOptions *)options {
  NSDictionary<NSString *, NSString *> *headers = options.httpHeaders;
  NSLog(@"🔵 PlayerItemCreation: 开始创建AVPlayerItem,URI: %@", options.uri);

  NSDictionary<NSString *, id> *itemOptions =
      headers.count == 0 ? nil : @{@"AVURLAssetHTTPHeaderFieldsKey" : headers};

  if (headers.count > 0) {
    NSLog(@"🔵 PlayerItemCreation: HTTP头信息配置完成,头数量: %lu", (unsigned long)headers.count);
  }

  AVURLAsset *asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:options.uri]
                                          options:itemOptions];
  NSLog(@"🔵 PlayerItemCreation: AVURLAsset创建成功");

  AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
  NSLog(@"🔵 PlayerItemCreation: AVPlayerItem创建完成并返回");

  return playerItem;
}

🔵 【核心】根据创建选项构建AVPlayerItem

此方法是视频播放器的核心工厂方法,负责创建AVPlayerItem实例:

  1. 提取HTTP头信息并构建资源选项
  2. 创建AVURLAsset,支持网络、本地文件等多种资源类型
  3. 基于资源创建AVPlayerItem,作为播放器的媒体源
  4. 返回配置完成的播放器项目实例

@param options 创建选项,包含视频URI和HTTP头信息 @return 配置完成的AVPlayerItem实例,可直接用于播放器

configurePlayer

配置并返回播放器ID

ini 复制代码
- (int64_t)configurePlayer:(FVPVideoPlayer *)player
    withExtraDisposeHandler:(nullable void (^)(void))extraDisposeHandler {
  int64_t playerIdentifier = self.nextPlayerIdentifier++;
  self.playersByIdentifier[@(playerIdentifier)] = player;
  NSLog(@"🆔 ConfigurePlayer: 播放器已分配ID: %lld,当前播放器总数: %lu",
        playerIdentifier, (unsigned long)self.playersByIdentifier.count);

  NSObject<FlutterBinaryMessenger> *messenger = self.registrar.messenger;
  NSString *channelSuffix = [NSString stringWithFormat:@"%lld", playerIdentifier];
  // Set up the player-specific API handler, and its onDispose unregistration.
  SetUpFVPVideoPlayerInstanceApiWithSuffix(messenger, player, channelSuffix);
  NSLog(@"🔗 ConfigurePlayer: API处理器已设置,通道后缀: %@", channelSuffix);

  __weak typeof(self) weakSelf = self;
  player.onDisposed = ^() {
    SetUpFVPVideoPlayerInstanceApiWithSuffix(messenger, nil, channelSuffix);
    if (extraDisposeHandler) {
      extraDisposeHandler();
    }
    [weakSelf.playersByIdentifier removeObjectForKey:@(playerIdentifier)];
    NSLog(@"🗑️ ConfigurePlayer: 播放器ID %lld 已销毁并清理", playerIdentifier);
  };
  NSLog(@"🧹 ConfigurePlayer: 销毁回调已配置");

  // Set up the event channel.
  FVPEventBridge *eventBridge = [[FVPEventBridge alloc]
      initWithMessenger:messenger
            channelName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%@",
                                                   channelSuffix]];
  player.eventListener = eventBridge;
  NSLog(@"📡 ConfigurePlayer: 事件通道已建立,通道名: flutter.io/videoPlayer/videoEvents%@", channelSuffix);

  NSLog(@"✅ ConfigurePlayer: 播放器配置完成,返回ID: %lld", playerIdentifier);
  return playerIdentifier;
}

🟢 【核心】配置视频播放器实例

此方法负责配置新创建的视频播放器实例的完整功能:

  1. 为播放器分配唯一标识符并存储到字典中
  2. 设置播放器特定的API处理器,建立Flutter与原生的通信桥梁
  3. 配置播放器销毁时的清理逻辑,包括取消注册API处理器和执行额外的清理回调
  4. 建立事件通道,用于向Flutter端发送播放器状态变化事件
  5. 返回播放器的唯一标识符供Flutter端使用

@param player 要配置的视频播放器实例 @param extraDisposeHandler 可选的额外销毁处理回调,在播放器销毁时执行 @return 播放器的唯一标识符

相关推荐
Ting_橘子2 小时前
Ajax&Json
前端·ajax·json
2501_916013743 小时前
Web 抓包全指南 Web抓包工具、浏览器抓包方法、HTTPS 解密
前端·网络协议·ios·小程序·https·uni-app·iphone
海涛高软3 小时前
QT 两种库写法 LIBS += .a和LIBS += -L -l
前端·javascript·qt
好学且牛逼的马3 小时前
GO实战项目:流量统计系统完整实现(Go+XORM+MySQL + 前端)
前端·mysql·golang
Beginner x_u3 小时前
Vue 3 项目实战教程大事件管理系统 (一):从零开始搭建项目基础
前端·javascript·vue.js·pinia
aesthetician3 小时前
ahooks:一套高质量、可靠的 React Hooks 库
前端·react.js·前端框架
shizhenshide3 小时前
如何在同一站点支持多版本的 reCAPTCHA 的兼容性方案
服务器·前端·网络·安全·captcha·ezcaptcha
CodeCraft Studio3 小时前
借助Aspose.HTML控件,使用 Python 编程创建 HTML 页面
前端·python·html·aspose·python创建html·html sdk
杨超越luckly4 小时前
HTML应用指南:利用GET请求获取全国奥迪授权经销商门店位置信息
大数据·前端·python·html·数据可视化·门店数据