Android 本地播放器
概述
详细
一、先看效果图







二、项目的播放流程简要介绍
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大师发表,拒绝转载,转载需要作者授权
Android 本地播放器的更多相关文章
- 使用Vitamio打造自己的Android万能播放器(4)——本地播放(快捷搜索、数据存储)
前言 关键字:Vitamio.VPlayer.Android播放器.Android影音.Android开源播放器 本章节把Android万能播放器本地播放的主要功能(缓存播放列表和A-Z快速查询功能) ...
- 使用Vitamio打造自己的Android万能播放器(3)——本地播放(主界面、播放列表)
前言 打造一款完整可用的Android播放器有许多功能和细节需要完成,也涉及到各种丰富的知识和内容,本章将结合Fragment.ViewPager来搭建播放器的主界面,并实现本地播放基本功能.系列文章 ...
- 使用Vitamio打造自己的Android万能播放器(6)——在线播放(播放列表)
前言 新版本的VPlayer由设计转入开发阶段,预计开发周期为一个月,这也意味着新版本的Vitamio将随之发布,开发者们可以和本系列文章一样,先开发其他功能.本章内容为"在线视频播放列表& ...
- 使用Vitamio打造自己的Android万能播放器(5)——在线播放(播放优酷视频)
前言 为了保证每周一篇的进度,又由于Vitamio新版本没有发布, 决定推迟本地播放的一些功能(截图.视频时间.尺寸等),跳过直接写在线播放部分的章节.从Vitamio的介绍可以看得出,其支持http ...
- 使用Vitamio打造自己的Android万能播放器(7)——在线播放(下载视频)
前言 本章将实现非常实用的功能——下载在线视频.涉及到多线程.线程更新UI等技术,还需思考产品的设计,如何将新加的功能更好的融入到现有的产品中,并不是简单的加一个界面就行了,欢迎大家交流产品设计和技术 ...
- Android VLC播放器二次开发3——音乐播放(歌曲列表+歌词同步滚动)
今天讲一下对VLC播放器音频播放功能进行二次开发,讲解如何改造音乐播放相关功能.最近一直在忙着优化视频解码部分代码,因为我的视频播放器需要在一台主频比较低的机器上跑(800M主频),所以视频解码能力受 ...
- Android VLC播放器二次开发2——CPU类型检查+界面初始化
上一篇讲了VLC整个程序的模块划分和界面主要使用的技术,今天分析一下VLC程序初始化过程,主要是初始化界面.加载解码库的操作.今天主要分析一下org.videolan.vlc.gui.MainActi ...
- android音乐播放器开发 SweetMusicPlayer 载入歌曲列表
上一篇写了播放器的总体实现思路,http://blog.csdn.net/huweigoodboy/article/details/39855653,如今来总结下载入歌曲列表. 代码地址:https: ...
- Android 基于ijkplayer+Rxjava+Rxandroid+Retrofit2.0+MVP+Material Design的android万能播放器aaa
MDPlayer万能播放器 MDPlayer,基于ijkplayer+Rxjava+Rxandroid+Retrofit2.0+MVP+Material Design的android万能播放器,可以播 ...
随机推荐
- 奇怪吸引子---DequanLi
奇怪吸引子是混沌学的重要组成理论,用于演化过程的终极状态,具有如下特征:终极性.稳定性.吸引性.吸引子是一个数学概念,描写运动的收敛类型.它是指这样的一个集合,当时间趋于无穷大时,在任何一个有界集上出 ...
- mysqlpump:更加合理的mysql数据库逻辑备份工具
端看参见就知道了! E:\mysql-8.0.12-winx64>mysqlpump --helpmysqlpump Ver 8.0.12 for Win64 on x86_64 (MySQL ...
- [leetcode]Path Sum II @ Python
原题地址:https://oj.leetcode.com/problems/path-sum-ii/ 题意: Given a binary tree and a sum, find all root- ...
- Laravel学习笔记之Session源码解析(中)
说明:在上篇中学习了session的启动过程,主要分为两步,一是session的实例化,即\Illuminate\Session\Store的实例化:二是从session存储介质redis中读取id ...
- 【大数据】Spark-Hadoop-架构对比
Spark-Hadoop-架构对比 spark executor - zyc920716的博客 - CSDN博客 董的博客 » Apache Spark探秘:多进程模型还是多线程模型? Apache ...
- Spring(七):IOC&DI
什么是IOC? IOC(Inversion of Control):其思想是反转资源获取的方向. 传统的资源查找方式要求组件向容器发起请求查找资源,作为回应,容器适时的返回资源. 而应用了IOC之后, ...
- webstorm和intellij idea下如何自动编译sass和scss文件
webstorm和intellij idea下如何自动编译sass和scss文件 https://segmentfault.com/a/1190000008996504 https://www.jia ...
- [Algorithm] Circular buffer
You run an e-commerce website and want to record the last N order ids in a log. Implement a data str ...
- 全国第二届Revit开发实战训练营在北京圆满落幕
由北京橄榄山软件公司与筑城网校中国BIM培训网共同举办的"全国第二届Revit开发实战训练营于1月1日在筑城网校培训教室如期开班. 參加此次培训的有上海同济大学建筑设计研究院(集团)有限公司 ...
- matlab之simulink仿真入门
Matlab Simulink仿真工具的应用 ****Simulink是一个用来对动态系统进行建模.仿真和分析的软件包.使用Simulink来建模.分析和仿真各种动态系统(包含连续系统.离散系统和混合 ...
