33// found in the LICENSE file.
44
55#import " FLTVideoPlayerPlugin.h"
6+ #import " FLTVideoPlayerPlugin_Test.h"
67
78#import < AVFoundation/AVFoundation.h>
89#import < GLKit/GLKit.h>
@@ -33,6 +34,16 @@ - (void)onDisplayLink:(CADisplayLink *)link {
3334}
3435@end
3536
37+ @interface FVPDefaultPlayerFactory : NSObject <FVPPlayerFactory>
38+ @end
39+
40+ @implementation FVPDefaultPlayerFactory
41+ - (AVPlayer *)playerWithPlayerItem : (AVPlayerItem *)playerItem {
42+ return [AVPlayer playerWithPlayerItem: playerItem];
43+ }
44+
45+ @end
46+
3647@interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler>
3748@property (readonly , nonatomic ) AVPlayer *player;
3849@property (readonly , nonatomic ) AVPlayerItemVideoOutput *videoOutput;
@@ -52,7 +63,8 @@ @interface FLTVideoPlayer : NSObject <FlutterTexture, FlutterStreamHandler>
5263@property (nonatomic , readonly ) BOOL isInitialized;
5364- (instancetype )initWithURL : (NSURL *)url
5465 frameUpdater : (FLTFrameUpdater *)frameUpdater
55- httpHeaders : (nonnull NSDictionary <NSString *, NSString *> *)headers ;
66+ httpHeaders : (nonnull NSDictionary <NSString *, NSString *> *)headers
67+ playerFactory : (id <FVPPlayerFactory>)playerFactory ;
5668@end
5769
5870static void *timeRangeContext = &timeRangeContext;
@@ -65,9 +77,14 @@ - (instancetype)initWithURL:(NSURL *)url
6577static void *rateContext = &rateContext;
6678
6779@implementation FLTVideoPlayer
68- - (instancetype )initWithAsset : (NSString *)asset frameUpdater : (FLTFrameUpdater *)frameUpdater {
80+ - (instancetype )initWithAsset : (NSString *)asset
81+ frameUpdater : (FLTFrameUpdater *)frameUpdater
82+ playerFactory : (id <FVPPlayerFactory>)playerFactory {
6983 NSString *path = [[NSBundle mainBundle ] pathForResource: asset ofType: nil ];
70- return [self initWithURL: [NSURL fileURLWithPath: path] frameUpdater: frameUpdater httpHeaders: @{}];
84+ return [self initWithURL: [NSURL fileURLWithPath: path]
85+ frameUpdater: frameUpdater
86+ httpHeaders: @{}
87+ playerFactory: playerFactory];
7188}
7289
7390- (void )addObserversForItem : (AVPlayerItem *)item player : (AVPlayer *)player {
@@ -203,18 +220,20 @@ - (void)createVideoOutputAndDisplayLink:(FLTFrameUpdater *)frameUpdater {
203220
204221- (instancetype )initWithURL : (NSURL *)url
205222 frameUpdater : (FLTFrameUpdater *)frameUpdater
206- httpHeaders : (nonnull NSDictionary <NSString *, NSString *> *)headers {
223+ httpHeaders : (nonnull NSDictionary <NSString *, NSString *> *)headers
224+ playerFactory : (id <FVPPlayerFactory>)playerFactory {
207225 NSDictionary <NSString *, id > *options = nil ;
208226 if ([headers count ] != 0 ) {
209227 options = @{@" AVURLAssetHTTPHeaderFieldsKey" : headers};
210228 }
211229 AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL: url options: options];
212230 AVPlayerItem *item = [AVPlayerItem playerItemWithAsset: urlAsset];
213- return [self initWithPlayerItem: item frameUpdater: frameUpdater];
231+ return [self initWithPlayerItem: item frameUpdater: frameUpdater playerFactory: playerFactory ];
214232}
215233
216234- (instancetype )initWithPlayerItem : (AVPlayerItem *)item
217- frameUpdater : (FLTFrameUpdater *)frameUpdater {
235+ frameUpdater : (FLTFrameUpdater *)frameUpdater
236+ playerFactory : (id <FVPPlayerFactory>)playerFactory {
218237 self = [super init ];
219238 NSAssert (self, @" super init cannot be nil" );
220239
@@ -247,7 +266,7 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem *)item
247266 }
248267 };
249268
250- _player = [AVPlayer playerWithPlayerItem: item];
269+ _player = [playerFactory playerWithPlayerItem: item];
251270 _player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
252271
253272 // This is to fix 2 bugs: 1. blank video for encrypted video streams on iOS 16
@@ -420,9 +439,15 @@ - (int64_t)duration {
420439}
421440
422441- (void )seekTo : (int )location completionHandler : (void (^)(BOOL ))completionHandler {
423- [_player seekToTime: CMTimeMake (location, 1000 )
424- toleranceBefore: kCMTimeZero
425- toleranceAfter: kCMTimeZero
442+ CMTime locationCMT = CMTimeMake (location, 1000 );
443+ CMTimeValue duration = _player.currentItem .asset .duration .value ;
444+ // Without adding tolerance when seeking to duration,
445+ // seekToTime will never complete, and this call will hang.
446+ // see issue https://github.com/flutter/flutter/issues/124475.
447+ CMTime tolerance = location == duration ? CMTimeMake (1 , 1000 ) : kCMTimeZero ;
448+ [_player seekToTime: locationCMT
449+ toleranceBefore: tolerance
450+ toleranceAfter: tolerance
426451 completionHandler: completionHandler];
427452}
428453
@@ -523,6 +548,7 @@ @interface FLTVideoPlayerPlugin () <FLTAVFoundationVideoPlayerApi>
523548@property (readonly , strong , nonatomic )
524549 NSMutableDictionary <NSNumber *, FLTVideoPlayer *> *playersByTextureId;
525550@property (readonly , strong , nonatomic ) NSObject <FlutterPluginRegistrar> *registrar;
551+ @property (nonatomic , strong ) id <FVPPlayerFactory> playerFactory;
526552@end
527553
528554@implementation FLTVideoPlayerPlugin
@@ -533,11 +559,17 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
533559}
534560
535561- (instancetype )initWithRegistrar : (NSObject <FlutterPluginRegistrar> *)registrar {
562+ return [self initWithPlayerFactory: [[FVPDefaultPlayerFactory alloc ] init ] registrar: registrar];
563+ }
564+
565+ - (instancetype )initWithPlayerFactory : (id <FVPPlayerFactory>)playerFactory
566+ registrar : (NSObject <FlutterPluginRegistrar> *)registrar {
536567 self = [super init ];
537568 NSAssert (self, @" super init cannot be nil" );
538569 _registry = [registrar textures ];
539570 _messenger = [registrar messenger ];
540571 _registrar = registrar;
572+ _playerFactory = playerFactory;
541573 _playersByTextureId = [NSMutableDictionary dictionaryWithCapacity: 1 ];
542574 return self;
543575}
@@ -588,12 +620,15 @@ - (FLTTextureMessage *)create:(FLTCreateMessage *)input error:(FlutterError **)e
588620 } else {
589621 assetPath = [_registrar lookupKeyForAsset: input.asset];
590622 }
591- player = [[FLTVideoPlayer alloc ] initWithAsset: assetPath frameUpdater: frameUpdater];
623+ player = [[FLTVideoPlayer alloc ] initWithAsset: assetPath
624+ frameUpdater: frameUpdater
625+ playerFactory: _playerFactory];
592626 return [self onPlayerSetup: player frameUpdater: frameUpdater];
593627 } else if (input.uri ) {
594628 player = [[FLTVideoPlayer alloc ] initWithURL: [NSURL URLWithString: input.uri]
595629 frameUpdater: frameUpdater
596- httpHeaders: input.httpHeaders];
630+ httpHeaders: input.httpHeaders
631+ playerFactory: _playerFactory];
597632 return [self onPlayerSetup: player frameUpdater: frameUpdater];
598633 } else {
599634 *error = [FlutterError errorWithCode: @" video_player" message: @" not implemented" details: nil ];
0 commit comments