From e9604dd415ed75a3a0bf9fdd8da3660847181d34 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Sat, 28 Jun 2025 09:21:28 -0400 Subject: [PATCH 1/8] Implement per-instance channels --- .../darwin/RunnerTests/VideoPlayerTests.m | 101 ++-- .../FVPTextureBasedVideoPlayer.m | 23 +- .../FVPVideoPlayer.m | 77 ++- .../FVPVideoPlayerPlugin.m | 81 +-- .../FVPTextureBasedVideoPlayer.h | 6 +- .../FVPVideoPlayer.h | 2 + .../FVPVideoPlayer_Test.h | 3 +- .../video_player_avfoundation/messages.g.h | 37 +- .../video_player_avfoundation/messages.g.m | 131 ++-- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 27 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 + .../example/lib/main.dart | 6 +- .../lib/src/avfoundation_video_player.dart | 57 +- .../lib/src/messages.g.dart | 179 ++++-- .../pigeons/messages.dart | 35 +- .../video_player_avfoundation/pubspec.yaml | 4 +- .../test/avfoundation_video_player_test.dart | 569 +++++++++++------- .../avfoundation_video_player_test.mocks.dart | 173 ++++++ .../test/test_api.g.dart | 447 -------------- 21 files changed, 930 insertions(+), 1034 deletions(-) create mode 100644 packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart delete mode 100644 packages/video_player/video_player_avfoundation/test/test_api.g.dart 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 221991c19eb..d33772a94ec 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -311,28 +311,23 @@ - (void)testSeekToWhilePausedStartsDisplayLinkTemporarily { FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; + // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. + 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. OCMVerify([mockDisplayLink setRunning:YES]); - 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]) @@ -435,28 +430,22 @@ - (void)testSeekToWhilePlayingDoesNotStopDisplayLink { FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; + // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. + 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]; OCMVerify([mockDisplayLink setRunning:YES]); - 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() @@ -504,10 +493,14 @@ - (void)testPauseWhileWaitingForFrameDoesNotStopDisplayLink { FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; + // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. + 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. OCMVerify(never(), [mockDisplayLink setRunning:NO]); @@ -687,13 +680,16 @@ - (void)testSeekToleranceWhenNotSeekingToEnd { FlutterError *createError; NSNumber *playerIdentifier = [pluginWithMockAVPlayer createWithOptions:create error:&createError]; + // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. + FVPTextureBasedVideoPlayer *player = + (FVPTextureBasedVideoPlayer *)pluginWithMockAVPlayer.playersByIdentifier[playerIdentifier]; + XCTestExpectation *initializedExpectation = [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) { + [initializedExpectation fulfill]; + }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; XCTAssertEqual([stubAVPlayer.beforeTolerance intValue], 0); @@ -726,14 +722,17 @@ - (void)testSeekToleranceWhenSeekingToEnd { FlutterError *createError; NSNumber *playerIdentifier = [pluginWithMockAVPlayer createWithOptions:create error:&createError]; + // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. + FVPTextureBasedVideoPlayer *player = + (FVPTextureBasedVideoPlayer *)pluginWithMockAVPlayer.playersByIdentifier[playerIdentifier]; + XCTestExpectation *initializedExpectation = [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) { + [initializedExpectation fulfill]; + }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; XCTAssertGreaterThan([stubAVPlayer.beforeTolerance intValue], 0); XCTAssertGreaterThan([stubAVPlayer.afterTolerance intValue], 0); @@ -754,6 +753,7 @@ - (void)testSeekToleranceWhenSeekingToEnd { viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; + // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. FVPVideoPlayer *player = videoPlayerPlugin.playersByIdentifier[playerIdentifier]; XCTAssertNotNil(player); @@ -776,15 +776,15 @@ - (void)testSeekToleranceWhenSeekingToEnd { XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused); // Change playback speed. - [videoPlayerPlugin setPlaybackSpeed:2 forPlayer:playerIdentifier.integerValue error:&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); @@ -982,6 +982,7 @@ - (void)testUpdatePlayingStateShouldNotResetRate { httpHeaders:@{} viewType:FVPPlatformVideoViewTypeTextureView]; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&error]; + // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. FVPVideoPlayer *player = videoPlayerPlugin.playersByIdentifier[playerIdentifier]; XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"]; @@ -993,8 +994,8 @@ - (void)testUpdatePlayingStateShouldNotResetRate { }]; [self waitForExpectationsWithTimeout:10 handler:nil]; - [videoPlayerPlugin setPlaybackSpeed:2 forPlayer:playerIdentifier.integerValue error:&error]; - [videoPlayerPlugin playPlayer:playerIdentifier.integerValue error:&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 0fd1ca77f07..bc268392fb5 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:(FVPDisplayLink *)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:(FVPDisplayLink *)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:(FVPDisplayLink *)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 @@ -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..c842722671d 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 @@ -104,6 +104,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 +424,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 +498,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 6292422f935..fdc3b605944 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 @@ -108,18 +108,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 @@ -204,27 +218,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; @@ -264,54 +271,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 23005841d48..4c53d08ffb2 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:(FVPDisplayLink *)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:(FVPDisplayLink *)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..a14e9c0f807 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 @@ -31,6 +31,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. 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 index 967855d85bd..5f7ffa7d9ec 100644 --- 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 @@ -3,6 +3,7 @@ // found in the LICENSE file. #import "FVPVideoPlayer.h" +#import "messages.g.h" #if TARGET_OS_OSX #import @@ -12,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface FVPVideoPlayer () +@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/ios/Flutter/AppFrameworkInfo.plist b/packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist index b3aaa733dfb..1f6b98f117b 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist @@ -25,6 +25,6 @@ arm64 MinimumOSVersion - 12.0 + 13.0 diff --git a/packages/video_player/video_player_avfoundation/example/ios/Podfile b/packages/video_player/video_player_avfoundation/example/ios/Podfile index c9339a034eb..6eafd7e2e95 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Podfile +++ b/packages/video_player/video_player_avfoundation/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index e8166db9ca2..c15fd78b86a 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 2A2EA522BDC492279A91AB75 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 6CDC4DA5940705A6E7671616 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -133,6 +134,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -220,7 +222,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 39F8523A410339490F826122 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -351,26 +352,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 39F8523A410339490F826122 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", - "${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation/path_provider_foundation_privacy.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/video_player_avfoundation/video_player_avfoundation_privacy.bundle", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/path_provider_foundation_privacy.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/video_player_avfoundation_privacy.bundle", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -554,7 +535,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -604,7 +585,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index d7730d34dab..6ef3fa75e7f 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -44,6 +44,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> { _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..aaa6a04c569 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)? apiProvider, + }) : _api = pluginApi ?? AVFoundationVideoPlayerApi(), + _apiProvider = apiProvider ?? _productionApiProvider; + + final AVFoundationVideoPlayerApi _api; + // A method to create VideoPlayerInstanceApi instances, which can be + //overridden for testing. + final VideoPlayerInstanceApi Function(int mapId) _apiProvider; /// 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 _playerApis = + {}; + /// 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); + _playerApis.remove(playerId); } @override @@ -92,45 +112,58 @@ 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) { + VideoPlayerInstanceApi? api = _playerApis[playerId]; + if (api == null) { + api = _apiProvider(playerId); + _playerApis[playerId] ??= api; + } + return api; + } + @override Future setLooping(int playerId, bool looping) { - return _api.setLooping(looping, playerId); + return _apiFor(playerId).setLooping(looping); } @override Future play(int playerId) { - return _api.play(playerId); + return _apiFor(playerId).play(); } @override Future pause(int playerId) { - return _api.pause(playerId); + return _apiFor(playerId).pause(); } @override Future setVolume(int playerId, double volume) { - return _api.setVolume(volume, playerId); + return _apiFor(playerId).setVolume(volume); } @override Future setPlaybackSpeed(int playerId, double speed) { assert(speed > 0); - return _api.setPlaybackSpeed(speed, playerId); + return _apiFor(playerId).setPlaybackSpeed(speed); } @override Future seekTo(int playerId, Duration position) { - return _api.seekTo(position.inMilliseconds, playerId); + return _apiFor(playerId).seekTo(position.inMilliseconds); } @override Future getPosition(int playerId) async { - final int position = await _api.getPosition(playerId); + final int position = await _apiFor(playerId).getPosition(); return Duration(milliseconds: position); } @@ -219,6 +252,14 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { return EventChannel('flutter.io/videoPlayer/videoEvents$playerId'); } + VideoPlayerInstanceApi _apiFor(int playerId) { + final VideoPlayerInstanceApi? api = _playerApis[playerId]; + if (api == null) { + throw StateError('No active player with ID $playerId.'); + } + return api; + } + 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 7972cd03c1d..98b944a678d 100644 --- a/packages/video_player/video_player_avfoundation/pubspec.yaml +++ b/packages/video_player/video_player_avfoundation/pubspec.yaml @@ -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..fde3b2763d8 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, + apiProvider: (_) => 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.platformView); + 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())); - } - }); - } - } - } -} From 1963bb362c96ae7b98d7f1013df819a5b142e299 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 30 Jun 2025 12:59:28 -0400 Subject: [PATCH 2/8] Simplify tests --- .../darwin/RunnerTests/VideoPlayerTests.m | 180 +++++------------- 1 file changed, 50 insertions(+), 130 deletions(-) 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 d33772a94ec..141e33f26e3 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -292,6 +292,8 @@ - (void)testSeekToWhilePausedStartsDisplayLinkTemporarily { StubFVPDisplayLinkFactory *stubDisplayLinkFactory = [[StubFVPDisplayLinkFactory alloc] initWithDisplayLink:mockDisplayLink]; 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 @@ -310,8 +312,6 @@ - (void)testSeekToWhilePausedStartsDisplayLinkTemporarily { viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; - - // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. FVPTextureBasedVideoPlayer *player = (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; @@ -411,6 +411,8 @@ - (void)testSeekToWhilePlayingDoesNotStopDisplayLink { StubFVPDisplayLinkFactory *stubDisplayLinkFactory = [[StubFVPDisplayLinkFactory alloc] initWithDisplayLink:mockDisplayLink]; 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 @@ -429,8 +431,6 @@ - (void)testSeekToWhilePlayingDoesNotStopDisplayLink { viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; - - // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. FVPTextureBasedVideoPlayer *player = (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; @@ -474,6 +474,8 @@ - (void)testPauseWhileWaitingForFrameDoesNotStopDisplayLink { StubFVPDisplayLinkFactory *stubDisplayLinkFactory = [[StubFVPDisplayLinkFactory alloc] initWithDisplayLink:mockDisplayLink]; 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 @@ -492,8 +494,6 @@ - (void)testPauseWhileWaitingForFrameDoesNotStopDisplayLink { viewType:FVPPlatformVideoViewTypeTextureView]; FlutterError *createError; NSNumber *playerIdentifier = [videoPlayerPlugin createWithOptions:create error:&createError]; - - // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. FVPTextureBasedVideoPlayer *player = (FVPTextureBasedVideoPlayer *)videoPlayerPlugin.playersByIdentifier[playerIdentifier]; @@ -583,28 +583,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. @@ -612,14 +600,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); @@ -627,15 +609,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); @@ -655,40 +632,22 @@ - (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]; - - // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. - FVPTextureBasedVideoPlayer *player = - (FVPTextureBasedVideoPlayer *)pluginWithMockAVPlayer.playersByIdentifier[playerIdentifier]; - - 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"]; [player seekTo:1234 completion:^(FlutterError *_Nullable error) { - [initializedExpectation fulfill]; + [seekExpectation fulfill]; }]; [self waitForExpectationsWithTimeout:30.0 handler:nil]; @@ -697,64 +656,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]; - - // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. - FVPTextureBasedVideoPlayer *player = - (FVPTextureBasedVideoPlayer *)pluginWithMockAVPlayer.playersByIdentifier[playerIdentifier]; - - 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. [player seekTo:0 completion:^(FlutterError *_Nullable error) { - [initializedExpectation fulfill]; + [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]; - - // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. - 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"]; @@ -776,6 +708,7 @@ - (void)testSeekToleranceWhenSeekingToEnd { XCTAssertEqual(avPlayer.timeControlStatus, AVPlayerTimeControlStatusPaused); // Change playback speed. + FlutterError *error; [player setPlaybackSpeed:2 error:&error]; XCTAssertNil(error); [player playWithError:&error]; @@ -963,27 +896,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]; - // TODO(stuartmorgan): Rework this test to only create the player, not the whole plugin. - 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 @@ -994,6 +913,7 @@ - (void)testUpdatePlayingStateShouldNotResetRate { }]; [self waitForExpectationsWithTimeout:10 handler:nil]; + FlutterError *error; [player setPlaybackSpeed:2 error:&error]; [player playWithError:&error]; XCTAssertEqual(player.player.rate, 2); From 3f7993a4e900abe332cc1f4686badde0d893b478 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 30 Jun 2025 13:34:46 -0400 Subject: [PATCH 3/8] Version bump --- packages/video_player/video_player_avfoundation/CHANGELOG.md | 5 +++-- packages/video_player/video_player_avfoundation/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 935a6eabe93..07192e924cd 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,7 +1,8 @@ -## NEXT +## 2.7.2 -* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. +* Restructures the communication between Dart and native code. * Refactors native code for improved testing. +* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. ## 2.7.1 diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 98b944a678d..109e1c978cb 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.1 +version: 2.7.2 environment: sdk: ^3.6.0 From fc05c2ddfe30f5996d485d358af84a1ed9afba4c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 30 Jun 2025 13:38:05 -0400 Subject: [PATCH 4/8] Revert project automigrations --- .../ios/Flutter/AppFrameworkInfo.plist | 2 +- .../example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 27 ++++++++++++++++--- .../xcshareddata/xcschemes/Runner.xcscheme | 2 -- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist b/packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist index 1f6b98f117b..b3aaa733dfb 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/video_player/video_player_avfoundation/example/ios/Flutter/AppFrameworkInfo.plist @@ -25,6 +25,6 @@ arm64 MinimumOSVersion - 13.0 + 12.0 diff --git a/packages/video_player/video_player_avfoundation/example/ios/Podfile b/packages/video_player/video_player_avfoundation/example/ios/Podfile index 6eafd7e2e95..c9339a034eb 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Podfile +++ b/packages/video_player/video_player_avfoundation/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '13.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj index c15fd78b86a..e8166db9ca2 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj @@ -59,7 +59,6 @@ 2A2EA522BDC492279A91AB75 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 6CDC4DA5940705A6E7671616 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -134,7 +133,6 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -222,6 +220,7 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 39F8523A410339490F826122 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -352,6 +351,26 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 39F8523A410339490F826122 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/path_provider_foundation/path_provider_foundation_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/video_player_avfoundation/video_player_avfoundation_privacy.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/path_provider_foundation_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/video_player_avfoundation_privacy.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -535,7 +554,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -585,7 +604,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 6ef3fa75e7f..d7730d34dab 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -44,7 +44,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> Date: Mon, 7 Jul 2025 14:38:44 -0400 Subject: [PATCH 5/8] Fix unit test update copypasta --- .../test/avfoundation_video_player_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fde3b2763d8..f7992fb547b 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 @@ -346,7 +346,7 @@ void main() { final VerificationResult verification = verify(api.create(captureAny)); final CreationOptions creationOptions = verification.captured[0] as CreationOptions; - expect(creationOptions.viewType, PlatformVideoViewType.platformView); + expect(creationOptions.viewType, PlatformVideoViewType.textureView); expect(playerId, newPlayerId); expect(player.playerViewStates[newPlayerId], const VideoPlayerTextureViewState(textureId: newPlayerId)); From 765f9bf9efb846d18d7f8414366e4644a6d8ad91 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 7 Jul 2025 16:40:29 -0400 Subject: [PATCH 6/8] Fix podspec-check warnings --- .../darwin/RunnerTests/VideoPlayerTests.m | 1 - .../FVPTextureBasedVideoPlayer.m | 10 +++++----- .../FVPVideoPlayer.m | 1 - .../FVPVideoPlayer.h | 18 ++---------------- .../FVPVideoPlayer_Test.h | 19 ------------------- 5 files changed, 7 insertions(+), 42 deletions(-) delete mode 100644 packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Test.h 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 ae1d719bebd..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 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 68d863f770c..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 @@ -110,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 @@ -124,8 +124,8 @@ - (void)seekTo:(int64_t)location completionHandler:(void (^)(BOOL))completionHan [self expectFrame]; } - if (completionHandler) { - completionHandler(completed); + if (completion) { + completion(error); } }]; } 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 c842722671d..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 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 a14e9c0f807..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. @@ -55,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 5f7ffa7d9ec..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,19 +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" -#import "messages.g.h" - -#if TARGET_OS_OSX -#import -#else -#import -#endif - -NS_ASSUME_NONNULL_BEGIN - -@interface FVPVideoPlayer () -@end - -NS_ASSUME_NONNULL_END From 748d9aaaf945e9b4938b7dc838c8844adaeecd8c Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Mon, 7 Jul 2025 17:08:47 -0400 Subject: [PATCH 7/8] Rename away from 'api' --- .../lib/src/avfoundation_video_player.dart | 44 ++++++++----------- .../test/avfoundation_video_player_test.dart | 2 +- 2 files changed, 20 insertions(+), 26 deletions(-) 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 aaa6a04c569..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 @@ -23,14 +23,14 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { AVFoundationVideoPlayer({ @visibleForTesting AVFoundationVideoPlayerApi? pluginApi, @visibleForTesting - VideoPlayerInstanceApi Function(int playerId)? apiProvider, + VideoPlayerInstanceApi Function(int playerId)? playerProvider, }) : _api = pluginApi ?? AVFoundationVideoPlayerApi(), - _apiProvider = apiProvider ?? _productionApiProvider; + _playerProvider = playerProvider ?? _productionApiProvider; final AVFoundationVideoPlayerApi _api; // A method to create VideoPlayerInstanceApi instances, which can be - //overridden for testing. - final VideoPlayerInstanceApi Function(int mapId) _apiProvider; + // 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. @@ -38,7 +38,7 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { final Map playerViewStates = {}; - final Map _playerApis = + final Map _players = {}; /// Registers this class as the default instance of [VideoPlayerPlatform]. @@ -55,7 +55,7 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { Future dispose(int playerId) async { await _api.dispose(playerId); playerViewStates.remove(playerId); - _playerApis.remove(playerId); + _players.remove(playerId); } @override @@ -121,49 +121,46 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { /// exist. @visibleForTesting VideoPlayerInstanceApi ensureApiInitialized(int playerId) { - VideoPlayerInstanceApi? api = _playerApis[playerId]; - if (api == null) { - api = _apiProvider(playerId); - _playerApis[playerId] ??= api; - } - return api; + return _players.putIfAbsent(playerId, () { + return _playerProvider(playerId); + }); } @override Future setLooping(int playerId, bool looping) { - return _apiFor(playerId).setLooping(looping); + return _playerWith(id: playerId).setLooping(looping); } @override Future play(int playerId) { - return _apiFor(playerId).play(); + return _playerWith(id: playerId).play(); } @override Future pause(int playerId) { - return _apiFor(playerId).pause(); + return _playerWith(id: playerId).pause(); } @override Future setVolume(int playerId, double volume) { - return _apiFor(playerId).setVolume(volume); + return _playerWith(id: playerId).setVolume(volume); } @override Future setPlaybackSpeed(int playerId, double speed) { assert(speed > 0); - return _apiFor(playerId).setPlaybackSpeed(speed); + return _playerWith(id: playerId).setPlaybackSpeed(speed); } @override Future seekTo(int playerId, Duration position) { - return _apiFor(playerId).seekTo(position.inMilliseconds); + return _playerWith(id: playerId).seekTo(position.inMilliseconds); } @override Future getPosition(int playerId) async { - final int position = await _apiFor(playerId).getPosition(); + final int position = await _playerWith(id: playerId).getPosition(); return Duration(milliseconds: position); } @@ -252,12 +249,9 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { return EventChannel('flutter.io/videoPlayer/videoEvents$playerId'); } - VideoPlayerInstanceApi _apiFor(int playerId) { - final VideoPlayerInstanceApi? api = _playerApis[playerId]; - if (api == null) { - throw StateError('No active player with ID $playerId.'); - } - return api; + VideoPlayerInstanceApi _playerWith({required int id}) { + final VideoPlayerInstanceApi? player = _players[id]; + return player ?? (throw StateError('No active player with ID $id.')); } static const Map _videoFormatStringMap = 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 f7992fb547b..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 @@ -30,7 +30,7 @@ void main() { final MockVideoPlayerInstanceApi instanceApi = MockVideoPlayerInstanceApi(); final AVFoundationVideoPlayer player = AVFoundationVideoPlayer( pluginApi: pluginApi, - apiProvider: (_) => instanceApi, + playerProvider: (_) => instanceApi, ); player.ensureApiInitialized(playerId); return (player, pluginApi, instanceApi); From e1f606544eb6e0a57f7b00f69a46ba271c95c85a Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 8 Jul 2025 12:34:38 -0400 Subject: [PATCH 8/8] Fix macOS compilation warnings --- .../darwin/RunnerTests/VideoPlayerTests.m | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) 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 3992bb418af..9a912db66d6 100644 --- a/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m +++ b/packages/video_player/video_player_avfoundation/darwin/RunnerTests/VideoPlayerTests.m @@ -603,13 +603,11 @@ - (void)testSeekToleranceWhenNotSeekingToEnd { StubAVPlayer *stubAVPlayer = [[StubAVPlayer alloc] init]; StubFVPAVFactory *stubAVFactory = [[StubFVPAVFactory alloc] initWithPlayer:stubAVPlayer output:nil]; - 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]]; + FVPVideoPlayer *player = + [[FVPVideoPlayer alloc] initWithURL:self.mp4TestURL + httpHeaders:@{} + avFactory:stubAVFactory + viewProvider:[[StubViewProvider alloc] initWithView:nil]]; XCTestExpectation *seekExpectation = [self expectationWithDescription:@"seekTo has zero tolerance when seeking not to end"]; @@ -627,13 +625,11 @@ - (void)testSeekToleranceWhenSeekingToEnd { StubAVPlayer *stubAVPlayer = [[StubAVPlayer alloc] init]; StubFVPAVFactory *stubAVFactory = [[StubFVPAVFactory alloc] initWithPlayer:stubAVPlayer output:nil]; - 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]]; + FVPVideoPlayer *player = + [[FVPVideoPlayer alloc] initWithURL:self.mp4TestURL + httpHeaders:@{} + avFactory:stubAVFactory + viewProvider:[[StubViewProvider alloc] initWithView:nil]]; XCTestExpectation *seekExpectation = [self expectationWithDescription:@"seekTo has non-zero tolerance when seeking to end"]; @@ -649,9 +645,11 @@ - (void)testSeekToleranceWhenSeekingToEnd { /// 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 { +- (NSDictionary *)sanityTestURI:(NSString *)testURI { + NSURL *testURL = [NSURL URLWithString:testURI]; + XCTAssertNotNil(testURL); FVPVideoPlayer *player = - [[FVPVideoPlayer alloc] initWithURL:[NSURL URLWithString:uri] + [[FVPVideoPlayer alloc] initWithURL:testURL httpHeaders:@{} avFactory:[[FVPDefaultAVFactory alloc] init] viewProvider:[[StubViewProvider alloc] initWithView:nil]]; @@ -864,13 +862,11 @@ - (void)testFailedToLoadVideoEventShouldBeAlwaysSent { } - (void)testUpdatePlayingStateShouldNotResetRate { - 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]]; + FVPVideoPlayer *player = + [[FVPVideoPlayer alloc] initWithURL:self.mp4TestURL + httpHeaders:@{} + avFactory:[[StubFVPAVFactory alloc] initWithPlayer:nil output:nil] + viewProvider:[[StubViewProvider alloc] initWithView:nil]]; XCTestExpectation *initializedExpectation = [self expectationWithDescription:@"initialized"]; [player onListenWithArguments:nil @@ -1038,4 +1034,10 @@ - (void)validateTransformFixForOrientation:(UIImageOrientation)orientation { } #endif +/// Returns a test URL for creating a player from a network source. +- (nonnull NSURL *)mp4TestURL { + return (NSURL *_Nonnull)[NSURL + URLWithString:@"https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4"]; +} + @end