安卓MP3播放器开发实例(3)之进度条和歌词更新的实现
上一次谈了音乐播放的实现,这次说下最复杂的进度条和歌词更新。因为须要在播放的Activity和播放的Service间进行交互,所以就涉及了Activity对Service的绑定以及绑定后数据的传输,这个须要对服务绑定熟悉才干够理解。原理不复杂。可是步骤略微繁琐,代码贴起来可能会非常混乱。
进度条和歌词放在一起说比較好,不然比較混乱。进度条的调整大家都懂的,就是进度条调到哪里歌曲的播放就跟到哪里,歌词也得跟到哪里。首先看下上一篇看过的開始button监听事件中服务的绑定代码:
//绑定播放服务
bindService(intent, conn,BIND_AUTO_CREATE);
//Runnable对象增加消息队列,在handler绑定的线程中运行,用于歌词更新
handler.post(updateTimeCallback);
start = System.currentTimeMillis();
bindService(intent, conn,BIND_AUTO_CREATE); 中的conn是绑定服务后的一个回调接口。我们看下代码:
/**
* 传入bindService()中的回调接口
*/
ServiceConnection conn = new ServiceConnection(){ @Override
public void onServiceConnected(ComponentName arg0, IBinder binder) {//绑定成功后调用该方法
PlayActivity.this.binder = (Binder)binder;
} @Override
public void onServiceDisconnected(ComponentName arg0) { } };
事实上就是一个语句。作用就是将播放服务返回的IBinder对象引用赋给播放Activity,这样Activity就能够获取Service返回的数据了,我们这里是为了获取此时播放的进度数据。
handler.post(updateTimeCallback);是将一个Runnable对象增加队列中。这个Runnable对象updateTimeCallback
就是实现进度条和歌词更新的核心实现代码,只是看这个类之前。我们先看下进度条的监听事件:
/**
* 经过试验假设把进度条调动之前和调动之后合并为一种方式实现歌词的更新的话会奔溃,原因可能是不同线程间资源的同步问题,仅仅好拆成
* 进度条调动之前(change == false)和调动之后(change == true)两部分在歌词更新的线程中运行
* @author yan
*
*/
class SeekBarListener implements OnSeekBarChangeListener{ @Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override
public void onStartTrackingTouch(SeekBar arg0) {
Log.d("yinan", "start--------"+seekBar.getProgress()); } @Override
public void onStopTrackingTouch(SeekBar arg0) { if(stopMusic == false){
change = true;//进度条进度人为改变。将改变后的进度发送给播放服务 Intent intent = new Intent();
intent.setClass(PlayActivity.this, PlayService.class);
intent.putExtra("progress", seekBar.getProgress());
startService(intent);
}else{
seekBar.setProgress(0);
} } }
在进度条位置得到调整之后,仍然用startService(intent)方式。将封装进度条进度的Intent对象传给Service的onStartCommand方法处理。
如今来看看实现的核心代码Runnable对象updateTimeCallback的类:
/**
* 异步调整进度条位置与歌曲进度一致和显示歌词的类
* @author yinan
*
*/
class UpdateTimeCallback implements Runnable{
Queue times = null;
Queue messages = null;
ArrayList<Queue> queues = null; public UpdateTimeCallback(ArrayList<Queue> queues){
times = queues.get(0);
messages = queues.get(1);
this.queues = queues; }
/**
* run方法因为没有条用start所以并没有开辟子线程。所以在内部开启子线程
*/
@Override
public void run() {
total = PlayService.totalLength;
if(change == true){ Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeString("change"); try {
binder.transact(0, data, reply, 0);
} catch (RemoteException e) { e.printStackTrace();
}
//直到Service返回才运行这一句
float s = reply.readFloat();//s为歌曲播放的时间进度 float f = s*100;
seekBar.setProgress((int)f);//转化为进度条进度 //此时相应的播放时间点
final long t = (long) (s*total); preparelrc(mp3Info.getLrcName());
times = queues.get(0);
messages = queues.get(1); System.out.println("times"+times.size());
System.out.println("messages"+messages.size());
if(stopMusic == false){
new Thread(){
public void run(){
while(nextTime < t){ //从头遍历歌词,时间队列直到时间点大于当前时间
System.out.println("nextTime"+nextTime);///////////////////////////////
lyric = message;
if((times.size()>0)||messages.size()>0){
nextTime = (Long)times.poll();
message = (String)messages.poll();
}else{
lyric = null; stopMusic = true; }//保存时间点刚大于当前时间的上一条歌词,即最接近当前的歌词 System.out.println("nextTime"+nextTime);
}
}
}.start(); System.out.println("lyric"+lyric);
if(lyric != null){
lrcText.setText(lyric); }
}
} if(!change){ nowTime = System.currentTimeMillis() - start;
seekBar.setProgress((int)(nowTime*100/total)); if(stopMusic == false){
new Thread(){
public void run(){
while(nextTime < nowTime){ //从头遍历歌词。时间队列直到时间点小于当前时间
System.out.println("nextTime"+nextTime);
lyric = message;//保存时间点刚大于当前时间的上一条歌词,即最接近当前的歌词
if((times.size()>0)||messages.size()>0){
nextTime = (Long)times.poll();
message = (String)messages.poll();
}else{
lyric = null;
stopMusic = true; }
}
}
}.start(); if(lyric != null){
lrcText.setText(lyric); }
} } if(stopMusic == true){
handler.removeCallbacks(updateTimeCallback);
}
handler.postDelayed( updateTimeCallback, 200);//每20毫秒运行一次线程 } }
这段代码大致是当获取到服务返回的歌曲实时进度的数据时,将进度条的位置进行调整到相应为孩子的处理,然后将歌词更新到相应的部分。因为进度条要实时跟进。全部UpdateTimeCallback类的run方法是每隔200毫秒就运行一次,到Service中获取返回值。
这里是将处理的情况分为进度条被改变了和未被改变两种情况,用标志位change表示(一旦进度条被改变了就运行了change为true那一段代码)。
开子线程是由于对歌词的处理耗时。不适合放在UI线程中。
详细的流程是:
首先,Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeString("change");
binder.transact(0, data, reply, 0);
binder就是之前和Service绑定的IBinder对象,binder.transact是将一个Parcel对象(传入"change"不过一个标志)传给Service,然后Service返回歌曲进度,在float s
= reply.readFloat()这句中接收,s是一个当前进度占总进度的百分比数。然后将进度条位置设置一下就可以。
然后进行歌词更新的处理。
首先是对歌词的准备处理工作,preparelrc(mp3Info.getLrcName()),将lrc格式的歌词中的时间点和相应的详细歌词拆分成两个队列。(详细代码请看源代码)然后依据Service返回的歌曲播放进度。对两个队列进行遍历。当有时间点超过进度相应的时间点时,将该时间点相应的歌词取出,显示在Activity上。
前面总是说Service返回歌曲实时进度,我们来看看Service中对于数据返回的处理。
当进度条被调整时。会进入Service的onStartCommand方法,进入到下面的if语句中:
if(intent.getIntExtra("progress", 0) != 0){///////////////////////
if(isPlay){
//进度条进度
int progress = intent.getIntExtra("progress", 0);
if(progress != 0)
//将音乐进度调整到相应位置
mediaPlayer.seekTo(progress*totalLength/100);
} }
Service又是怎样实时返回歌曲进度的呢?Service中有一个Binder类。是IBinder的子类:
/*
*运行 binder.transact()后运行
*/
class FirstBinder extends Binder{ @Override
protected boolean onTransact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException { String str = data.readString(); int currentPosition = mediaPlayer.getCurrentPosition(); float s = (float)currentPosition/mediaPlayer.getDuration(); if(isPlay)
reply.writeFloat(s); return super.onTransact(code, data, reply, flags);
}
是的。它就是之前Activity与Service绑定后获得的IBinder对象。每当Activity的binder.transact(0, data, reply, 0);被调用的时候。Service就会调用onTransact方法。将获取到的数据装入Parcel对象reply中,然后将整个IBinder对象返回Activity。
MP3播放器就这样讲完了,可能讲的非常乱。希望对各位有帮助。源码下载链接:http://download.csdn.net/detail/sinat_23092639/8933995
安卓MP3播放器开发实例(3)之进度条和歌词更新的实现的更多相关文章
- 安卓MP3播放器开发实例(1)之音乐列表界面
学习安卓开发有一年了,想想这一年的努力,确实也收获了不少.也找到了比較如意的工作. 今天准备分享一个以前在初学阶段练习的一个项目.通过这个项目我真正的找到了开发安卓软件的感觉,从此逐渐步入安卓开发的正 ...
- 【Android】利用安卓的数据接口、多媒体处理编写内存卡Mp3播放器app
通过调用安卓的MediaPlayer能够直接完毕Mp3等主流音频的播放,同一时候利用ContentResolver与Cursor能够直接读取安卓内在数据库的信息.直接获取当前sdcard中全部音频的列 ...
- android音乐播放器开发教程
android音乐播放器开发教程 Android扫描sd卡和系统文件 Android 关于录音文件的编解码 实现米聊 微信一类的录音上传的功能 android操作sdcard中的多媒体文件——音乐列表 ...
- 你用java的swing可以做出这么炫的mp3播放器吗?
这个mp3播放器是基于java的swing编写的,我认为界面还是可以拿出来和大家看一看评一评. 先说说创作的初衷,由于前段时间工作不是很忙,与其闲着,还不如找一些东西来给自己捣腾捣腾,在 之前写的 j ...
- 开源mp3播放器--madplay 编译和移植 简记
madplay是一款开源的mp3播放器. http://madplay.sourcearchive.com/ 下面简单记录一下madplay的编译与移植到ARM开发板上的过程 一.编译x86版本的ma ...
- 从零开始学习PYTHON3讲义(十四)写一个mp3播放器
<从零开始PYTHON3>第十四讲 通常来说,Python解释执行,运行速度慢,并不适合完整的开发游戏.随着电脑速度的快速提高,这种情况有所好转,但开发游戏仍然不是Python的重点工作. ...
- C# wave mp3 播放器探寻
C# wave mp3 播放器探寻 最近无聊,想听听歌曲.可怜新电脑上歌曲就两三首,要听其它的就得在旧电脑上播放.可是,那台古董但不失健壮的本本被老婆无情的霸占了.无奈. 思来想去,得,写个程序播 ...
- 团队编程--MP3播放器
设计思路: 这次的作业是一个MP3播放器,它是一个团队项目.由于我们都没接触过这类的编程.刚开始的时候我们是不知道从什么地方着手的.经过我们的商量我们决定从现在市场主流的音乐播放器上找到几个主要的功能 ...
- 基于GStreamer编写Mp3播放器
一.简介 作者系统为CentOS6,本文在此基础上对Mp3播放器进行开发,需要使用mp3解码库libmad和gstreamer0.10-plugins-ugly,详细步骤如下. 二.操作步骤 1) ...
随机推荐
- powershell无法拖动文件到命令行
PS C:\Program Files\PowerShell\6.0.0-beta.6> New-Service -Name LISA_43_Dev_Batch -DisplayName LIS ...
- [poj 2480] Longge's problem 解题报告 (欧拉函数)
题目链接:http://poj.org/problem?id=2480 题目大意: 题解: 我一直很欣赏数学题完美的复杂度 #include<cstring> #include<al ...
- 【DNN 系列】 模块开发 8.0.1
1.创建第一个模块需要准备的东西有 https://github.com/dnnsoftware/DNN.Templates/releases/tag/1.0.1 VS 2015 插件 创建一个项目M ...
- SQL SERVER中求上月、本月和下月的第一天和最后一天
1.上月的第一天 SELECT CONVERT(CHAR(10),DATEADD(month,-1,DATEADD(dd,-DAY(GETDATE())+1,GETDATE())),111) 2.上月 ...
- webi和universe
Universe是一个包含以下内容的文件: 1 一个或多个数据库中间件的连接参数. 2 称为对象的SQL结构,映射到数据库中的实际SQL结构,如列,表和数据库函数.其中对象是按类分组的.用户既可以看到 ...
- Android框架-Volley(二)
1. ImageRequest的用法 前面我们已经学习过了StringRequest和JsonRequest的用法,并且总结出了它们的用法都是非常类似的,基本就是进行以下三步操作即可: 1. 创建一个 ...
- Monad的基本运算
A monad is created by defining a type constructor M and two operations, bind and return (where retur ...
- Threading and Tasks in Chrome
Threading and Tasks in Chrome Contents Overview Threads Tasks Prefer Sequences to Threads Posting a ...
- 浅谈Sass与Less区别、优缺点
Sass是一种动态样式语言,Sass语法的缩排语法,比Css比多出很多功能,如变量,嵌套,运算,继承,颜色处理,函数等,易于阅读.Cass的安装需要安装Ruby环境,是服务器端处理的,Less是需要引 ...
- git新克隆代码的时候ssh协议