在网上查了一下资料,感谢 http://www.cr173.com/html/20184_1.html 给了我思路,可以说他提供了最基本的歌词同步的功能,我在其上面添加了自己的修改的代码。
主要是自己为了实现歌词同步,并且通过移动seekbar,改变歌曲的歌词位置。当然还有自己不一样的地方。
首先歌词播放,是要一个子线程来操作,这个子线程负责在找到两段歌词之间的时间差,然后显示当前正在播放的歌词。
1
歌词部分
1.歌词的格式为.lrc 这是有一定格式的,最重要的是[MM:ss,mm]
以白玫瑰.lrc歌词为例
需要对歌词进行解析,歌词的实体类。
MyLrc.java
public class MyLrc implements Comparable<Object>{
private int time;
private String lyric;
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public String getLyric() {
return lyric;
}
public void setLyric(String lyric) {
this.lyric = lyric;
}
//放在set集合中可以看下面要求进行排序
@Override
public int compareTo(Object arg0) {
int later=0;
if(arg0 instanceof MyLrc)
{
later=((MyLrc)arg0).getTime();
}
return this.time-later;
}
@Override
public String toString() {
return this.time+""+this.lyric;
}
}
实现comparaTo的方法的目的是在把对象放入TreeSet中的时候,按照歌词时间的循序放入,方便之后拿出来。比较使用(我觉得这部很重要,因为后期需要判断拉动SeekBar的时候找到对应的时间的位置)
LrcUtil.java
//对歌词进行解析
public class LrcUtil {
private static TreeSet<MyLrc> tree;
// 将对应的lrc文件转化为treeMap,分别对应的时间以及歌词
public LrcUtil(InputStream musicTitle) {
TreeSet<MyLrc> treeset = new TreeSet<MyLrc>();
// 用来存放歌曲的时间和对应的歌词
InputStreamReader inReader = null;
BufferedReader reader = null;
try {
inReader = new InputStreamReader(musicTitle);
reader = new BufferedReader(inReader);
String line = "";
while ((line = reader.readLine()) != null) {
// 对那行歌词进行分割,判断,然后存储
String[] substr = line.split("\\]");
for (String ss : substr) {
if (ss.contains("[") && ss.contains(":")
&& ss.contains(".")) {
String sss = ss.replaceAll("\\[", "");
String[] timeStart = sss.split(":");
String[] timeEnd = timeStart[1].split("\\.");
// 计算出当前的时间的毫秒数
int time = (Integer.valueOf(timeStart[0]) * 60 + Integer
.valueOf(timeEnd[0]))
* 1000
+ Integer.valueOf(timeEnd[1]) * 10;
// 对应的时间放一个对应的歌词
MyLrc lrc = new MyLrc();
lrc.setTime(time);
lrc.setLyric(substr[substr.length - 1]);
treeset.add(lrc);
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inReader.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
tree = treeset;
}
//获得lrc文件的歌词点的列表
public List<String> getWords() {
List<String> list = new ArrayList<String>();
Iterator<MyLrc> it = tree.iterator();
while (it.hasNext()) {
MyLrc my = it.next();
list.add(my.getLyric());
}
return list;
}
//获得lrc文件的时间点的列表
public List<Integer> getTimes() {
List<Integer> list = new ArrayList<Integer>();
Iterator<MyLrc> it = tree.iterator();
while (it.hasNext()) {
MyLrc my = it.next();
list.add(my.getTime());
}
return list;
}
}
Lrc工具类在构造方法中传入一个InputStream流,由于现在是本地测试,所以本地先放入lrc文件。Android中可以把歌词文件放在Assets文件中,但是有些歌是中文的,而Assets文件中是不允许放中文的文件的
显示为非法名字,所以我打算把中文歌曲的名字转化为拼音再查找。
Activity中歌词显示的代码
根据当前歌曲的进度启一个子线程,sleep一段时间,其他时间刷新控件显示。
private void initLrcThread(int currentPosition) {
LrcUtil lrcUtil = null;
try {
lrcUtil = new LrcUtil(getAssets().open(Trans2PinYin.trans2PinYin(currentMusic.getTitle())+".lrc"));
playing_text_lrc.init(Trans2PinYin.trans2PinYin(currentMusic.getTitle())+".lrc");
} catch (IOException e) {
ToastInfo(R.string.notfind_lrc);
}
if(lrcUtil==null)
return;
final List<Integer> lrctime = lrcUtil.getTimes();
if(currentPosition>lrctime.get(lrctime.size()-1))
return;
int position = 0;
for (int i = 0; i < lrctime.size()-1; i++) {
if (currentPosition < lrctime.get(0)) {
position = 0;
break;
} else if (currentPosition > lrctime.get(i)
&& currentPosition < lrctime.get(i + 1)) {
position = i;
break;
}
}
final int p = position;
//找到对应位置的歌词
playing_text_lrc.changeIndex(p);
// 起一个子线程进行歌词显示
new Thread() {
int i = p;
public void run() {
while (!mService.isPause()) {
handler.post(new Runnable() {
@Override
public void run() {
playing_text_lrc.invalidate();
}
});
try {
if (i == 0)
Thread.sleep(lrctime.get(i));
else {
Thread.sleep(lrctime.get(i + 1) - lrctime.get(i));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
//防止下标越界!
if (i+1 >= lrctime.size())
break;
}
;
}
}.start();
}
这里是对自定义控件显示歌词的操作,如果没找到对应的lrc就打的Toast即可。
对于歌曲显示的子线程。当歌曲在播放的时候。线程sleep一段时间后对自定义的歌词控件进行刷新。每次刷新都会走空间中的ondraw的方法。
在ondraw方法中,刷新一次就跳到下一个歌词。
当然了,这里通过SeekBar移动的时候可以得到当前歌曲的位置(时间点),通过这个时间点(currentPosition)找到对应的getTimes列表中的歌词的位置
<span style="white-space:pre"> </span>for (int i = 0; i < lrctime.size()-1; i++) {
<span style="white-space:pre"> </span>if (currentPosition < lrctime.get(0)) {
<span style="white-space:pre"> </span>position = 0;
<span style="white-space:pre"> </span>break;
<span style="white-space:pre"> </span>} else if (currentPosition > lrctime.get(i)
<span style="white-space:pre"> </span>&& currentPosition < lrctime.get(i + 1)) {
<span style="white-space:pre"> </span>position = i;
<span style="white-space:pre"> </span>break;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
并且同时改变playing_text_lrc.changeIndex(p);
LRCTextView.java
public class LRCTextView extends TextView {
private List<String> mWordsList = new ArrayList<String>();
private Paint mLoseFocusPaint;
private Paint mOnFocusePaint;
private float mX = 0;
private float mMiddleY = 0;
private float mY = 0;
private static final int DY = 50;
private int mIndex = 0;
private Context context;
public LRCTextView(Context context) throws IOException {
super(context);
this.context = context;
}
public LRCTextView(Context context, AttributeSet attrs) throws IOException {
super(context, attrs);
this.context = context;
}
public LRCTextView(Context context, AttributeSet attrs, int defStyle)
throws IOException {
super(context, attrs, defStyle);
this.context = context;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint p = mLoseFocusPaint;
// 未走到init未找到歌词
if (p == null) {
p=new Paint();
p.setTextSize(50);
canvas.drawText("未找到歌词", mX-100, mMiddleY, p);
return;
}
p.setTextAlign(Paint.Align.CENTER);
Paint p2 = mOnFocusePaint;
p2.setTextAlign(Paint.Align.CENTER);
// 防止下表越界
if (mIndex >= mWordsList.size())
return;
canvas.drawText(mWordsList.get(mIndex), mX, mMiddleY, p2);
int alphaValue = 25;
float tempY = mMiddleY;
for (int i = mIndex - 1; i >= 0; i--) {
tempY -= DY;
if (tempY < 0) {
break;
}
p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
canvas.drawText(mWordsList.get(i), mX, tempY, p);
alphaValue += 25;
}
alphaValue = 25;
tempY = mMiddleY;
for (int i = mIndex + 1, len = mWordsList.size(); i < len; i++) {
tempY += DY;
if (tempY > mY) {
break;
}
p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
canvas.drawText(mWordsList.get(i), mX, tempY, p);
alphaValue += 25;
}
mIndex++;
}
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
mX = w * 0.5f;
mY = h;
mMiddleY = h * 0.3f;
}
public void init(String musicName) throws IOException {
setFocusable(true);
LrcUtil lrcHandler = new LrcUtil(context.getAssets().open(musicName));
mWordsList = lrcHandler.getWords();
mLoseFocusPaint = new Paint();
mLoseFocusPaint.setAntiAlias(true);
mLoseFocusPaint.setTextSize(30);
mLoseFocusPaint.setColor(Color.BLACK);
mLoseFocusPaint.setTypeface(Typeface.SERIF);
mOnFocusePaint = new Paint();
mOnFocusePaint.setAntiAlias(true);
mOnFocusePaint.setColor(Color.RED);
mOnFocusePaint.setTextSize(50);
mOnFocusePaint.setTypeface(Typeface.SANS_SERIF);
}
public void changeIndex(int i) {
mIndex = i;
}
这里我稍微修改了这个方法,传入一个changeIndex用于Seekbar被拉动时候,歌词的改变。其他的与http://www.cr173.com/html/20184_1.html相似
而在每次歌曲切换或者进度条移动的时候调用这个方法即可
initLrcThread(currentPosition);
这里用到了
Trans2PinYin.trans2PinYin(currentMusic.getTitle()
这个方法,这是中文转化为拼音的方法。百度即可。