Android-歌詞同步功能程式碼展示
阿新 • • 發佈:2019-02-02
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;
}
}