diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 26299c26dbc..b069183f6aa 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.7.3 + +* Restructures the communication between Dart and native code. + ## 2.7.2 * Uses `CADisplayLink` on macOS 14.0+. 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 a67a9003693..3992bb418af 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -10,7 +10,6 @@ #import #import #import -#import #if TARGET_OS_IOS #import @@ -276,6 +275,8 @@ - (void)testSeekToWhilePausedStartsDisplayLinkTemporarily { OCMStub([registrar textures]).andReturn(mockTextureRegistry); StubFVPDisplayLinkFactory *stubDisplayLinkFactory = [[StubFVPDisplayLinkFactory alloc] init]; AVPlayerItemVideoOutput *mockVideoOutput = OCMPartialMock([[AVPlayerItemVideoOutput alloc] init]); + // Display link and frame updater wire-up is currently done in FVPVideoPlayerPlugin, so create + // the player via the plugin instead of directly to include that logic in the test. FVPVideoPlayerPlugin *videoPlayerPlugin = [[FVPVideoPlayerPlugin alloc] initWithAVFactory:[[StubFVPAVFactory alloc] initWithPlayer:nil output:mockVideoOutput] displayLinkFactory:stubDisplayLinkFactory @@ -294,29 +295,22 @@ - (void)testSeekToWhilePausedStartsDisplayLinkTemporarily { viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; + FVPTextureBasedVideoPlayer *player = + (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; // Ensure that the video playback is paused before seeking. FlutterError *pauseError; - [videoPlayerPlugin pausePlayer:playerIdentifier.integerValue error:&pauseError]; - - XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"seekTo completes"]; - [videoPlayerPlugin seekTo:1234 - forPlayer:playerIdentifier.integerValue - completion:^(FlutterError *_Nullable error) { - [initializedExpectation fulfill]; - }]; + [player pauseWithError:&pauseError]; + + XCTestExpectation *seekExpectation = [self expectationWithDescription:@"seekTo completes"]; + [player seekTo:1234 + completion:^(FlutterError *_Nullable error) { + [seekExpectation fulfill]; + }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; // Seeking to a new position should start the display link temporarily. XCTAssertTrue(stubDisplayLinkFactory.displayLink.running); - FVPTextureBasedVideoPlayer *player = - (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; - // Wait for the player's position to update, it shouldn't take long. - XCTestExpectation *positionExpectation = - [self expectationForPredicate:[NSPredicate predicateWithFormat:@"position == 1234"] - evaluatedWithObject:player - handler:nil]; - [self waitForExpectations:@[ positionExpectation ] timeout:3.0]; // Simulate a buffer being available. OCMStub([mockVideoOutput hasNewPixelBufferForItemTime:kCMTimeZero]) @@ -390,6 +384,8 @@ - (void)testSeekToWhilePlayingDoesNotStopDisplayLink { OCMStub([registrar textures]).andReturn(mockTextureRegistry); StubFVPDisplayLinkFactory *stubDisplayLinkFactory = [[StubFVPDisplayLinkFactory alloc] init]; AVPlayerItemVideoOutput *mockVideoOutput = OCMPartialMock([[AVPlayerItemVideoOutput alloc] init]); + // Display link and frame updater wire-up is currently done in FVPVideoPlayerPlugin, so create + // the player via the plugin instead of directly to include that logic in the test. FVPVideoPlayerPlugin *videoPlayerPlugin = [[FVPVideoPlayerPlugin alloc] initWithAVFactory:[[StubFVPAVFactory alloc] initWithPlayer:nil output:mockVideoOutput] displayLinkFactory:stubDisplayLinkFactory @@ -408,29 +404,21 @@ - (void)testSeekToWhilePlayingDoesNotStopDisplayLink { viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; + FVPTextureBasedVideoPlayer *player = + (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; // Ensure that the video is playing before seeking. FlutterError *playError; - [videoPlayerPlugin playPlayer:playerIdentifier.integerValue error:&playError]; - - XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"seekTo completes"]; - [videoPlayerPlugin seekTo:1234 - forPlayer:playerIdentifier.integerValue - completion:^(FlutterError *_Nullable error) { - [initializedExpectation fulfill]; - }]; + [player playWithError:&playError]; + + XCTestExpectation *seekExpectation = [self expectationWithDescription:@"seekTo completes"]; + [player seekTo:1234 + completion:^(FlutterError *_Nullable error) { + [seekExpectation fulfill]; + }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; XCTAssertTrue(stubDisplayLinkFactory.displayLink.running); - FVPTextureBasedVideoPlayer *player = - (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; - // Wait for the player's position to update, it shouldn't take long. - XCTestExpectation *positionExpectation = - [self expectationForPredicate:[NSPredicate predicateWithFormat:@"position == 1234"] - evaluatedWithObject:player - handler:nil]; - [self waitForExpectations:@[ positionExpectation ] timeout:3.0]; - // Simulate a buffer being available. OCMStub([mockVideoOutput hasNewPixelBufferForItemTime:kCMTimeZero]) .ignoringNonObjectArgs() @@ -454,6 +442,8 @@ - (void)testPauseWhileWaitingForFrameDoesNotStopDisplayLink { OCMStub([registrar textures]).andReturn(mockTextureRegistry); StubFVPDisplayLinkFactory *stubDisplayLinkFactory = [[StubFVPDisplayLinkFactory alloc] init]; AVPlayerItemVideoOutput *mockVideoOutput = OCMPartialMock([[AVPlayerItemVideoOutput alloc] init]); + // Display link and frame updater wire-up is currently done in FVPVideoPlayerPlugin, so create + // the player via the plugin instead of directly to include that logic in the test. FVPVideoPlayerPlugin *videoPlayerPlugin = [[FVPVideoPlayerPlugin alloc] initWithAVFactory:[[StubFVPAVFactory alloc] initWithPlayer:nil output:mockVideoOutput] displayLinkFactory:stubDisplayLinkFactory @@ -472,11 +462,13 @@ - (void)testPauseWhileWaitingForFrameDoesNotStopDisplayLink { viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; + FVPTextureBasedVideoPlayer *player = + (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; // Run a play/pause cycle to force the pause codepath to run completely. FlutterError *playPauseError; - [videoPlayerPlugin playPlayer:playerIdentifier.integerValue error:&playPauseError]; - [videoPlayerPlugin pausePlayer:playerIdentifier.integerValue error:&playPauseError]; + [player playWithError:&playPauseError]; + [player pauseWithError:&playPauseError]; // Since a buffer hasn't been available yet, the pause should not have stopped the display link. XCTAssertTrue(stubDisplayLinkFactory.displayLink.running); @@ -559,28 +551,16 @@ - (void)testBufferingStateFromPlayer { } - (void)testVideoControls { - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - - FVPVideoPlayerPlugin *videoPlayerPlugin = - (FVPVideoPlayerPlugin *)[[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar]; - NSDictionary *videoInitialization = - [self testPlugin:videoPlayerPlugin - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"]; + [self sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"]; XCTAssertEqualObjects(videoInitialization[@"height"], @720); XCTAssertEqualObjects(videoInitialization[@"width"], @1280); XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200); } - (void)testAudioControls { - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - - FVPVideoPlayerPlugin *videoPlayerPlugin = - (FVPVideoPlayerPlugin *)[[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar]; - - NSDictionary *audioInitialization = - [self testPlugin:videoPlayerPlugin - uri:@"https://flutter.github.io/assets-for-api-docs/assets/audio/rooster.mp3"]; + NSDictionary *audioInitialization = [self + sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/audio/rooster.mp3"]; XCTAssertEqualObjects(audioInitialization[@"height"], @0); XCTAssertEqualObjects(audioInitialization[@"width"], @0); // Perfect precision not guaranteed. @@ -588,14 +568,8 @@ - (void)testAudioControls { } - (void)testHLSControls { - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - - FVPVideoPlayerPlugin *videoPlayerPlugin = - (FVPVideoPlayerPlugin *)[[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar]; - - NSDictionary *videoInitialization = - [self testPlugin:videoPlayerPlugin - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"]; + NSDictionary *videoInitialization = [self + sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/bee.m3u8"]; XCTAssertEqualObjects(videoInitialization[@"height"], @720); XCTAssertEqualObjects(videoInitialization[@"width"], @1280); XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200); @@ -603,15 +577,10 @@ - (void)testHLSControls { - (void)testAudioOnlyHLSControls { XCTSkip(@"Flaky; see https://github.com/flutter/flutter/issues/164381"); - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - - FVPVideoPlayerPlugin *videoPlayerPlugin = - (FVPVideoPlayerPlugin *)[[FVPVideoPlayerPlugin alloc] initWithRegistrar:registrar]; NSDictionary *videoInitialization = - [self testPlugin:videoPlayerPlugin - uri:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/" - @"bee_audio_only.m3u8"]; + [self sanityTestURI:@"https://flutter.github.io/assets-for-api-docs/assets/videos/hls/" + @"bee_audio_only.m3u8"]; XCTAssertEqualObjects(videoInitialization[@"height"], @0); XCTAssertEqualObjects(videoInitialization[@"width"], @0); XCTAssertEqualWithAccuracy([videoInitialization[@"duration"] intValue], 4000, 200); @@ -631,38 +600,23 @@ - (void)testTransformFix { #endif - (void)testSeekToleranceWhenNotSeekingToEnd { - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - StubAVPlayer *stubAVPlayer = [[StubAVPlayer alloc] init]; StubFVPAVFactory *stubAVFactory = [[StubFVPAVFactory alloc] initWithPlayer:stubAVPlayer output:nil]; - FVPVideoPlayerPlugin *pluginWithMockAVPlayer = - [[FVPVideoPlayerPlugin alloc] initWithAVFactory:stubAVFactory - displayLinkFactory:nil - viewProvider:[[StubViewProvider alloc] initWithView:nil] - registrar:registrar]; - - FlutterError *initializationError; - [pluginWithMockAVPlayer initialize:&initializationError]; - XCTAssertNil(initializationError); - - 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]; - FlutterError *createError; - NSNumber *playerIdentifier = [pluginWithMockAVPlayer createWithOptions:create error:&createError]; - - XCTestExpectation *initializedExpectation = + FVPVideoPlayer *player = [[FVPVideoPlayer alloc] + initWithURL: + [NSURL + URLWithString:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"] + httpHeaders:@{} + avFactory:stubAVFactory + viewProvider:[[StubViewProvider alloc] initWithView:nil]]; + + XCTestExpectation *seekExpectation = [self expectationWithDescription:@"seekTo has zero tolerance when seeking not to end"]; - [pluginWithMockAVPlayer seekTo:1234 - forPlayer:playerIdentifier.integerValue - completion:^(FlutterError *_Nullable error) { - [initializedExpectation fulfill]; - }]; + [player seekTo:1234 + completion:^(FlutterError *_Nullable error) { + [seekExpectation fulfill]; + }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; XCTAssertEqual([stubAVPlayer.beforeTolerance intValue], 0); @@ -670,60 +624,37 @@ - (void)testSeekToleranceWhenNotSeekingToEnd { } - (void)testSeekToleranceWhenSeekingToEnd { - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - StubAVPlayer *stubAVPlayer = [[StubAVPlayer alloc] init]; StubFVPAVFactory *stubAVFactory = [[StubFVPAVFactory alloc] initWithPlayer:stubAVPlayer output:nil]; - FVPVideoPlayerPlugin *pluginWithMockAVPlayer = - [[FVPVideoPlayerPlugin alloc] initWithAVFactory:stubAVFactory - displayLinkFactory:nil - viewProvider:[[StubViewProvider alloc] initWithView:nil] - registrar:registrar]; - - FlutterError *initializationError; - [pluginWithMockAVPlayer initialize:&initializationError]; - XCTAssertNil(initializationError); - - 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]; - FlutterError *createError; - NSNumber *playerIdentifier = [pluginWithMockAVPlayer createWithOptions:create error:&createError]; - - XCTestExpectation *initializedExpectation = + FVPVideoPlayer *player = [[FVPVideoPlayer alloc] + initWithURL: + [NSURL + URLWithString:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"] + httpHeaders:@{} + avFactory:stubAVFactory + viewProvider:[[StubViewProvider alloc] initWithView:nil]]; + + XCTestExpectation *seekExpectation = [self expectationWithDescription:@"seekTo has non-zero tolerance when seeking to end"]; // The duration of this video is "0" due to the non standard initiliatazion process. - [pluginWithMockAVPlayer seekTo:0 - forPlayer:playerIdentifier.integerValue - completion:^(FlutterError *_Nullable error) { - [initializedExpectation fulfill]; - }]; + [player seekTo:0 + completion:^(FlutterError *_Nullable error) { + [seekExpectation fulfill]; + }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; XCTAssertGreaterThan([stubAVPlayer.beforeTolerance intValue], 0); XCTAssertGreaterThan([stubAVPlayer.afterTolerance intValue], 0); } -- (NSDictionary *)testPlugin:(FVPVideoPlayerPlugin *)videoPlayerPlugin - uri:(NSString *)uri { - FlutterError *error; - [videoPlayerPlugin initialize:&error]; - XCTAssertNil(error); - - FVPCreationOptions *create = - [FVPCreationOptions makeWithAsset:nil - uri:uri - packageName:nil - formatHint:nil - httpHeaders:@{} - viewType:FVPPlatformVideoViewTypeTextureView]; - NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; - - FVPVideoPlayer *player = videoPlayerPlugin.playersByIdentifier[playerIdentifier]; +/// Sanity checks a video player playing the given URL with the actual AVPlayer. This is essentially +/// a mini integration test of the player component. +- (NSDictionary *)sanityTestURI:(NSString *)uri { + FVPVideoPlayer *player = + [[FVPVideoPlayer alloc] initWithURL:[NSURL URLWithString:uri] + httpHeaders:@{} + avFactory:[[FVPDefaultAVFactory alloc] init] + viewProvider:[[StubViewProvider alloc] initWithView:nil]]; XCTAssertNotNil(player); XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"]; @@ -745,15 +676,16 @@ - (void)testSeekToleranceWhenSeekingToEnd { XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused); // Change playback speed. - [videoPlayerPlugin setPlaybackSpeed:2 forPlayer:playerIdentifier.integerValue error:&error]; + FlutterError *error; + [player setPlaybackSpeed:2 error:&error]; XCTAssertNil(error); - [videoPlayerPlugin playPlayer:playerIdentifier.integerValue error:&error]; + [player playWithError:&error]; XCTAssertNil(error); XCTAssertEqual(avPlayer.rate, 2); XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate); // Volume - [videoPlayerPlugin setVolume:0.1 forPlayer:playerIdentifier.integerValue error:&error]; + [player setVolume:0.1 error:&error]; XCTAssertNil(error); XCTAssertEqual(avPlayer.volume, 0.1f); @@ -932,26 +864,13 @@ - (void)testFailedToLoadVideoEventShouldBeAlwaysSent { } - (void)testUpdatePlayingStateShouldNotResetRate { - NSObject *registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - - FVPVideoPlayerPlugin *videoPlayerPlugin = [[FVPVideoPlayerPlugin alloc] - initWithAVFactory:[[StubFVPAVFactory alloc] initWithPlayer:nil output:nil] - displayLinkFactory:nil - viewProvider:[[StubViewProvider alloc] initWithView:nil] - registrar:registrar]; - - FlutterError *error; - [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]; - NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; - FVPVideoPlayer *player = videoPlayerPlugin.playersByIdentifier[playerIdentifier]; + FVPVideoPlayer *player = [[FVPVideoPlayer alloc] + initWithURL: + [NSURL + URLWithString:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"] + httpHeaders:@{} + avFactory:[[StubFVPAVFactory alloc] initWithPlayer:nil output:nil] + viewProvider:[[StubViewProvider alloc] initWithView:nil]]; XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"]; [player onListenWithArguments:nil @@ -962,8 +881,9 @@ - (void)testUpdatePlayingStateShouldNotResetRate { }]; [self waitForExpectationsWithTimeout:10 handler:nil]; - [videoPlayerPlugin setPlaybackSpeed:2 forPlayer:playerIdentifier.integerValue error:&error]; - [videoPlayerPlugin playPlayer:playerIdentifier.integerValue error:&error]; + FlutterError *error; + [player setPlaybackSpeed:2 error:&error]; + [player playWithError:&error]; XCTAssertEqual(player.player.rate, 2); } 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 53d399f11c7..071518e36f2 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 @@ -28,7 +28,6 @@ @interface FVPTextureBasedVideoPlayer () // (e.g., after a seek while paused). If YES, the display link should continue to run until the next // frame is successfully provided. @property(nonatomic, assign) BOOL waitingForFrame; -@property(nonatomic, copy) void (^onDisposed)(int64_t); @end @implementation FVPTextureBasedVideoPlayer @@ -36,15 +35,13 @@ - (instancetype)initWithAsset:(NSString *)asset frameUpdater:(FVPFrameUpdater *)frameUpdater displayLink:(NSObject *)displayLink avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider - onDisposed:(void (^)(int64_t))onDisposed { + viewProvider:(NSObject *)viewProvider { return [self initWithURL:[NSURL fileURLWithPath:[FVPVideoPlayer absolutePathForAssetName:asset]] frameUpdater:frameUpdater displayLink:displayLink httpHeaders:@{} avFactory:avFactory - viewProvider:viewProvider - onDisposed:onDisposed]; + viewProvider:viewProvider]; } - (instancetype)initWithURL:(NSURL *)url @@ -52,8 +49,7 @@ - (instancetype)initWithURL:(NSURL *)url displayLink:(NSObject *)displayLink httpHeaders:(nonnull NSDictionary *)headers avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider - onDisposed:(void (^)(int64_t))onDisposed { + viewProvider:(NSObject *)viewProvider { NSDictionary *options = nil; if ([headers count] != 0) { options = @{@"AVURLAssetHTTPHeaderFieldsKey" : headers}; @@ -64,16 +60,14 @@ - (instancetype)initWithURL:(NSURL *)url frameUpdater:frameUpdater displayLink:displayLink avFactory:avFactory - viewProvider:viewProvider - onDisposed:onDisposed]; + viewProvider:viewProvider]; } - (instancetype)initWithPlayerItem:(AVPlayerItem *)item frameUpdater:(FVPFrameUpdater *)frameUpdater displayLink:(NSObject *)displayLink avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider - onDisposed:(void (^)(int64_t))onDisposed { + viewProvider:(NSObject *)viewProvider { self = [super initWithPlayerItem:item avFactory:avFactory viewProvider:viewProvider]; if (self) { @@ -81,7 +75,6 @@ - (instancetype)initWithPlayerItem:(AVPlayerItem *)item _displayLink = displayLink; _frameUpdater.displayLink = _displayLink; _selfRefresh = true; - _onDisposed = [onDisposed copy]; // 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 @@ -117,10 +110,10 @@ - (void)updatePlayingState { _displayLink.running = self.isPlaying || self.waitingForFrame; } -- (void)seekTo:(int64_t)location completionHandler:(void (^)(BOOL))completionHandler { +- (void)seekTo:(NSInteger)position completion:(void (^)(FlutterError *_Nullable))completion { CMTime previousCMTime = self.player.currentTime; - [super seekTo:location - completionHandler:^(BOOL completed) { + [super seekTo:position + completion:^(FlutterError *error) { if (CMTimeCompare(self.player.currentTime, previousCMTime) != 0) { // Ensure that a frame is drawn once available, even if currently paused. In theory a // race is possible here where the new frame has already drawn by the time this code @@ -131,8 +124,8 @@ - (void)seekTo:(int64_t)location completionHandler:(void (^)(BOOL))completionHan [self expectFrame]; } - if (completionHandler) { - completionHandler(completed); + if (completion) { + completion(error); } }]; } @@ -153,12 +146,6 @@ - (void)disposeSansEventChannel { _displayLink = nil; } -- (void)dispose { - [super dispose]; - - _onDisposed(self.frameUpdater.textureIdentifier); -} - #pragma mark - FlutterTexture - (CVPixelBufferRef)copyPixelBuffer { 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 59e82934e16..77a7195c873 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 @@ -4,7 +4,6 @@ #import "./include/video_player_avfoundation/FVPVideoPlayer.h" #import "./include/video_player_avfoundation/FVPVideoPlayer_Internal.h" -#import "./include/video_player_avfoundation/FVPVideoPlayer_Test.h" #import @@ -104,6 +103,25 @@ - (void)dealloc { } } +/// This method allows you to dispose without touching the event channel. This +/// is useful for the case where the Engine is in the process of deconstruction +/// so the channel is going to die or is already dead. +- (void)disposeSansEventChannel { + _disposed = YES; + [self removeKeyValueObservers]; + + [self.player replaceCurrentItemWithPlayerItem:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)dispose { + [self disposeSansEventChannel]; + if (_onDisposed) { + _onDisposed(); + } + [_eventChannel setStreamHandler:nil]; +} + + (NSString *)absolutePathForAssetName:(NSString *)assetName { NSString *path = [[NSBundle mainBundle] pathForResource:assetName ofType:nil]; #if TARGET_OS_OSX @@ -405,57 +423,56 @@ - (void)setupEventSinkIfReadyToPlay { } } -- (void)play { +#pragma mark - FVPVideoPlayerInstanceApi + +- (void)playWithError:(FlutterError *_Nullable *_Nonnull)error { _isPlaying = YES; [self updatePlayingState]; } -- (void)pause { +- (void)pauseWithError:(FlutterError *_Nullable *_Nonnull)error { _isPlaying = NO; [self updatePlayingState]; } -- (int64_t)position { - return FVPCMTimeToMillis([_player currentTime]); -} - -- (int64_t)duration { - // Note: https://openradar.appspot.com/radar?id=4968600712511488 - // `[AVPlayerItem duration]` can be `kCMTimeIndefinite`, - // use `[[AVPlayerItem asset] duration]` instead. - return FVPCMTimeToMillis([[[_player currentItem] asset] duration]); +- (nullable NSNumber *)position:(FlutterError *_Nullable *_Nonnull)error { + return @(FVPCMTimeToMillis([_player currentTime])); } -- (void)seekTo:(int64_t)location completionHandler:(void (^)(BOOL))completionHandler { - CMTime targetCMTime = CMTimeMake(location, 1000); +- (void)seekTo:(NSInteger)position completion:(void (^)(FlutterError *_Nullable))completion { + CMTime targetCMTime = CMTimeMake(position, 1000); CMTimeValue duration = _player.currentItem.asset.duration.value; // Without adding tolerance when seeking to duration, // seekToTime will never complete, and this call will hang. // see issue https://github.com/flutter/flutter/issues/124475. - CMTime tolerance = location == duration ? CMTimeMake(1, 1000) : kCMTimeZero; + CMTime tolerance = position == duration ? CMTimeMake(1, 1000) : kCMTimeZero; [_player seekToTime:targetCMTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL completed) { - if (completionHandler) { - completionHandler(completed); + if (completion) { + dispatch_async(dispatch_get_main_queue(), ^{ + completion(nil); + }); } }]; } -- (void)setIsLooping:(BOOL)isLooping { - _isLooping = isLooping; +- (void)setLooping:(BOOL)looping error:(FlutterError *_Nullable *_Nonnull)error { + _isLooping = looping; } -- (void)setVolume:(double)volume { +- (void)setVolume:(double)volume error:(FlutterError *_Nullable *_Nonnull)error { _player.volume = (float)((volume < 0.0) ? 0.0 : ((volume > 1.0) ? 1.0 : volume)); } -- (void)setPlaybackSpeed:(double)speed { +- (void)setPlaybackSpeed:(double)speed error:(FlutterError *_Nullable *_Nonnull)error { _targetPlaybackSpeed = @(speed); [self updatePlayingState]; } +#pragma mark - FlutterStreamHandler + - (FlutterError *_Nullable)onCancelWithArguments:(id _Nullable)arguments { _eventSink = nil; return nil; @@ -480,20 +497,13 @@ - (FlutterError *_Nullable)onListenWithArguments:(id _Nullable)arguments return nil; } -/// This method allows you to dispose without touching the event channel. This -/// is useful for the case where the Engine is in the process of deconstruction -/// so the channel is going to die or is already dead. -- (void)disposeSansEventChannel { - _disposed = YES; - [self removeKeyValueObservers]; - - [self.player replaceCurrentItemWithPlayerItem:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} +#pragma mark - Private -- (void)dispose { - [self disposeSansEventChannel]; - [_eventChannel setStreamHandler:nil]; +- (int64_t)duration { + // Note: https://openradar.appspot.com/radar?id=4968600712511488 + // `[AVPlayerItem duration]` can be `kCMTimeIndefinite`, + // use `[[AVPlayerItem asset] duration]` instead. + return FVPCMTimeToMillis([[[_player currentItem] asset] duration]); } /// Removes all key-value observers set up for the player. 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 0d76e6d24c5..e73c80191cb 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 @@ -115,18 +115,32 @@ - (int64_t)onPlayerSetup:(FVPVideoPlayer *)player { int64_t playerIdentifier; if (textureBasedPlayer) { - playerIdentifier = [self.registry registerTexture:(FVPTextureBasedVideoPlayer *)player]; + playerIdentifier = [self.registry registerTexture:textureBasedPlayer]; [textureBasedPlayer setTextureIdentifier:playerIdentifier]; } else { playerIdentifier = self.nextNonTexturePlayerIdentifier--; } + NSObject *messenger = self.messenger; + NSString *channelSuffix = [NSString stringWithFormat:@"%lld", playerIdentifier]; + // Set up the player-specific API handler, and its onDispose unregistration. + SetUpFVPVideoPlayerInstanceApiWithSuffix(messenger, player, channelSuffix); + __weak typeof(self) weakSelf = self; + BOOL isTextureBased = textureBasedPlayer != nil; + player.onDisposed = ^() { + SetUpFVPVideoPlayerInstanceApiWithSuffix(messenger, nil, channelSuffix); + if (isTextureBased) { + [weakSelf.registry unregisterTexture:playerIdentifier]; + } + }; + // Set up the event channel. FlutterEventChannel *eventChannel = [FlutterEventChannel - eventChannelWithName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%lld", - playerIdentifier] - binaryMessenger:_messenger]; + eventChannelWithName:[NSString stringWithFormat:@"flutter.io/videoPlayer/videoEvents%@", + channelSuffix] + binaryMessenger:messenger]; [eventChannel setStreamHandler:player]; player.eventChannel = eventChannel; + self.playersByIdentifier[@(playerIdentifier)] = player; // Ensure that the first frame is drawn once available, even if the video isn't played, since @@ -211,27 +225,20 @@ - (nullable FVPTextureBasedVideoPlayer *)texturePlayerWithOptions: [frameUpdater displayLinkFired]; }]; - __weak typeof(self) weakSelf = self; - void (^onDisposed)(int64_t) = ^(int64_t textureIdentifier) { - [weakSelf.registry unregisterTexture:textureIdentifier]; - }; - if (options.asset) { NSString *assetPath = [self assetPathFromCreationOptions:options]; return [[FVPTextureBasedVideoPlayer alloc] initWithAsset:assetPath frameUpdater:frameUpdater displayLink:displayLink avFactory:self.avFactory - viewProvider:self.viewProvider - onDisposed:onDisposed]; + 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 - onDisposed:onDisposed]; + viewProvider:self.viewProvider]; } return nil; @@ -271,54 +278,6 @@ - (void)disposePlayer:(NSInteger)playerIdentifier error:(FlutterError **)error { [player dispose]; } -- (void)setLooping:(BOOL)isLooping - forPlayer:(NSInteger)playerIdentifier - error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; - player.isLooping = isLooping; -} - -- (void)setVolume:(double)volume - forPlayer:(NSInteger)playerIdentifier - error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; - [player setVolume:volume]; -} - -- (void)setPlaybackSpeed:(double)speed - forPlayer:(NSInteger)playerIdentifier - error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; - [player setPlaybackSpeed:speed]; -} - -- (void)playPlayer:(NSInteger)playerIdentifier error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; - [player play]; -} - -- (nullable NSNumber *)positionForPlayer:(NSInteger)playerIdentifier error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; - return @([player position]); -} - -- (void)seekTo:(NSInteger)position - forPlayer:(NSInteger)playerIdentifier - completion:(nonnull void (^)(FlutterError *_Nullable))completion { - FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; - [player seekTo:position - completionHandler:^(BOOL finished) { - dispatch_async(dispatch_get_main_queue(), ^{ - completion(nil); - }); - }]; -} - -- (void)pausePlayer:(NSInteger)playerIdentifier error:(FlutterError **)error { - FVPVideoPlayer *player = self.playersByIdentifier[@(playerIdentifier)]; - [player pause]; -} - - (void)setMixWithOthers:(BOOL)mixWithOthers error:(FlutterError *_Nullable __autoreleasing *)error { #if TARGET_OS_OSX 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 3e19f986371..229fd78ca24 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 @@ -21,8 +21,7 @@ NS_ASSUME_NONNULL_BEGIN displayLink:(NSObject *)displayLink httpHeaders:(nonnull NSDictionary *)headers avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider - onDisposed:(void (^)(int64_t))onDisposed; + viewProvider:(NSObject *)viewProvider; /// Initializes a new instance of FVPTextureBasedVideoPlayer with the given asset, frame updater, /// display link, AV factory, and registrar. @@ -30,8 +29,7 @@ NS_ASSUME_NONNULL_BEGIN frameUpdater:(FVPFrameUpdater *)frameUpdater displayLink:(NSObject *)displayLink avFactory:(id)avFactory - viewProvider:(NSObject *)viewProvider - onDisposed:(void (^)(int64_t))onDisposed; + 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. 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 9c980fac23f..9a18197ee24 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 @@ -4,6 +4,7 @@ #import +#import "./messages.g.h" #import "FVPAVFactory.h" #import "FVPViewProvider.h" @@ -20,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN /// This class contains all functionalities needed to manage video playback in platform views and is /// typically used alongside FVPNativeVideoViewFactory. If you need to display a video using a /// texture, use FVPTextureBasedVideoPlayer instead. -@interface FVPVideoPlayer : NSObject +@interface FVPVideoPlayer : NSObject /// The Flutter event channel used to communicate with the Flutter engine. @property(nonatomic) FlutterEventChannel *eventChannel; /// The AVPlayer instance used for video playback. @@ -31,6 +32,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) BOOL isLooping; /// The current playback position of the video, in milliseconds. @property(nonatomic, readonly) int64_t position; +/// 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. @@ -53,21 +56,6 @@ NS_ASSUME_NONNULL_BEGIN /// so the channel is going to die or is already dead. - (void)disposeSansEventChannel; -/// Sets the volume of the video player. -- (void)setVolume:(double)volume; - -/// Sets the playback speed of the video player. -- (void)setPlaybackSpeed:(double)speed; - -/// Starts playing the video. -- (void)play; - -/// Pauses the video. -- (void)pause; - -/// Seeks to the specified location in the video and calls the completion handler when done, if one -/// is supplied. -- (void)seekTo:(int64_t)location completionHandler:(void (^_Nullable)(BOOL))completionHandler; @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/FVPVideoPlayer_Test.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Test.h deleted file mode 100644 index 967855d85bd..00000000000 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Test.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "FVPVideoPlayer.h" - -#if TARGET_OS_OSX -#import -#else -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -@interface FVPVideoPlayer () -@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 6a115c6b9ac..1f3966d6e77 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 @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @@ -62,23 +62,6 @@ NSObject *FVPGetMessagesCodec(void); - (nullable NSNumber *)createWithOptions:(FVPCreationOptions *)creationOptions error:(FlutterError *_Nullable *_Nonnull)error; - (void)disposePlayer:(NSInteger)playerId error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setLooping:(BOOL)isLooping - forPlayer:(NSInteger)playerId - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setVolume:(double)volume - forPlayer:(NSInteger)playerId - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)setPlaybackSpeed:(double)speed - forPlayer:(NSInteger)playerId - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)playPlayer:(NSInteger)playerId error:(FlutterError *_Nullable *_Nonnull)error; -/// @return `nil` only when `error != nil`. -- (nullable NSNumber *)positionForPlayer:(NSInteger)playerId - error:(FlutterError *_Nullable *_Nonnull)error; -- (void)seekTo:(NSInteger)position - forPlayer:(NSInteger)playerId - completion:(void (^)(FlutterError *_Nullable))completion; -- (void)pausePlayer:(NSInteger)playerId error:(FlutterError *_Nullable *_Nonnull)error; - (void)setMixWithOthers:(BOOL)mixWithOthers error:(FlutterError *_Nullable *_Nonnull)error; @end @@ -90,4 +73,22 @@ extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix( id binaryMessenger, NSObject *_Nullable api, NSString *messageChannelSuffix); +@protocol FVPVideoPlayerInstanceApi +- (void)setLooping:(BOOL)looping error:(FlutterError *_Nullable *_Nonnull)error; +- (void)setVolume:(double)volume error:(FlutterError *_Nullable *_Nonnull)error; +- (void)setPlaybackSpeed:(double)speed error:(FlutterError *_Nullable *_Nonnull)error; +- (void)playWithError:(FlutterError *_Nullable *_Nonnull)error; +/// @return `nil` only when `error != nil`. +- (nullable NSNumber *)position:(FlutterError *_Nullable *_Nonnull)error; +- (void)seekTo:(NSInteger)position completion:(void (^)(FlutterError *_Nullable))completion; +- (void)pauseWithError:(FlutterError *_Nullable *_Nonnull)error; +@end + +extern void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, + NSObject *_Nullable api); + +extern void SetUpFVPVideoPlayerInstanceApiWithSuffix( + id binaryMessenger, NSObject *_Nullable api, + NSString *messageChannelSuffix); + NS_ASSUME_NONNULL_END 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 83c41211f74..90519755c2e 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 @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "./include/video_player_avfoundation/messages.g.h" @@ -264,46 +264,56 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.setLooping", + @"AVFoundationVideoPlayerApi.setMixWithOthers", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setLooping:forPlayer:error:)], + NSCAssert([api respondsToSelector:@selector(setMixWithOthers:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(setLooping:forPlayer:error:)", + @"@selector(setMixWithOthers:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - BOOL arg_isLooping = [GetNullableObjectAtIndex(args, 0) boolValue]; - NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 1) integerValue]; + BOOL arg_mixWithOthers = [GetNullableObjectAtIndex(args, 0) boolValue]; FlutterError *error; - [api setLooping:arg_isLooping forPlayer:arg_playerId error:&error]; + [api setMixWithOthers:arg_mixWithOthers error:&error]; callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; } } +} +void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, + NSObject *api) { + SetUpFVPVideoPlayerInstanceApiWithSuffix(binaryMessenger, api, @""); +} + +void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryMessenger, + NSObject *api, + NSString *messageChannelSuffix) { + messageChannelSuffix = messageChannelSuffix.length > 0 + ? [NSString stringWithFormat:@".%@", messageChannelSuffix] + : @""; { FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.setVolume", + @"VideoPlayerInstanceApi.setLooping", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setVolume:forPlayer:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(setVolume:forPlayer:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(setLooping:error:)], + @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setLooping:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - double arg_volume = [GetNullableObjectAtIndex(args, 0) doubleValue]; - NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 1) integerValue]; + BOOL arg_looping = [GetNullableObjectAtIndex(args, 0) boolValue]; FlutterError *error; - [api setVolume:arg_volume forPlayer:arg_playerId error:&error]; + [api setLooping:arg_looping error:&error]; callback(wrapResult(nil, error)); }]; } else { @@ -314,21 +324,20 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.setPlaybackSpeed", + @"VideoPlayerInstanceApi.setVolume", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setPlaybackSpeed:forPlayer:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(setPlaybackSpeed:forPlayer:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(setVolume:error:)], + @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setVolume:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - double arg_speed = [GetNullableObjectAtIndex(args, 0) doubleValue]; - NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 1) integerValue]; + double arg_volume = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; - [api setPlaybackSpeed:arg_speed forPlayer:arg_playerId error:&error]; + [api setVolume:arg_volume error:&error]; callback(wrapResult(nil, error)); }]; } else { @@ -339,20 +348,20 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.play", + @"VideoPlayerInstanceApi.setPlaybackSpeed", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(playPlayer:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(playPlayer:error:)", - api); + NSCAssert([api respondsToSelector:@selector(setPlaybackSpeed:error:)], + @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to " + @"@selector(setPlaybackSpeed:error:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 0) integerValue]; + double arg_speed = [GetNullableObjectAtIndex(args, 0) doubleValue]; FlutterError *error; - [api playPlayer:arg_playerId error:&error]; + [api setPlaybackSpeed:arg_speed error:&error]; callback(wrapResult(nil, error)); }]; } else { @@ -363,21 +372,18 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.getPosition", + @"VideoPlayerInstanceApi.play", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(positionForPlayer:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(positionForPlayer:error:)", + NSCAssert([api respondsToSelector:@selector(playWithError:)], + @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(playWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 0) integerValue]; FlutterError *error; - NSNumber *output = [api positionForPlayer:arg_playerId error:&error]; - callback(wrapResult(output, error)); + [api playWithError:&error]; + callback(wrapResult(nil, error)); }]; } else { [channel setMessageHandler:nil]; @@ -387,24 +393,17 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.seekTo", + @"VideoPlayerInstanceApi.getPosition", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(seekTo:forPlayer:completion:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(seekTo:forPlayer:completion:)", - api); + NSCAssert([api respondsToSelector:@selector(position:)], + @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(position:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - NSInteger arg_position = [GetNullableObjectAtIndex(args, 0) integerValue]; - NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 1) integerValue]; - [api seekTo:arg_position - forPlayer:arg_playerId - completion:^(FlutterError *_Nullable error) { - callback(wrapResult(nil, error)); - }]; + FlutterError *error; + NSNumber *output = [api position:&error]; + callback(wrapResult(output, error)); }]; } else { [channel setMessageHandler:nil]; @@ -414,21 +413,22 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.pause", + @"VideoPlayerInstanceApi.seekTo", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(pausePlayer:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(pausePlayer:error:)", - api); + NSCAssert( + [api respondsToSelector:@selector(seekTo:completion:)], + @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(seekTo:completion:)", + api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; - NSInteger arg_playerId = [GetNullableObjectAtIndex(args, 0) integerValue]; - FlutterError *error; - [api pausePlayer:arg_playerId error:&error]; - callback(wrapResult(nil, error)); + NSInteger arg_position = [GetNullableObjectAtIndex(args, 0) integerValue]; + [api seekTo:arg_position + completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; }]; } else { [channel setMessageHandler:nil]; @@ -438,20 +438,17 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.setMixWithOthers", + @"VideoPlayerInstanceApi.pause", messageChannelSuffix] binaryMessenger:binaryMessenger codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setMixWithOthers:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(setMixWithOthers:error:)", + NSCAssert([api respondsToSelector:@selector(pauseWithError:)], + @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(pauseWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - NSArray *args = message; - BOOL arg_mixWithOthers = [GetNullableObjectAtIndex(args, 0) boolValue]; FlutterError *error; - [api setMixWithOthers:arg_mixWithOthers error:&error]; + [api pauseWithError:&error]; callback(wrapResult(nil, error)); }]; } else { 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 0067b639c96..6fc41eca1b9 100644 --- a/packages/video_player/video_player_avfoundation/example/lib/main.dart +++ b/packages/video_player/video_player_avfoundation/example/lib/main.dart @@ -153,8 +153,10 @@ class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { _controller.addListener(() { setState(() {}); }); - _controller.initialize().then((_) => setState(() {})); - _controller.play(); + _controller.initialize().then((_) { + _controller.play(); + setState(() {}); + }); } @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 ec790187abf..a991e3e594c 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 @@ -11,10 +11,26 @@ import 'package:video_player_platform_interface/video_player_platform_interface. import 'messages.g.dart'; +/// The non-test implementation of `_apiProvider`. +VideoPlayerInstanceApi _productionApiProvider(int playerId) { + return VideoPlayerInstanceApi(messageChannelSuffix: playerId.toString()); +} + /// An iOS implementation of [VideoPlayerPlatform] that uses the /// Pigeon-generated [VideoPlayerApi]. class AVFoundationVideoPlayer extends VideoPlayerPlatform { - final AVFoundationVideoPlayerApi _api = AVFoundationVideoPlayerApi(); + /// Creates a new AVFoundation-based video player implementation instance. + AVFoundationVideoPlayer({ + @visibleForTesting AVFoundationVideoPlayerApi? pluginApi, + @visibleForTesting + VideoPlayerInstanceApi Function(int playerId)? playerProvider, + }) : _api = pluginApi ?? AVFoundationVideoPlayerApi(), + _playerProvider = playerProvider ?? _productionApiProvider; + + final AVFoundationVideoPlayerApi _api; + // A method to create VideoPlayerInstanceApi instances, which can be + // overridden for testing. + final VideoPlayerInstanceApi Function(int mapId) _playerProvider; /// A map that associates player ID with a view state. /// This is used to determine which view type to use when building a view. @@ -22,6 +38,9 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { final Map playerViewStates = {}; + final Map _players = + {}; + /// Registers this class as the default instance of [VideoPlayerPlatform]. static void registerWith() { VideoPlayerPlatform.instance = AVFoundationVideoPlayer(); @@ -36,6 +55,7 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { Future dispose(int playerId) async { await _api.dispose(playerId); playerViewStates.remove(playerId); + _players.remove(playerId); } @override @@ -92,45 +112,55 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { VideoPlayerTextureViewState(textureId: playerId), VideoViewType.platformView => const VideoPlayerPlatformViewState(), }; + ensureApiInitialized(playerId); return playerId; } + /// Returns the API instance for [playerId], creating it if it doesn't already + /// exist. + @visibleForTesting + VideoPlayerInstanceApi ensureApiInitialized(int playerId) { + return _players.putIfAbsent(playerId, () { + return _playerProvider(playerId); + }); + } + @override Future setLooping(int playerId, bool looping) { - return _api.setLooping(looping, playerId); + return _playerWith(id: playerId).setLooping(looping); } @override Future play(int playerId) { - return _api.play(playerId); + return _playerWith(id: playerId).play(); } @override Future pause(int playerId) { - return _api.pause(playerId); + return _playerWith(id: playerId).pause(); } @override Future setVolume(int playerId, double volume) { - return _api.setVolume(volume, playerId); + return _playerWith(id: playerId).setVolume(volume); } @override Future setPlaybackSpeed(int playerId, double speed) { assert(speed > 0); - return _api.setPlaybackSpeed(speed, playerId); + return _playerWith(id: playerId).setPlaybackSpeed(speed); } @override Future seekTo(int playerId, Duration position) { - return _api.seekTo(position.inMilliseconds, playerId); + return _playerWith(id: playerId).seekTo(position.inMilliseconds); } @override Future getPosition(int playerId) async { - final int position = await _api.getPosition(playerId); + final int position = await _playerWith(id: playerId).getPosition(); return Duration(milliseconds: position); } @@ -219,6 +249,11 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { return EventChannel('flutter.io/videoPlayer/videoEvents$playerId'); } + VideoPlayerInstanceApi _playerWith({required int id}) { + final VideoPlayerInstanceApi? player = _players[id]; + return player ?? (throw StateError('No active player with ID $id.')); + } + static const Map _videoFormatStringMap = { VideoFormat.ss: 'ss', 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 c1d16ecce5e..cc184ae7db0 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 @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -18,15 +18,19 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse( - {Object? result, PlatformException? error, bool empty = false}) { - if (empty) { - return []; +bool _deepEquals(Object? a, Object? b) { + if (a is List && b is List) { + return a.length == b.length && + a.indexed + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); } - if (error == null) { - return [result]; + if (a is Map && b is Map) { + return a.length == b.length && + a.entries.every((MapEntry entry) => + (b as Map).containsKey(entry.key) && + _deepEquals(entry.value, b[entry.key])); } - return [error.code, error.message, error.details]; + return a == b; } /// Pigeon equivalent of VideoViewType. @@ -43,18 +47,39 @@ class PlatformVideoViewCreationParams { int playerId; - Object encode() { + List _toList() { return [ playerId, ]; } + Object encode() { + return _toList(); + } + static PlatformVideoViewCreationParams decode(Object result) { result as List; return PlatformVideoViewCreationParams( playerId: result[0]! as int, ); } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PlatformVideoViewCreationParams || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); } class CreationOptions { @@ -79,7 +104,7 @@ class CreationOptions { PlatformVideoViewType viewType; - Object encode() { + List _toList() { return [ asset, uri, @@ -90,6 +115,10 @@ class CreationOptions { ]; } + Object encode() { + return _toList(); + } + static CreationOptions decode(Object result) { result as List; return CreationOptions( @@ -102,6 +131,22 @@ class CreationOptions { viewType: result[5]! as PlatformVideoViewType, ); } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! CreationOptions || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); } class _PigeonCodec extends StandardMessageCodec { @@ -165,8 +210,9 @@ class AVFoundationVideoPlayerApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -189,8 +235,10 @@ class AVFoundationVideoPlayerApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([creationOptions]) as List?; + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([creationOptions]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -218,8 +266,10 @@ class AVFoundationVideoPlayerApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([playerId]); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -233,17 +283,19 @@ class AVFoundationVideoPlayerApi { } } - Future setLooping(bool isLooping, int playerId) async { + Future setMixWithOthers(bool mixWithOthers) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setLooping$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([isLooping, playerId]) as List?; + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([mixWithOthers]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -256,18 +308,36 @@ class AVFoundationVideoPlayerApi { return; } } +} - Future setVolume(double volume, int playerId) async { +class VideoPlayerInstanceApi { + /// Constructor for [VideoPlayerInstanceApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + VideoPlayerInstanceApi( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future setLooping(bool looping) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setVolume$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setLooping$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([volume, playerId]) as List?; + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([looping]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -281,17 +351,19 @@ class AVFoundationVideoPlayerApi { } } - Future setPlaybackSpeed(double speed, int playerId) async { + Future setVolume(double volume) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setVolume$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([speed, playerId]) as List?; + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([volume]); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -305,17 +377,19 @@ class AVFoundationVideoPlayerApi { } } - Future play(int playerId) async { + Future setPlaybackSpeed(double speed) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.play$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([speed]); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -329,17 +403,18 @@ class AVFoundationVideoPlayerApi { } } - Future getPosition(int playerId) async { + Future play() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getPosition$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.play$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -348,27 +423,23 @@ class AVFoundationVideoPlayerApi { 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?)!; + return; } } - Future seekTo(int position, int playerId) async { + Future getPosition() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekTo$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getPosition$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([position, playerId]) as List?; + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -377,22 +448,29 @@ class AVFoundationVideoPlayerApi { 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; + return (pigeonVar_replyList[0] as int?)!; } } - Future pause(int playerId) async { + Future seekTo(int position) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.pause$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = + pigeonVar_channel.send([position]); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -406,17 +484,18 @@ class AVFoundationVideoPlayerApi { } } - Future setMixWithOthers(bool mixWithOthers) async { + Future pause() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([mixWithOthers]) as List?; + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index ffa2f968c32..c01fc094f1f 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -6,7 +6,6 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon(PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.g.dart', objcHeaderOut: 'darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/messages.g.h', objcSourceOut: @@ -47,7 +46,7 @@ class CreationOptions { PlatformVideoViewType viewType; } -@HostApi(dartHostTestHandler: 'TestHostVideoPlayerApi') +@HostApi() abstract class AVFoundationVideoPlayerApi { @ObjCSelector('initialize') void initialize(); @@ -56,21 +55,23 @@ abstract class AVFoundationVideoPlayerApi { int create(CreationOptions creationOptions); @ObjCSelector('disposePlayer:') void dispose(int playerId); - @ObjCSelector('setLooping:forPlayer:') - void setLooping(bool isLooping, int playerId); - @ObjCSelector('setVolume:forPlayer:') - void setVolume(double volume, int playerId); - @ObjCSelector('setPlaybackSpeed:forPlayer:') - void setPlaybackSpeed(double speed, int playerId); - @ObjCSelector('playPlayer:') - void play(int playerId); - @ObjCSelector('positionForPlayer:') - int getPosition(int playerId); - @async - @ObjCSelector('seekTo:forPlayer:') - void seekTo(int position, int playerId); - @ObjCSelector('pausePlayer:') - void pause(int playerId); @ObjCSelector('setMixWithOthers:') void setMixWithOthers(bool mixWithOthers); } + +@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(); +} diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index ded01c7a74d..89bc263053a 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.7.2 +version: 2.7.3 environment: sdk: ^3.6.0 @@ -27,9 +27,11 @@ dependencies: video_player_platform_interface: ^6.3.0 dev_dependencies: + build_runner: ^2.3.3 flutter_test: sdk: flutter - pigeon: ^22.4.2 + mockito: ^5.4.4 + pigeon: ^25.5.0 topics: - video 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 3d02cef3cbe..9e62654ebf5 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 @@ -5,365 +5,470 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; import 'package:video_player_avfoundation/src/messages.g.dart'; import 'package:video_player_avfoundation/video_player_avfoundation.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; -import 'test_api.g.dart'; - -class _ApiLogger implements TestHostVideoPlayerApi { - final List log = []; - int? playerId; - CreationOptions? creationOptions; - int? position; - bool? looping; - double? volume; - double? playbackSpeed; - bool? mixWithOthers; - - @override - int create(CreationOptions options) { - log.add('create'); - creationOptions = options; - return 3; - } - - @override - void dispose(int playerId) { - log.add('dispose'); - this.playerId = playerId; - } - - @override - void initialize() { - log.add('init'); - } - - @override - void pause(int playerId) { - log.add('pause'); - this.playerId = playerId; - } - - @override - void play(int playerId) { - log.add('play'); - this.playerId = playerId; - } - - @override - void setMixWithOthers(bool enabled) { - log.add('setMixWithOthers'); - mixWithOthers = enabled; - } - - @override - int getPosition(int playerId) { - log.add('position'); - this.playerId = playerId; - return 234; - } - - @override - Future seekTo(int position, int playerId) async { - log.add('seekTo'); - this.position = position; - this.playerId = playerId; - } - - @override - void setLooping(bool loop, int playerId) { - log.add('setLooping'); - looping = loop; - this.playerId = playerId; - } - - @override - void setVolume(double volume, int playerId) { - log.add('setVolume'); - this.volume = volume; - this.playerId = playerId; - } - - @override - void setPlaybackSpeed(double speed, int playerId) { - log.add('setPlaybackSpeed'); - playbackSpeed = speed; - this.playerId = playerId; - } -} +import 'avfoundation_video_player_test.mocks.dart'; +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), +]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); + ( + AVFoundationVideoPlayer, + MockAVFoundationVideoPlayerApi, + MockVideoPlayerInstanceApi + ) setUpMockPlayer({required int playerId}) { + final MockAVFoundationVideoPlayerApi pluginApi = + MockAVFoundationVideoPlayerApi(); + final MockVideoPlayerInstanceApi instanceApi = MockVideoPlayerInstanceApi(); + final AVFoundationVideoPlayer player = AVFoundationVideoPlayer( + pluginApi: pluginApi, + playerProvider: (_) => instanceApi, + ); + player.ensureApiInitialized(playerId); + return (player, pluginApi, instanceApi); + } + test('registration', () async { AVFoundationVideoPlayer.registerWith(); expect(VideoPlayerPlatform.instance, isA()); }); - group('$AVFoundationVideoPlayer', () { - final AVFoundationVideoPlayer player = AVFoundationVideoPlayer(); - late _ApiLogger log; - - setUp(() { - log = _ApiLogger(); - TestHostVideoPlayerApi.setUp(log); - }); - + group('AVFoundationVideoPlayer', () { test('init', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); await player.init(); - expect( - log.log.last, - 'init', - ); + + verify(api.initialize()); }); test('dispose', () async { - player.playerViewStates[1] = - const VideoPlayerTextureViewState(textureId: 1); - + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); await player.dispose(1); - expect(log.log.last, 'dispose'); - expect(log.playerId, 1); + + verify(api.dispose(1)); expect(player.playerViewStates, isEmpty); }); test('create with asset', () async { - final int? playerId = await player.create(DataSource( - sourceType: DataSourceType.asset, - asset: 'someAsset', - package: 'somePackage', - )); - expect(log.log.last, 'create'); - expect(log.creationOptions?.asset, 'someAsset'); - expect(log.creationOptions?.packageName, 'somePackage'); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String asset = 'someAsset'; + const String package = 'somePackage'; + final int? playerId = await player.create( + DataSource( + sourceType: DataSourceType.asset, + asset: asset, + package: package, + ), + ); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.asset, asset); + expect(creationOptions.packageName, package); + expect(playerId, newPlayerId); + expect(player.playerViewStates[newPlayerId], + const VideoPlayerTextureViewState(textureId: newPlayerId)); }); test('create with network', () async { - final int? playerId = await player.create(DataSource( - sourceType: DataSourceType.network, - uri: 'someUri', - formatHint: VideoFormat.dash, - )); - expect(log.log.last, 'create'); - expect(log.creationOptions?.asset, null); - expect(log.creationOptions?.uri, 'someUri'); - expect(log.creationOptions?.packageName, null); - expect(log.creationOptions?.formatHint, 'dash'); - expect(log.creationOptions?.httpHeaders, {}); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String uri = 'https://example.com'; + final int? playerId = await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: uri, + formatHint: VideoFormat.dash, + ), + ); + + 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], + const VideoPlayerTextureViewState(textureId: newPlayerId)); }); - test('create with network (some headers)', () async { - final int? playerId = await player.create(DataSource( - sourceType: DataSourceType.network, - uri: 'someUri', - httpHeaders: {'Authorization': 'Bearer token'}, - )); - expect(log.log.last, 'create'); - expect(log.creationOptions?.asset, null); - expect(log.creationOptions?.uri, 'someUri'); - expect(log.creationOptions?.packageName, null); - expect(log.creationOptions?.formatHint, null); - expect(log.creationOptions?.httpHeaders, - {'Authorization': 'Bearer token'}); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + test('create with network passes headers', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + when(api.create(any)).thenAnswer((_) async => 2); + + const Map headers = { + 'Authorization': 'Bearer token', + }; + await player.create( + DataSource( + sourceType: DataSourceType.network, + uri: 'https://example.com', + httpHeaders: headers, + ), + ); + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.httpHeaders, headers); }); test('create with file', () async { - final int? playerId = await player.create(DataSource( - sourceType: DataSourceType.file, - uri: 'someUri', - )); - expect(log.log.last, 'create'); - expect(log.creationOptions?.uri, 'someUri'); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String fileUri = 'file:///foo/bar'; + final int? playerId = await player.create( + DataSource(sourceType: DataSourceType.file, uri: fileUri), + ); + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.uri, fileUri); + expect(playerId, newPlayerId); + expect(player.playerViewStates[newPlayerId], + const VideoPlayerTextureViewState(textureId: newPlayerId)); }); test('createWithOptions with asset', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String asset = 'someAsset'; + const String package = 'somePackage'; final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.asset, - asset: 'someAsset', - package: 'somePackage', + asset: asset, + package: package, ), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.creationOptions?.asset, 'someAsset'); - expect(log.creationOptions?.packageName, 'somePackage'); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.asset, asset); + expect(creationOptions.packageName, package); + expect(playerId, newPlayerId); + expect(player.playerViewStates[newPlayerId], + const VideoPlayerTextureViewState(textureId: newPlayerId)); }); test('createWithOptions with network', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String uri = 'https://example.com'; final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.network, - uri: 'someUri', + uri: uri, formatHint: VideoFormat.dash, ), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.creationOptions?.asset, null); - expect(log.creationOptions?.uri, 'someUri'); - expect(log.creationOptions?.packageName, null); - expect(log.creationOptions?.formatHint, 'dash'); - expect(log.creationOptions?.httpHeaders, {}); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + + 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], + const VideoPlayerTextureViewState(textureId: newPlayerId)); }); - test('createWithOptions with network (some headers)', () async { + test('createWithOptions with network passes headers', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const Map headers = { + 'Authorization': 'Bearer token', + }; final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.network, - uri: 'someUri', - httpHeaders: {'Authorization': 'Bearer token'}, + uri: 'https://example.com', + httpHeaders: headers, ), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.creationOptions?.asset, null); - expect(log.creationOptions?.uri, 'someUri'); - expect(log.creationOptions?.packageName, null); - expect(log.creationOptions?.formatHint, null); - expect(log.creationOptions?.httpHeaders, - {'Authorization': 'Bearer token'}); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.httpHeaders, headers); + expect(playerId, newPlayerId); }); test('createWithOptions with file', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String fileUri = 'file:///foo/bar'; final int? playerId = await player.createWithOptions( VideoCreationOptions( - dataSource: DataSource( - sourceType: DataSourceType.file, - uri: 'someUri', - ), + dataSource: DataSource(sourceType: DataSourceType.file, uri: fileUri), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.creationOptions?.uri, 'someUri'); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.uri, fileUri); + expect(playerId, newPlayerId); + expect(player.playerViewStates[newPlayerId], + const VideoPlayerTextureViewState(textureId: newPlayerId)); }); test('createWithOptions with platform view on iOS', () async { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.file, - uri: 'someUri', + uri: 'file:///foo/bar', ), viewType: VideoViewType.platformView, ), ); - expect(log.log.last, 'create'); - expect(log.creationOptions?.viewType, PlatformVideoViewType.platformView); - expect(playerId, 3); - expect(player.playerViewStates[3], const VideoPlayerPlatformViewState()); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.viewType, PlatformVideoViewType.platformView); + expect(playerId, newPlayerId); + expect(player.playerViewStates[newPlayerId], + const VideoPlayerPlatformViewState()); }); test('createWithOptions with platform view uses texture view on MacOS', () async { debugDefaultTargetPlatformOverride = TargetPlatform.macOS; + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.file, - uri: 'someUri', + uri: 'file:///foo/bar', ), viewType: VideoViewType.platformView, ), ); - expect(log.log.last, 'create'); - expect(log.creationOptions?.viewType, PlatformVideoViewType.textureView); - expect(playerId, 3); - expect(player.playerViewStates[3], - const VideoPlayerTextureViewState(textureId: 3)); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreationOptions creationOptions = + verification.captured[0] as CreationOptions; + expect(creationOptions.viewType, PlatformVideoViewType.textureView); + expect(playerId, newPlayerId); + expect(player.playerViewStates[newPlayerId], + const VideoPlayerTextureViewState(textureId: newPlayerId)); }); test('setLooping', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); await player.setLooping(1, true); - expect(log.log.last, 'setLooping'); - expect(log.playerId, 1); - expect(log.looping, true); + + verify(playerApi.setLooping(true)); }); test('play', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); await player.play(1); - expect(log.log.last, 'play'); - expect(log.playerId, 1); + + verify(playerApi.play()); }); test('pause', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); await player.pause(1); - expect(log.log.last, 'pause'); - expect(log.playerId, 1); - }); - test('setMixWithOthers', () async { - await player.setMixWithOthers(true); - expect(log.log.last, 'setMixWithOthers'); - expect(log.mixWithOthers, true); + verify(playerApi.pause()); + }); - await player.setMixWithOthers(false); - expect(log.log.last, 'setMixWithOthers'); - expect(log.mixWithOthers, false); + group('setMixWithOthers', () { + test('passes true', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + await player.setMixWithOthers(true); + + verify(api.setMixWithOthers(true)); + }); + + test('passes false', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + await player.setMixWithOthers(false); + + verify(api.setMixWithOthers(false)); + }); }); test('setVolume', () async { - await player.setVolume(1, 0.7); - expect(log.log.last, 'setVolume'); - expect(log.playerId, 1); - expect(log.volume, 0.7); + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const double volume = 0.7; + await player.setVolume(1, volume); + + verify(playerApi.setVolume(volume)); }); test('setPlaybackSpeed', () async { - await player.setPlaybackSpeed(1, 1.5); - expect(log.log.last, 'setPlaybackSpeed'); - expect(log.playerId, 1); - expect(log.playbackSpeed, 1.5); + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const double speed = 1.5; + await player.setPlaybackSpeed(1, speed); + + verify(playerApi.setPlaybackSpeed(speed)); }); test('seekTo', () async { - await player.seekTo(1, const Duration(milliseconds: 12345)); - expect(log.log.last, 'seekTo'); - expect(log.playerId, 1); - expect(log.position, 12345); + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const int positionMilliseconds = 12345; + await player.seekTo( + 1, + const Duration(milliseconds: positionMilliseconds), + ); + + verify(playerApi.seekTo(positionMilliseconds)); }); test('getPosition', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const int positionMilliseconds = 12345; + when( + playerApi.getPosition(), + ).thenAnswer((_) async => positionMilliseconds); + final Duration position = await player.getPosition(1); - expect(log.log.last, 'position'); - expect(log.playerId, 1); - expect(position, const Duration(milliseconds: 234)); + expect(position, const Duration(milliseconds: positionMilliseconds)); }); test('videoEventsFor', () async { + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); const String mockChannel = 'flutter.io/videoPlayer/videoEvents123'; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMessageHandler( 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 new file mode 100644 index 00000000000..1e9968cf0d7 --- /dev/null +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart @@ -0,0 +1,173 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in video_player_avfoundation/test/avfoundation_video_player_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; +import 'package:video_player_avfoundation/src/messages.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [AVFoundationVideoPlayerApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAVFoundationVideoPlayerApi extends _i1.Mock + implements _i2.AVFoundationVideoPlayerApi { + @override + String get pigeonVar_messageChannelSuffix => (super.noSuchMethod( + Invocation.getter(#pigeonVar_messageChannelSuffix), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + returnValueForMissingStub: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + ) as String); + + @override + _i4.Future initialize() => (super.noSuchMethod( + Invocation.method( + #initialize, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future create(_i2.CreationOptions? creationOptions) => + (super.noSuchMethod( + Invocation.method( + #create, + [creationOptions], + ), + returnValue: _i4.Future.value(0), + returnValueForMissingStub: _i4.Future.value(0), + ) as _i4.Future); + + @override + _i4.Future dispose(int? playerId) => (super.noSuchMethod( + Invocation.method( + #dispose, + [playerId], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setMixWithOthers(bool? mixWithOthers) => (super.noSuchMethod( + Invocation.method( + #setMixWithOthers, + [mixWithOthers], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); +} + +/// A class which mocks [VideoPlayerInstanceApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoPlayerInstanceApi extends _i1.Mock + implements _i2.VideoPlayerInstanceApi { + @override + String get pigeonVar_messageChannelSuffix => (super.noSuchMethod( + Invocation.getter(#pigeonVar_messageChannelSuffix), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + returnValueForMissingStub: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + ) as String); + + @override + _i4.Future setLooping(bool? looping) => (super.noSuchMethod( + Invocation.method( + #setLooping, + [looping], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setVolume(double? volume) => (super.noSuchMethod( + Invocation.method( + #setVolume, + [volume], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future setPlaybackSpeed(double? speed) => (super.noSuchMethod( + Invocation.method( + #setPlaybackSpeed, + [speed], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future play() => (super.noSuchMethod( + Invocation.method( + #play, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future getPosition() => (super.noSuchMethod( + Invocation.method( + #getPosition, + [], + ), + returnValue: _i4.Future.value(0), + returnValueForMissingStub: _i4.Future.value(0), + ) as _i4.Future); + + @override + _i4.Future seekTo(int? position) => (super.noSuchMethod( + Invocation.method( + #seekTo, + [position], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + + @override + _i4.Future pause() => (super.noSuchMethod( + Invocation.method( + #pause, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); +} diff --git a/packages/video_player/video_player_avfoundation/test/test_api.g.dart b/packages/video_player/video_player_avfoundation/test/test_api.g.dart deleted file mode 100644 index 38ed42f1f9e..00000000000 --- a/packages/video_player/video_player_avfoundation/test/test_api.g.dart +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers -// ignore_for_file: avoid_relative_lib_imports -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:video_player_avfoundation/src/messages.g.dart'; - -class _PigeonCodec extends StandardMessageCodec { - const _PigeonCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is int) { - buffer.putUint8(4); - buffer.putInt64(value); - } else if (value is PlatformVideoViewType) { - buffer.putUint8(129); - writeValue(buffer, value.index); - } else if (value is PlatformVideoViewCreationParams) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is CreationOptions) { - buffer.putUint8(131); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 129: - final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformVideoViewType.values[value]; - case 130: - return PlatformVideoViewCreationParams.decode(readValue(buffer)!); - case 131: - return CreationOptions.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - -abstract class TestHostVideoPlayerApi { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => - TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - - void initialize(); - - int create(CreationOptions creationOptions); - - void dispose(int playerId); - - void setLooping(bool isLooping, int playerId); - - void setVolume(double volume, int playerId); - - void setPlaybackSpeed(double speed, int playerId); - - void play(int playerId); - - int getPosition(int playerId); - - Future seekTo(int position, int playerId); - - void pause(int playerId); - - void setMixWithOthers(bool mixWithOthers); - - static void setUp( - TestHostVideoPlayerApi? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - try { - api.initialize(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.create$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.create was null.'); - final List args = (message as List?)!; - final CreationOptions? arg_creationOptions = - (args[0] as CreationOptions?); - assert(arg_creationOptions != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.create was null, expected non-null CreationOptions.'); - try { - final int output = api.create(arg_creationOptions!); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.dispose$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.dispose was null.'); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.dispose was null, expected non-null int.'); - try { - api.dispose(arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setLooping$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setLooping was null.'); - final List args = (message as List?)!; - final bool? arg_isLooping = (args[0] as bool?); - assert(arg_isLooping != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setLooping was null, expected non-null bool.'); - final int? arg_playerId = (args[1] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setLooping was null, expected non-null int.'); - try { - api.setLooping(arg_isLooping!, arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setVolume$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setVolume was null.'); - final List args = (message as List?)!; - final double? arg_volume = (args[0] as double?); - assert(arg_volume != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setVolume was null, expected non-null double.'); - final int? arg_playerId = (args[1] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setVolume was null, expected non-null int.'); - try { - api.setVolume(arg_volume!, arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setPlaybackSpeed$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setPlaybackSpeed was null.'); - final List args = (message as List?)!; - final double? arg_speed = (args[0] as double?); - assert(arg_speed != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setPlaybackSpeed was null, expected non-null double.'); - final int? arg_playerId = (args[1] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setPlaybackSpeed was null, expected non-null int.'); - try { - api.setPlaybackSpeed(arg_speed!, arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.play$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.play was null.'); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.play was null, expected non-null int.'); - try { - api.play(arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getPosition$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getPosition was null.'); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getPosition was null, expected non-null int.'); - try { - final int output = api.getPosition(arg_playerId!); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekTo$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekTo was null.'); - final List args = (message as List?)!; - final int? arg_position = (args[0] as int?); - assert(arg_position != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekTo was null, expected non-null int.'); - final int? arg_playerId = (args[1] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.seekTo was null, expected non-null int.'); - try { - await api.seekTo(arg_position!, arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.pause$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.pause was null.'); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert(arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.pause was null, expected non-null int.'); - try { - api.pause(arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers was null.'); - final List args = (message as List?)!; - final bool? arg_mixWithOthers = (args[0] as bool?); - assert(arg_mixWithOthers != null, - 'Argument for dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers was null, expected non-null bool.'); - try { - api.setMixWithOthers(arg_mixWithOthers!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - } -}