您的位置:首页 > 其它

安卓 播放MP3 实现歌词同步例子

2016-03-01 02:53 555 查看
哎,敢接触这个东西,看了好些东西,才明白,其中,借鉴如下这位网友:http://www.cnblogs.com/wenjiang/archive/2013/05/06/3063259.html?utm_source=tuicool

但还是看得很难懂:后来终于搞明白了,特简单易懂地写下来。

首先,如果解析lrc歌词文件:有些歌词是一句接着一句按时间顺序排列好的,但是有些事重复的没有顺序排列的,如下:

[00:00.00]午夜怨曲
[00:09.00]词 \ 叶世荣.   曲 \ 黄家驹.  主唱 \ 黄家驹.
[00:18.00]
[00:27.00]从来不知想拥有多少的理想
[00:33.00]还离不开种种困忧
[00:40.00]勉强去掩饰失意的感觉
[00:46.00]再次听到昨日的冷嘲
[00:52.00]徘徊於街中恐怕只得孤独
[00:58.00]寻回思忆中的碎片
[01:05.00]变作了一堆草芥风中散
[01:11.00]与你奏过午夜的怨曲
[04:12.00][03:47.00][03:22.00][02:19.00][01:16.00]总有挫折打碎我的心
[04:16.00][03:51.00][03:25.00][02:22.00][01:19.00]紧抱过去抑压了的手
[04:20.00][03:55.00][03:30.00][02:27.00][01:23.00]我与你也彼此一起艰苦过
[04:00.00][03:35.00][02:32.00][01:28.00]写上每句冰冷冷的诗
[04:03.00][03:38.00][02:35.00][01:31.00]不会放弃高唱这首歌
[04:07.00][03:42.00][02:39.00][01:36.00]我与你也彼此真的相识过
[01:55.00]从回忆中找不到天真的笑声
[02:01.00]曾留不低心中斗争
[02:08.00]每次去担当失意的主角
[02:14.00]冷笑变作故事的作者
[03:11.00]啊......啊......障碍能撕破
[04:25.00]
[04:27.00]BEYOND再见理想
[04:29.00]/~byfaith


但是解析也是差不多而已,只不过,找个稍微多用力一点点,

首选,建立一个map存放上面的有时间的内容;开头这些信息也没什么用,另放一个,有用则用,没用则不用;因为map存放时无序的,于是乎建立一个数组存放把key排序好的时间数组:

<span style="white-space:pre">	</span>//存放开头那些没有时间的信息
private List<String> info = new ArrayList<String>();
//存放有时间的歌词
private Map<String, String> lrcs = new Hashtable<String, String>();
//存放按照从小到大排序好的时间信息
private Object[] arr;

然后写一个主要运用indexof、substring来截取文本的方法:

//获取歌词,放进集合中
public void decodeLrc(String str) {

if (str.startsWith("[ti:")) {
info.add(str.substring(4, str.lastIndexOf("]")));

} else if (str.startsWith("[ar:")) {

info.add(str.substring(4, str.lastIndexOf("]")));

} else if (str.startsWith("[al:")) {

info.add(str.substring(4, str.lastIndexOf("]")));
} else if (str.startsWith("[la:")) {
info.add(str.substring(4, str.lastIndexOf("]")));

} else if (str.startsWith("[by:")) {
info.add(str.substring(4, str.lastIndexOf("]")));

} else {

//这里获取歌词信息
int startIndex;
int tempIndex = -1;
//获取多个中括号的相同歌词的信息
while ((startIndex = str.indexOf("[", tempIndex + 1)) != -1) {

int endIndex = str.indexOf("]", tempIndex + 1);
String tempTime = str.substring(tempIndex + 2, endIndex);
lrcs.put(tempTime, str.substring(str.lastIndexOf("]") + 1, str.length()));

tempIndex = endIndex;

}
}
}

通过上面这个方法,就把数据各自存放好了。接着写一个把hashtable的key键,也就是“时间”排序好,存放于arr数组中

<span style="white-space:pre">	</span>//把hashtable转为有秩序的组合
public void convertArrays() {
arr = lrcs.keySet().toArray();
Arrays.sort(arr);
}


================================================================================================================================

以上就是从lrc文件中获取歌词信息的内容。但往回看,如果读取lrc内,这样也就是一个方法:通过这个方法读取文件,然后再用上面的解析......

