diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index ee0accb22c79..a44f0e352e66 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.3.0 + +* Adds `allowBackgroundPlayback` to `VideoPlayerOptions`. + ## 2.2.19 * Internal code cleanup for stricter analysis options. diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 672ba2efcde3..b77c530b5831 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -295,7 +295,7 @@ class VideoPlayerController extends ValueNotifier { bool _isDisposed = false; Completer? _creatingCompleter; StreamSubscription? _eventSubscription; - late _VideoAppLifeCycleObserver _lifeCycleObserver; + _VideoAppLifeCycleObserver? _lifeCycleObserver; /// The id of a texture that hasn't been initialized. @visibleForTesting @@ -309,8 +309,12 @@ class VideoPlayerController extends ValueNotifier { /// Attempts to open the given [dataSource] and load metadata about the video. Future initialize() async { - _lifeCycleObserver = _VideoAppLifeCycleObserver(this); - _lifeCycleObserver.initialize(); + final bool allowBackgroundPlayback = + videoPlayerOptions?.allowBackgroundPlayback ?? false; + if (!allowBackgroundPlayback) { + _lifeCycleObserver = _VideoAppLifeCycleObserver(this); + } + _lifeCycleObserver?.initialize(); _creatingCompleter = Completer(); late DataSource dataSourceDescription; @@ -423,7 +427,7 @@ class VideoPlayerController extends ValueNotifier { await _eventSubscription?.cancel(); await _videoPlayerPlatform.dispose(_textureId); } - _lifeCycleObserver.dispose(); + _lifeCycleObserver?.dispose(); } _isDisposed = true; super.dispose(); diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 2f6e28919d00..d58de120541b 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, and web. repository: https://github.com/flutter/plugins/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.2.19 +version: 2.3.0 environment: sdk: ">=2.14.0 <3.0.0" @@ -25,7 +25,7 @@ dependencies: html: ^0.15.0 video_player_android: ^2.2.17 video_player_avfoundation: ^2.2.17 - video_player_platform_interface: ">=4.2.0 <6.0.0" + video_player_platform_interface: ^5.1.0 video_player_web: ^2.0.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 07afd0f65402..5e31e22c7010 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -98,6 +98,19 @@ class _FakeClosedCaptionFile extends ClosedCaptionFile { } void main() { + void _verifyPlayStateRespondsToLifecycle( + VideoPlayerController controller, { + required bool shouldPlayInBackground, + }) { + expect(controller.value.isPlaying, true); + _ambiguate(WidgetsBinding.instance)! + .handleAppLifecycleStateChanged(AppLifecycleState.paused); + expect(controller.value.isPlaying, shouldPlayInBackground); + _ambiguate(WidgetsBinding.instance)! + .handleAppLifecycleStateChanged(AppLifecycleState.resumed); + expect(controller.value.isPlaying, true); + } + testWidgets('update texture', (WidgetTester tester) async { final FakeController controller = FakeController(); await tester.pumpWidget(VideoPlayer(controller)); @@ -194,6 +207,16 @@ void main() { }); group('initialize', () { + test('started app lifecycle observing', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + await controller.play(); + _verifyPlayStateRespondsToLifecycle(controller, + shouldPlayInBackground: false); + }); + test('asset', () async { final VideoPlayerController controller = VideoPlayerController.asset( 'a.avi', @@ -900,6 +923,46 @@ void main() { }); }); + group('VideoPlayerOptions', () { + test('setMixWithOthers', () async { + final VideoPlayerController controller = VideoPlayerController.file( + File(''), + videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true)); + await controller.initialize(); + expect(controller.videoPlayerOptions!.mixWithOthers, true); + }); + + test('true allowBackgroundPlayback continues playback', () async { + final VideoPlayerController controller = VideoPlayerController.file( + File(''), + videoPlayerOptions: VideoPlayerOptions( + allowBackgroundPlayback: true, + ), + ); + await controller.initialize(); + await controller.play(); + _verifyPlayStateRespondsToLifecycle( + controller, + shouldPlayInBackground: true, + ); + }); + + test('false allowBackgroundPlayback pauses playback', () async { + final VideoPlayerController controller = VideoPlayerController.file( + File(''), + videoPlayerOptions: VideoPlayerOptions( + allowBackgroundPlayback: false, + ), + ); + await controller.initialize(); + await controller.play(); + _verifyPlayStateRespondsToLifecycle( + controller, + shouldPlayInBackground: false, + ); + }); + }); + test('VideoProgressColors', () { const Color playedColor = Color.fromRGBO(0, 0, 255, 0.75); const Color bufferedColor = Color.fromRGBO(0, 255, 0, 0.5); @@ -914,14 +977,6 @@ void main() { expect(colors.bufferedColor, bufferedColor); expect(colors.backgroundColor, backgroundColor); }); - - test('setMixWithOthers', () async { - final VideoPlayerController controller = VideoPlayerController.file( - File(''), - videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true)); - await controller.initialize(); - expect(controller.videoPlayerOptions!.mixWithOthers, true); - }); } class FakeVideoPlayerPlatform extends VideoPlayerPlatform { @@ -1010,3 +1065,9 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { calls.add('setMixWithOthers'); } } + +/// This allows a value of type T or T? to be treated as a value of type T?. +/// +/// We use this so that APIs that have become non-nullable can still be used +/// with `!` and `?` on the stable branch. +T? _ambiguate(T? value) => value;