Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/video_player/video_player_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.8.9

* Restructures the communication between Dart and Java code.

## 2.8.8

* * Updates Media3-ExoPlayer to 1.5.1.
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (v25.5.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon

package io.flutter.plugins.videoplayer;
Expand Down Expand Up @@ -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<Object> getCodec() {
return PigeonCodec.INSTANCE;
}

/**
* Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the
* `binaryMessenger`.
Expand Down Expand Up @@ -493,18 +477,17 @@ static void setUp(
BasicMessageChannel<Object> 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) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) 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);
Expand All @@ -515,22 +498,59 @@ 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<Object> 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<Object> 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) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) 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);
Expand All @@ -545,18 +565,17 @@ static void setUp(
BasicMessageChannel<Object> 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) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) 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);
Expand All @@ -571,17 +590,17 @@ static void setUp(
BasicMessageChannel<Object> 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) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) 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);
Expand All @@ -596,18 +615,16 @@ static void setUp(
BasicMessageChannel<Object> 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<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) 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);
}
Expand All @@ -621,19 +638,16 @@ static void setUp(
BasicMessageChannel<Object> 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<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) 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);
}
Expand All @@ -647,17 +661,17 @@ static void setUp(
BasicMessageChannel<Object> 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) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) 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);
Expand All @@ -672,17 +686,15 @@ static void setUp(
BasicMessageChannel<Object> 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<Object> wrapped = new ArrayList<>();
ArrayList<Object> args = (ArrayList<Object>) message;
Boolean mixWithOthersArg = (Boolean) args.get(0);
try {
api.setMixWithOthers(mixWithOthersArg);
api.pause();
wrapped.add(0, null);
} catch (Throwable exception) {
wrapped = wrapError(exception);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
*
* <p>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. */
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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.
Copy link
Collaborator Author

@stuartmorgan-g stuartmorgan-g Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hack is pre-existing; it's just being moved from the VideoPlayerPlugin.java version of this method. I added the comment so that future people don't have to go through the same process I did of wondering why getting the position sends a buffer update as a side effect.

sendBufferingUpdate();
return position;
}

long getPosition() {
return exoPlayer.getCurrentPosition();
@Override
public void seekTo(@NonNull Long position) {
exoPlayer.seekTo(position.intValue());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The ExoPlayer.seekTo method expects a long value for the position in milliseconds. By casting position to an int using position.intValue(), you risk truncating the value if it exceeds Integer.MAX_VALUE (approximately 24.8 days). This can lead to incorrect seeking for very long videos.

You should pass the long value directly to seekTo.

Suggested change
public void seekTo(@NonNull Long position) {
exoPlayer.seekTo(position.intValue());
}
@Override
public void seekTo(@NonNull Long position) {
exoPlayer.seekTo(position);
}


@NonNull
Expand All @@ -105,6 +126,9 @@ public ExoPlayer getExoPlayer() {
}

public void dispose() {
if (disposeHandler != null) {
disposeHandler.onDispose();
}
exoPlayer.release();
}
}
Loading