第7节 播放音乐
音乐播放列表也准备好了,我们来播放音乐吧。完成后效果如下,
![](https://i-blog.csdnimg.cn/blog_migrate/e9585133711f6e2fca689033194963e1.png)
实现音乐的播放,我们需要播放界面和音乐服务两个方面的合作。
7.1 MusicService
前面我们已经为播放音乐的功能在MusicService
中搭建好了框架,下面就要来实现它们了。这些实现接口的函数,我们在命名的时候都用上了xxxInner()
这样的格式,例如playInner()
,这是功能真正实现的地方。
7.1.1 创建播放器
MediaPlayer
是实现音乐播放的核心,所有与播放相关的操作都是通过它来完成的。当MusicService
运行起来的时候,就需要创建它的实例,退出时,要释放MediaPlayer
实例,
public class MusicService extends Service {
......
private MediaPlayer mMusicPlayer;
@Override
public void onCreate() {
super.onCreate();
......
//创建
mMusicPlayer = new MediaPlayer();
......
}
@Override
public void onDestroy() {
super.onDestroy();
......
//释放
mMusicPlayer.release();
}
......
}
7.1.2 监听器
MusicService
提供注册监听器的接口,当播放音乐的状态发生变化的时候,能够实时的将当前的状态传递给关心的组件。
监听器的接口在之前的章节中,我们已经定义成了,
public class MusicService extends Service {
public interface OnStateChangeListenr {
void onPlayProgressChange(MusicItem item);
void onPlay(MusicItem item);
void onPause(MusicItem item);
}
}
当某个组件(例如MusicListActivity
)想要监听MusicServcie
的时候,就要利用registerOnStateChangeListener()
和unregisterOnStateChangeListener()
进行注册。MusicService
把注册的监听器存储到数组列表当中,需要的时候取出来使用,
public class MusicService extends Service {
//创建存储监听器的列表
private List<OnStateChangeListenr> mListenerList = new ArrayList<OnStateChangeListenr>();
private void registerOnStateChangeListenerInner(OnStateChangeListenr l) {
//将监听器添加到列表
mListenerList.add(l);
}
private void unregisterOnStateChangeListenerInner(OnStateChangeListenr l) {
//将监听器从列表中移除
mListenerList.remove(l);
}
@Override
public void onDestroy() {
super.onDestroy();
......
//当MusicService销毁的时候,清空监听器列表
mListenerList.clear();
}
}
7.1.3 控制播放
播放、暂停、播放前一首、播放后一首的功能实现,并通过监听器通知监听者MusicService
的状态变化。当前要播放的歌曲,将被放在叫做mCurrentMusicItem
的MusicItem
数据结构中,
public class MusicService extends Service {
//当前是否为播放暂停状态
private boolean mPaused;
//存放当前要播放的音乐
private MusicItem mCurrentMusicItem;
......
@Override
public void onCreate() {
super.onCreate();
......
mPaused = false;
......
}
......
//将要播放的音乐载入MediaPlayer,但是并不播放
private void prepareToPlay(MusicItem item) {
try {
//重置播放器状态
mMusicPlayer.reset();
//设置播放音乐的地址
mMusicPlayer.setDataSource(MusicService.this, item.songUri);
//准备播放音乐
mMusicPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
//播放音乐,根据reload标志位判断是非需要重新加载音乐
private void playMusicItem(MusicItem item, boolean reload) {
//如果这里传入的是空值,就什么也不做
if(item == null) {
return;
}
if(reload) {
//需要重新加载音乐
prepareToPlay(item);
}
//开始播放,如果之前只是暂停播放,那么音乐将继续播放
mMusicPlayer.start();
//将音乐设置到指定时间开始播放,时间单位为毫秒
seekToInner((int)item.playedTime);
//将播放的状态通过监听器通知给监听者
for(OnStateChangeListenr l : mListenerList) {
l.onPlay(item);
}
//设置为非暂停播放状态
mPaused = false;
}
//播放播放列表中,当前音乐的下一首音乐
private void playNextInner() {
int currentIndex = mPlayList.indexOf(mCurrentMusicItem);
if(currentIndex < mPlayList.size() -1 ) {
//获取当前播放(或者被加载)音乐的下一首音乐
//如果后面有要播放的音乐,把那首音乐设置成要播放的音乐
//并重新加载该音乐,开始播放
mCurrentMusicItem = mPlayList.get(currentIndex + 1);
playMusicItem(mCurrentMusicItem, true);
}
}
private void playInner() {
//如果之前没有选定要播放的音乐,就选列表中的第一首音乐开始播放
if(mCurrentMusicItem == null && mPlayList.size() > 0) {
mCurrentMusicItem = mPlayList.get(0);
}
//如果是从暂停状态恢复播放音乐,那么不需要重新加载音乐;
//如果是从完全没有播放过的状态开始播放音乐,那么就需要重新加载音乐
if(mPaused) {
playMusicItem(mCurrentMusicItem, false);
}
else {
playMusicItem(mCurrentMusicItem, true);
}
}
private void playPreInner() {
int currentIndex = mPlayList.indexOf(mCurrentMusicItem);
if(currentIndex - 1 >= 0 ) {
//获取当前播放(或者被加载)音乐的上一首音乐
//如果前面有要播放的音乐,把那首音乐设置成要播放的音乐
//并重新加载该音乐,开始播放
mCurrentMusicItem = mPlayList.get(currentIndex - 1);
playMusicItem(mCurrentMusicItem, true);
}
}
private void pauseInner() {
//暂停当前正在播放的音乐
mMusicPlayer.pause();
//将播放状态的改变通知给监听者
for(OnStateChangeListenr l : mListenerList) {
l.onPause(mCurrentMusicItem);
}
//设置为暂停播放状态
mPaused = true;
}
private void seekToInner(int pos) {
//将音乐拖动到指定的时间
mMusicPlayer.seekTo(pos);
}
private MusicItem getCurrentMusicInner() {
//返回当前正加载好的音乐
return mCurrentMusicItem;
}
private boolean isPlayingInner() {
//返回当前的播放器是非正在播放音乐
return mMusicPlayer.isPlaying();
}
......
}
7.1.4 通知播放进度
除了控制播放,还要能向监听者报告当前音乐的播放进度。
我们设计的策略是:当音乐开始播放的时候,就每隔一秒钟报告一次当前播放的进度。要实现每隔一秒报告一次,可以通过创建Handler
,每间隔一秒发送一条通知的消息来实现。
public class MusicService extends Service {
......
//定义循环发送的消息
private final int MSG_PROGRESS_UPDATE = 0;
//定义处理消息的Handler
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_PROGRESS_UPDATE: {
//将音乐的时长和当前播放的进度保存到MusicItem数据结构中,
mCurrentMusicItem.playedTime = mMusicPlayer.getCurrentPosition();
mCurrentMusicItem.duration = mMusicPlayer.getDuration();
//通知监听者当前的播放进度
for(OnStateChangeListenr l : mListenerList) {
l.onPlayProgressChange(mCurrentMusicItem);
}
//将当前的播放进度保存到数据库中
updateMusicItem(mCurrentMusicItem);
//间隔一秒发送一次更新播放进度的消息
sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE, 1000);
}
break;
}
}
};
//将播放时间更新到ContentProvider中
private void updateMusicItem(MusicItem item) {
ContentValues cv = new ContentValues();
cv.put(DBHelper.DURATION, item.duration);
cv.put(DBHelper.LAST_PLAY_TIME, item.playedTime);
String strUri = item.songUri.toString();
mResolver.update(PlayListContentProvider.CONTENT_SONGS_URI, cv, DBHelper.SONG_URI + "=\"" + strUri + "\"", null);
}
......
}
当音乐开始播放的时候,触发进度更新;当音乐暂停或者停止的时候,停止进度的更新;
public class MusicService extends Service {
......
private void playMusicItem(MusicItem item, boolean reload) {
......
//移除现有的更新消息,重新启动更新
mHandler.removeMessages(MSG_PROGRESS_UPDATE);
mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE);
......
}
private void pauseInner() {
......
//停止更新
mHandler.removeMessages(MSG_PROGRESS_UPDATE);
......
}
@Override
public void onDestroy() {
super.onDestroy();
......
//停止更新
mHandler.removeMessages(MSG_PROGRESS_UPDATE);
......
}
}
7.1.5 连续播放
我们设计的播放规则是-将播放列表里的音乐按照次序自动播放,直到播完。所以我们要在每一首音乐结束后,自动播放下一首音乐。为此,要给播放器注册一个播放是否完成的监听器,当监听器被触发,开始下一首音乐的播放,
public class MusicService extends Service {
......
@Override
public void onCreate() {
super.onCreate();
......
mMusicPlayer = new MediaPlayer();
mMusicPlayer.setOnCompletionListener(mOnCompletionListener);
......
}
private MediaPlayer.OnCompletionListener mOnCompletionListener = new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
//将当前播放的音乐记录时间重置为0,更新到数据库
//下次播放就可以从头开始
mCurrentMusicItem.playedTime = 0;
updateMusicItem(mCurrentMusicItem);
//播放下一首音乐
playNextInner();
}
};
}
7.1.6 初始化待播音乐
当MusicService
启动之后,要先读取数据库中存储的播放列表,把第一首音乐作为默认的待播放的音乐,
public class MusicService extends Service {
private void initPlayList() {
......
if( mPlayList.size() > 0) {
mCurrentMusicItem = mPlayList.get(0);
}
}
}
7.1.7 添加到播放列表
关于我们定制的规则是这样的:如果用户选择添加一首音乐,那么添加后,自动播放该首音乐;如果用户选择添加一组音乐,那么添加后,自动播放该组音乐列表中的第一首。所以添加播放列表的代码还要做一些修改,
public class MusicService extends Service {
......
private void addPlayListInner(List<MusicItem> items) {
mResolver.delete(PlayListContentProvider.CONTENT_SONGS_URI, null, null);
mPlayList.clear();
for (MusicItem item : items) {
//逐个添加到播放列表,不要求添加后播放
addPlayListInner(item, false);
}
//添加完成后,开始播放
mCurrentMusicItem = mPlayList.get(0);
playInner();
}
//将函数增加了needPlay参数
private void addPlayListInner(MusicItem item, boolean needPlay) {
if(mPlayList.contains(item)) {
return;
}
mPlayList.add(0, item);
insertMusicItemToContentProvider(item);
if(needPlay) {
//添加完成后,开始播放
mCurrentMusicItem = mPlayList.get(0);
playInner();
}
}
......
}
/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。
*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。
*最后再次感谢各位读者对安豆
的支持,谢谢:)
/*******************************************************************/