diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index bccc6346903..e98f438609f 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.1 + +* Restructures internal logic to move more code to Dart. + ## 2.8.0 * Adds platform view support for macOS. diff --git a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m index 0dd84c55149..58ad53674d0 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -168,32 +168,6 @@ - (instancetype)init { @implementation VideoPlayerTests -- (void)testCreateWithOptionsReturnsErrorForInvalidAssetPath { - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - OCMStub([registrar lookupKeyForAsset:[OCMArg any]]).andReturn(nil); - FVPVideoPlayerPlugin *videoPlayerPlugin = - [[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar]; - - FlutterError *initializationError; - [videoPlayerPlugin initialize:&initializationError]; - XCTAssertNil(initializationError); - - FVPCreationOptions *create = - [FVPCreationOptions makeWithAsset:@"invalid/path/to/asset" - uri:nil - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; - - FlutterError *createError; - NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; - - XCTAssertNil(playerIdentifier); - XCTAssertNotNil(createError); - XCTAssertEqualObjects(createError.code, @"video_player"); -} - - (void)testBlankVideoBugWithEncryptedVideoStreamAndInvertedAspectRatioBugForSomeVideoStream { // This is to fix 2 bugs: 1. blank video for encrypted video streams on iOS 16 // (https://github.com/flutter/flutter/issues/111457) and 2. swapped width and height for some @@ -218,12 +192,9 @@ - (void)testBlankVideoBugWithEncryptedVideoStreamAndInvertedAspectRatioBugForSom XCTAssertNil(error); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(playerIdentifier); @@ -253,12 +224,9 @@ - (void)testPlayerForPlatformViewDoesNotRegisterTexture { [videoPlayerPlugin initialize:&initalizationError]; XCTAssertNil(initalizationError); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypePlatformView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypePlatformView]; FlutterError *createError; [videoPlayerPlugin createWithOptions:create error:&createError]; @@ -284,12 +252,9 @@ - (void)testSeekToWhilePausedStartsDisplayLinkTemporarily { [videoPlayerPlugin initialize:&initalizationError]; XCTAssertNil(initalizationError); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; FVPTextureBasedVideoPlayer *player = @@ -344,12 +309,9 @@ - (void)testInitStartsDisplayLinkTemporarily { [videoPlayerPlugin initialize:&initalizationError]; XCTAssertNil(initalizationError); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; @@ -393,12 +355,9 @@ - (void)testSeekToWhilePlayingDoesNotStopDisplayLink { [videoPlayerPlugin initialize:&initalizationError]; XCTAssertNil(initalizationError); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; FVPTextureBasedVideoPlayer *player = @@ -451,12 +410,9 @@ - (void)testPauseWhileWaitingForFrameDoesNotStopDisplayLink { [videoPlayerPlugin initialize:&initalizationError]; XCTAssertNil(initalizationError); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; FVPTextureBasedVideoPlayer *player = @@ -481,12 +437,9 @@ - (void)testDeregistersFromPlayer { XCTAssertNil(error); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(playerIdentifier); @@ -513,12 +466,9 @@ - (void)testBufferingStateFromPlayer { XCTAssertNil(error); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(playerIdentifier); @@ -709,12 +659,9 @@ - (void)testDoesNotCrashOnRateObservationAfterDisposal { XCTAssertNil(error); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(playerIdentifier); @@ -763,12 +710,9 @@ - (void)testHotReloadDoesNotCrash { XCTAssertNil(error); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; XCTAssertNil(error); XCTAssertNotNil(playerIdentifier); @@ -830,13 +774,9 @@ - (void)testFailedToLoadVideoEventShouldBeAlwaysSent { [videoPlayerPlugin initialize:&error]; - FVPCreationOptions *create = - [FVPCreationOptions makeWithAsset:nil - uri:@"" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + FVPCreationOptions *create = [FVPCreationOptions makeWithUri:@"" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; FVPVideoPlayer *player = videoPlayerPlugin.playersByIdentifier[playerIdentifier]; XCTAssertNotNil(player); @@ -896,12 +836,9 @@ - (void)testPlayerShouldNotDropEverySecondFrame { [videoPlayerPlugin initialize:&error]; XCTAssertNil(error); FVPCreationOptions *create = [FVPCreationOptions - makeWithAsset:nil - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; + makeWithUri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4" + httpHeaders:@{} + viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; FVPTextureBasedVideoPlayer *player = (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPTextureBasedVideoPlayer.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPTextureBasedVideoPlayer.m index 071518e36f2..d8ef40c456e 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPTextureBasedVideoPlayer.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPTextureBasedVideoPlayer.m @@ -31,18 +31,6 @@ @interface FVPTextureBasedVideoPlayer () @end @implementation FVPTextureBasedVideoPlayer -- (instancetype)initWithAsset:(NSString *)asset - frameUpdater:(FVPFrameUpdater *)frameUpdater - displayLink:(NSObject *)displayLink - avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider { - return [self initWithURL:[NSURL fileURLWithPath:[FVPVideoPlayer absolutePathForAssetName:asset]] - frameUpdater:frameUpdater - displayLink:displayLink - httpHeaders:@{} - avFactory:avFactory - viewProvider:viewProvider]; -} - (instancetype)initWithURL:(NSURL *)url frameUpdater:(FVPFrameUpdater *)frameUpdater diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m index 77a7195c873..acf5bf60805 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayer.m @@ -17,14 +17,6 @@ static void *rateContext = &rateContext; @implementation FVPVideoPlayer -- (instancetype)initWithAsset:(NSString *)asset - avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider { - return [self initWithURL:[NSURL fileURLWithPath:[FVPVideoPlayer absolutePathForAssetName:asset]] - httpHeaders:@{} - avFactory:avFactory - viewProvider:viewProvider]; -} - (instancetype)initWithURL:(NSURL *)url httpHeaders:(nonnull NSDictionary *)headers @@ -122,19 +114,6 @@ - (void)dispose { [_eventChannel setStreamHandler:nil]; } -+ (NSString *)absolutePathForAssetName:(NSString *)assetName { - NSString *path = [[NSBundle mainBundle] pathForResource:assetName ofType:nil]; -#if TARGET_OS_OSX - // See https://github.com/flutter/flutter/issues/135302 - // TODO(stuartmorgan): Remove this if the asset APIs are adjusted to work better for macOS. - if (!path) { - path = [NSURL URLWithString:assetName relativeToURL:NSBundle.mainBundle.bundleURL].path; - } -#endif - - return path; -} - - (void)addObserversForItem:(AVPlayerItem *)item player:(AVPlayer *)player { [item addObserver:self forKeyPath:@"loadedTimeRanges" @@ -250,8 +229,10 @@ - (void)observeValueForKeyPath:(NSString *)path NSMutableArray *> *values = [[NSMutableArray alloc] init]; for (NSValue *rangeValue in [object loadedTimeRanges]) { CMTimeRange range = [rangeValue CMTimeRangeValue]; - int64_t start = FVPCMTimeToMillis(range.start); - [values addObject:@[ @(start), @(start + FVPCMTimeToMillis(range.duration)) ]]; + [values addObject:@[ + @(FVPCMTimeToMillis(range.start)), + @(FVPCMTimeToMillis(range.duration)), + ]]; } _eventSink(@{@"event" : @"bufferingUpdate", @"values" : values}); } diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m index 114bba8552a..4c53923cd42 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPVideoPlayerPlugin.m @@ -43,8 +43,6 @@ @implementation FVPDefaultDisplayLinkFactory #pragma mark - @interface FVPVideoPlayerPlugin () -@property(readonly, weak, nonatomic) NSObject *registry; -@property(readonly, weak, nonatomic) NSObject *messenger; @property(readonly, strong, nonatomic) NSObject *registrar; @property(nonatomic, strong) id displayLinkFactory; @property(nonatomic, strong) id avFactory; @@ -79,8 +77,6 @@ - (instancetype)initWithAVFactory:(id)avFactory registrar:(NSObject *)registrar { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _registry = [registrar textures]; - _messenger = [registrar messenger]; _registrar = registrar; _viewProvider = viewProvider; _displayLinkFactory = displayLinkFactory ?: [[FVPDefaultDisplayLinkFactory alloc] init]; @@ -108,13 +104,13 @@ - (int64_t)onPlayerSetup:(FVPVideoPlayer *)player { int64_t playerIdentifier; if (textureBasedPlayer) { - playerIdentifier = [self.registry registerTexture:textureBasedPlayer]; + playerIdentifier = [self.registrar.textures registerTexture:textureBasedPlayer]; [textureBasedPlayer setTextureIdentifier:playerIdentifier]; } else { playerIdentifier = self.nextNonTexturePlayerIdentifier--; } - NSObject *messenger = self.messenger; + NSObject *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); @@ -123,7 +119,7 @@ - (int64_t)onPlayerSetup:(FVPVideoPlayer *)player { player.onDisposed = ^() { SetUpFVPVideoPlayerInstanceApiWithSuffix(messenger, nil, channelSuffix); if (isTextureBased) { - [weakSelf.registry unregisterTexture:playerIdentifier]; + [weakSelf.registrar.textures unregisterTexture:playerIdentifier]; } }; // Set up the event channel. @@ -196,12 +192,6 @@ - (nullable NSNumber *)createWithOptions:(nonnull FVPCreationOptions *)options @try { FVPVideoPlayer *player = textureBased ? [self texturePlayerWithOptions:options] : [self platformViewPlayerWithOptions:options]; - - if (player == nil) { - *error = [FlutterError errorWithCode:@"video_player" message:@"not implemented" details:nil]; - return nil; - } - return @([self onPlayerSetup:player]); } @catch (NSException *exception) { *error = [FlutterError errorWithCode:@"video_player" message:exception.reason details:nil]; @@ -209,59 +199,30 @@ - (nullable NSNumber *)createWithOptions:(nonnull FVPCreationOptions *)options } } -- (nullable FVPTextureBasedVideoPlayer *)texturePlayerWithOptions: +- (nonnull FVPTextureBasedVideoPlayer *)texturePlayerWithOptions: (nonnull FVPCreationOptions *)options { - FVPFrameUpdater *frameUpdater = [[FVPFrameUpdater alloc] initWithRegistry:_registry]; + FVPFrameUpdater *frameUpdater = + [[FVPFrameUpdater alloc] initWithRegistry:self.registrar.textures]; NSObject *displayLink = [self.displayLinkFactory displayLinkWithRegistrar:_registrar callback:^() { [frameUpdater displayLinkFired]; }]; - if (options.asset) { - NSString *assetPath = [self assetPathFromCreationOptions:options]; - return [[FVPTextureBasedVideoPlayer alloc] initWithAsset:assetPath - frameUpdater:frameUpdater - displayLink:displayLink - avFactory:self.avFactory - viewProvider:self.viewProvider]; - } else if (options.uri) { - return [[FVPTextureBasedVideoPlayer alloc] initWithURL:[NSURL URLWithString:options.uri] - frameUpdater:frameUpdater - displayLink:displayLink - httpHeaders:options.httpHeaders - avFactory:self.avFactory - viewProvider:self.viewProvider]; - } - - return nil; + return [[FVPTextureBasedVideoPlayer alloc] initWithURL:[NSURL URLWithString:options.uri] + frameUpdater:frameUpdater + displayLink:displayLink + httpHeaders:options.httpHeaders + avFactory:self.avFactory + viewProvider:self.viewProvider]; } -- (nullable FVPVideoPlayer *)platformViewPlayerWithOptions:(nonnull FVPCreationOptions *)options { +- (nonnull FVPVideoPlayer *)platformViewPlayerWithOptions:(nonnull FVPCreationOptions *)options { // FVPVideoPlayer contains all required logic for platform views. - if (options.asset) { - NSString *assetPath = [self assetPathFromCreationOptions:options]; - return [[FVPVideoPlayer alloc] initWithAsset:assetPath - avFactory:self.avFactory - viewProvider:self.viewProvider]; - } else if (options.uri) { - return [[FVPVideoPlayer alloc] initWithURL:[NSURL URLWithString:options.uri] - httpHeaders:options.httpHeaders - avFactory:self.avFactory - viewProvider:self.viewProvider]; - } - - return nil; -} - -- (NSString *)assetPathFromCreationOptions:(nonnull FVPCreationOptions *)options { - NSString *assetPath; - if (options.packageName) { - assetPath = [self.registrar lookupKeyForAsset:options.asset fromPackage:options.packageName]; - } else { - assetPath = [self.registrar lookupKeyForAsset:options.asset]; - } - return assetPath; + return [[FVPVideoPlayer alloc] initWithURL:[NSURL URLWithString:options.uri] + httpHeaders:options.httpHeaders + avFactory:self.avFactory + viewProvider:self.viewProvider]; } - (void)disposePlayer:(NSInteger)playerIdentifier error:(FlutterError **)error { @@ -286,4 +247,26 @@ - (void)setMixWithOthers:(BOOL)mixWithOthers #endif } +- (nullable NSString *)fileURLForAssetWithName:(NSString *)asset + package:(nullable NSString *)package + error:(FlutterError *_Nullable *_Nonnull)error { + NSString *resource = package == nil + ? [self.registrar lookupKeyForAsset:asset] + : [self.registrar lookupKeyForAsset:asset fromPackage:package]; + + NSString *path = [[NSBundle mainBundle] pathForResource:resource ofType:nil]; +#if TARGET_OS_OSX + // See https://github.com/flutter/flutter/issues/135302 + // TODO(stuartmorgan): Remove this if the asset APIs are adjusted to work better for macOS. + if (!path) { + path = [NSURL URLWithString:resource relativeToURL:NSBundle.mainBundle.bundleURL].path; + } +#endif + + if (!path) { + return nil; + } + return [NSURL fileURLWithPath:path].absoluteString; +} + @end diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPTextureBasedVideoPlayer.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPTextureBasedVideoPlayer.h index 229fd78ca24..bae9105039e 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPTextureBasedVideoPlayer.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPTextureBasedVideoPlayer.h @@ -23,14 +23,6 @@ NS_ASSUME_NONNULL_BEGIN avFactory:(id)avFactory viewProvider:(NSObject *)viewProvider; -/// Initializes a new instance of FVPTextureBasedVideoPlayer with the given asset, frame updater, -/// display link, AV factory, and registrar. -- (instancetype)initWithAsset:(NSString *)asset - frameUpdater:(FVPFrameUpdater *)frameUpdater - displayLink:(NSObject *)displayLink - avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider; - /// Sets the texture Identifier for the frame updater. This method should be called once the texture /// identifier is obtained from the texture registry. - (void)setTextureIdentifier:(int64_t)textureIdentifier; diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h index 9a18197ee24..469dda24a61 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h @@ -35,12 +35,6 @@ NS_ASSUME_NONNULL_BEGIN /// A block that will be called when dispose is called. @property(nonatomic, nullable, copy) void (^onDisposed)(void); -/// Initializes a new instance of FVPVideoPlayer with the given asset, AV factory, and view -/// provider. -- (instancetype)initWithAsset:(NSString *)asset - avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider; - /// Initializes a new instance of FVPVideoPlayer with the given URL, HTTP headers, AV factory, and /// view provider. - (instancetype)initWithURL:(NSURL *)url diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h index 7b282be4c06..c03736e62f5 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h @@ -40,10 +40,6 @@ NS_ASSUME_NONNULL_BEGIN /// Updates the playing state of the video player. - (void)updatePlayingState; - -/// Returns the absolute file path for a given asset name. -/// This method attempts to locate the specified asset within the app bundle. -+ (NSString *)absolutePathForAssetName:(NSString *)assetName; @end NS_ASSUME_NONNULL_END diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h index 1f3966d6e77..b895043931e 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h @@ -39,16 +39,10 @@ typedef NS_ENUM(NSUInteger, FVPPlatformVideoViewType) { @interface FVPCreationOptions : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithAsset:(nullable NSString *)asset - uri:(nullable NSString *)uri - packageName:(nullable NSString *)packageName - formatHint:(nullable NSString *)formatHint - httpHeaders:(NSDictionary *)httpHeaders - viewType:(FVPPlatformVideoViewType)viewType; -@property(nonatomic, copy, nullable) NSString *asset; -@property(nonatomic, copy, nullable) NSString *uri; -@property(nonatomic, copy, nullable) NSString *packageName; -@property(nonatomic, copy, nullable) NSString *formatHint; ++ (instancetype)makeWithUri:(NSString *)uri + httpHeaders:(NSDictionary *)httpHeaders + viewType:(FVPPlatformVideoViewType)viewType; +@property(nonatomic, copy) NSString *uri; @property(nonatomic, copy) NSDictionary *httpHeaders; @property(nonatomic, assign) FVPPlatformVideoViewType viewType; @end @@ -63,6 +57,9 @@ NSObject *FVPGetMessagesCodec(void); error:(FlutterError *_Nullable *_Nonnull)error; - (void)disposePlayer:(NSInteger)playerId error:(FlutterError *_Nullable *_Nonnull)error; - (void)setMixWithOthers:(BOOL)mixWithOthers error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSString *)fileURLForAssetWithName:(NSString *)asset + package:(nullable NSString *)package + error:(FlutterError *_Nullable *_Nonnull)error; @end extern void SetUpFVPAVFoundationVideoPlayerApi( diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m index 90519755c2e..13076be0e91 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/messages.g.m @@ -77,29 +77,20 @@ + (nullable FVPPlatformVideoViewCreationParams *)nullableFromList:(NSArray * @end @implementation FVPCreationOptions -+ (instancetype)makeWithAsset:(nullable NSString *)asset - uri:(nullable NSString *)uri - packageName:(nullable NSString *)packageName - formatHint:(nullable NSString *)formatHint - httpHeaders:(NSDictionary *)httpHeaders - viewType:(FVPPlatformVideoViewType)viewType { ++ (instancetype)makeWithUri:(NSString *)uri + httpHeaders:(NSDictionary *)httpHeaders + viewType:(FVPPlatformVideoViewType)viewType { FVPCreationOptions *pigeonResult = [[FVPCreationOptions alloc] init]; - pigeonResult.asset = asset; pigeonResult.uri = uri; - pigeonResult.packageName = packageName; - pigeonResult.formatHint = formatHint; pigeonResult.httpHeaders = httpHeaders; pigeonResult.viewType = viewType; return pigeonResult; } + (FVPCreationOptions *)fromList:(NSArray *)list { FVPCreationOptions *pigeonResult = [[FVPCreationOptions alloc] init]; - pigeonResult.asset = GetNullableObjectAtIndex(list, 0); - pigeonResult.uri = GetNullableObjectAtIndex(list, 1); - pigeonResult.packageName = GetNullableObjectAtIndex(list, 2); - pigeonResult.formatHint = GetNullableObjectAtIndex(list, 3); - pigeonResult.httpHeaders = GetNullableObjectAtIndex(list, 4); - FVPPlatformVideoViewTypeBox *boxedFVPPlatformVideoViewType = GetNullableObjectAtIndex(list, 5); + pigeonResult.uri = GetNullableObjectAtIndex(list, 0); + pigeonResult.httpHeaders = GetNullableObjectAtIndex(list, 1); + FVPPlatformVideoViewTypeBox *boxedFVPPlatformVideoViewType = GetNullableObjectAtIndex(list, 2); pigeonResult.viewType = boxedFVPPlatformVideoViewType.value; return pigeonResult; } @@ -108,10 +99,7 @@ + (nullable FVPCreationOptions *)nullableFromList:(NSArray *)list { } - (NSArray *)toList { return @[ - self.asset ?: [NSNull null], self.uri ?: [NSNull null], - self.packageName ?: [NSNull null], - self.formatHint ?: [NSNull null], self.httpHeaders ?: [NSNull null], [[FVPPlatformVideoViewTypeBox alloc] initWithValue:self.viewType], ]; @@ -284,6 +272,31 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", + @"dev.flutter.pigeon.video_player_avfoundation." + @"AVFoundationVideoPlayerApi.getAssetUrl", + messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(fileURLForAssetWithName:package:error:)], + @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " + @"@selector(fileURLForAssetWithName:package:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSString *arg_asset = GetNullableObjectAtIndex(args, 0); + NSString *arg_package = GetNullableObjectAtIndex(args, 1); + FlutterError *error; + NSString *output = [api fileURLForAssetWithName:arg_asset package:arg_package error:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } } void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, NSObject *api) { diff --git a/packages/video_player/video_player_avfoundation/example/lib/main.dart b/packages/video_player/video_player_avfoundation/example/lib/main.dart index f98516b2003..c3b61debf30 100644 --- a/packages/video_player/video_player_avfoundation/example/lib/main.dart +++ b/packages/video_player/video_player_avfoundation/example/lib/main.dart @@ -269,9 +269,9 @@ class _BumbleBeeEncryptedLiveStreamState _controller.addListener(() { setState(() {}); }); - _controller.initialize(); - - _controller.play(); + _controller.initialize().then((_) { + _controller.play(); + }); } @override diff --git a/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart b/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart index bd89408eeb7..3558dccd67c 100644 --- a/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart +++ b/packages/video_player/video_player_avfoundation/lib/src/avfoundation_video_player.dart @@ -74,30 +74,38 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { final DataSource dataSource = options.dataSource; final VideoViewType viewType = options.viewType; - String? asset; - String? packageName; String? uri; - String? formatHint; - Map httpHeaders = {}; switch (dataSource.sourceType) { case DataSourceType.asset: - asset = dataSource.asset; - packageName = dataSource.package; + final String? asset = dataSource.asset; + if (asset == null) { + throw ArgumentError( + '"asset" must be non-null for an asset data source', + ); + } + uri = await _api.getAssetUrl( + asset, + dataSource.package, + ); + if (uri == null) { + // Throw a platform exception for compatibility with the previous + // implementation, which threw on the native side. + throw PlatformException( + code: 'video_player', + message: + 'Asset $asset not found in package ${dataSource.package}.'); + } case DataSourceType.network: - uri = dataSource.uri; - formatHint = _videoFormatStringMap[dataSource.formatHint]; - httpHeaders = dataSource.httpHeaders; case DataSourceType.file: - uri = dataSource.uri; case DataSourceType.contentUri: uri = dataSource.uri; } + if (uri == null) { + throw ArgumentError('Unable to construct a video asset from $options'); + } final CreationOptions pigeonCreationOptions = CreationOptions( - asset: asset, - packageName: packageName, uri: uri, - httpHeaders: httpHeaders, - formatHint: formatHint, + httpHeaders: dataSource.httpHeaders, viewType: _platformVideoViewTypeFromVideoViewType(viewType), ); @@ -166,37 +174,33 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { .receiveBroadcastStream() .map((dynamic event) { final Map map = event as Map; - switch (map['event']) { - case 'initialized': - return VideoEvent( + 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), - ); - case 'completed': - return VideoEvent( + size: Size( + (map['width'] as num?)?.toDouble() ?? 0.0, + (map['height'] as num?)?.toDouble() ?? 0.0, + ), + ), + 'completed' => VideoEvent( eventType: VideoEventType.completed, - ); - case 'bufferingUpdate': - final List values = map['values'] as List; - - return VideoEvent( - buffered: values.map(_toDurationRange).toList(), + ), + 'bufferingUpdate' => VideoEvent( + buffered: (map['values'] as List) + .map(_toDurationRange) + .toList(), eventType: VideoEventType.bufferingUpdate, - ); - case 'bufferingStart': - return VideoEvent(eventType: VideoEventType.bufferingStart); - case 'bufferingEnd': - return VideoEvent(eventType: VideoEventType.bufferingEnd); - case 'isPlayingStateUpdate': - return VideoEvent( + ), + 'bufferingStart' => + VideoEvent(eventType: VideoEventType.bufferingStart), + 'bufferingEnd' => VideoEvent(eventType: VideoEventType.bufferingEnd), + 'isPlayingStateUpdate' => VideoEvent( eventType: VideoEventType.isPlayingStateUpdate, isPlaying: map['isPlaying'] as bool, - ); - default: - return VideoEvent(eventType: VideoEventType.unknown); - } + ), + _ => VideoEvent(eventType: VideoEventType.unknown), + }; }); } @@ -250,19 +254,13 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { return player ?? (throw StateError('No active player with ID $id.')); } - static const Map _videoFormatStringMap = - { - VideoFormat.ss: 'ss', - VideoFormat.hls: 'hls', - VideoFormat.dash: 'dash', - VideoFormat.other: 'other', - }; - DurationRange _toDurationRange(dynamic value) { final List pair = value as List; + final int startMilliseconds = pair[0] as int; + final int durationMilliseconds = pair[1] as int; return DurationRange( - Duration(milliseconds: pair[0] as int), - Duration(milliseconds: pair[1] as int), + Duration(milliseconds: startMilliseconds), + Duration(milliseconds: startMilliseconds + durationMilliseconds), ); } } diff --git a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart b/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart index cc184ae7db0..ae5ec1d9f6c 100644 --- a/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart +++ b/packages/video_player/video_player_avfoundation/lib/src/messages.g.dart @@ -84,21 +84,12 @@ class PlatformVideoViewCreationParams { class CreationOptions { CreationOptions({ - this.asset, - this.uri, - this.packageName, - this.formatHint, + required this.uri, required this.httpHeaders, required this.viewType, }); - String? asset; - - String? uri; - - String? packageName; - - String? formatHint; + String uri; Map httpHeaders; @@ -106,10 +97,7 @@ class CreationOptions { List _toList() { return [ - asset, uri, - packageName, - formatHint, httpHeaders, viewType, ]; @@ -122,13 +110,10 @@ class CreationOptions { static CreationOptions decode(Object result) { result as List; return CreationOptions( - asset: result[0] as String?, - uri: result[1] as String?, - packageName: result[2] as String?, - formatHint: result[3] as String?, + uri: result[0]! as String, httpHeaders: - (result[4] as Map?)!.cast(), - viewType: result[5]! as PlatformVideoViewType, + (result[1] as Map?)!.cast(), + viewType: result[2]! as PlatformVideoViewType, ); } @@ -308,6 +293,32 @@ class AVFoundationVideoPlayerApi { return; } } + + Future getAssetUrl(String asset, String? package) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([asset, package]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; + 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?); + } + } } class VideoPlayerInstanceApi { diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index c01fc094f1f..4b336cca5e6 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -34,14 +34,12 @@ class PlatformVideoViewCreationParams { class CreationOptions { CreationOptions({ + required this.uri, required this.httpHeaders, required this.viewType, }); - String? asset; - String? uri; - String? packageName; - String? formatHint; + String uri; Map httpHeaders; PlatformVideoViewType viewType; } @@ -57,6 +55,8 @@ abstract class AVFoundationVideoPlayerApi { void dispose(int playerId); @ObjCSelector('setMixWithOthers:') void setMixWithOthers(bool mixWithOthers); + @ObjCSelector('fileURLForAssetWithName:package:') + String? getAssetUrl(String asset, String? package); } @HostApi() diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 1482e8a5457..29e56655cac 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_avfoundation description: iOS and macOS implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_avfoundation issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.8.0 +version: 2.8.1 environment: sdk: ^3.6.0 diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart index 6862e999616..fb367310ec1 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.dart @@ -75,6 +75,11 @@ void main() { const String asset = 'someAsset'; const String package = 'somePackage'; + const String assetUrl = 'file:///some/asset/path'; + when( + api.getAssetUrl(asset, package), + ).thenAnswer((_) async => assetUrl); + final int? playerId = await player.create( DataSource( sourceType: DataSourceType.asset, @@ -86,13 +91,37 @@ void main() { final VerificationResult verification = verify(api.create(captureAny)); final CreationOptions creationOptions = verification.captured[0] as CreationOptions; - expect(creationOptions.asset, asset); - expect(creationOptions.packageName, package); + expect(creationOptions.uri, assetUrl); expect(playerId, newPlayerId); expect(player.playerViewStates[newPlayerId], const VideoPlayerTextureViewState(textureId: newPlayerId)); }); + test('create with asset throws PlatformException for missing asset', + () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + + const String asset = 'someAsset'; + const String package = 'somePackage'; + when( + api.getAssetUrl(asset, package), + ).thenAnswer((_) async => null); + + expect( + player.create( + DataSource( + sourceType: DataSourceType.asset, + asset: asset, + package: package, + ), + ), + throwsA(isA())); + }); + test('create with network', () async { final ( AVFoundationVideoPlayer player, @@ -114,10 +143,7 @@ void main() { final VerificationResult verification = verify(api.create(captureAny)); final CreationOptions creationOptions = verification.captured[0] as CreationOptions; - expect(creationOptions.asset, null); expect(creationOptions.uri, uri); - expect(creationOptions.packageName, null); - expect(creationOptions.formatHint, 'dash'); expect(creationOptions.httpHeaders, {}); expect(playerId, newPlayerId); expect(player.playerViewStates[newPlayerId], @@ -181,6 +207,10 @@ void main() { const String asset = 'someAsset'; const String package = 'somePackage'; + const String assetUrl = 'file:///some/asset/path'; + when( + api.getAssetUrl(asset, package), + ).thenAnswer((_) async => assetUrl); final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( @@ -195,8 +225,7 @@ void main() { final VerificationResult verification = verify(api.create(captureAny)); final CreationOptions creationOptions = verification.captured[0] as CreationOptions; - expect(creationOptions.asset, asset); - expect(creationOptions.packageName, package); + expect(creationOptions.uri, assetUrl); expect(playerId, newPlayerId); expect(player.playerViewStates[newPlayerId], const VideoPlayerTextureViewState(textureId: newPlayerId)); @@ -226,10 +255,7 @@ void main() { final VerificationResult verification = verify(api.create(captureAny)); final CreationOptions creationOptions = verification.captured[0] as CreationOptions; - expect(creationOptions.asset, null); expect(creationOptions.uri, uri); - expect(creationOptions.packageName, null); - expect(creationOptions.formatHint, 'dash'); expect(creationOptions.httpHeaders, {}); expect(playerId, newPlayerId); expect(player.playerViewStates[newPlayerId], @@ -550,7 +576,7 @@ void main() { ), DurationRange( const Duration(milliseconds: 1235), - const Duration(milliseconds: 4000), + const Duration(milliseconds: 1235 + 4000), ), ]), VideoEvent(eventType: VideoEventType.bufferingStart), diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart index 1e9968cf0d7..76199b56c02 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart @@ -81,6 +81,23 @@ class MockAVFoundationVideoPlayerApi extends _i1.Mock returnValue: _i4.Future.value(), returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); + + @override + _i4.Future getAssetUrl( + String? asset, + String? package, + ) => + (super.noSuchMethod( + Invocation.method( + #getAssetUrl, + [ + asset, + package, + ], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); } /// A class which mocks [VideoPlayerInstanceApi].