Android 超簡單音樂播放器(十)歌詞的實現

ArcheH發表於2017-10-27

關於歌詞

有下面這些:

  • 歌詞的獲取

  • 歌詞的解析

  • 自定義View


歌詞的獲取

歌詞的獲取分為兩種,一種是從本地一種是通過網路上提供的API獲取。我選擇的是歌詞迷的API http://api.geci.me/en/latest/ 說實話,這個API並不是很好用,因為很多歌它都無法提供歌詞。但是我懶得去找其他的啦,所以就用它好啦。

  • 首先,我們要(-。-;)不知道這裡怎麼說,就說通過API找到我們需要的資料吧
    和之前獲取熱門和搜尋網路歌曲一樣,因為這個API是根據歌名查詢的,所以我們需要傳入一個String name。但是這個地方需要注意,有的歌曲名可能會有空格,這樣會影響我們獲取歌詞,所以,需要把空格都去掉。總的來說這裡問題並不難..但是我弄了很久,就是因為這個空格問題,導致我每首歌曲查詢都是沒有歌詞0 0讓我產生了一種錯覺,是我使用了(line = in.readLine() )!= null 的問題(這裡摸一把淚( ▼-▼ ))
 public static void requstLrcData(final String name,okhttp3.Callback callback){
        OkHttpClient client = new OkHttpClient();
        Log.i(TAG, "requstLrcData: "+name);
        String name1 = name.trim();//去掉空格
        Log.i(TAG, "requstLrcData:"+name1);
        Request request = new Request.Builder()
                .url("http://gecimi.com/api/lyric/"+name1)
                .build();
        client.newCall(request).enqueue(callback);
    }
  • 然後我們就要解析我們得到的資料
    通過官方文件我們可以看到,會提供給我們歌詞檔案以及相應的數量。一步一步解析出自己需要的資料就好。這裡寫圖片描述

