diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index 29585dd1120..466a0a6f37b 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.10.0 +* Adds support for platform views as an optional way of displaying a video on Android and iOS. * Updates README to indicate that Andoid SDK <21 is no longer supported. * Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md index dcf1a6018bf..da86c78668b 100644 --- a/packages/video_player/video_player/README.md +++ b/packages/video_player/video_player/README.md @@ -140,3 +140,10 @@ and so on. To learn about playback speed limitations, see the [`setPlaybackSpeed` method documentation](https://pub.dev/documentation/video_player/latest/video_player/VideoPlayerController/setPlaybackSpeed.html). Furthermore, see the example app for an example playback speed implementation. + +### Video view type + +You can set the video view type of your controller (instance of `VideoPlayerController`) during its creation by passing the `videoViewType` argument. +If set to `VideoViewType.platformView`, platform views will be used instead of texture view on supported platforms. + +The relative performance of the different view types may vary by platform, and on some platforms the use of platform views may have correctness issues in certain circumstances due to limitations of Flutter's platform view system. diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart index 0ce77d603b5..3f76f8c32e0 100644 --- a/packages/video_player/video_player/example/lib/main.dart +++ b/packages/video_player/video_player/example/lib/main.dart @@ -56,9 +56,18 @@ class _App extends StatelessWidget { ), body: TabBarView( children: [ - _BumbleBeeRemoteVideo(), - _ButterFlyAssetVideo(), - _ButterFlyAssetVideoInList(), + _ViewTypeTabBar( + builder: (VideoViewType viewType) => + _BumbleBeeRemoteVideo(viewType), + ), + _ViewTypeTabBar( + builder: (VideoViewType viewType) => + _ButterFlyAssetVideo(viewType), + ), + _ViewTypeTabBar( + builder: (VideoViewType viewType) => + _ButterFlyAssetVideoInList(viewType), + ), ], ), ), @@ -66,7 +75,70 @@ class _App extends StatelessWidget { } } +class _ViewTypeTabBar extends StatefulWidget { + const _ViewTypeTabBar({ + required this.builder, + }); + + final Widget Function(VideoViewType) builder; + + @override + State<_ViewTypeTabBar> createState() => _ViewTypeTabBarState(); +} + +class _ViewTypeTabBarState extends State<_ViewTypeTabBar> + with SingleTickerProviderStateMixin { + late final TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + TabBar( + controller: _tabController, + isScrollable: true, + tabs: const [ + Tab( + icon: Icon(Icons.texture), + text: 'Texture view', + ), + Tab( + icon: Icon(Icons.construction), + text: 'Platform view', + ), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + widget.builder(VideoViewType.textureView), + widget.builder(VideoViewType.platformView), + ], + ), + ), + ], + ); + } +} + class _ButterFlyAssetVideoInList extends StatelessWidget { + const _ButterFlyAssetVideoInList(this.viewType); + + final VideoViewType viewType; + @override Widget build(BuildContext context) { return ListView( @@ -90,7 +162,7 @@ class _ButterFlyAssetVideoInList extends StatelessWidget { alignment: FractionalOffset.bottomRight + const FractionalOffset(-0.1, -0.1), children: [ - _ButterFlyAssetVideo(), + _ButterFlyAssetVideo(viewType), Image.asset('assets/flutter-mark-square-64.png'), ]), ], @@ -150,6 +222,10 @@ class _ExampleCard extends StatelessWidget { } class _ButterFlyAssetVideo extends StatefulWidget { + const _ButterFlyAssetVideo(this.viewType); + + final VideoViewType viewType; + @override _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); } @@ -160,7 +236,10 @@ class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { @override void initState() { super.initState(); - _controller = VideoPlayerController.asset('assets/Butterfly-209.mp4'); + _controller = VideoPlayerController.asset( + 'assets/Butterfly-209.mp4', + viewType: widget.viewType, + ); _controller.addListener(() { setState(() {}); @@ -206,6 +285,10 @@ class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { } class _BumbleBeeRemoteVideo extends StatefulWidget { + const _BumbleBeeRemoteVideo(this.viewType); + + final VideoViewType viewType; + @override _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); } @@ -228,6 +311,7 @@ class _BumbleBeeRemoteVideoState extends State<_BumbleBeeRemoteVideo> { 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'), closedCaptionFile: _loadCaptions(), videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true), + viewType: widget.viewType, ); _controller.addListener(() { diff --git a/packages/video_player/video_player/lib/video_player.dart b/packages/video_player/video_player/lib/video_player.dart index 12b30c2a146..17c5bcb2995 100644 --- a/packages/video_player/video_player/lib/video_player.dart +++ b/packages/video_player/video_player/lib/video_player.dart @@ -19,7 +19,8 @@ export 'package:video_player_platform_interface/video_player_platform_interface. VideoFormat, VideoPlayerOptions, VideoPlayerWebOptions, - VideoPlayerWebOptionsControls; + VideoPlayerWebOptionsControls, + VideoViewType; export 'src/closed_caption_file.dart'; @@ -269,11 +270,17 @@ class VideoPlayerController extends ValueNotifier { /// The name of the asset is given by the [dataSource] argument and must not be /// null. The [package] argument must be non-null when the asset comes from a /// package and null otherwise. - VideoPlayerController.asset(this.dataSource, - {this.package, - Future? closedCaptionFile, - this.videoPlayerOptions}) - : _closedCaptionFileFuture = closedCaptionFile, + /// + /// The [viewType] option allows the caller to request a specific display mode + /// for the video. Platforms that do not support the request view type will + /// ignore this parameter. + VideoPlayerController.asset( + this.dataSource, { + this.package, + Future? closedCaptionFile, + this.videoPlayerOptions, + this.viewType = VideoViewType.textureView, + }) : _closedCaptionFileFuture = closedCaptionFile, dataSourceType = DataSourceType.asset, formatHint = null, httpHeaders = const {}, @@ -286,6 +293,10 @@ class VideoPlayerController extends ValueNotifier { /// **Android only**: The [formatHint] option allows the caller to override /// the video format detection code. /// + /// The [viewType] option allows the caller to request a specific display mode + /// for the video. Platforms that do not support the request view type will + /// ignore this parameter. + /// /// [httpHeaders] option allows to specify HTTP headers /// for the request to the [dataSource]. @Deprecated('Use VideoPlayerController.networkUrl instead') @@ -295,6 +306,7 @@ class VideoPlayerController extends ValueNotifier { Future? closedCaptionFile, this.videoPlayerOptions, this.httpHeaders = const {}, + this.viewType = VideoViewType.textureView, }) : _closedCaptionFileFuture = closedCaptionFile, dataSourceType = DataSourceType.network, package = null, @@ -315,6 +327,7 @@ class VideoPlayerController extends ValueNotifier { Future? closedCaptionFile, this.videoPlayerOptions, this.httpHeaders = const {}, + this.viewType = VideoViewType.textureView, }) : _closedCaptionFileFuture = closedCaptionFile, dataSource = url.toString(), dataSourceType = DataSourceType.network, @@ -325,11 +338,13 @@ class VideoPlayerController extends ValueNotifier { /// /// This will load the file from a file:// URI constructed from [file]'s path. /// [httpHeaders] option allows to specify HTTP headers, mainly used for hls files like (m3u8). - VideoPlayerController.file(File file, - {Future? closedCaptionFile, - this.videoPlayerOptions, - this.httpHeaders = const {}}) - : _closedCaptionFileFuture = closedCaptionFile, + VideoPlayerController.file( + File file, { + Future? closedCaptionFile, + this.videoPlayerOptions, + this.httpHeaders = const {}, + this.viewType = VideoViewType.textureView, + }) : _closedCaptionFileFuture = closedCaptionFile, dataSource = Uri.file(file.absolute.path).toString(), dataSourceType = DataSourceType.file, package = null, @@ -340,9 +355,12 @@ class VideoPlayerController extends ValueNotifier { /// /// This will load the video from the input content-URI. /// This is supported on Android only. - VideoPlayerController.contentUri(Uri contentUri, - {Future? closedCaptionFile, this.videoPlayerOptions}) - : assert(defaultTargetPlatform == TargetPlatform.android, + VideoPlayerController.contentUri( + Uri contentUri, { + Future? closedCaptionFile, + this.videoPlayerOptions, + this.viewType = VideoViewType.textureView, + }) : assert(defaultTargetPlatform == TargetPlatform.android, 'VideoPlayerController.contentUri is only supported on Android.'), _closedCaptionFileFuture = closedCaptionFile, dataSource = contentUri.toString(), @@ -375,6 +393,11 @@ class VideoPlayerController extends ValueNotifier { /// Only set for [asset] videos. The package that the asset was loaded from. final String? package; + /// The requested display mode for the video. + /// + /// Platforms that do not support the request view type will ignore this. + final VideoViewType viewType; + Future? _closedCaptionFileFuture; ClosedCaptionFile? _closedCaptionFile; Timer? _timer; @@ -383,15 +406,15 @@ class VideoPlayerController extends ValueNotifier { StreamSubscription? _eventSubscription; _VideoAppLifeCycleObserver? _lifeCycleObserver; - /// The id of a texture that hasn't been initialized. + /// The id of a player that hasn't been initialized. @visibleForTesting - static const int kUninitializedTextureId = -1; - int _textureId = kUninitializedTextureId; + static const int kUninitializedPlayerId = -1; + int _playerId = kUninitializedPlayerId; /// This is just exposed for testing. It shouldn't be used by anyone depending /// on the plugin. @visibleForTesting - int get textureId => _textureId; + int get playerId => _playerId; /// Attempts to open the given [dataSource] and load metadata about the video. Future initialize() async { @@ -431,20 +454,26 @@ class VideoPlayerController extends ValueNotifier { ); } + final VideoCreationOptions creationOptions = VideoCreationOptions( + dataSource: dataSourceDescription, + viewType: viewType, + ); + if (videoPlayerOptions?.mixWithOthers != null) { await _videoPlayerPlatform .setMixWithOthers(videoPlayerOptions!.mixWithOthers); } - _textureId = (await _videoPlayerPlatform.create(dataSourceDescription)) ?? - kUninitializedTextureId; + _playerId = + (await _videoPlayerPlatform.createWithOptions(creationOptions)) ?? + kUninitializedPlayerId; _creatingCompleter!.complete(null); final Completer initializingCompleter = Completer(); // Apply the web-specific options if (kIsWeb && videoPlayerOptions?.webOptions != null) { await _videoPlayerPlatform.setWebOptions( - _textureId, + _playerId, videoPlayerOptions!.webOptions!, ); } @@ -517,7 +546,7 @@ class VideoPlayerController extends ValueNotifier { } _eventSubscription = _videoPlayerPlatform - .videoEventsFor(_textureId) + .videoEventsFor(_playerId) .listen(eventListener, onError: errorListener); return initializingCompleter.future; } @@ -534,7 +563,7 @@ class VideoPlayerController extends ValueNotifier { _isDisposed = true; _timer?.cancel(); await _eventSubscription?.cancel(); - await _videoPlayerPlatform.dispose(_textureId); + await _videoPlayerPlatform.dispose(_playerId); } _lifeCycleObserver?.dispose(); } @@ -574,7 +603,7 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposedOrNotInitialized) { return; } - await _videoPlayerPlatform.setLooping(_textureId, value.isLooping); + await _videoPlayerPlatform.setLooping(_playerId, value.isLooping); } Future _applyPlayPause() async { @@ -582,7 +611,7 @@ class VideoPlayerController extends ValueNotifier { return; } if (value.isPlaying) { - await _videoPlayerPlatform.play(_textureId); + await _videoPlayerPlatform.play(_playerId); _timer?.cancel(); _timer = Timer.periodic( @@ -605,7 +634,7 @@ class VideoPlayerController extends ValueNotifier { await _applyPlaybackSpeed(); } else { _timer?.cancel(); - await _videoPlayerPlatform.pause(_textureId); + await _videoPlayerPlatform.pause(_playerId); } } @@ -613,7 +642,7 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposedOrNotInitialized) { return; } - await _videoPlayerPlatform.setVolume(_textureId, value.volume); + await _videoPlayerPlatform.setVolume(_playerId, value.volume); } Future _applyPlaybackSpeed() async { @@ -629,7 +658,7 @@ class VideoPlayerController extends ValueNotifier { } await _videoPlayerPlatform.setPlaybackSpeed( - _textureId, + _playerId, value.playbackSpeed, ); } @@ -639,7 +668,7 @@ class VideoPlayerController extends ValueNotifier { if (_isDisposed) { return null; } - return _videoPlayerPlatform.getPosition(_textureId); + return _videoPlayerPlatform.getPosition(_playerId); } /// Sets the video's current timestamp to be at [moment]. The next @@ -656,7 +685,7 @@ class VideoPlayerController extends ValueNotifier { } else if (position < Duration.zero) { position = Duration.zero; } - await _videoPlayerPlatform.seekTo(_textureId, position); + await _videoPlayerPlatform.seekTo(_playerId, position); _updatePosition(position); } @@ -833,10 +862,10 @@ class VideoPlayer extends StatefulWidget { class _VideoPlayerState extends State { _VideoPlayerState() { _listener = () { - final int newTextureId = widget.controller.textureId; - if (newTextureId != _textureId) { + final int newPlayerId = widget.controller.playerId; + if (newPlayerId != _playerId) { setState(() { - _textureId = newTextureId; + _playerId = newPlayerId; }); } }; @@ -844,13 +873,13 @@ class _VideoPlayerState extends State { late VoidCallback _listener; - late int _textureId; + late int _playerId; @override void initState() { super.initState(); - _textureId = widget.controller.textureId; - // Need to listen for initialization events since the actual texture ID + _playerId = widget.controller.playerId; + // Need to listen for initialization events since the actual widget ID // becomes available after asynchronous initialization finishes. widget.controller.addListener(_listener); } @@ -859,7 +888,7 @@ class _VideoPlayerState extends State { void didUpdateWidget(VideoPlayer oldWidget) { super.didUpdateWidget(oldWidget); oldWidget.controller.removeListener(_listener); - _textureId = widget.controller.textureId; + _playerId = widget.controller.playerId; widget.controller.addListener(_listener); } @@ -871,11 +900,13 @@ class _VideoPlayerState extends State { @override Widget build(BuildContext context) { - return _textureId == VideoPlayerController.kUninitializedTextureId + return _playerId == VideoPlayerController.kUninitializedPlayerId ? Container() : _VideoPlayerWithRotation( rotation: widget.controller.value.rotationCorrection, - child: _videoPlayerPlatform.buildView(_textureId), + child: _videoPlayerPlatform.buildViewWithOptions( + VideoViewOptions(playerId: _playerId), + ), ); } } diff --git a/packages/video_player/video_player/pubspec.yaml b/packages/video_player/video_player/pubspec.yaml index 807e4a144ae..7ae423d6bdb 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -1,9 +1,9 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter - widgets on Android, iOS, and web. + widgets on Android, iOS, macOS and web. repository: https://github.com/flutter/packages/tree/main/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.9.5 +version: 2.10.0 environment: sdk: ^3.6.0 @@ -25,9 +25,9 @@ dependencies: flutter: sdk: flutter html: ^0.15.0 - video_player_android: ^2.3.5 - video_player_avfoundation: ^2.5.6 - video_player_platform_interface: ^6.2.0 + video_player_android: ^2.8.1 + video_player_avfoundation: ^2.7.0 + video_player_platform_interface: ^6.3.0 video_player_web: ^2.1.0 dev_dependencies: 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 5ecc7e4d69b..5ebb4bb9b52 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 @@ -52,9 +52,34 @@ void main() { reason: 'setWebOptions must be called exactly once.', ); expect( - fakeVideoPlayerPlatform.webOptions[controller.textureId], + fakeVideoPlayerPlatform.webOptions[controller.playerId], expected, reason: 'web options must be passed to the platform', ); }, skip: !kIsWeb); + + test('video view type is applied', () async { + const VideoViewType expected = VideoViewType.platformView; + + final VideoPlayerController controller = VideoPlayerController.networkUrl( + Uri.parse('https://127.0.0.1'), + viewType: expected, + ); + await controller.initialize(); + + expect( + () { + fakeVideoPlayerPlatform.calls.singleWhere( + (String call) => call == 'createWithOptions', + ); + }, + returnsNormally, + reason: 'createWithOptions must be called exactly once.', + ); + expect( + fakeVideoPlayerPlatform.viewTypes[controller.playerId], + expected, + reason: 'view type must be passed to the platform', + ); + }); } 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 a7b0888faa6..38acf159dd0 100644 --- a/packages/video_player/video_player/test/video_player_test.dart +++ b/packages/video_player/video_player/test/video_player_test.dart @@ -12,9 +12,6 @@ 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'; -// TODO(FirentisTFW): Remove the ignore and rename parameters when adding support for platform views. -// ignore_for_file: avoid_renaming_method_parameters - const String _localhost = 'https://127.0.0.1'; final Uri _localhostUri = Uri.parse(_localhost); @@ -30,7 +27,7 @@ class FakeController extends ValueNotifier } @override - int textureId = VideoPlayerController.kUninitializedTextureId; + int playerId = VideoPlayerController.kUninitializedPlayerId; @override String get dataSource => ''; @@ -47,6 +44,9 @@ class FakeController extends ValueNotifier @override Future get position async => value.position; + @override + VideoViewType get viewType => VideoViewType.textureView; + @override Future seekTo(Duration moment) async {} @@ -136,7 +136,7 @@ void main() { await tester.pumpWidget(VideoPlayer(controller)); expect(find.byType(Texture), findsNothing); - controller.textureId = 123; + controller.playerId = 123; controller.value = controller.value.copyWith( duration: const Duration(milliseconds: 100), isInitialized: true, @@ -149,7 +149,7 @@ void main() { testWidgets('update controller', (WidgetTester tester) async { final FakeController controller1 = FakeController(); addTearDown(controller1.dispose); - controller1.textureId = 101; + controller1.playerId = 101; await tester.pumpWidget(VideoPlayer(controller1)); expect( find.byWidgetPredicate( @@ -159,7 +159,7 @@ void main() { final FakeController controller2 = FakeController(); addTearDown(controller2.dispose); - controller2.textureId = 102; + controller2.playerId = 102; await tester.pumpWidget(VideoPlayer(controller2)); expect( find.byWidgetPredicate( @@ -174,7 +174,7 @@ void main() { const VideoPlayerValue( duration: Duration.zero, rotationCorrection: 180)); addTearDown(controller.dispose); - controller.textureId = 1; + controller.playerId = 1; await tester.pumpWidget(VideoPlayer(controller)); final RotatedBox actualRotationCorrection = find.byType(RotatedBox).evaluate().single.widget as RotatedBox; @@ -187,7 +187,7 @@ void main() { final FakeController controller = FakeController.value(const VideoPlayerValue(duration: Duration.zero)); addTearDown(controller.dispose); - controller.textureId = 1; + controller.playerId = 1; await tester.pumpWidget(VideoPlayer(controller)); expect(find.byType(RotatedBox), findsNothing); }); @@ -307,6 +307,7 @@ void main() { ); }); }); + group('initialize', () { test('started app lifecycle observing', () async { final VideoPlayerController controller = @@ -449,6 +450,7 @@ void main() { {'Authorization': 'Bearer token'}, ); }, skip: kIsWeb /* Web does not support file assets. */); + test('successful initialize on controller with error clears error', () async { final VideoPlayerController controller = VideoPlayerController.network( @@ -491,14 +493,13 @@ void main() { VideoPlayerController.networkUrl(_localhostUri); addTearDown(controller.dispose); - expect( - controller.textureId, VideoPlayerController.kUninitializedTextureId); + expect(controller.playerId, VideoPlayerController.kUninitializedPlayerId); expect(await controller.position, Duration.zero); await controller.initialize(); await controller.dispose(); - expect(controller.textureId, 0); + expect(controller.playerId, 0); expect(await controller.position, isNull); }); @@ -771,7 +772,7 @@ void main() { // Simulate continuous playback by incrementing in 50ms steps. for (int ms = 0; ms <= totalDurationMs; ms += 50) { - fakeVideoPlayerPlatform._positions[controller.textureId] = + fakeVideoPlayerPlatform._positions[controller.playerId] = Duration(milliseconds: ms); await Future.delayed(updateInterval); } @@ -949,7 +950,7 @@ void main() { await controller.play(); expect(controller.value.isPlaying, isTrue); final StreamController fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]!; + fakeVideoPlayerPlatform.streams[controller.playerId]!; fakeVideoEventStream .add(VideoEvent(eventType: VideoEventType.completed)); @@ -967,7 +968,7 @@ void main() { await controller.initialize(); expect(controller.value.isPlaying, isFalse); final StreamController fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]!; + fakeVideoPlayerPlatform.streams[controller.playerId]!; fakeVideoEventStream.add(VideoEvent( eventType: VideoEventType.isPlayingStateUpdate, @@ -995,7 +996,7 @@ void main() { expect(controller.value.isBuffering, false); expect(controller.value.buffered, isEmpty); final StreamController fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]!; + fakeVideoPlayerPlatform.streams[controller.playerId]!; fakeVideoEventStream .add(VideoEvent(eventType: VideoEventType.bufferingStart)); @@ -1047,7 +1048,7 @@ void main() { await controller.play(); for (int i = 0; i < 3; i++) { await Future.delayed(updatesInterval); - fakeVideoPlayerPlatform._positions[controller.textureId] = + fakeVideoPlayerPlatform._positions[controller.playerId] = Duration(milliseconds: i * updatesInterval.inMilliseconds); } @@ -1321,7 +1322,7 @@ void main() { await controller.initialize(); final StreamController fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]!; + fakeVideoPlayerPlatform.streams[controller.playerId]!; bool currentIsCompleted = controller.value.isCompleted; @@ -1349,7 +1350,7 @@ void main() { await controller.initialize(); final StreamController fakeVideoEventStream = - fakeVideoPlayerPlatform.streams[controller.textureId]!; + fakeVideoPlayerPlatform.streams[controller.playerId]!; bool currentIsCompleted = controller.value.isCompleted; @@ -1414,10 +1415,11 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { Completer initialized = Completer(); List calls = []; List dataSources = []; + List viewTypes = []; final Map> streams = >{}; bool forceInitError = false; - int nextTextureId = 0; + int nextPlayerId = 0; final Map _positions = {}; final Map webOptions = {}; @@ -1426,7 +1428,7 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { Future create(DataSource dataSource) async { calls.add('create'); final StreamController stream = StreamController(); - streams[nextTextureId] = stream; + streams[nextPlayerId] = stream; if (forceInitError) { stream.addError(PlatformException( code: 'VideoError', message: 'Video player had error XYZ')); @@ -1437,11 +1439,30 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { duration: const Duration(seconds: 1))); } dataSources.add(dataSource); - return nextTextureId++; + return nextPlayerId++; + } + + @override + Future createWithOptions(VideoCreationOptions options) async { + calls.add('createWithOptions'); + final StreamController stream = StreamController(); + streams[nextPlayerId] = stream; + if (forceInitError) { + stream.addError(PlatformException( + code: 'VideoError', message: 'Video player had error XYZ')); + } else { + stream.add(VideoEvent( + eventType: VideoEventType.initialized, + size: const Size(100, 100), + duration: const Duration(seconds: 1))); + } + dataSources.add(options.dataSource); + viewTypes.add(options.viewType); + return nextPlayerId++; } @override - Future dispose(int textureId) async { + Future dispose(int playerId) async { calls.add('dispose'); } @@ -1452,44 +1473,44 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { } @override - Stream videoEventsFor(int textureId) { - return streams[textureId]!.stream; + Stream videoEventsFor(int playerId) { + return streams[playerId]!.stream; } @override - Future pause(int textureId) async { + Future pause(int playerId) async { calls.add('pause'); } @override - Future play(int textureId) async { + Future play(int playerId) async { calls.add('play'); } @override - Future getPosition(int textureId) async { + Future getPosition(int playerId) async { calls.add('position'); - return _positions[textureId] ?? Duration.zero; + return _positions[playerId] ?? Duration.zero; } @override - Future seekTo(int textureId, Duration position) async { + Future seekTo(int playerId, Duration position) async { calls.add('seekTo'); - _positions[textureId] = position; + _positions[playerId] = position; } @override - Future setLooping(int textureId, bool looping) async { + Future setLooping(int playerId, bool looping) async { calls.add('setLooping'); } @override - Future setVolume(int textureId, double volume) async { + Future setVolume(int playerId, double volume) async { calls.add('setVolume'); } @override - Future setPlaybackSpeed(int textureId, double speed) async { + Future setPlaybackSpeed(int playerId, double speed) async { calls.add('setPlaybackSpeed'); } @@ -1499,17 +1520,17 @@ class FakeVideoPlayerPlatform extends VideoPlayerPlatform { } @override - Widget buildView(int textureId) { - return Texture(textureId: textureId); + Widget buildView(int playerId) { + return Texture(textureId: playerId); } @override Future setWebOptions( - int textureId, VideoPlayerWebOptions options) async { + int playerId, VideoPlayerWebOptions options) async { if (!kIsWeb) { throw UnimplementedError('setWebOptions() is only available in the web.'); } calls.add('setWebOptions'); - webOptions[textureId] = options; + webOptions[playerId] = options; } }