flutter视频播放器video_player_avfoundation之AVFoundationVideoPlayer(三)

184 阅读9分钟

flutter端的AVFoundationVideoPlayer是实现PlatformInterface接口的插件。它就是flutter端跟原生端通信的桥梁。

AVFoundationVideoPlayer

/// iOS平台的视频播放器实现,使用Pigeon生成的VideoPlayerApi
class AVFoundationVideoPlayer extends VideoPlayerPlatform {
  /// 创建一个新的基于AVFoundation的视频播放器实现实例
  AVFoundationVideoPlayer({
    @visibleForTesting AVFoundationVideoPlayerApi? pluginApi,
    @visibleForTesting VideoPlayerInstanceApi Function(int playerId)? playerProvider,
  }) : _api = pluginApi ?? AVFoundationVideoPlayerApi(),
       _playerProvider = playerProvider ?? _productionApiProvider;

  final AVFoundationVideoPlayerApi _api;

  // 用于创建VideoPlayerInstanceApi实例的方法,可以在测试中被重写
  final VideoPlayerInstanceApi Function(int mapId) _playerProvider;

  /// 将播放器ID与视图状态关联的映射表
  /// 用于确定构建视图时使用哪种视图类型
  @visibleForTesting
  final Map<int, VideoPlayerViewState> playerViewStates = <int, VideoPlayerViewState>{};

  final Map<int, VideoPlayerInstanceApi> _players = <int, VideoPlayerInstanceApi>{};

  /// 将此类注册为VideoPlayerPlatform的默认实例
  static void registerWith() {
    VideoPlayerPlatform.instance = AVFoundationVideoPlayer();
  }

  @override
  Future<void> init() {
    return _api.initialize();
  }

  @override
  Future<void> dispose(int playerId) async {
    final VideoPlayerInstanceApi? player = _players.remove(playerId);
    await player?.dispose();
    playerViewStates.remove(playerId);
  }

  @override
  Future<int?> create(DataSource dataSource) async {
    return createWithOptions(
      VideoCreationOptions(
        dataSource: dataSource,
        // 在引入createWithOptions之前,纹理视图是唯一支持的视图类型
        viewType: VideoViewType.textureView,
      ),
    );
  }

  @override
  Future<int?> createWithOptions(VideoCreationOptions options) async {
    final DataSource dataSource = options.dataSource;
    final VideoViewType viewType = options.viewType;

    String? uri;
    switch (dataSource.sourceType) {
      case DataSourceType.asset:
        final String? asset = dataSource.asset;
        if (asset == null) {
          throw ArgumentError('"asset"对于资源数据源必须非空');
        }
        uri = await _api.getAssetUrl(asset, dataSource.package);
        if (uri == null) {
          // 为了与之前的实现兼容,抛出平台异常
          throw PlatformException(code: 'video_player', message: '在包${dataSource.package}中找不到资源$asset');
        }
      case DataSourceType.network:
      case DataSourceType.file:
      case DataSourceType.contentUri:
        uri = dataSource.uri;
    }
    if (uri == null) {
      throw ArgumentError('无法从$options构造视频资源');
    }
    final CreationOptions pigeonCreationOptions = CreationOptions(uri: uri, httpHeaders: dataSource.httpHeaders);

    final int playerId;
    final VideoPlayerViewState state;
    switch (viewType) {
      case VideoViewType.textureView:
        final TexturePlayerIds ids = await _api.createForTextureView(pigeonCreationOptions);
        playerId = ids.playerId;
        state = VideoPlayerTextureViewState(textureId: ids.textureId);
      case VideoViewType.platformView:
        playerId = await _api.createForPlatformView(pigeonCreationOptions);
        state = const VideoPlayerPlatformViewState();
    }
    playerViewStates[playerId] = state;
    ensureApiInitialized(playerId);

    return playerId;
  }

  /// 返回playerId对应的API实例,如果不存在则创建它
  @visibleForTesting
  VideoPlayerInstanceApi ensureApiInitialized(int playerId) {
    return _players.putIfAbsent(playerId, () {
      return _playerProvider(playerId);
    });
  }

  @override
  Future<void> setLooping(int playerId, bool looping) {
    return _playerWith(id: playerId).setLooping(looping);
  }

  @override
  Future<void> play(int playerId) {
    return _playerWith(id: playerId).play();
  }

  @override
  Future<void> pause(int playerId) {
    return _playerWith(id: playerId).pause();
  }

  @override
  Future<void> setVolume(int playerId, double volume) {
    return _playerWith(id: playerId).setVolume(volume);
  }

  @override
  Future<void> setPlaybackSpeed(int playerId, double speed) {
    assert(speed > 0);
    return _playerWith(id: playerId).setPlaybackSpeed(speed);
  }

  @override
  Future<void> seekTo(int playerId, Duration position) {
    return _playerWith(id: playerId).seekTo(position.inMilliseconds);
  }

  @override
  Future<Duration> getPosition(int playerId) async {
    final int position = await _playerWith(id: playerId).getPosition();
    return Duration(milliseconds: position);
  }

  @override
  Stream<VideoEvent> videoEventsFor(int playerId) {
    debugPrint('---✅dart启动视频播放器流监听videoEventsFor, playerId: $playerId✅---');
    return _eventChannelFor(playerId).receiveBroadcastStream().map((dynamic event) {
      final Map<dynamic, dynamic> map = event as Map<dynamic, dynamic>;
      debugPrint('---✅dart视频播放器流监听videoEventsFor结果, map: $map✅---');
      return switch (map['event']) {
        'initialized' => VideoEvent(
          eventType: VideoEventType.initialized,
          duration: Duration(milliseconds: map['duration'] as int),
          size: Size((map['width'] as num?)?.toDouble() ?? 0.0, (map['height'] as num?)?.toDouble() ?? 0.0),
        ),
        'completed' => VideoEvent(eventType: VideoEventType.completed),
        'bufferingUpdate' => VideoEvent(
          buffered: (map['values'] as List<dynamic>).map<DurationRange>(_toDurationRange).toList(),
          eventType: VideoEventType.bufferingUpdate,
        ),
        'bufferingStart' => VideoEvent(eventType: VideoEventType.bufferingStart),
        'bufferingEnd' => VideoEvent(eventType: VideoEventType.bufferingEnd),
        'isPlayingStateUpdate' => VideoEvent(eventType: VideoEventType.isPlayingStateUpdate, isPlaying: map['isPlaying'] as bool),
        _ => VideoEvent(eventType: VideoEventType.unknown),
      };
    });
  }

  @override
  Future<void> setMixWithOthers(bool mixWithOthers) {
    return _api.setMixWithOthers(mixWithOthers);
  }

  @override
  Widget buildView(int playerId) {
    return buildViewWithOptions(VideoViewOptions(playerId: playerId));
  }

  @override
  Widget buildViewWithOptions(VideoViewOptions options) {
    final int playerId = options.playerId;
    final VideoPlayerViewState? viewState = playerViewStates[playerId];

    return switch (viewState) {
      VideoPlayerTextureViewState(:final int textureId) => Texture(textureId: textureId),
      VideoPlayerPlatformViewState() => _buildPlatformView(playerId),
      null => throw Exception('找不到playerId对应的视图类型: $playerId'),
    };
  }

  Widget _buildPlatformView(int playerId) {
    final PlatformVideoViewCreationParams creationParams = PlatformVideoViewCreationParams(playerId: playerId);

    return IgnorePointer(
      // 使用IgnorePointer以便在平台视图上方使用GestureDetector
      child: UiKitView(
        viewType: 'plugins.flutter.dev/video_player_ios',
        creationParams: creationParams,
        creationParamsCodec: AVFoundationVideoPlayerApi.pigeonChannelCodec,
      ),
    );
  }

