diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index e88f3385c87..0bd45e401ac 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,7 +1,15 @@ +## 2.7.2 + +* Updates minimum supported SDK version to Flutter 3.24/Dart 3.5. + +* Re-adds Impeller support. + + ## 2.7.1 * Revert Impeller support. + ## 2.7.0 * Re-adds [support for Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins). 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 new file mode 100644 index 00000000000..cd55b54c124 --- /dev/null +++ b/packages/video_player/video_player_android/android/src/main/java/io/flutter/plugins/videoplayer/ExoPlayerState.java @@ -0,0 +1,69 @@ +// 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 ab20b30f42d..8b12c8a2a0b 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,8 +8,9 @@
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;
@@ -18,60 +19,97 @@
import androidx.media3.exoplayer.ExoPlayer;
import io.flutter.view.TextureRegistry;
-final class VideoPlayer {
- private ExoPlayer exoPlayer;
- private Surface surface;
- private final TextureRegistry.SurfaceTextureEntry textureEntry;
- private final VideoPlayerCallbacks videoPlayerEvents;
- private final VideoPlayerOptions options;
+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;
/**
* Creates a video player.
*
* @param context application context.
* @param events event callbacks.
- * @param textureEntry texture to render to.
+ * @param surfaceProducer produces a texture to render to.
* @param asset asset to play.
* @param options options for playback.
* @return a video player instance.
*/
@NonNull
static VideoPlayer create(
- 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);
+ @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();
}
@VisibleForTesting
VideoPlayer(
- ExoPlayer.Builder builder,
- VideoPlayerCallbacks events,
- TextureRegistry.SurfaceTextureEntry textureEntry,
- MediaItem mediaItem,
- VideoPlayerOptions options) {
+ @NonNull ExoPlayerProvider exoPlayerProvider,
+ @NonNull VideoPlayerCallbacks events,
+ @NonNull TextureRegistry.SurfaceProducer surfaceProducer,
+ @NonNull MediaItem mediaItem,
+ @NonNull VideoPlayerOptions options) {
+ this.exoPlayerProvider = exoPlayerProvider;
this.videoPlayerEvents = events;
- this.textureEntry = textureEntry;
+ this.surfaceProducer = surfaceProducer;
+ this.mediaItem = mediaItem;
this.options = options;
+ this.exoPlayer = createVideoPlayer();
+ surfaceProducer.setCallback(this);
+ }
- ExoPlayer exoPlayer = builder.build();
- exoPlayer.setMediaItem(mediaItem);
- exoPlayer.prepare();
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public void onSurfaceCreated() {
+ exoPlayer = createVideoPlayer();
+ if (savedStateDuring != null) {
+ savedStateDuring.restore(exoPlayer);
+ savedStateDuring = null;
+ }
+ }
- setUpVideoPlayer(exoPlayer);
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ public void onSurfaceDestroyed() {
+ exoPlayer.stop();
+ savedStateDuring = ExoPlayerState.save(exoPlayer);
+ exoPlayer.release();
}
- private void setUpVideoPlayer(ExoPlayer exoPlayer) {
- this.exoPlayer = exoPlayer;
+ private ExoPlayer createVideoPlayer() {
+ ExoPlayer exoPlayer = exoPlayerProvider.get();
+ exoPlayer.setMediaItem(mediaItem);
+ exoPlayer.prepare();
- surface = new Surface(textureEntry.surfaceTexture());
- exoPlayer.setVideoSurface(surface);
- setAudioAttributes(exoPlayer, options.mixWithOthers);
+ exoPlayer.setVideoSurface(surfaceProducer.getSurface());
exoPlayer.addListener(new ExoPlayerEventListener(exoPlayer, videoPlayerEvents));
+ setAudioAttributes(exoPlayer, options.mixWithOthers);
+
+ return exoPlayer;
}
void sendBufferingUpdate() {
@@ -85,11 +123,11 @@ private static void setAudioAttributes(ExoPlayer exoPlayer, boolean isMixMode) {
}
void play() {
- exoPlayer.setPlayWhenReady(true);
+ exoPlayer.play();
}
void pause() {
- exoPlayer.setPlayWhenReady(false);
+ exoPlayer.pause();
}
void setLooping(boolean value) {
@@ -118,12 +156,7 @@ long getPosition() {
}
void dispose() {
- textureEntry.release();
- if (surface != null) {
- surface.release();
- }
- if (exoPlayer != null) {
- exoPlayer.release();
- }
+ surfaceProducer.release();
+ 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 af13c539551..2a9f5cc3857 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,7 +24,6 @@
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. */
@@ -94,8 +93,7 @@ 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());
@@ -113,7 +111,6 @@ public void initialize() {
} else if (arg.getUri().startsWith("rtsp://")) {
videoAsset = VideoAsset.fromRtspUrl(arg.getUri());
} else {
- Map