[video_player] Eliminate platform channel from mock platform (#4588)
diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md
index 9d4e36d..6f2a2b7 100644
--- a/packages/video_player/video_player/CHANGELOG.md
+++ b/packages/video_player/video_player/CHANGELOG.md
@@ -1,5 +1,8 @@
-## NEXT
+## 2.2.8
+* Changes the way the `VideoPlayerPlatform` instance is cached in the
+ controller, so that it's no longer impossible to change after the first use.
+* Updates unit tests to be self-contained.
* Fixes integration tests.
* Updates Android compileSdkVersion to 31.
* Fixes a flaky integration test.
diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart
index ff727f1..523a1ad 100644
--- a/packages/video_player/video_player/lib/video_player.dart
+++ b/packages/video_player/video_player/lib/video_player.dart
@@ -17,10 +17,18 @@
import 'src/closed_caption_file.dart';
export 'src/closed_caption_file.dart';
-final VideoPlayerPlatform _videoPlayerPlatform = VideoPlayerPlatform.instance
- // This will clear all open videos on the platform when a full restart is
- // performed.
- ..init();
+VideoPlayerPlatform? _lastVideoPlayerPlatform;
+
+VideoPlayerPlatform get _videoPlayerPlatform {
+ VideoPlayerPlatform currentInstance = VideoPlayerPlatform.instance;
+ if (_lastVideoPlayerPlatform != currentInstance) {
+ // This will clear all open videos on the platform when a full restart is
+ // performed.
+ currentInstance.init();
+ _lastVideoPlayerPlatform = currentInstance;
+ }
+ return currentInstance;
+}
/// The duration, current position, buffering state, error state and settings
/// of a [VideoPlayerController].
diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml
index f0d5951..f8c0917 100644
--- a/packages/video_player/video_player/pubspec.yaml
+++ b/packages/video_player/video_player/pubspec.yaml
@@ -3,7 +3,7 @@
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.2.7
+version: 2.2.8
environment:
sdk: ">=2.14.0 <3.0.0"
@@ -25,12 +25,6 @@
sdk: flutter
meta: ^1.3.0
video_player_platform_interface: ^4.2.0
- # The design on https://flutter.dev/go/federated-plugins was to leave
- # this constraint as "any". We cannot do it right now as it fails pub publish
- # validation, so we set a ^ constraint. The exact value doesn't matter since
- # the constraints on the interface pins it.
- # TODO(amirh): Revisit this (either update this part in the design or the pub tool).
- # https://github.com/flutter/flutter/issues/46264
video_player_web: ^2.0.0
html: ^0.15.0
diff --git a/packages/video_player/video_player/test/video_player_initialization_test.dart b/packages/video_player/video_player/test/video_player_initialization_test.dart
index 13bfd7b..1870934 100644
--- a/packages/video_player/video_player/test/video_player_initialization_test.dart
+++ b/packages/video_player/video_player/test/video_player_initialization_test.dart
@@ -4,6 +4,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:video_player/video_player.dart';
+import 'package:video_player_platform_interface/video_player_platform_interface.dart';
import 'video_player_test.dart' show FakeVideoPlayerPlatform;
@@ -13,6 +14,7 @@
test('plugin initialized', () async {
TestWidgetsFlutterBinding.ensureInitialized();
FakeVideoPlayerPlatform fakeVideoPlayerPlatform = FakeVideoPlayerPlatform();
+ VideoPlayerPlatform.instance = fakeVideoPlayerPlatform;
final VideoPlayerController controller = VideoPlayerController.network(
'https://127.0.0.1',
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 08fd9dc..959f98f 100644
--- a/packages/video_player/video_player/test/video_player_test.dart
+++ b/packages/video_player/video_player/test/video_player_test.dart
@@ -11,8 +11,6 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:video_player/video_player.dart';
-import 'package:video_player_platform_interface/messages.dart';
-import 'package:video_player_platform_interface/test.dart';
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
class FakeController extends ValueNotifier<VideoPlayerValue>
@@ -187,6 +185,7 @@
setUp(() {
fakeVideoPlayerPlatform = FakeVideoPlayerPlatform();
+ VideoPlayerPlatform.instance = fakeVideoPlayerPlatform;
});
group('initialize', () {
@@ -196,10 +195,8 @@
);
await controller.initialize();
- expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].asset, 'a.avi');
- expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].packageName,
- null);
+ expect(fakeVideoPlayerPlatform.dataSources[0].asset, 'a.avi');
+ expect(fakeVideoPlayerPlatform.dataSources[0].package, null);
});
test('network', () async {
@@ -209,15 +206,15 @@
await controller.initialize();
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri,
+ fakeVideoPlayerPlatform.dataSources[0].uri,
'https://127.0.0.1',
);
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint,
+ fakeVideoPlayerPlatform.dataSources[0].formatHint,
null,
);
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].httpHeaders,
+ fakeVideoPlayerPlatform.dataSources[0].httpHeaders,
{},
);
});
@@ -230,16 +227,16 @@
await controller.initialize();
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri,
+ fakeVideoPlayerPlatform.dataSources[0].uri,
'https://127.0.0.1',
);
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint,
- 'dash',
+ fakeVideoPlayerPlatform.dataSources[0].formatHint,
+ VideoFormat.dash,
);
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].httpHeaders,
- {},
+ fakeVideoPlayerPlatform.dataSources[0].httpHeaders,
+ <String, String>{},
);
});
@@ -251,15 +248,15 @@
await controller.initialize();
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri,
+ fakeVideoPlayerPlatform.dataSources[0].uri,
'https://127.0.0.1',
);
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].formatHint,
+ fakeVideoPlayerPlatform.dataSources[0].formatHint,
null,
);
expect(
- fakeVideoPlayerPlatform.dataSourceDescriptions[0].httpHeaders,
+ fakeVideoPlayerPlatform.dataSources[0].httpHeaders,
{'Authorization': 'Bearer token'},
);
});
@@ -268,15 +265,12 @@
final VideoPlayerController controller = VideoPlayerController.network(
'http://testing.com/invalid_url',
);
- try {
- late dynamic error;
- fakeVideoPlayerPlatform.forceInitError = true;
- await controller.initialize().catchError((dynamic e) => error = e);
- final PlatformException platformEx = error;
- expect(platformEx.code, equals('VideoError'));
- } finally {
- fakeVideoPlayerPlatform.forceInitError = false;
- }
+
+ late dynamic error;
+ fakeVideoPlayerPlatform.forceInitError = true;
+ await controller.initialize().catchError((dynamic e) => error = e);
+ final PlatformException platformEx = error;
+ expect(platformEx.code, equals('VideoError'));
});
test('file', () async {
@@ -284,8 +278,7 @@
VideoPlayerController.file(File('a.avi'));
await controller.initialize();
- expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri,
- 'file://a.avi');
+ expect(fakeVideoPlayerPlatform.dataSources[0].uri, 'file://a.avi');
});
});
@@ -294,8 +287,7 @@
VideoPlayerController.contentUri(Uri.parse('content://video'));
await controller.initialize();
- expect(fakeVideoPlayerPlatform.dataSourceDescriptions[0].uri,
- 'content://video');
+ expect(fakeVideoPlayerPlatform.dataSources[0].uri, 'content://video');
});
test('dispose', () async {
@@ -571,11 +563,11 @@
expect(controller.value.isPlaying, isFalse);
await controller.play();
expect(controller.value.isPlaying, isTrue);
- final FakeVideoEventStream fakeVideoEventStream =
+ final StreamController<VideoEvent> fakeVideoEventStream =
fakeVideoPlayerPlatform.streams[controller.textureId]!;
- fakeVideoEventStream.eventsChannel
- .sendEvent(<String, dynamic>{'event': 'completed'});
+ fakeVideoEventStream
+ .add(VideoEvent(eventType: VideoEventType.completed));
await tester.pumpAndSettle();
expect(controller.value.isPlaying, isFalse);
@@ -589,30 +581,30 @@
await controller.initialize();
expect(controller.value.isBuffering, false);
expect(controller.value.buffered, isEmpty);
- final FakeVideoEventStream fakeVideoEventStream =
+ final StreamController<VideoEvent> fakeVideoEventStream =
fakeVideoPlayerPlatform.streams[controller.textureId]!;
- fakeVideoEventStream.eventsChannel
- .sendEvent(<String, dynamic>{'event': 'bufferingStart'});
+ fakeVideoEventStream
+ .add(VideoEvent(eventType: VideoEventType.bufferingStart));
await tester.pumpAndSettle();
expect(controller.value.isBuffering, isTrue);
const Duration bufferStart = Duration(seconds: 0);
const Duration bufferEnd = Duration(milliseconds: 500);
- fakeVideoEventStream.eventsChannel.sendEvent(<String, dynamic>{
- 'event': 'bufferingUpdate',
- 'values': <List<int>>[
- <int>[bufferStart.inMilliseconds, bufferEnd.inMilliseconds]
- ],
- });
+ fakeVideoEventStream
+ ..add(VideoEvent(
+ eventType: VideoEventType.bufferingUpdate,
+ buffered: <DurationRange>[
+ DurationRange(bufferStart, bufferEnd),
+ ]));
await tester.pumpAndSettle();
expect(controller.value.isBuffering, isTrue);
expect(controller.value.buffered.length, 1);
expect(controller.value.buffered[0].toString(),
DurationRange(bufferStart, bufferEnd).toString());
- fakeVideoEventStream.eventsChannel
- .sendEvent(<String, dynamic>{'event': 'bufferingEnd'});
+ fakeVideoEventStream
+ .add(VideoEvent(eventType: VideoEventType.bufferingEnd));
await tester.pumpAndSettle();
expect(controller.value.isBuffering, isFalse);
});
@@ -807,155 +799,88 @@
});
}
-class FakeVideoPlayerPlatform extends TestHostVideoPlayerApi {
- FakeVideoPlayerPlatform() {
- TestHostVideoPlayerApi.setup(this);
- }
-
+class FakeVideoPlayerPlatform extends VideoPlayerPlatform {
Completer<bool> initialized = Completer<bool>();
List<String> calls = <String>[];
- List<CreateMessage> dataSourceDescriptions = <CreateMessage>[];
- final Map<int, FakeVideoEventStream> streams = <int, FakeVideoEventStream>{};
+ List<DataSource> dataSources = <DataSource>[];
+ final Map<int, StreamController<VideoEvent>> streams =
+ <int, StreamController<VideoEvent>>{};
bool forceInitError = false;
int nextTextureId = 0;
final Map<int, Duration> _positions = <int, Duration>{};
@override
- TextureMessage create(CreateMessage arg) {
+ Future<int?> create(DataSource dataSource) async {
calls.add('create');
- streams[nextTextureId] = FakeVideoEventStream(
- nextTextureId, 100, 100, const Duration(seconds: 1), forceInitError);
- TextureMessage result = TextureMessage();
- result.textureId = nextTextureId++;
- dataSourceDescriptions.add(arg);
- return result;
+ StreamController<VideoEvent> stream = StreamController<VideoEvent>();
+ streams[nextTextureId] = stream;
+ if (forceInitError) {
+ stream.addError(PlatformException(
+ code: 'VideoError', message: 'Video player had error XYZ'));
+ } else {
+ stream.add(VideoEvent(
+ eventType: VideoEventType.initialized,
+ size: Size(100, 100),
+ duration: Duration(seconds: 1)));
+ }
+ dataSources.add(dataSource);
+ return nextTextureId++;
}
@override
- void dispose(TextureMessage arg) {
+ Future<void> dispose(int textureId) async {
calls.add('dispose');
}
@override
- void initialize() {
+ Future<void> init() async {
calls.add('init');
initialized.complete(true);
}
+ Stream<VideoEvent> videoEventsFor(int textureId) {
+ return streams[textureId]!.stream;
+ }
+
@override
- void pause(TextureMessage arg) {
+ Future<void> pause(int textureId) async {
calls.add('pause');
}
@override
- void play(TextureMessage arg) {
+ Future<void> play(int textureId) async {
calls.add('play');
}
@override
- PositionMessage position(TextureMessage arg) {
+ Future<Duration> getPosition(int textureId) async {
calls.add('position');
- final Duration position =
- _positions[arg.textureId] ?? const Duration(seconds: 0);
- return PositionMessage()..position = position.inMilliseconds;
+ return _positions[textureId] ?? const Duration(seconds: 0);
}
@override
- void seekTo(PositionMessage arg) {
+ Future<void> seekTo(int textureId, Duration position) async {
calls.add('seekTo');
- _positions[arg.textureId!] = Duration(milliseconds: arg.position!);
+ _positions[textureId] = position;
}
@override
- void setLooping(LoopingMessage arg) {
+ Future<void> setLooping(int textureId, bool looping) async {
calls.add('setLooping');
}
@override
- void setVolume(VolumeMessage arg) {
+ Future<void> setVolume(int textureId, double volume) async {
calls.add('setVolume');
}
@override
- void setPlaybackSpeed(PlaybackSpeedMessage arg) {
+ Future<void> setPlaybackSpeed(int textureId, double speed) async {
calls.add('setPlaybackSpeed');
}
@override
- void setMixWithOthers(MixWithOthersMessage arg) {
+ Future<void> setMixWithOthers(bool mixWithOthers) async {
calls.add('setMixWithOthers');
}
}
-
-class FakeVideoEventStream {
- FakeVideoEventStream(this.textureId, this.width, this.height, this.duration,
- this.initWithError) {
- eventsChannel = FakeEventsChannel(
- 'flutter.io/videoPlayer/videoEvents$textureId', onListen);
- }
-
- int textureId;
- int width;
- int height;
- Duration duration;
- bool initWithError;
- late FakeEventsChannel eventsChannel;
-
- void onListen() {
- if (!initWithError) {
- eventsChannel.sendEvent(<String, dynamic>{
- 'event': 'initialized',
- 'duration': duration.inMilliseconds,
- 'width': width,
- 'height': height,
- });
- } else {
- eventsChannel.sendError('VideoError', 'Video player had error XYZ');
- }
- }
-}
-
-class FakeEventsChannel {
- FakeEventsChannel(String name, this.onListen) {
- eventsMethodChannel = MethodChannel(name);
- eventsMethodChannel.setMockMethodCallHandler(onMethodCall);
- }
-
- late MethodChannel eventsMethodChannel;
- VoidCallback onListen;
-
- Future<dynamic> onMethodCall(MethodCall call) {
- switch (call.method) {
- case 'listen':
- onListen();
- break;
- }
- return Future<void>.sync(() {});
- }
-
- void sendEvent(dynamic event) {
- _sendMessage(const StandardMethodCodec().encodeSuccessEnvelope(event));
- }
-
- void sendError(String code, [String? message, dynamic details]) {
- _sendMessage(const StandardMethodCodec().encodeErrorEnvelope(
- code: code,
- message: message,
- details: details,
- ));
- }
-
- void _sendMessage(ByteData data) {
- _ambiguate(ServicesBinding.instance)!
- .defaultBinaryMessenger
- .handlePlatformMessage(
- eventsMethodChannel.name, data, (ByteData? data) {});
- }
-}
-
-/// 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.
-// TODO(ianh): Remove this once we roll stable in late 2021.
-T? _ambiguate<T>(T? value) => value;