  EventChannel _eventChannelFor(int playerId) {
    return EventChannel('flutter.io/videoPlayer/videoEvents$playerId');
  }

  VideoPlayerInstanceApi _playerWith({required int id}) {
    final VideoPlayerInstanceApi? player = _players[id];
    return player ?? (throw StateError('没有ID为$id的活跃播放器'));
  }

  DurationRange _toDurationRange(dynamic value) {
    final List<dynamic> pair = value as List<dynamic>;
    final int startMilliseconds = pair[0] as int;
    final int durationMilliseconds = pair[1] as int;
    return DurationRange(
      Duration(milliseconds: startMilliseconds),
      Duration(milliseconds: startMilliseconds + durationMilliseconds),
    );
  }
}

AVFoundationVideoPlayerApi


/**
 * AVFoundation视频播放器API
 * 
 * 这是一个由Pigeon工具自动生成的类,用于Flutter与原生iOS/macOS平台之间的通信。
 * 该类提供了视频播放器的核心平台级别操作接口。
 * 
 * 主要功能:
 * 1. 播放器初始化和生命周期管理
 * 2. 创建不同渲染模式的播放器(平台视图/纹理视图)
 * 3. 音频会话配置管理
 * 4. Flutter资源文件路径解析
 * 
 * 通信机制:
 * - 使用BasicMessageChannel进行跨平台消息传递
 * - 支持异步操作和错误处理
 * - 自动处理数据序列化和反序列化
 */
class AVFoundationVideoPlayerApi {
  /// AVFoundationVideoPlayerApi构造函数
  /// 
  /// [binaryMessenger] 二进制消息传递器参数,用于依赖注入。
  /// 如果为null,将使用默认的BinaryMessenger路由到宿主平台。
  /// 
  /// [messageChannelSuffix] 消息通道后缀,用于区分不同的API实例
  AVFoundationVideoPlayerApi({
    BinaryMessenger? binaryMessenger,
    String messageChannelSuffix = '',
  }) : pigeonVar_binaryMessenger = binaryMessenger,
       pigeonVar_messageChannelSuffix =
           messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
           
  /// 二进制消息传递器实例,用于与原生平台通信
  final BinaryMessenger? pigeonVar_binaryMessenger;

  /// Pigeon消息编解码器,处理数据类型转换
  static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();

  /// 消息通道后缀,用于标识特定的API实例
  final String pigeonVar_messageChannelSuffix;

  /**
   * 初始化AVFoundation视频播放器插件
   * 
   * 这是使用播放器之前必须调用的方法,用于:
   * - 设置AVFoundation框架的基础配置
   * - 初始化音频会话
   * - 准备播放器工厂和相关组件
   * 
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当初始化失败时抛出平台异常
   */
  Future<void> initialize() async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 创建使用平台视图渲染的视频播放器
   * 
   * 平台视图模式特点:
   * - 使用原生UIView/NSView进行渲染
   * - 更好的兼容性,支持所有视频格式和特性
   * - 可能存在性能开销和层级问题
   * - 适用于需要完整原生功能的场景
   * 
   * @param params 创建选项,包含视频URI和HTTP头信息
   * @return Future<int> 返回创建的播放器ID
   * @throws PlatformException 当创建失败时抛出异常
   */
  Future<int> createForPlatformView(CreationOptions params) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[params],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      return (pigeonVar_replyList[0] as int?)!;
    }
  }

  /**
   * 创建使用纹理渲染的视频播放器
   * 
   * 纹理模式特点:
   * - 使用Flutter的Texture Widget进行渲染
   * - 更好的性能表现和Flutter集成
   * - 支持Flutter的变换、动画等效果
   * - 可能在某些特殊视频格式上有限制
   * - 推荐用于大多数常规播放场景
   * 
   * @param creationOptions 创建选项,包含视频URI和HTTP头信息
   * @return Future<TexturePlayerIds> 返回包含播放器ID和纹理ID的对象
   * @throws PlatformException 当创建失败时抛出异常
   */
  Future<TexturePlayerIds> createForTextureView(
    CreationOptions creationOptions,
  ) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[creationOptions],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      return (pigeonVar_replyList[0] as TexturePlayerIds?)!;
    }
  }

  /**
   * 设置音频混合模式
   * 
   * 控制视频播放器的音频是否与其他应用的音频混合播放:
   * - true: 允许与其他音频混合(如音乐应用、通话等)
   * - false: 独占音频输出,会暂停其他应用的音频
   * 
   * 使用场景:
   * - 背景音乐播放:设置为true,允许与系统音频混合
   * - 视频通话、游戏:设置为false,获得独占音频控制
   * 
   * @param mixWithOthers 是否允许与其他音频混合
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当设置失败时抛出异常
   */
  Future<void> setMixWithOthers(bool mixWithOthers) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[mixWithOthers],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 获取Flutter资源文件的本地URL路径
   * 
   * 将Flutter项目中的资源文件路径转换为原生平台可访问的本地文件URL。
   * 这对于播放打包在应用内的视频文件非常重要。
   * 
   * 资源路径解析过程:
   * 1. 根据资源名称在Flutter资源清单中查找
   * 2. 考虑包名(如果指定)进行路径解析
   * 3. 返回原生平台可直接访问的file:// URL
   * 
   * @param asset 资源文件名称(如 "videos/sample.mp4")
   * @param package 可选的包名,用于访问其他包中的资源
   * @return Future<String?> 返回本地文件URL,如果资源不存在则返回null
   * @throws PlatformException 当路径解析失败时抛出异常
   */
  Future<String?> getAssetUrl(String asset, String? package) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[asset, package],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return (pigeonVar_replyList[0] as String?);
    }
  }
}

VideoPlayerInstanceApi


/**
 * 视频播放器实例API
 * 
 * 这是一个由Pigeon工具自动生成的类,用于控制单个视频播放器实例的播放行为。
 * 该类提供了视频播放器的核心控制功能,每个播放器实例都有一个对应的API实例。
 * 
 * 主要功能:
 * 1. 播放控制(播放、暂停、跳转)
 * 2. 播放参数设置(音量、播放速度、循环模式)
 * 3. 播放状态查询(当前播放位置)
 * 4. 资源管理(释放播放器资源)
 * 
 * 使用方式:
 * - 通过AVFoundationVideoPlayerApi创建播放器后获得实例
 * - 使用messageChannelSuffix区分不同的播放器实例
 * - 所有操作都是异步的,返回Future对象
 */
class VideoPlayerInstanceApi {
  /// VideoPlayerInstanceApi构造函数
  /// 
  /// [binaryMessenger] 二进制消息传递器参数,用于依赖注入。
  /// 如果为null,将使用默认的BinaryMessenger路由到宿主平台。
  /// 
  /// [messageChannelSuffix] 消息通道后缀,用于标识特定的播放器实例
  /// 通常使用播放器ID作为后缀,确保每个播放器实例有独立的通信通道
  VideoPlayerInstanceApi({
    BinaryMessenger? binaryMessenger,
    String messageChannelSuffix = '',
  }) : pigeonVar_binaryMessenger = binaryMessenger,
       pigeonVar_messageChannelSuffix =
           messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
           
  /// 二进制消息传递器实例,用于与原生平台通信
  final BinaryMessenger? pigeonVar_binaryMessenger;

  /// Pigeon消息编解码器,处理数据类型转换
  static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();

  /// 消息通道后缀,用于标识特定的播放器实例
  final String pigeonVar_messageChannelSuffix;

