java - Android VideoView 在 onPrepared 中崩溃

标签 java android

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

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() 来修复它,但没有帮助。

还有其他解决办法吗?

最佳答案

我通过扩展 VideoView、使用反射访问 VideoView 的私有(private)变量并用包装器监听器替换 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;
        }
    }

}

关于java - Android VideoView 在 onPrepared 中崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29073541/

相关文章:

java - 二叉树数组迭代

Android QuickContactBadge 带有类似 Android Lollipop 的字母

android - 如何在没有 root 的情况下使用 adb 授予权限?

java - 嗅探访问过的站点并保存到文件

java - 将 Python 分区生成器转换为 Java

java - if 语句中的条件

java - 使用自上而下方法的 Eclipse 介绍性 JAX-WS 教程

android - 我的设置 Activity 的布局文件在哪里?

android - 线性布局内的两个框架布局

android - 我怎样才能控制android中的动画速度