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.