MediaPlayer中onCompletion和onError的坑点

3,133 阅读1分钟

问题介绍:

在写一个音乐播放器时,遇到一个很莫名奇妙的问题,就是我在播放列表中切换歌曲的时候,歌曲会抽搐了一样的切换好几首。起初以为是Livedata的粘性事件引起的。可是排查了一下,并不是这个原因。于是怀疑是与控制台上报的 Error (-38,0) 的问题有关。

分析

一开始我只为MediaPlayer设置了OnCompletionListener,逻辑如下:

private MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {
        Log.d(TAG, "onCompletion: ");
        //单曲循环
        if (playMode == TYPE_SINGLE) {
            playInner(); //继续播放当前的歌曲
        } else {
            playNextInner(); //播放下一首歌曲
        }
        
    }
};

我在其中加入Log

Log.d(TAG, "onCompletion: ");

果然当出现Error时OnCompletionListener中的onCompletion执行。Error如下:

MediaPlayerNative: Attempt to call getDuration in wrong state: mPlayer=0x70d0559040, mCurrentState=4 error (-38, 0)
MediaPlayer: Error (-38,0)

那么到这就知道为什么之前切歌的时候歌曲会鬼畜了。

原因

这时我有一个大大的疑问:为什么出现error的时候会执行 onCompletion 呢?不应该只有当前歌曲播放结束的时候才执行吗?

于是去查看源码中onCompletion的执行时机

case MEDIA_PLAYBACK_COMPLETE:
    {
        mOnCompletionInternalListener.onCompletion(mMediaPlayer);
        OnCompletionListener onCompletionListener = mOnCompletionListener;
        if (onCompletionListener != null)
            onCompletionListener.onCompletion(mMediaPlayer);
    }
    stayAwake(false);
    return;
    
case MEDIA_ERROR:
    Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
    boolean error_was_handled = false;
    OnErrorListener onErrorListener = mOnErrorListener;
    if (onErrorListener != null) {
        error_was_handled = onErrorListener.onError(mMediaPlayer, msg.arg1, msg.arg2);
    }
    {
    mOnCompletionInternalListener.onCompletion(mMediaPlayer);
    OnCompletionListener onCompletionListener = mOnCompletionListener;
    if (onCompletionListener != null && ! error_was_handled) {
        onCompletionListener.onCompletion(mMediaPlayer);
        }
    }
    stayAwake(false);
    return;

查看源码发现,不仅只是歌曲播放完成时调用onCompletion,如果发生error并且你的Mediaplay并没有设置onErrorListener时 或者 你的OnErrorListener中的onError返回值为false,同样也会调用onCompletion方法。

解决方法

private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() {
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        //你自己的逻辑
        
        //此处返回值需要为true,不然还是会执行OnCompletionListener中的onCompletion方法
        return true;
    }
};