diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md index a1938965024c..466a0a6f37b9 100644 --- a/packages/video_player/video_player/CHANGELOG.md +++ b/packages/video_player/video_player/CHANGELOG.md @@ -1,7 +1,8 @@ -## 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.24/Dart 3.5. +* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6. ## 2.9.5 diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md index dcf1a6018bfa..e4ebe930c7ed 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 (Android and iOS). + +Using `VideoViewType.platformView` is not recommended on Android due to a known [issue][3] affecting platform views on Android. diff --git a/packages/video_player/video_player/example/lib/main.dart b/packages/video_player/video_player/example/lib/main.dart index 0ce77d603b5a..2f06d6d8fdc4 100644 --- a/packages/video_player/video_player/example/lib/main.dart +++ b/packages/video_player/video_player/example/lib/main.dart @@ -8,6 +8,9 @@ /// video. library; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; @@ -55,18 +58,97 @@ class _App extends StatelessWidget { ), ), body: TabBarView( - children: [ - _BumbleBeeRemoteVideo(), - _ButterFlyAssetVideo(), - _ButterFlyAssetVideoInList(), - ], + children: !kIsWeb && (Platform.isIOS || Platform.isAndroid) + ? [ + _ViewTypeTabBar( + builder: (VideoViewType viewType) => + _BumbleBeeRemoteVideo(viewType), + ), + _ViewTypeTabBar( + builder: (VideoViewType viewType) => + _ButterFlyAssetVideo(viewType), + ), + _ViewTypeTabBar( + builder: (VideoViewType viewType) => + _ButterFlyAssetVideoInList(viewType), + ), + ] + // The plugin does not support platform views on other platforms yet. + : const [ + _BumbleBeeRemoteVideo(VideoViewType.textureView), + _ButterFlyAssetVideo(VideoViewType.textureView), + _ButterFlyAssetVideoInList(VideoViewType.textureView), + ], ), ), ); } } +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 +172,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 +232,10 @@ class _ExampleCard extends StatelessWidget { } class _ButterFlyAssetVideo extends StatefulWidget { + const _ButterFlyAssetVideo(this.viewType); + + final VideoViewType viewType; + @override _ButterFlyAssetVideoState createState() => _ButterFlyAssetVideoState(); } @@ -160,7 +246,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 +295,10 @@ class _ButterFlyAssetVideoState extends State<_ButterFlyAssetVideo> { } class _BumbleBeeRemoteVideo extends StatefulWidget { + const _BumbleBeeRemoteVideo(this.viewType); + + final VideoViewType viewType; + @override _BumbleBeeRemoteVideoState createState() => _BumbleBeeRemoteVideoState(); } @@ -228,6 +321,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 12b30c2a146f..94c7c13667b5 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,16 @@ 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, + /// + /// **Android and iOS only**. The [viewType] option allows the caller to + /// specify the type of native view used to display the video. + 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 +292,9 @@ class VideoPlayerController extends ValueNotifier { /// **Android only**: The [formatHint] option allows the caller to override /// the video format detection code. /// + /// **Android and iOS only**. The [viewType] option allows the caller to + /// specify the type of native view used to display the video. + /// /// [httpHeaders] option allows to specify HTTP headers /// for the request to the [dataSource]. @Deprecated('Use VideoPlayerController.networkUrl instead') @@ -295,6 +304,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 +325,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 +336,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 +353,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 +391,14 @@ class VideoPlayerController extends ValueNotifier { /// Only set for [asset] videos. The package that the asset was loaded from. final String? package; + /// **Android and iOS only**. The type of view used to display the video. + /// + /// Regardless of the value of this parameter, the video will always be + /// displayed using a platform view on web. On macOS, the video will always + /// be displayed using a texture view (the plugin does not support + /// platform views on macOS yet). + final VideoViewType viewType; + Future? _closedCaptionFileFuture; ClosedCaptionFile? _closedCaptionFile; Timer? _timer; @@ -383,15 +407,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 +455,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 +547,7 @@ class VideoPlayerController extends ValueNotifier { } _eventSubscription = _videoPlayerPlatform - .videoEventsFor(_textureId) + .videoEventsFor(_playerId) .listen(eventListener, onError: errorListener); return initializingCompleter.future; } @@ -534,7 +564,7 @@ class VideoPlayerController extends ValueNotifier { _isDisposed = true; _timer?.cancel(); await _eventSubscription?.cancel(); - await _videoPlayerPlatform.dispose(_textureId); + await _videoPlayerPlatform.dispose(_playerId); } _lifeCycleObserver?.dispose(); } @@ -574,7 +604,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 +612,7 @@ class VideoPlayerController extends ValueNotifier { return; } if (value.isPlaying) { - await _videoPlayerPlatform.play(_textureId); + await _videoPlayerPlatform.play(_playerId); _timer?.cancel(); _timer = Timer.periodic( @@ -605,7 +635,7 @@ class VideoPlayerController extends ValueNotifier { await _applyPlaybackSpeed(); } else { _timer?.cancel(); - await _videoPlayerPlatform.pause(_textureId); + await _videoPlayerPlatform.pause(_playerId); } } @@ -613,7 +643,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 +659,7 @@ class VideoPlayerController extends ValueNotifier { } await _videoPlayerPlatform.setPlaybackSpeed( - _textureId, + _playerId, value.playbackSpeed, ); } @@ -639,7 +669,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 +686,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 +863,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 +874,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 +889,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 +901,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 7457b7d849c7..7ae423d6bdbf 100644 --- a/packages/video_player/video_player/pubspec.yaml +++ b/packages/video_player/video_player/pubspec.yaml @@ -1,13 +1,13 @@ 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.5.0 - flutter: ">=3.24.0" + sdk: ^3.6.0 + flutter: ">=3.27.0" flutter: plugin: @@ -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 5ecc7e4d69b8..5ebb4bb9b523 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 a7b0888faa6b..38acf159dd0b 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; } }