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 af04695c070..7fdcd60c2f6 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,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -77,6 +79,7 @@ import com.google.android.exoplayer2.ui.SubtitleView; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.DummySurface; import com.google.android.exoplayer2.video.VideoListener; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.nostra13.universalimageloader.core.ImageLoader; @@ -267,6 +270,7 @@ public final class Player implements private SimpleExoPlayer simpleExoPlayer; private AudioReactor audioReactor; private MediaSessionManager mediaSessionManager; + private SurfaceHolderCallback surfaceHolderCallback; @NonNull private final CustomTrackSelector trackSelector; @NonNull private final LoadController loadController; @@ -489,7 +493,7 @@ private void initPlayer(final boolean playOnReady) { registerBroadcastReceiver(); // Setup video view - simpleExoPlayer.setVideoSurfaceView(binding.surfaceView); + setupVideoSurface(); simpleExoPlayer.addVideoListener(this); // Setup subtitle view @@ -768,8 +772,12 @@ private void destroyPlayer() { if (DEBUG) { Log.d(TAG, "destroyPlayer() called"); } + + cleanupVideoSurface(); + if (!exoPlayerIsNull()) { simpleExoPlayer.removeListener(this); + simpleExoPlayer.removeVideoListener(this); simpleExoPlayer.stop(); simpleExoPlayer.release(); } @@ -4234,4 +4242,86 @@ public PlayQueueAdapter getPlayQueueAdapter() { } //endregion + + + + //region SurfaceHandlerCallback + /** + * 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 + */ + private final class SurfaceHolderCallback implements SurfaceHolder.Callback { + + private final SimpleExoPlayer player; + private DummySurface dummySurface; + + SurfaceHolderCallback(final SimpleExoPlayer player) { + 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; + } + } + } + + private void setupVideoSurface() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // >=API23 + surfaceHolderCallback = new SurfaceHolderCallback(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 SurfaceHandlerCallback }