【问题标题】:Android VideoView crash in onPreparedonPrepared 中的 Android VideoView 崩溃
【发布时间】:2015-03-17 19:33:31
【问题描述】:

在我的应用程序中,我在 RecyclerView 列表中有一堆 VideoView。当 Android SDK 中的 VideoView 从 MediaPlayer 接收到 onPrepared 回调时,它有时会崩溃。异常本身是在本机代码中触发的。这只是偶尔发生,我无法可靠地重现它。

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)

在我看来,这是在 MediaPlayer 开始加载视频时引起的,然后在加载视频之前它以某种方式失效。尽管无效,onPrepared 仍然被调用,并且 VideoView 尝试访问视频宽度,导致异常。 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.

我试图通过仅在收到 onPrepared() 后调用 stopPlayback() 来修复它,但它没有帮助。

还有其他解决方案吗?

【问题讨论】:

    标签: java android


    【解决方案1】:

    我通过扩展 VideoView、使用反射来访问 VideoView 的私有变量以及用包装侦听器替换 onPrepared 侦听器来破解一个(非常丑陋的)解决方法。包装器在将回调传递给 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;
            }
        }
    
    }
    

    【讨论】:

    • 以及如何在mainactivity中使用这个类?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-26
    相关资源
    最近更新 更多