  /**
   * 设置视频循环播放模式
   * 
   * 控制视频播放完成后的行为:
   * - true: 视频播放完成后自动重新开始播放,形成无限循环
   * - false: 视频播放完成后停止,不会自动重播
   * 
   * 使用场景:
   * - 背景视频、装饰性视频:通常设置为true,持续循环播放
   * - 教学视频、宣传片:通常设置为false,播放完成后停止
   * - 短视频应用:可根据用户设置或内容类型动态调整
   * 
   * @param looping 是否启用循环播放
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当设置失败时抛出异常
   */
  Future<void> setLooping(bool looping) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setLooping$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[looping],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 设置视频播放音量
   * 
   * 调整当前播放器实例的音频输出音量大小。
   * 这个设置只影响当前播放器,不会改变系统全局音量。
   * 
   * 音量范围和效果:
   * - 0.0: 完全静音,无音频输出
   * - 1.0: 最大音量,使用视频原始音频级别
   * - 0.0-1.0之间: 按比例调整音量大小
   * - 超出范围的值会被自动限制在有效范围内
   * 
   * 使用场景:
   * - 多媒体应用中的音量控制滑块
   * - 背景视频的音量调节
   * - 音频淡入淡出效果实现
   * 
   * @param volume 音量值,范围0.0-1.0
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当设置失败时抛出异常
   */
  Future<void> setVolume(double volume) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setVolume$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[volume],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 设置视频播放速度
   * 
   * 调整视频播放的速率,可以实现快进、慢放、倒放等效果。
   * 播放速度的改变会同时影响视频和音频的播放速率。
   * 
   * 速度值说明:
   * - 1.0: 正常播放速度(默认值)
   * - 0.5: 半速播放(慢放)
   * - 2.0: 双倍速播放(快进)
   * - 0.0: 暂停播放
   * - 负值: 倒放(部分平台支持)
   * 
   * 常用速度设置:
   * - 0.25x, 0.5x, 0.75x: 慢速播放,适用于学习、分析场景
   * - 1.25x, 1.5x, 2.0x: 快速播放,适用于快速浏览
   * - 变速播放在在线教育、体育分析等场景中非常有用
   * 
   * @param speed 播放速度倍率
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当设置失败时抛出异常
   */
  Future<void> setPlaybackSpeed(double speed) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[speed],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 开始播放视频
   * 
   * 启动或恢复视频播放。如果视频处于暂停状态,将从当前位置继续播放;
   * 如果视频尚未开始,将从头开始播放。
   * 
   * 播放行为说明:
   * - 首次调用:从视频开头开始播放
   * - 暂停后调用:从暂停位置继续播放
   * - 播放完成后调用:根据循环设置决定是否重新播放
   * - 可以在视频加载过程中调用,播放器会在准备就绪后自动开始
   * 
   * 注意事项:
   * - 某些平台可能需要用户交互才能开始播放(自动播放策略)
   * - 网络视频需要缓冲时间,可能不会立即开始播放
   * - 播放状态变化会通过事件流通知Flutter层
   * 
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当播放启动失败时抛出异常
   */
  Future<void> play() async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.play$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 获取当前播放位置
   * 
   * 返回视频当前的播放进度,以毫秒为单位。
   * 这个值表示从视频开始到当前播放点的时间长度。
   * 
   * 返回值说明:
   * - 返回值为毫秒数(int类型)
   * - 视频开始位置为0
   * - 播放过程中该值会持续增加
   * - 跳转后会立即反映新的位置
   * 
   * 使用场景:
   * - 实现播放进度条显示
   * - 保存播放断点,支持续播功能
   * - 实现播放时间显示(需要转换为时分秒格式)
   * - 同步多个播放器的播放进度
   * 
   * 注意事项:
   * - 频繁调用可能影响性能,建议配合定时器适度查询
   * - 网络视频的位置可能不够精确,存在缓冲影响
   * 
   * @return Future<int> 当前播放位置(毫秒)
   * @throws PlatformException 当获取位置失败时抛出异常
   */
  Future<int> getPosition() async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getPosition$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      return (pigeonVar_replyList[0] as int?)!;
    }
  }

  /**
   * 跳转到指定播放位置
   * 
   * 将视频播放位置跳转到指定的时间点。这是一个异步操作,
   * 跳转完成后播放器会从新位置继续播放或保持暂停状态。
   * 
   * 参数说明:
   * - position: 目标位置,以毫秒为单位
   * - 可以跳转到视频的任意有效位置
   * - 超出视频长度的值会被限制到视频末尾
   * - 负值会被限制到视频开头(0位置)
   * 
   * 跳转行为:
   * - 播放中跳转:会在新位置继续播放
   * - 暂停中跳转:会跳转到新位置但保持暂停状态
   * - 网络视频跳转:可能需要重新缓冲
   * 
   * 使用场景:
   * - 进度条拖拽跳转
   * - 章节跳转功能
   * - 续播功能(跳转到上次播放位置)
   * - 精确定位到特定时间点
   * 
   * @param position 目标播放位置(毫秒)
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当跳转失败时抛出异常
   */
  Future<void> seekTo(int position) async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(
      <Object?>[position],
    );
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 暂停视频播放
   * 
   * 暂停当前正在播放的视频,保持在当前播放位置。
   * 暂停后可以通过调用play()方法从当前位置恢复播放。
   * 
   * 暂停行为说明:
   * - 立即停止视频和音频播放
   * - 保持当前播放位置不变
   * - 可以多次调用,不会产生副作用
   * - 如果视频已经暂停,调用此方法无效果
   * 
   * 使用场景:
   * - 用户主动暂停播放
   * - 应用进入后台时自动暂停
   * - 接收到电话等中断事件时暂停
   * - 实现播放/暂停切换功能
   * 
   * 与其他操作的关系:
   * - 暂停后仍可以调用seekTo()跳转位置
   * - 暂停后可以调用setVolume()等设置方法
   * - 播放状态变化会通过事件流通知Flutter层
   * 
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当暂停操作失败时抛出异常
   */
  Future<void> pause() async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }

  /**
   * 释放播放器资源
   * 
   * 彻底释放当前播放器实例占用的所有系统资源,包括内存、解码器、
   * 网络连接等。调用此方法后,播放器实例将不再可用。
   * 
   * 释放操作包括:
   * - 停止视频播放和音频输出
   * - 释放视频解码器和音频解码器
   * - 关闭网络连接和文件句柄
   * - 清理缓存和临时数据
   * - 注销事件监听器和KVO观察者
   * 
   * 调用时机:
   * - 播放器不再需要时(如页面销毁)
   * - 应用内存不足时主动释放
   * - 切换到新的视频源之前
   * - 应用进入后台长时间不使用时
   * 
   * 重要注意事项:
   * - 调用dispose()后不能再使用此播放器实例
   * - 必须在适当的时机调用,避免内存泄漏
   * - Flutter Widget销毁时应确保调用此方法
   * - 这是一个不可逆操作,无法恢复播放器状态
   * 
   * @return Future<void> 异步操作完成标识
   * @throws PlatformException 当资源释放失败时抛出异常
   */
  Future<void> dispose() async {
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.dispose$pigeonVar_messageChannelSuffix';
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
          pigeonVar_channelName,
          pigeonChannelCodec,
          binaryMessenger: pigeonVar_binaryMessenger,
        );
    final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_sendFuture as List<Object?>?;
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else {
      return;
    }
  }
}

MiniController

VideoPlayerPlatform? _cachedPlatform;

VideoPlayerPlatform get _platform {
  if (_cachedPlatform == null) {
    _cachedPlatform = VideoPlayerPlatform.instance;
    // 立即执行初始化
    _cachedPlatform!.init();
  }
  return _cachedPlatform!;
}

