简体   繁体   中英

Android VideoView crash in onPrepared

In my app, I have a bunch of VideoViews inside a RecyclerView list. I am sometimes getting a crash in the VideoView in the Android SDK when it is receiving the onPrepared callback from MediaPlayer. The exception itself is being triggered in native code. This happens only occasionally and I am unable to reliably reproduce it.

Fatal Exception: java.lang.IllegalStateException
    media.MediaPlayer.getVideoWidth (MediaPlayer.java)
    android.widget.VideoView$2.onPrepared (VideoView.java:422)
    android.media.MediaPlayer$EventHandler.handleMessage (MediaPlayer.java:2208)
    android.os.Looper.loop (Looper.java:136)
    android.app.ActivityThread.main (ActivityThread.java:5086)
    java.lang.reflect.Method.invokeNative (Method.java)

It seems to me that this is caused when the MediaPlayer starts loading a video, and then it is somehow invalidated before the video is loaded. Despite being invalidated, onPrepared is called anyways, and VideoView tries to access the video width, causing an exception. The relevant source code from the VideoView.java is:

MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
    public void onPrepared(MediaPlayer mp) {
        mCurrentState = STATE_PREPARED;

        // Get the capabilities of the player for this stream
        Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
                                  MediaPlayer.BYPASS_METADATA_FILTER);

        if (data != null) {
            mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
                    || data.getBoolean(Metadata.PAUSE_AVAILABLE);
            mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
                    || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
            mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
                    || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
        } else {
            mCanPause = mCanSeekBack = mCanSeekForward = true;
        }

        if (mOnPreparedListener != null) {
            mOnPreparedListener.onPrepared(mMediaPlayer);
        }
        if (mMediaController != null) {
            mMediaController.setEnabled(true);
        }
        mVideoWidth = mp.getVideoWidth();
        mVideoHeight = mp.getVideoHeight();

        int seekToPosition = mSeekWhenPrepared;  // mSeekWhenPrepared may be changed after seekTo() call
        if (seekToPosition != 0) {
            seekTo(seekToPosition);
        }
... etc.

I tried to fix it by only calling stopPlayback() after onPrepared() has been received, but it hasn't helped.

Are there any other solutions?

I hacked a (very ugly) workaround by extending VideoView, using reflection to access VideoView's private variables, and replacing the onPrepared listener with a wrapper listener. The wrapper checks for IllegalStateExceptions before passing the callbacks on to VideoView's own listener.

public class CustomVideoView extends VideoView {

    public CustomVideoView(Context context, AttributeSet attrs,
            int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init2();
    }

    public CustomVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init2();
    }

    public CustomVideoView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init2();
    }

    public CustomVideoView(Context context) {
        super(context);
        init2();
    }

    private void init2() {
        final SurfaceHolder.Callback mSHCallback=getPrivateVar("mSHCallback",SurfaceHolder.Callback.class);
        if (mSHCallback==null) return;
        SurfaceHolder.Callback wrapper=new SurfaceHolder.Callback() {
            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                mSHCallback.surfaceDestroyed(holder);
            }

            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                mSHCallback.surfaceCreated(holder);
                postOpenVideo();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width,
                    int height) {
                mSHCallback.surfaceChanged(holder, format, width, height);
            }
        };
        getHolder().addCallback(wrapper);
    }



    @Override
    public void setVideoURI(Uri uri, Map<String, String> headers) {
        super.setVideoURI(uri, headers);
        postOpenVideo();
    }

    @Override
    public void resume() {
        super.resume();
        postOpenVideo();
    }

    protected void postOpenVideo() {
        final MediaPlayer mMediaPlayer=getPrivateVar("mMediaPlayer",MediaPlayer.class);
        final MediaPlayer.OnPreparedListener mPreparedListener=
                getPrivateVar("mPreparedListener",MediaPlayer.OnPreparedListener.class);
        if (mPreparedListener==null||mMediaPlayer==null) return;

        MediaPlayer.OnPreparedListener wrapper=new MediaPlayer.OnPreparedListener() {

            @Override
            public void onPrepared(MediaPlayer mp) {
                try {
                    mp.getVideoWidth();
                    mPreparedListener.onPrepared(mp);
                } catch (IllegalStateException e) {
                }
            }
        };
        mMediaPlayer.setOnPreparedListener(wrapper);        
    }

    private <T> T getPrivateVar(String varName, Class<T> clazz) {
        try {
            Field field = VideoView.class.getDeclaredField(varName);
            field.setAccessible(true);
            Object value = field.get(this);
            field.setAccessible(false);

            return clazz.cast(value);
        } catch (NoSuchFieldException e) {
            return null;
        } catch (IllegalAccessException e) {
            return null;
        } catch (ClassCastException e) {
            return null;
        }
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM