[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;