上一次谈了音乐播放的实现,这次说下最复杂的进度条和歌词更新。因为须要在播放的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)之进度条和歌词更新的实现的更多相关文章

  1. 安卓MP3播放器开发实例(1)之音乐列表界面

    学习安卓开发有一年了,想想这一年的努力,确实也收获了不少.也找到了比較如意的工作. 今天准备分享一个以前在初学阶段练习的一个项目.通过这个项目我真正的找到了开发安卓软件的感觉,从此逐渐步入安卓开发的正 ...

  2. 【Android】利用安卓的数据接口、多媒体处理编写内存卡Mp3播放器app

    通过调用安卓的MediaPlayer能够直接完毕Mp3等主流音频的播放,同一时候利用ContentResolver与Cursor能够直接读取安卓内在数据库的信息.直接获取当前sdcard中全部音频的列 ...

  3. android音乐播放器开发教程

    android音乐播放器开发教程 Android扫描sd卡和系统文件 Android 关于录音文件的编解码 实现米聊 微信一类的录音上传的功能 android操作sdcard中的多媒体文件——音乐列表 ...

  4. 你用java的swing可以做出这么炫的mp3播放器吗?

    这个mp3播放器是基于java的swing编写的,我认为界面还是可以拿出来和大家看一看评一评. 先说说创作的初衷,由于前段时间工作不是很忙,与其闲着,还不如找一些东西来给自己捣腾捣腾,在 之前写的 j ...

  5. 开源mp3播放器--madplay 编译和移植 简记

    madplay是一款开源的mp3播放器. http://madplay.sourcearchive.com/ 下面简单记录一下madplay的编译与移植到ARM开发板上的过程 一.编译x86版本的ma ...

  6. 从零开始学习PYTHON3讲义(十四)写一个mp3播放器

    <从零开始PYTHON3>第十四讲 通常来说,Python解释执行,运行速度慢,并不适合完整的开发游戏.随着电脑速度的快速提高,这种情况有所好转,但开发游戏仍然不是Python的重点工作. ...

  7. C# wave mp3 播放器探寻

    C# wave mp3 播放器探寻   最近无聊,想听听歌曲.可怜新电脑上歌曲就两三首,要听其它的就得在旧电脑上播放.可是,那台古董但不失健壮的本本被老婆无情的霸占了.无奈. 思来想去,得,写个程序播 ...

  8. 团队编程--MP3播放器

    设计思路: 这次的作业是一个MP3播放器,它是一个团队项目.由于我们都没接触过这类的编程.刚开始的时候我们是不知道从什么地方着手的.经过我们的商量我们决定从现在市场主流的音乐播放器上找到几个主要的功能 ...

  9. 基于GStreamer编写Mp3播放器

    一.简介 作者系统为CentOS6,本文在此基础上对Mp3播放器进行开发,需要使用mp3解码库libmad和gstreamer0.10-plugins-ugly,详细步骤如下.   二.操作步骤 1) ...

随机推荐

  1. hdoj--5562--Clarke and food(模拟)

    Clarke and food Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) ...

  2. 集合区别(list和linkedlist的区别)?

    1.list和linkedlist都是有序可重复,为什么还要用linkedlist呢? 数组和数组列表都有一个重大的缺陷,这就是从数组的中间位置删除一个元素需要付出很大的代价,其原因是数组中处于被删除 ...

  3. Android chromium 1

    For Developers‎ > ‎Design Documents‎ > ‎ Java Resources on Android Overview Chrome for Android ...

  4. NodeJS学习笔记 (29)二进制解码-string_decoder(ok)

    原文:https://github.com/chyingp/nodejs-learning-guide 自己过一遍: 模块简介 string_decoder模块用于将Buffer转成对应的字符串.使用 ...

  5. ES6学习笔记(十一)Object的继承者Reflect

    1.概述 Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API.Reflect对象的设计目的有这样几个. (1) 将Object对象的一些明显属于语言内部的方法(比如O ...

  6. js中return 、return false 、return true、break、continue区别

    在开发中不熟悉这三者区别的同学,一般都知道return可以中止,但会根据字面意思觉得return true 中止当前函数执行,但其后的函数还会继续执行.return false 中止当前函数执行,其后 ...

  7. Could not create connection to database server. Attempted reconnect 3 times. Giving up.错误

    项目是基于springboot框架,昨天从git上pull代码之后也没有具体看更改的地方,结果运行的时候就报错了. java.sql.SQLNonTransientConnectionExceptio ...

  8. Ehcache学习总结(2)--Ehcache整合spring配置

    首先需要的maven依赖为: [html] view plain copy <!--ehcache--> <dependency> <groupId>com.goo ...

  9. [Python] Slice the data with pandas

    For example we have dataframe like this: SPY AAPL IBM GOOG GLD 2017-01-03 222.073914 114.311760 160. ...

  10. Spark修炼之道(高级篇)——Spark源代码阅读:第十二节 Spark SQL 处理流程分析

    作者:周志湖 以下的代码演示了通过Case Class进行表Schema定义的样例: // sc is an existing SparkContext. val sqlContext = new o ...