您的位置:首页 > 移动开发 > Android开发

Android歌词播放的实现

2016-07-27 14:22 615 查看
公司项目最近有需求,要实现一个音乐系统,涉及到一个歌词播放的功能,现将这个实现过程写下来。

做之前先上网查了下相关APP的情况,发现QQ音乐的桌面歌词效果,正是我想要的,显示两行歌词,轮流播放,并显示播放时的过渡效果。

首先分析的出,歌词有两行,那么应该是两个控件来的分别显示,然后通过一系列算法来控制两行歌词交替显示,这样的话,我们要先写出用于显示歌词的控件来。




歌词显示自定义控件

歌词控件应该具备的方法应该有,设置歌词,设置字体颜色,字体大小,播放方法。

播放方法的参数一定要有播放的时长,和一个播放结束完毕的监听,这个监听提供给调用者,这样才可以播放下一句歌词。

我先将整个播放代码贴上来:

/**
* 每执行一次,都会降计时器清零
*
* @param delay  执行前等待时间
* @param period 循环时间
*/
public void showLrc(final Activity context, long delay, long period, final ShowListener listener) {
tvSelect.setVisibility(VISIBLE);
tvSelect.setWidth(0);
tvDefault.setText(lrc);
tvSelect.setText(lrc);

Log.i("_lrc", "start_openCounter:" + mOpenCounter.get());
mOpenCounter.addAndGet(1);
if (period < 100)
period = 100;
float speed = period / 100;

mTimer = new Timer();
TimerTask task = new TimerTask() {
public void run() {
context.runOnUiThread(new Runnable() {
@Override
public void run() {
if (percent < 100) {
percent++;
} else {
Log.i("_lrc", "end_openCounter:" + mOpenCounter.get());
if (mOpenCounter.get() > 0) {
mOpenCounter.set(0);
percent = 0;
listener.showFinish();
tvSelect.setVisibility(INVISIBLE);
cancel();
}
}
setPercent();
}
});
}
};
mTimer.schedule(task, delay, (long) speed);
}


代码上有我刚才提到的几个方法,比如设置歌词,设置字体大小,播放等方法,但仔细看下歌词的参数,仔细看第二个参数,这个参数是用于播放前等待时间,因为歌手在唱下一句的时候,会停留一些时间,会喘口气,休息一会。
歌词要播放的时候显示过渡效果的话,就还要做一些处理,这里的实现原理是,用两个不同颜色的TextView重叠着,我们动态设置上面的TextView的宽度,就可以实现我们要的过渡效果了。

解析歌词工具类

歌词显示的控件有了,接下来我们写一个解析歌词的工具类,这个类的作用是传入一个LRC歌词路径,然后将歌词和歌词对应时间用两个数组容器存放着,提供给调用者获取出歌词和对应的时间即可。
LRC歌词是歌词前面会有对应的播放时间,这个时间用中括号括着,我们通过一点点解析算法,将其放到两个容器就好了。
package com.ysbing.lrcshow;

import android.text.TextUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LrcUtil {

private List<Integer> mTimeList = new ArrayList<>();
private List<String> mWords = new ArrayList<>();
private Iterator<Integer> mTimeIterator;
private Iterator<String> mWordIterator;

public LrcUtil(File lrcFile) throws FileNotFoundException {
readLrcFile(lrcFile);
}

public boolean hasTime() {
return mTimeIterator.hasNext();
}

public boolean hasWord() {
return mWordIterator.hasNext();
}

public int getTime() {
return mTimeIterator.next();
}

public String getWord() {
return mWordIterator.next();
}

//处理歌词文件
private void readLrcFile(File file) throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream(file);
InputStreamReader inputStreamReader;
try {
inputStreamReader = new InputStreamReader(
fileInputStream, "utf-8");
readLrcStream(inputStreamReader);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}

private void readLrcStream(InputStreamReader inputStreamReader) {
BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String s;
try {
while ((s = bufferedReader.readLine()) != null) {
addTimeToList(s);
if ((s.contains("[ar:")) || (s.contains("[ti:")) || (s.contains("[by:")) || (s.contains("[al:"))) {
//                    s = s.substring(s.indexOf(":") + 1, s.indexOf("]"));
continue;
} else {
if (TextUtils.isEmpty(s))
continue;
int startIndex = s.indexOf("[");
int endIndex = s.indexOf("]");
if (startIndex >= 0 && endIndex >= 0) {
String ss = s.substring(startIndex, endIndex + 1);
s = s.replace(ss, "");
} else continue;
}
mWords.add(s);
}
mTimeIterator = mTimeList.iterator();
mWordIterator = mWords.iterator();
bufferedReader.close();
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
mWords.add("没有读取到歌词");
}
}

private void addTimeToList(String string) {
Matcher matcher = Pattern.compile(
"\\[\\d{1,2}:\\d{1,2}([\\.:]\\d{1,2})?\\]").matcher(string);
if (matcher.find()) {
String str = matcher.group();
mTimeList.add(timeHandler(str.substring(1,
str.length() - 1)));
}
}

// 分离出时间
private int timeHandler(String string) {
string = string.replace(".", ":");
String timeData[] = string.split(":");
// 分离出分、秒并转换为整型
int minute = Integer.parseInt(timeData[0]);
int second = Integer.parseInt(timeData[1]);
int millisecond = Integer.parseInt(timeData[2]);
if (timeData[2].length() == 1)
millisecond *= 100;
else if (timeData[2].length() == 2)
millisecond *= 10;

// 计算上一行与下一行的时间转换为毫秒数
return (minute * 60 + second) * 1000 + millisecond;
}
}


歌词控制

歌词解析工具也有了,接下来要做的事情,就是本节博客的核心,分析如何控制两个歌词的交替显示。
歌词有两行,我们先开始命名,第一行歌词的时间叫“时间一”,歌词叫“歌词一”,第二行则叫“时间二”和“歌词二”。
如果歌词1为空,设置歌词1为下一句歌词,记录时间1-时间2为歌词2的等待时间

歌词1播放->时间2-时间1

我们要考虑到的情况有,在LRC歌词文件中,在歌手喘气休息的时候,是没歌词的,这种情况在LRC中,没歌词,有时间;
假设我们在播放第一行歌词的时候,就有这样的逻辑

如果歌词二为空,就获取下一句歌词作为歌词二,获取下一个时间为时间二

在歌词一播放完后,我们在播放完的回调中,再播放歌词二,这样循环下去,知道歌词结束。

这样我们就实现了歌词的交叉播放,但还存在一个问题,就是,播放的时候是一句接着一句的,如果手机性能比较差,就可能慢慢的,就会和音乐不同了,这个情况也是不允许出现的,所以开始升级。

升级后逻辑是:

记录开始时间

播放前获取现在时间,减去和歌词时间的差

歌词结束时间减去开始播放时间,算出速度

这样总体我们实现完了,效果还可以,就算手机再差,也会在播放下一句中的时候,准确对准歌词的时间。源码我上传到CSDN,再去下载看看具体逻辑吧,谢谢观看!

资源下载地址:http://download.csdn.net/detail/ysb794008002/9586853
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息