From 71cad8555f03b246e433178a42dcc8af99bfb7da Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 24 Jun 2025 13:27:20 -0400 Subject: [PATCH 1/7] Switch most methods to a per-instance API channel --- .../flutter/plugins/videoplayer/Messages.java | 112 ++++--- .../plugins/videoplayer/VideoPlayer.java | 50 ++- .../videoplayer/VideoPlayerPlugin.java | 53 +-- .../plugins/videoplayer/VideoPlayerTest.java | 8 +- .../lib/src/android_video_player.dart | 53 ++- .../lib/src/messages.g.dart | 85 +++-- .../pigeons/messages.dart | 18 +- .../video_player_android/pubspec.yaml | 2 + .../test/android_video_player_test.dart | 161 ++++----- .../video_player_android/test/test_api.g.dart | 307 +----------------- 10 files changed, 307 insertions(+), 542 deletions(-) diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java index 581cb7b80f6..fade087a259 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.videoplayer; @@ -380,28 +380,12 @@ public interface AndroidVideoPlayerApi { void dispose(@NonNull Long playerId); - void setLooping(@NonNull Long playerId, @NonNull Boolean looping); - - void setVolume(@NonNull Long playerId, @NonNull Double volume); - - void setPlaybackSpeed(@NonNull Long playerId, @NonNull Double speed); - - void play(@NonNull Long playerId); - - @NonNull - Long position(@NonNull Long playerId); - - void seekTo(@NonNull Long playerId, @NonNull Long position); - - void pause(@NonNull Long playerId); - void setMixWithOthers(@NonNull Boolean mixWithOthers); /** The codec used by AndroidVideoPlayerApi. */ static @NonNull MessageCodec getCodec() { return PigeonCodec.INSTANCE; } - /** * Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the * `binaryMessenger`. @@ -493,7 +477,7 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setLooping" + "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers" + messageChannelSuffix, getCodec()); if (api != null) { @@ -501,10 +485,9 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); ArrayList args = (ArrayList) message; - Long playerIdArg = (Long) args.get(0); - Boolean loopingArg = (Boolean) args.get(1); + Boolean mixWithOthersArg = (Boolean) args.get(0); try { - api.setLooping(playerIdArg, loopingArg); + api.setMixWithOthers(mixWithOthersArg); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); @@ -515,11 +498,49 @@ static void setUp( channel.setMessageHandler(null); } } + } + } + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface VideoPlayerInstanceApi { + + void setLooping(@NonNull Boolean looping); + + void setVolume(@NonNull Double volume); + + void setPlaybackSpeed(@NonNull Double speed); + + void play(); + + @NonNull + Long getPosition(); + + void seekTo(@NonNull Long position); + + void pause(); + + /** The codec used by VideoPlayerInstanceApi. */ + static @NonNull MessageCodec getCodec() { + return PigeonCodec.INSTANCE; + } + /** + * Sets up an instance of `VideoPlayerInstanceApi` to handle messages through the + * `binaryMessenger`. + */ + static void setUp( + @NonNull BinaryMessenger binaryMessenger, @Nullable VideoPlayerInstanceApi api) { + setUp(binaryMessenger, "", api); + } + + static void setUp( + @NonNull BinaryMessenger binaryMessenger, + @NonNull String messageChannelSuffix, + @Nullable VideoPlayerInstanceApi api) { + messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix; { BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setVolume" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping" + messageChannelSuffix, getCodec()); if (api != null) { @@ -527,10 +548,9 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); ArrayList args = (ArrayList) message; - Long playerIdArg = (Long) args.get(0); - Double volumeArg = (Double) args.get(1); + Boolean loopingArg = (Boolean) args.get(0); try { - api.setVolume(playerIdArg, volumeArg); + api.setLooping(loopingArg); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); @@ -545,7 +565,7 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setPlaybackSpeed" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume" + messageChannelSuffix, getCodec()); if (api != null) { @@ -553,10 +573,9 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); ArrayList args = (ArrayList) message; - Long playerIdArg = (Long) args.get(0); - Double speedArg = (Double) args.get(1); + Double volumeArg = (Double) args.get(0); try { - api.setPlaybackSpeed(playerIdArg, speedArg); + api.setVolume(volumeArg); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); @@ -571,7 +590,7 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.play" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed" + messageChannelSuffix, getCodec()); if (api != null) { @@ -579,9 +598,9 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); ArrayList args = (ArrayList) message; - Long playerIdArg = (Long) args.get(0); + Double speedArg = (Double) args.get(0); try { - api.play(playerIdArg); + api.setPlaybackSpeed(speedArg); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); @@ -596,18 +615,16 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.position" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - Long playerIdArg = (Long) args.get(0); try { - Long output = api.position(playerIdArg); - wrapped.add(0, output); + api.play(); + wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); } @@ -621,19 +638,16 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekTo" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getPosition" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - Long playerIdArg = (Long) args.get(0); - Long positionArg = (Long) args.get(1); try { - api.seekTo(playerIdArg, positionArg); - wrapped.add(0, null); + Long output = api.getPosition(); + wrapped.add(0, output); } catch (Throwable exception) { wrapped = wrapError(exception); } @@ -647,7 +661,7 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.pause" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo" + messageChannelSuffix, getCodec()); if (api != null) { @@ -655,9 +669,9 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); ArrayList args = (ArrayList) message; - Long playerIdArg = (Long) args.get(0); + Long positionArg = (Long) args.get(0); try { - api.pause(playerIdArg); + api.seekTo(positionArg); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); @@ -672,17 +686,15 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); - ArrayList args = (ArrayList) message; - Boolean mixWithOthersArg = (Boolean) args.get(0); try { - api.setMixWithOthers(mixWithOthersArg); + api.pause(); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index 87ad4cb6273..f9c558ef823 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -21,9 +21,10 @@ * *

