From 3fc86552e4c1508e7ef0d2806054acd005699c4e Mon Sep 17 00:00:00 2001 From: akaboshinit Date: Tue, 24 Feb 2026 06:13:34 +0900 Subject: [PATCH 1/2] [video_player] Add Picture-in-Picture support for iOS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds PiP support across three packages: ## video_player_platform_interface (6.6.0 → 6.7.0) - Add enablePictureInPicture, disablePictureInPicture, startPictureInPicture, stopPictureInPicture, isPictureInPictureSupported, isPictureInPictureActive - Add pipStarted, pipStopped, pipRestoreUserInterface event types ## video_player_avfoundation (2.9.3 → 2.10.0) - Integrate AVPictureInPictureController with AVPlayerLayer - Support both texture-based and platform view players - Implement PiP delegate callbacks (start/stop/restore UI) - macOS returns stub implementations (PiP not supported) - Requires iOS 14.2+ ## video_player (2.11.0 → 2.12.0) - Add isPictureInPictureActive field to VideoPlayerValue - Add PiP methods to VideoPlayerController - Handle PiP events in the event listener Fixes flutter/flutter#60048 --- .../video_player/video_player/CHANGELOG.md | 6 + .../video_player/lib/video_player.dart | 85 +++- .../video_player/video_player/pubspec.yaml | 6 +- .../video_player/test/video_player_test.dart | 175 +++++++- .../video_player_avfoundation/CHANGELOG.md | 5 + .../FVPEventBridge.m | 12 + .../FVPNativeVideoViewFactory.m | 12 +- .../FVPTextureBasedVideoPlayer.m | 7 + .../FVPVideoPlayer.m | 178 ++++++++ .../FVPNativeVideoView.h | 4 + .../FVPVideoEventListener.h | 6 + .../FVPVideoPlayer_Internal.h | 14 + .../video_player_avfoundation/messages.g.h | 86 ++-- .../video_player_avfoundation/messages.g.m | 401 +++++++++--------- .../FVPNativeVideoView.m | 4 + .../example/ios/Runner/Info.plist | 4 + .../example/lib/main.dart | 77 ++++ .../example/lib/mini_controller.dart | 38 +- .../lib/src/avfoundation_video_player.dart | 48 +++ .../lib/src/messages.g.dart | 154 +++++++ .../pigeons/messages.dart | 24 ++ .../video_player_avfoundation/pubspec.yaml | 4 +- .../test/avfoundation_video_player_test.dart | 155 +++++++ .../avfoundation_video_player_test.mocks.dart | 79 ++++ .../CHANGELOG.md | 4 +- .../lib/video_player_platform_interface.dart | 51 +++ .../pubspec.yaml | 2 +- .../video_player_platform_interface_test.dart | 60 +++ 28 files changed, 1459 insertions(+), 242 deletions(-) diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 3f70c390c861..246571b484cd 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.12.0 + +* Adds Picture-in-Picture (PiP) support for iOS. +* Adds `isPictureInPictureActive` field to `VideoPlayerValue`. +* Adds `enablePictureInPicture()`, `disablePictureInPicture()`, `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods to `VideoPlayerController`. + ## 2.11.0 * Adds `getAudioTracks()` and `selectAudioTrack()` methods to retrieve and select available audio tracks. diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 232dda0858b7..7e9bb777c5ce 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -172,6 +172,7 @@ class VideoPlayerValue { this.rotationCorrection = 0, this.errorDescription, this.isCompleted = false, + this.isPictureInPictureActive = false, }); /// Returns an instance for a video that hasn't been loaded. @@ -238,6 +239,9 @@ class VideoPlayerValue { /// Does not update if video is looping. final bool isCompleted; + /// Whether Picture-in-Picture is currently active. + final bool isPictureInPictureActive; + /// The [size] of the currently loaded video. final Size size; @@ -286,6 +290,7 @@ class VideoPlayerValue { int? rotationCorrection, String? errorDescription = _defaultErrorDescription, bool? isCompleted, + bool? isPictureInPictureActive, }) { return VideoPlayerValue( duration: duration ?? this.duration, @@ -305,6 +310,8 @@ class VideoPlayerValue { ? errorDescription : this.errorDescription, isCompleted: isCompleted ?? this.isCompleted, + isPictureInPictureActive: + isPictureInPictureActive ?? this.isPictureInPictureActive, ); } @@ -324,7 +331,8 @@ class VideoPlayerValue { 'volume: $volume, ' 'playbackSpeed: $playbackSpeed, ' 'errorDescription: $errorDescription, ' - 'isCompleted: $isCompleted),'; + 'isCompleted: $isCompleted, ' + 'isPictureInPictureActive: $isPictureInPictureActive),'; } @override @@ -346,7 +354,8 @@ class VideoPlayerValue { size == other.size && rotationCorrection == other.rotationCorrection && isInitialized == other.isInitialized && - isCompleted == other.isCompleted; + isCompleted == other.isCompleted && + isPictureInPictureActive == other.isPictureInPictureActive; @override int get hashCode => Object.hash( @@ -365,6 +374,7 @@ class VideoPlayerValue { rotationCorrection, isInitialized, isCompleted, + isPictureInPictureActive, ); } @@ -646,6 +656,12 @@ class VideoPlayerController extends ValueNotifier { } else { value = value.copyWith(isPlaying: event.isPlaying); } + case platform_interface.VideoEventType.pipStarted: + value = value.copyWith(isPictureInPictureActive: true); + case platform_interface.VideoEventType.pipStopped: + value = value.copyWith(isPictureInPictureActive: false); + case platform_interface.VideoEventType.pipRestoreUserInterface: + break; case platform_interface.VideoEventType.unknown: break; } @@ -988,6 +1004,71 @@ class VideoPlayerController extends ValueNotifier { return _videoPlayerPlatform.isAudioTrackSupportAvailable(); } + /// Enables Picture-in-Picture for this player. + /// + /// This must be called before [startPictureInPicture] to prepare the player + /// for PiP mode. On platforms that don't support PiP, this may be a no-op. + /// + /// Throws a [StateError] if the controller is disposed or not initialized. + Future enablePictureInPicture() async { + if (_isDisposedOrNotInitialized) { + throw StateError('VideoPlayerController is disposed or not initialized'); + } + await _videoPlayerPlatform.enablePictureInPicture(_playerId); + } + + /// Disables Picture-in-Picture for this player. + /// + /// Throws a [StateError] if the controller is disposed or not initialized. + Future disablePictureInPicture() async { + if (_isDisposedOrNotInitialized) { + throw StateError('VideoPlayerController is disposed or not initialized'); + } + await _videoPlayerPlatform.disablePictureInPicture(_playerId); + } + + /// Starts Picture-in-Picture mode for this player. + /// + /// [enablePictureInPicture] must be called before this method. + /// + /// Throws a [StateError] if the controller is disposed or not initialized. + Future startPictureInPicture() async { + if (_isDisposedOrNotInitialized) { + throw StateError('VideoPlayerController is disposed or not initialized'); + } + await _videoPlayerPlatform.startPictureInPicture(_playerId); + } + + /// Stops Picture-in-Picture mode for this player. + /// + /// Throws a [StateError] if the controller is disposed or not initialized. + Future stopPictureInPicture() async { + if (_isDisposedOrNotInitialized) { + throw StateError('VideoPlayerController is disposed or not initialized'); + } + await _videoPlayerPlatform.stopPictureInPicture(_playerId); + } + + /// Returns whether Picture-in-Picture is supported on this device. + /// + /// Throws a [StateError] if the controller is disposed or not initialized. + Future isPictureInPictureSupported() async { + if (_isDisposedOrNotInitialized) { + throw StateError('VideoPlayerController is disposed or not initialized'); + } + return _videoPlayerPlatform.isPictureInPictureSupported(_playerId); + } + + /// Returns whether Picture-in-Picture is currently active for this player. + /// + /// Throws a [StateError] if the controller is disposed or not initialized. + Future isPictureInPictureActive() async { + if (_isDisposedOrNotInitialized) { + throw StateError('VideoPlayerController is disposed or not initialized'); + } + return _videoPlayerPlatform.isPictureInPictureActive(_playerId); + } + bool get _isDisposedOrNotInitialized => _isDisposed || !value.isInitialized; } diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index f48636e14cde..8b8c7612e040 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter widgets on Android, iOS, macOS and web. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.11.0 +version: 2.12.0 environment: sdk: ^3.10.0 @@ -26,8 +26,8 @@ dependencies: sdk: flutter html: ^0.15.0 video_player_android: ^2.9.1 - video_player_avfoundation: ^2.9.0 - video_player_platform_interface: ^6.6.0 + video_player_avfoundation: ^2.10.0 + video_player_platform_interface: ^6.7.0 video_player_web: ^2.1.0 dev_dependencies: diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index 70bbf643675d..a7a383843f09 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -130,6 +130,24 @@ class FakeController extends ValueNotifier return true; } + @override + Future enablePictureInPicture() async {} + + @override + Future disablePictureInPicture() async {} + + @override + Future startPictureInPicture() async {} + + @override + Future stopPictureInPicture() async {} + + @override + Future isPictureInPictureSupported() async => true; + + @override + Future isPictureInPictureActive() async => false; + String? selectedAudioTrackId; } @@ -1050,6 +1068,128 @@ void main() { }); }); + group('Picture-in-Picture', () { + test('enablePictureInPicture calls platform', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + await controller.enablePictureInPicture(); + + expect(fakeVideoPlayerPlatform.calls.last, 'enablePictureInPicture'); + }); + + test('disablePictureInPicture calls platform', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + await controller.disablePictureInPicture(); + + expect(fakeVideoPlayerPlatform.calls.last, 'disablePictureInPicture'); + }); + + test('startPictureInPicture calls platform', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + await controller.startPictureInPicture(); + + expect(fakeVideoPlayerPlatform.calls.last, 'startPictureInPicture'); + }); + + test('stopPictureInPicture calls platform', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + await controller.stopPictureInPicture(); + + expect(fakeVideoPlayerPlatform.calls.last, 'stopPictureInPicture'); + }); + + test('isPictureInPictureSupported returns result', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + final bool result = await controller.isPictureInPictureSupported(); + + expect(result, true); + expect( + fakeVideoPlayerPlatform.calls.last, + 'isPictureInPictureSupported', + ); + }); + + test('isPictureInPictureActive returns result', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + final bool result = await controller.isPictureInPictureActive(); + + expect(result, false); + expect(fakeVideoPlayerPlatform.calls.last, 'isPictureInPictureActive'); + }); + + test('PiP methods before initialization throw', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + expect( + () => controller.enablePictureInPicture(), + throwsA(isA()), + ); + expect( + () => controller.startPictureInPicture(), + throwsA(isA()), + ); + expect( + () => controller.stopPictureInPicture(), + throwsA(isA()), + ); + }); + + test('pipStarted event updates VideoPlayerValue', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + + expect(controller.value.isPictureInPictureActive, false); + + fakeVideoPlayerPlatform.streams[controller.playerId]!.add( + VideoEvent(eventType: VideoEventType.pipStarted), + ); + await Future.delayed(Duration.zero); + + expect(controller.value.isPictureInPictureActive, true); + }); + + test('pipStopped event updates VideoPlayerValue', () async { + final controller = VideoPlayerController.networkUrl(_localhostUri); + addTearDown(controller.dispose); + + await controller.initialize(); + + // Simulate PiP started first. + fakeVideoPlayerPlatform.streams[controller.playerId]!.add( + VideoEvent(eventType: VideoEventType.pipStarted), + ); + await Future.delayed(Duration.zero); + expect(controller.value.isPictureInPictureActive, true); + + // Now stop PiP. + fakeVideoPlayerPlatform.streams[controller.playerId]!.add( + VideoEvent(eventType: VideoEventType.pipStopped), + ); + await Future.delayed(Duration.zero); + expect(controller.value.isPictureInPictureActive, false); + }); + }); + group('caption', () { test('works when position updates', () async { final controller = VideoPlayerController.networkUrl( @@ -1476,7 +1616,8 @@ void main() { 'volume: 0.5, ' 'playbackSpeed: 1.5, ' 'errorDescription: null, ' - 'isCompleted: false),', + 'isCompleted: false, ' + 'isPictureInPictureActive: false),', ); }); @@ -1904,4 +2045,36 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { } final Map selectedAudioTrackIds = {}; + + @override + Future enablePictureInPicture(int playerId) async { + calls.add('enablePictureInPicture'); + } + + @override + Future disablePictureInPicture(int playerId) async { + calls.add('disablePictureInPicture'); + } + + @override + Future startPictureInPicture(int playerId) async { + calls.add('startPictureInPicture'); + } + + @override + Future stopPictureInPicture(int playerId) async { + calls.add('stopPictureInPicture'); + } + + @override + Future isPictureInPictureSupported(int playerId) async { + calls.add('isPictureInPictureSupported'); + return true; + } + + @override + Future isPictureInPictureActive(int playerId) async { + calls.add('isPictureInPictureActive'); + return false; + } } diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index b22e2b1231e9..152df0493742 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,3 +1,8 @@ +## 2.10.0 + +* Adds Picture-in-Picture (PiP) support for iOS. +* Implements `enablePictureInPicture()`, `disablePictureInPicture()`, `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods. + ## 2.9.3 * Fixes a regression where HTTP headers were ignored. diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPEventBridge.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPEventBridge.m index 0df3569da9bd..da390306ccbf 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPEventBridge.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPEventBridge.m @@ -103,6 +103,18 @@ - (void)videoPlayerDidSetPlaying:(BOOL)playing { [self sendOrQueue:@{@"event" : @"isPlayingStateUpdate", @"isPlaying" : @(playing)}]; } +- (void)videoPlayerDidStartPictureInPicture { + [self sendOrQueue:@{@"event" : @"pipStarted"}]; +} + +- (void)videoPlayerDidStopPictureInPicture { + [self sendOrQueue:@{@"event" : @"pipStopped"}]; +} + +- (void)videoPlayerShouldRestoreUserInterfaceForPictureInPicture { + [self sendOrQueue:@{@"event" : @"pipRestoreUserInterface"}]; +} + - (void)videoPlayerWasDisposed { [self.eventChannel setStreamHandler:nil]; } diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPNativeVideoViewFactory.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPNativeVideoViewFactory.m index 21d7c4bf6752..db2d9b0ee468 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPNativeVideoViewFactory.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/FVPNativeVideoViewFactory.m @@ -6,6 +6,7 @@ #import "../video_player_avfoundation/include/video_player_avfoundation/FVPNativeVideoView.h" #import "../video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer.h" +#import "../video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h" #import "../video_player_avfoundation/include/video_player_avfoundation/messages.g.h" @interface FVPNativeVideoViewFactory () @@ -37,7 +38,16 @@ - (NSView *)createWithViewIdentifier:(int64_t)viewIdentifier #endif NSNumber *playerIdentifier = @(args.playerId); FVPVideoPlayer *player = self.playerByIdProvider(playerIdentifier); - return [[FVPNativeVideoView alloc] initWithPlayer:player.player]; + FVPNativeVideoView *nativeView = [[FVPNativeVideoView alloc] initWithPlayer:player.player]; + +#if TARGET_OS_IOS + // Set up PiP with the platform view's player layer. + if (@available(iOS 14.2, *)) { + [player setupPictureInPictureWithPlayerLayer:nativeView.playerLayer]; + } +#endif + + return nativeView; } - (NSObject *)createArgsCodec { 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 b474f8457e8d..65c8472baa6b 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 @@ -60,6 +60,13 @@ - (instancetype)initWithPlayerItem:(NSObject *)item CALayer *flutterLayer = viewProvider.view.layer; #endif [flutterLayer addSublayer:self.playerLayer]; + +#if TARGET_OS_IOS + // Set up PiP using the existing invisible player layer. + if (@available(iOS 14.2, *)) { + [self setupPictureInPictureWithPlayerLayer:_playerLayer]; + } +#endif } return self; } 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 2270120378d5..e8ce67743e26 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 @@ -165,6 +165,12 @@ - (void)disposeWithError:(FlutterError *_Nullable *_Nonnull)error { } _disposed = YES; +#if TARGET_OS_IOS + if (@available(iOS 14.2, *)) { + [self tearDownPictureInPicture]; + } +#endif + if (_listenersRegistered) { [[NSNotificationCenter defaultCenter] removeObserver:self]; FVPRemoveKeyValueObservers(self, FVPGetPlayerItemObservations(), self.player.currentItem); @@ -508,6 +514,178 @@ - (void)selectAudioTrackAtIndex:(NSInteger)trackIndex } } +#pragma mark - Picture-in-Picture + +#if TARGET_OS_IOS + +- (void)setupPictureInPictureWithPlayerLayer:(AVPlayerLayer *)playerLayer { + if (@available(iOS 14.2, *)) { + if (![AVPictureInPictureController isPictureInPictureSupported]) { + return; + } + _pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:playerLayer]; + _pipController.delegate = (id)self; + } +} + +- (void)tearDownPictureInPicture API_AVAILABLE(ios(14.2)) { + if (_pipController.isPictureInPictureActive) { + [_pipController stopPictureInPicture]; + } + _pipController = nil; +} + +- (void)enablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { + if (@available(iOS 14.2, *)) { + // PiP controller is already set up automatically for texture-based players. + // For platform view players, it's set up when the view is created. + if (!_pipController) { + *error = [FlutterError + errorWithCode:@"pip_not_available" + message:@"PiP controller not configured. Ensure a player layer is available." + details:nil]; + } + } else { + *error = [FlutterError errorWithCode:@"pip_not_supported" + message:@"PiP requires iOS 14.2+" + details:nil]; + } +} + +- (void)disablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { + if (@available(iOS 14.2, *)) { + [self tearDownPictureInPicture]; + } +} + +- (void)startPictureInPicture:(FlutterError *_Nullable *_Nonnull)error { + if (@available(iOS 14.2, *)) { + if (!_pipController) { + *error = [FlutterError errorWithCode:@"pip_not_possible" + message:@"PiP is not possible at this time" + details:nil]; + return; + } + + if (_pipController.isPictureInPicturePossible) { + [_pipController startPictureInPicture]; + return; + } + + // Float-up animation workaround: pause to make PiP possible with empty frame, + // then resume immediately after starting PiP. + AVPlayer *player = _pipController.playerLayer.player; + BOOL wasPlaying = player.rate > 0; + if (wasPlaying && CGRectIsEmpty(_pipController.playerLayer.frame)) { + [player pause]; + + if (_pipController.isPictureInPicturePossible) { + [_pipController startPictureInPicture]; + [player play]; + return; + } + + // isPossible may update asynchronously; retry on next run loop. + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_pipController.isPictureInPicturePossible) { + [self->_pipController startPictureInPicture]; + } + // Restore playback regardless — PiP will continue playing independently. + [player play]; + }); + return; + } + + *error = [FlutterError errorWithCode:@"pip_not_possible" + message:@"PiP is not possible at this time" + details:nil]; + } else { + *error = [FlutterError errorWithCode:@"pip_not_supported" + message:@"PiP requires iOS 14.2+" + details:nil]; + } +} + +- (void)stopPictureInPicture:(FlutterError *_Nullable *_Nonnull)error { + if (@available(iOS 14.2, *)) { + if (_pipController && _pipController.isPictureInPictureActive) { + [_pipController stopPictureInPicture]; + } + } +} + +- (nullable NSNumber *)isPictureInPictureSupported:(FlutterError *_Nullable *_Nonnull)error { + if (@available(iOS 14.2, *)) { + return @([AVPictureInPictureController isPictureInPictureSupported]); + } + return @(NO); +} + +- (nullable NSNumber *)isPictureInPictureActive:(FlutterError *_Nullable *_Nonnull)error { + if (@available(iOS 14.2, *)) { + return @(_pipController != nil && _pipController.isPictureInPictureActive); + } + return @(NO); +} + +#pragma mark - AVPictureInPictureControllerDelegate + +- (void)pictureInPictureControllerDidStartPictureInPicture: + (AVPictureInPictureController *)controller { + [self.eventListener videoPlayerDidStartPictureInPicture]; +} + +- (void)pictureInPictureControllerDidStopPictureInPicture: + (AVPictureInPictureController *)controller { + [self.eventListener videoPlayerDidStopPictureInPicture]; + [self updatePlayingState]; +} + +- (void)pictureInPictureController:(AVPictureInPictureController *)controller + restoreUserInterfaceForPictureInPictureStopWithCompletionHandler: + (void (^)(BOOL))completionHandler { + [self.eventListener videoPlayerShouldRestoreUserInterfaceForPictureInPicture]; + // In a production implementation, this should wait for the Dart side to confirm UI restoration. + // For now, immediately signal completion. + completionHandler(YES); +} + +- (void)pictureInPictureController:(AVPictureInPictureController *)controller + failedToStartPictureInPictureWithError:(NSError *)error { + NSLog(@"Failed to start Picture-in-Picture: %@", error); +} + +#else +// macOS stubs - PiP is iOS only in this implementation. + +- (void)enablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { + *error = [FlutterError errorWithCode:@"pip_not_supported" + message:@"PiP is only supported on iOS" + details:nil]; +} + +- (void)disablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { +} + +- (void)startPictureInPicture:(FlutterError *_Nullable *_Nonnull)error { + *error = [FlutterError errorWithCode:@"pip_not_supported" + message:@"PiP is only supported on iOS" + details:nil]; +} + +- (void)stopPictureInPicture:(FlutterError *_Nullable *_Nonnull)error { +} + +- (nullable NSNumber *)isPictureInPictureSupported:(FlutterError *_Nullable *_Nonnull)error { + return @(NO); +} + +- (nullable NSNumber *)isPictureInPictureActive:(FlutterError *_Nullable *_Nonnull)error { + return @(NO); +} + +#endif + #pragma mark - Private - (int64_t)duration { diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPNativeVideoView.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPNativeVideoView.h index 627195470f6e..b36246fb7967 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPNativeVideoView.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPNativeVideoView.h @@ -20,4 +20,8 @@ /// Initializes a new instance of a native view. /// It creates a video view instance and sets the provided AVPlayer instance to it. - (instancetype)initWithPlayer:(AVPlayer *)player; +#if TARGET_OS_IOS +/// Returns the AVPlayerLayer used by this view, for PiP controller setup. +- (AVPlayerLayer *)playerLayer; +#endif @end diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoEventListener.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoEventListener.h index a267adbb902b..bdba5eb11dfe 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoEventListener.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoEventListener.h @@ -27,6 +27,12 @@ - (void)videoPlayerDidUpdateBufferRegions:(NSArray *> *)regions; /// Called when the player starts or stops playing. - (void)videoPlayerDidSetPlaying:(BOOL)playing; +/// Called when Picture-in-Picture playback starts. +- (void)videoPlayerDidStartPictureInPicture; +/// Called when Picture-in-Picture playback stops. +- (void)videoPlayerDidStopPictureInPicture; +/// Called when the user taps the restore button in the PiP window. +- (void)videoPlayerShouldRestoreUserInterfaceForPictureInPicture; /// Called when the video player has been disposed on the Dart side. - (void)videoPlayerWasDisposed; @end diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h index 6f86b7d0e3ef..52aca6cb3645 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation/include/video_player_avfoundation/FVPVideoPlayer_Internal.h @@ -3,6 +3,9 @@ // found in the LICENSE file. @import AVFoundation; +#if TARGET_OS_IOS +@import AVKit; +#endif #import "FVPAVFactory.h" #import "FVPVideoEventListener.h" @@ -35,6 +38,17 @@ NS_ASSUME_NONNULL_BEGIN /// Updates the playing state of the video player. - (void)updatePlayingState; + +#if TARGET_OS_IOS +/// The Picture-in-Picture controller. Created when setupPictureInPictureWithPlayerLayer: is called. +@property(nonatomic, nullable) AVPictureInPictureController *pipController + API_AVAILABLE(ios(14.2)); + +/// Sets up the PiP controller with the given player layer. +/// Called by subclasses or view factories that have access to an AVPlayerLayer. +- (void)setupPictureInPictureWithPlayerLayer:(AVPlayerLayer *)playerLayer + API_AVAILABLE(ios(14.2)); +#endif @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 3b2dd3952245..08320965c246 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 @@ -22,41 +22,42 @@ NS_ASSUME_NONNULL_BEGIN @interface FVPPlatformVideoViewCreationParams : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithPlayerId:(NSInteger)playerId; -@property(nonatomic, assign) NSInteger playerId; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId; +@property(nonatomic, assign) NSInteger playerId; @end @interface FVPCreationOptions : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; + (instancetype)makeWithUri:(NSString *)uri - httpHeaders:(NSDictionary *)httpHeaders; -@property(nonatomic, copy) NSString *uri; -@property(nonatomic, copy) NSDictionary *httpHeaders; + httpHeaders:(NSDictionary *)httpHeaders; +@property(nonatomic, copy) NSString * uri; +@property(nonatomic, copy) NSDictionary * httpHeaders; @end @interface FVPTexturePlayerIds : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithPlayerId:(NSInteger)playerId textureId:(NSInteger)textureId; -@property(nonatomic, assign) NSInteger playerId; -@property(nonatomic, assign) NSInteger textureId; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId + textureId:(NSInteger )textureId; +@property(nonatomic, assign) NSInteger playerId; +@property(nonatomic, assign) NSInteger textureId; @end /// Raw audio track data from AVMediaSelectionOption (for HLS streams). @interface FVPMediaSelectionAudioTrackData : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. - (instancetype)init NS_UNAVAILABLE; -+ (instancetype)makeWithIndex:(NSInteger)index - displayName:(nullable NSString *)displayName - languageCode:(nullable NSString *)languageCode - isSelected:(BOOL)isSelected - commonMetadataTitle:(nullable NSString *)commonMetadataTitle; -@property(nonatomic, assign) NSInteger index; -@property(nonatomic, copy, nullable) NSString *displayName; -@property(nonatomic, copy, nullable) NSString *languageCode; -@property(nonatomic, assign) BOOL isSelected; -@property(nonatomic, copy, nullable) NSString *commonMetadataTitle; ++ (instancetype)makeWithIndex:(NSInteger )index + displayName:(nullable NSString *)displayName + languageCode:(nullable NSString *)languageCode + isSelected:(BOOL )isSelected + commonMetadataTitle:(nullable NSString *)commonMetadataTitle; +@property(nonatomic, assign) NSInteger index; +@property(nonatomic, copy, nullable) NSString * displayName; +@property(nonatomic, copy, nullable) NSString * languageCode; +@property(nonatomic, assign) BOOL isSelected; +@property(nonatomic, copy, nullable) NSString * commonMetadataTitle; @end /// The codec used by all APIs. @@ -65,25 +66,17 @@ NSObject *FVPGetMessagesCodec(void); @protocol FVPAVFoundationVideoPlayerApi - (void)initialize:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSNumber *)createPlatformViewPlayerWithOptions:(FVPCreationOptions *)params - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSNumber *)createPlatformViewPlayerWithOptions:(FVPCreationOptions *)params error:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable FVPTexturePlayerIds *) - createTexturePlayerWithOptions:(FVPCreationOptions *)creationOptions - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable FVPTexturePlayerIds *)createTexturePlayerWithOptions:(FVPCreationOptions *)creationOptions error:(FlutterError *_Nullable *_Nonnull)error; - (void)setMixWithOthers:(BOOL)mixWithOthers error:(FlutterError *_Nullable *_Nonnull)error; -- (nullable NSString *)fileURLForAssetWithName:(NSString *)asset - package:(nullable NSString *)package - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSString *)fileURLForAssetWithName:(NSString *)asset package:(nullable NSString *)package error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void SetUpFVPAVFoundationVideoPlayerApi( - id binaryMessenger, - NSObject *_Nullable api); +extern void SetUpFVPAVFoundationVideoPlayerApi(id binaryMessenger, NSObject *_Nullable api); + +extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id binaryMessenger, NSObject *_Nullable api, NSString *messageChannelSuffix); -extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix( - id binaryMessenger, - NSObject *_Nullable api, NSString *messageChannelSuffix); @protocol FVPVideoPlayerInstanceApi - (void)setLooping:(BOOL)looping error:(FlutterError *_Nullable *_Nonnull)error; @@ -96,17 +89,28 @@ extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix( - (void)pauseWithError:(FlutterError *_Nullable *_Nonnull)error; - (void)disposeWithError:(FlutterError *_Nullable *_Nonnull)error; /// @return `nil` only when `error != nil`. -- (nullable NSArray *)getAudioTracks: - (FlutterError *_Nullable *_Nonnull)error; -- (void)selectAudioTrackAtIndex:(NSInteger)trackIndex - error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable NSArray *)getAudioTracks:(FlutterError *_Nullable *_Nonnull)error; +- (void)selectAudioTrackAtIndex:(NSInteger)trackIndex error:(FlutterError *_Nullable *_Nonnull)error; +/// Enables Picture-in-Picture for this player. +- (void)enablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error; +/// Disables Picture-in-Picture for this player. +- (void)disablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error; +/// Starts Picture-in-Picture playback. +- (void)startPictureInPicture:(FlutterError *_Nullable *_Nonnull)error; +/// Stops Picture-in-Picture playback. +- (void)stopPictureInPicture:(FlutterError *_Nullable *_Nonnull)error; +/// Returns whether Picture-in-Picture is supported on this device. +/// +/// @return `nil` only when `error != nil`. +- (nullable NSNumber *)isPictureInPictureSupported:(FlutterError *_Nullable *_Nonnull)error; +/// Returns whether Picture-in-Picture is currently active. +/// +/// @return `nil` only when `error != nil`. +- (nullable NSNumber *)isPictureInPictureActive:(FlutterError *_Nullable *_Nonnull)error; @end -extern void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, - NSObject *_Nullable api); +extern void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, NSObject *_Nullable api); -extern void SetUpFVPVideoPlayerInstanceApiWithSuffix( - id binaryMessenger, NSObject *_Nullable api, - NSString *messageChannelSuffix); +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 abb8efbad50d..8709b6eecfb7 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 @@ -51,15 +51,13 @@ + (nullable FVPMediaSelectionAudioTrackData *)nullableFromList:(NSArray *)li @end @implementation FVPPlatformVideoViewCreationParams -+ (instancetype)makeWithPlayerId:(NSInteger)playerId { - FVPPlatformVideoViewCreationParams *pigeonResult = - [[FVPPlatformVideoViewCreationParams alloc] init]; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId { + FVPPlatformVideoViewCreationParams* pigeonResult = [[FVPPlatformVideoViewCreationParams alloc] init]; pigeonResult.playerId = playerId; return pigeonResult; } + (FVPPlatformVideoViewCreationParams *)fromList:(NSArray *)list { - FVPPlatformVideoViewCreationParams *pigeonResult = - [[FVPPlatformVideoViewCreationParams alloc] init]; + FVPPlatformVideoViewCreationParams *pigeonResult = [[FVPPlatformVideoViewCreationParams alloc] init]; pigeonResult.playerId = [GetNullableObjectAtIndex(list, 0) integerValue]; return pigeonResult; } @@ -75,8 +73,8 @@ + (nullable FVPPlatformVideoViewCreationParams *)nullableFromList:(NSArray * @implementation FVPCreationOptions + (instancetype)makeWithUri:(NSString *)uri - httpHeaders:(NSDictionary *)httpHeaders { - FVPCreationOptions *pigeonResult = [[FVPCreationOptions alloc] init]; + httpHeaders:(NSDictionary *)httpHeaders { + FVPCreationOptions* pigeonResult = [[FVPCreationOptions alloc] init]; pigeonResult.uri = uri; pigeonResult.httpHeaders = httpHeaders; return pigeonResult; @@ -99,8 +97,9 @@ + (nullable FVPCreationOptions *)nullableFromList:(NSArray *)list { @end @implementation FVPTexturePlayerIds -+ (instancetype)makeWithPlayerId:(NSInteger)playerId textureId:(NSInteger)textureId { - FVPTexturePlayerIds *pigeonResult = [[FVPTexturePlayerIds alloc] init]; ++ (instancetype)makeWithPlayerId:(NSInteger )playerId + textureId:(NSInteger )textureId { + FVPTexturePlayerIds* pigeonResult = [[FVPTexturePlayerIds alloc] init]; pigeonResult.playerId = playerId; pigeonResult.textureId = textureId; return pigeonResult; @@ -123,12 +122,12 @@ + (nullable FVPTexturePlayerIds *)nullableFromList:(NSArray *)list { @end @implementation FVPMediaSelectionAudioTrackData -+ (instancetype)makeWithIndex:(NSInteger)index - displayName:(nullable NSString *)displayName - languageCode:(nullable NSString *)languageCode - isSelected:(BOOL)isSelected - commonMetadataTitle:(nullable NSString *)commonMetadataTitle { - FVPMediaSelectionAudioTrackData *pigeonResult = [[FVPMediaSelectionAudioTrackData alloc] init]; ++ (instancetype)makeWithIndex:(NSInteger )index + displayName:(nullable NSString *)displayName + languageCode:(nullable NSString *)languageCode + isSelected:(BOOL )isSelected + commonMetadataTitle:(nullable NSString *)commonMetadataTitle { + FVPMediaSelectionAudioTrackData* pigeonResult = [[FVPMediaSelectionAudioTrackData alloc] init]; pigeonResult.index = index; pigeonResult.displayName = displayName; pigeonResult.languageCode = languageCode; @@ -164,13 +163,13 @@ @interface FVPMessagesPigeonCodecReader : FlutterStandardReader @implementation FVPMessagesPigeonCodecReader - (nullable id)readValueOfType:(UInt8)type { switch (type) { - case 129: + case 129: return [FVPPlatformVideoViewCreationParams fromList:[self readValue]]; - case 130: + case 130: return [FVPCreationOptions fromList:[self readValue]]; - case 131: + case 131: return [FVPTexturePlayerIds fromList:[self readValue]]; - case 132: + case 132: return [FVPMediaSelectionAudioTrackData fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -215,35 +214,25 @@ - (FlutterStandardReader *)readerWithData:(NSData *)data { static FlutterStandardMessageCodec *sSharedObject = nil; static dispatch_once_t sPred = 0; dispatch_once(&sPred, ^{ - FVPMessagesPigeonCodecReaderWriter *readerWriter = - [[FVPMessagesPigeonCodecReaderWriter alloc] init]; + FVPMessagesPigeonCodecReaderWriter *readerWriter = [[FVPMessagesPigeonCodecReaderWriter alloc] init]; sSharedObject = [FlutterStandardMessageCodec codecWithReaderWriter:readerWriter]; }); return sSharedObject; } -void SetUpFVPAVFoundationVideoPlayerApi(id binaryMessenger, - NSObject *api) { +void SetUpFVPAVFoundationVideoPlayerApi(id binaryMessenger, NSObject *api) { SetUpFVPAVFoundationVideoPlayerApiWithSuffix(binaryMessenger, api, @""); } -void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id binaryMessenger, - NSObject *api, - NSString *messageChannelSuffix) { - messageChannelSuffix = messageChannelSuffix.length > 0 - ? [NSString stringWithFormat:@".%@", messageChannelSuffix] - : @""; +void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(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.initialize", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(initialize:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(initialize:)", - api); + NSCAssert([api respondsToSelector:@selector(initialize:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(initialize:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api initialize:&error]; @@ -254,19 +243,13 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString - stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.createForPlatformView", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(createPlatformViewPlayerWithOptions:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(createPlatformViewPlayerWithOptions:error:)", - api); + NSCAssert([api respondsToSelector:@selector(createPlatformViewPlayerWithOptions:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(createPlatformViewPlayerWithOptions:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FVPCreationOptions *arg_params = GetNullableObjectAtIndex(args, 0); @@ -279,25 +262,18 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString - stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.createForTextureView", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(createTexturePlayerWithOptions:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(createTexturePlayerWithOptions:error:)", - api); + NSCAssert([api respondsToSelector:@selector(createTexturePlayerWithOptions:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(createTexturePlayerWithOptions:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; FVPCreationOptions *arg_creationOptions = GetNullableObjectAtIndex(args, 0); FlutterError *error; - FVPTexturePlayerIds *output = [api createTexturePlayerWithOptions:arg_creationOptions - error:&error]; + FVPTexturePlayerIds *output = [api createTexturePlayerWithOptions:arg_creationOptions error:&error]; callback(wrapResult(output, error)); }]; } else { @@ -305,18 +281,13 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.setMixWithOthers", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setMixWithOthers:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(setMixWithOthers:error:)", - api); + NSCAssert([api respondsToSelector:@selector(setMixWithOthers:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(setMixWithOthers:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; BOOL arg_mixWithOthers = [GetNullableObjectAtIndex(args, 0) boolValue]; @@ -329,18 +300,13 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"AVFoundationVideoPlayerApi.getAssetUrl", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(fileURLForAssetWithName:package:error:)], - @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to " - @"@selector(fileURLForAssetWithName:package:error:)", - api); + NSCAssert([api respondsToSelector:@selector(fileURLForAssetWithName:package:error:)], @"FVPAVFoundationVideoPlayerApi api (%@) doesn't respond to @selector(fileURLForAssetWithName:package:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSString *arg_asset = GetNullableObjectAtIndex(args, 0); @@ -354,30 +320,20 @@ void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id bin } } } -void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, - NSObject *api) { +void SetUpFVPVideoPlayerInstanceApi(id binaryMessenger, NSObject *api) { SetUpFVPVideoPlayerInstanceApiWithSuffix(binaryMessenger, api, @""); } -void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryMessenger, - NSObject *api, - NSString *messageChannelSuffix) { - messageChannelSuffix = messageChannelSuffix.length > 0 - ? [NSString stringWithFormat:@".%@", messageChannelSuffix] - : @""; +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." - @"VideoPlayerInstanceApi.setLooping", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setLooping", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(setLooping:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setLooping: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; BOOL arg_looping = [GetNullableObjectAtIndex(args, 0) boolValue]; @@ -390,18 +346,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.setVolume", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setVolume", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(setVolume:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(setVolume: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_volume = [GetNullableObjectAtIndex(args, 0) doubleValue]; @@ -414,18 +365,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.setPlaybackSpeed", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setPlaybackSpeed", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(setPlaybackSpeed:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to " - @"@selector(setPlaybackSpeed: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; double arg_speed = [GetNullableObjectAtIndex(args, 0) doubleValue]; @@ -438,17 +384,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.play", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.play", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(playWithError:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(playWithError:)", - api); + NSCAssert([api respondsToSelector:@selector(playWithError:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(playWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api playWithError:&error]; @@ -459,16 +401,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.getPosition", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getPosition", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(position:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(position:)", api); + NSCAssert([api respondsToSelector:@selector(position:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(position:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; NSNumber *output = [api position:&error]; @@ -479,42 +418,32 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.seekTo", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.seekTo", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(seekTo:completion:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(seekTo:completion:)", - 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_position = [GetNullableObjectAtIndex(args, 0) integerValue]; - [api seekTo:arg_position - completion:^(FlutterError *_Nullable error) { - callback(wrapResult(nil, error)); - }]; + [api seekTo:arg_position completion:^(FlutterError *_Nullable error) { + callback(wrapResult(nil, error)); + }]; }]; } else { [channel setMessageHandler:nil]; } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.pause", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.pause", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(pauseWithError:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(pauseWithError:)", - api); + NSCAssert([api respondsToSelector:@selector(pauseWithError:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(pauseWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api pauseWithError:&error]; @@ -525,18 +454,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.dispose", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.dispose", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert( - [api respondsToSelector:@selector(disposeWithError:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(disposeWithError:)", - api); + NSCAssert([api respondsToSelector:@selector(disposeWithError:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(disposeWithError:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; [api disposeWithError:&error]; @@ -547,17 +471,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.getAudioTracks", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getAudioTracks", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(getAudioTracks:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(getAudioTracks:)", - api); + NSCAssert([api respondsToSelector:@selector(getAudioTracks:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(getAudioTracks:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { FlutterError *error; NSArray *output = [api getAudioTracks:&error]; @@ -568,18 +488,13 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM } } { - FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", - @"dev.flutter.pigeon.video_player_avfoundation." - @"VideoPlayerInstanceApi.selectAudioTrack", - messageChannelSuffix] + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.selectAudioTrack", messageChannelSuffix] binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; + codec:FVPGetMessagesCodec()]; if (api) { - NSCAssert([api respondsToSelector:@selector(selectAudioTrackAtIndex:error:)], - @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to " - @"@selector(selectAudioTrackAtIndex:error:)", - api); + NSCAssert([api respondsToSelector:@selector(selectAudioTrackAtIndex:error:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(selectAudioTrackAtIndex:error:)", api); [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { NSArray *args = message; NSInteger arg_trackIndex = [GetNullableObjectAtIndex(args, 0) integerValue]; @@ -591,4 +506,112 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM [channel setMessageHandler:nil]; } } + /// Enables Picture-in-Picture for this player. + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.enablePictureInPicture", messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(enablePictureInPicture:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(enablePictureInPicture:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api enablePictureInPicture:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Disables Picture-in-Picture for this player. + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.disablePictureInPicture", messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(disablePictureInPicture:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(disablePictureInPicture:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api disablePictureInPicture:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Starts Picture-in-Picture playback. + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.startPictureInPicture", messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(startPictureInPicture:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(startPictureInPicture:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api startPictureInPicture:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Stops Picture-in-Picture playback. + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.stopPictureInPicture", messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(stopPictureInPicture:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(stopPictureInPicture:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api stopPictureInPicture:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns whether Picture-in-Picture is supported on this device. + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureSupported", messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(isPictureInPictureSupported:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(isPictureInPictureSupported:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + NSNumber *output = [api isPictureInPictureSupported:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + /// Returns whether Picture-in-Picture is currently active. + { + FlutterBasicMessageChannel *channel = + [[FlutterBasicMessageChannel alloc] + initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureActive", messageChannelSuffix] + binaryMessenger:binaryMessenger + codec:FVPGetMessagesCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(isPictureInPictureActive:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(isPictureInPictureActive:)", api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + NSNumber *output = [api isPictureInPictureActive:&error]; + callback(wrapResult(output, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } } diff --git a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPNativeVideoView.m b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPNativeVideoView.m index 6c20d144d5aa..3cb3046cfd8c 100644 --- a/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPNativeVideoView.m +++ b/packages/video_player/video_player_avfoundation/darwin/video_player_avfoundation/Sources/video_player_avfoundation_ios/FVPNativeVideoView.m @@ -35,4 +35,8 @@ - (instancetype)initWithPlayer:(AVPlayer *)player { - (FVPPlayerView *)view { return self.playerView; } + +- (AVPlayerLayer *)playerLayer { + return (AVPlayerLayer *)self.playerView.layer; +} @end diff --git a/packages/video_player/video_player_avfoundation/example/ios/Runner/Info.plist b/packages/video_player/video_player_avfoundation/example/ios/Runner/Info.plist index 4e29652e6d2e..23a952e51002 100644 --- a/packages/video_player/video_player_avfoundation/example/ios/Runner/Info.plist +++ b/packages/video_player/video_player_avfoundation/example/ios/Runner/Info.plist @@ -52,5 +52,9 @@ UIApplicationSupportsIndirectInputEvents + UIBackgroundModes + + audio + diff --git a/packages/video_player/video_player_avfoundation/example/lib/main.dart b/packages/video_player/video_player_avfoundation/example/lib/main.dart index 007c4698f0bf..2e5e07c909bb 100644 --- a/packages/video_player/video_player_avfoundation/example/lib/main.dart +++ b/packages/video_player/video_player_avfoundation/example/lib/main.dart @@ -324,6 +324,10 @@ class _ControlsOverlay extends StatelessWidget { controller.value.isPlaying ? controller.pause() : controller.play(); }, ), + Align( + alignment: Alignment.topLeft, + child: _PipButton(controller: controller), + ), Align( alignment: Alignment.topRight, child: PopupMenuButton( @@ -354,3 +358,76 @@ class _ControlsOverlay extends StatelessWidget { ); } } + +class _PipButton extends StatefulWidget { + const _PipButton({required this.controller}); + + final MiniController controller; + + @override + State<_PipButton> createState() => _PipButtonState(); +} + +class _PipButtonState extends State<_PipButton> { + bool _isSupported = false; + bool _waitingForInit = false; + + @override + void initState() { + super.initState(); + if (widget.controller.value.isInitialized) { + _querySupport(); + } else { + _waitingForInit = true; + widget.controller.addListener(_onInitialized); + } + } + + @override + void dispose() { + if (_waitingForInit) { + widget.controller.removeListener(_onInitialized); + } + super.dispose(); + } + + void _onInitialized() { + if (widget.controller.value.isInitialized) { + _waitingForInit = false; + widget.controller.removeListener(_onInitialized); + _querySupport(); + } + } + + Future _querySupport() async { + final bool supported = await widget.controller + .isPictureInPictureSupported(); + if (mounted) { + setState(() { + _isSupported = supported; + }); + } + } + + @override + Widget build(BuildContext context) { + if (!_isSupported) { + return const SizedBox.shrink(); + } + final bool isActive = widget.controller.value.isPictureInPictureActive; + return IconButton( + icon: Icon( + isActive ? Icons.picture_in_picture : Icons.picture_in_picture_alt, + color: Colors.white, + ), + tooltip: isActive ? 'Stop PiP' : 'Start PiP', + onPressed: () async { + if (isActive) { + await widget.controller.stopPictureInPicture(); + } else { + await widget.controller.startPictureInPicture(); + } + }, + ); + } +} diff --git a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart index bfc6b1dd496f..3dadaca5060c 100644 --- a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart +++ b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart @@ -36,6 +36,7 @@ class VideoPlayerValue { this.isBuffering = false, this.playbackSpeed = 1.0, this.errorDescription, + this.isPictureInPictureActive = false, }); /// Returns an instance for a video that hasn't been loaded. @@ -81,6 +82,9 @@ class VideoPlayerValue { /// Indicates whether or not the video has been loaded and is ready to play. final bool isInitialized; + /// Whether Picture-in-Picture is currently active. + final bool isPictureInPictureActive; + /// Indicates whether or not the video is in an error state. If this is true /// [errorDescription] should have information about the problem. bool get hasError => errorDescription != null; @@ -114,6 +118,7 @@ class VideoPlayerValue { bool? isBuffering, double? playbackSpeed, String? errorDescription, + bool? isPictureInPictureActive, }) { return VideoPlayerValue( duration: duration ?? this.duration, @@ -125,6 +130,8 @@ class VideoPlayerValue { isBuffering: isBuffering ?? this.isBuffering, playbackSpeed: playbackSpeed ?? this.playbackSpeed, errorDescription: errorDescription ?? this.errorDescription, + isPictureInPictureActive: + isPictureInPictureActive ?? this.isPictureInPictureActive, ); } @@ -141,7 +148,8 @@ class VideoPlayerValue { playbackSpeed == other.playbackSpeed && errorDescription == other.errorDescription && size == other.size && - isInitialized == other.isInitialized; + isInitialized == other.isInitialized && + isPictureInPictureActive == other.isPictureInPictureActive; @override int get hashCode => Object.hash( @@ -154,6 +162,7 @@ class VideoPlayerValue { errorDescription, size, isInitialized, + isPictureInPictureActive, ); } @@ -278,6 +287,13 @@ class MiniController extends ValueNotifier { value = value.copyWith(isBuffering: false); case VideoEventType.isPlayingStateUpdate: value = value.copyWith(isPlaying: event.isPlaying); + case VideoEventType.pipStarted: + value = value.copyWith(isPictureInPictureActive: true); + case VideoEventType.pipStopped: + value = value.copyWith(isPictureInPictureActive: false); + case VideoEventType.pipRestoreUserInterface: + // Return to the app from PiP. + break; case VideoEventType.unknown: break; } @@ -372,6 +388,26 @@ class MiniController extends ValueNotifier { void _updatePosition(Duration position) { value = value.copyWith(position: position); } + + /// Enables Picture-in-Picture for this player. + Future enablePictureInPicture() async { + await _platform.enablePictureInPicture(_playerId); + } + + /// Starts Picture-in-Picture playback. + Future startPictureInPicture() async { + await _platform.startPictureInPicture(_playerId); + } + + /// Stops Picture-in-Picture playback. + Future stopPictureInPicture() async { + await _platform.stopPictureInPicture(_playerId); + } + + /// Returns whether Picture-in-Picture is supported on this device. + Future isPictureInPictureSupported() async { + return _platform.isPictureInPictureSupported(_playerId); + } } /// Widget that displays the video controlled by [controller]. 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 6684d9c4c658..d8c0d51f22ee 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 @@ -243,6 +243,36 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { ); } + @override + Future enablePictureInPicture(int playerId) { + return _playerWith(id: playerId).enablePictureInPicture(); + } + + @override + Future disablePictureInPicture(int playerId) { + return _playerWith(id: playerId).disablePictureInPicture(); + } + + @override + Future startPictureInPicture(int playerId) { + return _playerWith(id: playerId).startPictureInPicture(); + } + + @override + Future stopPictureInPicture(int playerId) { + return _playerWith(id: playerId).stopPictureInPicture(); + } + + @override + Future isPictureInPictureSupported(int playerId) { + return _playerWith(id: playerId).isPictureInPictureSupported(); + } + + @override + Future isPictureInPictureActive(int playerId) { + return _playerWith(id: playerId).isPictureInPictureActive(); + } + _PlayerInstance _playerWith({required int id}) { final _PlayerInstance? player = _players[id]; return player ?? (throw StateError('No active player with ID $id.')); @@ -289,6 +319,19 @@ class _PlayerInstance { Future selectAudioTrack(int trackIndex) => _api.selectAudioTrack(trackIndex); + Future enablePictureInPicture() => _api.enablePictureInPicture(); + + Future disablePictureInPicture() => _api.disablePictureInPicture(); + + Future startPictureInPicture() => _api.startPictureInPicture(); + + Future stopPictureInPicture() => _api.stopPictureInPicture(); + + Future isPictureInPictureSupported() => + _api.isPictureInPictureSupported(); + + Future isPictureInPictureActive() => _api.isPictureInPictureActive(); + Stream get videoEvents { _eventSubscription ??= _eventChannel.receiveBroadcastStream().listen( _onStreamEvent, @@ -331,6 +374,11 @@ class _PlayerInstance { eventType: VideoEventType.isPlayingStateUpdate, isPlaying: map['isPlaying'] as bool, ), + 'pipStarted' => VideoEvent(eventType: VideoEventType.pipStarted), + 'pipStopped' => VideoEvent(eventType: VideoEventType.pipStopped), + 'pipRestoreUserInterface' => VideoEvent( + eventType: VideoEventType.pipRestoreUserInterface, + ), _ => VideoEvent(eventType: VideoEventType.unknown), }); } 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 24644d8f42d0..4f52aed4533c 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 @@ -678,4 +678,158 @@ class VideoPlayerInstanceApi { return; } } + + /// Enables Picture-in-Picture for this player. + Future enablePictureInPicture() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.enablePictureInPicture$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Disables Picture-in-Picture for this player. + Future disablePictureInPicture() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.disablePictureInPicture$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Starts Picture-in-Picture playback. + Future startPictureInPicture() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.startPictureInPicture$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Stops Picture-in-Picture playback. + Future stopPictureInPicture() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.stopPictureInPicture$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else { + return; + } + } + + /// Returns whether Picture-in-Picture is supported on this device. + Future isPictureInPictureSupported() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureSupported$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else 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 bool?)!; + } + } + + /// Returns whether Picture-in-Picture is currently active. + Future isPictureInPictureActive() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureActive$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { + throw PlatformException( + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], + ); + } else 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 bool?)!; + } + } } diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index f49b46005307..eafa51be64d3 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -93,4 +93,28 @@ abstract class VideoPlayerInstanceApi { List getAudioTracks(); @ObjCSelector('selectAudioTrackAtIndex:') void selectAudioTrack(int trackIndex); + + /// Enables Picture-in-Picture for this player. + @ObjCSelector('enablePictureInPicture') + void enablePictureInPicture(); + + /// Disables Picture-in-Picture for this player. + @ObjCSelector('disablePictureInPicture') + void disablePictureInPicture(); + + /// Starts Picture-in-Picture playback. + @ObjCSelector('startPictureInPicture') + void startPictureInPicture(); + + /// Stops Picture-in-Picture playback. + @ObjCSelector('stopPictureInPicture') + void stopPictureInPicture(); + + /// Returns whether Picture-in-Picture is supported on this device. + @ObjCSelector('isPictureInPictureSupported') + bool isPictureInPictureSupported(); + + /// Returns whether Picture-in-Picture is currently active. + @ObjCSelector('isPictureInPictureActive') + bool isPictureInPictureActive(); } diff --git a/packages/video_player/video_player_avfoundation/pubspec.yaml b/packages/video_player/video_player_avfoundation/pubspec.yaml index 853ce2760ae8..7b677575486f 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.9.3 +version: 2.10.0 environment: sdk: ^3.10.0 @@ -24,7 +24,7 @@ flutter: dependencies: flutter: sdk: flutter - video_player_platform_interface: ^6.6.0 + video_player_platform_interface: ^6.7.0 dev_dependencies: build_runner: ^2.3.3 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 c16d5bcb08e9..9494b81447a8 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 @@ -690,5 +690,160 @@ void main() { ]), ); }); + + group('Picture-in-Picture', () { + test('enablePictureInPicture', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer( + playerId: 1, + ); + await player.enablePictureInPicture(1); + + verify(playerApi.enablePictureInPicture()); + }); + + test('disablePictureInPicture', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer( + playerId: 1, + ); + await player.disablePictureInPicture(1); + + verify(playerApi.disablePictureInPicture()); + }); + + test('startPictureInPicture', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer( + playerId: 1, + ); + await player.startPictureInPicture(1); + + verify(playerApi.startPictureInPicture()); + }); + + test('stopPictureInPicture', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer( + playerId: 1, + ); + await player.stopPictureInPicture(1); + + verify(playerApi.stopPictureInPicture()); + }); + + test('isPictureInPictureSupported', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer( + playerId: 1, + ); + when( + playerApi.isPictureInPictureSupported(), + ).thenAnswer((_) async => true); + + final bool result = await player.isPictureInPictureSupported(1); + + expect(result, true); + verify(playerApi.isPictureInPictureSupported()); + }); + + test('isPictureInPictureActive', () async { + final ( + AVFoundationVideoPlayer player, + _, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer( + playerId: 1, + ); + when( + playerApi.isPictureInPictureActive(), + ).thenAnswer((_) async => false); + + final bool result = await player.isPictureInPictureActive(1); + + expect(result, false); + verify(playerApi.isPictureInPictureActive()); + }); + }); + + test('videoEventsFor emits PiP events', () async { + const playerId = 1; + final ( + AVFoundationVideoPlayer player, + MockAVFoundationVideoPlayerApi api, + _, + ) = setUpMockPlayer( + playerId: playerId, + ); + const mockChannel = 'flutter.dev/videoPlayer/videoEvents$playerId'; + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMessageHandler(mockChannel, (ByteData? message) async { + final MethodCall methodCall = const StandardMethodCodec() + .decodeMethodCall(message); + if (methodCall.method == 'listen') { + await TestDefaultBinaryMessengerBinding + .instance + .defaultBinaryMessenger + .handlePlatformMessage( + mockChannel, + const StandardMethodCodec().encodeSuccessEnvelope( + {'event': 'pipStarted'}, + ), + (ByteData? data) {}, + ); + + await TestDefaultBinaryMessengerBinding + .instance + .defaultBinaryMessenger + .handlePlatformMessage( + mockChannel, + const StandardMethodCodec().encodeSuccessEnvelope( + {'event': 'pipStopped'}, + ), + (ByteData? data) {}, + ); + + await TestDefaultBinaryMessengerBinding + .instance + .defaultBinaryMessenger + .handlePlatformMessage( + mockChannel, + const StandardMethodCodec().encodeSuccessEnvelope( + {'event': 'pipRestoreUserInterface'}, + ), + (ByteData? data) {}, + ); + + return const StandardMethodCodec().encodeSuccessEnvelope(null); + } else if (methodCall.method == 'cancel') { + return const StandardMethodCodec().encodeSuccessEnvelope(null); + } else { + fail('Expected listen or cancel'); + } + }); + expect( + player.videoEventsFor(playerId), + emitsInOrder([ + VideoEvent(eventType: VideoEventType.pipStarted), + VideoEvent(eventType: VideoEventType.pipStopped), + VideoEvent(eventType: VideoEventType.pipRestoreUserInterface), + ]), + ); + }); }); } diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart index 8caf6ad8dc43..e27f8408f357 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart @@ -22,6 +22,7 @@ import 'package:video_player_avfoundation/src/messages.g.dart' as _i2; // ignore_for_file: unnecessary_parenthesis // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +// ignore_for_file: invalid_use_of_internal_member class _FakeTexturePlayerIds_0 extends _i1.SmartFake implements _i2.TexturePlayerIds { @@ -198,4 +199,82 @@ class MockVideoPlayerInstanceApi extends _i1.Mock returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); + + @override + _i4.Future> getAudioTracks() => + (super.noSuchMethod( + Invocation.method(#getAudioTracks, []), + returnValue: + _i4.Future>.value( + <_i2.MediaSelectionAudioTrackData>[], + ), + returnValueForMissingStub: + _i4.Future>.value( + <_i2.MediaSelectionAudioTrackData>[], + ), + ) + as _i4.Future>); + + @override + _i4.Future selectAudioTrack(int? trackIndex) => + (super.noSuchMethod( + Invocation.method(#selectAudioTrack, [trackIndex]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future enablePictureInPicture() => + (super.noSuchMethod( + Invocation.method(#enablePictureInPicture, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future disablePictureInPicture() => + (super.noSuchMethod( + Invocation.method(#disablePictureInPicture, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future startPictureInPicture() => + (super.noSuchMethod( + Invocation.method(#startPictureInPicture, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future stopPictureInPicture() => + (super.noSuchMethod( + Invocation.method(#stopPictureInPicture, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future isPictureInPictureSupported() => + (super.noSuchMethod( + Invocation.method(#isPictureInPictureSupported, []), + returnValue: _i4.Future.value(false), + returnValueForMissingStub: _i4.Future.value(false), + ) + as _i4.Future); + + @override + _i4.Future isPictureInPictureActive() => + (super.noSuchMethod( + Invocation.method(#isPictureInPictureActive, []), + returnValue: _i4.Future.value(false), + returnValueForMissingStub: _i4.Future.value(false), + ) + as _i4.Future); } diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 4b44b050047a..8fd35bc454e1 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,6 +1,8 @@ -## NEXT +## 6.7.0 * Updates minimum supported SDK version to Flutter 3.35/Dart 3.9. +* Adds `enablePictureInPicture()`, `disablePictureInPicture()`, `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods for Picture-in-Picture support. +* Adds `pipStarted`, `pipStopped`, and `pipRestoreUserInterface` event types to `VideoEventType`. ## 6.6.0 diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index 1cec5f42c218..f77130ffa36a 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -153,6 +153,48 @@ abstract class VideoPlayerPlatform extends PlatformInterface { bool isAudioTrackSupportAvailable() { return false; } + + /// Enables Picture-in-Picture for the given player. + Future enablePictureInPicture(int playerId) { + throw UnimplementedError( + 'enablePictureInPicture() has not been implemented.', + ); + } + + /// Disables Picture-in-Picture for the given player. + Future disablePictureInPicture(int playerId) { + throw UnimplementedError( + 'disablePictureInPicture() has not been implemented.', + ); + } + + /// Starts Picture-in-Picture mode for the given player. + Future startPictureInPicture(int playerId) { + throw UnimplementedError( + 'startPictureInPicture() has not been implemented.', + ); + } + + /// Stops Picture-in-Picture mode for the given player. + Future stopPictureInPicture(int playerId) { + throw UnimplementedError( + 'stopPictureInPicture() has not been implemented.', + ); + } + + /// Returns whether Picture-in-Picture is supported on this device. + Future isPictureInPictureSupported(int playerId) { + throw UnimplementedError( + 'isPictureInPictureSupported() has not been implemented.', + ); + } + + /// Returns whether Picture-in-Picture is currently active for the given player. + Future isPictureInPictureActive(int playerId) { + throw UnimplementedError( + 'isPictureInPictureActive() has not been implemented.', + ); + } } class _PlaceholderImplementation extends VideoPlayerPlatform {} @@ -355,6 +397,15 @@ enum VideoEventType { /// phone calls, or other app media such as music players. isPlayingStateUpdate, + /// Picture-in-Picture playback started. + pipStarted, + + /// Picture-in-Picture playback stopped. + pipStopped, + + /// The user tapped the restore button in the PiP window to return to the app. + pipRestoreUserInterface, + /// An unknown event has been received. unknown, } diff --git a/packages/video_player/video_player_platform_interface/pubspec.yaml b/packages/video_player/video_player_platform_interface/pubspec.yaml index b39acce19665..373ddb91f1fb 100644 --- a/packages/video_player/video_player_platform_interface/pubspec.yaml +++ b/packages/video_player/video_player_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/video_player/ issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 6.6.0 +version: 6.7.0 environment: sdk: ^3.9.0 diff --git a/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart b/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart index 2d920161ec9e..101c9a49350c 100644 --- a/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart +++ b/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart @@ -40,4 +40,64 @@ void main() { test('default implementation isAudioTrackSupportAvailable returns false', () { expect(initialInstance.isAudioTrackSupportAvailable(), false); }); + + test( + 'default implementation enablePictureInPicture throws unimplemented', + () async { + await expectLater( + () => initialInstance.enablePictureInPicture(1), + throwsUnimplementedError, + ); + }, + ); + + test( + 'default implementation disablePictureInPicture throws unimplemented', + () async { + await expectLater( + () => initialInstance.disablePictureInPicture(1), + throwsUnimplementedError, + ); + }, + ); + + test( + 'default implementation startPictureInPicture throws unimplemented', + () async { + await expectLater( + () => initialInstance.startPictureInPicture(1), + throwsUnimplementedError, + ); + }, + ); + + test( + 'default implementation stopPictureInPicture throws unimplemented', + () async { + await expectLater( + () => initialInstance.stopPictureInPicture(1), + throwsUnimplementedError, + ); + }, + ); + + test( + 'default implementation isPictureInPictureSupported throws unimplemented', + () async { + await expectLater( + () => initialInstance.isPictureInPictureSupported(1), + throwsUnimplementedError, + ); + }, + ); + + test( + 'default implementation isPictureInPictureActive throws unimplemented', + () async { + await expectLater( + () => initialInstance.isPictureInPictureActive(1), + throwsUnimplementedError, + ); + }, + ); } From a9ccb4314e12ebd83e2e96111c9c21ac87b2ea30 Mon Sep 17 00:00:00 2001 From: akaboshinit Date: Tue, 24 Feb 2026 10:00:05 +0900 Subject: [PATCH 2/2] [video_player] Remove redundant enablePictureInPicture/disablePictureInPicture APIs The native iOS implementation already sets up PiP automatically during player initialization, making enablePictureInPicture a no-op check. disablePictureInPicture's cleanup is already handled by dispose. Removing these simplifies the API surface without losing functionality. --- .../video_player/video_player/CHANGELOG.md | 2 +- .../video_player/lib/video_player.dart | 26 -- .../video_player/test/video_player_test.dart | 40 --- .../video_player_avfoundation/CHANGELOG.md | 2 +- .../FVPVideoPlayer.m | 32 --- .../video_player_avfoundation/messages.g.h | 4 - .../video_player_avfoundation/messages.g.m | 36 --- .../example/lib/mini_controller.dart | 7 - .../lib/src/avfoundation_video_player.dart | 35 +-- .../lib/src/messages.g.dart | 266 ++++++------------ .../pigeons/messages.dart | 8 - .../test/avfoundation_video_player_test.dart | 26 -- .../avfoundation_video_player_test.mocks.dart | 18 -- .../CHANGELOG.md | 2 +- .../lib/video_player_platform_interface.dart | 14 - .../video_player_platform_interface_test.dart | 20 -- 16 files changed, 101 insertions(+), 437 deletions(-) diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 246571b484cd..ad9c2c1f70c0 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -2,7 +2,7 @@ * Adds Picture-in-Picture (PiP) support for iOS. * Adds `isPictureInPictureActive` field to `VideoPlayerValue`. -* Adds `enablePictureInPicture()`, `disablePictureInPicture()`, `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods to `VideoPlayerController`. +* Adds `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods to `VideoPlayerController`. ## 2.11.0 diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 7e9bb777c5ce..6f3d6351a2e1 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -661,7 +661,6 @@ class VideoPlayerController extends ValueNotifier { case platform_interface.VideoEventType.pipStopped: value = value.copyWith(isPictureInPictureActive: false); case platform_interface.VideoEventType.pipRestoreUserInterface: - break; case platform_interface.VideoEventType.unknown: break; } @@ -1004,33 +1003,8 @@ class VideoPlayerController extends ValueNotifier { return _videoPlayerPlatform.isAudioTrackSupportAvailable(); } - /// Enables Picture-in-Picture for this player. - /// - /// This must be called before [startPictureInPicture] to prepare the player - /// for PiP mode. On platforms that don't support PiP, this may be a no-op. - /// - /// Throws a [StateError] if the controller is disposed or not initialized. - Future enablePictureInPicture() async { - if (_isDisposedOrNotInitialized) { - throw StateError('VideoPlayerController is disposed or not initialized'); - } - await _videoPlayerPlatform.enablePictureInPicture(_playerId); - } - - /// Disables Picture-in-Picture for this player. - /// - /// Throws a [StateError] if the controller is disposed or not initialized. - Future disablePictureInPicture() async { - if (_isDisposedOrNotInitialized) { - throw StateError('VideoPlayerController is disposed or not initialized'); - } - await _videoPlayerPlatform.disablePictureInPicture(_playerId); - } - /// Starts Picture-in-Picture mode for this player. /// - /// [enablePictureInPicture] must be called before this method. - /// /// Throws a [StateError] if the controller is disposed or not initialized. Future startPictureInPicture() async { if (_isDisposedOrNotInitialized) { diff --git a/packages/video_player/video_player/test/video_player_test.dart b/packages/video_player/video_player/test/video_player_test.dart index a7a383843f09..9bce563962ce 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -130,12 +130,6 @@ class FakeController extends ValueNotifier return true; } - @override - Future enablePictureInPicture() async {} - - @override - Future disablePictureInPicture() async {} - @override Future startPictureInPicture() async {} @@ -1069,26 +1063,6 @@ void main() { }); group('Picture-in-Picture', () { - test('enablePictureInPicture calls platform', () async { - final controller = VideoPlayerController.networkUrl(_localhostUri); - addTearDown(controller.dispose); - - await controller.initialize(); - await controller.enablePictureInPicture(); - - expect(fakeVideoPlayerPlatform.calls.last, 'enablePictureInPicture'); - }); - - test('disablePictureInPicture calls platform', () async { - final controller = VideoPlayerController.networkUrl(_localhostUri); - addTearDown(controller.dispose); - - await controller.initialize(); - await controller.disablePictureInPicture(); - - expect(fakeVideoPlayerPlatform.calls.last, 'disablePictureInPicture'); - }); - test('startPictureInPicture calls platform', () async { final controller = VideoPlayerController.networkUrl(_localhostUri); addTearDown(controller.dispose); @@ -1138,10 +1112,6 @@ void main() { final controller = VideoPlayerController.networkUrl(_localhostUri); addTearDown(controller.dispose); - expect( - () => controller.enablePictureInPicture(), - throwsA(isA()), - ); expect( () => controller.startPictureInPicture(), throwsA(isA()), @@ -2046,16 +2016,6 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { final Map selectedAudioTrackIds = {}; - @override - Future enablePictureInPicture(int playerId) async { - calls.add('enablePictureInPicture'); - } - - @override - Future disablePictureInPicture(int playerId) async { - calls.add('disablePictureInPicture'); - } - @override Future startPictureInPicture(int playerId) async { calls.add('startPictureInPicture'); diff --git a/packages/video_player/video_player_avfoundation/CHANGELOG.md b/packages/video_player/video_player_avfoundation/CHANGELOG.md index 152df0493742..d2f4fc1c3493 100644 --- a/packages/video_player/video_player_avfoundation/CHANGELOG.md +++ b/packages/video_player/video_player_avfoundation/CHANGELOG.md @@ -1,7 +1,7 @@ ## 2.10.0 * Adds Picture-in-Picture (PiP) support for iOS. -* Implements `enablePictureInPicture()`, `disablePictureInPicture()`, `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods. +* Implements `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods. ## 2.9.3 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 e8ce67743e26..851366b849c8 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 @@ -535,29 +535,6 @@ - (void)tearDownPictureInPicture API_AVAILABLE(ios(14.2)) { _pipController = nil; } -- (void)enablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { - if (@available(iOS 14.2, *)) { - // PiP controller is already set up automatically for texture-based players. - // For platform view players, it's set up when the view is created. - if (!_pipController) { - *error = [FlutterError - errorWithCode:@"pip_not_available" - message:@"PiP controller not configured. Ensure a player layer is available." - details:nil]; - } - } else { - *error = [FlutterError errorWithCode:@"pip_not_supported" - message:@"PiP requires iOS 14.2+" - details:nil]; - } -} - -- (void)disablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { - if (@available(iOS 14.2, *)) { - [self tearDownPictureInPicture]; - } -} - - (void)startPictureInPicture:(FlutterError *_Nullable *_Nonnull)error { if (@available(iOS 14.2, *)) { if (!_pipController) { @@ -658,15 +635,6 @@ - (void)pictureInPictureController:(AVPictureInPictureController *)controller #else // macOS stubs - PiP is iOS only in this implementation. -- (void)enablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { - *error = [FlutterError errorWithCode:@"pip_not_supported" - message:@"PiP is only supported on iOS" - details:nil]; -} - -- (void)disablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error { -} - - (void)startPictureInPicture:(FlutterError *_Nullable *_Nonnull)error { *error = [FlutterError errorWithCode:@"pip_not_supported" message:@"PiP is only supported on iOS" 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 08320965c246..c3cfef2594f9 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 @@ -91,10 +91,6 @@ extern void SetUpFVPAVFoundationVideoPlayerApiWithSuffix(id *)getAudioTracks:(FlutterError *_Nullable *_Nonnull)error; - (void)selectAudioTrackAtIndex:(NSInteger)trackIndex error:(FlutterError *_Nullable *_Nonnull)error; -/// Enables Picture-in-Picture for this player. -- (void)enablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error; -/// Disables Picture-in-Picture for this player. -- (void)disablePictureInPicture:(FlutterError *_Nullable *_Nonnull)error; /// Starts Picture-in-Picture playback. - (void)startPictureInPicture:(FlutterError *_Nullable *_Nonnull)error; /// Stops Picture-in-Picture playback. 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 8709b6eecfb7..90b5b9326297 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 @@ -506,42 +506,6 @@ void SetUpFVPVideoPlayerInstanceApiWithSuffix(id binaryM [channel setMessageHandler:nil]; } } - /// Enables Picture-in-Picture for this player. - { - FlutterBasicMessageChannel *channel = - [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.enablePictureInPicture", messageChannelSuffix] - binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(enablePictureInPicture:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(enablePictureInPicture:)", api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api enablePictureInPicture:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } - /// Disables Picture-in-Picture for this player. - { - FlutterBasicMessageChannel *channel = - [[FlutterBasicMessageChannel alloc] - initWithName:[NSString stringWithFormat:@"%@%@", @"dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.disablePictureInPicture", messageChannelSuffix] - binaryMessenger:binaryMessenger - codec:FVPGetMessagesCodec()]; - if (api) { - NSCAssert([api respondsToSelector:@selector(disablePictureInPicture:)], @"FVPVideoPlayerInstanceApi api (%@) doesn't respond to @selector(disablePictureInPicture:)", api); - [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { - FlutterError *error; - [api disablePictureInPicture:&error]; - callback(wrapResult(nil, error)); - }]; - } else { - [channel setMessageHandler:nil]; - } - } /// Starts Picture-in-Picture playback. { FlutterBasicMessageChannel *channel = diff --git a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart index 3dadaca5060c..6617df409cf6 100644 --- a/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart +++ b/packages/video_player/video_player_avfoundation/example/lib/mini_controller.dart @@ -292,8 +292,6 @@ class MiniController extends ValueNotifier { case VideoEventType.pipStopped: value = value.copyWith(isPictureInPictureActive: false); case VideoEventType.pipRestoreUserInterface: - // Return to the app from PiP. - break; case VideoEventType.unknown: break; } @@ -389,11 +387,6 @@ class MiniController extends ValueNotifier { value = value.copyWith(position: position); } - /// Enables Picture-in-Picture for this player. - Future enablePictureInPicture() async { - await _platform.enablePictureInPicture(_playerId); - } - /// Starts Picture-in-Picture playback. Future startPictureInPicture() async { await _platform.startPictureInPicture(_playerId); 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 d8c0d51f22ee..5aa3c1cc8f0b 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 @@ -183,21 +183,14 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { final List nativeData = await _playerWith( id: playerId, ).getAudioTracks(); - final tracks = []; - - for (final track in nativeData) { - final String? label = track.commonMetadataTitle ?? track.displayName; - tracks.add( - VideoAudioTrack( - id: track.index.toString(), - label: label, - language: track.languageCode, - isSelected: track.isSelected, - ), + return nativeData.map((MediaSelectionAudioTrackData track) { + return VideoAudioTrack( + id: track.index.toString(), + label: track.commonMetadataTitle ?? track.displayName, + language: track.languageCode, + isSelected: track.isSelected, ); - } - - return tracks; + }).toList(); } @override @@ -243,16 +236,6 @@ class AVFoundationVideoPlayer extends VideoPlayerPlatform { ); } - @override - Future enablePictureInPicture(int playerId) { - return _playerWith(id: playerId).enablePictureInPicture(); - } - - @override - Future disablePictureInPicture(int playerId) { - return _playerWith(id: playerId).disablePictureInPicture(); - } - @override Future startPictureInPicture(int playerId) { return _playerWith(id: playerId).startPictureInPicture(); @@ -319,10 +302,6 @@ class _PlayerInstance { Future selectAudioTrack(int trackIndex) => _api.selectAudioTrack(trackIndex); - Future enablePictureInPicture() => _api.enablePictureInPicture(); - - Future disablePictureInPicture() => _api.disablePictureInPicture(); - Future startPictureInPicture() => _api.startPictureInPicture(); Future stopPictureInPicture() => _api.stopPictureInPicture(); 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 4f52aed4533c..9210d36f2869 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 @@ -17,49 +17,49 @@ PlatformException _createConnectionError(String channelName) { message: 'Unable to establish connection on channel: "$channelName".', ); } - 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]), - ); + a.indexed + .every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); } 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 a.length == b.length && a.entries.every((MapEntry entry) => + (b as Map).containsKey(entry.key) && + _deepEquals(entry.value, b[entry.key])); } return a == b; } + /// Information passed to the platform view creation. class PlatformVideoViewCreationParams { - PlatformVideoViewCreationParams({required this.playerId}); + PlatformVideoViewCreationParams({ + required this.playerId, + }); int playerId; List _toList() { - return [playerId]; + return [ + playerId, + ]; } Object encode() { - return _toList(); - } + return _toList(); } static PlatformVideoViewCreationParams decode(Object result) { result as List; - return PlatformVideoViewCreationParams(playerId: result[0]! as int); + 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) { + if (other is! PlatformVideoViewCreationParams || other.runtimeType != runtimeType) { return false; } if (identical(this, other)) { @@ -70,30 +70,35 @@ class PlatformVideoViewCreationParams { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => Object.hashAll(_toList()) +; } class CreationOptions { - CreationOptions({required this.uri, required this.httpHeaders}); + CreationOptions({ + required this.uri, + required this.httpHeaders, + }); String uri; Map httpHeaders; List _toList() { - return [uri, httpHeaders]; + return [ + uri, + httpHeaders, + ]; } Object encode() { - return _toList(); - } + return _toList(); } static CreationOptions decode(Object result) { result as List; return CreationOptions( uri: result[0]! as String, - httpHeaders: (result[1] as Map?)! - .cast(), + httpHeaders: (result[1] as Map?)!.cast(), ); } @@ -111,23 +116,29 @@ class CreationOptions { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => Object.hashAll(_toList()) +; } class TexturePlayerIds { - TexturePlayerIds({required this.playerId, required this.textureId}); + TexturePlayerIds({ + required this.playerId, + required this.textureId, + }); int playerId; int textureId; List _toList() { - return [playerId, textureId]; + return [ + playerId, + textureId, + ]; } Object encode() { - return _toList(); - } + return _toList(); } static TexturePlayerIds decode(Object result) { result as List; @@ -151,7 +162,8 @@ class TexturePlayerIds { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => Object.hashAll(_toList()) +; } /// Raw audio track data from AVMediaSelectionOption (for HLS streams). @@ -185,8 +197,7 @@ class MediaSelectionAudioTrackData { } Object encode() { - return _toList(); - } + return _toList(); } static MediaSelectionAudioTrackData decode(Object result) { result as List; @@ -202,8 +213,7 @@ class MediaSelectionAudioTrackData { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) { - if (other is! MediaSelectionAudioTrackData || - other.runtimeType != runtimeType) { + if (other is! MediaSelectionAudioTrackData || other.runtimeType != runtimeType) { return false; } if (identical(this, other)) { @@ -214,9 +224,11 @@ class MediaSelectionAudioTrackData { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => Object.hashAll(_toList()); + int get hashCode => Object.hashAll(_toList()) +; } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -224,16 +236,16 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is PlatformVideoViewCreationParams) { + } else if (value is PlatformVideoViewCreationParams) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is CreationOptions) { + } else if (value is CreationOptions) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is TexturePlayerIds) { + } else if (value is TexturePlayerIds) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is MediaSelectionAudioTrackData) { + } else if (value is MediaSelectionAudioTrackData) { buffer.putUint8(132); writeValue(buffer, value.encode()); } else { @@ -244,13 +256,13 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: return PlatformVideoViewCreationParams.decode(readValue(buffer)!); - case 130: + case 130: return CreationOptions.decode(readValue(buffer)!); - case 131: + case 131: return TexturePlayerIds.decode(readValue(buffer)!); - case 132: + case 132: return MediaSelectionAudioTrackData.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -262,13 +274,9 @@ class AVFoundationVideoPlayerApi { /// Constructor for [AVFoundationVideoPlayerApi]. 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. - AVFoundationVideoPlayerApi({ - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty - ? '.$messageChannelSuffix' - : ''; + AVFoundationVideoPlayerApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -276,8 +284,7 @@ class AVFoundationVideoPlayerApi { final String pigeonVar_messageChannelSuffix; Future initialize() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.initialize$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -299,16 +306,13 @@ class AVFoundationVideoPlayerApi { } Future createForPlatformView(CreationOptions params) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForPlatformView$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [params], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([params]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -328,19 +332,14 @@ class AVFoundationVideoPlayerApi { } } - Future createForTextureView( - CreationOptions creationOptions, - ) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView$pigeonVar_messageChannelSuffix'; + Future createForTextureView(CreationOptions creationOptions) async { + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.createForTextureView$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [creationOptions], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([creationOptions]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -361,16 +360,13 @@ class AVFoundationVideoPlayerApi { } Future setMixWithOthers(bool mixWithOthers) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [mixWithOthers], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([mixWithOthers]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -386,16 +382,13 @@ class AVFoundationVideoPlayerApi { } Future getAssetUrl(String asset, String? package) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.AVFoundationVideoPlayerApi.getAssetUrl$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [asset, package], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([asset, package]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -415,13 +408,9 @@ 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' - : ''; + VideoPlayerInstanceApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -429,16 +418,13 @@ class VideoPlayerInstanceApi { final String pigeonVar_messageChannelSuffix; Future setLooping(bool looping) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setLooping$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setLooping$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [looping], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([looping]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -454,16 +440,13 @@ class VideoPlayerInstanceApi { } Future setVolume(double volume) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setVolume$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setVolume$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [volume], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([volume]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -479,16 +462,13 @@ class VideoPlayerInstanceApi { } Future setPlaybackSpeed(double speed) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [speed], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([speed]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -504,8 +484,7 @@ class VideoPlayerInstanceApi { } Future play() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.play$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.play$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -527,8 +506,7 @@ class VideoPlayerInstanceApi { } Future getPosition() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getPosition$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getPosition$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -555,16 +533,13 @@ class VideoPlayerInstanceApi { } Future seekTo(int position) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [position], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([position]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -580,8 +555,7 @@ class VideoPlayerInstanceApi { } Future pause() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -603,8 +577,7 @@ class VideoPlayerInstanceApi { } Future dispose() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.dispose$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.dispose$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -626,8 +599,7 @@ class VideoPlayerInstanceApi { } Future> getAudioTracks() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getAudioTracks$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.getAudioTracks$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -649,70 +621,18 @@ class VideoPlayerInstanceApi { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); + return (pigeonVar_replyList[0] as List?)!.cast(); } } Future selectAudioTrack(int trackIndex) async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.selectAudioTrack$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [trackIndex], - ); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - /// Enables Picture-in-Picture for this player. - Future enablePictureInPicture() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.enablePictureInPicture$pigeonVar_messageChannelSuffix'; - final pigeonVar_channel = BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); - final pigeonVar_replyList = await pigeonVar_sendFuture as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - /// Disables Picture-in-Picture for this player. - Future disablePictureInPicture() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.disablePictureInPicture$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.selectAudioTrack$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([trackIndex]); final pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -729,8 +649,7 @@ class VideoPlayerInstanceApi { /// Starts Picture-in-Picture playback. Future startPictureInPicture() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.startPictureInPicture$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.startPictureInPicture$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -753,8 +672,7 @@ class VideoPlayerInstanceApi { /// Stops Picture-in-Picture playback. Future stopPictureInPicture() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.stopPictureInPicture$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.stopPictureInPicture$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -777,8 +695,7 @@ class VideoPlayerInstanceApi { /// Returns whether Picture-in-Picture is supported on this device. Future isPictureInPictureSupported() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureSupported$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureSupported$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, @@ -806,8 +723,7 @@ class VideoPlayerInstanceApi { /// Returns whether Picture-in-Picture is currently active. Future isPictureInPictureActive() async { - final pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureActive$pigeonVar_messageChannelSuffix'; + final pigeonVar_channelName = 'dev.flutter.pigeon.video_player_avfoundation.VideoPlayerInstanceApi.isPictureInPictureActive$pigeonVar_messageChannelSuffix'; final pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, diff --git a/packages/video_player/video_player_avfoundation/pigeons/messages.dart b/packages/video_player/video_player_avfoundation/pigeons/messages.dart index eafa51be64d3..0c47f387aeea 100644 --- a/packages/video_player/video_player_avfoundation/pigeons/messages.dart +++ b/packages/video_player/video_player_avfoundation/pigeons/messages.dart @@ -94,14 +94,6 @@ abstract class VideoPlayerInstanceApi { @ObjCSelector('selectAudioTrackAtIndex:') void selectAudioTrack(int trackIndex); - /// Enables Picture-in-Picture for this player. - @ObjCSelector('enablePictureInPicture') - void enablePictureInPicture(); - - /// Disables Picture-in-Picture for this player. - @ObjCSelector('disablePictureInPicture') - void disablePictureInPicture(); - /// Starts Picture-in-Picture playback. @ObjCSelector('startPictureInPicture') void startPictureInPicture(); 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 9494b81447a8..b4de2af273cc 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 @@ -692,32 +692,6 @@ void main() { }); group('Picture-in-Picture', () { - test('enablePictureInPicture', () async { - final ( - AVFoundationVideoPlayer player, - _, - MockVideoPlayerInstanceApi playerApi, - ) = setUpMockPlayer( - playerId: 1, - ); - await player.enablePictureInPicture(1); - - verify(playerApi.enablePictureInPicture()); - }); - - test('disablePictureInPicture', () async { - final ( - AVFoundationVideoPlayer player, - _, - MockVideoPlayerInstanceApi playerApi, - ) = setUpMockPlayer( - playerId: 1, - ); - await player.disablePictureInPicture(1); - - verify(playerApi.disablePictureInPicture()); - }); - test('startPictureInPicture', () async { final ( AVFoundationVideoPlayer player, diff --git a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart index e27f8408f357..7b525a30767d 100644 --- a/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart +++ b/packages/video_player/video_player_avfoundation/test/avfoundation_video_player_test.mocks.dart @@ -224,24 +224,6 @@ class MockVideoPlayerInstanceApi extends _i1.Mock ) as _i4.Future); - @override - _i4.Future enablePictureInPicture() => - (super.noSuchMethod( - Invocation.method(#enablePictureInPicture, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future disablePictureInPicture() => - (super.noSuchMethod( - Invocation.method(#disablePictureInPicture, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - @override _i4.Future startPictureInPicture() => (super.noSuchMethod( diff --git a/packages/video_player/video_player_platform_interface/CHANGELOG.md b/packages/video_player/video_player_platform_interface/CHANGELOG.md index 8fd35bc454e1..2a0fb8535cd1 100644 --- a/packages/video_player/video_player_platform_interface/CHANGELOG.md +++ b/packages/video_player/video_player_platform_interface/CHANGELOG.md @@ -1,7 +1,7 @@ ## 6.7.0 * Updates minimum supported SDK version to Flutter 3.35/Dart 3.9. -* Adds `enablePictureInPicture()`, `disablePictureInPicture()`, `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods for Picture-in-Picture support. +* Adds `startPictureInPicture()`, `stopPictureInPicture()`, `isPictureInPictureSupported()`, and `isPictureInPictureActive()` methods for Picture-in-Picture support. * Adds `pipStarted`, `pipStopped`, and `pipRestoreUserInterface` event types to `VideoEventType`. ## 6.6.0 diff --git a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart index f77130ffa36a..f8e76e8a4dd0 100644 --- a/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart +++ b/packages/video_player/video_player_platform_interface/lib/video_player_platform_interface.dart @@ -154,20 +154,6 @@ abstract class VideoPlayerPlatform extends PlatformInterface { return false; } - /// Enables Picture-in-Picture for the given player. - Future enablePictureInPicture(int playerId) { - throw UnimplementedError( - 'enablePictureInPicture() has not been implemented.', - ); - } - - /// Disables Picture-in-Picture for the given player. - Future disablePictureInPicture(int playerId) { - throw UnimplementedError( - 'disablePictureInPicture() has not been implemented.', - ); - } - /// Starts Picture-in-Picture mode for the given player. Future startPictureInPicture(int playerId) { throw UnimplementedError( diff --git a/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart b/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart index 101c9a49350c..5d82a0a984a4 100644 --- a/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart +++ b/packages/video_player/video_player_platform_interface/test/video_player_platform_interface_test.dart @@ -41,26 +41,6 @@ void main() { expect(initialInstance.isAudioTrackSupportAvailable(), false); }); - test( - 'default implementation enablePictureInPicture throws unimplemented', - () async { - await expectLater( - () => initialInstance.enablePictureInPicture(1), - throwsUnimplementedError, - ); - }, - ); - - test( - 'default implementation disablePictureInPicture throws unimplemented', - () async { - await expectLater( - () => initialInstance.disablePictureInPicture(1), - throwsUnimplementedError, - ); - }, - ); - test( 'default implementation startPictureInPicture throws unimplemented', () async {