概述
這是一款Android 端的本地音樂播放器,界面風格有模仿網易雲音樂、bilibili、酷安、酷狗等。整體設計遵循了 Material Design 設計風格,界面美觀,輕便實用,目前實現了基本的播放控制功能,還有主題切換, 可以掃描本地所有音樂文件並按歌單分類播放,目前已經上架到酷安應用市場。
詳細
一、先看效果圖







二、項目的播放流程簡要介紹
1.首先我們需要一個常駐在后台的播放服務,在播放服務中綁定一個播放廣播,我們在打開播放器的時候就啟動這個播放服務。
public class MusicPlayerService extends Service {
private static final String TAG = MusicPlayerService.class.getName();
public static final String PLAYER_MANAGER_ACTION = "com.lijunyan.blackmusic.service.MusicPlayerService.player.action";
private PlayerManagerReceiver mReceiver;
public MusicPlayerService() {
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate: ");
register();
}
@Override
public void onDestroy() {
super.onDestroy();
Log.e(TAG, "onDestroy: ");
unRegister();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
private void register() {
mReceiver = new PlayerManagerReceiver(MusicPlayerService.this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PLAYER_MANAGER_ACTION);
registerReceiver(mReceiver, intentFilter);
}
private void unRegister() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
}
}
2.播放服務中的廣播可以接受各種音頻控制操作,包括播放、暫停、切歌等。程序在響應用戶的音頻控制操作時向這個播放廣播發送對應的播放、暫停、停止等指令。廣播收到不同的指令做不同的功能實現。
public class PlayerManagerReceiver extends BroadcastReceiver {
private static final String TAG = PlayerManagerReceiver.class.getName();
public static final String ACTION_UPDATE_UI_ADAPTER = "com.lijunyan.blackmusic.receiver.PlayerManagerReceiver:action_update_ui_adapter_broad_cast";
private MediaPlayer mediaPlayer;
private DBManager dbManager;
public static int status = Constant.STATUS_STOP;
private int playMode;
private int threadNumber;
private Context context;
public PlayerManagerReceiver() {
}
public PlayerManagerReceiver(Context context) {
super();
this.context = context;
dbManager = DBManager.getInstance(context);
mediaPlayer = new MediaPlayer();
Log.d(TAG, "create");
initMediaPlayer();
}
@Override
public void onReceive(Context context, Intent intent) {
int cmd = intent.getIntExtra(Constant.COMMAND,Constant.COMMAND_INIT);
Log.d(TAG, "cmd = " + cmd);
switch (cmd) {
case Constant.COMMAND_INIT:
Log.d(TAG, "COMMAND_INIT");
break;
case Constant.COMMAND_PLAY:
Log.d(TAG, "COMMAND_PLAY");
status = Constant.STATUS_PLAY;
String musicPath = intent.getStringExtra(Constant.KEY_PATH);
if (musicPath!=null) {
playMusic(musicPath);
}else {
mediaPlayer.start();
}
break;
case Constant.COMMAND_PAUSE:
mediaPlayer.pause();
status = Constant.STATUS_PAUSE;
break;
case Constant.COMMAND_STOP:
NumberRandom();
status = Constant.STATUS_STOP;
if(mediaPlayer!=null) {
mediaPlayer.stop();
}
initStopOperate();
break;
case Constant.COMMAND_PROGRESS://拖動進度
int curProgress = intent.getIntExtra(Constant.KEY_CURRENT, 0);
//異步的,可以設置完成監聽來獲取真正定位完成的時候
mediaPlayer.seekTo(curProgress);
break;
case Constant.COMMAND_RELEASE:
NumberRandom();
status = Constant.STATUS_STOP;
if(mediaPlayer!=null) {
mediaPlayer.stop();
mediaPlayer.release();
}
break;
}
UpdateUI();
}
private void initStopOperate(){
MyMusicUtil.setShared(Constant.KEY_ID,dbManager.getFirstId(Constant.LIST_ALLMUSIC));
}
private void playMusic(String musicPath) {
NumberRandom();
if (mediaPlayer!=null) {
mediaPlayer.release();
}
mediaPlayer = new MediaPlayer();
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "playMusic onCompletion: ");
NumberRandom(); //切換線程
onComplete(); //調用音樂切換模塊,進行相應操作
UpdateUI(); //更新界面
}
});
try {
File file = new File(musicPath);
if(!file.exists()){
Toast.makeText(context,"歌曲文件不存在,請重新掃描",Toast.LENGTH_SHORT).show();
MyMusicUtil.playNextMusic(context);
return;
}
mediaPlayer.setDataSource(musicPath); //設置MediaPlayer數據源
mediaPlayer.prepare();
mediaPlayer.start();
new UpdateUIThread(this, context, threadNumber).start();
} catch (Exception e) {
e.printStackTrace();
}
}
//取一個(0,100)之間的不一樣的隨機數
private void NumberRandom() {
int count;
do {
count =(int)(Math.random()*100);
} while (count == threadNumber);
threadNumber = count;
}
private void onComplete() {
MyMusicUtil.playNextMusic(context);
}
private void UpdateUI() {
Intent playBarintent = new Intent(PlayBarFragment.ACTION_UPDATE_UI_PlayBar); //接收廣播為MusicUpdateMain
playBarintent.putExtra(Constant.STATUS, status);
context.sendBroadcast(playBarintent);
Intent intent = new Intent(ACTION_UPDATE_UI_ADAPTER); //接收廣播為所有歌曲列表的adapter
context.sendBroadcast(intent);
}
private void initMediaPlayer() {
NumberRandom(); // 改變線程號,使舊的播放線程停止
int musicId = MyMusicUtil.getIntShared(Constant.KEY_ID);
int current = MyMusicUtil.getIntShared(Constant.KEY_CURRENT);
Log.d(TAG, "initMediaPlayer musicId = " + musicId);
// 如果是沒取到當前正在播放的音樂ID,則從數據庫中獲取第一首音樂的播放信息初始化
if (musicId == -1) {
return;
}
String path = dbManager.getMusicPath(musicId);
if (path == null) {
Log.e(TAG, "initMediaPlayer: path == null");
return;
}
if (current == 0) {
status = Constant.STATUS_STOP; // 設置播放狀態為停止
}else {
status = Constant.STATUS_PAUSE; // 設置播放狀態為暫停
}
Log.d(TAG, "initMediaPlayer status = " + status);
MyMusicUtil.setShared(Constant.KEY_ID,musicId);
MyMusicUtil.setShared(Constant.KEY_PATH,path);
UpdateUI();
}
public MediaPlayer getMediaPlayer() {
return mediaPlayer;
}
public int getThreadNumber() {
return threadNumber;
}
}
3.項目在播放一個音頻的同時維護了一個線程實時去通知界面刷新,該線程從MediaPlayer中獲取當前的播放進度、總時間等信息發送給播放界面,播放界面拿到數據就可以刷新播放顯示信息了。
public class UpdateUIThread extends Thread {
private static final String TAG = UpdateUIThread.class.getName();
private int threadNumber;
private Context context;
private PlayerManagerReceiver playerManagerReceiver;
private int duration;
private int curPosition;
public UpdateUIThread(PlayerManagerReceiver playerManagerReceiver, Context context, int threadNumber) {
Log.i(TAG, "UpdateUIThread: " );
this.playerManagerReceiver = playerManagerReceiver;
this.context = context;
this.threadNumber = threadNumber;
}
@Override
public void run() {
try {
while (playerManagerReceiver.getThreadNumber() == this.threadNumber) {
if (playerManagerReceiver.status == Constant.STATUS_STOP) {
Log.e(TAG, "run: Constant.STATUS_STOP");
break;
}
if (playerManagerReceiver.status == Constant.STATUS_PLAY ||
playerManagerReceiver.status == Constant.STATUS_PAUSE) {
if (!playerManagerReceiver.getMediaPlayer().isPlaying()) {
Log.i(TAG, "run: getMediaPlayer().isPlaying() = " + playerManagerReceiver.getMediaPlayer().isPlaying());
break;
}
duration = playerManagerReceiver.getMediaPlayer().getDuration();
curPosition = playerManagerReceiver.getMediaPlayer().getCurrentPosition();
Intent intent = new Intent(PlayBarFragment.ACTION_UPDATE_UI_PlayBar);
intent.putExtra(Constant.STATUS, Constant.STATUS_RUN);
intent.putExtra(Constant.KEY_DURATION, duration);
intent.putExtra(Constant.KEY_CURRENT, curPosition);
context.sendBroadcast(intent);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
三、項目文件結構
四、其他
如果對你有幫助的話,可以給我github個star,謝謝。
https://github.com/lijunyandev/MeetMusic
注:本文著作權歸作者,由demo大師發表,拒絕轉載,轉載需要作者授權