傳入c值表示當前需要的歌詞檔案是第幾個。這樣寫是為了當使用者發現歌詞不同時,可以切換歌詞。當沒有資料或者失敗時,返回“”。

  public static String parseJOSNWithGSON(Response response ,int c){
        try{
            String ResponsData = response.body().string();
            JSONObject jsonObject = new JSONObject(ResponsData);
            int count = Integer.parseInt(jsonObject.getString("count"));
            Log.i("TAG", "parseJOSNWithGSONCOUNT:"+count);
            if (count>=c){
                String result = jsonObject.getString("result");
                JSONArray jsonArray = new JSONArray(result);
                JSONObject jsonObject1 = jsonArray.getJSONObject(c-1);
                String url = jsonObject1.getString("lrc");
                Log.i("TAG", "parseJOSNWithGSON:1 "+url);
                return url;
            }else {
                Log.i("TAG", "parseJOSNWithGSON: "+c);
                return "";
            }
        }catch (Exception e){

        }
        return "";

    }
  • 最後根據解析出來的URL讀取出歌詞,準備解析
    public static String getLrcFromAssets(String Url){
        Log.i("first","getLrcFromAssets: "+Url);
        if (Url.equals("")){
            return "";
        }
        try {
            URL url=new URL(Url);
            HttpURLConnection conn=(HttpURLConnection)url.openConnection();
            conn.setDoInput(true);
            conn.setRequestMethod("GET");
            InputStream input=conn.getInputStream();
            BufferedReader in=new BufferedReader(new InputStreamReader(input));
            String line = "" ;
            String result = "";
            while ((line = in.readLine() )!= null){//逐行讀取
                if (line.trim().equals(""))
                    continue;
                result += line + "\r\n";
                Log.i("first","getLrcFromAssets: "+result);
            }
            Log.i("total","getLrcFromAssets: "+result);
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

到這裡我們的歌詞就讀取出來(此處應有掌聲)~

歌詞的解析

我們一般的歌詞檔案分為兩種。
第一種:

[ti:流浪的小孩]
[ar:伊能靜]
[al:流浪的小孩]
[00:01]流浪的小孩
[00:05]專輯:流浪的小孩
[00:07] 唱: 伊能靜
[00:08]流浪的小孩 淚為自己流
[00:15]流浪的小孩 笑發自心中
[00:22]流浪的小孩 少年多揮霍
[00:28]心比世界還寬容
[00:34]MUSIC
[01:06]不斷要往那裡走 找到一個地方屬於我
[01:14]不需要勉強虛偽 心像風一樣自由
[01:22]告別金色和無知 不希望自己變得太成熟
[01:30]成人的謊言太多 夢也漸漸被現實奪走
[01:38]我要出去走一走 年少的心有勇氣追求
[01:46]看世界多開闊 天能夠有多大 夢多難求
[01:54]我要出去走一走 孤單的生活會更快樂
[02:02]也許會有挫折 但這是我的選擇
[02:10]流浪的小孩 淚為自己流

第二種:

[00:00.00]海闊天空
[00:15.00]專輯:樂與怒
[00:18.00] [01:43.00][00:19.00]今天我寒夜裡看雪飄過
[01:49.00][00:25.00]懷著冷卻了的心窩飄遠方
[01:55.00][00:31.00]風雨裡追趕
[01:58.00][00:34.00]霧裡分不清影蹤
[02:05.00][00:40.00]可會變(誰沒在變)
[00:50.00]從沒有放棄過心中的理想
[00:56.00]一剎那恍惚
[01:05.00]心裡愛(誰明白我)

第一種只有分和秒,而第二種還包含了毫秒。除此之外,有的時間會對應多句歌詞。所以我們在解析前就需要把時間都分開,最後按照時間大小排序 。

  • 新建一個介面
public interface ILrcBulider {
    List<LrcRow> getLrcRows(String rawLrc);
}
  • 然後是解析歌詞構造器
    實現上面寫的那個介面,得到一個按時間從小到大歌詞集合。
public class DefaultLrcBulider implements ILrcBulider {
    @Override
    public List<LrcRow> getLrcRows(final String rawLrc) {
        Log.i(TAG,"getLrcRows by rawString"+rawLrc+"");
        if (rawLrc == null || rawLrc.length() == 0){
            Log.i(TAG,"getLrcRows rawLrc null or empty");
            return null;
        }
        StringReader reader = new StringReader(rawLrc);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String line = null;
        List<LrcRow> rows = new ArrayList<LrcRow>();
        try{
            //迴圈地讀取歌詞的每一行
            do{
                line = bufferedReader.readLine();
                Log.i(TAG,"lrc raw line: " + line);
                if(line != null && line.length() > 0){
                    //解析每一行歌詞 得到每行歌詞的集合,因為有些歌詞重複有多個時間,就可以解析出多個歌詞行來
                    List<LrcRow> lrcRows = LrcRow.createRows(line);
                    if(lrcRows != null && lrcRows.size() > 0){
                        for(LrcRow row : lrcRows){//將每一個LrcRow依次新增
                            rows.add(row);
                        }
                    }
                }
            }while(line != null);

            if( rows.size() > 0 ){
                // 根據歌詞行的時間排序
                Collections.sort(rows);
                if(rows!=null&&rows.size()>0){
                    for(LrcRow lrcRow:rows){
                        Log.i(TAG, "lrcRow:" + lrcRow.toString());
                    }
                }
            }
        }catch(Exception e){
            Log.e(TAG,"parse exceptioned:" + e.getMessage());
            return null;
        }finally{
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            reader.close();
        }
        return rows;
    }
}
}

這裡插播一下

1.什麼是Comparable介面

此介面強行對實現它的每個類的物件進行整體排序。此排序被稱為該類的自然排序 ,類的 compareTo 方法被稱為它的自然比較方法
。實現此介面的物件列表(和陣列)可以通過 Collections.sort (和 Arrays.sort
)進行自動排序。實現此介面的物件可以用作有序對映表中的鍵或有序集合中的元素,無需指定比較器。 強烈推薦(雖然不是必需的)使自然排序與
equals 一致。所謂與equals一致是指對於類 C 的每一個 e1 和 e2 來說,當且僅當
(e1.compareTo((Object)e2) == 0) 與e1.equals((Object)e2) 具有相同的布林值時,類 C
的自然排序才叫做與 equals 一致 。

2.實現什麼方法

int compareTo(T o) 比較此物件與指定物件的順序。如果該物件小於、等於或大於指定物件,則分別返回負整數、零或正整數。 引數:
o - 要比較的物件。 返回:
負整數、零或正整數,根據此物件是小於、等於還是大於指定物件。 丟擲:
ClassCastException - 如果指定物件的型別不允許它與此物件進行比較。

因為我們要實現排序,所以要通過實現Comparable介面來實現。

public class LrcRow implements Comparable<LrcRow>{
    public final static String TAG = "LrcRow";
    /** 該行歌詞要開始播放的時間/
    public String strTime;
    /** 該行歌詞要開始播放的時間轉換為long型,
     * 即將2分34秒14毫秒都轉為毫秒後 得到的long型值:time=02*60*1000+34*1000+14
     */
    public long time;
    /** 該行歌詞的內容 */
    public String content;

    public LrcRow(){}

    public LrcRow(String strTime,long time,String content){
        this.strTime = strTime;
        this.time = time;
        this.content = content;
    }

    @Override
    public String toString() {
        return "[" + strTime + " ]"  + content;
    }

    /**
     * 讀取歌詞的每一行內容,轉換為LrcRow,加入到集合中
     */
    public static List<LrcRow> createRows(String standardLrcLine){

        try{//判斷是否包含為有時間的歌詞內容(兩種歌詞檔案都進行判斷)
            if (standardLrcLine.indexOf("[") == 0){
                 if (standardLrcLine.indexOf("]") == 6 ||standardLrcLine.indexOf("]") == 9 ){

                 }else{
                    if (standardLrcLine.indexOf(".") == 6){

                    }else {
                        return null;
                    }
                 }
            }else{
                return null ;
            }

            //[02:34.14][01:07.00]當你我不小心又想起她
            //找到最後一個 ‘]’ 的位置
            int lastIndexOfRightBracket = standardLrcLine.lastIndexOf("]");
            //歌詞內容就是 ‘]’ 的位置之後的文字   eg:   當你我不小心又想起她
            String content = standardLrcLine.substring(lastIndexOfRightBracket + 1, standardLrcLine.length());
            //歌詞時間就是 ‘]’ 的位置之前的文字   eg:   [02:34.14][01:07.00]

            /**
             將時間格式轉換一下  [mm:ss.SS][mm:ss.SS] 轉換為  -mm:ss.SS--mm:ss.SS-
             即:[02:34.14][01:07.00]  轉換為      -02:34.14--01:07.00-
             */
            String times = standardLrcLine.substring(0,lastIndexOfRightBracket + 1).replace("[", "-").replace("]", "-");
            //通過 ‘-’ 來拆分字串
            String arrTimes[] = times.split("-");
            List<LrcRow> listTimes = new ArrayList<LrcRow>();
            for(String temp : arrTimes){
                if(temp.trim().length() == 0){
                    continue;
                }
                /** [02:34.14][01:07.00]當你我不小心又想起她
                 *
                 上面的歌詞的就可以拆分為下面兩句歌詞了
                 [02:34.14]當你我不小心又想起她
                 [01:07.00]當你我不小心又想起她
                 */
                LrcRow lrcRow = new LrcRow(temp, timeConvert(temp), content);
                listTimes.add(lrcRow);
            }
            return listTimes;
        }catch(Exception e){
            Log.e(TAG,"createRows exception:" + e.getMessage());
            return null;
        }
    }

    /**
     * 將解析得到的表示時間的字元轉化為Long型
     */
    private static long timeConvert(String timeString){
        if(timeString.length() == 5){
            timeString =timeString+":00";
        }//如果為第一種歌詞檔案,則將其轉換為第二種。
        //因為給如的字串的時間格式為XX:XX.XX,返回的long要求是以毫秒為單位
        //將字串 XX:XX.XX 轉換為 XX:XX:XX
        timeString = timeString.replace('.', ':');
        //將字串 XX:XX:XX 拆分
        String[] times = timeString.split(":");
        // mm:ss:SS
        return Integer.valueOf(times[0]) * 60 * 1000 +//分
                Integer.valueOf(times[1]) * 1000 +//秒
                Integer.valueOf(times[2]) ;//毫秒
    }

    /**
     * 排序的時候,根據歌詞的時間來排序
     */
    public int compareTo(LrcRow another) {
        return (int)(this.time - another.time);
    }
}
  • 自定義View

跟著網上大神寫的。 https://github.com/ouyangpeng/android-lrc-view-oyp
主要就是根據當前時間去判斷應該讓哪一句歌詞高亮。
通過計時器每秒鐘更新當前時間,(我是放在歌詞進度條的那個計時器中的,這樣可以簡潔很多嘛)
當這個時間處於AB兩句歌詞之間時,就讓A高亮。


學習這個雖然用了很多時間,但是收穫還是很大的。
路漫漫其修遠兮,加油吧。
珍惜這段時間,可以做自己喜歡的事情。
覺得非常幸運~
[愛心]


相關文章