"The surface has been released" inside "surfaceCreated"

bclymer picture bclymer · Aug 26, 2013 · Viewed 11.2k times · Source

I know this is a common question, however this stack trace shows something else is wrong. You can see that even though setDisplay(holder) is called inside of surfaceCreated it still throws IllegalArgumentException. This isn't a rare exception either, yesterday happening ~125,000 times in ~3,000,000 clip views. I can assure you that mCurrentPlayer is initialized correctly as well.

surfaceCreated:

@Override
public void surfaceCreated(SurfaceHolder holder) {
    mIsSurfaceCreated = true;
    mCurrentPlayer.setDisplay(holder);
}

surfaceDestroy:

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    mIsSurfaceCreated = false;

    // Could be called after player was released in onDestroy.
    if (mCurrentPlayer != null) {
        mCurrentPlayer.setDisplay(null);
    }
}

Stacktrace:

java.lang.IllegalArgumentException: The surface has been released
    at android.media.MediaPlayer._setVideoSurface(Native Method)
    at android.media.MediaPlayer.setDisplay(MediaPlayer.java:660)
    at com.xxx.xxx.view.VideoPlayerView.surfaceCreated(VideoPlayerView.java:464)
    at android.view.SurfaceView.updateWindow(SurfaceView.java:543)
    at android.view.SurfaceView.access$000(SurfaceView.java:81)
    at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:169)
    at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:590)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1644)
    at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2505)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:4945)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:511)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
    at dalvik.system.NativeStart.main(Native Method)

Any ideas on what else could be going wrong? Is SurfaceHolder potentially destroying the surface on a background thread and then waiting for the main thread (currently occupied by surfaceCreated) to finish it's block before it can call surfaceDestroyed on the main thread (which I don't even think locks can fix)? Something else?

Update -- After drilling down a little farther I found out what causes "The surface has been released" to be thrown:

Which references android_view_Surface_getSurface which can be found here:

This is where my lack of C++ knowledge hurts, it's looking like it's trying to lock onto the surface, and if it can't the surface returned will be null. Once it gets returned as null, IllegalArgumentException will be thrown.

Answer

E.L.K. picture E.L.K. · Nov 29, 2013

I've just fighted a similar problem.

And my investigation shows, that there is a bug in the SurfaceView, which causes passing invalid surface to the surfaceCreated callback method.

This is the commit in the android repo that fixes it: link.

It seems that fix in android sources was introduced in 4.2 version. And, from crashes of application I see that crash caused by invalid surface occured only on 4.0 and 4.1.

So, I can assume that before 4.0 it was valid to pass invalid surface to the MediaPlayer. And there was change in logic of SurfaceView/MediaPlayer in 4.0 which caused this being valid no more. But code in SurfaceView was not updated before 4.2 (in which this problem in SurfaceView is fixed).

I have checked in git repo of android and really, version tagged android-4.0.1_r1 does not include fix and version tagged android-4.2.1_r1 includes it.

So, to fix it for platforms which doesn't contain fix for it, manual check if surface valid before setting it to the MediaPlayer is needed only for platforms 4.0 and after:

@Override public void surfaceCreated(final SurfaceHolder holder) {
    final Surface surface = holder.getSurface();

    if ( surface == null ) return;

    // on pre Ice Scream Sandwich (4.0) versions invalid surfaces seems to be accepted (or at least do not cause crash)
    final boolean invalidSurfaceAccepted = Build.VERSION.SDK_INT < Build.ICE_CREAM_SANDWICH;
    final boolean invalidSurface = ! surface.isValid();

    if ( invalidSurface && ( ! invalidSurfaceAccepted ) ) return;

    _mediaPlayer.setDisplay(holder);
}

In this way, on older platforms invalid surface will be successfully set to the media player and video will playback, on platforms 4.0-4.1 it will throw invalid surfaces away (and i think that surfaceCreated will be called again with valid surface) and on 4.2 and later surfaceCreated will just not be called with invalid surface.