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
pull/6503/head
evermind 2021-06-03 11:32:10 +02:00
rodzic d6e0bd8c26
commit f78d2a5ed8
2 zmienionych plików z 104 dodań i 2 usunięć

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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.
* <p>
* 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.
* <p>
* This implementation is based on:
* 'ExoPlayer stuck in buffering after re-adding the surface view a few time #2703'
* <p>
* -> 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;
}
}
}