Skip to content
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

Revert "[video_player] Relands #6456: Uses SurfaceProducer, this time with setCallback for suspend/resume lifecycles" #7497

Merged
Merged
Show file tree
Hide file tree
Changes from all 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.7.1

* Revert Impeller support.

## 2.7.0

* Re-adds [support for Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins).
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
import static androidx.media3.common.Player.REPEAT_MODE_OFF;

import android.content.Context;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
Expand All @@ -19,97 +18,60 @@
import androidx.media3.exoplayer.ExoPlayer;
import io.flutter.view.TextureRegistry;

final class VideoPlayer implements TextureRegistry.SurfaceProducer.Callback {
@NonNull private final ExoPlayerProvider exoPlayerProvider;
@NonNull private final MediaItem mediaItem;
@NonNull private final TextureRegistry.SurfaceProducer surfaceProducer;
@NonNull private final VideoPlayerCallbacks videoPlayerEvents;
@NonNull private final VideoPlayerOptions options;
@NonNull private ExoPlayer exoPlayer;
@Nullable private ExoPlayerState savedStateDuring;
final class VideoPlayer {
private ExoPlayer exoPlayer;
private Surface surface;
private final TextureRegistry.SurfaceTextureEntry textureEntry;
private final VideoPlayerCallbacks videoPlayerEvents;
private final VideoPlayerOptions options;

/**
* Creates a video player.
*
* @param context application context.
* @param events event callbacks.
* @param surfaceProducer produces a texture to render to.
* @param textureEntry texture to render to.
* @param asset asset to play.
* @param options options for playback.
* @return a video player instance.
*/
@NonNull
static VideoPlayer create(
@NonNull Context context,
@NonNull VideoPlayerCallbacks events,
@NonNull TextureRegistry.SurfaceProducer surfaceProducer,
@NonNull VideoAsset asset,
@NonNull VideoPlayerOptions options) {
return new VideoPlayer(
() -> {
ExoPlayer.Builder builder =
new ExoPlayer.Builder(context)
.setMediaSourceFactory(asset.getMediaSourceFactory(context));
return builder.build();
},
events,
surfaceProducer,
asset.getMediaItem(),
options);
}

/** A closure-compatible signature since {@link java.util.function.Supplier} is API level 24. */
interface ExoPlayerProvider {
/**
* Returns a new {@link ExoPlayer}.
*
* @return new instance.
*/
ExoPlayer get();
Context context,
VideoPlayerCallbacks events,
TextureRegistry.SurfaceTextureEntry textureEntry,
VideoAsset asset,
VideoPlayerOptions options) {
ExoPlayer.Builder builder =
new ExoPlayer.Builder(context).setMediaSourceFactory(asset.getMediaSourceFactory(context));
return new VideoPlayer(builder, events, textureEntry, asset.getMediaItem(), options);
}

@VisibleForTesting
VideoPlayer(
@NonNull ExoPlayerProvider exoPlayerProvider,
@NonNull VideoPlayerCallbacks events,
@NonNull TextureRegistry.SurfaceProducer surfaceProducer,
@NonNull MediaItem mediaItem,
@NonNull VideoPlayerOptions options) {
this.exoPlayerProvider = exoPlayerProvider;
ExoPlayer.Builder builder,
VideoPlayerCallbacks events,
TextureRegistry.SurfaceTextureEntry textureEntry,
MediaItem mediaItem,
VideoPlayerOptions options) {
this.videoPlayerEvents = events;
this.surfaceProducer = surfaceProducer;
this.mediaItem = mediaItem;
this.textureEntry = textureEntry;
this.options = options;
this.exoPlayer = createVideoPlayer();
surfaceProducer.setCallback(this);
}

@RestrictTo(RestrictTo.Scope.LIBRARY)
public void onSurfaceCreated() {
exoPlayer = createVideoPlayer();
if (savedStateDuring != null) {
savedStateDuring.restore(exoPlayer);
savedStateDuring = null;
}
}
ExoPlayer exoPlayer = builder.build();
exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();

@RestrictTo(RestrictTo.Scope.LIBRARY)
public void onSurfaceDestroyed() {
exoPlayer.stop();
savedStateDuring = ExoPlayerState.save(exoPlayer);
exoPlayer.release();
setUpVideoPlayer(exoPlayer);
}

private ExoPlayer createVideoPlayer() {
ExoPlayer exoPlayer = exoPlayerProvider.get();
exoPlayer.setMediaItem(mediaItem);
exoPlayer.prepare();
private void setUpVideoPlayer(ExoPlayer exoPlayer) {
this.exoPlayer = exoPlayer;

exoPlayer.setVideoSurface(surfaceProducer.getSurface());
exoPlayer.addListener(new ExoPlayerEventListener(exoPlayer, videoPlayerEvents));
surface = new Surface(textureEntry.surfaceTexture());
exoPlayer.setVideoSurface(surface);
setAudioAttributes(exoPlayer, options.mixWithOthers);

return exoPlayer;
exoPlayer.addListener(new ExoPlayerEventListener(exoPlayer, videoPlayerEvents));
}

void sendBufferingUpdate() {
Expand All @@ -123,11 +85,11 @@ private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) {
}

void play() {
exoPlayer.play();
exoPlayer.setPlayWhenReady(true);
}

void pause() {
exoPlayer.pause();
exoPlayer.setPlayWhenReady(false);
}

void setLooping(boolean value) {
Expand Down Expand Up @@ -156,7 +118,12 @@ long getPosition() {
}

void dispose() {
surfaceProducer.release();
exoPlayer.release();
textureEntry.release();
if (surface != null) {
surface.release();
}
if (exoPlayer != null) {
exoPlayer.release();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.flutter.view.TextureRegistry;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;

/** Android platform implementation of the VideoPlayerPlugin. */
Expand Down Expand Up @@ -93,7 +94,8 @@ public void initialize() {
}

public @NonNull TextureMessage create(@NonNull CreateMessage arg) {
TextureRegistry.SurfaceProducer handle = flutterState.textureRegistry.createSurfaceProducer();
TextureRegistry.SurfaceTextureEntry handle =
flutterState.textureRegistry.createSurfaceTexture();
EventChannel eventChannel =
new EventChannel(
flutterState.binaryMessenger, "flutter.io/videoPlayer/videoEvents" + handle.id());
Expand All @@ -111,6 +113,7 @@ public void initialize() {
} else if (arg.getUri().startsWith("rtsp://")) {
videoAsset = VideoAsset.fromRtspUrl(arg.getUri());
} else {
Map<String, String> httpHeaders = arg.getHttpHeaders();
VideoAsset.StreamingFormat streamingFormat = VideoAsset.StreamingFormat.UNKNOWN;
String formatHint = arg.getFormatHint();
if (formatHint != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

import android.view.Surface;
import android.graphics.SurfaceTexture;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.PlaybackParameters;
Expand Down Expand Up @@ -44,17 +44,18 @@ public final class VideoPlayerTest {
private FakeVideoAsset fakeVideoAsset;

@Mock private VideoPlayerCallbacks mockEvents;
@Mock private TextureRegistry.SurfaceProducer mockProducer;
@Mock private TextureRegistry.SurfaceTextureEntry mockTexture;
@Mock private ExoPlayer.Builder mockBuilder;
@Mock private ExoPlayer mockExoPlayer;
@Captor private ArgumentCaptor<AudioAttributes> attributesCaptor;
@Captor private ArgumentCaptor<TextureRegistry.SurfaceProducer.Callback> callbackCaptor;

@Rule public MockitoRule initRule = MockitoJUnit.rule();

@Before
public void setUp() {
fakeVideoAsset = new FakeVideoAsset(FAKE_ASSET_URL);
when(mockProducer.getSurface()).thenReturn(mock(Surface.class));
when(mockBuilder.build()).thenReturn(mockExoPlayer);
when(mockTexture.surfaceTexture()).thenReturn(mock(SurfaceTexture.class));
}

private VideoPlayer createVideoPlayer() {
Expand All @@ -63,7 +64,7 @@ private VideoPlayer createVideoPlayer() {

private VideoPlayer createVideoPlayer(VideoPlayerOptions options) {
return new VideoPlayer(
() -> mockExoPlayer, mockEvents, mockProducer, fakeVideoAsset.getMediaItem(), options);
mockBuilder, mockEvents, mockTexture, fakeVideoAsset.getMediaItem(), options);
}

@Test
Expand All @@ -72,7 +73,7 @@ public void loadsAndPreparesProvidedMediaEnablesAudioFocusByDefault() {

verify(mockExoPlayer).setMediaItem(fakeVideoAsset.getMediaItem());
verify(mockExoPlayer).prepare();
verify(mockProducer).getSurface();
verify(mockTexture).surfaceTexture();
verify(mockExoPlayer).setVideoSurface(any());

verify(mockExoPlayer).setAudioAttributes(attributesCaptor.capture(), eq(true));
Expand All @@ -99,10 +100,10 @@ public void playsAndPausesProvidedMedia() {
VideoPlayer videoPlayer = createVideoPlayer();

videoPlayer.play();
verify(mockExoPlayer).play();
verify(mockExoPlayer).setPlayWhenReady(true);

videoPlayer.pause();
verify(mockExoPlayer).pause();
verify(mockExoPlayer).setPlayWhenReady(false);

videoPlayer.dispose();
}
Expand Down Expand Up @@ -168,41 +169,12 @@ public void seekAndGetPosition() {
assertEquals(20L, videoPlayer.getPosition());
}

@Test
public void onSurfaceProducerDestroyedAndRecreatedReleasesAndThenRecreatesAndResumesPlayer() {
VideoPlayer videoPlayer = createVideoPlayer();

verify(mockProducer).setCallback(callbackCaptor.capture());
verify(mockExoPlayer, never()).release();

when(mockExoPlayer.getCurrentPosition()).thenReturn(10L);
when(mockExoPlayer.getRepeatMode()).thenReturn(Player.REPEAT_MODE_ALL);
when(mockExoPlayer.getVolume()).thenReturn(0.5f);
when(mockExoPlayer.getPlaybackParameters()).thenReturn(new PlaybackParameters(2.5f));

TextureRegistry.SurfaceProducer.Callback producerLifecycle = callbackCaptor.getValue();
producerLifecycle.onSurfaceDestroyed();

verify(mockExoPlayer).release();

// Create a new mock exo player so that we get a new instance.
mockExoPlayer = mock(ExoPlayer.class);
producerLifecycle.onSurfaceCreated();

verify(mockExoPlayer).seekTo(10L);
verify(mockExoPlayer).setRepeatMode(Player.REPEAT_MODE_ALL);
verify(mockExoPlayer).setVolume(0.5f);
verify(mockExoPlayer).setPlaybackParameters(new PlaybackParameters(2.5f));

videoPlayer.dispose();
}

@Test
public void disposeReleasesTextureAndPlayer() {
VideoPlayer videoPlayer = createVideoPlayer();
videoPlayer.dispose();

verify(mockProducer).release();
verify(mockTexture).release();
verify(mockExoPlayer).release();
}
}
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 @@ -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.7.0
version: 2.7.1

environment:
sdk: ^3.4.0
Expand Down