From 43b106a795c2f64b669cf0f700f01be796828312 Mon Sep 17 00:00:00 2001 From: LinXunFeng Date: Mon, 26 Aug 2024 11:09:44 +0800 Subject: [PATCH 1/2] Revert "[video_player] Relands #6456: Uses `SurfaceProducer`, this time with `setCallback` for suspend/resume lifecycles. (#6989)" This reverts commit 62b4cb0afbfb12a796bad20eb8674a941552cc2c. --- .../video_player_android/CHANGELOG.md | 4 - .../plugins/videoplayer/ExoPlayerState.java | 69 ----------- .../plugins/videoplayer/VideoPlayer.java | 113 +++++++----------- .../videoplayer/VideoPlayerPlugin.java | 5 +- .../plugins/videoplayer/VideoPlayerTest.java | 48 ++------ .../video_player_android/pubspec.yaml | 2 +- 6 files changed, 55 insertions(+), 186 deletions(-) delete mode 100644 packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerState.java diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index 721c08cdbf5..e6815fce6ef 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,7 +1,3 @@ -## 2.7.0 - -* Re-adds [support for Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins). - ## 2.6.0 * Adds RTSP support. diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerState.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerState.java deleted file mode 100644 index cd55b54c124..00000000000 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerState.java +++ /dev/null @@ -1,69 +0,0 @@ -// 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. - -package io.flutter.plugins.videoplayer; - -import androidx.media3.common.PlaybackParameters; -import androidx.media3.exoplayer.ExoPlayer; - -/** - * Internal state representing an {@link ExoPlayer} instance at a snapshot in time. - * - *

During the Android application lifecycle, the underlying {@link android.view.Surface} being - * rendered to by the player can be destroyed when the application is in the background and memory - * is reclaimed. Upon resume, the player will need to be recreated, but start again at the - * previous point (and settings). - */ -final class ExoPlayerState { - /** - * Saves a representation of the current state of the player at the current point in time. - * - *

The inverse of this operation is {@link #restore(ExoPlayer)}. - * - * @param exoPlayer the active player instance. - * @return an opaque object representing the state. - */ - static ExoPlayerState save(ExoPlayer exoPlayer) { - return new ExoPlayerState( - /*position=*/ exoPlayer.getCurrentPosition(), - /*repeatMode=*/ exoPlayer.getRepeatMode(), - /*volume=*/ exoPlayer.getVolume(), - /*playbackParameters=*/ exoPlayer.getPlaybackParameters()); - } - - private ExoPlayerState( - long position, int repeatMode, float volume, PlaybackParameters playbackParameters) { - this.position = position; - this.repeatMode = repeatMode; - this.volume = volume; - this.playbackParameters = playbackParameters; - } - - /** Previous value of {@link ExoPlayer#getCurrentPosition()}. */ - private final long position; - - /** Previous value of {@link ExoPlayer#getRepeatMode()}. */ - private final int repeatMode; - - /** Previous value of {@link ExoPlayer#getVolume()}. */ - private final float volume; - - /** Previous value of {@link ExoPlayer#getPlaybackParameters()}. */ - private final PlaybackParameters playbackParameters; - - /** - * Restores the captured state onto the provided player. - * - *

This will typically be done after creating a new player, setting up a media source, and - * listening to events. - * - * @param exoPlayer the new player instance to reflect the state back to. - */ - void restore(ExoPlayer exoPlayer) { - exoPlayer.seekTo(position); - exoPlayer.setRepeatMode(repeatMode); - exoPlayer.setVolume(volume); - exoPlayer.setPlaybackParameters(playbackParameters); - } -} diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java index 8b12c8a2a0b..ab20b30f42d 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayer.java @@ -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; @@ -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() { @@ -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) { @@ -156,7 +118,12 @@ long getPosition() { } void dispose() { - surfaceProducer.release(); - exoPlayer.release(); + textureEntry.release(); + if (surface != null) { + surface.release(); + } + if (exoPlayer != null) { + exoPlayer.release(); + } } } diff --git a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java index 2a9f5cc3857..af13c539551 100644 --- a/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/VideoPlayerPlugin.java @@ -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. */ @@ -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()); @@ -111,6 +113,7 @@ public void initialize() { } else if (arg.getUri().startsWith("rtsp://")) { videoAsset = VideoAsset.fromRtspUrl(arg.getUri()); } else { + Map httpHeaders = arg.getHttpHeaders(); VideoAsset.StreamingFormat streamingFormat = VideoAsset.StreamingFormat.UNKNOWN; String formatHint = arg.getFormatHint(); if (formatHint != null) { diff --git a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java index 1b7ff407119..a30166c0aa5 100644 --- a/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java +++ b/packages/video_player/video_player_android/android/src/test/java/io/flutter/plugins/videoplayer/VideoPlayerTest.java @@ -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; @@ -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 attributesCaptor; - @Captor private ArgumentCaptor 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() { @@ -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 @@ -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)); @@ -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(); } @@ -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(); } } diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 25094c9186b..ed629fa91f8 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -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.6.0 environment: sdk: ^3.4.0 From a4dccbd4b6d1dd42279abe272dc13fb4640d010b Mon Sep 17 00:00:00 2001 From: LinXunFeng Date: Mon, 26 Aug 2024 11:14:57 +0800 Subject: [PATCH 2/2] update version and changelog. --- packages/video_player/video_player_android/CHANGELOG.md | 8 ++++++++ packages/video_player/video_player_android/pubspec.yaml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index e6815fce6ef..e88f3385c87 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.7.1 + +* Revert Impeller support. + +## 2.7.0 + +* Re-adds [support for Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins). + ## 2.6.0 * Adds RTSP support. diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index ed629fa91f8..488e5e32e47 100644 --- a/packages/video_player/video_player_android/pubspec.yaml +++ b/packages/video_player/video_player_android/pubspec.yaml @@ -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.6.0 +version: 2.7.1 environment: sdk: ^3.4.0