以下为旧版代码,新版lrc解析代码已经写完,全面修复兼容性,请直接参考:http://www.qylk.blog.163.com/blog/static/134687356201162023117621/
先定义一个存放每句歌词的类,它定义了每句歌词的起始时间,持续时间,及每句歌词正文,相当于C语言的结构体。
public class LRCbean {
private int beginTime=0;
public int getBeginTime() {
return beginTime;
private int lineTime=0;
private String lrcBody = null;
}
public void setBeginTime(int beginTime) {
this.beginTime = beginTime;
}
public int getLineTime() {
return lineTime;
}
public void setLineTime(int lineTime) {
this.lineTime = lineTime;
}
public String getLrcBody() {
return lrcBody;
}
public void setLrcBody(String lrcBody) {
this.lrcBody = lrcBody;
}
}
歌词解析类如下:
public class LrcUtils {
private TreeMap<Integer, LRCbean> read(File path){//读取歌词
TreeMap<Integer, LRCbean> lrc_read = new TreeMap<Integer, LRCbean>();//临时表
String data =null;
BufferedReader br = null;
FileInputStream stream = null;
try {
stream = new FileInputStream(path);//读入歌词文件流
br = new BufferedReader(new InputStreamReader(stream, "UTF-8"));//向内存写入
} catch (Exception e) {}
try {
while((data=br.readLine())!=null){//逐行读取
if (data.length()>6){//此行有歌词
if (data.charAt(3)==':'&&data.charAt(6)=='.'){//从歌词正文开始
data = data.replace("[", "");
data = data.replace("]", "@");
data = data.replace(".", ":");
String lrc[] = data.split("@");//将时间与歌词分开
String lrcContent= null;
if (lrc.length==2){//是正规的lrc文件,即每行只定义一句歌词
lrcContent = lrc[lrc.length-1];//歌词正文
}else{//非正规,抛弃此行
lrcContent = "";
}
if(lrcContent.equals("")) lrcContent="Mucic..."; //此行歌词为空,添加提示信息
String lrcTime[] = lrc[0].split(":");//计算开始结束时间
int m = Integer.parseInt(lrcTime[0]);//分
int s = Integer.parseInt(lrcTime[1]);//秒
int ms = Integer.parseInt(lrcTime[2]);//毫秒
int begintime = (m*60 + s) * 1000 + ms;//转换成毫秒
LRCbean lrcbean = new LRCbean();
lrcbean.setBeginTime(begintime);//设置歌词开始时间
lrcbean.setLrcBody(lrcContent);//设置歌词内容
lrc_read.put(begintime,lrcbean);//装载入表
}
}
}
stream.close();
br.close();
} catch (Exception e) {}
//计算每句歌词需要的时间
Iterator<Integer> iterator = lrc_read.keySet().iterator();//迭代器
LRCbean oldval = null;
int i = 0;
TreeMap<Integer, LRCbean> lrc_map=new TreeMap<Integer, LRCbean>();
while (iterator.hasNext()){//逐行处理,加入起始、持续时间
Object ob = iterator.next();
LRCbean val = lrc_read.get(ob);
if (oldval==null){
oldval = val;
} else{
LRCbean item1 = new LRCbean();//相当于每句歌词的结构体,包含次句歌词信息
item1 = oldval;
item1.setLineTime(val.getBeginTime()-oldval.getBeginTime());//持续时间
lrc_map.put(new Integer(i), item1);//正式装载入歌词表
i++;
oldval = val;
}
}
return lrc_map;
}
public TreeMap<Integer, LRCbean> check_lrc(String mname){
String tem[]=mname.split("\\."); //分离出例如"小三.mp3"中的"小三"
File lrc=new File("mnt/sdcard/music/"+tem[0]+".lrc");//组装歌词路径,这里的路径你自己提供
if(lrc.exists()){ //查询歌词文件是否存在
TreeMap<Integer, LRCbean> lrcm=read(lrc);//读歌词
return lrcm;
}
else return null;
}
public int find_lrc_index(Integer cur_pos,TreeMap<Integer, LRCbean> Lrcmap){ //当拖动进度条,需要重新查询歌词的位置,输入参数是进度,及歌词表,返回查询到的歌词在表中的索引
Iterator<Integer> iterator=Lrcmap.keySet().iterator();//定义迭代器
int pos=0;
while(iterator.hasNext()){
pos = iterator.next();
LRCbean val = Lrcmap.get(pos);
if (val!=null){
if (cur_pos<val.getBeginTime()){
break;//查询到了索引,跳出循环
}
}
}
if(pos==0) return pos;
return pos-1;//送回查询到的歌词索引
}
}
UI调用:配合进度条使用,在进度条进度发生改变时刷新歌词显示:
原理:事先将lrc歌词解析后,每句存入一个歌词表中,表中每行记录着这句歌词的相关信息,根据每句歌词的持续时间,将其等分为500us的段,将段数记为j,将进度条设置为每500us更新一次,这样每次将j减1,减到0时,则时间到,该显示下句歌词了。当改变进度条,需要重新查询歌词的位置,然后将i置为查询到的歌词索引值即可。i为当前歌词索引,切记i、j均为全局变量。
先注部分对象名:
( private SeekBar bar;//进度条
private TextView time,dura;//显示歌曲当前时间,总时间
private TextView Lrc,Lrc2;//双行歌词
private TreeMap<Integer, LRCbean> lrc_map = new TreeMap<Integer, LRCbean>();//歌词表
private int i=0,j=0;//
private LrcUtils lrcUtils=new LrcUtils();
private boolean haslrc=false;//歌词存在标志
private boolean lrcwait=true;//歌词等待标志
//用到的其余成员不写了,请自行判断
)
bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { //(进度条位置发生改变事件)
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
time.setText(String.format("%02d:%02d", progress/60000,progress%60000/1000).toString());//更新显示时间进度
try {
if(haslrc==true && lrcwait==false){//有歌词并且等待标志为false
while(j--<=0){// j大约每500us减1,进度条更新需要一个线程控制,这里的500us就是进度条的更新周期,一定要与之一致
LRCbean val2;
//下面的代码逻辑较难理解,我写成这样,你只管用就行了,为防止歌词显示异常,加了一些必要条件处理
if(i==lrc_map.size()) {lrcwait=true;break;}//显示过了最后一条歌词,应该退出,lrcwait设为true,歌词将不再更新
LRCbean val = lrc_map.get(i++);//获取当前歌词
if(i==lrc_map.size()) {val2=new LRCbean();val2.setLrcBody("");}//到了结尾,新增一个结尾
else val2 = lrc_map.get(i);//取出下一条歌词
Integer linetime=val.getLineTime();//取持续时间
if(i==1) {Lrc.setText(val.getLrcBody());}//初始化首句歌词
if(i%2!=0) {Lrc2.setText(val2.getLrcBody());//偶数行
Lrc2.setTextColor(Color.WHITE);
Lrc.setTextColor(Color.GREEN);}
else {Lrc.setText(val2.getLrcBody());//奇数行
Lrc.setTextColor(Color.WHITE);
Lrc2.setTextColor(Color.GREEN);}
linetime-=mPlayer.getCurrentPos()-val.getBeginTime();
//实际等待时间,这里mplayer.getcurrentpos是取得歌曲现在进度,这是我自定义的方法,你改成你的mPlayer.getCurrentPosition()之类的。
j=(int)linetime/500; //置等待计数器j
}
}
} catch (Exception e) {}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) { //开始拖拉进度条
wait=true; //进度条更新线程"暂停",
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) { //停止拖拉进度条
try{
mPlayer.setCurrent(seekBar.getProgress());//歌曲进度调整,mPlayer.setCurrent改为你的mPlayer.seekTo(pos)之类的
if(haslrc) set_lrc_pos();//歌词进度调整,set_lrc_pos此方法下面有定义,haslrc是歌词存在的标志
if(mPlayer.isPlaying()) threadresume(); //进度条线程恢复,歌词继续更新
}
catch (RemoteException e) {}
}});
当手动改变歌词进度,歌词需要同步更新,歌词显示调整
private void set_lrc_pos() {
LrcUtils lrcUtils=new LrcUtils();//歌词业务类,上面有定义过
i=lrcUtils.find_lrc_index(bar.getProgress(),lrc_map);//寻找当前歌词索引
LRCbean val2;
LRCbean val = lrc_map.get(i++);//读取当前歌词(注意这里i加1了)
if(i!=lrc_map.size()){//找到的不是结尾
val2 = lrc_map.get(i);//读取下一条歌词
}
else {//恰好为结尾
val2=new LRCbean();//新增一个空白结尾" ",(看不见的)
val2.setLrcBody(" ");
}
int linetime=val.getLineTime();//读取持续时间
if(i%2!=0){//偶数行歌词(注意这里i是加1后的i,实际上加1之前为偶数)
Lrc.setText(val.getLrcBody());//显示歌词
Lrc.setTextColor(Color.GREEN);
Lrc2.setText(val2.getLrcBody());
Lrc2.setTextColor(Color.WHITE);
}
else{
Lrc2.setText(val.getLrcBody());
Lrc2.setTextColor(Color.GREEN);
Lrc.setText(val2.getLrcBody());
Lrc.setTextColor(Color.WHITE);
}
linetime -=mPlayer.getCurrentPos()-val.getBeginTime();
//计算实际要等待的时间,使其更加精确。mPlayer.getCurrentPos改成你的mPlayer.getCurrentPosition()之类的
j=(int)linetime/500;//转换成等待计数器的值
}
//进度条更新线程,记得在适当的时候开启这个线程
thread = new Thread(){
@Override
public void run() {
while(! thread_des){ // thread_des你就设为false就行了,设为死循环。
try {
sleep(500);//睡500u秒
if(wait==false){//wait是用于主进程控制该线程是否暂停更新进度条的标志
bar.setProgress(mPlayer.getCurrentPos());
//设置当前播放的进度值,mPlayer.getCurrentPos改成你的mPlayer.getCurrentPosition()。
}
}catch (Exception e)
{Log.i("tag", "ThreadError");}
}}};
//检查歌词,每当换歌的时候调用
private void check_lrc(){
lrc_map=null;
lrc_map=lrcUtils.check_lrc(curr_name);
//检查是否存在,输入参数为歌曲名,如“小三.mp3”, curr_name改为你自己的。
if(lrc_map ! =null){//存在
i=0;j=0;//参数初始化
haslrc=true;//歌词存在标志
}
else {haslrc=false;Lrc.setText("无歌词");Lrc.setTextColor(Color.GREEN);}
}
这是我写的一个音乐播放器里的部分代码,完整代码请参阅http://www.qylk.blog.163.com/blog/static/134687356201162023117621/