Skip to content

Migrate video_player/android from SurfaceTexture->SurfaceProducer. #6456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion packages/video_player/video_player_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT

* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.
* Updates minimum supported SDK version to Flutter 3.22/Dart 3.2.
* [Supports Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins)

## 2.4.14

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ final class VideoPlayer {

private Surface surface;
Copy link
Contributor

Choose a reason for hiding this comment

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

don't need to cache surface.


private final TextureRegistry.SurfaceTextureEntry textureEntry;
private final TextureRegistry.SurfaceProducer surfaceProducer;

private QueuingEventSink eventSink;

Expand All @@ -67,13 +67,13 @@ final class VideoPlayer {
VideoPlayer(
Context context,
EventChannel eventChannel,
TextureRegistry.SurfaceTextureEntry textureEntry,
TextureRegistry.SurfaceProducer surfaceProducer,
String dataSource,
String formatHint,
@NonNull Map<String, String> httpHeaders,
VideoPlayerOptions options) {
this.eventChannel = eventChannel;
this.textureEntry = textureEntry;
this.surfaceProducer = surfaceProducer;
this.options = options;

ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build();
Expand All @@ -96,12 +96,12 @@ final class VideoPlayer {
VideoPlayer(
ExoPlayer exoPlayer,
EventChannel eventChannel,
TextureRegistry.SurfaceTextureEntry textureEntry,
TextureRegistry.SurfaceProducer surfaceProducer,
VideoPlayerOptions options,
QueuingEventSink eventSink,
DefaultHttpDataSource.Factory httpDataSourceFactory) {
this.eventChannel = eventChannel;
this.textureEntry = textureEntry;
this.surfaceProducer = surfaceProducer;
this.options = options;
this.httpDataSourceFactory = httpDataSourceFactory;

Expand Down Expand Up @@ -186,7 +186,7 @@ public void onCancel(Object o) {
}
});

surface = new Surface(textureEntry.surfaceTexture());
surface = surfaceProducer.getSurface();
Copy link
Contributor

Choose a reason for hiding this comment

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

Note, the returned Surface may become invalid after a suspend / resume. Is there a place in the plugin that will be notified when we resume? We should update the video surface in that callback.

Copy link
Contributor

Choose a reason for hiding this comment

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

Need to look at this still.

Copy link
Contributor

Choose a reason for hiding this comment

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

I've made this activity aware. It now tears down the exoplayer on pause and recreates on reusme.

exoPlayer.setVideoSurface(surface);
setAudioAttributes(exoPlayer, options.mixWithOthers);

Expand Down Expand Up @@ -334,7 +334,7 @@ void dispose() {
if (isInitialized) {
exoPlayer.stop();
}
textureEntry.release();
surfaceProducer.release();
eventChannel.setStreamHandler(null);
if (surface != null) {
surface.release();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ public void initialize() {
}

public @NonNull TextureMessage create(@NonNull CreateMessage arg) {
TextureRegistry.SurfaceTextureEntry handle =
flutterState.textureRegistry.createSurfaceTexture();
TextureRegistry.SurfaceProducer handle =
flutterState.textureRegistry.createSurfaceProducer();
EventChannel eventChannel =
new EventChannel(
flutterState.binaryMessenger, "flutter.io/videoPlayer/videoEvents" + handle.id());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@
public class VideoPlayerTest {
private ExoPlayer fakeExoPlayer;
private EventChannel fakeEventChannel;
private TextureRegistry.SurfaceTextureEntry fakeSurfaceTextureEntry;
private SurfaceTexture fakeSurfaceTexture;
private TextureRegistry.SurfaceProducer fakeSurfaceProducer;
private VideoPlayerOptions fakeVideoPlayerOptions;
private QueuingEventSink fakeEventSink;
private DefaultHttpDataSource.Factory httpDataSourceFactorySpy;
Expand All @@ -52,24 +51,16 @@ public void before() {

fakeExoPlayer = mock(ExoPlayer.class);
fakeEventChannel = mock(EventChannel.class);
fakeSurfaceTextureEntry = mock(TextureRegistry.SurfaceTextureEntry.class);
fakeSurfaceTexture = mock(SurfaceTexture.class);
when(fakeSurfaceTextureEntry.surfaceTexture()).thenReturn(fakeSurfaceTexture);
fakeSurfaceProducer = mock(TextureRegistry.SurfaceProducer.class);
fakeVideoPlayerOptions = mock(VideoPlayerOptions.class);
fakeEventSink = mock(QueuingEventSink.class);
httpDataSourceFactorySpy = spy(new DefaultHttpDataSource.Factory());
}

@Test
public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);

videoPlayer.buildHttpDataSourceFactory(new HashMap<>());

Expand All @@ -80,22 +71,15 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull()

@Test
public void
videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNonNullAndUserAgentSpecified() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
Map<String, String> httpHeaders =
new HashMap<String, String>() {
{
put("header", "value");
put("User-Agent", "userAgent");
}
};
videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNonNullAndUserAgentSpecified() {
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);
Map<String, String> httpHeaders = new HashMap<String, String>() {
{
put("header", "value");
put("User-Agent", "userAgent");
}
};

videoPlayer.buildHttpDataSourceFactory(httpHeaders);

Expand All @@ -106,21 +90,12 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull()

@Test
public void
videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNonNullAndUserAgentNotSpecified() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
Map<String, String> httpHeaders =
new HashMap<String, String>() {
{
put("header", "value");
}
};
videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNonNullAndUserAgentNotSpecified() {
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);
Map<String, String> httpHeaders = new HashMap<String, String>() {
{ put("header", "value"); }
};

videoPlayer.buildHttpDataSourceFactory(httpHeaders);

Expand All @@ -131,14 +106,8 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull()

@Test
public void sendInitializedSendsExpectedEvent_90RotationDegrees() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(90).build();

Expand All @@ -160,14 +129,8 @@ public void sendInitializedSendsExpectedEvent_90RotationDegrees() {

@Test
public void sendInitializedSendsExpectedEvent_270RotationDegrees() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(270).build();

Expand All @@ -189,14 +152,8 @@ public void sendInitializedSendsExpectedEvent_270RotationDegrees() {

@Test
public void sendInitializedSendsExpectedEvent_0RotationDegrees() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(0).build();

Expand All @@ -218,14 +175,8 @@ public void sendInitializedSendsExpectedEvent_0RotationDegrees() {

@Test
public void sendInitializedSendsExpectedEvent_180RotationDegrees() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);
Format testFormat =
new Format.Builder().setWidth(100).setHeight(200).setRotationDegrees(180).build();

Expand All @@ -247,24 +198,16 @@ public void sendInitializedSendsExpectedEvent_180RotationDegrees() {

@Test
public void onIsPlayingChangedSendsExpectedEvent() {
VideoPlayer videoPlayer =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);

doAnswer(
(Answer<Void>)
invocation -> {
Map<String, Object> event = new HashMap<>();
event.put("event", "isPlayingStateUpdate");
event.put("isPlaying", (Boolean) invocation.getArguments()[0]);
fakeEventSink.success(event);
return null;
})
VideoPlayer videoPlayer = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);

doAnswer((Answer<Void>) invocation -> {
Map<String, Object> event = new HashMap<>();
event.put("event", "isPlayingStateUpdate");
event.put("isPlaying", (Boolean) invocation.getArguments()[0]);
fakeEventSink.success(event);
return null;
})
.when(fakeExoPlayer)
.setPlayWhenReady(anyBoolean());

Expand Down Expand Up @@ -292,14 +235,8 @@ public void behindLiveWindowErrorResetsPlayerToDefaultPosition() {
.when(fakeExoPlayer)
.addListener(any());

VideoPlayer unused =
new VideoPlayer(
fakeExoPlayer,
fakeEventChannel,
fakeSurfaceTextureEntry,
fakeVideoPlayerOptions,
fakeEventSink,
httpDataSourceFactorySpy);
VideoPlayer unused = new VideoPlayer(fakeExoPlayer, fakeEventChannel, fakeSurfaceProducer,
fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy);

PlaybackException exception =
new PlaybackException(null, null, PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW);
Expand Down
2 changes: 1 addition & 1 deletion packages/video_player/video_player_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ version: 2.4.14

environment:
sdk: ^3.2.0
flutter: ">=3.16.0"
flutter: ">=3.22.0"

flutter:
plugin:
Expand Down