使用MediaPlayer播放本地音频时报错:
04-22 14:46:59.852 2996 3110 E AndroidRuntime: java.lang.IllegalStateException
04-22 14:46:59.852 2996 3110 E AndroidRuntime: at android.media.MediaPlayer._setDataSource(Native Method)
04-22 14:46:59.852 2996 3110 E AndroidRuntime: at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1154)
04-22 14:46:59.852 2996 3110 E AndroidRuntime: at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1139)
04-22 14:46:59.852 2996 3110 E AndroidRuntime: at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1118)
04-22 14:46:59.852 2996 3110 E AndroidRuntime: at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1067)
04-22 14:46:59.852 2996 3110 E AndroidRuntime: at com.demo.CoreController.startMediaPlayer(CoreController.java:1595)
...
04-22 14:46:59.852 2996 3110 E AndroidRuntime: at java.lang.Thread.run(Thread.java:818)
【原因分析:】
Android的MediaRecorder 和 MediaPlayer (MediaRecorder ,MediaPlayer 都是用c++实现的)API中用到了JNI。此异常的大部分原因是java里面的MediaRecorder /MediaPlayer 对象和native的对象不一致导致的。由于java会分别在堆内存和栈内存中操作(堆中放内容,栈中放地址,栈中的地址指向堆中内容)。在java中释放掉MediaRecorder /MediaPlayer的对象时,释放的是jni的对象,而java中只释放了栈中的内容,而堆中的内容没有释放。说到底就是没释放干净,导致出现对象不一致。
【解决办法:】
在使用setDataSource()前使用reset()方法重置MediaPlayer为未初始化状态(即创建MediaPlayer 后加入的资源路径、流类型等)。
注意:使用reset()方法后必须设置数据源并调用prepare()(同步的方式装载流媒体文件方法)或prepareAsync()(异步的方式装载流媒体文件方法)再次对其进行初始化。
【代码示例:】
注意:在setDataSource()方法的异常里一定要加如下逻辑,目前遇到(低概率)使用reset()方法后底层native状态有时会与当前创建的MediaPlayer对象不一致也会造成IllegalStateException异常,若加上如下规避措施,即使遇到低概率异常也不会影响音频的播放。
//重置MediaPlayer对象,使新的MediaPlayer对象与native状态一致
mMediaPlayer.release();
mMediaPlayer = null;
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(DIR_VOICEPATH + name);
具体如下:
/**
* 通过MediaPlayer开始播放音频
* @param name
*/
public void startMediaPlayer(String name) {
Log.i(TAG, "startMediaPlayer: name = " + name);
try {
if (mMediaPlayer == null) {
Log.i(TAG, "startMediaPlayer: mMediaPlayer == null");
mMediaPlayer = new MediaPlayer();
}
try {
//将MediaPlayer重置为未初始化状态,避免与底层native状态不一致导致IllegalStateException
mMediaPlayer.reset();
//传参为资源路径,例如D:/WorkSpace/ASProjects/test.mp3
mMediaPlayer.setDataSource(DIR_VOICEPATH + name);
} catch (Exception e) {
Log.i(TAG, "startMediaPlayer reset:Exception --- " + e.toString());
//若setDataSource过程产生异常,首先检测本地文件是否存在
File file = new File(DIR_VOICEPATH + name);
if (!file.exists()) {
Log.e(TAG, "startMediaPlayer: fileIsNotExists" );
checkCopyLocalWakeTTS(mContext);
} else {
Log.i(TAG, "startMediaPlayer: fileExists");
}
//重置MediaPlayer对象,使新的MediaPlayer对象与native状态一致
mMediaPlayer.release();
mMediaPlayer = null;
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(DIR_VOICEPATH + name);
}
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
//通过异步的方式装载媒体资源
mMediaPlayer.prepareAsync();
//为防止媒体资源未装载完直接调用start()报错,使用setOnPreparedListener事件待加载完后执行播放
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.i(TAG, "startMediaPlayer onPrepared: MediaPlayerStart");
//装载完成后执行播放
mMediaPlayer.start();
}
});
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "startMediaPlayer 本地唤醒音频播放完毕");
//播放完成后释放MediaPlayer
if (!mMediaPlayer.isPlaying()) {
Log.i(TAG, "startMediaPlayer onCompletion: isNotPlaying");
mMediaPlayer.release();
mMediaPlayer = null;
}
startASR();
}
});
//使用reset()方法产生异常后回调此方法
mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.i(TAG, "startMediaPlayer onError: ");
//本地唤醒音频问题导致异常后不应影响后续逻辑,启动识别
startASR();
return false;
}
});
} catch (Exception e) {
Log.e(TAG, "startMediaPlayer: Exception ---" + e.toString() );
//本地唤醒音频问题导致异常后不应影响后续逻辑,启动识别
startASR();
}
}