private void getLrcs(InputStreamReader isr) {

try {

BufferedReader reader = new BufferedReader(isr);
String line = "";

while ((line = reader.readLine()) != null) {

//一行一行读取,到方法中去解析信息
decodeLrc(line);

}

//key键的时间排序、组合
convertArrays();

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}


===========================================================================================================

歌词信息准备好了,接着就是写一个自定义的View来显示歌词同步,这个原理大概就是这样的:假设mediaplyaer传过来的时间是X,好了,我们把上面数组中的时间转为long型,假设第一句高亮歌词是Y,和这个X作比较,如果X大于或者等于Y,那么我们就开始显示下一行的高亮(上面已经做了歌词的排序是吧),通过重绘更新UI。

整个View的代码如下:

package china.testwt;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

import java.io.*;
import java.util.*;

public class MyView extends View {

private int mBgCol, mCurTextCol, mNorTextCol;
private int mCurrentTextSize, mNormalTextSize;

//存放开头那些没有时间的信息
private List<String> info = new ArrayList<String>();
//存放有时间的歌词
private Map<String, String> lrcs = new Hashtable<String, String>();
//存放按照从小到大排序好的时间信息
private Object[] arr;

//不高亮的歌词画笔
private Paint mLoseFocusPaint;
//高亮的
private Paint mOnFocusePaint;
//一行歌词的开始位置X
private float drawTextX = 0;
//Y
private float drawTextY = 0;
//整个View的高
private float viewHeight = 0;
//间隔,移动的大小
private int mSpacing;
//高亮的行数
private int mIndex = 0;

//获取数据源,接口
public void setLrcSource(FileInputStream reader) {
InputStreamReader isr = new InputStreamReader(reader);
getLrcs(isr);
}

//获取当前行位置,接口
public void setLrcPostion(long position) {

if (mIndex < lrcs.size()) {

long tmepPos = parseTime(arr[mIndex].toString());

if (tmepPos < position) {

//更新UI
postInvalidate();
}
}

}

//构造方法
public MyView(Context context) {
this(context, null);
}

public MyView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

//以下关联好各种自定义属性
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView, defStyleAttr, 0);

mBgCol = a.getColor(R.styleable.MyView_bgColor, Color.GREEN);
mCurTextCol = a.getColor(R.styleable.MyView_CurTextColor, Color.YELLOW);
mNorTextCol = a.getColor(R.styleable.MyView_NorTextColor, Color.WHITE);
mCurrentTextSize = a.getDimensionPixelSize(R.styleable.MyView_currentTextSize, 28);
mNormalTextSize = a.getDimensionPixelSize(R.styleable.MyView_normalTextSize, 24);

a.recycle();

mLoseFocusPaint = new Paint();
mLoseFocusPaint.setAntiAlias(true);
mLoseFocusPaint.setTextSize(mNormalTextSize);
mLoseFocusPaint.setColor(mNorTextCol);
mLoseFocusPaint.setTypeface(Typeface.SERIF);

mOnFocusePaint = new Paint();
mOnFocusePaint.setAntiAlias(true);
mOnFocusePaint.setColor(mCurTextCol);
mOnFocusePaint.setTextSize(mCurrentTextSize);
mOnFocusePaint.setTypeface(Typeface.SANS_SERIF);

}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//非wrap_content 用默认的
}

//这个方法运行了才运行ondraw()
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);

//从中间开始,而且歌词居中。在下面设置了
drawTextX = w * 0.5f;
//高就是高了
viewHeight = h;
//从0.3高度的地方开始画
drawTextY = h * 0.3f;

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//话背景
canvas.drawColor(mBgCol);

//居中设置
Paint p = mLoseFocusPaint;
p.setTextAlign(Paint.Align.CENTER);
Paint p2 = mOnFocusePaint;
p2.setTextAlign(Paint.Align.CENTER);

//间隔,为文字大小加上10个像素
mSpacing = mCurrentTextSize + 10;

//画高亮的
canvas.drawText(lrcs.get(arr[mIndex]), drawTextX, drawTextY, p2);

//画高亮上面的歌词,高度递减,透明度递减
int alphaValue = 25;
float tempY = drawTextY;
for (int i = mIndex - 1; i >= 0; i--) {
tempY -= mSpacing;
if (tempY < 0) {
break;
}
p.setColor(Color.argb(255 - alphaValue, 255, 255, 255));
canvas.drawText(lrcs.get(arr[i]), drawTextX, tempY, p);
alphaValue += 25;
}