/**
 * 视频播放器状态值类
 * 
 * 这是一个不可变的数据类,用于存储和传递视频播放器的所有状态信息。
 * 包含了播放器的持续时间、当前位置、缓冲状态、错误状态和播放设置等。
 * 
 * 主要功能:
 * 1. 存储视频播放器的完整状态信息
 * 2. 提供状态查询方法(如错误检查、宽高比计算)
 * 3. 支持状态复制和更新(copyWith方法)
 * 4. 实现相等性比较和哈希计算
 * 
 * 使用场景:
 * - MiniController 使用此类来管理播放器状态
 * - UI 组件通过此类获取播放器状态信息
 * - 状态变化时创建新实例来通知监听者
 */
/// The duration, current position, buffering state, error state and settings
/// of a [MiniController].
@immutable
class VideoPlayerValue {
  /**
   * 构造一个视频播放器状态值
   * 
   * 创建 VideoPlayerValue 实例,只有 duration 是必需的参数。
   * 其他参数如果未设置,将使用默认值进行初始化。
   * 
   * @param duration 视频总时长(必需参数)
   * @param size 视频尺寸,默认为 Size.zero
   * @param position 当前播放位置,默认为 Duration.zero
   * @param buffered 已缓冲的时间范围列表,默认为空列表
   * @param isInitialized 是否已初始化,默认为 false
   * @param isPlaying 是否正在播放,默认为 false
   * @param isBuffering 是否正在缓冲,默认为 false
   * @param playbackSpeed 播放速度,默认为 1.0
   * @param errorDescription 错误描述,默认为 null
   */
  /// Constructs a video with the given values. Only [duration] is required. The
  /// rest will initialize with default values when unset.
  const VideoPlayerValue({
    required this.duration,
    this.size = Size.zero,
    this.position = Duration.zero,
    this.buffered = const <DurationRange>[],
    this.isInitialized = false,
    this.isPlaying = false,
    this.isBuffering = false,
    this.playbackSpeed = 1.0,
    this.errorDescription,
  });

  /**
   * 创建一个未初始化的视频播放器状态值
   * 
   * 返回一个表示视频尚未加载的实例。
   * duration 为 Duration.zero,isInitialized 为 false。
   * 
   * 使用场景:
   * - 播放器创建时的初始状态
   * - 重置播放器状态时使用
   */
  /// Returns an instance for a video that hasn't been loaded.
  const VideoPlayerValue.uninitialized() : this(duration: Duration.zero, isInitialized: false);

  /**
   * 创建一个包含错误信息的视频播放器状态值
   * 
   * 返回一个包含指定错误描述的实例。
   * duration 为 Duration.zero,isInitialized 为 false。
   * 
   * @param errorDescription 错误描述信息
   * 
   * 使用场景:
   * - 视频加载失败时创建错误状态
   * - 播放过程中发生错误时更新状态
   */
  /// Returns an instance with the given [errorDescription].
  const VideoPlayerValue.erroneous(String errorDescription)
    : this(duration: Duration.zero, isInitialized: false, errorDescription: errorDescription);

  /**
   * 视频总时长
   * 
   * 表示视频文件的完整播放时长。
   * 如果视频尚未初始化,该值为 Duration.zero。
   * 
   * 使用场景:
   * - 显示视频总时长信息
   * - 计算播放进度百分比
   * - 验证跳转位置是否有效
   */
  /// The total duration of the video.
  ///
  /// The duration is [Duration.zero] if the video hasn't been initialized.
  final Duration duration;

  /**
   * 当前播放位置
   * 
   * 表示视频当前的播放进度位置。
   * 从视频开始(Duration.zero)到当前播放点的时间长度。
   * 
   * 使用场景:
   * - 显示当前播放时间
   * - 更新进度条位置
   * - 实现续播功能
   */
  /// The current playback position.
  final Duration position;

  /**
   * 当前已缓冲的时间范围
   * 
   * 包含所有已缓冲的视频片段的时间范围列表。
   * 每个 DurationRange 表示一个连续的已缓冲区间。
   * 
   * 使用场景:
   * - 显示缓冲进度条
   * - 判断是否可以流畅播放到某个位置
   * - 优化网络视频的播放体验
   */
  /// The currently buffered ranges.
  final List<DurationRange> buffered;

  /**
   * 视频是否正在播放
   * 
   * true 表示视频正在播放,false 表示视频已暂停。
   * 这个状态反映了用户的播放意图,而不是实际的播放状态。
   * 
   * 使用场景:
   * - 控制播放/暂停按钮的显示状态
   * - 决定是否显示播放控制覆盖层
   * - 管理播放器的生命周期
   */
  /// True if the video is playing. False if it's paused.
  final bool isPlaying;

  /**
   * 视频是否正在缓冲
   * 
   * true 表示播放器正在缓冲数据,通常会显示加载指示器。
   * false 表示缓冲完成,可以正常播放。
   * 
   * 使用场景:
   * - 显示/隐藏缓冲加载指示器
   * - 提供用户反馈,说明播放暂停的原因
   * - 统计缓冲时间和频率
   */
  /// True if the video is currently buffering.
  final bool isBuffering;

  /**
   * 当前播放速度
   * 
   * 表示视频播放的速率倍数。
   * 1.0 为正常速度,0.5 为半速,2.0 为双倍速。
   * 
   * 使用场景:
   * - 实现变速播放功能
   * - 显示当前播放速度
   * - 快进/慢放控制
   */
  /// The current speed of the playback.
  final double playbackSpeed;

  /**
   * 错误描述信息
   * 
   * 如果播放器发生错误,此字段包含错误的详细描述。
   * 如果 hasError 为 false,此字段为 null。
   * 
   * 使用场景:
   * - 向用户显示错误信息
   * - 日志记录和错误分析
   * - 错误恢复处理
   */
  /// A description of the error if present.
  ///
  /// If [hasError] is false this is `null`.
  final String? errorDescription;

  /**
   * 当前加载视频的尺寸
   * 
   * 表示视频的原始宽度和高度。
   * 只有在视频初始化完成后才会有有效值。
   * 
   * 使用场景:
   * - 计算视频显示的宽高比
   * - 调整播放器容器尺寸
   * - 实现全屏播放功能
   */
  /// The [size] of the currently loaded video.
  final Size size;

  /**
   * 视频是否已初始化完成
   * 
   * true 表示视频已加载完成,可以开始播放。
   * false 表示视频尚未加载或加载失败。
   * 
   * 使用场景:
   * - 判断是否可以开始播放操作
   * - 控制 UI 组件的显示状态
   * - 决定是否显示视频信息
   */
  /// Indicates whether or not the video has been loaded and is ready to play.
  final bool isInitialized;

  /**
   * 是否存在错误
   * 
   * 返回播放器是否处于错误状态。
   * 如果此值为 true,errorDescription 应该包含错误信息。
   * 
   * @return true 如果存在错误,false 如果正常
   * 
   * 使用场景:
   * - 快速检查播放器状态
   * - 条件渲染错误提示
   * - 错误状态处理逻辑
   */
  /// Indicates whether or not the video is in an error state. If this is true
  /// [errorDescription] should have information about the problem.
  bool get hasError => errorDescription != null;

  /**
   * 获取视频宽高比
   * 
   * 返回视频的宽高比(宽度/高度)。
   * 
   * 返回值说明:
   * - 如果 isInitialized 为 false,返回 1.0
   * - 如果 size.width 或 size.height 等于 0.0,返回 1.0  
   * - 如果计算出的宽高比小于等于 0.0,返回 1.0
   * - 否则返回实际的宽高比值
   * 
   * 使用场景:
   * - 调整视频显示容器的尺寸
   * - 保持视频原始比例显示
   * - 实现响应式视频播放器布局
   */
  /// Returns [size.width] / [size.height].
  ///
  /// Will return `1.0` if:
  /// * [isInitialized] is `false`
  /// * [size.width], or [size.height] is equal to `0.0`
  /// * aspect ratio would be less than or equal to `0.0`
  double get aspectRatio {
    if (!isInitialized || size.width == 0 || size.height == 0) {
      return 1.0;
    }
    final double aspectRatio = size.width / size.height;
    if (aspectRatio <= 0) {
      return 1.0;
    }
    return aspectRatio;
  }

