Android_播放器_利用Service通过MediaPlayer播放歌曲并完成歌词同步绘制

本文主要是介绍Android_播放器_利用Service通过MediaPlayer播放歌曲并完成歌词同步绘制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本博文为子墨原创,转载请注明出处!
http://blog.csdn.net/zimo2013/article/details/16849695

1.示意图

           

2. 代码实现

/*** MainActivity.java* @author zimo2013* @see http://blog.csdn.net/zimo2013**/
public class MainActivity extends Activity implements OnClickListener,OnSeekBarChangeListener {private LrcSurfaceView surfaceView;private Button btPlay;private Button btPause;private Button btStop;private SeekBar bar;private PlayerConn conn;private IMyBinder binder;private boolean isDrag;	//进度条是否正在拖拽private List<LrcInfo> list;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initViews();list = new ArrayList<LrcInfo>();conn = new PlayerConn();Intent intent = new Intent(this, PlayService.class);bindService(intent, conn, BIND_AUTO_CREATE);bar.setOnSeekBarChangeListener(this);btPlay.setOnClickListener(this);btPause.setOnClickListener(this);btStop.setOnClickListener(this);parseLrc();}private void initViews() {surfaceView = (LrcSurfaceView) findViewById(R.id.lrc);btPlay = (Button) findViewById(R.id.bt_play);btPause = (Button) findViewById(R.id.bt_pause);btStop = (Button) findViewById(R.id.bt_stop);bar = (SeekBar) findViewById(R.id.bar);}/*** 歌词解析*/public void parseLrc() {BufferedReader bufr = null;try {if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {bufr = new BufferedReader(new InputStreamReader(new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/music.lrc")));String data = null;//添加第一条以歌名开头的歌词list.add(new LrcInfo(0, "music"));// "[00:09.77]Why did the writer complain to the people behind him?"Pattern pattern = Pattern.compile("\\[(\\d{2}):(\\d{2}.\\d{2,3})\\](.*)");while ((data = bufr.readLine()) != null) {Matcher matcher = pattern.matcher(data);while (matcher.find()) {int minute = Integer.parseInt(matcher.group(1));//分钟 00 float second = Float.parseFloat(matcher.group(2));//秒 09.77list.add(new LrcInfo((int) ((minute * 60 + second) * 1000), matcher.group(3)));}}//为surfaceView设定歌词surfaceView.setList(list);}} catch (Exception e) {e.printStackTrace();}finally{if(bufr != null){try {bufr.close();} catch (IOException e) {e.printStackTrace();}}}}@Overridepublic void onClick(View v) {Intent intent = new Intent(this, PlayService.class);if (btPlay == v) {intent.setAction(PlayService.ACTION_PLAY);} else if (btPause == v) {intent.setAction(PlayService.ACTION_PAUSE);} else if (btStop == v) {intent.setAction(PlayService.ACTION_STOP);}startService(intent);}private class PlayerConn implements ServiceConnection {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {binder = (IMyBinder) service;binder.setHandler(handler);	//设定handler}@Overridepublic void onServiceDisconnected(ComponentName name) {}}private Handler handler = new Handler() {public void handleMessage(Message msg) {System.out.println("msg what:" + msg.what + " arg1:" + msg.arg1);switch (msg.what) {case PlayService.WHAT_DURATION:bar.setMax(msg.arg1);break;case PlayService.WHAT_CURRENT:if (!isDrag) {//更新进度条以及歌词bar.setProgress(msg.arg1);surfaceView.update(msg.arg1);}break;}}};@Overridepublic void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {if(fromUser){surfaceView.update(bar.getProgress());//更新歌词}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {//移除所有未处理的消息handler.removeMessages(PlayService.WHAT_CURRENT);isDrag = true;}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {//拖拽完成,更新Intent intent = new Intent(this, PlayService.class);intent.setAction(PlayService.ACTION_PLAY);intent.putExtra("progress", bar.getProgress());startService(intent);isDrag = false;}@Overrideprotected void onDestroy() {super.onDestroy();//解除服务unbindService(conn);}
}
/*** PlayService.java* @author zimo2013* @see http://blog.csdn.net/zimo2013**/
public class PlayService extends Service {public static final String ACTION_PLAY = "play";public static final String ACTION_PAUSE = "pause";public static final String ACTION_STOP = "stop";public static final int WHAT_CURRENT = 1;public static final int WHAT_DURATION = 2;private MediaPlayer player;private Handler handler;private IMyBinder binder;private int progress;@Overridepublic void onCreate() {super.onCreate();binder = new MyBinder();}@Overridepublic IBinder onBind(Intent intent) {return (IBinder) binder;}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {String action = intent.getAction();if (ACTION_PLAY.equals(action)) {progress = intent.getIntExtra("progress", 0);toPlay(progress);} else if (ACTION_PAUSE.equals(action)) {pause();} else if (ACTION_STOP.equals(action)) {stop();}return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();if(player != null){player.release();	//是否资源player = null;		//置空,拷贝GC回收}}/*** 将要播放* @param pos*/private void toPlay(int pos) {checkMediaPlayer();if (state == PlayerState.Initialized) {state = PlayerState.Preparing;player.prepareAsync();} else {play(pos);}}/*** 实际播放音乐* @param pos*/private void play(int pos) {if (state == PlayerState.Started || state == PlayerState.Prepared|| state == PlayerState.Paused|| state == PlayerState.PlaybackCompleted) {if (state != PlayerState.Paused) {player.seekTo(pos);}player.start();state = PlayerState.Started;new Thread() {public void run() {// updateUI();while (state == PlayerState.Started) {send(WHAT_CURRENT, player.getCurrentPosition());try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}.start();} else {show("播放异常");}}/*** 暂停播放*/private void pause() {if (state == PlayerState.Started) {player.pause();state = PlayerState.Paused;} else if (state == PlayerState.Paused) {show("播放已经暂停,现不需要再暂停!");} else {show("暂停异常");}}/*** 停止播放*/private void stop() {if (state == PlayerState.Started || state == PlayerState.Prepared|| state == PlayerState.Paused|| state == PlayerState.PlaybackCompleted) {player.release();	//是否资源player = null;		//置空,拷贝GC回收state = PlayerState.End;} else {show("停止异常");}}/*** 发送消息,用于更新ui* @param what* @param value*/private void send(int what, int value) {// 更新进度条Message msg = handler.obtainMessage();msg.what = what;msg.arg1 = value;handler.sendMessage(msg);}/*** 检查MediaPlayer对象,如果不存在则应该创建,并完成相关初始化*/private void checkMediaPlayer() {if (player == null) {// 完成多媒体的初始化player = new MediaPlayer();state = PlayerState.Idle;player.setAudioStreamType(AudioManager.STREAM_MUSIC);// 判断SDcard正常挂载if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {try {player.setDataSource(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/music.mp3");state = PlayerState.Initialized;//异步准备player.setOnPreparedListener(new OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {state = PlayerState.Prepared;// 设定进度条的max值send(WHAT_DURATION, player.getDuration());// 准备完成play(progress);}});//播放完成player.setOnCompletionListener(new OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {state = PlayerState.PlaybackCompleted;// 播放完成play(0);show("已经播放完成,现在正在重新播放");}});} catch (Exception e) {e.printStackTrace();}}}}/*** 自定义MyBinder* @author Administrator**/private class MyBinder extends Binder implements IMyBinder {@Overridepublic void setHandler(Handler handler) {PlayService.this.handler = handler;}}/*** 弹出吐司* @param info*/public void show(String info) {Toast.makeText(getApplicationContext(), info, 0).show();}private PlayerState state = PlayerState.Idle;/*** 播放器的播放状态枚举* @author Administrator**/private enum PlayerState {Idle, Initialized, Preparing, Prepared, Started, Stopped, Paused, End, PlaybackCompleted,}
}
/*** LrcSurfaceView.java* @author zimo2013* @see http://blog.csdn.net/zimo2013**/
public class LrcSurfaceView extends SurfaceView {private SurfaceHolder holder;private List<LrcInfo> list;private boolean isCreated;private int mWidth;private int mHeight;private int pos = 0;private Paint paintNew;private Paint paintOld;private int current;private ExecutorService thread;private Runnable runnable;public LrcSurfaceView(Context context) {this(context, null, 0);}public LrcSurfaceView(Context context, AttributeSet attrs) {this(context, attrs, 0);this.getHolder();}public LrcSurfaceView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);holder = getHolder();holder.addCallback(new LrcCallBack());// 线程池thread = Executors.newSingleThreadExecutor();runnable = new LrcUpdateRunnable();// 创建2种画笔对象paintNew = new Paint();paintNew.setTextSize(20);paintNew.setTextAlign(Paint.Align.CENTER);paintNew.setColor(Color.YELLOW);paintOld = new Paint();paintOld.setTextSize(15);paintOld.setTextAlign(Paint.Align.CENTER);//居中显示paintOld.setColor(Color.WHITE);}/*** 为surfaceView 设定歌词* * @param list*/public void setList(List<LrcInfo> list) {Collections.sort(list); // 歌词排序,从前往后this.list = list;}/*** 更新歌词信息* * @param current*/public void update(int current) {if (isCreated && list != null && list.size() > 0) {// 如果surfaceView对应已经被创建或者存在歌词this.current = current;thread.execute(runnable); // 线程池更新歌词信息}}private class LrcUpdateRunnable implements Runnable {@Overridepublic void run() {pos = 0;// 默认从第一条歌词开始寻找Canvas canvas = holder.lockCanvas();canvas.drawColor(Color.BLACK);while (true) {if (pos == list.size() - 1) {break;}if (current >= list.get(pos).startTime && current < list.get(pos + 1).startTime) {break;}pos++;}//Surface可以直接操作UIif (pos != 0) {// 上一条已经播放歌词canvas.drawText(list.get(pos - 1).lrc, mWidth / 2, mHeight / 2 - 50, paintOld);}// 当前正在播放歌词canvas.drawText(list.get(pos).lrc, mWidth / 2, mHeight / 2, paintNew);// 下一条需要播放歌词if (pos != list.size() - 1) {canvas.drawText(list.get(pos + 1).lrc, mWidth / 2, mHeight / 2 + 50, paintOld);}// 接触锁定并提交holder.unlockCanvasAndPost(canvas);}}private class LrcCallBack implements SurfaceHolder.Callback {@Overridepublic void surfaceCreated(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {isCreated = true;mHeight = height;mWidth = width;}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}}}
/*** IMyBinder.java* @author zimo2013* @see http://blog.csdn.net/zimo2013**/
public interface IMyBinder {void setHandler(Handler handler);
}

这篇关于Android_播放器_利用Service通过MediaPlayer播放歌曲并完成歌词同步绘制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/685693

相关文章

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Android里面的Service种类以及启动方式

《Android里面的Service种类以及启动方式》Android中的Service分为前台服务和后台服务,前台服务需要亮身份牌并显示通知,后台服务则有启动方式选择,包括startService和b... 目录一句话总结:一、Service 的两种类型:1. 前台服务(必须亮身份牌)2. 后台服务(偷偷干

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

使用TomCat,service输出台出现乱码的解决

《使用TomCat,service输出台出现乱码的解决》本文介绍了解决Tomcat服务输出台中文乱码问题的两种方法,第一种方法是修改`logging.properties`文件中的`prefix`和`... 目录使用TomCat,service输出台出现乱码问题1解决方案问题2解决方案总结使用TomCat,

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

python安装完成后可以进行的后续步骤和注意事项小结

《python安装完成后可以进行的后续步骤和注意事项小结》本文详细介绍了安装Python3后的后续步骤,包括验证安装、配置环境、安装包、创建和运行脚本,以及使用虚拟环境,还强调了注意事项,如系统更新、... 目录验证安装配置环境(可选)安装python包创建和运行Python脚本虚拟环境(可选)注意事项安装

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

使用Python绘制可爱的招财猫

《使用Python绘制可爱的招财猫》招财猫,也被称为“幸运猫”,是一种象征财富和好运的吉祥物,经常出现在亚洲文化的商店、餐厅和家庭中,今天,我将带你用Python和matplotlib库从零开始绘制一... 目录1. 为什么选择用 python 绘制?2. 绘图的基本概念3. 实现代码解析3.1 设置绘图画