//画高亮下面的歌词,高度递增,透明度递减
alphaValue = 25;
tempY = drawTextY;
for (int i = mIndex + 1; i < lrcs.size(); i++) {

tempY += mSpacing;
//超出不显示啦
if (tempY > viewHeight) {
break;
}

p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
canvas.drawText(lrcs.get(arr[i]), drawTextX, tempY, p);

//如果没超出就达到了100%透明,往后的都100%透明
if (alphaValue + 25 > 255) {

alphaValue = 255;

} else {

alphaValue += 25;
}
}

//准备下一行刷新,重绘,这有赖于传进来的时间对比

mIndex++;
}

private void getLrcs(InputStreamReader isr) { try { BufferedReader reader = new BufferedReader(isr); String line = ""; while ((line = reader.readLine()) != null) { //一行一行读取,到方法中去解析信息 decodeLrc(line); } //key键的时间排序、组合 convertArrays(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }

//获取歌词,放进集合中 public void decodeLrc(String str) { if (str.startsWith("[ti:")) { info.add(str.substring(4, str.lastIndexOf("]"))); } else if (str.startsWith("[ar:")) { info.add(str.substring(4, str.lastIndexOf("]"))); } else if (str.startsWith("[al:")) { info.add(str.substring(4, str.lastIndexOf("]"))); } else if (str.startsWith("[la:")) { info.add(str.substring(4, str.lastIndexOf("]"))); } else if (str.startsWith("[by:")) { info.add(str.substring(4, str.lastIndexOf("]"))); } else { //这里获取歌词信息 int startIndex; int tempIndex = -1; //获取多个中括号的相同歌词的信息 while ((startIndex = str.indexOf("[", tempIndex + 1)) != -1) { int endIndex = str.indexOf("]", tempIndex + 1); String tempTime = str.substring(tempIndex + 2, endIndex); lrcs.put(tempTime, str.substring(str.lastIndexOf("]") + 1, str.length())); tempIndex = endIndex; } } }

// 解析时间,把时间转为long
@Nullable
private Long parseTime(String time) {
// 03:02.12
if (time.indexOf(":") != -1) {

String[] min = time.split(":");
String[] sec = min[1].split("\\.");

long minInt = Long.parseLong(min[0]
.replaceAll("\\D+", "")
.replaceAll("\r", "")
.replaceAll("\n", "")
.trim());
long secInt = Long.parseLong(sec[0]
.replaceAll("\\D+", "")
.replaceAll("\r", "")
.replaceAll("\n", "")
.trim());
long milInt = Long.parseLong(sec[1]
.replaceAll("\\D+", "")
.replaceAll("\r", "")
.replaceAll("\n", "")
.trim());

return minInt * 60 * 1000 + secInt * 1000 + milInt * 10;
} else {

return null;

}

}

//把hashtable转为有秩序的组合
public void convertArrays() {
arr = lrcs.keySet().toArray();
Arrays.sort(arr);
}
}


mainActivity是这样的:

package china.testwt;

import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import butterknife.Bind;
import butterknife.ButterKnife;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class MainActivity extends AppCompatActivity {

private static final String path = "/storage/emulated/0/Music/Beyond - 午夜怨曲.mp3";
@Bind(R.id.songs)
china.testwt.MyView songs;

private MediaPlayer mPlayer;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);

mPlayer = new MediaPlayer();

try {
//数据源
songs.setLrcSource(new FileInputStream(new File("/storage/emulated/0/Music/Beyond - 午夜怨曲.lrc")));

} catch (FileNotFoundException e) {
e.printStackTrace();
}

try {
mPlayer.setDataSource(path);
mPlayer.setOnPreparedListener(new PreparedListener());
mPlayer.prepareAsync();

} catch (Exception e) {
e.printStackTrace();
}
}

private class PreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mp) {

mPlayer.start();

new Thread(new Runnable() {
@Override
public void run() {

while (mPlayer.isPlaying()) {

//传进去进度
songs.setLrcPostion(mPlayer.getCurrentPosition());

try {
//睡觉
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}

}


xml是这样的:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="china.testwt.MainActivity">

<china.testwt.MyView
android:id="@+id/songs"
app:normalTextSize="25dp"
app:currentTextSize="30dp"
app:bgColor="@color/colorPrimary"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>


PS,因为上面是基于本地lrc文件写的,所以不会引起空指针。但是如果在网络情况下,就是根据歌词名去搜索网络,你懂的,有些歌词名不一定规则,搜索不到,就会引起空指针。最好在自定义文件加个判断,判断那些集合或数组非空情况下才画画
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: