Skip to content

Commit

Permalink
Revert "[video_player] Relands #6456: Uses SurfaceProducer, this time…
Browse files Browse the repository at this point in the history
… with setCallback for suspend/resume lifecycles" (#7497)

Reverts #6989
Fixes flutter/flutter#154054 , flutter/flutter#154050
  • Loading branch information
LinXunFeng committed Aug 26, 2024
1 parent 62b4cb0 commit f61a98a
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 182 deletions.
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

0 comments on commit f61a98a

Please sign in to comment.