It provides methods to control playback, adjust volume, and handle seeking. */ -public abstract class VideoPlayer { +public abstract class VideoPlayer implements Messages.VideoPlayerInstanceApi { @NonNull protected final VideoPlayerCallbacks videoPlayerEvents; @Nullable protected final SurfaceProducer surfaceProducer; + @Nullable private DisposeHandler disposeHandler; @NonNull protected ExoPlayer exoPlayer; /** A closure-compatible signature since {@link java.util.function.Supplier} is API level 24. */ @@ -37,6 +38,11 @@ public interface ExoPlayerProvider { ExoPlayer get(); } + /** A handler to run when dispose is called. */ + public interface DisposeHandler { + void onDispose(); + } + public VideoPlayer( @NonNull VideoPlayerCallbacks events, @NonNull MediaItem mediaItem, @@ -52,6 +58,10 @@ public VideoPlayer( setAudioAttributes(exoPlayer, options.mixWithOthers); } + public void setDisposeHandler(@Nullable DisposeHandler handler) { + disposeHandler = handler; + } + @NonNull protected abstract ExoPlayerEventListener createExoPlayerEventListener( @NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer); @@ -66,37 +76,48 @@ private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) { !isMixMode); } - void play() { + @Override + public void play() { exoPlayer.play(); } - void pause() { + @Override + public void pause() { exoPlayer.pause(); } - void setLooping(boolean value) { - exoPlayer.setRepeatMode(value ? REPEAT_MODE_ALL : REPEAT_MODE_OFF); + @Override + public void setLooping(@NonNull Boolean looping) { + exoPlayer.setRepeatMode(looping ? REPEAT_MODE_ALL : REPEAT_MODE_OFF); } - void setVolume(double value) { - float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value)); + @Override + public void setVolume(@NonNull Double volume) { + float bracketedValue = (float) Math.max(0.0, Math.min(1.0, volume)); exoPlayer.setVolume(bracketedValue); } - void setPlaybackSpeed(double value) { + @Override + public void setPlaybackSpeed(@NonNull Double speed) { // We do not need to consider pitch and skipSilence for now as we do not handle them and // therefore never diverge from the default values. - final PlaybackParameters playbackParameters = new PlaybackParameters(((float) value)); + final PlaybackParameters playbackParameters = new PlaybackParameters(speed.floatValue()); exoPlayer.setPlaybackParameters(playbackParameters); } - void seekTo(int location) { - exoPlayer.seekTo(location); + @Override + public @NonNull Long getPosition() { + long position = exoPlayer.getCurrentPosition(); + // TODO(stuartmorgan): Move this; this is relying on the fact that getPosition is called + // frequently to drive buffering updates, which is a fragile hack. + sendBufferingUpdate(); + return position; } - long getPosition() { - return exoPlayer.getCurrentPosition(); + @Override + public void seekTo(@NonNull Long position) { + exoPlayer.seekTo(position.intValue()); } @NonNull @@ -105,6 +126,9 @@ public ExoPlayer getExoPlayer() { } public void dispose() { + if (disposeHandler != null) { + disposeHandler.onDispose(); + } exoPlayer.release(); } } diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index 3db5fd42a26..38dd9960fe5 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -14,6 +14,7 @@ import io.flutter.plugin.common.EventChannel; import io.flutter.plugins.videoplayer.Messages.AndroidVideoPlayerApi; import io.flutter.plugins.videoplayer.Messages.CreateMessage; +import io.flutter.plugins.videoplayer.Messages.VideoPlayerInstanceApi; import io.flutter.plugins.videoplayer.platformview.PlatformVideoViewFactory; import io.flutter.plugins.videoplayer.platformview.PlatformViewVideoPlayer; import io.flutter.plugins.videoplayer.texture.TextureVideoPlayer; @@ -141,6 +142,14 @@ public void initialize() { options); } + // Set up the instance-specific API handler, and make sure it is removed when the player is + // disposed. + BinaryMessenger messenger = flutterState.binaryMessenger; + final String channelSuffix = Integer.toString((int) id); + VideoPlayerInstanceApi.setUp(messenger, channelSuffix, videoPlayer); + videoPlayer.setDisposeHandler( + () -> VideoPlayerInstanceApi.setUp(messenger, channelSuffix, null)); + videoPlayers.put(id, videoPlayer); return id; } @@ -174,50 +183,6 @@ public void dispose(@NonNull Long playerId) { videoPlayers.remove(playerId); } - @Override - public void setLooping(@NonNull Long playerId, @NonNull Boolean looping) { - VideoPlayer player = getPlayer(playerId); - player.setLooping(looping); - } - - @Override - public void setVolume(@NonNull Long playerId, @NonNull Double volume) { - VideoPlayer player = getPlayer(playerId); - player.setVolume(volume); - } - - @Override - public void setPlaybackSpeed(@NonNull Long playerId, @NonNull Double speed) { - VideoPlayer player = getPlayer(playerId); - player.setPlaybackSpeed(speed); - } - - @Override - public void play(@NonNull Long playerId) { - VideoPlayer player = getPlayer(playerId); - player.play(); - } - - @Override - public @NonNull Long position(@NonNull Long playerId) { - VideoPlayer player = getPlayer(playerId); - long position = player.getPosition(); - player.sendBufferingUpdate(); - return position; - } - - @Override - public void seekTo(@NonNull Long playerId, @NonNull Long position) { - VideoPlayer player = getPlayer(playerId); - player.seekTo(position.intValue()); - } - - @Override - public void pause(@NonNull Long playerId) { - VideoPlayer player = getPlayer(playerId); - player.pause(); - } - @Override public void setMixWithOthers(@NonNull Boolean mixWithOthers) { options.mixWithOthers = mixWithOthers; diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java index 0b663b16e28..ef42438e908 100644 --- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java @@ -95,7 +95,7 @@ public void loadsAndPreparesProvidedMediaEnablesAudioFocusByDefault() { verify(mockExoPlayer).prepare(); verify(mockExoPlayer).setAudioAttributes(attributesCaptor.capture(), eq(true)); - assertEquals(attributesCaptor.getValue().contentType, C.AUDIO_CONTENT_TYPE_MOVIE); + assertEquals(C.AUDIO_CONTENT_TYPE_MOVIE, attributesCaptor.getValue().contentType); videoPlayer.dispose(); } @@ -108,7 +108,7 @@ public void loadsAndPreparesProvidedMediaDisablesAudioFocusWhenMixModeSet() { VideoPlayer videoPlayer = createVideoPlayer(options); verify(mockExoPlayer).setAudioAttributes(attributesCaptor.capture(), eq(false)); - assertEquals(attributesCaptor.getValue().contentType, C.AUDIO_CONTENT_TYPE_MOVIE); + assertEquals(C.AUDIO_CONTENT_TYPE_MOVIE, attributesCaptor.getValue().contentType); videoPlayer.dispose(); } @@ -180,11 +180,11 @@ public void setPlaybackSpeedSetsPlaybackParametersWithValue() { public void seekAndGetPosition() { VideoPlayer videoPlayer = createVideoPlayer(); - videoPlayer.seekTo(10); + videoPlayer.seekTo(10L); verify(mockExoPlayer).seekTo(10); when(mockExoPlayer.getCurrentPosition()).thenReturn(20L); - assertEquals(20L, videoPlayer.getPosition()); + assertEquals(20L, videoPlayer.getPosition().doubleValue(), 0.001); } @Test diff --git a/packages/video_player/video_player_android/lib/src/android_video_player.dart b/packages/video_player/video_player_android/lib/src/android_video_player.dart index bd255f4b0f5..e323eb9619f 100644 --- a/packages/video_player/video_player_android/lib/src/android_video_player.dart +++ b/packages/video_player/video_player_android/lib/src/android_video_player.dart @@ -11,16 +11,33 @@ import 'package:video_player_platform_interface/video_player_platform_interface. import 'messages.g.dart'; import 'platform_view_player.dart'; +/// The non-test implementation of `_apiProvider`. +VideoPlayerInstanceApi _productionApiProvider(int mapId) { + return VideoPlayerInstanceApi(messageChannelSuffix: mapId.toString()); +} + /// An Android implementation of [VideoPlayerPlatform] that uses the /// Pigeon-generated [VideoPlayerApi]. class AndroidVideoPlayer extends VideoPlayerPlatform { + /// Creates a new Android maps implementation instance. + AndroidVideoPlayer({ + @visibleForTesting + VideoPlayerInstanceApi Function(int playerId)? apiProvider, + }) : _apiProvider = apiProvider ?? _productionApiProvider; + final AndroidVideoPlayerApi _api = AndroidVideoPlayerApi(); + // A method to create VideoPlayerInstanceApi instances, which can be + //overridden for testing. + final VideoPlayerInstanceApi Function(int mapId) _apiProvider; /// A map that associates player ID with a view state. /// This is used to determine which view type to use when building a view. final Map _playerViewStates = {}; + final Map _playerApis = + {}; + /// Registers this class as the default instance of [PathProviderPlatform]. static void registerWith() { VideoPlayerPlatform.instance = AndroidVideoPlayer(); @@ -35,6 +52,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { Future dispose(int playerId) async { await _api.dispose(playerId); _playerViewStates.remove(playerId); + _playerApis.remove(playerId); } @override @@ -89,45 +107,58 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { ), VideoViewType.platformView => const _VideoPlayerPlatformViewState(), }; + ensureApiInitialized(playerId); return playerId; } + /// Returns the API instance for [playerId], creating it if it doesn't already + /// exist. + @visibleForTesting + VideoPlayerInstanceApi ensureApiInitialized(int playerId) { + VideoPlayerInstanceApi? api = _playerApis[playerId]; + if (api == null) { + api = _apiProvider(playerId); + _playerApis[playerId] ??= api; + } + return api; + } + @override Future setLooping(int playerId, bool looping) { - return _api.setLooping(playerId, looping); + return _apiFor(playerId).setLooping(looping); } @override Future play(int playerId) { - return _api.play(playerId); + return _apiFor(playerId).play(); } @override Future pause(int playerId) { - return _api.pause(playerId); + return _apiFor(playerId).pause(); } @override Future setVolume(int playerId, double volume) { - return _api.setVolume(playerId, volume); + return _apiFor(playerId).setVolume(volume); } @override Future setPlaybackSpeed(int playerId, double speed) { assert(speed > 0); - return _api.setPlaybackSpeed(playerId, speed); + return _apiFor(playerId).setPlaybackSpeed(speed); } @override Future seekTo(int playerId, Duration position) { - return _api.seekTo(playerId, position.inMilliseconds); + return _apiFor(playerId).seekTo(position.inMilliseconds); } @override Future getPosition(int playerId) async { - final int position = await _api.position(playerId); + final int position = await _apiFor(playerId).getPosition(); return Duration(milliseconds: position); } @@ -203,6 +234,14 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return EventChannel('flutter.io/videoPlayer/videoEvents$playerId'); } + VideoPlayerInstanceApi _apiFor(int playerId) { + final VideoPlayerInstanceApi? api = _playerApis[playerId]; + if (api == null) { + throw StateError('No active player with ID $playerId.'); + } + return api; + } + static const Map _videoFormatStringMap = { VideoFormat.ss: 'ss', diff --git a/packages/video_player/video_player_android/lib/src/messages.g.dart b/packages/video_player/video_player_android/lib/src/messages.g.dart index 253b1c4fb9f..04c4ca3d636 100644 --- a/packages/video_player/video_player_android/lib/src/messages.g.dart +++ b/packages/video_player/video_player_android/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -228,9 +228,9 @@ class AndroidVideoPlayerApi { } } - Future setLooping(int playerId, bool looping) async { + Future setMixWithOthers(bool mixWithOthers) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setLooping$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -238,7 +238,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId, looping]) + await pigeonVar_channel.send([mixWithOthers]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); @@ -252,10 +252,27 @@ class AndroidVideoPlayerApi { return; } } +} + +class VideoPlayerInstanceApi { + /// Constructor for [VideoPlayerInstanceApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + VideoPlayerInstanceApi({ + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; - Future setVolume(int playerId, double volume) async { + Future setLooping(bool looping) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setVolume$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -263,8 +280,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId, volume]) - as List?; + await pigeonVar_channel.send([looping]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -278,9 +294,9 @@ class AndroidVideoPlayerApi { } } - Future setPlaybackSpeed(int playerId, double speed) async { + Future setVolume(double volume) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -288,8 +304,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId, speed]) - as List?; + await pigeonVar_channel.send([volume]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -303,9 +318,9 @@ class AndroidVideoPlayerApi { } } - Future play(int playerId) async { + Future setPlaybackSpeed(double speed) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.play$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -313,7 +328,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_channel.send([speed]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -327,9 +342,9 @@ class AndroidVideoPlayerApi { } } - Future position(int playerId) async { + Future play() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.position$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -337,7 +352,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -346,19 +361,14 @@ class AndroidVideoPlayerApi { message: pigeonVar_replyList[1] as String?, details: pigeonVar_replyList[2], ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); } else { - return (pigeonVar_replyList[0] as int?)!; + return; } } - Future seekTo(int playerId, int position) async { + Future getPosition() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekTo$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getPosition$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -366,8 +376,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId, position]) - as List?; + await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -376,14 +385,19 @@ class AndroidVideoPlayerApi { message: pigeonVar_replyList[1] as String?, details: pigeonVar_replyList[2], ); + } else if (pigeonVar_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); } else { - return; + return (pigeonVar_replyList[0] as int?)!; } } - Future pause(int playerId) async { + Future seekTo(int position) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.pause$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -391,7 +405,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_channel.send([position]) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -405,9 +419,9 @@ class AndroidVideoPlayerApi { } } - Future setMixWithOthers(bool mixWithOthers) async { + Future pause() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -415,8 +429,7 @@ class AndroidVideoPlayerApi { binaryMessenger: pigeonVar_binaryMessenger, ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([mixWithOthers]) - as List?; + await pigeonVar_channel.send(null) as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index 9916e38aaf8..6a6062d1ee3 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -39,12 +39,16 @@ abstract class AndroidVideoPlayerApi { void initialize(); int create(CreateMessage msg); void dispose(int playerId); - void setLooping(int playerId, bool looping); - void setVolume(int playerId, double volume); - void setPlaybackSpeed(int playerId, double speed); - void play(int playerId); - int position(int playerId); - void seekTo(int playerId, int position); - void pause(int playerId); void setMixWithOthers(bool mixWithOthers); } + +@HostApi() +abstract class VideoPlayerInstanceApi { + void setLooping(bool looping); + void setVolume(double volume); + void setPlaybackSpeed(double speed); + void play(); + int getPosition(); + void seekTo(int position); + void pause(); +} diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index b3c62d6508c..2e7a20fb5ea 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -23,8 +23,10 @@ dependencies: video_player_platform_interface: ^6.3.0 dev_dependencies: + build_runner: ^2.3.3 flutter_test: sdk: flutter + mockito: ^5.4.4 pigeon: ^22.4.2 topics: diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index 7214c4376bc..c0a80f52dc2 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -5,21 +5,20 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; import 'package:video_player_android/src/messages.g.dart'; import 'package:video_player_android/src/platform_view_player.dart'; import 'package:video_player_android/video_player_android.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; +import 'android_video_player_test.mocks.dart'; import 'test_api.g.dart'; class _ApiLogger implements TestHostVideoPlayerApi { final List log = []; int? passedPlayerId; CreateMessage? passedCreateMessage; - int? passedPosition; - bool? passedLooping; - double? passedVolume; - double? passedPlaybackSpeed; bool? passedMixWithOthers; @override @@ -40,70 +39,34 @@ class _ApiLogger implements TestHostVideoPlayerApi { log.add('init'); } - @override - void pause(int playerId) { - log.add('pause'); - passedPlayerId = playerId; - } - - @override - void play(int playerId) { - log.add('play'); - passedPlayerId = playerId; - } - @override void setMixWithOthers(bool mixWithOthers) { log.add('setMixWithOthers'); passedMixWithOthers = mixWithOthers; } - - @override - int position(int playerId) { - log.add('position'); - passedPlayerId = playerId; - return 234; - } - - @override - void seekTo(int playerId, int position) { - log.add('seekTo'); - passedPlayerId = playerId; - passedPosition = position; - } - - @override - void setLooping(int playerId, bool looping) { - log.add('setLooping'); - passedPlayerId = playerId; - passedLooping = looping; - } - - @override - void setVolume(int playerId, double volume) { - log.add('setVolume'); - passedPlayerId = playerId; - passedVolume = volume; - } - - @override - void setPlaybackSpeed(int playerId, double speed) { - log.add('setPlaybackSpeed'); - passedPlayerId = playerId; - passedPlaybackSpeed = speed; - } } +@GenerateNiceMocks(>[MockSpec()]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); + (AndroidVideoPlayer, MockVideoPlayerInstanceApi) setUpMockPlayer({ + required int playerId, + }) { + final MockVideoPlayerInstanceApi api = MockVideoPlayerInstanceApi(); + final AndroidVideoPlayer player = AndroidVideoPlayer( + apiProvider: (_) => api, + ); + player.ensureApiInitialized(playerId); + return (player, api); + } + test('registration', () async { AndroidVideoPlayer.registerWith(); expect(VideoPlayerPlatform.instance, isA()); }); - group('$AndroidVideoPlayer', () { - final AndroidVideoPlayer player = AndroidVideoPlayer(); + group('AndroidVideoPlayer', () { late _ApiLogger log; setUp(() { @@ -112,17 +75,20 @@ void main() { }); test('init', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); await player.init(); expect(log.log.last, 'init'); }); test('dispose', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); await player.dispose(1); expect(log.log.last, 'dispose'); expect(log.passedPlayerId, 1); }); test('create with asset', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.create( DataSource( sourceType: DataSourceType.asset, @@ -141,6 +107,7 @@ void main() { }); test('create with network', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.create( DataSource( sourceType: DataSourceType.network, @@ -162,6 +129,7 @@ void main() { }); test('create with network (some headers)', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.create( DataSource( sourceType: DataSourceType.network, @@ -185,6 +153,7 @@ void main() { }); test('create with file', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.create( DataSource(sourceType: DataSourceType.file, uri: 'someUri'), ); @@ -198,6 +167,7 @@ void main() { }); test('create with file (some headers)', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.create( DataSource( sourceType: DataSourceType.file, @@ -218,6 +188,7 @@ void main() { }); test('createWithOptions with asset', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( @@ -239,6 +210,7 @@ void main() { }); test('createWithOptions with network', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( @@ -263,6 +235,7 @@ void main() { }); test('createWithOptions with network (some headers)', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( @@ -289,6 +262,7 @@ void main() { }); test('createWithOptions with file', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( @@ -308,6 +282,7 @@ void main() { }); test('createWithOptions with file (some headers)', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( @@ -331,6 +306,7 @@ void main() { }); test('createWithOptions with platform view', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( @@ -353,25 +329,37 @@ void main() { }); test('setLooping', () async { + final ( + AndroidVideoPlayer player, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); await player.setLooping(1, true); - expect(log.log.last, 'setLooping'); - expect(log.passedPlayerId, 1); - expect(log.passedLooping, true); + + verify(playerApi.setLooping(true)); }); test('play', () async { + final ( + AndroidVideoPlayer player, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); await player.play(1); - expect(log.log.last, 'play'); - expect(log.passedPlayerId, 1); + + verify(playerApi.play()); }); test('pause', () async { + final ( + AndroidVideoPlayer player, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); await player.pause(1); - expect(log.log.last, 'pause'); - expect(log.passedPlayerId, 1); + + verify(playerApi.pause()); }); test('setMixWithOthers', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); await player.setMixWithOthers(true); expect(log.log.last, 'setMixWithOthers'); expect(log.passedMixWithOthers, true); @@ -382,34 +370,57 @@ void main() { }); test('setVolume', () async { - await player.setVolume(1, 0.7); - expect(log.log.last, 'setVolume'); - expect(log.passedPlayerId, 1); - expect(log.passedVolume, 0.7); + final ( + AndroidVideoPlayer player, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const double volume = 0.7; + await player.setVolume(1, volume); + + verify(playerApi.setVolume(volume)); }); test('setPlaybackSpeed', () async { - await player.setPlaybackSpeed(1, 1.5); - expect(log.log.last, 'setPlaybackSpeed'); - expect(log.passedPlayerId, 1); - expect(log.passedPlaybackSpeed, 1.5); + final ( + AndroidVideoPlayer player, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const double speed = 1.5; + await player.setPlaybackSpeed(1, speed); + + verify(playerApi.setPlaybackSpeed(speed)); }); test('seekTo', () async { - await player.seekTo(1, const Duration(milliseconds: 12345)); - expect(log.log.last, 'seekTo'); - expect(log.passedPlayerId, 1); - expect(log.passedPosition, 12345); + final ( + AndroidVideoPlayer player, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const int positionMilliseconds = 12345; + await player.seekTo( + 1, + const Duration(milliseconds: positionMilliseconds), + ); + + verify(playerApi.seekTo(positionMilliseconds)); }); test('getPosition', () async { + final ( + AndroidVideoPlayer player, + MockVideoPlayerInstanceApi playerApi, + ) = setUpMockPlayer(playerId: 1); + const int positionMilliseconds = 12345; + when( + playerApi.getPosition(), + ).thenAnswer((_) async => positionMilliseconds); + final Duration position = await player.getPosition(1); - expect(log.log.last, 'position'); - expect(log.passedPlayerId, 1); - expect(position, const Duration(milliseconds: 234)); + expect(position, const Duration(milliseconds: positionMilliseconds)); }); test('videoEventsFor', () async { + final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); const String mockChannel = 'flutter.io/videoPlayer/videoEvents123'; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMessageHandler(mockChannel, (ByteData? message) async { diff --git a/packages/video_player/video_player_android/test/test_api.g.dart b/packages/video_player/video_player_android/test/test_api.g.dart index a7cc6a7d87a..716ca5d5dd2 100644 --- a/packages/video_player/video_player_android/test/test_api.g.dart +++ b/packages/video_player/video_player_android/test/test_api.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.6.1), do not edit directly. +// Autogenerated from Pigeon (v22.7.4), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports @@ -61,20 +61,6 @@ abstract class TestHostVideoPlayerApi { void dispose(int playerId); - void setLooping(int playerId, bool looping); - - void setVolume(int playerId, double volume); - - void setPlaybackSpeed(int playerId, double speed); - - void play(int playerId); - - int position(int playerId); - - void seekTo(int playerId, int position); - - void pause(int playerId); - void setMixWithOthers(bool mixWithOthers); static void setUp( @@ -197,297 +183,6 @@ abstract class TestHostVideoPlayerApi { }); } } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setLooping$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler< - Object? - >(pigeonVar_channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setLooping was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setLooping was null, expected non-null int.', - ); - final bool? arg_looping = (args[1] as bool?); - assert( - arg_looping != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setLooping was null, expected non-null bool.', - ); - try { - api.setLooping(arg_playerId!, arg_looping!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setVolume$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler< - Object? - >(pigeonVar_channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setVolume was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setVolume was null, expected non-null int.', - ); - final double? arg_volume = (args[1] as double?); - assert( - arg_volume != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setVolume was null, expected non-null double.', - ); - try { - api.setVolume(arg_playerId!, arg_volume!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setPlaybackSpeed$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler< - Object? - >(pigeonVar_channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setPlaybackSpeed was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setPlaybackSpeed was null, expected non-null int.', - ); - final double? arg_speed = (args[1] as double?); - assert( - arg_speed != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setPlaybackSpeed was null, expected non-null double.', - ); - try { - api.setPlaybackSpeed(arg_playerId!, arg_speed!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.play$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, ( - Object? message, - ) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.play was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.play was null, expected non-null int.', - ); - try { - api.play(arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException( - code: 'error', - message: e.toString(), - ), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.position$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, ( - Object? message, - ) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.position was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.position was null, expected non-null int.', - ); - try { - final int output = api.position(arg_playerId!); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException( - code: 'error', - message: e.toString(), - ), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekTo$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler< - Object? - >(pigeonVar_channel, (Object? message) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekTo was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekTo was null, expected non-null int.', - ); - final int? arg_position = (args[1] as int?); - assert( - arg_position != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekTo was null, expected non-null int.', - ); - try { - api.seekTo(arg_playerId!, arg_position!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString()), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.pause$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, ( - Object? message, - ) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.pause was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.pause was null, expected non-null int.', - ); - try { - api.pause(arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException( - code: 'error', - message: e.toString(), - ), - ); - } - }); - } - } { final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( From e3684d119cc4999ba4ec42c38b0d09ebd005aee2 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 26 Jun 2025 16:59:41 -0400 Subject: [PATCH 2/7] Switch remaining tests from Pigeon generated tests to mockito --- .../lib/src/android_video_player.dart | 6 +- .../pigeons/messages.dart | 3 +- .../test/android_video_player_test.dart | 437 +++++++++++------- .../test/android_video_player_test.mocks.dart | 165 +++++++ .../video_player_android/test/test_api.g.dart | 228 --------- 5 files changed, 428 insertions(+), 411 deletions(-) create mode 100644 packages/video_player/video_player_android/test/android_video_player_test.mocks.dart delete mode 100644 packages/video_player/video_player_android/test/test_api.g.dart diff --git a/packages/video_player/video_player_android/lib/src/android_video_player.dart b/packages/video_player/video_player_android/lib/src/android_video_player.dart index e323eb9619f..59be59eb7cf 100644 --- a/packages/video_player/video_player_android/lib/src/android_video_player.dart +++ b/packages/video_player/video_player_android/lib/src/android_video_player.dart @@ -21,11 +21,13 @@ VideoPlayerInstanceApi _productionApiProvider(int mapId) { class AndroidVideoPlayer extends VideoPlayerPlatform { /// Creates a new Android maps implementation instance. AndroidVideoPlayer({ + @visibleForTesting AndroidVideoPlayerApi? pluginApi, @visibleForTesting VideoPlayerInstanceApi Function(int playerId)? apiProvider, - }) : _apiProvider = apiProvider ?? _productionApiProvider; + }) : _api = pluginApi ?? AndroidVideoPlayerApi(), + _apiProvider = apiProvider ?? _productionApiProvider; - final AndroidVideoPlayerApi _api = AndroidVideoPlayerApi(); + final AndroidVideoPlayerApi _api; // A method to create VideoPlayerInstanceApi instances, which can be //overridden for testing. final VideoPlayerInstanceApi Function(int mapId) _apiProvider; diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index 6a6062d1ee3..114c90452b3 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -7,7 +7,6 @@ import 'package:pigeon/pigeon.dart'; @ConfigurePigeon( PigeonOptions( dartOut: 'lib/src/messages.g.dart', - dartTestOut: 'test/test_api.g.dart', javaOut: 'android/src/main/java/io/flutter/plugins/videoplayer/Messages.java', javaOptions: JavaOptions(package: 'io.flutter.plugins.videoplayer'), @@ -34,7 +33,7 @@ class CreateMessage { PlatformVideoViewType? viewType; } -@HostApi(dartHostTestHandler: 'TestHostVideoPlayerApi') +@HostApi() abstract class AndroidVideoPlayerApi { void initialize(); int create(CreateMessage msg); diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index c0a80f52dc2..1c3bc6a428b 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -13,52 +13,24 @@ import 'package:video_player_android/video_player_android.dart'; import 'package:video_player_platform_interface/video_player_platform_interface.dart'; import 'android_video_player_test.mocks.dart'; -import 'test_api.g.dart'; - -class _ApiLogger implements TestHostVideoPlayerApi { - final List log = []; - int? passedPlayerId; - CreateMessage? passedCreateMessage; - bool? passedMixWithOthers; - - @override - int create(CreateMessage arg) { - log.add('create'); - passedCreateMessage = arg; - return 3; - } - - @override - void dispose(int playerId) { - log.add('dispose'); - passedPlayerId = playerId; - } - - @override - void initialize() { - log.add('init'); - } - - @override - void setMixWithOthers(bool mixWithOthers) { - log.add('setMixWithOthers'); - passedMixWithOthers = mixWithOthers; - } -} -@GenerateNiceMocks(>[MockSpec()]) +@GenerateNiceMocks(>[ + MockSpec(), + MockSpec(), +]) void main() { TestWidgetsFlutterBinding.ensureInitialized(); - (AndroidVideoPlayer, MockVideoPlayerInstanceApi) setUpMockPlayer({ - required int playerId, - }) { - final MockVideoPlayerInstanceApi api = MockVideoPlayerInstanceApi(); + (AndroidVideoPlayer, MockAndroidVideoPlayerApi, MockVideoPlayerInstanceApi) + setUpMockPlayer({required int playerId}) { + final MockAndroidVideoPlayerApi pluginApi = MockAndroidVideoPlayerApi(); + final MockVideoPlayerInstanceApi instanceApi = MockVideoPlayerInstanceApi(); final AndroidVideoPlayer player = AndroidVideoPlayer( - apiProvider: (_) => api, + pluginApi: pluginApi, + apiProvider: (_) => instanceApi, ); player.ensureApiInitialized(playerId); - return (player, api); + return (player, pluginApi, instanceApi); } test('registration', () async { @@ -67,39 +39,53 @@ void main() { }); group('AndroidVideoPlayer', () { - late _ApiLogger log; - - setUp(() { - log = _ApiLogger(); - TestHostVideoPlayerApi.setUp(log); - }); - test('init', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); await player.init(); - expect(log.log.last, 'init'); + + verify(api.initialize()); }); test('dispose', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); await player.dispose(1); - expect(log.log.last, 'dispose'); - expect(log.passedPlayerId, 1); + + verify(api.dispose(1)); }); test('create with asset', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String asset = 'someAsset'; + const String package = 'somePackage'; final int? playerId = await player.create( DataSource( sourceType: DataSourceType.asset, - asset: 'someAsset', - package: 'somePackage', + asset: asset, + package: package, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.asset, 'someAsset'); - expect(log.passedCreateMessage?.packageName, 'somePackage'); - expect(playerId, 3); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.asset, asset); + expect(createMessage.packageName, package); + expect(playerId, newPlayerId); expect( player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA(), @@ -107,221 +93,290 @@ void main() { }); test('create with network', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String uri = 'https://example.com'; final int? playerId = await player.create( DataSource( sourceType: DataSourceType.network, - uri: 'someUri', + uri: uri, formatHint: VideoFormat.dash, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.asset, null); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(log.passedCreateMessage?.packageName, null); - expect(log.passedCreateMessage?.formatHint, 'dash'); - expect(log.passedCreateMessage?.httpHeaders, {}); - expect(playerId, 3); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.asset, null); + expect(createMessage.uri, uri); + expect(createMessage.packageName, null); + expect(createMessage.formatHint, 'dash'); + expect(createMessage.httpHeaders, {}); + expect(playerId, newPlayerId); expect( player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA(), ); }); - test('create with network (some headers)', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); - final int? playerId = await player.create( + test('create with network passes headers', () async { + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + when(api.create(any)).thenAnswer((_) async => 2); + + const Map headers = { + 'Authorization': 'Bearer token', + }; + await player.create( DataSource( sourceType: DataSourceType.network, - uri: 'someUri', - httpHeaders: {'Authorization': 'Bearer token'}, + uri: 'https://example.com', + httpHeaders: headers, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.asset, null); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(log.passedCreateMessage?.packageName, null); - expect(log.passedCreateMessage?.formatHint, null); - expect(log.passedCreateMessage?.httpHeaders, { - 'Authorization': 'Bearer token', - }); - expect(playerId, 3); - expect( - player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), - isA(), - ); + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.httpHeaders, headers); }); test('create with file', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + when(api.create(any)).thenAnswer((_) async => 2); + + const String fileUri = 'file:///foo/bar'; final int? playerId = await player.create( - DataSource(sourceType: DataSourceType.file, uri: 'someUri'), + DataSource(sourceType: DataSourceType.file, uri: fileUri), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(playerId, 3); + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.uri, fileUri); expect( player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA(), ); }); - test('create with file (some headers)', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); - final int? playerId = await player.create( + test('create with file passes headers', () async { + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + when(api.create(any)).thenAnswer((_) async => 2); + + const String fileUri = 'file:///foo/bar'; + const Map headers = { + 'Authorization': 'Bearer token', + }; + await player.create( DataSource( sourceType: DataSourceType.file, - uri: 'someUri', - httpHeaders: {'Authorization': 'Bearer token'}, + uri: fileUri, + httpHeaders: headers, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(log.passedCreateMessage?.httpHeaders, { - 'Authorization': 'Bearer token', - }); - expect(playerId, 3); - expect( - player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), - isA(), - ); + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.httpHeaders, headers); }); test('createWithOptions with asset', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String asset = 'someAsset'; + const String package = 'somePackage'; final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.asset, - asset: 'someAsset', - package: 'somePackage', + asset: asset, + package: package, ), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.asset, 'someAsset'); - expect(log.passedCreateMessage?.packageName, 'somePackage'); - expect(playerId, 3); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.asset, asset); + expect(createMessage.packageName, package); + expect(playerId, newPlayerId); expect( - player.buildViewWithOptions(const VideoViewOptions(playerId: 3)), + player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA(), ); }); test('createWithOptions with network', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String uri = 'https://example.com'; final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.network, - uri: 'someUri', + uri: uri, formatHint: VideoFormat.dash, ), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.asset, null); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(log.passedCreateMessage?.packageName, null); - expect(log.passedCreateMessage?.formatHint, 'dash'); - expect(log.passedCreateMessage?.httpHeaders, {}); - expect(playerId, 3); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.asset, null); + expect(createMessage.uri, uri); + expect(createMessage.packageName, null); + expect(createMessage.formatHint, 'dash'); + expect(createMessage.httpHeaders, {}); + expect(playerId, newPlayerId); expect( player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA(), ); }); - test('createWithOptions with network (some headers)', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + test('createWithOptions with network passes headers', () async { + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const Map headers = { + 'Authorization': 'Bearer token', + }; final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.network, - uri: 'someUri', - httpHeaders: {'Authorization': 'Bearer token'}, + uri: 'https://example.com', + httpHeaders: headers, ), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.asset, null); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(log.passedCreateMessage?.packageName, null); - expect(log.passedCreateMessage?.formatHint, null); - expect(log.passedCreateMessage?.httpHeaders, { - 'Authorization': 'Bearer token', - }); - expect(playerId, 3); - expect( - player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), - isA(), - ); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.httpHeaders, headers); + expect(playerId, newPlayerId); }); test('createWithOptions with file', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + + const String fileUri = 'file:///foo/bar'; final int? playerId = await player.createWithOptions( VideoCreationOptions( - dataSource: DataSource( - sourceType: DataSourceType.file, - uri: 'someUri', - ), + dataSource: DataSource(sourceType: DataSourceType.file, uri: fileUri), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(playerId, 3); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.uri, fileUri); + expect(playerId, newPlayerId); expect( player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA(), ); }); - test('createWithOptions with file (some headers)', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); - final int? playerId = await player.createWithOptions( + test('createWithOptions with file passes headers', () async { + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + when(api.create(any)).thenAnswer((_) async => 2); + + const String fileUri = 'file:///foo/bar'; + const Map headers = { + 'Authorization': 'Bearer token', + }; + await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.file, - uri: 'someUri', - httpHeaders: {'Authorization': 'Bearer token'}, + uri: fileUri, + httpHeaders: headers, ), viewType: VideoViewType.textureView, ), ); - expect(log.log.last, 'create'); - expect(log.passedCreateMessage?.uri, 'someUri'); - expect(log.passedCreateMessage?.httpHeaders, { - 'Authorization': 'Bearer token', - }); - expect(playerId, 3); - expect( - player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), - isA(), - ); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.httpHeaders, headers); }); test('createWithOptions with platform view', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + const int newPlayerId = 2; + when(api.create(any)).thenAnswer((_) async => newPlayerId); + final int? playerId = await player.createWithOptions( VideoCreationOptions( dataSource: DataSource( sourceType: DataSourceType.file, - uri: 'someUri', + uri: 'file:///foo/bar', ), viewType: VideoViewType.platformView, ), ); - expect(log.log.last, 'create'); - expect( - log.passedCreateMessage?.viewType, - PlatformVideoViewType.platformView, - ); - expect(playerId, 3); + + final VerificationResult verification = verify(api.create(captureAny)); + final CreateMessage createMessage = + verification.captured[0] as CreateMessage; + expect(createMessage.viewType, PlatformVideoViewType.platformView); + expect(playerId, newPlayerId); expect( player.buildViewWithOptions(VideoViewOptions(playerId: playerId!)), isA(), @@ -331,6 +386,7 @@ void main() { test('setLooping', () async { final ( AndroidVideoPlayer player, + _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); await player.setLooping(1, true); @@ -341,6 +397,7 @@ void main() { test('play', () async { final ( AndroidVideoPlayer player, + _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); await player.play(1); @@ -351,6 +408,7 @@ void main() { test('pause', () async { final ( AndroidVideoPlayer player, + _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); await player.pause(1); @@ -358,20 +416,34 @@ void main() { verify(playerApi.pause()); }); - test('setMixWithOthers', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); - await player.setMixWithOthers(true); - expect(log.log.last, 'setMixWithOthers'); - expect(log.passedMixWithOthers, true); + group('setMixWithOthers', () { + test('passes true', () async { + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + await player.setMixWithOthers(true); - await player.setMixWithOthers(false); - expect(log.log.last, 'setMixWithOthers'); - expect(log.passedMixWithOthers, false); + verify(api.setMixWithOthers(true)); + }); + + test('passes false', () async { + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); + await player.setMixWithOthers(false); + + verify(api.setMixWithOthers(false)); + }); }); test('setVolume', () async { final ( AndroidVideoPlayer player, + _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); const double volume = 0.7; @@ -383,6 +455,7 @@ void main() { test('setPlaybackSpeed', () async { final ( AndroidVideoPlayer player, + _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); const double speed = 1.5; @@ -394,6 +467,7 @@ void main() { test('seekTo', () async { final ( AndroidVideoPlayer player, + _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); const int positionMilliseconds = 12345; @@ -408,6 +482,7 @@ void main() { test('getPosition', () async { final ( AndroidVideoPlayer player, + _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); const int positionMilliseconds = 12345; @@ -420,7 +495,11 @@ void main() { }); test('videoEventsFor', () async { - final (AndroidVideoPlayer player, _) = setUpMockPlayer(playerId: 1); + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: 1); const String mockChannel = 'flutter.io/videoPlayer/videoEvents123'; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMessageHandler(mockChannel, (ByteData? message) async { diff --git a/packages/video_player/video_player_android/test/android_video_player_test.mocks.dart b/packages/video_player/video_player_android/test/android_video_player_test.mocks.dart new file mode 100644 index 00000000000..e014eeb979a --- /dev/null +++ b/packages/video_player/video_player_android/test/android_video_player_test.mocks.dart @@ -0,0 +1,165 @@ +// Mocks generated by Mockito 5.4.6 from annotations +// in video_player_android/test/android_video_player_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; +import 'package:video_player_android/src/messages.g.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [AndroidVideoPlayerApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAndroidVideoPlayerApi extends _i1.Mock + implements _i2.AndroidVideoPlayerApi { + @override + String get pigeonVar_messageChannelSuffix => + (super.noSuchMethod( + Invocation.getter(#pigeonVar_messageChannelSuffix), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + returnValueForMissingStub: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + ) + as String); + + @override + _i4.Future initialize() => + (super.noSuchMethod( + Invocation.method(#initialize, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future create(_i2.CreateMessage? msg) => + (super.noSuchMethod( + Invocation.method(#create, [msg]), + returnValue: _i4.Future.value(0), + returnValueForMissingStub: _i4.Future.value(0), + ) + as _i4.Future); + + @override + _i4.Future dispose(int? playerId) => + (super.noSuchMethod( + Invocation.method(#dispose, [playerId]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setMixWithOthers(bool? mixWithOthers) => + (super.noSuchMethod( + Invocation.method(#setMixWithOthers, [mixWithOthers]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); +} + +/// A class which mocks [VideoPlayerInstanceApi]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVideoPlayerInstanceApi extends _i1.Mock + implements _i2.VideoPlayerInstanceApi { + @override + String get pigeonVar_messageChannelSuffix => + (super.noSuchMethod( + Invocation.getter(#pigeonVar_messageChannelSuffix), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + returnValueForMissingStub: _i3.dummyValue( + this, + Invocation.getter(#pigeonVar_messageChannelSuffix), + ), + ) + as String); + + @override + _i4.Future setLooping(bool? looping) => + (super.noSuchMethod( + Invocation.method(#setLooping, [looping]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setVolume(double? volume) => + (super.noSuchMethod( + Invocation.method(#setVolume, [volume]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setPlaybackSpeed(double? speed) => + (super.noSuchMethod( + Invocation.method(#setPlaybackSpeed, [speed]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future play() => + (super.noSuchMethod( + Invocation.method(#play, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future getPosition() => + (super.noSuchMethod( + Invocation.method(#getPosition, []), + returnValue: _i4.Future.value(0), + returnValueForMissingStub: _i4.Future.value(0), + ) + as _i4.Future); + + @override + _i4.Future seekTo(int? position) => + (super.noSuchMethod( + Invocation.method(#seekTo, [position]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future pause() => + (super.noSuchMethod( + Invocation.method(#pause, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); +} diff --git a/packages/video_player/video_player_android/test/test_api.g.dart b/packages/video_player/video_player_android/test/test_api.g.dart deleted file mode 100644 index 716ca5d5dd2..00000000000 --- a/packages/video_player/video_player_android/test/test_api.g.dart +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// Autogenerated from Pigeon (v22.7.4), do not edit directly. -// See also: https://pub.dev/packages/pigeon -// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers -// ignore_for_file: avoid_relative_lib_imports -import 'dart:async'; -import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; -import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:video_player_android/src/messages.g.dart'; - -class _PigeonCodec extends StandardMessageCodec { - const _PigeonCodec(); - @override - void writeValue(WriteBuffer buffer, Object? value) { - if (value is int) { - buffer.putUint8(4); - buffer.putInt64(value); - } else if (value is PlatformVideoViewType) { - buffer.putUint8(129); - writeValue(buffer, value.index); - } else if (value is PlatformVideoViewCreationParams) { - buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is CreateMessage) { - buffer.putUint8(131); - writeValue(buffer, value.encode()); - } else { - super.writeValue(buffer, value); - } - } - - @override - Object? readValueOfType(int type, ReadBuffer buffer) { - switch (type) { - case 129: - final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformVideoViewType.values[value]; - case 130: - return PlatformVideoViewCreationParams.decode(readValue(buffer)!); - case 131: - return CreateMessage.decode(readValue(buffer)!); - default: - return super.readValueOfType(type, buffer); - } - } -} - -abstract class TestHostVideoPlayerApi { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => - TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - - void initialize(); - - int create(CreateMessage msg); - - void dispose(int playerId); - - void setMixWithOthers(bool mixWithOthers); - - static void setUp( - TestHostVideoPlayerApi? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.initialize$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, ( - Object? message, - ) async { - try { - api.initialize(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException( - code: 'error', - message: e.toString(), - ), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.create$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, ( - Object? message, - ) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.create was null.', - ); - final List args = (message as List?)!; - final CreateMessage? arg_msg = (args[0] as CreateMessage?); - assert( - arg_msg != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.create was null, expected non-null CreateMessage.', - ); - try { - final int output = api.create(arg_msg!); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException( - code: 'error', - message: e.toString(), - ), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.dispose$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, ( - Object? message, - ) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.dispose was null.', - ); - final List args = (message as List?)!; - final int? arg_playerId = (args[0] as int?); - assert( - arg_playerId != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.dispose was null, expected non-null int.', - ); - try { - api.dispose(arg_playerId!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException( - code: 'error', - message: e.toString(), - ), - ); - } - }); - } - } - { - final BasicMessageChannel - pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger, - ); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, ( - Object? message, - ) async { - assert( - message != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers was null.', - ); - final List args = (message as List?)!; - final bool? arg_mixWithOthers = (args[0] as bool?); - assert( - arg_mixWithOthers != null, - 'Argument for dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers was null, expected non-null bool.', - ); - try { - api.setMixWithOthers(arg_mixWithOthers!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException( - code: 'error', - message: e.toString(), - ), - ); - } - }); - } - } - } -} From bc7452e221f738fa4bcd7912dfa65cb79f6198b7 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 27 Jun 2025 10:44:36 -0400 Subject: [PATCH 3/7] Pigeon update --- .../flutter/plugins/videoplayer/Messages.java | 2 +- .../lib/src/messages.g.dart | 113 +++++++++++++++--- .../video_player_android/pubspec.yaml | 2 +- 3 files changed, 100 insertions(+), 17 deletions(-) diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java index fade087a259..fe451b25da7 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.7.4), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.videoplayer; diff --git a/packages/video_player/video_player_android/lib/src/messages.g.dart b/packages/video_player/video_player_android/lib/src/messages.g.dart index 04c4ca3d636..07b3ae8e66f 100644 --- a/packages/video_player/video_player_android/lib/src/messages.g.dart +++ b/packages/video_player/video_player_android/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Autogenerated from Pigeon (v22.7.4), do not edit directly. +// Autogenerated from Pigeon (v25.5.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -32,6 +32,24 @@ List wrapResponse({ return [error.code, error.message, error.details]; } +bool _deepEquals(Object? a, Object? b) { + if (a is List && b is List) { + return a.length == b.length && + a.indexed.every( + ((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]), + ); + } + if (a is Map && b is Map) { + return a.length == b.length && + a.entries.every( + (MapEntry entry) => + (b as Map).containsKey(entry.key) && + _deepEquals(entry.value, b[entry.key]), + ); + } + return a == b; +} + /// Pigeon equivalent of VideoViewType. enum PlatformVideoViewType { textureView, platformView } @@ -41,14 +59,35 @@ class PlatformVideoViewCreationParams { int playerId; - Object encode() { + List _toList() { return [playerId]; } + Object encode() { + return _toList(); + } + static PlatformVideoViewCreationParams decode(Object result) { result as List; return PlatformVideoViewCreationParams(playerId: result[0]! as int); } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PlatformVideoViewCreationParams || + other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); } class CreateMessage { @@ -73,7 +112,7 @@ class CreateMessage { PlatformVideoViewType? viewType; - Object encode() { + List _toList() { return [ asset, uri, @@ -84,6 +123,10 @@ class CreateMessage { ]; } + Object encode() { + return _toList(); + } + static CreateMessage decode(Object result) { result as List; return CreateMessage( @@ -96,6 +139,22 @@ class CreateMessage { viewType: result[5] as PlatformVideoViewType?, ); } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! CreateMessage || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); } class _PigeonCodec extends StandardMessageCodec { @@ -160,8 +219,9 @@ class AndroidVideoPlayerApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -184,8 +244,11 @@ class AndroidVideoPlayerApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [msg], + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([msg]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -213,8 +276,11 @@ class AndroidVideoPlayerApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [playerId], + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([playerId]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -237,9 +303,11 @@ class AndroidVideoPlayerApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [mixWithOthers], + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([mixWithOthers]) - as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -279,8 +347,11 @@ class VideoPlayerInstanceApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [looping], + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([looping]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -303,8 +374,11 @@ class VideoPlayerInstanceApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [volume], + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([volume]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -327,8 +401,11 @@ class VideoPlayerInstanceApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [speed], + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([speed]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -351,8 +428,9 @@ class VideoPlayerInstanceApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -375,8 +453,9 @@ class VideoPlayerInstanceApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -404,8 +483,11 @@ class VideoPlayerInstanceApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [position], + ); final List? pigeonVar_replyList = - await pigeonVar_channel.send([position]) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -428,8 +510,9 @@ class VideoPlayerInstanceApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; + await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 2e7a20fb5ea..4742c35c567 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -27,7 +27,7 @@ dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.4 - pigeon: ^22.4.2 + pigeon: ^25.5.0 topics: - video From 18d5e4bac63d4e514f26ddc22ab44a540b755e19 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 27 Jun 2025 11:05:15 -0400 Subject: [PATCH 4/7] Version bump --- packages/video_player/video_player_android/CHANGELOG.md | 4 ++++ packages/video_player/video_player_android/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index e517227cb8e..b04a12bf74e 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.8.8 + +* Restructures the communication between Dart and Java code. + ## 2.8.7 * Adds note about known issue regarding platform views to the README. diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 4742c35c567..ac9dbf5901a 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player_android description: Android implementation of the video_player plugin. repository: https://github.com/flutter/packages/tree/main/packages/video_player/video_player_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+video_player%22 -version: 2.8.7 +version: 2.8.8 environment: sdk: ^3.7.0 From a16cf7e6858fa6916925852a58e73e2feacf54f7 Mon Sep 17 00:00:00 2001 From: stuartmorgan-g Date: Fri, 27 Jun 2025 11:26:28 -0400 Subject: [PATCH 5/7] Copypasta fix --- .../lib/src/android_video_player.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/video_player/video_player_android/lib/src/android_video_player.dart b/packages/video_player/video_player_android/lib/src/android_video_player.dart index 59be59eb7cf..d8ac9562fcc 100644 --- a/packages/video_player/video_player_android/lib/src/android_video_player.dart +++ b/packages/video_player/video_player_android/lib/src/android_video_player.dart @@ -12,14 +12,14 @@ import 'messages.g.dart'; import 'platform_view_player.dart'; /// The non-test implementation of `_apiProvider`. -VideoPlayerInstanceApi _productionApiProvider(int mapId) { - return VideoPlayerInstanceApi(messageChannelSuffix: mapId.toString()); +VideoPlayerInstanceApi _productionApiProvider(int playerId) { + return VideoPlayerInstanceApi(messageChannelSuffix: playerId.toString()); } /// An Android implementation of [VideoPlayerPlatform] that uses the /// Pigeon-generated [VideoPlayerApi]. class AndroidVideoPlayer extends VideoPlayerPlatform { - /// Creates a new Android maps implementation instance. + /// Creates a new Android video player implementation instance. AndroidVideoPlayer({ @visibleForTesting AndroidVideoPlayerApi? pluginApi, @visibleForTesting @@ -30,7 +30,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { final AndroidVideoPlayerApi _api; // A method to create VideoPlayerInstanceApi instances, which can be //overridden for testing. - final VideoPlayerInstanceApi Function(int mapId) _apiProvider; + final VideoPlayerInstanceApi Function(int playerId) _apiProvider; /// A map that associates player ID with a view state. /// This is used to determine which view type to use when building a view. From ec2ddc7fddc5598cc13f96b8297aaaa265e5d579 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 8 Jul 2025 15:32:48 -0400 Subject: [PATCH 6/7] Apply iOS review changes for consistency --- .../lib/src/android_video_player.dart | 42 ++++++++----------- .../test/android_video_player_test.dart | 2 +- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/packages/video_player/video_player_android/lib/src/android_video_player.dart b/packages/video_player/video_player_android/lib/src/android_video_player.dart index d8ac9562fcc..56a6d1b3524 100644 --- a/packages/video_player/video_player_android/lib/src/android_video_player.dart +++ b/packages/video_player/video_player_android/lib/src/android_video_player.dart @@ -23,21 +23,21 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { AndroidVideoPlayer({ @visibleForTesting AndroidVideoPlayerApi? pluginApi, @visibleForTesting - VideoPlayerInstanceApi Function(int playerId)? apiProvider, + VideoPlayerInstanceApi Function(int playerId)? playerProvider, }) : _api = pluginApi ?? AndroidVideoPlayerApi(), - _apiProvider = apiProvider ?? _productionApiProvider; + _playerProvider = playerProvider ?? _productionApiProvider; final AndroidVideoPlayerApi _api; // A method to create VideoPlayerInstanceApi instances, which can be //overridden for testing. - final VideoPlayerInstanceApi Function(int playerId) _apiProvider; + final VideoPlayerInstanceApi Function(int playerId) _playerProvider; /// A map that associates player ID with a view state. /// This is used to determine which view type to use when building a view. final Map _playerViewStates = {}; - final Map _playerApis = + final Map _players = {}; /// Registers this class as the default instance of [PathProviderPlatform]. @@ -54,7 +54,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { Future dispose(int playerId) async { await _api.dispose(playerId); _playerViewStates.remove(playerId); - _playerApis.remove(playerId); + _players.remove(playerId); } @override @@ -118,49 +118,46 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { /// exist. @visibleForTesting VideoPlayerInstanceApi ensureApiInitialized(int playerId) { - VideoPlayerInstanceApi? api = _playerApis[playerId]; - if (api == null) { - api = _apiProvider(playerId); - _playerApis[playerId] ??= api; - } - return api; + return _players.putIfAbsent(playerId, () { + return _playerProvider(playerId); + }); } @override Future setLooping(int playerId, bool looping) { - return _apiFor(playerId).setLooping(looping); + return _playerWith(id: playerId).setLooping(looping); } @override Future play(int playerId) { - return _apiFor(playerId).play(); + return _playerWith(id: playerId).play(); } @override Future pause(int playerId) { - return _apiFor(playerId).pause(); + return _playerWith(id: playerId).pause(); } @override Future setVolume(int playerId, double volume) { - return _apiFor(playerId).setVolume(volume); + return _playerWith(id: playerId).setVolume(volume); } @override Future setPlaybackSpeed(int playerId, double speed) { assert(speed > 0); - return _apiFor(playerId).setPlaybackSpeed(speed); + return _playerWith(id: playerId).setPlaybackSpeed(speed); } @override Future seekTo(int playerId, Duration position) { - return _apiFor(playerId).seekTo(position.inMilliseconds); + return _playerWith(id: playerId).seekTo(position.inMilliseconds); } @override Future getPosition(int playerId) async { - final int position = await _apiFor(playerId).getPosition(); + final int position = await _playerWith(id: playerId).getPosition(); return Duration(milliseconds: position); } @@ -236,12 +233,9 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return EventChannel('flutter.io/videoPlayer/videoEvents$playerId'); } - VideoPlayerInstanceApi _apiFor(int playerId) { - final VideoPlayerInstanceApi? api = _playerApis[playerId]; - if (api == null) { - throw StateError('No active player with ID $playerId.'); - } - return api; + VideoPlayerInstanceApi _playerWith({required int id}) { + final VideoPlayerInstanceApi? player = _players[id]; + return player ?? (throw StateError('No active player with ID $id.')); } static const Map _videoFormatStringMap = diff --git a/packages/video_player/video_player_android/test/android_video_player_test.dart b/packages/video_player/video_player_android/test/android_video_player_test.dart index 1c3bc6a428b..d590655927f 100644 --- a/packages/video_player/video_player_android/test/android_video_player_test.dart +++ b/packages/video_player/video_player_android/test/android_video_player_test.dart @@ -27,7 +27,7 @@ void main() { final MockVideoPlayerInstanceApi instanceApi = MockVideoPlayerInstanceApi(); final AndroidVideoPlayer player = AndroidVideoPlayer( pluginApi: pluginApi, - apiProvider: (_) => instanceApi, + playerProvider: (_) => instanceApi, ); player.ensureApiInitialized(playerId); return (player, pluginApi, instanceApi); From f41c1b1ca4e1f9e1757ec7d33ada8aa067116e47 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 16 Jul 2025 16:25:04 -0400 Subject: [PATCH 7/7] Gemini review feedback --- .../main/java/io/flutter/plugins/videoplayer/VideoPlayer.java | 2 +- .../java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java | 2 +- .../java/io/flutter/plugins/videoplayer/VideoPlayerTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index f9c558ef823..2c4876de6e0 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -117,7 +117,7 @@ public void setPlaybackSpeed(@NonNull Double speed) { @Override public void seekTo(@NonNull Long position) { - exoPlayer.seekTo(position.intValue()); + exoPlayer.seekTo(position); } @NonNull diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index 38dd9960fe5..79e09364295 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -145,7 +145,7 @@ public void initialize() { // Set up the instance-specific API handler, and make sure it is removed when the player is // disposed. BinaryMessenger messenger = flutterState.binaryMessenger; - final String channelSuffix = Integer.toString((int) id); + final String channelSuffix = Long.toString(id); VideoPlayerInstanceApi.setUp(messenger, channelSuffix, videoPlayer); videoPlayer.setDisposeHandler( () -> VideoPlayerInstanceApi.setUp(messenger, channelSuffix, null)); diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java index ef42438e908..86003fc4a30 100644 --- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java @@ -184,7 +184,7 @@ public void seekAndGetPosition() { verify(mockExoPlayer).seekTo(10); when(mockExoPlayer.getCurrentPosition()).thenReturn(20L); - assertEquals(20L, videoPlayer.getPosition().doubleValue(), 0.001); + assertEquals(20L, videoPlayer.getPosition().longValue()); } @Test