MainActivity:
import android.media.MediaPlayer;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class MainActivity extends AppCompatActivity implements View.OnClickListener, MediaPlayer.OnCompletionListener {
private Button btnMediaPlayer;
private MediaPlayer mp;
private TextView tvShowLrc;
private long begin; //获取开始播放media时的系统时间
private long currentTimeMill; //歌词播放计时器
private long nextTimeMill; //歌词时间戳
private String lyric; //临时保存需要显示的歌词
private Handler handler;
private UpdateTimeCallback updateTimeCallback = null;
private Queue time = null; //时间戳队列
private Queue content = null; //歌词内容队列
private final String textStart = "Start"; //播放/停止按钮文字内容
private final String textStop = "Stop"; //播放/停止按钮文字内容
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化View
initViews();
}
/**
* 初始化View
*/
public void initViews() {
//加载View控件
btnMediaPlayer = (Button) findViewById(R.id.btnMediaPlayer);
tvShowLrc = (TextView) findViewById(R.id.tvShowLrc);
//设置按钮的文字内容为"Start"
btnMediaPlayer.setText(textStart);
//为按钮设置监听器
btnMediaPlayer.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnMediaPlayer:
//判断播放media还是停止media
playOrStop();
break;
}
}
/**
* 检测当前media播放状态,如果正在播放中则停止,如果是停止状态则进行播放
*/
private void playOrStop() {
//判断按钮的文字内容,并进行播放/停止的处理
if (textStart.equals(btnMediaPlayer.getText().toString())) {
//准备media资源
mediaPrep();
if (!mp.isPlaying()) {
//准备歌词
lyricPrep();
//播放media
mediaPlay();
//开始同步歌词
lyricStart();
//将按钮的文字改成Stop
btnMediaPlayer.setText(textStop);
}
} else if (textStop.equals(btnMediaPlayer.getText().toString())) {
if (mp != null) {
if (mp.isPlaying()) {
//停止播放media
mediaStop();
//停止同步歌词
lyricStop();
//将按钮文字改成Start
btnMediaPlayer.setText(textStart);
}
}
}
}
/**
* 准备media资源
*/
private void mediaPrep() {
//加载音频文件
mp = MediaPlayer.create(this, R.raw.save_me_from_myself);
//设置播放完成监听器
mp.setOnCompletionListener(this);
}
/**
* 播放media
*/
private void mediaPlay() {
if (mp != null) {
mp.start();
}
}
/**
* 停止media
*/
private void mediaStop() {
if (mp != null) {
mp.stop();
}
}
/**
* 准备歌词
*/
private void lyricPrep() {
//创建时间戳队列和歌词队列的对象实例
time = new LinkedList<>();
content = new LinkedList<>();
try {
//读取歌词并传入LyricProcessor的对象中进行处理
InputStream in = getResources().getAssets().open("save_me_from_myself.lrc");
LyricProcessor lp = new LyricProcessor(in);
//将处理后的歌词保存至myLyric中
ArrayList<Lyric> myLyric = lp.getLyric();
//将myLyric中的时间戳和歌词分别保存至队列time,content中.
for (int i = 0; i < myLyric.size(); i++) {
Lyric l = myLyric.get(i);
time.offer(l.getTime());
content.offer(l.getContent());
}
//关闭流
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 开始同步歌词
*/
private void lyricStart() {
//实例化handler
handler = new Handler();
//初始化歌词同步的时间
currentTimeMill = 0;
//获取开始播放media时的系统时间
begin = System.currentTimeMillis();
//实例化updateTimeCallback,并传入时间戳队列和歌词队列
updateTimeCallback = new UpdateTimeCallback(time, content);
//开启线程
handler.postDelayed(updateTimeCallback, 5);
}
/**
* 停止歌词
*/
private void lyricStop() {
if (updateTimeCallback != null) {
handler.removeCallbacks(updateTimeCallback);
tvShowLrc.setText("");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mp != null) {
if (mp.isPlaying()) {
mp.stop();
mp.release();
} else {
mp.release();
}
}
lyricStop();
}
@Override
public void onCompletion(MediaPlayer mp) {
mediaStop();
lyricStop();
btnMediaPlayer.setText(textStart);
}
class UpdateTimeCallback implements Runnable {
Queue time = null;
Queue content = null;
public UpdateTimeCallback(Queue time, Queue content) {
this.time = time;
this.content = content;
}
@Override
public void run() {
if (time.peek() != null && content.peek() != null) {
//用系统当前时间减去开始播放media的时间,获得时间差offset
long offset = System.currentTimeMillis() - begin;
//判断是否第一行歌词
if (currentTimeMill == 0) {
nextTimeMill = (Long) time.poll();
lyric = (String) content.poll();
}
//判断当offset>= 第一个时间戳时,显示歌词,并将time,content两个队列中的下一个值取出
if (offset >= nextTimeMill) {
tvShowLrc.setText(lyric);
lyric = (String) content.poll();
nextTimeMill = (Long) time.poll();
}
//增加当前的歌词计时器以10毫秒不断增长
currentTimeMill = currentTimeMill + 10;
//延时10毫秒重复执行此任务
handler.postDelayed(updateTimeCallback, 10);
}
}
}
}
LrcProccessor:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LyricProcessor {
private InputStream inputStream; //歌词文件输入流
public LyricProcessor(InputStream inputStream) {
this.inputStream = inputStream;
}
public ArrayList<Lyric> getLyric() {
//将分析获取到的歌词保存至lyric里面
ArrayList<Lyric> lyric = new ArrayList<>();
//创建流实例
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
//通过分析歌词文件,分离时间段和歌词内容
String line;//临时存放歌词文件每一行的数据
Long time; //每一个时间点
String content;//每一句歌词
Pattern p = Pattern.compile("\\[([^\\]]+)\\]");//正则表达式
try {
while ((line = bufferedReader.readLine()) != null) {
Matcher m = p.matcher(line);//每一行歌词匹配规则
if (m.find()) {
String timeStr = m.group();//将时间戳临时保存
time = time2Long(timeStr.substring(1, timeStr.length() - 1));//转换时间戳为毫秒数
content = line.substring(10);//读取歌词内容
Lyric eachLyric = new Lyric(time, content);//将时间戳对应的歌词内容放入Lyric的对象中
lyric.add(eachLyric); //保存每一行歌词至ArrayList中
} else {
System.out.println("没有找到歌词");
}
}
} catch (IOException e) {
e.printStackTrace();
}
return lyric;
}
/**
* 将歌词里的时间转换成毫秒
*
* @param timeStr 歌词文件中的时间戳
* @return
*/
private Long time2Long(String timeStr) {
//通过":"符号将分钟分离为min
String s[] = timeStr.split(":");
int min = Integer.parseInt(s[0]);
//通过"."符号分离秒与分秒
String ss[] = s[1].split("\\.");
int sec = Integer.parseInt(ss[0]);
int mill = Integer.parseInt(ss[1]);
return min * 60000 + sec * 1000 + mill * 10L;//返回时间戳的毫秒数
}
}
Lyric:
public class Lyric {
private long time;//时间戳
private String content;//歌词内容
public Lyric(long time, String content) {
this.time = time;
this.content = content;
}
public long getTime() {
return time;
}
public String getContent() {
return content;
}
}