Skip to content

Commit

Permalink
Prevent error msg: 'Unrecoverable player error occurred' while playin…
Browse files Browse the repository at this point in the history
…g 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
  google/ExoPlayer#2703 (comment)
  • Loading branch information
evermind-zz committed Jun 19, 2021
1 parent d6e0bd8 commit 2a68475
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 2 deletions.
41 changes: 39 additions & 2 deletions app/src/main/java/org/schabi/newpipe/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
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;
Expand Down Expand Up @@ -112,6 +113,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -489,7 +492,7 @@ private void initPlayer(final boolean playOnReady) {
registerBroadcastReceiver();

// Setup video view
simpleExoPlayer.setVideoSurfaceView(binding.surfaceView);
setupVideoSurface();
simpleExoPlayer.addVideoListener(this);

// Setup subtitle view
Expand Down Expand Up @@ -768,8 +771,12 @@ private void destroyPlayer() {
if (DEBUG) {
Log.d(TAG, "destroyPlayer() called");
}

cleanupVideoSurface();

if (!exoPlayerIsNull()) {
simpleExoPlayer.removeListener(this);
simpleExoPlayer.removeVideoListener(this);
simpleExoPlayer.stop();
simpleExoPlayer.release();
}
Expand Down Expand Up @@ -4232,6 +4239,36 @@ public ExpandableSurfaceView getSurfaceView() {
public PlayQueueAdapter getPlayQueueAdapter() {
return playQueueAdapter;
}

//endregion


//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
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}

0 comments on commit 2a68475

Please sign in to comment.