Skip to content

Commit 0b88ffb

Browse files
authored
[video_player] Pause video when it completes (#3727)
1 parent e1b4ba4 commit 0b88ffb

File tree

5 files changed

+80
-4
lines changed

5 files changed

+80
-4
lines changed

packages/video_player/video_player/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.1.10
2+
3+
* Ensure video pauses correctly when it finishes.
4+
15
## 2.1.9
26

37
* Silenced warnings that may occur during build when using a very

packages/video_player/video_player/example/integration_test/video_player_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,51 @@ void main() {
143143
},
144144
);
145145

146+
testWidgets(
147+
'stay paused when seeking after video completed',
148+
(WidgetTester tester) async {
149+
await _controller.initialize();
150+
// Mute to allow playing without DOM interaction on Web.
151+
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
152+
await _controller.setVolume(0);
153+
Duration tenMillisBeforeEnd =
154+
_controller.value.duration - const Duration(milliseconds: 10);
155+
await _controller.seekTo(tenMillisBeforeEnd);
156+
await _controller.play();
157+
await tester.pumpAndSettle(_playDuration);
158+
expect(_controller.value.isPlaying, false);
159+
expect(_controller.value.position, _controller.value.duration);
160+
161+
await _controller.seekTo(tenMillisBeforeEnd);
162+
await tester.pumpAndSettle(_playDuration);
163+
164+
expect(_controller.value.isPlaying, false);
165+
expect(_controller.value.position, tenMillisBeforeEnd);
166+
},
167+
);
168+
169+
testWidgets(
170+
'do not exceed duration on play after video completed',
171+
(WidgetTester tester) async {
172+
await _controller.initialize();
173+
// Mute to allow playing without DOM interaction on Web.
174+
// See https://developers.google.com/web/updates/2017/09/autoplay-policy-changes
175+
await _controller.setVolume(0);
176+
await _controller.seekTo(
177+
_controller.value.duration - const Duration(milliseconds: 10));
178+
await _controller.play();
179+
await tester.pumpAndSettle(_playDuration);
180+
expect(_controller.value.isPlaying, false);
181+
expect(_controller.value.position, _controller.value.duration);
182+
183+
await _controller.play();
184+
await tester.pumpAndSettle(_playDuration);
185+
186+
expect(_controller.value.position,
187+
lessThanOrEqualTo(_controller.value.duration));
188+
},
189+
);
190+
146191
testWidgets('test video player view with local asset',
147192
(WidgetTester tester) async {
148193
Future<bool> started() async {

packages/video_player/video_player/lib/video_player.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,11 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
328328
_applyPlayPause();
329329
break;
330330
case VideoEventType.completed:
331-
value = value.copyWith(isPlaying: false, position: value.duration);
332-
_timer?.cancel();
331+
// In this case we need to stop _timer, set isPlaying=false, and
332+
// position=value.duration. Instead of setting the values directly,
333+
// we use pause() and seekTo() to ensure the platform stops playing
334+
// and seeks to the last frame of the video.
335+
pause().then((void pauseResult) => seekTo(value.duration));
333336
break;
334337
case VideoEventType.bufferingUpdate:
335338
value = value.copyWith(buffered: event.buffered);
@@ -385,10 +388,15 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
385388

386389
/// Starts playing the video.
387390
///
391+
/// If the video is at the end, this method starts playing from the beginning.
392+
///
388393
/// This method returns a future that completes as soon as the "play" command
389394
/// has been sent to the platform, not when playback itself is totally
390395
/// finished.
391396
Future<void> play() async {
397+
if (value.position == value.duration) {
398+
await seekTo(const Duration());
399+
}
392400
value = value.copyWith(isPlaying: true);
393401
await _applyPlayPause();
394402
}

packages/video_player/video_player/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Flutter plugin for displaying inline video with other Flutter
33
widgets on Android, iOS, and web.
44
repository: https://github.com/flutter/plugins/tree/master/packages/video_player/video_player
55
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22
6-
version: 2.1.9
6+
version: 2.1.10
77

88
environment:
99
sdk: ">=2.12.0 <3.0.0"

packages/video_player/video_player/test/video_player_test.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,23 @@ void main() {
318318
expect(fakeVideoPlayerPlatform.calls.last, 'setPlaybackSpeed');
319319
});
320320

321+
test('play restarts from beginning if video is at end', () async {
322+
final VideoPlayerController controller = VideoPlayerController.network(
323+
'https://127.0.0.1',
324+
);
325+
await controller.initialize();
326+
const Duration nonzeroDuration = Duration(milliseconds: 100);
327+
controller.value = controller.value.copyWith(duration: nonzeroDuration);
328+
await controller.seekTo(nonzeroDuration);
329+
expect(controller.value.isPlaying, isFalse);
330+
expect(controller.value.position, nonzeroDuration);
331+
332+
await controller.play();
333+
334+
expect(controller.value.isPlaying, isTrue);
335+
expect(controller.value.position, Duration.zero);
336+
});
337+
321338
test('setLooping', () async {
322339
final VideoPlayerController controller = VideoPlayerController.network(
323340
'https://127.0.0.1',
@@ -459,6 +476,8 @@ void main() {
459476
'https://127.0.0.1',
460477
);
461478
await controller.initialize();
479+
const Duration nonzeroDuration = Duration(milliseconds: 100);
480+
controller.value = controller.value.copyWith(duration: nonzeroDuration);
462481
expect(controller.value.isPlaying, isFalse);
463482
await controller.play();
464483
expect(controller.value.isPlaying, isTrue);
@@ -470,7 +489,7 @@ void main() {
470489
await tester.pumpAndSettle();
471490

472491
expect(controller.value.isPlaying, isFalse);
473-
expect(controller.value.position, controller.value.duration);
492+
expect(controller.value.position, nonzeroDuration);
474493
});
475494

476495
testWidgets('buffering status', (WidgetTester tester) async {

0 commit comments

Comments
 (0)