1. 程式人生 > >Android-歌詞同步功能程式碼展示

Android-歌詞同步功能程式碼展示

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;
    }
}