Skip to content

Commit 43b5424

Browse files
authored
[video_player] synchronize isPlaying state (#3261)
[video_player] synchronize isPlaying state
1 parent 32aa7df commit 43b5424

File tree

29 files changed

+438
-80
lines changed

29 files changed

+438
-80
lines changed

packages/video_player/video_player/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ Anton Borries <mail@antonborri.es>
6565
Alex Li <google@alexv525.com>
6666
Rahul Raj <64.rahulraj@gmail.com>
6767
Koen Van Looveren <vanlooverenkoen.dev@gmail.com>
68+
Márton Matuz <matuzmarci@gmail.com>

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.6.1
2+
3+
* Synchronizes `VideoPlayerValue.isPlaying` with underlying video player.
4+
15
## 2.6.0
26

37
* Adds option to configure HTTP headers via `VideoPlayerController` to fix access to M3U8 files on Android.

packages/video_player/video_player/lib/src/closed_caption_file.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import 'package:flutter/foundation.dart' show objectRuntimeType;
5+
import 'package:flutter/foundation.dart' show immutable, objectRuntimeType;
66

77
import 'sub_rip.dart';
88
import 'web_vtt.dart';
@@ -32,6 +32,7 @@ abstract class ClosedCaptionFile {
3232
///
3333
/// A typical closed captioning file will include several [Caption]s, each
3434
/// linked to a start and end time.
35+
@immutable
3536
class Caption {
3637
/// Creates a new [Caption] object.
3738
///
@@ -74,4 +75,22 @@ class Caption {
7475
'end: $end, '
7576
'text: $text)';
7677
}
78+
79+
@override
80+
bool operator ==(Object other) =>
81+
identical(this, other) ||
82+
other is Caption &&
83+
runtimeType == other.runtimeType &&
84+
number == other.number &&
85+
start == other.start &&
86+
end == other.end &&
87+
text == other.text;
88+
89+
@override
90+
int get hashCode => Object.hash(
91+
number,
92+
start,
93+
end,
94+
text,
95+
);
7796
}

packages/video_player/video_player/lib/video_player.dart

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ VideoPlayerPlatform get _videoPlayerPlatform {
3333

3434
/// The duration, current position, buffering state, error state and settings
3535
/// of a [VideoPlayerController].
36+
@immutable
3637
class VideoPlayerValue {
3738
/// Constructs a video with the given values. Only [duration] is required. The
3839
/// rest will initialize with default values when unset.
39-
VideoPlayerValue({
40+
const VideoPlayerValue({
4041
required this.duration,
4142
this.size = Size.zero,
4243
this.position = Duration.zero,
@@ -54,11 +55,11 @@ class VideoPlayerValue {
5455
});
5556

5657
/// Returns an instance for a video that hasn't been loaded.
57-
VideoPlayerValue.uninitialized()
58+
const VideoPlayerValue.uninitialized()
5859
: this(duration: Duration.zero, isInitialized: false);
5960

6061
/// Returns an instance with the given [errorDescription].
61-
VideoPlayerValue.erroneous(String errorDescription)
62+
const VideoPlayerValue.erroneous(String errorDescription)
6263
: this(
6364
duration: Duration.zero,
6465
isInitialized: false,
@@ -195,6 +196,44 @@ class VideoPlayerValue {
195196
'playbackSpeed: $playbackSpeed, '
196197
'errorDescription: $errorDescription)';
197198
}
199+
200+
@override
201+
bool operator ==(Object other) =>
202+
identical(this, other) ||
203+
other is VideoPlayerValue &&
204+
runtimeType == other.runtimeType &&
205+
duration == other.duration &&
206+
position == other.position &&
207+
caption == other.caption &&
208+
captionOffset == other.captionOffset &&
209+
listEquals(buffered, other.buffered) &&
210+
isPlaying == other.isPlaying &&
211+
isLooping == other.isLooping &&
212+
isBuffering == other.isBuffering &&
213+
volume == other.volume &&
214+
playbackSpeed == other.playbackSpeed &&
215+
errorDescription == other.errorDescription &&
216+
size == other.size &&
217+
rotationCorrection == other.rotationCorrection &&
218+
isInitialized == other.isInitialized;
219+
220+
@override
221+
int get hashCode => Object.hash(
222+
duration,
223+
position,
224+
caption,
225+
captionOffset,
226+
buffered,
227+
isPlaying,
228+
isLooping,
229+
isBuffering,
230+
volume,
231+
playbackSpeed,
232+
errorDescription,
233+
size,
234+
rotationCorrection,
235+
isInitialized,
236+
);
198237
}
199238

200239
/// Controls a platform video player, and provides updates when the state is
@@ -221,7 +260,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
221260
dataSourceType = DataSourceType.asset,
222261
formatHint = null,
223262
httpHeaders = const <String, String>{},
224-
super(VideoPlayerValue(duration: Duration.zero));
263+
super(const VideoPlayerValue(duration: Duration.zero));
225264

226265
/// Constructs a [VideoPlayerController] playing a video from obtained from
227266
/// the network.
@@ -241,7 +280,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
241280
}) : _closedCaptionFileFuture = closedCaptionFile,
242281
dataSourceType = DataSourceType.network,
243282
package = null,
244-
super(VideoPlayerValue(duration: Duration.zero));
283+
super(const VideoPlayerValue(duration: Duration.zero));
245284

246285
/// Constructs a [VideoPlayerController] playing a video from a file.
247286
///
@@ -256,7 +295,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
256295
dataSourceType = DataSourceType.file,
257296
package = null,
258297
formatHint = null,
259-
super(VideoPlayerValue(duration: Duration.zero));
298+
super(const VideoPlayerValue(duration: Duration.zero));
260299

261300
/// Constructs a [VideoPlayerController] playing a video from a contentUri.
262301
///
@@ -272,7 +311,7 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
272311
package = null,
273312
formatHint = null,
274313
httpHeaders = const <String, String>{},
275-
super(VideoPlayerValue(duration: Duration.zero));
314+
super(const VideoPlayerValue(duration: Duration.zero));
276315

277316
/// The URI to the video file. This will be in different formats depending on
278317
/// the [DataSourceType] of the original video.
@@ -372,7 +411,6 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
372411
return;
373412
}
374413

375-
// ignore: missing_enum_constant_in_switch
376414
switch (event.eventType) {
377415
case VideoEventType.initialized:
378416
value = value.copyWith(
@@ -403,6 +441,9 @@ class VideoPlayerController extends ValueNotifier<VideoPlayerValue> {
403441
case VideoEventType.bufferingEnd:
404442
value = value.copyWith(isBuffering: false);
405443
break;
444+
case VideoEventType.isPlayingStateUpdate:
445+
value = value.copyWith(isPlaying: event.isPlaying);
446+
break;
406447
case VideoEventType.unknown:
407448
break;
408449
}

packages/video_player/video_player/pubspec.yaml

Lines changed: 2 additions & 2 deletions
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/packages/tree/main/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.6.0
6+
version: 2.6.1
77

88
environment:
99
sdk: ">=2.17.0 <4.0.0"
@@ -25,7 +25,7 @@ dependencies:
2525
html: ^0.15.0
2626
video_player_android: ^2.3.5
2727
video_player_avfoundation: ^2.2.17
28-
video_player_platform_interface: ">=5.1.1 <7.0.0"
28+
video_player_platform_interface: ">=6.1.0 <7.0.0"
2929
video_player_web: ^2.0.0
3030

3131
dev_dependencies:

packages/video_player/video_player/test/video_player_test.dart

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import 'package:video_player_platform_interface/video_player_platform_interface.
1616

1717
class FakeController extends ValueNotifier<VideoPlayerValue>
1818
implements VideoPlayerController {
19-
FakeController() : super(VideoPlayerValue(duration: Duration.zero));
19+
FakeController() : super(const VideoPlayerValue(duration: Duration.zero));
2020

2121
FakeController.value(super.value);
2222

@@ -164,7 +164,8 @@ void main() {
164164
testWidgets('non-zero rotationCorrection value is used',
165165
(WidgetTester tester) async {
166166
final FakeController controller = FakeController.value(
167-
VideoPlayerValue(duration: Duration.zero, rotationCorrection: 180));
167+
const VideoPlayerValue(
168+
duration: Duration.zero, rotationCorrection: 180));
168169
controller.textureId = 1;
169170
await tester.pumpWidget(VideoPlayer(controller));
170171
final Transform actualRotationCorrection =
@@ -184,7 +185,7 @@ void main() {
184185
testWidgets('no transform when rotationCorrection is zero',
185186
(WidgetTester tester) async {
186187
final FakeController controller =
187-
FakeController.value(VideoPlayerValue(duration: Duration.zero));
188+
FakeController.value(const VideoPlayerValue(duration: Duration.zero));
188189
controller.textureId = 1;
189190
await tester.pumpWidget(VideoPlayer(controller));
190191
expect(find.byType(Transform), findsNothing);
@@ -803,6 +804,30 @@ void main() {
803804
expect(controller.value.position, nonzeroDuration);
804805
});
805806

807+
testWidgets('playback status', (WidgetTester tester) async {
808+
final VideoPlayerController controller = VideoPlayerController.network(
809+
'https://127.0.0.1',
810+
);
811+
await controller.initialize();
812+
expect(controller.value.isPlaying, isFalse);
813+
final StreamController<VideoEvent> fakeVideoEventStream =
814+
fakeVideoPlayerPlatform.streams[controller.textureId]!;
815+
816+
fakeVideoEventStream.add(VideoEvent(
817+
eventType: VideoEventType.isPlayingStateUpdate,
818+
isPlaying: true,
819+
));
820+
await tester.pumpAndSettle();
821+
expect(controller.value.isPlaying, isTrue);
822+
823+
fakeVideoEventStream.add(VideoEvent(
824+
eventType: VideoEventType.isPlayingStateUpdate,
825+
isPlaying: false,
826+
));
827+
await tester.pumpAndSettle();
828+
expect(controller.value.isPlaying, isFalse);
829+
});
830+
806831
testWidgets('buffering status', (WidgetTester tester) async {
807832
final VideoPlayerController controller = VideoPlayerController.network(
808833
'https://127.0.0.1',
@@ -865,7 +890,7 @@ void main() {
865890

866891
group('VideoPlayerValue', () {
867892
test('uninitialized()', () {
868-
final VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized();
893+
const VideoPlayerValue uninitialized = VideoPlayerValue.uninitialized();
869894

870895
expect(uninitialized.duration, equals(Duration.zero));
871896
expect(uninitialized.position, equals(Duration.zero));
@@ -886,7 +911,7 @@ void main() {
886911

887912
test('erroneous()', () {
888913
const String errorMessage = 'foo';
889-
final VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage);
914+
const VideoPlayerValue error = VideoPlayerValue.erroneous(errorMessage);
890915

891916
expect(error.duration, equals(Duration.zero));
892917
expect(error.position, equals(Duration.zero));
@@ -956,26 +981,26 @@ void main() {
956981

957982
group('copyWith()', () {
958983
test('exact copy', () {
959-
final VideoPlayerValue original = VideoPlayerValue.uninitialized();
984+
const VideoPlayerValue original = VideoPlayerValue.uninitialized();
960985
final VideoPlayerValue exactCopy = original.copyWith();
961986

962987
expect(exactCopy.toString(), original.toString());
963988
});
964989
test('errorDescription is not persisted when copy with null', () {
965-
final VideoPlayerValue original = VideoPlayerValue.erroneous('error');
990+
const VideoPlayerValue original = VideoPlayerValue.erroneous('error');
966991
final VideoPlayerValue copy = original.copyWith(errorDescription: null);
967992

968993
expect(copy.errorDescription, null);
969994
});
970995
test('errorDescription is changed when copy with another error', () {
971-
final VideoPlayerValue original = VideoPlayerValue.erroneous('error');
996+
const VideoPlayerValue original = VideoPlayerValue.erroneous('error');
972997
final VideoPlayerValue copy =
973998
original.copyWith(errorDescription: 'new error');
974999

9751000
expect(copy.errorDescription, 'new error');
9761001
});
9771002
test('errorDescription is changed when copy with error', () {
978-
final VideoPlayerValue original = VideoPlayerValue.uninitialized();
1003+
const VideoPlayerValue original = VideoPlayerValue.uninitialized();
9791004
final VideoPlayerValue copy =
9801005
original.copyWith(errorDescription: 'new error');
9811006

@@ -985,45 +1010,45 @@ void main() {
9851010

9861011
group('aspectRatio', () {
9871012
test('640x480 -> 4:3', () {
988-
final VideoPlayerValue value = VideoPlayerValue(
1013+
const VideoPlayerValue value = VideoPlayerValue(
9891014
isInitialized: true,
990-
size: const Size(640, 480),
991-
duration: const Duration(seconds: 1),
1015+
size: Size(640, 480),
1016+
duration: Duration(seconds: 1),
9921017
);
9931018
expect(value.aspectRatio, 4 / 3);
9941019
});
9951020

9961021
test('no size -> 1.0', () {
997-
final VideoPlayerValue value = VideoPlayerValue(
1022+
const VideoPlayerValue value = VideoPlayerValue(
9981023
isInitialized: true,
999-
duration: const Duration(seconds: 1),
1024+
duration: Duration(seconds: 1),
10001025
);
10011026
expect(value.aspectRatio, 1.0);
10021027
});
10031028

10041029
test('height = 0 -> 1.0', () {
1005-
final VideoPlayerValue value = VideoPlayerValue(
1030+
const VideoPlayerValue value = VideoPlayerValue(
10061031
isInitialized: true,
1007-
size: const Size(640, 0),
1008-
duration: const Duration(seconds: 1),
1032+
size: Size(640, 0),
1033+
duration: Duration(seconds: 1),
10091034
);
10101035
expect(value.aspectRatio, 1.0);
10111036
});
10121037

10131038
test('width = 0 -> 1.0', () {
1014-
final VideoPlayerValue value = VideoPlayerValue(
1039+
const VideoPlayerValue value = VideoPlayerValue(
10151040
isInitialized: true,
1016-
size: const Size(0, 480),
1017-
duration: const Duration(seconds: 1),
1041+
size: Size(0, 480),
1042+
duration: Duration(seconds: 1),
10181043
);
10191044
expect(value.aspectRatio, 1.0);
10201045
});
10211046

10221047
test('negative aspect ratio -> 1.0', () {
1023-
final VideoPlayerValue value = VideoPlayerValue(
1048+
const VideoPlayerValue value = VideoPlayerValue(
10241049
isInitialized: true,
1025-
size: const Size(640, -480),
1026-
duration: const Duration(seconds: 1),
1050+
size: Size(640, -480),
1051+
duration: Duration(seconds: 1),
10271052
);
10281053
expect(value.aspectRatio, 1.0);
10291054
});

packages/video_player/video_player_android/AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ Aleksandr Yurkovskiy <sanekyy@gmail.com>
6464
Anton Borries <mail@antonborri.es>
6565
Alex Li <google@alexv525.com>
6666
Rahul Raj <64.rahulraj@gmail.com>
67+
Márton Matuz <matuzmarci@gmail.com>

packages/video_player/video_player_android/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
## NEXT
1+
## 2.4.4
22

3+
* Synchronizes `VideoPlayerValue.isPlaying` with `ExoPlayer`.
34
* Updates minimum Flutter version to 3.3.
45

56
## 2.4.3

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@ public void onPlayerError(final PlaybackException error) {
231231
eventSink.error("VideoError", "Video player had error " + error, null);
232232
}
233233
}
234+
235+
@Override
236+
public void onIsPlayingChanged(boolean isPlaying) {
237+
if (eventSink != null) {
238+
Map<String, Object> event = new HashMap<>();
239+
event.put("event", "isPlayingStateUpdate");
240+
event.put("isPlaying", isPlaying);
241+
eventSink.success(event);
242+
}
243+
}
234244
});
235245
}
236246

0 commit comments

Comments
 (0)