diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index b082d1b66980..b9f029b31454 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.1.10 + +* Ensure video pauses correctly when it finishes. + ## 2.1.9 * Silenced warnings that may occur during build when using a very diff --git a/packages/video_player/video_player/example/integration_test/video_player_test.dart b/packages/video_player/video_player/example/integration_test/video_player_test.dart index aef3beb1a10e..6821b26e0409 100644 --- a/packages/video_player/video_player/example/integration_test/video_player_test.dart +++ b/packages/video_player/video_player/example/integration_test/video_player_test.dart @@ -143,6 +143,51 @@ void main() { }, ); + testWidgets( + 'stay paused when seeking after video completed', + (WidgetTester tester) async { + await _controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await _controller.setVolume(0); + Duration tenMillisBeforeEnd = + _controller.value.duration - const Duration(milliseconds: 10); + await _controller.seekTo(tenMillisBeforeEnd); + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, _controller.value.duration); + + await _controller.seekTo(tenMillisBeforeEnd); + await tester.pumpAndSettle(_playDuration); + + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, tenMillisBeforeEnd); + }, + ); + + testWidgets( + 'do not exceed duration on play after video completed', + (WidgetTester tester) async { + await _controller.initialize(); + // Mute to allow playing without DOM interaction on Web. + // See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes + await _controller.setVolume(0); + await _controller.seekTo( + _controller.value.duration - const Duration(milliseconds: 10)); + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + expect(_controller.value.isPlaying, false); + expect(_controller.value.position, _controller.value.duration); + + await _controller.play(); + await tester.pumpAndSettle(_playDuration); + + expect(_controller.value.position, + lessThanOrEqualTo(_controller.value.duration)); + }, + ); + testWidgets('test video player view with local asset', (WidgetTester tester) async { Future started() async { diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 1708d49d678b..772409258ac4 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -328,8 +328,11 @@ class VideoPlayerController extends ValueNotifier { _applyPlayPause(); break; case VideoEventType.completed: - value = value.copyWith(isPlaying: false, position: value.duration); - _timer?.cancel(); + // In this case we need to stop _timer, set isPlaying=false, and + // position=value.duration. Instead of setting the values directly, + // we use pause() and seekTo() to ensure the platform stops playing + // and seeks to the last frame of the video. + pause().then((void pauseResult) => seekTo(value.duration)); break; case VideoEventType.bufferingUpdate: value = value.copyWith(buffered: event.buffered); @@ -385,10 +388,15 @@ class VideoPlayerController extends ValueNotifier { /// Starts playing the video. /// + /// If the video is at the end, this method starts playing from the beginning. + /// /// This method returns a future that completes as soon as the "play" command /// has been sent to the platform, not when playback itself is totally /// finished. Future play() async { + if (value.position == value.duration) { + await seekTo(const Duration()); + } value = value.copyWith(isPlaying: true); await _applyPlayPause(); } diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index c24377fc1b8b..b8684075b718 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/master/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.1.9 +version: 2.1.10 environment: sdk: ">=2.12.0 <3.0.0" 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 e17dac7897a6..63498c4e18cb 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -318,6 +318,23 @@ void main() { expect(fakeVideoPlayerPlatform.calls.last, 'setPlaybackSpeed'); }); + test('play restarts from beginning if video is at end', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + const Duration nonzeroDuration = Duration(milliseconds: 100); + controller.value = controller.value.copyWith(duration: nonzeroDuration); + await controller.seekTo(nonzeroDuration); + expect(controller.value.isPlaying, isFalse); + expect(controller.value.position, nonzeroDuration); + + await controller.play(); + + expect(controller.value.isPlaying, isTrue); + expect(controller.value.position, Duration.zero); + }); + test('setLooping', () async { final VideoPlayerController controller = VideoPlayerController.network( 'https://127.0.0.1', @@ -459,6 +476,8 @@ void main() { 'https://127.0.0.1', ); await controller.initialize(); + const Duration nonzeroDuration = Duration(milliseconds: 100); + controller.value = controller.value.copyWith(duration: nonzeroDuration); expect(controller.value.isPlaying, isFalse); await controller.play(); expect(controller.value.isPlaying, isTrue); @@ -470,7 +489,7 @@ void main() { await tester.pumpAndSettle(); expect(controller.value.isPlaying, isFalse); - expect(controller.value.position, controller.value.duration); + expect(controller.value.position, nonzeroDuration); }); testWidgets('buffering status', (WidgetTester tester) async {