发布时间:2018-09-06
 
技术:Glide+pinyin4j+SwipeDelMenuLayout
 

概述

这是一款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大师发表,拒绝转载,转载需要作者授权

最新文章

  1. VB.net中Ajaxpro的使用
  2. 实现winform DataGridView控件判断滚动条是否滚动到当前已加载的数据行底部
  3. Unable to execute dex: Multiple dex files define
  4. 神奇的Noip模拟试题第一试 合理种植 枚举+技巧
  5. SecureCRT相关设置
  6. (转载)delphi文件流
  7. MyBatis(3.2.3) - Dynamic SQL
  8. 在 Azure 虚拟机上部署反恶意软件解决方案
  9. [置顶] LED办公楼宇照明节能方案及城市夜景照明节能方案
  10. rxjava2学习笔记(1)
  11. Codeforces Round #203 (Div. 2)B Resort
  12. Thread之十:停止线程方法汇总
  13. 安卓调试工具adb返回的png截图,直接输出到控制台的修复问题
  14. 每天学点SpringCloud(八):使用Apollo做配置中心
  15. python基础四-文件读取
  16. 第二章 STM32的结构和组成
  17. GUI_鼠标事件
  18. windows常见数据类型
  19. 源码安装php时出现configure: error: xml2-config not found. Please check your libxml2 installation
  20. android 模仿今日头条ViewPager+TabLayout

热门文章

  1. 使用Log4j将程序日志实时写入Kafka(转)
  2. go语言之进阶篇runtime包中 Gosched Goexit GOMAXPROCS的使用
  3. scrapy框架系列 (4) Scrapy Shell
  4. 简短介绍 C# 6 的新特性
  5. 大数据开发实战:Stream SQL实时开发一
  6. Spring Boot集成持久化Quartz定时任务管理和界面展示
  7. Kafka:ZK+Kafka+Spark Streaming集群环境搭建(十八)ES6.2.2 增删改查基本操作
  8. O2O、C2C、B2B、B2C
  9. 很有用的mobile web application远程调试工具 weinre
  10. php tools 破解