Skip to content

Commit f2ff383

Browse files
[video_player] Move Android to per-player-instance Pigeon APIs (#9511)
Rather than having a single API to talk directly to the plugin, and having most of the methods in that API just do a map lookup and dispatch to a player instance, which was necessary when Pigeon didn't support instantiating multiple instances of an API, have each player instance set up an API instance that the Dart code can talk to directly. This follows the pattern used by plugins that migrated to Pigeon more recently (e.g., `google_maps_flutter` has a very similar pattern), reduces boilerplate native code, and moves closer to what an FFI-based implementation would look like if we go that route in the future. Since the Dart unit tests needed to be significantly reworked anyway, this also moves to the pattern we are using in newer plugin code, where we use `mockito` to mock the Pigeon API surface. The "call log" approach it replaces dates back to pre-Pigeon, when only way to test that the right platform calls were made was to intercept and track method channel calls at the framework level. Also updates to the latest version of Pigeon. ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 2fdb7b0 commit f2ff383

File tree

12 files changed

+800
-937
lines changed

12 files changed

+800
-937
lines changed

packages/video_player/video_player_android/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.8.9
2+
3+
* Restructures the communication between Dart and Java code.
4+
15
## 2.8.8
26

37
* * Updates Media3-ExoPlayer to 1.5.1.

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/Messages.java

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright 2013 The Flutter Authors. All rights reserved.
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
4-
// Autogenerated from Pigeon (v22.6.1), do not edit directly.
4+
// Autogenerated from Pigeon (v25.5.0), do not edit directly.
55
// See also: https://pub.dev/packages/pigeon
66

77
package io.flutter.plugins.videoplayer;
@@ -380,28 +380,12 @@ public interface AndroidVideoPlayerApi {
380380

381381
void dispose(@NonNull Long playerId);
382382

383-
void setLooping(@NonNull Long playerId, @NonNull Boolean looping);
384-
385-
void setVolume(@NonNull Long playerId, @NonNull Double volume);
386-
387-
void setPlaybackSpeed(@NonNull Long playerId, @NonNull Double speed);
388-
389-
void play(@NonNull Long playerId);
390-
391-
@NonNull
392-
Long position(@NonNull Long playerId);
393-
394-
void seekTo(@NonNull Long playerId, @NonNull Long position);
395-
396-
void pause(@NonNull Long playerId);
397-
398383
void setMixWithOthers(@NonNull Boolean mixWithOthers);
399384

400385
/** The codec used by AndroidVideoPlayerApi. */
401386
static @NonNull MessageCodec<Object> getCodec() {
402387
return PigeonCodec.INSTANCE;
403388
}
404-
405389
/**
406390
* Sets up an instance of `AndroidVideoPlayerApi` to handle messages through the
407391
* `binaryMessenger`.
@@ -493,18 +477,17 @@ static void setUp(
493477
BasicMessageChannel<Object> channel =
494478
new BasicMessageChannel<>(
495479
binaryMessenger,
496-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setLooping"
480+
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers"
497481
+ messageChannelSuffix,
498482
getCodec());
499483
if (api != null) {
500484
channel.setMessageHandler(
501485
(message, reply) -> {
502486
ArrayList<Object> wrapped = new ArrayList<>();
503487
ArrayList<Object> args = (ArrayList<Object>) message;
504-
Long playerIdArg = (Long) args.get(0);
505-
Boolean loopingArg = (Boolean) args.get(1);
488+
Boolean mixWithOthersArg = (Boolean) args.get(0);
506489
try {
507-
api.setLooping(playerIdArg, loopingArg);
490+
api.setMixWithOthers(mixWithOthersArg);
508491
wrapped.add(0, null);
509492
} catch (Throwable exception) {
510493
wrapped = wrapError(exception);
@@ -515,22 +498,59 @@ static void setUp(
515498
channel.setMessageHandler(null);
516499
}
517500
}
501+
}
502+
}
503+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
504+
public interface VideoPlayerInstanceApi {
505+
506+
void setLooping(@NonNull Boolean looping);
507+
508+
void setVolume(@NonNull Double volume);
509+
510+
void setPlaybackSpeed(@NonNull Double speed);
511+
512+
void play();
513+
514+
@NonNull
515+
Long getPosition();
516+
517+
void seekTo(@NonNull Long position);
518+
519+
void pause();
520+
521+
/** The codec used by VideoPlayerInstanceApi. */
522+
static @NonNull MessageCodec<Object> getCodec() {
523+
return PigeonCodec.INSTANCE;
524+
}
525+
/**
526+
* Sets up an instance of `VideoPlayerInstanceApi` to handle messages through the
527+
* `binaryMessenger`.
528+
*/
529+
static void setUp(
530+
@NonNull BinaryMessenger binaryMessenger, @Nullable VideoPlayerInstanceApi api) {
531+
setUp(binaryMessenger, "", api);
532+
}
533+
534+
static void setUp(
535+
@NonNull BinaryMessenger binaryMessenger,
536+
@NonNull String messageChannelSuffix,
537+
@Nullable VideoPlayerInstanceApi api) {
538+
messageChannelSuffix = messageChannelSuffix.isEmpty() ? "" : "." + messageChannelSuffix;
518539
{
519540
BasicMessageChannel<Object> channel =
520541
new BasicMessageChannel<>(
521542
binaryMessenger,
522-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setVolume"
543+
"dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setLooping"
523544
+ messageChannelSuffix,
524545
getCodec());
525546
if (api != null) {
526547
channel.setMessageHandler(
527548
(message, reply) -> {
528549
ArrayList<Object> wrapped = new ArrayList<>();
529550
ArrayList<Object> args = (ArrayList<Object>) message;
530-
Long playerIdArg = (Long) args.get(0);
531-
Double volumeArg = (Double) args.get(1);
551+
Boolean loopingArg = (Boolean) args.get(0);
532552
try {
533-
api.setVolume(playerIdArg, volumeArg);
553+
api.setLooping(loopingArg);
534554
wrapped.add(0, null);
535555
} catch (Throwable exception) {
536556
wrapped = wrapError(exception);
@@ -545,18 +565,17 @@ static void setUp(
545565
BasicMessageChannel<Object> channel =
546566
new BasicMessageChannel<>(
547567
binaryMessenger,
548-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setPlaybackSpeed"
568+
"dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setVolume"
549569
+ messageChannelSuffix,
550570
getCodec());
551571
if (api != null) {
552572
channel.setMessageHandler(
553573
(message, reply) -> {
554574
ArrayList<Object> wrapped = new ArrayList<>();
555575
ArrayList<Object> args = (ArrayList<Object>) message;
556-
Long playerIdArg = (Long) args.get(0);
557-
Double speedArg = (Double) args.get(1);
576+
Double volumeArg = (Double) args.get(0);
558577
try {
559-
api.setPlaybackSpeed(playerIdArg, speedArg);
578+
api.setVolume(volumeArg);
560579
wrapped.add(0, null);
561580
} catch (Throwable exception) {
562581
wrapped = wrapError(exception);
@@ -571,17 +590,17 @@ static void setUp(
571590
BasicMessageChannel<Object> channel =
572591
new BasicMessageChannel<>(
573592
binaryMessenger,
574-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.play"
593+
"dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.setPlaybackSpeed"
575594
+ messageChannelSuffix,
576595
getCodec());
577596
if (api != null) {
578597
channel.setMessageHandler(
579598
(message, reply) -> {
580599
ArrayList<Object> wrapped = new ArrayList<>();
581600
ArrayList<Object> args = (ArrayList<Object>) message;
582-
Long playerIdArg = (Long) args.get(0);
601+
Double speedArg = (Double) args.get(0);
583602
try {
584-
api.play(playerIdArg);
603+
api.setPlaybackSpeed(speedArg);
585604
wrapped.add(0, null);
586605
} catch (Throwable exception) {
587606
wrapped = wrapError(exception);
@@ -596,18 +615,16 @@ static void setUp(
596615
BasicMessageChannel<Object> channel =
597616
new BasicMessageChannel<>(
598617
binaryMessenger,
599-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.position"
618+
"dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.play"
600619
+ messageChannelSuffix,
601620
getCodec());
602621
if (api != null) {
603622
channel.setMessageHandler(
604623
(message, reply) -> {
605624
ArrayList<Object> wrapped = new ArrayList<>();
606-
ArrayList<Object> args = (ArrayList<Object>) message;
607-
Long playerIdArg = (Long) args.get(0);
608625
try {
609-
Long output = api.position(playerIdArg);
610-
wrapped.add(0, output);
626+
api.play();
627+
wrapped.add(0, null);
611628
} catch (Throwable exception) {
612629
wrapped = wrapError(exception);
613630
}
@@ -621,19 +638,16 @@ static void setUp(
621638
BasicMessageChannel<Object> channel =
622639
new BasicMessageChannel<>(
623640
binaryMessenger,
624-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.seekTo"
641+
"dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.getPosition"
625642
+ messageChannelSuffix,
626643
getCodec());
627644
if (api != null) {
628645
channel.setMessageHandler(
629646
(message, reply) -> {
630647
ArrayList<Object> wrapped = new ArrayList<>();
631-
ArrayList<Object> args = (ArrayList<Object>) message;
632-
Long playerIdArg = (Long) args.get(0);
633-
Long positionArg = (Long) args.get(1);
634648
try {
635-
api.seekTo(playerIdArg, positionArg);
636-
wrapped.add(0, null);
649+
Long output = api.getPosition();
650+
wrapped.add(0, output);
637651
} catch (Throwable exception) {
638652
wrapped = wrapError(exception);
639653
}
@@ -647,17 +661,17 @@ static void setUp(
647661
BasicMessageChannel<Object> channel =
648662
new BasicMessageChannel<>(
649663
binaryMessenger,
650-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.pause"
664+
"dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.seekTo"
651665
+ messageChannelSuffix,
652666
getCodec());
653667
if (api != null) {
654668
channel.setMessageHandler(
655669
(message, reply) -> {
656670
ArrayList<Object> wrapped = new ArrayList<>();
657671
ArrayList<Object> args = (ArrayList<Object>) message;
658-
Long playerIdArg = (Long) args.get(0);
672+
Long positionArg = (Long) args.get(0);
659673
try {
660-
api.pause(playerIdArg);
674+
api.seekTo(positionArg);
661675
wrapped.add(0, null);
662676
} catch (Throwable exception) {
663677
wrapped = wrapError(exception);
@@ -672,17 +686,15 @@ static void setUp(
672686
BasicMessageChannel<Object> channel =
673687
new BasicMessageChannel<>(
674688
binaryMessenger,
675-
"dev.flutter.pigeon.video_player_android.AndroidVideoPlayerApi.setMixWithOthers"
689+
"dev.flutter.pigeon.video_player_android.VideoPlayerInstanceApi.pause"
676690
+ messageChannelSuffix,
677691
getCodec());
678692
if (api != null) {
679693
channel.setMessageHandler(
680694
(message, reply) -> {
681695
ArrayList<Object> wrapped = new ArrayList<>();
682-
ArrayList<Object> args = (ArrayList<Object>) message;
683-
Boolean mixWithOthersArg = (Boolean) args.get(0);
684696
try {
685-
api.setMixWithOthers(mixWithOthersArg);
697+
api.pause();
686698
wrapped.add(0, null);
687699
} catch (Throwable exception) {
688700
wrapped = wrapError(exception);

packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
*
2222
* <p>It provides methods to control playback, adjust volume, and handle seeking.
2323
*/
24-
public abstract class VideoPlayer {
24+
public abstract class VideoPlayer implements Messages.VideoPlayerInstanceApi {
2525
@NonNull protected final VideoPlayerCallbacks videoPlayerEvents;
2626
@Nullable protected final SurfaceProducer surfaceProducer;
27+
@Nullable private DisposeHandler disposeHandler;
2728
@NonNull protected ExoPlayer exoPlayer;
2829

2930
/** A closure-compatible signature since {@link java.util.function.Supplier} is API level 24. */
@@ -37,6 +38,11 @@ public interface ExoPlayerProvider {
3738
ExoPlayer get();
3839
}
3940

41+
/** A handler to run when dispose is called. */
42+
public interface DisposeHandler {
43+
void onDispose();
44+
}
45+
4046
public VideoPlayer(
4147
@NonNull VideoPlayerCallbacks events,
4248
@NonNull MediaItem mediaItem,
@@ -52,6 +58,10 @@ public VideoPlayer(
5258
setAudioAttributes(exoPlayer, options.mixWithOthers);
5359
}
5460

61+
public void setDisposeHandler(@Nullable DisposeHandler handler) {
62+
disposeHandler = handler;
63+
}
64+
5565
@NonNull
5666
protected abstract ExoPlayerEventListener createExoPlayerEventListener(
5767
@NonNull ExoPlayer exoPlayer, @Nullable SurfaceProducer surfaceProducer);
@@ -66,37 +76,48 @@ private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) {
6676
!isMixMode);
6777
}
6878

69-
void play() {
79+
@Override
80+
public void play() {
7081
exoPlayer.play();
7182
}
7283

73-
void pause() {
84+
@Override
85+
public void pause() {
7486
exoPlayer.pause();
7587
}
7688

77-
void setLooping(boolean value) {
78-
exoPlayer.setRepeatMode(value ? REPEAT_MODE_ALL : REPEAT_MODE_OFF);
89+
@Override
90+
public void setLooping(@NonNull Boolean looping) {
91+
exoPlayer.setRepeatMode(looping ? REPEAT_MODE_ALL : REPEAT_MODE_OFF);
7992
}
8093

81-
void setVolume(double value) {
82-
float bracketedValue = (float) Math.max(0.0, Math.min(1.0, value));
94+
@Override
95+
public void setVolume(@NonNull Double volume) {
96+
float bracketedValue = (float) Math.max(0.0, Math.min(1.0, volume));
8397
exoPlayer.setVolume(bracketedValue);
8498
}
8599

86-
void setPlaybackSpeed(double value) {
100+
@Override
101+
public void setPlaybackSpeed(@NonNull Double speed) {
87102
// We do not need to consider pitch and skipSilence for now as we do not handle them and
88103
// therefore never diverge from the default values.
89-
final PlaybackParameters playbackParameters = new PlaybackParameters(((float) value));
104+
final PlaybackParameters playbackParameters = new PlaybackParameters(speed.floatValue());
90105

91106
exoPlayer.setPlaybackParameters(playbackParameters);
92107
}
93108

94-
void seekTo(int location) {
95-
exoPlayer.seekTo(location);
109+
@Override
110+
public @NonNull Long getPosition() {
111+
long position = exoPlayer.getCurrentPosition();
112+
// TODO(stuartmorgan): Move this; this is relying on the fact that getPosition is called
113+
// frequently to drive buffering updates, which is a fragile hack.
114+
sendBufferingUpdate();
115+
return position;
96116
}
97117

98-
long getPosition() {
99-
return exoPlayer.getCurrentPosition();
118+
@Override
119+
public void seekTo(@NonNull Long position) {
120+
exoPlayer.seekTo(position);
100121
}
101122

102123
@NonNull
@@ -105,6 +126,9 @@ public ExoPlayer getExoPlayer() {
105126
}
106127

107128
public void dispose() {
129+
if (disposeHandler != null) {
130+
disposeHandler.onDispose();
131+
}
108132
exoPlayer.release();
109133
}
110134
}

0 commit comments

Comments
 (0)