From f78d2a5ed8b629e328dd48474fa9032a6ac1a64f Mon Sep 17 00:00:00 2001 From: evermind Date: Thu, 3 Jun 2021 11:32:10 +0200 Subject: [PATCH] Prevent error msg: 'Unrecoverable player error occurred' while playing video during rotation (#6502) Playing a video in VideoDetailFragment and rotating the screen to landscape (back and forth more often) can trigger this error message. Especially if rotation for whatever reason takes long or playing a high resolution (1080p) video. The underlying logcat error messages: 05-12 16:38:38.251 24920 26037 E Surface : getSlotFromBufferLocked: unknown buffer: 0x923fc810 05-12 16:38:38.251 24920 26037 W ACodec : [OMX.qcom.video.decoder.avc] can not return buffer 35 to native window The problem is that that Exoplayer is trying to write to our -- during rotation -- no longer existant (VideoDetailFragment) SurfaceView. Solution: Implementing SurfaceHolder.Callback and using DummySurface we can now handle the lifecycle of the Surface. How?: In case we are no longer able to write to the Surface eg. through rotation/putting in background we can set a DummySurface. Although it only works on API >= 23. Result: we get a little video interruption (audio is still fine) but we won't get the 'Unrecoverable player error occurred' error message. This implementation is based on and more background information: 'ExoPlayer stuck in buffering after re-adding the surface view a few time 2703' -> exoplayer fix suggestion link https://github.com/google/ExoPlayer/issues/2703#issuecomment-300599981 --- .../org/schabi/newpipe/player/Player.java | 44 ++++++++++++- .../playback/SurfaceHolderCallback.java | 62 +++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java index af04695c0..f8265841c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/Player.java +++ b/app/src/main/java/org/schabi/newpipe/player/Player.java @@ -33,6 +33,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -112,6 +113,7 @@ import org.schabi.newpipe.player.playback.CustomTrackSelector; import org.schabi.newpipe.player.playback.MediaSourceManager; import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.player.playback.PlayerMediaSession; +import org.schabi.newpipe.player.playback.SurfaceHolderCallback; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueueAdapter; import org.schabi.newpipe.player.playqueue.PlayQueueItem; @@ -267,6 +269,7 @@ public final class Player implements private SimpleExoPlayer simpleExoPlayer; private AudioReactor audioReactor; private MediaSessionManager mediaSessionManager; + @Nullable private SurfaceHolderCallback surfaceHolderCallback; @NonNull private final CustomTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -489,7 +492,7 @@ public final class Player implements registerBroadcastReceiver(); // Setup video view - simpleExoPlayer.setVideoSurfaceView(binding.surfaceView); + setupVideoSurface(); simpleExoPlayer.addVideoListener(this); // Setup subtitle view @@ -768,8 +771,12 @@ public final class Player implements if (DEBUG) { Log.d(TAG, "destroyPlayer() called"); } + + cleanupVideoSurface(); + if (!exoPlayerIsNull()) { simpleExoPlayer.removeListener(this); + simpleExoPlayer.removeVideoListener(this); simpleExoPlayer.stop(); simpleExoPlayer.release(); } @@ -4232,6 +4239,39 @@ public final class Player implements public PlayQueueAdapter getPlayQueueAdapter() { return playQueueAdapter; } - //endregion + + + /*////////////////////////////////////////////////////////////////////////// + // SurfaceHolderCallback helpers + //////////////////////////////////////////////////////////////////////////*/ + //region SurfaceHolderCallback helpers + private void setupVideoSurface() { + // make sure there is nothing left over from previous calls + cleanupVideoSurface(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(context, simpleExoPlayer); + binding.surfaceView.getHolder().addCallback(surfaceHolderCallback); + final Surface surface = binding.surfaceView.getHolder().getSurface(); + // initially set the surface manually otherwise + // onRenderedFirstFrame() will not be called + simpleExoPlayer.setVideoSurface(surface); + } else { + simpleExoPlayer.setVideoSurfaceView(binding.surfaceView); + } + } + + private void cleanupVideoSurface() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + if (surfaceHolderCallback != null) { + if (binding != null) { + binding.surfaceView.getHolder().removeCallback(surfaceHolderCallback); + } + surfaceHolderCallback.release(); + surfaceHolderCallback = null; + } + } + } + //endregion SurfaceHolderCallback helpers } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java b/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java new file mode 100644 index 000000000..0814092fa --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/playback/SurfaceHolderCallback.java @@ -0,0 +1,62 @@ +package org.schabi.newpipe.player.playback; + +import android.content.Context; +import android.view.SurfaceHolder; + +import com.google.android.exoplayer2.SimpleExoPlayer; +import com.google.android.exoplayer2.video.DummySurface; + +/** + * Prevent error message: 'Unrecoverable player error occurred' + * In case of rotation some users see this kind of an error which is preventable + * having a Callback that handles the lifecycle of the surface. + *

+ * How?: In case we are no longer able to write to the surface eg. through rotation/putting in + * background we set set a DummySurface. Although it it works on API >= 23 only. + * Result: we get a little video interruption (audio is still fine) but we won't get the + * 'Unrecoverable player error occurred' error message. + *

+ * This implementation is based on: + * 'ExoPlayer stuck in buffering after re-adding the surface view a few time #2703' + *

+ * -> exoplayer fix suggestion link + * https://github.com/google/ExoPlayer/issues/2703#issuecomment-300599981 + */ +public final class SurfaceHolderCallback implements SurfaceHolder.Callback { + + private final Context context; + private final SimpleExoPlayer player; + private DummySurface dummySurface; + + public SurfaceHolderCallback(final Context context, final SimpleExoPlayer player) { + this.context = context; + this.player = player; + } + + @Override + public void surfaceCreated(final SurfaceHolder holder) { + player.setVideoSurface(holder.getSurface()); + } + + @Override + public void surfaceChanged(final SurfaceHolder holder, + final int format, + final int width, + final int height) { + } + + @Override + public void surfaceDestroyed(final SurfaceHolder holder) { + if (dummySurface == null) { + dummySurface = DummySurface.newInstanceV17(context, false); + } + player.setVideoSurface(dummySurface); + } + + public void release() { + if (dummySurface != null) { + dummySurface.release(); + dummySurface = null; + } + } +}