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: 所有播放器已清理完成,播放器字典已重置");
}
🟢 【核心】初始化视频播放器插件
此方法负责初始化视频播放器插件的核心功能:
- 在iOS平台上配置音频会话,允许在静音模式下播放音频
- 清理所有现有的视频播放器实例,释放相关资源
- 确保插件处于干净的初始状态
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;
}
}
- 🟡 【核心】创建纹理播放器
- 此方法用于创建基于纹理的视频播放器实例:
-
- 根据创建选项构建AVPlayerItem
-
- 初始化帧更新器和显示链接,用于纹理渲染
-
- 创建FVPTextureBasedVideoPlayer实例,包含纹理渲染所需的所有逻辑
-
- 注册纹理并配置播放器,返回播放器ID和纹理ID
-
- 处理创建过程中可能出现的异常
- @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实例:
- 提取HTTP头信息并构建资源选项
- 创建AVURLAsset,支持网络、本地文件等多种资源类型
- 基于资源创建AVPlayerItem,作为播放器的媒体源
- 返回配置完成的播放器项目实例
@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;
}
🟢 【核心】配置视频播放器实例
此方法负责配置新创建的视频播放器实例的完整功能:
- 为播放器分配唯一标识符并存储到字典中
- 设置播放器特定的API处理器,建立Flutter与原生的通信桥梁
- 配置播放器销毁时的清理逻辑,包括取消注册API处理器和执行额外的清理回调
- 建立事件通道,用于向Flutter端发送播放器状态变化事件
- 返回播放器的唯一标识符供Flutter端使用
@param player 要配置的视频播放器实例 @param extraDisposeHandler 可选的额外销毁处理回调,在播放器销毁时执行 @return 播放器的唯一标识符
