MediaPlayer中onCompletion和onError的坑

问题:

在做音乐播放功能时,发现点击下一曲或者上一曲有时会跳过曲目播放,通过检查下一曲next()和上一曲pre()方法逻辑并没有发现什么问题,为什么会出现这种情况呢?

基本功能:

1.可以手动点击上一曲下一曲
2.设置OnCompletionListener当播放完成时自动播放下一曲
3.播放时通过Handler循环获取当前播放进度和总时长更新视图

解决过程:

分析:通过打印日志发现,onCompletion方法在歌曲未播放完成时被调用了!很奇怪的问题,常识上来讲,只有音乐播放完成时此方法才会被调用。百思不得其解的情况下,忽然想到代码中只设置了OnPreparedListener和OnCompletionListener会不会是播放过程中发生了什么错误?于是设置OnErrorListener打印日志同时当错误回调时也执行下一曲:
这里写图片描述

这里写图片描述

嗯哼?position:6位置的确发生了错误直接跳到了7位置准备播放。but,刚刚提到的问题是onCompletion被调用了,怎么添加了OnErrorListener后onCompletion不再回调,只回调了onError?

不理解,好吧,翻一下源码。于是,MediaPlayer的Handler处理消息有这么一段代码
这里写图片描述

也就是说当有设置MediaPlayer的OnErrorListener时就在调用onError方法,error没有被处理,即没有设置错误监听或者onError方法返回false(通过log验证结论),就调用OnCompletionListener的onCompletion方法。

好吧,道理我都懂,但是哪里产生的错误呢?回到onError中打印日志时顺带把错误代码打印了一下发现错误代码-38,搜索了一下网上的答案大概有如下结论:

1) 当一个MediaPlayer对象被新建或调用reset()方法之后,它处于空闲状态,在调用release方法之后,才会处于结束状态。
2) 一个新建的MediaPlayer对象在调用getCurrenProgress()、getDuration、
getVideoHeight()、getVideoWith()、setAudioStreamType(int)、
setLooping(boolean)、setVolume(float,float)、pause()、start()、stop()、
seekTo()、prepare()、prepareAsync()方法时,不会触发OnErrorListenerError()事件,但是
MediaPlayer对象如果调用了reset()方法后,再使用这些方法则会触发OnErrorListenerError()事件。

以上结论不一定都准确,但给了一个参考方向。
过滤了一遍代码,又根据log中error出现位置的不确定性,终于确定是因为handler中循环调用getDuration,当点击下一曲后MediaPlayer已释放资源,下一曲资源还没有准备完成时,刚好handleMessage中调用了getDuration就会发生错误。于是在点击时先移除了相应的消息,完美解决了问题!

总之,在onCompletion中完成相应的业务逻辑时一定要谨慎,最好要添加OnErrorListener监听错误信息并返回true。