1. 程式人生 > >Android歌詞播放的實現

Android歌詞播放的實現

公司專案最近有需求,要實現一個音樂系統,涉及到一個歌詞播放的功能,現將這個實現過程寫下來。

做之前先上網查了下相關APP的情況,發現QQ音樂的桌面歌詞效果,正是我想要的,顯示兩行歌詞,輪流播放,並顯示播放時的過渡效果。

首先分析的出,歌詞有兩行,那麼應該是兩個控制元件來的分別顯示,然後通過一系列演算法來控制兩行歌詞交替顯示,這樣的話,我們要先寫出用於顯示歌詞的控制元件來。


歌詞顯示自定義控制元件

歌詞控制元件應該具備的方法應該有,設定歌詞,設定字型顏色,字型大小,播放方法。
播放方法的引數一定要有播放的時長,和一個播放結束完畢的監聽,這個監聽提供給呼叫者,這樣才可以播放下一句歌詞。

我先將整個播放程式碼貼上來:

/**
     * 每執行一次,都會降計時器清零
     *
     * @param delay  執行前等待時間
     * @param period 迴圈時間
     */
    public void showLrc(final Activity context, long delay, long period, final ShowListener listener) {
        tvSelect.setVisibility(VISIBLE);
        tvSelect.setWidth(0);
        tvDefault.setText(lrc);
        tvSelect.setText(lrc);

        Log.i("_lrc", "start_openCounter:" + mOpenCounter.get());
        mOpenCounter.addAndGet(1);
        if (period < 100)
            period = 100;
        float speed = period / 100;

        mTimer = new Timer();
        TimerTask task = new TimerTask() {
            public void run() {
                context.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (percent < 100) {
                            percent++;
                        } else {
                            Log.i("_lrc", "end_openCounter:" + mOpenCounter.get());
                            if (mOpenCounter.get() > 0) {
                                mOpenCounter.set(0);
                                percent = 0;
                                listener.showFinish();
                                tvSelect.setVisibility(INVISIBLE);
                                cancel();
                            }
                        }
                        setPercent();
                    }
                });
            }
        };
        mTimer.schedule(task, delay, (long) speed);
    }


程式碼上有我剛才提到的幾個方法,比如設定歌詞,設定字型大小,播放等方法,但仔細看下歌詞的引數,仔細看第二個引數,這個引數是用於播放前等待時間,因為歌手在唱下一句的時候,會停留一些時間,會喘口氣,休息一會。

歌詞要播放的時候顯示過渡效果的話,就還要做一些處理,這裡的實現原理是,用兩個不同顏色的TextView重疊著,我們動態設定上面的TextView的寬度,就可以實現我們要的過渡效果了。

解析歌詞工具類

歌詞顯示的控制元件有了,接下來我們寫一個解析歌詞的工具類,這個類的作用是傳入一個LRC歌詞路徑,然後將歌詞和歌詞對應時間用兩個陣列容器存放著,提供給呼叫者獲取出歌詞和對應的時間即可。 LRC歌詞是歌詞前面會有對應的播放時間,這個時間用中括號括著,我們通過一點點解析演算法,將其放到兩個容器就好了。
package com.ysbing.lrcshow;

