项目中需要用到把播放离线音乐设计成可以同时播放在线音乐和离线音乐的. onPrepareAsync 之前弄过,但是内部有很多错误, 所以一直使用onPrepare, 但是因为在网络差的环境中会阻塞线程, 痛定思痛花了心思研究了下,发现可以通过对播放错误进行处理, 结果完美解决.
下面是一些代码总结和心得:
点击音乐的时候需要song的集合传入, 这样虽然会播放歌曲但是会打开一个播放队列, 点击随机播放按钮的时候,这个队列会被打乱顺序, 取消随机播放的时候又会调成正常顺序. 会命令service在handler子线程中发送play_song , 让主线程处理. 在这里我考虑到方便项目维护方便, 所以在主线程处理的时候判断当前音乐是否在线音乐, 离线音乐的时候核心代码如下, 通过返回值判断歌曲是否能播放, 返回false时候弹toast 提示.
private boolean setDataSourceImpl(@NonNull final MediaPlayer player, @NonNull final String path) {
if (context == null) {
return false;
}
player.setOnCompletionListener(this);
player.setOnErrorListener(this);
try {
// player.stop();
player.reset();
player.setOnPreparedListener(null);
if (path.startsWith("content://")) {
player.setDataSource(context, Uri.parse(path));
} else {
player.setDataSource(path);
}
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.prepareAsync();
player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
MusicPlayerRemote.musicService.updateBeforeStart();
player.start();
UpdateStateUtil.updateNotifyCation();
}
});
} catch (Exception e) {
return false;
}
return true;
}
同时离线音乐返回true时候 完成准备播放下一首的逻辑
@Override
public void setNextDataSource(@Nullable final String path, Song song) {
if (context == null) {
return;
}
try {
mCurrentMediaPlayer.setNextMediaPlayer(null);
} catch (IllegalArgumentException e) {
return;
} catch (IllegalStateException e) {
return;
}
if (mNextMediaPlayer != null) {
mNextMediaPlayer.release();
mNextMediaPlayer = null;
}
if (path == null) {
return;
}
if (PreferenceUtil.getInstance(context).gaplessPlayback()) {
mNextMediaPlayer = new MediaPlayer();
mNextMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
if (setDataSourceImpl(mNextMediaPlayer, path)) {
try {
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
} catch (@NonNull IllegalArgumentException | IllegalStateException e) {
Log.e(TAG, "setNextDataSource: setNextMediaPlayer()", e);
if (mNextMediaPlayer != null) {
mNextMediaPlayer.release();
mNextMediaPlayer = null;
}
}
} else {
if (mNextMediaPlayer != null) {
mNextMediaPlayer.release();
mNextMediaPlayer = null;
}
}
}
而在播放在线音乐的时候 主要逻辑如下
@Override
public boolean playNetMusic(String path) {
if (mCurrentMediaPlayer != null) {
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = null;
}
mCurrentMediaPlayer = new MediaPlayer();
try {
mCurrentMediaPlayer.stop();
mCurrentMediaPlayer.reset();
mCurrentMediaPlayer.setDataSource(path);
mCurrentMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mCurrentMediaPlayer.prepareAsync();
mCurrentMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mCurrentMediaPlayer.start();
UpdateStateUtil.updateNotifyCation();
//处理音频信号变得嘈杂的
MusicPlayerRemote.musicService.onPlayUpdate();
//在线音乐播放准备完成 准备播放下一首
MusicPlayerRemote.musicService.prepareNextImpl();
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName());
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
context.sendBroadcast(intent);
}
});
mCurrentMediaPlayer.setOnErrorListener(this);
mCurrentMediaPlayer.setOnCompletionListener(this);
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
每次开启播放队列的时候就会,如果不重新释放掉重建会报IllegalstateException的错误.
AudioManager.ACTION_AUDIO_BECOMING_NOISY介绍
广播intent,提示应用程序音频信号由于音频输出的变化将变得“嘈杂”。
例如,当拔出一个有线耳机,或断开一个支持A2DP的音频接收器,这个intent就会被发送,且音频系统将自动切换音频线路到扬声器。收到这个intent后,控制音频流的应用程序会考虑暂停,减小音量或其他措施,以免扬声器的声音使用户惊奇。
当在线音乐播放成功的时候准备下一首播放的逻辑
public boolean prepareNextImpl() {
try {
int nextPosition = getNextPosition(false);
String path = getTrackUri(getSongAt(nextPosition));
playback.setNextDataSource(path, getSongAt(nextPosition));
// notifyChange(META_CHANGED);
this.nextPosition = nextPosition;
return true;
} catch (Exception e) {
return false;
}
}
关于mediaplayer isPlaying 或者getPosition(获取在track的播放位置)中的bug,通过报非法状态的时候释放然后重新建可以解决这个bug
@Override
public boolean isPlaying() {
try {
if (MusicPlayerRemote.getCurrentSong().isNetMusic()) {
return mCurrentMediaPlayer != null && mCurrentMediaPlayer.isPlaying();
} else {
return mIsInitialized && mCurrentMediaPlayer != null && mCurrentMediaPlayer.isPlaying();
}
} catch (IllegalStateException e) {
if (mCurrentMediaPlayer != null) {
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = null;
}
mCurrentMediaPlayer = new MediaPlayer();
mCurrentMediaPlayer.reset();
mCurrentMediaPlayer.pause();
Logger.d("ILLEGAL?");
return false;
}
}
点暂停键:
public boolean pause() {
try {
mCurrentMediaPlayer.pause();
return true;
} catch (IllegalStateException e) {
return false;
}
}
点播放键
**判断是否抢到音频焦点, 当是离线音乐的时候并且mediaplayer没有好的时候重新播放 , 否则直接点start播放就可以了
这里有个问题就是start播放的时候mediaplayer有可能并没有准备好会导致-38,0错误, 但是这个问题偶尔会发生.**
public void play() {
if (requestFocus()) {
int playSuccessful = SettingManager.getSetting(getApplicationContext(), "play_successful", 0);
SettingManager.saveSetting(getApplicationContext(), "play_successful", ++playSuccessful);
if (playback == null) {
return;
}
if (!playback.isPlaying()) {
if (!MusicPlayerRemote.getCurrentSong().isNetMusic() && !playback.isInitialized()) {
playSongAt(getPosition());
} else {
playback.start();
if (!becomingNoisyReceiverRegistered) {
registerReceiver(becomingNoisyReceiver, becomingNoisyReceiverIntentFilter);
becomingNoisyReceiverRegistered = true;
}
if (notHandledMetaChangedForCurrentTrack) {
handleChangeInternal(META_CHANGED);
notHandledMetaChangedForCurrentTrack = false;
}
notifyChange(PLAY_STATE_CHANGED);
// fixes a bug where the volume would stay ducked because the AudioManager.AUDIOFOCUS_GAIN event is not sent
playerHandler.removeMessages(DUCK);
playerHandler.sendEmptyMessage(UNDUCK);
}
UpdateStateUtil.updateNotifyCation();
}
} else {
Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show();
}
// }
}
错误回调:
/**
* {@inheritDoc}
*/
@Override
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
mIsInitialized = false;
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = new MediaPlayer();
mCurrentMediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK);
if (context != null) {
Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
// Toast.makeText(context, "播放错误 what :" + what + " extra " + extra, Toast.LENGTH_SHORT).show();
}
return true;
}
完成回调
/**
* {@inheritDoc}
*/
@Override
public void onCompletion(final MediaPlayer mp) {
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
Logger.d("mp =current , but next not null");
mIsInitialized = false;
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = mNextMediaPlayer;
mIsInitialized = true;
mNextMediaPlayer = null;
if (callbacks != null)
callbacks.onTrackWentToNext();
} else {
Logger.d(" next null");
if (callbacks != null)
callbacks.onTrackEnded();
}
项目在播放在线音乐的时候,onprepared回调调用的时候需要时间,不像本地音乐这样快, 所以我在点击目的歌曲的时候更新歌曲名字 ,歌手信息 , 此时mediaplayer还没有准备完成,如果调用mediaplay.getposition 等会导致非法状态
后来只能在播放的时候获取, 在未播放的时候,用成员变量 mPosition和mDuration 保存, 在播放器还未准备好和暂停的时候直接返回. 这样进度条可以紧跟播放器走. 而在初始化的时候 直接把position设置为0, duration设置为歌曲的duration时长.
@Override
public int position() {
if (MusicPlayerRemote.getCurrentSong().isNetMusic()) {
if (mCurrentMediaPlayer == null) {
return -1;
}
try {
if (mCurrentMediaPlayer.isPlaying()) {
mPosition = mCurrentMediaPlayer.getCurrentPosition();
}
} catch (IllegalStateException e) {
Logger.d(" becauseIllegalState");
return -1;
}
return mPosition;
} else {
if (!mIsInitialized) {
return -1;
}
try {
Logger.d(mCurrentMediaPlayer.getCurrentPosition());
return mCurrentMediaPlayer.getCurrentPosition();
} catch (IllegalStateException e) {
Logger.d(" becauseIllegalState");
return -1;
}
}
}