FVPVideoPlayerPlugin
@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
- (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
return [self initWithAVFactory:[[FVPDefaultAVFactory alloc] init]
displayLinkFactory:[[FVPDefaultDisplayLinkFactory alloc] init]
viewProvider:[[FVPDefaultViewProvider alloc] initWithRegistrar:registrar]
registrar:registrar];
}
- (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(message文件)
/**
* 【入口】设置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(message文件)
* 【重要】设置AVFoundation视频播放器API与指定后缀
*
* 此函数负责配置Flutter与原生iOS/macOS视频播放器之间的通信通道。
* 它为每个API方法创建消息通道,并设置相应的消息处理器。
initialize(message文件)
{
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(FVPVideoPlayerPlugin)
- (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(message文件)
{
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(FVPVideoPlayerPlugin)
- (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(FVPVideoPlayerPlugin)
- (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(FVPVideoPlayerPlugin)
配置并返回播放器ID
- (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 播放器的唯一标识符
配置视频播放器实例时,会同步创建FVPEventBridge事件通道并赋值给视频播放器实例
// Set up the event channel.
FVPEventBridge *eventBridge = [[FVPEventBridge alloc]
initWithMessenger:messenger
channelName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%@",
channelSuffix]];
player.eventListener = eventBridge;
这样视频播放器实例就可以在内部发送事件到Dart端。
FVPEventBridge
初始化事件桥接器
创建一个新的事件桥接器实例,建立与Flutter端的通信通道
- (instancetype)initWithMessenger:(NSObject <FlutterBinaryMessenger> *)messenger
channelName:(NSString *)channelName {
self = [super init];
if (self) {
NSLog(@"🟣 EventBridge开始初始化,通道名: %@", channelName);
// 初始化事件队列,用于缓存Flutter端未准备好时的事件
_queuedEvents = [[NSMutableArray alloc] init];
// 创建Flutter事件通道
_eventChannel = [FlutterEventChannel eventChannelWithName:channelName
binaryMessenger:messenger];
NSLog(@"🟣 EventBridge事件通道已创建,通道名: %@", channelName);
// This retain loop is broken in videoPlayerWasDisposed.
// 设置流处理器(这个保留循环在videoPlayerWasDisposed中被打破)
[_eventChannel setStreamHandler:self];
NSLog(@"🟣 EventBridge流处理器已设置");
}
NSLog(@"🟣 EventBridge初始化完成\n");
return self;
}
onListenWithArguments
开始监听事件流。
当Flutter端开始监听事件时调用此方法。
设置事件接收器并发送队列中缓存的所有事件。
- (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments
eventSink:(nonnull FlutterEventSink)events {
NSLog(@"🟣 EventBridge开始监听事件,参数: %@", arguments);
// 设置事件接收器
self.eventSink = events;
// Send any events that came in before the sink was ready.
// 发送在接收器准备好之前收到的所有事件
NSLog(@"🟣 EventBridge开始发送队列中的事件,队列长度: %lu",
(unsigned long) self.queuedEvents.count);
for (id event in self.queuedEvents) {
self.eventSink(event);
}
[self.queuedEvents removeAllObjects];
NSLog(@"🟣 EventBridge队列事件发送完成,队列已清空\n");
return nil;
}
onCancelWithArguments
/**
* 取消事件流监听
*
* 当Flutter端取消监听事件时调用此方法
* 清空事件接收器和事件队列
*
* @param arguments 取消参数(通常为nil)
* @return 错误信息(成功时返回nil)
*/
- (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments {
NSLog(@"🟣 EventBridge取消监听,参数: %@", arguments);
// 清空事件接收器
self.eventSink = nil;
NSLog(@"🟣 EventBridge事件接收器已清空");
// No need to queue events coming in after this point; nil the queue so they will be discarded.
// 此时之后不需要再排队事件;将队列设为nil以便丢弃后续事件
self.queuedEvents = nil;
NSLog(@"🟣 EventBridge事件队列已清空,后续事件将被丢弃");
NSLog(@"\n");
return nil;
}
videoEventsFor、videoEventsFor、_eventChannelFor(dart端)
// 启动视频播放器流监听
_eventSubscription = _platform.videoEventsFor(_playerId).listen(eventListener, onError: errorListener);
@override
Stream<VideoEvent> videoEventsFor(int playerId) {
debugPrint('---✅启动视频播放器流监听videoEventsFor, playerId: $playerId---');
return _eventChannelFor(playerId).receiveBroadcastStream().map((dynamic event) {
final Map<dynamic, dynamic> map = event as Map<dynamic, dynamic>;
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),
};
});
}
EventChannel _eventChannelFor(int playerId) {
return EventChannel('flutter.io/videoPlayer/videoEvents$playerId');
}
当iOS端开始监听广播流时,dart端FVPEventBridge里实现的FlutterStreamHandler协议onListenWithArguments方法立即执行。