From 5b227a497a31aa83ff31369e1105ab140b8e4ef6 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 6 Aug 2025 14:23:17 -0400 Subject: [PATCH 1/9] Add player class as an initial abstraction --- .../lib/src/android_video_player.dart | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 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 fb07e03f2a6..69e3eafe554 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 @@ -37,8 +37,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { final Map _playerViewStates = {}; - final Map _players = - {}; + final Map _players = {}; /// Registers this class as the default instance of [PathProviderPlatform]. static void registerWith() { @@ -134,12 +133,12 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return httpHeaders[userAgentKey] ?? defaultUserAgent; } - /// Returns the API instance for [playerId], creating it if it doesn't already - /// exist. + /// Returns the player instance for [playerId], creating it if it doesn't + /// already exist. @visibleForTesting - VideoPlayerInstanceApi ensureApiInitialized(int playerId) { - return _players.putIfAbsent(playerId, () { - return _playerProvider(playerId); + void ensureApiInitialized(int playerId) { + _players.putIfAbsent(playerId, () { + return _PlayerInstance(_playerProvider(playerId)); }); } @@ -251,8 +250,8 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return EventChannel('flutter.io/videoPlayer/videoEvents$playerId'); } - VideoPlayerInstanceApi _playerWith({required int id}) { - final VideoPlayerInstanceApi? player = _players[id]; + _PlayerInstance _playerWith({required int id}) { + final _PlayerInstance? player = _players[id]; return player ?? (throw StateError('No active player with ID $id.')); } @@ -280,6 +279,44 @@ PlatformVideoViewType _platformVideoViewTypeFromVideoViewType( }; } +/// An instance of a video player, corresponding to a single player ID in +/// [AndroidVideoPlayer]. +class _PlayerInstance { + /// Creates a new instance of [_PlayerInstance] corresponding to the given + /// API instance. + _PlayerInstance(this._api); + + final VideoPlayerInstanceApi _api; + + Future setLooping(bool looping) { + return _api.setLooping(looping); + } + + Future play() { + return _api.play(); + } + + Future pause() { + return _api.pause(); + } + + Future setVolume(double volume) { + return _api.setVolume(volume); + } + + Future setPlaybackSpeed(double speed) { + return _api.setPlaybackSpeed(speed); + } + + Future seekTo(int position) { + return _api.seekTo(position); + } + + Future getPosition() { + return _api.getPosition(); + } +} + /// Base class representing the state of a video player view. @immutable sealed class _VideoPlayerViewState { From fa87a011cc11fc0c72d24b8d061cce9e8c794ea5 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 6 Aug 2025 14:40:42 -0400 Subject: [PATCH 2/9] Fold view state into the instance --- .../lib/src/android_video_player.dart | 36 ++++++++----------- .../test/android_video_player_test.dart | 2 +- 2 files changed, 15 insertions(+), 23 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 69e3eafe554..2df75c0f113 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 @@ -32,11 +32,6 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { //overridden for testing. 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 _players = {}; /// Registers this class as the default instance of [PathProviderPlatform]. @@ -52,7 +47,6 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { @override Future dispose(int playerId) async { await _api.dispose(playerId); - _playerViewStates.remove(playerId); _players.remove(playerId); } @@ -108,14 +102,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { ); final int playerId = await _api.create(message); - _playerViewStates[playerId] = switch (options.viewType) { - // playerId is also the textureId when using texture view. - VideoViewType.textureView => _VideoPlayerTextureViewState( - textureId: playerId, - ), - VideoViewType.platformView => const _VideoPlayerPlatformViewState(), - }; - ensureApiInitialized(playerId); + ensureApiInitialized(playerId, options.viewType); return playerId; } @@ -136,9 +123,16 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { /// Returns the player instance for [playerId], creating it if it doesn't /// already exist. @visibleForTesting - void ensureApiInitialized(int playerId) { + void ensureApiInitialized(int playerId, VideoViewType viewType) { _players.putIfAbsent(playerId, () { - return _PlayerInstance(_playerProvider(playerId)); + final _VideoPlayerViewState viewState = switch (viewType) { + // playerId is also the textureId when using texture view. + VideoViewType.textureView => _VideoPlayerTextureViewState( + textureId: playerId, + ), + VideoViewType.platformView => const _VideoPlayerPlatformViewState(), + }; + return _PlayerInstance(_playerProvider(playerId), viewState); }); } @@ -227,17 +221,13 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { @override Widget buildViewWithOptions(VideoViewOptions options) { final int playerId = options.playerId; - final _VideoPlayerViewState? viewState = _playerViewStates[playerId]; + final _VideoPlayerViewState viewState = _playerWith(id: playerId).viewState; return switch (viewState) { _VideoPlayerTextureViewState(:final int textureId) => Texture( textureId: textureId, ), _VideoPlayerPlatformViewState() => PlatformViewPlayer(playerId: playerId), - null => - throw Exception( - 'Could not find corresponding view type for playerId: $playerId', - ), }; } @@ -284,10 +274,12 @@ PlatformVideoViewType _platformVideoViewTypeFromVideoViewType( class _PlayerInstance { /// Creates a new instance of [_PlayerInstance] corresponding to the given /// API instance. - _PlayerInstance(this._api); + _PlayerInstance(this._api, this.viewState); final VideoPlayerInstanceApi _api; + final _VideoPlayerViewState viewState; + Future setLooping(bool looping) { return _api.setLooping(looping); } 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 c544da8227c..0d431622322 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 @@ -29,7 +29,7 @@ void main() { pluginApi: pluginApi, playerProvider: (_) => instanceApi, ); - player.ensureApiInitialized(playerId); + player.ensureApiInitialized(playerId, VideoViewType.platformView); return (player, pluginApi, instanceApi); } From 2f3a4df41bc0da46d0b20432bbdd9d0ed0ebebff Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 Aug 2025 10:35:54 -0400 Subject: [PATCH 3/9] Move event stream to the instance class --- .../lib/src/android_video_player.dart | 112 +++++++++++------- .../test/android_video_player_test.dart | 19 +-- 2 files changed, 82 insertions(+), 49 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 2df75c0f113..6d8dda0aad2 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 @@ -46,8 +46,9 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { @override Future dispose(int playerId) async { + final _PlayerInstance? player = _players.remove(playerId); await _api.dispose(playerId); - _players.remove(playerId); + await player?.dispose(); } @override @@ -132,7 +133,13 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { ), VideoViewType.platformView => const _VideoPlayerPlatformViewState(), }; - return _PlayerInstance(_playerProvider(playerId), viewState); + final String eventChannelName = + 'flutter.io/videoPlayer/videoEvents$playerId'; + return _PlayerInstance( + _playerProvider(playerId), + viewState, + eventChannelName: eventChannelName, + ); }); } @@ -176,41 +183,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { @override Stream videoEventsFor(int playerId) { - return _eventChannelFor(playerId).receiveBroadcastStream().map(( - dynamic event, - ) { - final Map map = event as Map; - return switch (map['event']) { - 'initialized' => VideoEvent( - eventType: VideoEventType.initialized, - duration: Duration(milliseconds: map['duration'] as int), - size: Size( - (map['width'] as num?)?.toDouble() ?? 0.0, - (map['height'] as num?)?.toDouble() ?? 0.0, - ), - rotationCorrection: map['rotationCorrection'] as int? ?? 0, - ), - 'completed' => VideoEvent(eventType: VideoEventType.completed), - 'bufferingUpdate' => VideoEvent( - eventType: VideoEventType.bufferingUpdate, - buffered: [ - DurationRange( - Duration.zero, - Duration(milliseconds: map['position'] as int), - ), - ], - ), - 'bufferingStart' => VideoEvent( - eventType: VideoEventType.bufferingStart, - ), - 'bufferingEnd' => VideoEvent(eventType: VideoEventType.bufferingEnd), - 'isPlayingStateUpdate' => VideoEvent( - eventType: VideoEventType.isPlayingStateUpdate, - isPlaying: map['isPlaying'] as bool, - ), - _ => VideoEvent(eventType: VideoEventType.unknown), - }; - }); + return _playerWith(id: playerId).videoEvents(); } @override @@ -236,10 +209,6 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { return _api.setMixWithOthers(mixWithOthers); } - EventChannel _eventChannelFor(int playerId) { - return EventChannel('flutter.io/videoPlayer/videoEvents$playerId'); - } - _PlayerInstance _playerWith({required int id}) { final _PlayerInstance? player = _players[id]; return player ?? (throw StateError('No active player with ID $id.')); @@ -274,9 +243,25 @@ PlatformVideoViewType _platformVideoViewTypeFromVideoViewType( class _PlayerInstance { /// Creates a new instance of [_PlayerInstance] corresponding to the given /// API instance. - _PlayerInstance(this._api, this.viewState); + _PlayerInstance( + this._api, + this.viewState, { + required String eventChannelName, + }) { + _eventChannel = EventChannel(eventChannelName); + _eventSubscription = _eventChannel.receiveBroadcastStream().listen( + _onStreamEvent, + onError: (Object e) { + _eventStreamController.addError(e); + }, + ); + } final VideoPlayerInstanceApi _api; + late final EventChannel _eventChannel; + final StreamController _eventStreamController = + StreamController.broadcast(); + late final StreamSubscription _eventSubscription; final _VideoPlayerViewState viewState; @@ -307,6 +292,49 @@ class _PlayerInstance { Future getPosition() { return _api.getPosition(); } + + Stream videoEvents() { + return _eventStreamController.stream; + } + + Future dispose() async { + await _eventSubscription.cancel(); + } + + void _onStreamEvent(dynamic event) { + final Map map = event as Map; + _eventStreamController.add(switch (map['event']) { + 'initialized' => VideoEvent( + eventType: VideoEventType.initialized, + duration: Duration(milliseconds: map['duration'] as int), + size: Size( + (map['width'] as num?)?.toDouble() ?? 0.0, + (map['height'] as num?)?.toDouble() ?? 0.0, + ), + rotationCorrection: map['rotationCorrection'] as int? ?? 0, + ), + 'completed' => VideoEvent(eventType: VideoEventType.completed), + 'bufferingUpdate' => VideoEvent( + eventType: VideoEventType.bufferingUpdate, + buffered: _bufferRangeForPosition(map['position'] as int), + ), + 'bufferingStart' => VideoEvent(eventType: VideoEventType.bufferingStart), + 'bufferingEnd' => VideoEvent(eventType: VideoEventType.bufferingEnd), + 'isPlayingStateUpdate' => VideoEvent( + eventType: VideoEventType.isPlayingStateUpdate, + isPlaying: map['isPlaying'] as bool, + ), + _ => VideoEvent(eventType: VideoEventType.unknown), + }); + } + + // Turns a single buffer position, which is what ExoPlayer reports, into the + // DurationRange array expected by [VideoEventType.bufferingUpdate]. + List _bufferRangeForPosition(int milliseconds) { + return [ + DurationRange(Duration.zero, Duration(milliseconds: milliseconds)), + ]; + } } /// Base class representing the state of a video player view. 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 0d431622322..67b42bcf246 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 @@ -545,12 +545,8 @@ void main() { }); test('videoEventsFor', () async { - final ( - AndroidVideoPlayer player, - MockAndroidVideoPlayerApi api, - _, - ) = setUpMockPlayer(playerId: 1); - const String mockChannel = 'flutter.io/videoPlayer/videoEvents123'; + const int playerId = 1; + const String mockChannel = 'flutter.io/videoPlayer/videoEvents$playerId'; TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMessageHandler(mockChannel, (ByteData? message) async { final MethodCall methodCall = const StandardMethodCodec() @@ -669,8 +665,17 @@ void main() { fail('Expected listen or cancel'); } }); + + // Creating the player triggers the stream listener, so that must be done + // after setting up the mock native handler above. + final ( + AndroidVideoPlayer player, + MockAndroidVideoPlayerApi api, + _, + ) = setUpMockPlayer(playerId: playerId); + expect( - player.videoEventsFor(123), + player.videoEventsFor(playerId), emitsInOrder([ VideoEvent( eventType: VideoEventType.initialized, From 1f884572d4586ac41b828a663878f37380d1a657 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 Aug 2025 13:03:18 -0400 Subject: [PATCH 4/9] Move buffer update event synthesis to Dart --- .../flutter/plugins/videoplayer/Messages.java | 130 ++++++++++++++++-- .../plugins/videoplayer/VideoPlayer.java | 11 +- .../plugins/videoplayer/VideoPlayerTest.java | 32 +++-- .../lib/src/android_video_player.dart | 38 ++++- .../lib/src/messages.g.dart | 85 +++++++++--- .../pigeons/messages.dart | 17 ++- .../test/android_video_player_test.dart | 11 +- .../test/android_video_player_test.mocks.dart | 33 +++-- 8 files changed, 286 insertions(+), 71 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 c98f787d318..e482143effe 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 @@ -324,6 +324,100 @@ ArrayList toList() { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class PlaybackState { + /** The current playback position, in milliseconds. */ + private @NonNull Long playPosition; + + public @NonNull Long getPlayPosition() { + return playPosition; + } + + public void setPlayPosition(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"playPosition\" is null."); + } + this.playPosition = setterArg; + } + + /** The current buffer position, in milliseconds. */ + private @NonNull Long bufferPosition; + + public @NonNull Long getBufferPosition() { + return bufferPosition; + } + + public void setBufferPosition(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"bufferPosition\" is null."); + } + this.bufferPosition = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlaybackState() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlaybackState that = (PlaybackState) o; + return playPosition.equals(that.playPosition) && bufferPosition.equals(that.bufferPosition); + } + + @Override + public int hashCode() { + return Objects.hash(playPosition, bufferPosition); + } + + public static final class Builder { + + private @Nullable Long playPosition; + + @CanIgnoreReturnValue + public @NonNull Builder setPlayPosition(@NonNull Long setterArg) { + this.playPosition = setterArg; + return this; + } + + private @Nullable Long bufferPosition; + + @CanIgnoreReturnValue + public @NonNull Builder setBufferPosition(@NonNull Long setterArg) { + this.bufferPosition = setterArg; + return this; + } + + public @NonNull PlaybackState build() { + PlaybackState pigeonReturn = new PlaybackState(); + pigeonReturn.setPlayPosition(playPosition); + pigeonReturn.setBufferPosition(bufferPosition); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(2); + toListResult.add(playPosition); + toListResult.add(bufferPosition); + return toListResult; + } + + static @NonNull PlaybackState fromList(@NonNull ArrayList pigeonVar_list) { + PlaybackState pigeonResult = new PlaybackState(); + Object playPosition = pigeonVar_list.get(0); + pigeonResult.setPlayPosition((Long) playPosition); + Object bufferPosition = pigeonVar_list.get(1); + pigeonResult.setBufferPosition((Long) bufferPosition); + return pigeonResult; + } + } + private static class PigeonCodec extends StandardMessageCodec { public static final PigeonCodec INSTANCE = new PigeonCodec(); @@ -346,6 +440,8 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { return PlatformVideoViewCreationParams.fromList((ArrayList) readValue(buffer)); case (byte) 132: return CreateMessage.fromList((ArrayList) readValue(buffer)); + case (byte) 133: + return PlaybackState.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -365,6 +461,9 @@ protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { } else if (value instanceof CreateMessage) { stream.write(132); writeValue(stream, ((CreateMessage) value).toList()); + } else if (value instanceof PlaybackState) { + stream.write(133); + writeValue(stream, ((PlaybackState) value).toList()); } else { super.writeValue(stream, value); } @@ -541,12 +640,17 @@ public interface VideoPlayerInstanceApi { void play(); - @NonNull - Long getPosition(); - void seekTo(@NonNull Long position); void pause(); + /** + * Returns the current playback state. + * + *

This is combined into a single call to minimize platform channel calls for state that + * needs to be polled frequently. + */ + @NonNull + PlaybackState getPlaybackState(); /** The codec used by VideoPlayerInstanceApi. */ static @NonNull MessageCodec getCodec() { @@ -668,16 +772,18 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getPosition" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + Long positionArg = (Long) args.get(0); try { - Long output = api.getPosition(); - wrapped.add(0, output); + api.seekTo(positionArg); + wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); } @@ -691,17 +797,15 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo" + "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; - Long positionArg = (Long) args.get(0); try { - api.seekTo(positionArg); + api.pause(); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); @@ -716,7 +820,7 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getPlaybackState" + messageChannelSuffix, getCodec()); if (api != null) { @@ -724,8 +828,8 @@ static void setUp( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); try { - api.pause(); - wrapped.add(0, null); + PlaybackState output = api.getPlaybackState(); + wrapped.add(0, output); } 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 2c4876de6e0..0e371d83427 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 @@ -107,12 +107,11 @@ public void setPlaybackSpeed(@NonNull Double speed) { } @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; + public @NonNull Messages.PlaybackState getPlaybackState() { + return new Messages.PlaybackState.Builder() + .setPlayPosition(exoPlayer.getCurrentPosition()) + .setBufferPosition(exoPlayer.getBufferedPosition()) + .build(); } @Override 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 86003fc4a30..ce58579f0bc 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 @@ -126,17 +126,6 @@ public void playsAndPausesProvidedMedia() { videoPlayer.dispose(); } - @Test - public void sendsBufferingUpdatesOnDemand() { - VideoPlayer videoPlayer = createVideoPlayer(); - - when(mockExoPlayer.getBufferedPosition()).thenReturn(10L); - videoPlayer.sendBufferingUpdate(); - verify(mockEvents).onBufferingUpdate(10L); - - videoPlayer.dispose(); - } - @Test public void togglesLoopingEnablesAndDisablesRepeatMode() { VideoPlayer videoPlayer = createVideoPlayer(); @@ -177,14 +166,29 @@ public void setPlaybackSpeedSetsPlaybackParametersWithValue() { } @Test - public void seekAndGetPosition() { + public void seekTo() { VideoPlayer videoPlayer = createVideoPlayer(); videoPlayer.seekTo(10L); verify(mockExoPlayer).seekTo(10); - when(mockExoPlayer.getCurrentPosition()).thenReturn(20L); - assertEquals(20L, videoPlayer.getPosition().longValue()); + videoPlayer.dispose(); + } + + @Test + public void GetPlaybackState() { + VideoPlayer videoPlayer = createVideoPlayer(); + + final long playbackPosition = 20L; + final long bufferedPosition = 10L; + when(mockExoPlayer.getCurrentPosition()).thenReturn(playbackPosition); + when(mockExoPlayer.getBufferedPosition()).thenReturn(bufferedPosition); + + final Messages.PlaybackState state = videoPlayer.getPlaybackState(); + assertEquals(playbackPosition, state.getPlayPosition().longValue()); + assertEquals(bufferedPosition, state.getBufferPosition().longValue()); + + videoPlayer.dispose(); } @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 6d8dda0aad2..9736ccba817 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 @@ -172,13 +172,12 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { @override Future seekTo(int playerId, Duration position) { - return _playerWith(id: playerId).seekTo(position.inMilliseconds); + return _playerWith(id: playerId).seekTo(position); } @override Future getPosition(int playerId) async { - final int position = await _playerWith(id: playerId).getPosition(); - return Duration(milliseconds: position); + return _playerWith(id: playerId).getPosition(); } @override @@ -262,6 +261,7 @@ class _PlayerInstance { final StreamController _eventStreamController = StreamController.broadcast(); late final StreamSubscription _eventSubscription; + int _lastBufferPosition = -1; final _VideoPlayerViewState viewState; @@ -285,12 +285,22 @@ class _PlayerInstance { return _api.setPlaybackSpeed(speed); } - Future seekTo(int position) { - return _api.seekTo(position); + Future seekTo(Duration position) { + return _api.seekTo(position.inMilliseconds); } - Future getPosition() { - return _api.getPosition(); + Future getPosition() async { + final PlaybackState state = await _api.getPlaybackState(); + // TODO(stuartmorgan): Move this logic. This is a workaround for the fact + // that ExoPlayer doesn't have any way to observe buffer position + // changes, so polling is required. To minimize platform channel overhead, + // that's combined with getting the position, but this relies on the fact + // that the app-facing package polls getPosition frequently, which makes + // this fragile (for instance, as of writing, this won't be called while + // the video is paused). It should instead be called on its own timer, + // independent of higher-level package logic. + _updateBufferingState(state.bufferPosition); + return Duration(milliseconds: state.playPosition); } Stream videoEvents() { @@ -301,6 +311,20 @@ class _PlayerInstance { await _eventSubscription.cancel(); } + /// Sends a buffering update if the buffer position has changed since the + /// last check. + void _updateBufferingState(int bufferPosition) { + if (bufferPosition != _lastBufferPosition) { + _lastBufferPosition = bufferPosition; + _eventStreamController.add( + VideoEvent( + eventType: VideoEventType.bufferingUpdate, + buffered: _bufferRangeForPosition(bufferPosition), + ), + ); + } + } + void _onStreamEvent(dynamic event) { final Map map = event as Map; _eventStreamController.add(switch (map['event']) { 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 e5921e61175..207f8aa2730 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 @@ -135,6 +135,48 @@ class CreateMessage { int get hashCode => Object.hashAll(_toList()); } +class PlaybackState { + PlaybackState({required this.playPosition, required this.bufferPosition}); + + /// The current playback position, in milliseconds. + int playPosition; + + /// The current buffer position, in milliseconds. + int bufferPosition; + + List _toList() { + return [playPosition, bufferPosition]; + } + + Object encode() { + return _toList(); + } + + static PlaybackState decode(Object result) { + result as List; + return PlaybackState( + playPosition: result[0]! as int, + bufferPosition: result[1]! as int, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PlaybackState || 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 { const _PigeonCodec(); @override @@ -154,6 +196,9 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is CreateMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); + } else if (value is PlaybackState) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -172,6 +217,8 @@ class _PigeonCodec extends StandardMessageCodec { return PlatformVideoViewCreationParams.decode(readValue(buffer)!); case 132: return CreateMessage.decode(readValue(buffer)!); + case 133: + return PlaybackState.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -460,16 +507,18 @@ class VideoPlayerInstanceApi { } } - Future getPosition() async { + Future seekTo(int position) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getPosition$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [position], + ); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -480,28 +529,21 @@ class VideoPlayerInstanceApi { 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 position) async { + Future pause() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [position], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -517,9 +559,13 @@ class VideoPlayerInstanceApi { } } - Future pause() async { + /// Returns the current playback state. + /// + /// This is combined into a single call to minimize platform channel calls for + /// state that needs to be polled frequently. + Future getPlaybackState() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getPlaybackState$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, @@ -537,8 +583,13 @@ class VideoPlayerInstanceApi { 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 PlaybackState?)!; } } } diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index fc1f601bf29..f71d6a0fa85 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -35,6 +35,16 @@ class CreateMessage { PlatformVideoViewType? viewType; } +class PlaybackState { + PlaybackState({required this.playPosition, required this.bufferPosition}); + + /// The current playback position, in milliseconds. + final int playPosition; + + /// The current buffer position, in milliseconds. + final int bufferPosition; +} + @HostApi() abstract class AndroidVideoPlayerApi { void initialize(); @@ -50,7 +60,12 @@ abstract class VideoPlayerInstanceApi { void setVolume(double volume); void setPlaybackSpeed(double speed); void play(); - int getPosition(); void seekTo(int position); void pause(); + + /// Returns the current playback state. + /// + /// This is combined into a single call to minimize platform channel calls for + /// state that needs to be polled frequently. + PlaybackState getPlaybackState(); } 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 67b42bcf246..ac32c50496d 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 @@ -529,16 +529,19 @@ void main() { verify(playerApi.seekTo(positionMilliseconds)); }); - test('getPosition', () async { + test('getPlaybackState', () async { final ( AndroidVideoPlayer player, _, MockVideoPlayerInstanceApi playerApi, ) = setUpMockPlayer(playerId: 1); const int positionMilliseconds = 12345; - when( - playerApi.getPosition(), - ).thenAnswer((_) async => positionMilliseconds); + when(playerApi.getPlaybackState()).thenAnswer( + (_) async => PlaybackState( + playPosition: positionMilliseconds, + bufferPosition: 0, + ), + ); final Duration position = await player.getPosition(1); expect(position, const Duration(milliseconds: positionMilliseconds)); 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 index 68be21719be..4d7f49d18de 100644 --- 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 @@ -23,6 +23,11 @@ import 'package:video_player_android/src/messages.g.dart' as _i2; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class +class _FakePlaybackState_0 extends _i1.SmartFake implements _i2.PlaybackState { + _FakePlaybackState_0(Object parent, Invocation parentInvocation) + : super(parent, parentInvocation); +} + /// A class which mocks [AndroidVideoPlayerApi]. /// /// See the documentation for Mockito's code generation for more information. @@ -155,15 +160,6 @@ class MockVideoPlayerInstanceApi extends _i1.Mock ) 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( @@ -181,4 +177,23 @@ class MockVideoPlayerInstanceApi extends _i1.Mock returnValueForMissingStub: _i4.Future.value(), ) as _i4.Future); + + @override + _i4.Future<_i2.PlaybackState> getPlaybackState() => + (super.noSuchMethod( + Invocation.method(#getPlaybackState, []), + returnValue: _i4.Future<_i2.PlaybackState>.value( + _FakePlaybackState_0( + this, + Invocation.method(#getPlaybackState, []), + ), + ), + returnValueForMissingStub: _i4.Future<_i2.PlaybackState>.value( + _FakePlaybackState_0( + this, + Invocation.method(#getPlaybackState, []), + ), + ), + ) + as _i4.Future<_i2.PlaybackState>); } From fc1cd2a2815642801ed8ac5ce9170a9d25ae4d90 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 Aug 2025 13:23:17 -0400 Subject: [PATCH 5/9] 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 05ad6a287c3..b3a7a3c4f1c 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.11 + +* Moves buffer position update event generation to Dart. + ## 2.8.10 * Restructures internal logic to move more code to Dart. diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 5d6281f5494..a9e239057b8 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.10 +version: 2.8.11 environment: sdk: ^3.7.0 From 395b7ba736321dd4af4e7836ce719cced331a411 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 Aug 2025 13:25:42 -0400 Subject: [PATCH 6/9] Remove unused method --- .../main/java/io/flutter/plugins/videoplayer/VideoPlayer.java | 4 ---- 1 file changed, 4 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 0e371d83427..27dc9e95609 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 @@ -66,10 +66,6 @@ public void setDisposeHandler(@Nullable DisposeHandler handler) { protected abstract ExoPlayerEventListener createExoPlayerEventListener( @NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer); - void sendBufferingUpdate() { - videoPlayerEvents.onBufferingUpdate(exoPlayer.getBufferedPosition()); - } - private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) { exoPlayer.setAudioAttributes( new AudioAttributes.Builder().setContentType(C.AUDIO_CONTENT_TYPE_MOVIE).build(), From 796ab140a5b4cd73622536864639d2d0fa964e05 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Thu, 7 Aug 2025 13:46:47 -0400 Subject: [PATCH 7/9] Fix capitalization --- .../java/io/flutter/plugins/videoplayer/VideoPlayerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ce58579f0bc..f6dd319500b 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 @@ -176,7 +176,7 @@ public void seekTo() { } @Test - public void GetPlaybackState() { + public void getPlaybackState() { VideoPlayer videoPlayer = createVideoPlayer(); final long playbackPosition = 20L; From 93f36e32b2a28228c9ae8d678411fda4cb037616 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Wed, 13 Aug 2025 16:32:43 -0400 Subject: [PATCH 8/9] Switch to a non-broadcast stream to keep queue behavior --- .../video_player_android/lib/src/android_video_player.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9736ccba817..f25fecaf1b2 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 @@ -259,7 +259,7 @@ class _PlayerInstance { final VideoPlayerInstanceApi _api; late final EventChannel _eventChannel; final StreamController _eventStreamController = - StreamController.broadcast(); + StreamController(); late final StreamSubscription _eventSubscription; int _lastBufferPosition = -1; From c328f2597a269e7086550d7e77292f949e19e58b Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Fri, 15 Aug 2025 10:39:43 -0400 Subject: [PATCH 9/9] Review feedback --- .../flutter/plugins/videoplayer/Messages.java | 26 +++++++++---------- .../lib/src/android_video_player.dart | 9 +++++-- .../lib/src/messages.g.dart | 22 ++++++++++------ .../pigeons/messages.dart | 13 +++++++++- 4 files changed, 46 insertions(+), 24 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 e482143effe..ffd89e6137e 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 @@ -631,18 +631,18 @@ static void setUp( } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface VideoPlayerInstanceApi { - + /** Sets whether to automatically loop playback of the video. */ void setLooping(@NonNull Boolean looping); - + /** Sets the volume, with 0.0 being muted and 1.0 being full volume. */ void setVolume(@NonNull Double volume); - + /** Sets the playback speed as a multiple of normal speed. */ void setPlaybackSpeed(@NonNull Double speed); - + /** Begins playback if the video is not currently playing. */ void play(); - - void seekTo(@NonNull Long position); - + /** Pauses playback if the video is currently playing. */ void pause(); + /** Seeks to the given playback position, in milliseconds. */ + void seekTo(@NonNull Long position); /** * Returns the current playback state. * @@ -772,17 +772,15 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo" + "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; - Long positionArg = (Long) args.get(0); try { - api.seekTo(positionArg); + api.pause(); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); @@ -797,15 +795,17 @@ static void setUp( BasicMessageChannel channel = new BasicMessageChannel<>( binaryMessenger, - "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause" + "dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo" + messageChannelSuffix, getCodec()); if (api != null) { channel.setMessageHandler( (message, reply) -> { ArrayList wrapped = new ArrayList<>(); + ArrayList args = (ArrayList) message; + Long positionArg = (Long) args.get(0); try { - api.pause(); + api.seekTo(positionArg); wrapped.add(0, null); } catch (Throwable exception) { wrapped = wrapError(exception); 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 f25fecaf1b2..a3f147c31de 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,6 +11,12 @@ import 'package:video_player_platform_interface/video_player_platform_interface. import 'messages.g.dart'; import 'platform_view_player.dart'; +/// The string to append a player ID to in order to construct the event channel +/// name for the event channel used to receive player state updates. +/// +/// Must match the string used to create the EventChannel on the Java side. +const String _videoEventChannelNameBase = 'flutter.io/videoPlayer/videoEvents'; + /// The non-test implementation of `_apiProvider`. VideoPlayerInstanceApi _productionApiProvider(int playerId) { return VideoPlayerInstanceApi(messageChannelSuffix: playerId.toString()); @@ -133,8 +139,7 @@ class AndroidVideoPlayer extends VideoPlayerPlatform { ), VideoViewType.platformView => const _VideoPlayerPlatformViewState(), }; - final String eventChannelName = - 'flutter.io/videoPlayer/videoEvents$playerId'; + final String eventChannelName = '$_videoEventChannelNameBase$playerId'; return _PlayerInstance( _playerProvider(playerId), viewState, 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 207f8aa2730..e576b0336a4 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 @@ -401,6 +401,7 @@ class VideoPlayerInstanceApi { final String pigeonVar_messageChannelSuffix; + /// Sets whether to automatically loop playback of the video. Future setLooping(bool looping) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping$pigeonVar_messageChannelSuffix'; @@ -428,6 +429,7 @@ class VideoPlayerInstanceApi { } } + /// Sets the volume, with 0.0 being muted and 1.0 being full volume. Future setVolume(double volume) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume$pigeonVar_messageChannelSuffix'; @@ -455,6 +457,7 @@ class VideoPlayerInstanceApi { } } + /// Sets the playback speed as a multiple of normal speed. Future setPlaybackSpeed(double speed) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed$pigeonVar_messageChannelSuffix'; @@ -482,6 +485,7 @@ class VideoPlayerInstanceApi { } } + /// Begins playback if the video is not currently playing. Future play() async { final String pigeonVar_channelName = 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play$pigeonVar_messageChannelSuffix'; @@ -507,18 +511,17 @@ class VideoPlayerInstanceApi { } } - Future seekTo(int position) async { + /// Pauses playback if the video is currently playing. + Future pause() async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send( - [position], - ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { @@ -534,16 +537,19 @@ class VideoPlayerInstanceApi { } } - Future pause() async { + /// Seeks to the given playback position, in milliseconds. + Future seekTo(int position) async { final String pigeonVar_channelName = - 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause$pigeonVar_messageChannelSuffix'; + 'dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final Future pigeonVar_sendFuture = pigeonVar_channel.send( + [position], + ); final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; if (pigeonVar_replyList == null) { diff --git a/packages/video_player/video_player_android/pigeons/messages.dart b/packages/video_player/video_player_android/pigeons/messages.dart index f71d6a0fa85..b2246ec6d33 100644 --- a/packages/video_player/video_player_android/pigeons/messages.dart +++ b/packages/video_player/video_player_android/pigeons/messages.dart @@ -56,13 +56,24 @@ abstract class AndroidVideoPlayerApi { @HostApi() abstract class VideoPlayerInstanceApi { + /// Sets whether to automatically loop playback of the video. void setLooping(bool looping); + + /// Sets the volume, with 0.0 being muted and 1.0 being full volume. void setVolume(double volume); + + /// Sets the playback speed as a multiple of normal speed. void setPlaybackSpeed(double speed); + + /// Begins playback if the video is not currently playing. void play(); - void seekTo(int position); + + /// Pauses playback if the video is currently playing. void pause(); + /// Seeks to the given playback position, in milliseconds. + void seekTo(int position); + /// Returns the current playback state. /// /// This is combined into a single call to minimize platform channel calls for