打造 Android 网络音乐播放器 [1]
前言
大二的时候做过两个音乐播放器的项目,虽然功能都按需求实现了,但是因为开发比较赶,后面功能加起来的时候,代码就非常乱了,可见架构的重要性,这也是我这个野生程序员比较欠缺的。最近实习比较闲,就有了重写一个音乐播放器并且写一个系列博客的想法,一是可以提升技术,二是可以锻炼下写东西的能力,三是希望以后找工作能有帮助。
http://www.vvvdj.com/showapp/
大二暑假做的一个外包,有兴趣的可以看看哈哈
开发环境
Mac OS X 10.11
Android Studio 2.1.1
实现功能
- 基础的用户系统
- 我喜欢,用户歌单
- 排行榜,歌单广场
- 在线播放,歌词,下载
- 待添加
今天
今天的目标是先把播放的Service基础写好,发送播放事件可以播放歌曲。
开码
1.首先创建项目这个就不用说了,下面是程序的基本结构。
项目结构 代码结构2.这个要解释一下先 ManagedMediaPlayer 这个类是继承了系统的 MediaPlayer,重写了基本的播放控制函数,加上了一些状态。
MusicPlayer 这个类就是把播放控制的功能封装了。
PlayerService 就一直在后台接受播放控制的事件,消息传递用的是 EventBus,
接受到播放消息后就调用 MusicPlayer 的播放方法。
https://github.com/greenrobot/EventBus
EventBus 是一个 Android 端优化的 publish/subscribe 消息总线,简化了应用程序内各组件间、组件与后台线程间的通信。比如请求网络,等网络返回时通过 Handler 或 Broadcast 通知 UI,两个 Fragment 之间需要通过 Listener 通信,这些需求都可以通过 EventBus 实现。
3.我们先编写 player 包下面的 ManagedMediaPlayer,这个类是继承了 MediaPlayer,主要是实现了拓展一些播放状态。
/**
* Created by MASAILA on 16/5/13.
* 继承 MediaPlayer 拓展了状态功能
*/
public class ManagedMediaPlayer extends MediaPlayer implements MediaPlayer.OnCompletionListener {
public enum Status {
IDLE, INITIALIZED, STARTED, PAUSED, STOPPED, COMPLETED
}
private Status mState;
private OnCompletionListener mOnCompletionListener;
public ManagedMediaPlayer() {
super();
mState = Status.IDLE;
super.setOnCompletionListener(this);
}
@Override
public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
super.setDataSource(path);
mState = Status.INITIALIZED;
}
@Override
public void start() {
super.start();
mState = Status.STARTED;
}
@Override
public void setOnCompletionListener(OnCompletionListener listener) {
this.mOnCompletionListener = listener;
}
@Override
public void onCompletion(MediaPlayer mp) {
mState = Status.COMPLETED;
if (mOnCompletionListener != null) {
mOnCompletionListener.onCompletion(mp);
}
}
@Override
public void stop() throws IllegalStateException {
super.stop();
mState = Status.STOPPED;
}
@Override
public void pause() throws IllegalStateException {
super.pause();
mState = Status.PAUSED;
}
public Status getState() {
return mState;
}
public boolean isComplete() {
return mState == Status.COMPLETED;
}
}
代码很简单,应该就不用解释了吧,就是加多了个状态,方便上层的判断。
4.然后就是 MusicPlayer 这个类了,这个类是是把播放控制的代码封装起来了,还有播放队列的控制。
public class MusicPlayer implements MediaPlayer.OnCompletionListener {
private static MusicPlayer player = new MusicPlayer();
private MediaPlayer mMediaPlayer;
private Context mContext;
private List<Song> mQueue;
private int mQueueIndex;
private PlayMode mPlayMode;
private enum PlayMode {
LOOP, RANDOM, REPEAT
}
public static MusicPlayer getPlayer() {
return player;
}
public static void setPlayer(MusicPlayer player) {
MusicPlayer.player = player;
}
public MusicPlayer() {
mMediaPlayer = new ManagedMediaPlayer();
mMediaPlayer.setOnCompletionListener(this);
mQueue = new ArrayList<>();
mQueueIndex = 0;
mPlayMode = PlayMode.LOOP;
}
public void setQueue(List<Song> queue, int index) {
mQueue = queue;
mQueueIndex = index;
play(getNowPlaying());
}
public void play(Song song) {
try {
mMediaPlayer.reset();
mMediaPlayer.setDataSource(song.getPath());
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mMediaPlayer.start();
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
public void pause() {
mMediaPlayer.pause();
}
public void resume() {
mMediaPlayer.start();
}
public void next() {
play(getNextSong());
}
public void previous() {
play(getPreviousSong());
}
@Override
public void onCompletion(MediaPlayer mp) {
next();
}
private Song getNowPlaying() {
if (mQueue.isEmpty()) {
return null;
}
return mQueue.get(mQueueIndex);
}
private Song getNextSong() {
if (mQueue.isEmpty()) {
return null;
}
switch (mPlayMode) {
case LOOP:
return mQueue.get(getNextIndex());
case RANDOM:
return mQueue.get(getRandomIndex());
case REPEAT:
return mQueue.get(mQueueIndex);
}
return null;
}
private Song getPreviousSong() {
if (mQueue.isEmpty()) {
return null;
}
switch (mPlayMode) {
case LOOP:
return mQueue.get(getPreviousIndex());
case RANDOM:
return mQueue.get(getRandomIndex());
case REPEAT:
return mQueue.get(mQueueIndex);
}
return null;
}
public int getCurrentPosition() {
if (getNowPlaying() != null) {
return mMediaPlayer.getCurrentPosition();
}
return 0;
}
public int getDuration() {
if (getNowPlaying() != null) {
return mMediaPlayer.getDuration();
}
return 0;
}
public PlayMode getPlayMode() {
return mPlayMode;
}
public void setPlayMode(PlayMode playMode) {
mPlayMode = playMode;
}
private int getNextIndex() {
mQueueIndex = (mQueueIndex + 1) % mQueue.size();
return mQueueIndex;
}
private int getPreviousIndex() {
mQueueIndex = (mQueueIndex - 1) % mQueue.size();
return mQueueIndex;
}
private int getRandomIndex() {
mQueueIndex = new Random().nextInt(mQueue.size()) % mQueue.size();
return mQueueIndex;
}
private void release() {
mMediaPlayer.release();
mMediaPlayer = null;
mContext = null;
}
}
播放队列的控制,就是最开始传进来一个 list mQueue,然后通过更改 mQueueIndex 这个变量控制播放的歌曲。
5.然后我们在 PlayerService 里面写一个 EventBus 的接收函数,然后调用 MusicPlayer 里面的 setQueue 就可以播放了。
public class PlayerService extends Service {
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
EventBus.getDefault().register(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
//接收EventBus post过来的PlayEvent
@Subscribe
public void onEvent(PlayEvent playEvent) {
switch (playEvent.getAction()) {
case PLAY:
MusicPlayer.getPlayer().setQueue(playEvent.getQueue(), 0);
break;
case NEXT:
MusicPlayer.getPlayer().next();
break;
}
}
}
6.最后我们在 Activity 里面 post 一个 PlayEvent 事件就ok了。
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ButterKnife.bind(this);
startService(new Intent(this, PlayerService.class));
}
@OnClick({R.id.button_play, R.id.button_next, R.id.button_seek})
public void onClick(View view) {
PlayEvent playEvent;
switch (view.getId()) {
case R.id.button_play:
playEvent = new PlayEvent();
List<Song> queue = new ArrayList<>();
queue.add(getSong("http://m2.music.126.net/AuxCK2R5aJlTETgi8kwN3g==/5923069139252948.mp3"));
queue.add(getSong("http://m2.music.126.net/AuxCK2R5aJlTETgi8kwN3g==/5923069139252948.mp3"));
playEvent.setAction(PlayEvent.Action.PLAY);
playEvent.setQueue(queue);
EventBus.getDefault().post(playEvent);
break;
}
}
private Song getSong(String url) {
Song song = new Song();
song.setPath(url);
return song;
}
}
下面是 Song 和 PlayEvent 的代码。
public class Song {
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
public class PlayEvent {
public enum Action {
PLAY, STOP, RESUME, NEXT, PREVIOES, SEEK
}
private Action mAction;
private Song mSong;
private List<Song> mQueue;
private int seekTo;
public Song getSong() {
return mSong;
}
public void setSong(Song song) {
mSong = song;
}
public Action getAction() {
return mAction;
}
public void setAction(Action action) {
mAction = action;
}
public List<Song> getQueue() {
return mQueue;
}
public void setQueue(List<Song> queue) {
mQueue = queue;
}
public int getSeekTo() {
return seekTo;
}
public void setSeekTo(int seekTo) {
this.seekTo = seekTo;
}
}
7.然后就可以听到美妙的音乐了哈哈哈。
然而什么都看不到哈哈哈mp3的文件地址可能会失效,可以自己添加一个,或者在这里拿到最新的
http://music.163.com/api/song/detail?id=28755558&ids=[28755558]
https://github.com/MASAILA/Oink/tree/9ea6f1d36e7acba41c252dccd6f7ae5464c45195
今天的代码都在 github 上,有什么问题欢迎留言给我哈哈~