手把手教你做音乐播放器(七)播放音乐(上)

第7节 播放音乐

音乐播放列表也准备好了,我们来播放音乐吧。完成后效果如下,

实现音乐的播放,我们需要播放界面和音乐服务两个方面的合作。

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的状态变化。当前要播放的歌曲,将被放在叫做mCurrentMusicItemMusicItem数据结构中,

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编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)
/*******************************************************************/

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值