diff --git a/packages/video_player/video_player_android/CHANGELOG.md b/packages/video_player/video_player_android/CHANGELOG.md index cc9d5ceaa17..9486013f634 100644 --- a/packages/video_player/video_player_android/CHANGELOG.md +++ b/packages/video_player/video_player_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.4.16 + +* [Supports Impeller](https://docs.flutter.dev/release/breaking-changes/android-surface-plugins). + ## 2.4.15 * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. 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 b8b78191230..5cb5f0e2b30 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 @@ -9,7 +9,6 @@ import android.content.Context; import android.net.Uri; -import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.C; @@ -48,9 +47,7 @@ final class VideoPlayer { private ExoPlayer exoPlayer; - private Surface surface; - - private final TextureRegistry.SurfaceTextureEntry textureEntry; + private TextureRegistry.SurfaceProducer surfaceProducer; private QueuingEventSink eventSink; @@ -58,22 +55,29 @@ final class VideoPlayer { private static final String USER_AGENT = "User-Agent"; + private MediaSource mediaSource; + @VisibleForTesting boolean isInitialized = false; + // State that must be reset when the surface is re-created. private final VideoPlayerOptions options; + private long restoreVideoLocation = 0; + private int restoreRepeatMode = 0; + private float restoreVolume = 0; + private PlaybackParameters restorePlaybackParameters; private DefaultHttpDataSource.Factory httpDataSourceFactory = new DefaultHttpDataSource.Factory(); VideoPlayer( Context context, EventChannel eventChannel, - TextureRegistry.SurfaceTextureEntry textureEntry, + TextureRegistry.SurfaceProducer surfaceProducer, String dataSource, String formatHint, @NonNull Map httpHeaders, VideoPlayerOptions options) { this.eventChannel = eventChannel; - this.textureEntry = textureEntry; + this.surfaceProducer = surfaceProducer; this.options = options; ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build(); @@ -83,7 +87,7 @@ final class VideoPlayer { DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(context, httpDataSourceFactory); - MediaSource mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint); + mediaSource = buildMediaSource(uri, dataSourceFactory, formatHint); exoPlayer.setMediaSource(mediaSource); exoPlayer.prepare(); @@ -96,12 +100,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; @@ -169,6 +173,40 @@ private MediaSource buildMediaSource( } } + public void recreateSurface(Context context) { + ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build(); + + exoPlayer.setMediaSource(mediaSource); + exoPlayer.prepare(); + + setUpVideoPlayer(exoPlayer, new QueuingEventSink()); + exoPlayer.setVideoSurface(surfaceProducer.getSurface()); + exoPlayer.seekTo(restoreVideoLocation); + exoPlayer.setRepeatMode(restoreRepeatMode); + exoPlayer.setVolume(restoreVolume); + if (restorePlaybackParameters != null) { + exoPlayer.setPlaybackParameters(restorePlaybackParameters); + } + } + + public void pauseSurface() { + if (!isInitialized) { + return; + } + restoreVideoLocation = exoPlayer.getCurrentPosition(); + restoreRepeatMode = exoPlayer.getRepeatMode(); + restoreVolume = exoPlayer.getVolume(); + restorePlaybackParameters = exoPlayer.getPlaybackParameters(); + eventChannel.setStreamHandler(null); + if (isInitialized) { + exoPlayer.stop(); + } + if (exoPlayer != null) { + exoPlayer.release(); + } + isInitialized = false; + } + private void setUpVideoPlayer(ExoPlayer exoPlayer, QueuingEventSink eventSink) { this.exoPlayer = exoPlayer; this.eventSink = eventSink; @@ -186,8 +224,7 @@ public void onCancel(Object o) { } }); - surface = new Surface(textureEntry.surfaceTexture()); - exoPlayer.setVideoSurface(surface); + exoPlayer.setVideoSurface(surfaceProducer.getSurface()); setAudioAttributes(exoPlayer, options.mixWithOthers); exoPlayer.addListener( @@ -334,11 +371,8 @@ void dispose() { if (isInitialized) { exoPlayer.stop(); } - textureEntry.release(); + surfaceProducer.release(); eventChannel.setStreamHandler(null); - 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 5259e1ad3fe..54234dd6df8 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 @@ -8,9 +8,16 @@ import android.os.Build; import android.util.LongSparseArray; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; import io.flutter.plugins.videoplayer.Messages.AndroidVideoPlayerApi; @@ -29,11 +36,13 @@ import javax.net.ssl.HttpsURLConnection; /** Android platform implementation of the VideoPlayerPlugin. */ -public class VideoPlayerPlugin implements FlutterPlugin, AndroidVideoPlayerApi { +public class VideoPlayerPlugin + implements FlutterPlugin, AndroidVideoPlayerApi, DefaultLifecycleObserver, ActivityAware { private static final String TAG = "VideoPlayerPlugin"; private final LongSparseArray videoPlayers = new LongSparseArray<>(); private FlutterState flutterState; private final VideoPlayerOptions options = new VideoPlayerOptions(); + @Nullable Lifecycle lifecycle; /** Register this with the v2 embedding for the plugin to respond to lifecycle callbacks. */ public VideoPlayerPlugin() {} @@ -83,7 +92,7 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { } flutterState.stopListening(binding.getBinaryMessenger()); flutterState = null; - onDestroy(); + performDestroy(); } private void disposeAllPlayers() { @@ -93,7 +102,7 @@ private void disposeAllPlayers() { videoPlayers.clear(); } - public void onDestroy() { + public void performDestroy() { // The whole FlutterView is being destroyed. Here we release resources acquired for all // instances // of VideoPlayer. Once https://github.com/flutter/flutter/issues/19358 is resolved this may @@ -107,8 +116,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()); @@ -144,7 +152,6 @@ public void initialize() { options); } videoPlayers.put(handle.id(), player); - return new TextureMessage.Builder().setTextureId(handle.id()).build(); } @@ -200,6 +207,62 @@ public void setMixWithOthers(@NonNull MixWithOthersMessage arg) { options.mixWithOthers = arg.getMixWithOthers(); } + // Activity Aware + + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); + lifecycle.addObserver(this); + } + + @Override + public void onDetachedFromActivity() {} + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + onAttachedToActivity(binding); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); + } + + // DefaultLifecycleObserver + @Override + public void onResume(@NonNull LifecycleOwner owner) { + recreateAllSurfaces(); + } + + @Override + public void onPause(@NonNull LifecycleOwner owner) { + destroyAllSurfaces(); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + destroyAllSurfaces(); + } + + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + if (lifecycle != null) { + lifecycle.removeObserver(this); + } + } + + private void destroyAllSurfaces() { + for (int i = 0; i < videoPlayers.size(); i++) { + videoPlayers.valueAt(i).pauseSurface(); + } + } + + private void recreateAllSurfaces() { + for (int i = 0; i < videoPlayers.size(); i++) { + videoPlayers.valueAt(i).recreateSurface(flutterState.applicationContext); + } + } + private interface KeyForAssetFn { String get(String asset); } 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 7ff5000d3cf..e9f69a57d36 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 @@ -13,7 +13,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; -import android.graphics.SurfaceTexture; import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackException; @@ -38,8 +37,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; @@ -52,9 +50,7 @@ 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()); @@ -66,7 +62,7 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull() new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -85,7 +81,7 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull() new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -111,7 +107,7 @@ public void videoPlayer_buildsHttpDataSourceFactoryProperlyWhenHttpHeadersNull() new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -135,7 +131,7 @@ public void sendInitializedSendsExpectedEvent_90RotationDegrees() { new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -164,7 +160,7 @@ public void sendInitializedSendsExpectedEvent_270RotationDegrees() { new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -193,7 +189,7 @@ public void sendInitializedSendsExpectedEvent_0RotationDegrees() { new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -222,7 +218,7 @@ public void sendInitializedSendsExpectedEvent_180RotationDegrees() { new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -251,7 +247,7 @@ public void onIsPlayingChangedSendsExpectedEvent() { new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); @@ -296,7 +292,7 @@ public void behindLiveWindowErrorResetsPlayerToDefaultPosition() { new VideoPlayer( fakeExoPlayer, fakeEventChannel, - fakeSurfaceTextureEntry, + fakeSurfaceProducer, fakeVideoPlayerOptions, fakeEventSink, httpDataSourceFactorySpy); diff --git a/packages/video_player/video_player_android/example/android/app/src/test/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java b/packages/video_player/video_player_android/example/android/app/src/test/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java index 750d4a486dd..d41e80cf6d1 100644 --- a/packages/video_player/video_player_android/example/android/app/src/test/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java +++ b/packages/video_player/video_player_android/example/android/app/src/test/java/io/flutter/plugins/videoplayerexample/FlutterActivityTest.java @@ -45,6 +45,6 @@ public void disposeAllPlayers() { engine.destroy(); verify(videoPlayerPlugin, times(1)).onDetachedFromEngine(pluginBindingCaptor.capture()); - verify(videoPlayerPlugin, times(1)).onDestroy(); + verify(videoPlayerPlugin, times(1)).performDestroy(); } } diff --git a/packages/video_player/video_player_android/pubspec.yaml b/packages/video_player/video_player_android/pubspec.yaml index 35c7d0ee55c..b9de2c60ffe 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.4.15 +version: 2.4.16 environment: sdk: ^3.4.0 @@ -20,6 +20,7 @@ flutter: dependencies: flutter: sdk: flutter + flutter_plugin_android_lifecycle: ^2.0.1 video_player_platform_interface: ">=6.1.0 <7.0.0" dev_dependencies: