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

相关文章

QT6中绘制UI的两种方法详解与示例代码

《QT6中绘制UI的两种方法详解与示例代码》Qt6提供了两种主要的UI绘制技术:​​QML(QtMeta-ObjectLanguage)​​和​​C++Widgets​​,这两种技术各有优势,适用于不... 目录一、QML 技术详解1.1 QML 简介1.2 QML 的核心概念1.3 QML 示例:简单按钮

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

POI从入门到实战轻松完成EasyExcel使用及Excel导入导出功能

《POI从入门到实战轻松完成EasyExcel使用及Excel导入导出功能》ApachePOI是一个流行的Java库,用于处理MicrosoftOffice格式文件,提供丰富API来创建、读取和修改O... 目录前言:Apache POIEasyPoiEasyExcel一、EasyExcel1.1、核心特性

Android 实现一个隐私弹窗功能

《Android实现一个隐私弹窗功能》:本文主要介绍Android实现一个隐私弹窗功能,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 效果图如下:1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来res/l

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

MySQL主从同步延迟问题的全面解决方案

《MySQL主从同步延迟问题的全面解决方案》MySQL主从同步延迟是分布式数据库系统中的常见问题,会导致从库读取到过期数据,影响业务一致性,下面我将深入分析延迟原因并提供多层次的解决方案,需要的朋友可... 目录一、同步延迟原因深度分析1.1 主从复制原理回顾1.2 延迟产生的关键环节二、实时监控与诊断方案

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel