简体   繁体   English

Android VideoView在onPrepared中崩溃

[英]Android VideoView crash in onPrepared

In my app, I have a bunch of VideoViews inside a RecyclerView list. 在我的应用程序中,我在RecyclerView列表中有一堆VideoViews。 I am sometimes getting a crash in the VideoView in the Android SDK when it is receiving the onPrepared callback from MediaPlayer. 从MediaPlayer接收onPrepared回调时,有时我会在Android SDK的VideoView中崩溃。 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. 在我看来,这是由MediaPlayer开始加载视频引起的,然后在加载视频之前以某种方式使它无效。 Despite being invalidated, onPrepared is called anyways, and VideoView tries to access the video width, causing an exception. 尽管无效,但仍然会调用onPrepared,并且VideoView会尝试访问视频宽度,从而导致异常。 The relevant source code from the VideoView.java is: VideoView.java的相关源代码为:

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. 我试图通过仅在收到onPrepared()之后才调用stopPlayback()来修复它,但是它没有帮助。

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. 我通过扩展VideoView,使用反射来访问VideoView的私有变量,并用包装监听器替换onPrepared监听器,从而破解了一个(非常丑陋的)解决方法。 The wrapper checks for IllegalStateExceptions before passing the callbacks on to VideoView's own listener. 包装程序在将回调传递给VideoView自己的侦听器之前检查IllegalStateExceptions。

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

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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