import android.text.TextUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LrcUtil {

    private List<Integer> mTimeList = new ArrayList<>();
    private List<String> mWords = new ArrayList<>();
    private Iterator<Integer> mTimeIterator;
    private Iterator<String> mWordIterator;

    public LrcUtil(File lrcFile) throws FileNotFoundException {
        readLrcFile(lrcFile);
    }

    public boolean hasTime() {
        return mTimeIterator.hasNext();
    }

    public boolean hasWord() {
        return mWordIterator.hasNext();
    }

    public int getTime() {
        return mTimeIterator.next();
    }

    public String getWord() {
        return mWordIterator.next();
    }

    //處理歌詞檔案
    private void readLrcFile(File file) throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream(file);
        InputStreamReader inputStreamReader;
        try {
            inputStreamReader = new InputStreamReader(
                    fileInputStream, "utf-8");
            readLrcStream(inputStreamReader);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private void readLrcStream(InputStreamReader inputStreamReader) {
        BufferedReader bufferedReader = new BufferedReader(
                inputStreamReader);
        String s;
        try {
            while ((s = bufferedReader.readLine()) != null) {
                addTimeToList(s);
                if ((s.contains("[ar:")) || (s.contains("[ti:")) || (s.contains("[by:")) || (s.contains("[al:"))) {
//                    s = s.substring(s.indexOf(":") + 1, s.indexOf("]"));
                    continue;
                } else {
                    if (TextUtils.isEmpty(s))
                        continue;
                    int startIndex = s.indexOf("[");
                    int endIndex = s.indexOf("]");
                    if (startIndex >= 0 && endIndex >= 0) {
                        String ss = s.substring(startIndex, endIndex + 1);
                        s = s.replace(ss, "");
                    } else continue;
                }
                mWords.add(s);
            }
            mTimeIterator = mTimeList.iterator();
            mWordIterator = mWords.iterator();
            bufferedReader.close();
            inputStreamReader.close();
        } catch (IOException e) {
            e.printStackTrace();
            mWords.add("沒有讀取到歌詞");
        }
    }

    private void addTimeToList(String string) {
        Matcher matcher = Pattern.compile(
                "\\[\\d{1,2}:\\d{1,2}([\\.:]\\d{1,2})?\\]").matcher(string);
        if (matcher.find()) {
            String str = matcher.group();
            mTimeList.add(timeHandler(str.substring(1,
                    str.length() - 1)));
        }
    }

    // 分離出時間
    private int timeHandler(String string) {
        string = string.replace(".", ":");
        String timeData[] = string.split(":");
        // 分離出分、秒並轉換為整型
        int minute = Integer.parseInt(timeData[0]);
        int second = Integer.parseInt(timeData[1]);
        int millisecond = Integer.parseInt(timeData[2]);
        if (timeData[2].length() == 1)
            millisecond *= 100;
        else if (timeData[2].length() == 2)
            millisecond *= 10;

        // 計算上一行與下一行的時間轉換為毫秒數
        return (minute * 60 + second) * 1000 + millisecond;
    }
}

歌詞控制

歌詞解析工具也有了,接下來要做的事情,就是本節部落格的核心,分析如何控制兩個歌詞的交替顯示。 歌詞有兩行,我們先開始命名,第一行歌詞的時間叫“時間一”,歌詞叫“歌詞一”,第二行則叫“時間二”和“歌詞二”。 如果歌詞1為空,設定歌詞1為下一句歌詞,記錄時間1-時間2為歌詞2的等待時間
歌詞1播放->時間2-時間1

我們要考慮到的情況有,在LRC歌詞檔案中,在歌手喘氣休息的時候,是沒歌詞的,這種情況在LRC中,沒歌詞,有時間; 假設我們在播放第一行歌詞的時候,就有這樣的邏輯

如果歌詞二為空,就獲取下一句歌詞作為歌詞二,獲取下一個時間為時間二

在歌詞一播放完後,我們在播放完的回撥中,再播放歌詞二,這樣迴圈下去,知道歌詞結束。

這樣我們就實現了歌詞的交叉播放,但還存在一個問題,就是,播放的時候是一句接著一句的,如果手機效能比較差,就可能慢慢的,就會和音樂不同了,這個情況也是不允許出現的,所以開始升級。

升級後邏輯是:

記錄開始時間
播放前獲取現在時間,減去和歌詞時間的差
歌詞結束時間減去開始播放時間,算出速度

這樣總體我們實現完了,效果還可以,就算手機再差,也會在播放下一句中的時候,準確對準歌詞的時間。原始碼我上傳到CSDN,再去下載看看具體邏輯吧,謝謝觀看!

資源下載地址:http://download.csdn.net/detail/ysb794008002/9586853