  /**
   * 复制并更新状态值
   * 
   * 返回一个新的实例,该实例具有与当前实例相同的值,
   * 但会使用传入的参数覆盖对应的字段。
   * 
   * 这是不可变对象的标准更新模式,确保状态变化的可追踪性。
   * 
   * @param duration 新的视频时长
   * @param size 新的视频尺寸
   * @param position 新的播放位置
   * @param buffered 新的缓冲范围列表
   * @param isInitialized 新的初始化状态
   * @param isPlaying 新的播放状态
   * @param isBuffering 新的缓冲状态
   * @param playbackSpeed 新的播放速度
   * @param errorDescription 新的错误描述
   * @return 更新后的新实例
   * 
   * 使用场景:
   * - 播放器状态更新时创建新状态
   * - 响应播放事件时更新特定字段
   * - 保持状态不可变性的同时进行更新
   */
  /// Returns a new instance that has the same values as this current instance,
  /// except for any overrides passed in as arguments to [copyWidth].
  VideoPlayerValue copyWith({
    Duration? duration,
    Size? size,
    Duration? position,
    List<DurationRange>? buffered,
    bool? isInitialized,
    bool? isPlaying,
    bool? isBuffering,
    double? playbackSpeed,
    String? errorDescription,
  }) {
    return VideoPlayerValue(
      duration: duration ?? this.duration,
      size: size ?? this.size,
      position: position ?? this.position,
      buffered: buffered ?? this.buffered,
      isInitialized: isInitialized ?? this.isInitialized,
      isPlaying: isPlaying ?? this.isPlaying,
      isBuffering: isBuffering ?? this.isBuffering,
      playbackSpeed: playbackSpeed ?? this.playbackSpeed,
      errorDescription: errorDescription ?? this.errorDescription,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is VideoPlayerValue &&
          runtimeType == other.runtimeType &&
          duration == other.duration &&
          position == other.position &&
          listEquals(buffered, other.buffered) &&
          isPlaying == other.isPlaying &&
          isBuffering == other.isBuffering &&
          playbackSpeed == other.playbackSpeed &&
          errorDescription == other.errorDescription &&
          size == other.size &&
          isInitialized == other.isInitialized;

  @override
  int get hashCode =>
      Object.hash(duration, position, buffered, isPlaying, isBuffering, playbackSpeed, errorDescription, size, isInitialized);
}

/**
 * 迷你视频播放器控制器
 * 
 * 这是一个非常精简版本的 VideoPlayerController,专门用于运行示例程序,
 * 而不依赖于完整的 video_player 包。它继承自 ValueNotifier,
 * 可以通知监听者播放器状态的变化。
 * 
 * 主要功能:
 * 1. 支持多种视频源(资源文件、网络、本地文件)
 * 2. 提供基本的播放控制(播放、暂停、跳转、变速)
 * 3. 管理播放器生命周期(初始化、销毁)
 * 4. 处理播放事件和错误状态
 * 5. 定时更新播放位置信息
 * 
 * 设计特点:
 * - 轻量级实现,适合示例和测试使用
 * - 基于平台接口,与原生播放器通信
 * - 状态管理基于 ValueNotifier 模式
 * - 支持异步操作和错误处理
 */
/// A very minimal version of `VideoPlayerController` for running the example
/// without relying on `video_player`.
class MiniController extends ValueNotifier<VideoPlayerValue> {
  /**
   * 构造一个播放资源文件视频的 MiniController
   * 
   * 创建一个用于播放应用资源包中视频文件的控制器。
   * 
   * @param dataSource 资源文件名称,不能为 null
   * @param package 资源包名称,如果资源来自包则必须非空,否则为 null
   * @param viewType 视频视图类型,默认为 textureView
   * 
   * 使用场景:
   * - 播放应用内置的演示视频
   * - 播放教程或引导视频
   * - 播放应用主题相关的装饰视频
   */
  /// Constructs a [MiniController] playing a video from an asset.
  ///
  /// The name of the asset is given by the [dataSource] argument and must not be
  /// null. The [package] argument must be non-null when the asset comes from a
  /// package and null otherwise.
  MiniController.asset(this.dataSource, {this.package, this.viewType = VideoViewType.textureView})
    : dataSourceType = DataSourceType.asset,
      super(const VideoPlayerValue(duration: Duration.zero));

  /**
   * 构造一个播放网络视频的 MiniController
   * 
   * 创建一个用于播放网络视频的控制器。
   * 
   * @param dataSource 网络视频的 URL 地址
   * @param viewType 视频视图类型,默认为 textureView
   * 
   * 使用场景:
   * - 播放在线视频内容
   * - 流媒体视频播放
   * - 远程视频资源播放
   * 
   * 注意事项:
   * - 需要网络权限
   * - 可能存在缓冲延迟
   * - 需要处理网络错误
   */
  /// Constructs a [MiniController] playing a video from obtained from
  /// the network.
  MiniController.network(this.dataSource, {this.viewType = VideoViewType.textureView})
    : dataSourceType = DataSourceType.network,
      package = null,
      super(const VideoPlayerValue(duration: Duration.zero));

  /**
   * 构造一个播放本地文件视频的 MiniController
   * 
   * 创建一个用于播放本地文件系统中视频文件的控制器。
   * 
   * @param file 本地视频文件对象
   * @param viewType 视频视图类型,默认为 textureView
   * 
   * 使用场景:
   * - 播放用户录制的视频
   * - 播放下载到本地的视频文件
   * - 播放相册中的视频
   * 
   * 注意事项:
   * - 需要文件读取权限
   * - 文件路径必须有效
   * - 支持的视频格式取决于平台
   */
  /// Constructs a [MiniController] playing a video from obtained from a file.
  MiniController.file(File file, {this.viewType = VideoViewType.textureView})
    : dataSource = Uri.file(file.absolute.path).toString(),
      dataSourceType = DataSourceType.file,
      package = null,
      super(const VideoPlayerValue(duration: Duration.zero));

  /**
   * 视频文件的 URI
   * 
   * 根据 DataSourceType 的不同,这个 URI 会有不同的格式:
   * - asset: 资源文件路径
   * - network: HTTP/HTTPS URL
   * - file: file:// 协议的本地文件路径
   * - contentUri: content:// 协议的内容 URI
   */
  /// The URI to the video file. This will be in different formats depending on
  /// the [DataSourceType] of the original video.
  final String dataSource;

  /**
   * 数据源类型
   * 
   * 描述此 MiniController 构造时使用的数据源类型。
   * 决定了如何解释和处理 dataSource 字段。
   * 
   * 可能的值:
   * - DataSourceType.asset: 应用资源文件
   * - DataSourceType.network: 网络视频
   * - DataSourceType.file: 本地文件
   * - DataSourceType.contentUri: 内容 URI
   */
  /// Describes the type of data source this [MiniController]
  /// is constructed with.
  final DataSourceType dataSourceType;

  /**
   * 资源包名称
   * 
   * 仅在播放资源文件视频时设置。
   * 指定资源文件所在的包名称。
   * 
   * - 如果资源来自应用本身,则为 null
   * - 如果资源来自第三方包,则为包名称
   */
  /// Only set for [asset] videos. The package that the asset was loaded from.
  final String? package;

  /**
   * 视频视图类型
   * 
   * 指定用于显示视频的视图类型。
   * 
   * 可能的值:
   * - VideoViewType.textureView: 使用纹理视图(默认)
   * - VideoViewType.platformView: 使用平台视图
   * 
   * 不同类型的特点:
   * - textureView: 性能更好,支持变换和动画
   * - platformView: 兼容性更好,但性能稍差
   */
  /// The type of view used to display the video.
  final VideoViewType viewType;

  /// 定时器,用于定期更新播放位置
  Timer? _timer;
  
  /// 创建完成器,用于等待播放器创建完成
  Completer<void>? _creatingCompleter;
  
  /// 事件订阅,用于监听播放器事件
  StreamSubscription<dynamic>? _eventSubscription;

  /**
   * 未初始化播放器的 ID 常量
   * 
   * 表示播放器尚未初始化时的 ID 值。
   * 用于测试和状态检查。
   */
  /// The id of a player that hasn't been initialized.
  @visibleForTesting
  static const int kUninitializedPlayerId = -1;
  
  /// 播放器 ID,由平台分配的唯一标识符
  int _playerId = kUninitializedPlayerId;

  /**
   * 获取播放器 ID
   * 
   * 这个方法仅用于测试目的,不应该被依赖此插件的任何人使用。
   * 
   * @return 当前播放器的 ID
   */
  /// This is just exposed for testing. It shouldn't be used by anyone depending
  /// on the plugin.
  @visibleForTesting
  int get playerId => _playerId;

  /**
   * 初始化播放器
   * 
   * 尝试打开给定的数据源并加载视频的元数据信息。
   * 这是一个异步操作,会创建原生播放器实例并建立事件监听。
   * 
   * 初始化过程:
   * 1. 根据数据源类型创建 DataSource 描述对象
   * 2. 调用平台接口创建播放器实例
   * 3. 建立事件监听,处理播放器状态变化
   * 4. 等待初始化完成事件
   * 5. 设置默认播放参数(音量、循环等)
   * 
   * @return Future<void> 初始化完成的异步标识
   * @throws PlatformException 当初始化失败时抛出异常
   * 
   * 使用场景:
   * - 在播放视频之前必须调用
   * - 通常在 Widget 的 initState 中调用
   * - 可以在 FutureBuilder 中使用
   */
  /// Attempts to open the given [dataSource] and load metadata about the video.
  Future<void> initialize() async {
    _creatingCompleter = Completer<void>();

    late DataSource dataSourceDescription;
    switch (dataSourceType) {
      case DataSourceType.asset:
        dataSourceDescription = DataSource(sourceType: DataSourceType.asset, asset: dataSource, package: package);
      case DataSourceType.network:
        dataSourceDescription = DataSource(sourceType: DataSourceType.network, uri: dataSource);
      case DataSourceType.file:
        dataSourceDescription = DataSource(sourceType: DataSourceType.file, uri: dataSource);
      case DataSourceType.contentUri:
        dataSourceDescription = DataSource(sourceType: DataSourceType.contentUri, uri: dataSource);
    }

    final VideoCreationOptions creationOptions = VideoCreationOptions(dataSource: dataSourceDescription, viewType: viewType);

    // 跟原生通信, 调用createWithOptions方法创建视频播放器
    _playerId = (await _platform.createWithOptions(creationOptions)) ?? kUninitializedPlayerId;

    _creatingCompleter!.complete(null);

    final Completer<void> initializingCompleter = Completer<void>();

    void eventListener(VideoEvent event) {
      switch (event.eventType) {
        case VideoEventType.initialized:
          value = value.copyWith(duration: event.duration, size: event.size, isInitialized: event.duration != null);
          initializingCompleter.complete(null);
          _platform.setVolume(_playerId, 1.0);
          _platform.setLooping(_playerId, true);
          // 初始化成功后,根据用户手动设置的播放状态决定是否开始播放
          _applyPlayPause();
        case VideoEventType.completed:
          // 播放完成后,暂停
          pause().then((void pauseResult) => seekTo(value.duration));
        case VideoEventType.bufferingUpdate:
          value = value.copyWith(buffered: event.buffered);
        case VideoEventType.bufferingStart:
          value = value.copyWith(isBuffering: true);
        case VideoEventType.bufferingEnd:
          value = value.copyWith(isBuffering: false);
        case VideoEventType.isPlayingStateUpdate:
          value = value.copyWith(isPlaying: event.isPlaying);
        case VideoEventType.unknown:
          break;
      }
    }

    void errorListener(Object obj) {
      final PlatformException e = obj as PlatformException;
      value = VideoPlayerValue.erroneous(e.message!);
      _timer?.cancel();
      if (!initializingCompleter.isCompleted) {
        initializingCompleter.completeError(obj);
      }
    }

    // 启动视频播放器流监听
    _eventSubscription = _platform.videoEventsFor(_playerId).listen(eventListener, onError: errorListener);
    return initializingCompleter.future;
  }

  /**
   * 释放播放器资源
   * 
   * 清理播放器占用的所有资源,包括定时器、事件订阅和原生播放器实例。
   * 这是一个异步操作,确保所有资源都被正确释放。
   * 
   * 释放过程:
   * 1. 等待播放器创建完成(如果正在创建中)
   * 2. 取消定时器
   * 3. 取消事件订阅
   * 4. 释放原生播放器实例
   * 5. 调用父类的 dispose 方法
   * 
   * 注意事项:
   * - 必须在不再使用播放器时调用
   * - 通常在 Widget 的 dispose 方法中调用
   * - 调用后播放器实例不能再使用
   */
  @override
  Future<void> dispose() async {
    if (_creatingCompleter != null) {
      await _creatingCompleter!.future;
      _timer?.cancel();
      await _eventSubscription?.cancel();
      await _platform.dispose(_playerId);
    }
    super.dispose();
  }

  /**
   * 开始播放视频
   * 
   * 启动视频播放,如果视频处于暂停状态,将从当前位置继续播放。
   * 这个方法会更新播放状态并应用播放/暂停逻辑。
   * 
   * 播放行为:
   * - 更新内部播放状态为 true
   * - 调用原生播放器开始播放
   * - 启动定时器定期更新播放位置
   * - 应用当前的播放速度设置
   * 
   * @return Future<void> 异步操作完成标识
   * 
   * 使用场景:
   * - 用户点击播放按钮
   * - 自动播放功能
   * - 从暂停状态恢复播放
   */
  /// Starts playing the video.
  Future<void> play() async {
    value = value.copyWith(isPlaying: true);
    await _applyPlayPause();
  }

  /**
   * 暂停视频播放
   * 
   * 暂停当前正在播放的视频,保持在当前播放位置。
   * 这个方法会更新播放状态并应用播放/暂停逻辑。
   * 
   * 暂停行为:
   * - 更新内部播放状态为 false
   * - 调用原生播放器暂停播放
   * - 停止定时器,不再更新播放位置
   * 
   * @return Future<void> 异步操作完成标识
   * 
   * 使用场景:
   * - 用户点击暂停按钮
   * - 应用进入后台时自动暂停
   * - 接收到中断事件时暂停
   */
  /// Pauses the video.
  Future<void> pause() async {
    value = value.copyWith(isPlaying: false);
    await _applyPlayPause();
  }

  /**
   * 应用播放/暂停状态
   * 
   * 根据当前的播放状态,执行相应的播放或暂停操作。
   * 这是一个内部方法,用于统一处理播放状态变化。
   * 
   * 播放时的操作:
   * - 调用原生播放器开始播放
   * - 启动定时器,每500毫秒更新一次播放位置
   * - 应用当前的播放速度设置
   * 
   * 暂停时的操作:
   * - 取消定时器
   * - 调用原生播放器暂停播放
   */
  Future<void> _applyPlayPause() async {
    _timer?.cancel();
    if (value.isPlaying) {
      await _platform.play(_playerId);

      _timer = Timer.periodic(const Duration(milliseconds: 500), (Timer timer) async {
        final Duration? newPosition = await position;
        if (newPosition == null) {
          return;
        }
        _updatePosition(newPosition);
      });
      await _applyPlaybackSpeed();
    } else {
      await _platform.pause(_playerId);
    }
  }

  /**
   * 应用播放速度设置
   * 
   * 如果视频正在播放,将当前的播放速度设置应用到原生播放器。
   * 这是一个内部方法,确保播放速度设置的一致性。
   * 
   * 注意事项:
   * - 只在播放状态下应用速度设置
   * - 暂停状态下不会应用速度设置
   */
  Future<void> _applyPlaybackSpeed() async {
    if (value.isPlaying) {
      await _platform.setPlaybackSpeed(_playerId, value.playbackSpeed);
    }
  }

  /**
   * 获取当前播放位置
   * 
   * 从原生播放器获取当前的播放位置。
   * 这是一个异步操作,返回值可能为 null。
   * 
   * @return Future<Duration?> 当前播放位置,可能为 null
   * 
   * 使用场景:
   * - 定时器中更新播放位置
   * - 用户查询当前播放进度
   * - 实现播放位置同步
   */
  /// The position in the current video.
  Future<Duration?> get position async {
    return _platform.getPosition(_playerId);
  }

  /**
   * 跳转到指定播放位置
   * 
   * 将视频播放位置设置为指定的时间点。
   * 会自动处理边界情况,确保位置在有效范围内。
   * 
   * 边界处理:
   * - 如果位置超过视频总时长,设置为视频末尾
   * - 如果位置小于零,设置为视频开头
   * 
   * @param position 目标播放位置
   * @return Future<void> 异步操作完成标识
   * 
   * 使用场景:
   * - 进度条拖拽跳转
   * - 章节跳转功能
   * - 续播功能实现
   */
  /// Sets the video's current timestamp to be at [position].
  Future<void> seekTo(Duration position) async {
    if (position > value.duration) {
      position = value.duration;
    } else if (position < Duration.zero) {
      position = Duration.zero;
    }
    await _platform.seekTo(_playerId, position);
    _updatePosition(position);
  }

  /**
   * 设置播放速度
   * 
   * 调整视频的播放速率。支持快进、慢放等功能。
   * 
   * @param speed 播放速度倍率(1.0 为正常速度)
   * @return Future<void> 异步操作完成标识
   * 
   * 常用速度值:
   * - 0.5: 半速播放
   * - 1.0: 正常速度
   * - 1.5: 1.5倍速
   * - 2.0: 双倍速
   * 
   * 使用场景:
   * - 快进/慢放功能
   * - 学习模式的慢速播放
   * - 快速浏览的高速播放
   */
  /// Sets the playback speed.
  Future<void> setPlaybackSpeed(double speed) async {
    value = value.copyWith(playbackSpeed: speed);
    await _applyPlaybackSpeed();
  }

  /**
   * 更新播放位置
   * 
   * 内部方法,用于更新播放器状态中的当前位置。
   * 会触发状态变化通知,让监听者知道位置已更新。
   * 
   * @param position 新的播放位置
   */
  void _updatePosition(Duration position) {
    value = value.copyWith(position: position);
  }
}

VideoPlayer


/**
 * 视频播放器 Widget
 * 
 * 显示由控制器管理的视频内容的 Widget。
 * 这是一个有状态的 Widget,会监听控制器的状态变化并相应地更新显示。
 * 
 * 主要功能:
 * 1. 显示视频内容
 * 2. 监听播放器状态变化
 * 3. 处理播放器 ID 变化
 * 4. 管理播放器生命周期
 * 
 * 使用方式:
 * ```dart
 * VideoPlayer(controller)
 * ```
 */
/// Widget that displays the video controlled by [controller].
class VideoPlayer extends StatefulWidget {
  /**
   * 构造视频播放器 Widget
   * 
   * @param controller 负责此 Widget 中视频渲染的 MiniController
   */
  /// Uses the given [controller] for all video rendered in this widget.
  const VideoPlayer(this.controller, {super.key});

  /**
   * MiniController 实例
   * 
   * 负责在此 Widget 中渲染视频的控制器。
   * 提供视频播放的所有控制功能和状态信息。
   */
  /// The [MiniController] responsible for the video being rendered in
  /// this widget.
  final MiniController controller;

  @override
  State<VideoPlayer> createState() => _VideoPlayerState();
}

class _VideoPlayerState extends State<VideoPlayer> {
  _VideoPlayerState() {
    _listener = () {
      final int newPlayerId = widget.controller.playerId;
      if (newPlayerId != _playerId) {
        setState(() {
          _playerId = newPlayerId;
        });
      }
    };
  }

  late VoidCallback _listener;

  late int _playerId;

  @override
  void initState() {
    super.initState();
    _playerId = widget.controller.playerId;
    // Need to listen for initialization events since the actual player ID
    // becomes available after asynchronous initialization finishes.
    widget.controller.addListener(_listener);
  }

  @override
  void didUpdateWidget(VideoPlayer oldWidget) {
    super.didUpdateWidget(oldWidget);
    oldWidget.controller.removeListener(_listener);
    _playerId = widget.controller.playerId;
    widget.controller.addListener(_listener);
  }

  @override
  void deactivate() {
    super.deactivate();
    widget.controller.removeListener(_listener);
  }

  @override
  Widget build(BuildContext context) {
    return _playerId == MiniController.kUninitializedPlayerId
        ? Container()
        : _platform.buildViewWithOptions(VideoViewOptions(playerId: _playerId));
  }
}

VideoProgressIndicator


/**
 * 视频播放进度指示器
 * 
 * 显示由控制器管理的视频的播放/缓冲状态。
 * 提供可视化的进度条显示,包括播放进度和缓冲进度。
 * 
 * 主要功能:
 * 1. 显示播放进度(红色进度条)
 * 2. 显示缓冲进度(蓝色进度条)
 * 3. 支持点击跳转到指定位置
 * 4. 自动更新进度显示
 * 
 * 使用方式:
 * ```dart
 * VideoProgressIndicator(controller)
 * ```
 */
/// Displays the play/buffering status of the video controlled by [controller].
class VideoProgressIndicator extends StatefulWidget {
  /**
   * 构造视频播放进度指示器
   * 
   * 创建一个显示由控制器管理的视频播放/缓冲状态的实例。
   * 
   * @param controller 与此 Widget 关联的 MiniController
   */
  /// Construct an instance that displays the play/buffering status of the video
  /// controlled by [controller].
  const VideoProgressIndicator(this.controller, {super.key});

  /**
   * MiniController 实例
   * 
   * 实际与此 Widget 关联视频的 MiniController。
   * 提供视频状态信息和播放控制功能。
   */
  /// The [MiniController] that actually associates a video with this
  /// widget.
  final MiniController controller;

  @override
  State<VideoProgressIndicator> createState() => _VideoProgressIndicatorState();
}

class _VideoProgressIndicatorState extends State<VideoProgressIndicator> {
  _VideoProgressIndicatorState() {
    listener = () {
      if (mounted) {
        setState(() {});
      }
    };
  }

  late VoidCallback listener;

  MiniController get controller => widget.controller;

  @override
  void initState() {
    super.initState();
    controller.addListener(listener);
  }

  @override
  void deactivate() {
    controller.removeListener(listener);
    super.deactivate();
  }

  @override
  Widget build(BuildContext context) {
    const Color playedColor = Color.fromRGBO(255, 0, 0, 0.7);
    const Color bufferedColor = Color.fromRGBO(50, 50, 200, 0.2);
    const Color backgroundColor = Color.fromRGBO(200, 200, 200, 0.5);

    Widget progressIndicator;
    if (controller.value.isInitialized) {
      final int duration = controller.value.duration.inMilliseconds;
      final int position = controller.value.position.inMilliseconds;

      int maxBuffering = 0;
      for (final DurationRange range in controller.value.buffered) {
        final int end = range.end.inMilliseconds;
        if (end > maxBuffering) {
          maxBuffering = end;
        }
      }

      progressIndicator = Stack(
        fit: StackFit.passthrough,
        children: <Widget>[
          LinearProgressIndicator(
            value: maxBuffering / duration,
            valueColor: const AlwaysStoppedAnimation<Color>(bufferedColor),
            backgroundColor: backgroundColor,
          ),
          LinearProgressIndicator(
            value: position / duration,
            valueColor: const AlwaysStoppedAnimation<Color>(playedColor),
            backgroundColor: Colors.transparent,
          ),
        ],
      );
    } else {
      progressIndicator = const LinearProgressIndicator(
        valueColor: AlwaysStoppedAnimation<Color>(playedColor),
        backgroundColor: backgroundColor,
      );
    }
    return _VideoScrubber(
      controller: controller,
      child: Padding(padding: const EdgeInsets.only(top: 5.0), child: progressIndicator),
    );
  }
}

_BumbleBeeRemoteVideo

class _BumbleBeeRemoteVideo extends StatefulWidget {
  const _BumbleBeeRemoteVideo(this.viewType);

  final VideoViewType viewType;

  @override
  _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState();
}

class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> {
  late MiniController _controller;

  @override
  void initState() {
    super.initState();
    _controller = MiniController.network(
      'https://testcdn.vspo.cn/ec/test/2025-09-17/1758080659889485455.MOV',
      // 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4',
      viewType: widget.viewType,
    );

    _controller.addListener(() {
      setState(() {});
    });

    // 初始化
    _controller.initialize();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          Container(padding: const EdgeInsets.only(top: 20.0)),
          const Text('With remote mp4'),
          Container(
            padding: const EdgeInsets.all(20),
            child: AspectRatio(
              aspectRatio: _controller.value.aspectRatio,
              child: Stack(
                alignment: Alignment.bottomCenter,
                children: <Widget>[
                  VideoPlayer(_controller),
                  _ControlsOverlay(controller: _controller),
                  VideoProgressIndicator(_controller),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

_BumbleBeeEncryptedLiveStream

class _BumbleBeeEncryptedLiveStream extends StatefulWidget {
  const _BumbleBeeEncryptedLiveStream(this.viewType);

  final VideoViewType viewType;

  @override
  _BumbleBeeEncryptedLiveStreamState createState() => _BumbleBeeEncryptedLiveStreamState();
}

class _BumbleBeeEncryptedLiveStreamState extends State<_BumbleBeeEncryptedLiveStream> {
  late MiniController _controller;

  @override
  void initState() {
    super.initState();
    _controller = MiniController.network(
      'https://flutter.github.io/assets-for-api-docs/assets/videos/hls/encrypted_bee.m3u8',
      viewType: widget.viewType,
    );

    _controller.addListener(() {
      setState(() {});
    });
    _controller.initialize().then((_) {
      _controller.play();
    });
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          Container(padding: const EdgeInsets.only(top: 20.0)),
          const Text('With remote encrypted m3u8'),
          Container(
            padding: const EdgeInsets.all(20),
            child:
                _controller.value.isInitialized
                    ? AspectRatio(aspectRatio: _controller.value.aspectRatio, child: VideoPlayer(_controller))
                    : const Text('loading...'),
          ),
        ],
      ),
    );
  }
}

_ControlsOverlay

class _ControlsOverlay extends StatelessWidget {
  const _ControlsOverlay({required this.controller});

  static const List<double> _examplePlaybackRates = <double>[0.25, 0.5, 1.0, 1.5, 2.0, 3.0, 5.0, 10.0];

  final MiniController controller;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        AnimatedSwitcher(
          duration: const Duration(milliseconds: 50),
          reverseDuration: const Duration(milliseconds: 200),
          child:
              controller.value.isPlaying
                  ? const SizedBox.shrink()
                  : const ColoredBox(
                    color: Colors.black26,
                    child: Center(child: Icon(Icons.play_arrow, color: Colors.white, size: 100.0, semanticLabel: 'Play')),
                  ),
        ),
        GestureDetector(
          onTap: () {
            controller.value.isPlaying ? controller.pause() : controller.play();
          },
        ),
        Align(
          alignment: Alignment.topRight,
          child: PopupMenuButton<double>(
            initialValue: controller.value.playbackSpeed,
            tooltip: 'Playback speed',
            onSelected: (double speed) {
              controller.setPlaybackSpeed(speed);
            },
            itemBuilder: (BuildContext context) {
              return <PopupMenuItem<double>>[
                for (final double speed in _examplePlaybackRates) PopupMenuItem<double>(value: speed, child: Text('${speed}x')),
              ];
            },
            child: Padding(
              padding: const EdgeInsets.symmetric(
                // Using less vertical padding as the text is also longer
                // horizontally, so it feels like it would need more spacing
                // horizontally (matching the aspect ratio of the video).
                vertical: 12,
                horizontal: 16,
              ),
              child: Text('${controller.value.playbackSpeed}x'),
            ),
          ),
        ),
      ],
    );
  }
}

ConfigurePigeon

@ConfigurePigeon(
  PigeonOptions(
    dartOut: 'lib/src/messages.g.dart',
    objcHeaderOut:
        'darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h',
    objcSourceOut:
        'darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m',
    objcOptions: ObjcOptions(
      prefix: 'FVP',
      headerIncludePath: './include/video_player_avfoundation/messages.g.h',
    ),
    copyrightHeader: 'pigeons/copyright.txt',
  ),
)
/// Information passed to the platform view creation.
class PlatformVideoViewCreationParams {
  const PlatformVideoViewCreationParams({required this.playerId});

  final int playerId;
}

class CreationOptions {
  CreationOptions({required this.uri, required this.httpHeaders});

  String uri;
  Map<String, String> httpHeaders;
}

class TexturePlayerIds {
  TexturePlayerIds({required this.playerId, required this.textureId});

  final int playerId;
  final int textureId;
}

@HostApi()
abstract class AVFoundationVideoPlayerApi {
  @ObjCSelector('initialize')
  void initialize();
  // Creates a new player using a platform view for rendering and returns its
  // ID.
  @ObjCSelector('createPlatformViewPlayerWithOptions:')
  int createForPlatformView(CreationOptions params);
  // Creates a new player using a texture for rendering and returns its IDs.
  @ObjCSelector('createTexturePlayerWithOptions:')
  TexturePlayerIds createForTextureView(CreationOptions creationOptions);
  @ObjCSelector('setMixWithOthers:')
  void setMixWithOthers(bool mixWithOthers);
  @ObjCSelector('fileURLForAssetWithName:package:')
  String? getAssetUrl(String asset, String? package);
}

@HostApi()
abstract class VideoPlayerInstanceApi {
  @ObjCSelector('setLooping:')
  void setLooping(bool looping);
  @ObjCSelector('setVolume:')
  void setVolume(double volume);
  @ObjCSelector('setPlaybackSpeed:')
  void setPlaybackSpeed(double speed);
  void play();
  @ObjCSelector('position')
  int getPosition();
  @async
  @ObjCSelector('seekTo:')
  void seekTo(int position);
  void pause();
  void dispose();
}