Android歌詞文本解析

1. LRC文本解讀

Android中要實(shí)現(xiàn)滾動(dòng)歌詞的第一步,是需要對(duì)LRC歌詞文本進(jìn)行解析,首先來看一份標(biāo)準(zhǔn)的歌詞文本模式:

[ti:失戀戰(zhàn)線聯(lián)盟]
[ar:草蜢]
[al:]
[00:00.00]草蜢-失戀戰(zhàn)線聯(lián)盟
[00:08.78]編輯:小婧
[01:43.33][00:16.27]她總是只留下電話號(hào)碼
[01:46.97][00:19.81]從不肯讓我送她回家
[01:50.61][00:23.43]聽說你也曾經(jīng)愛上過她
[01:54.15][00:27.07]曾經(jīng)也同樣無法自拔
[01:57.78][00:30.72]你說你學(xué)不會(huì)假裝瀟灑
[02:01.41][00:34.36]卻叫我別太早放棄她
[02:05.05][00:37.99]把過去傳說成一段神話
[02:08.70][00:41.59]然后笑你是一樣的傻
[02:12.01][00:45.11]我們那么在乎她
[02:14.15][00:47.01]卻被她全部抹殺
[02:15.96][00:48.87]越談她越相信永遠(yuǎn)得不到回答
[02:19.57][00:52.49]到底她怎么想
[02:21.35][00:54.28]應(yīng)該繼續(xù)在這么
[02:23.37][00:56.36]還是說穿跑了吧
[02:26.89][00:59.80]找一個(gè)承認(rèn)失戀的方法
[02:30.48][01:03.41]讓心情好好地放個(gè)假
[02:34.14][01:07.00]當(dāng)你我不小心又想起她
[02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
[02:48.69]

LRC有兩種類型的標(biāo)簽,一種是 標(biāo)識(shí)標(biāo)簽,一種是 時(shí)間標(biāo)簽

  • 標(biāo)識(shí)標(biāo)簽,格式為[標(biāo)識(shí)名:值],例如[ti:失戀戰(zhàn)線聯(lián)盟],這種預(yù)定義的標(biāo)簽
  • 時(shí)間標(biāo)簽,格式為[mm:ss][mm:ss.ff](分鐘:秒數(shù).毫秒數(shù)),例如[02:48.69],其中數(shù)字必須為非負(fù)整數(shù)。
    時(shí)間標(biāo)簽必須位于行首位置,一行歌詞可以包含多個(gè)時(shí)間標(biāo)簽,例如[02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉,表示 “就在記憶里畫一個(gè)叉” 這句在這首歌的[02:45.69][02:42.20][02:37.69][01:10.60]這四個(gè)時(shí)間節(jié)點(diǎn)出現(xiàn)過。這句歌詞可以簡(jiǎn)化為
[02:45.69]就在記憶里畫一個(gè)叉
[02:42.20]就在記憶里畫一個(gè)叉
[02:37.69]就在記憶里畫一個(gè)叉
[01:10.60]就在記憶里畫一個(gè)叉

2. 從assets目錄下讀取LRC文件

在真實(shí)項(xiàng)目中一般文件需要及時(shí)去獲取,此處為了方便,我們將LRC文本放在assets目錄下進(jìn)行讀取,從assets目錄下讀取文件的代碼如下:

/**
     * 根據(jù)文件名獲取assets目錄下的文件內(nèi)容
     * @param fileName
     * @return
     */
private String getLrcText(String fileName) {
        String lrcText = null;
        try {
            InputStreamReader inputReader = new InputStreamReader(getResources().getAssets().open(fileName));
            BufferedReader bufReader = new BufferedReader(inputReader);
            String line = "";
            while ((line = bufReader.readLine()) != null)
                lrcText += line;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return lrcText;
    }

將text.lrc文件解析為字符串lrcText。

3. 解析由LRC文件得到的lrcText 字符串

創(chuàng)建LrcEntity類,用于解析每行歌詞包含的內(nèi)容,該類包括3個(gè)變量:time,timeLong,text

  • time: 表示當(dāng)前行歌詞的播放時(shí)間標(biāo)簽,例如[01:10.60]
  • timeLong: 表示當(dāng)前時(shí)間標(biāo)簽time轉(zhuǎn)換為long型后的數(shù)值,例如[01:10.60]轉(zhuǎn)換為long為70060毫秒(70060=01 * 60
    1000 + 10 * 1000+60)
  • text: 表示當(dāng)前歌詞的內(nèi)容,例如就在記憶里畫一個(gè)叉
    創(chuàng)建的LrcEntity類如下所示:
/**
 * 歌詞解析基類
 */
public class LrcEntity {
    public String time;
    public long timeLong;
    public String text;

    public LrcEntity(String time, String text, long timeLong) {
        this.time = time;
        this.text = text;
        this.timeLong = timeLong;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }

    public long getTimeLong() {
        return timeLong;
    }

    public void setTimeLong(long timeLong) {
        this.timeLong = timeLong;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    @Override
    public int compareTo(LrcEntity entity) {
        if (entity == null) {
            return -1;
        }
        return (int) (timeLong - entity.getTimeLong());
    }

    /**
     * 解析歌詞文本
     * @param lrcText
     * @return
     */
    private static List<LrcEntity> parseLrc(String lrcText) {
        if (TextUtils.isEmpty(lrcText)) {
            return null;
        }

        List<LrcEntity> entityList = new ArrayList<>();
        // 將字符串以換行符切割
        String[] array = lrcText.split("\\n");
        for (String line : array) {
            // 循環(huán)遍歷按行解析
            List<LrcEntity> list = parseLine(line);
            if (list != null && !list.isEmpty()) {
                entityList.addAll(list);
            }
        }

        // 使序列按大小升序排序(由小到大)
        Collections.sort(entityList);
        return entityList;
    }

    /**
     * 針對(duì)每一句歌詞解析,并存到LrcEntity中
     * @param line
     * @return
     */
    private static List<LrcEntity> parseLine(String line) {
        if (TextUtils.isEmpty(line)) {
            return null;
        }
        // 去除空格
        line = line.trim();
        // 正則表達(dá)式,判斷l(xiāng)ine中是否有[01:10.60]格式的片段
        Matcher lineMatcher = Pattern.compile("((\\[\\d\\d:\\d\\d\\.\\d\\d\\])+)(.+)").matcher(line);
        // 如果沒有,返回null
        if (!lineMatcher.matches()) {
            return null;
        }

        // 得到時(shí)間標(biāo)簽
        String times = lineMatcher.group(1);
        // 得到文本內(nèi)容
        String text = lineMatcher.group(3);
        List<LrcEntity> entryList = new ArrayList<>();

        Matcher timeMatcher = Pattern.compile("\\[(\\d\\d):(\\d\\d)\\.(\\d\\d)\\]").matcher(times);
        while (timeMatcher.find()) {
            long min = Long.parseLong(timeMatcher.group(1));// 分
            long sec = Long.parseLong(timeMatcher.group(2));// 秒
            long mil = Long.parseLong(timeMatcher.group(3));// 毫秒
            // 得到long型時(shí)間
            long time = min * DateUtils.MINUTE_IN_MILLIS + sec * DateUtils.SECOND_IN_MILLIS + mil * 10;
            // 最終解析得到一個(gè)list
            entryList.add(new LrcEntity(times, text, time));
        }
        return entryList;
    }
}

代碼中有很詳細(xì)的注釋, 上述parseLine方法中用到了正則表達(dá)式,不了解的同學(xué)請(qǐng)自行百度,很簡(jiǎn)單。這種解析方式的弊端在于不能解析多個(gè)時(shí)間標(biāo)簽同時(shí)存在,當(dāng)然肯定是有方法的,但是我還沒學(xué)會(huì)。。。
我們來看另外一種接地氣的方法:

/**
     * 針對(duì)每一句歌詞解析,并存到LrcEntity中
     *
     * @param line [02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
     * @return
     */
    public List<LrcEntity> parseLine2(String line) {
        List<LrcEntity> entryList = new ArrayList<>();
        int pos1 = line.indexOf("[");//0
        int pos2 = line.indexOf("]");//9  indexof如果找不到返回-1
        if (pos1 == 0 && pos2 != -1) {
            //long數(shù)組用于存放時(shí)間戳,判斷含有多少個(gè)時(shí)間標(biāo)簽
            String[] times = new String[getCount(line)];
            String strTime = line.substring(pos1, pos2+1);//[02:45.69]
            // 時(shí)間標(biāo)簽數(shù)組
            times[0] = strTime;
            //判斷是否還有下一個(gè)
            String text = line;//[02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
            int i = 1;
            while (pos1 == 0 && pos2 != -1) {//判斷是否有時(shí)間的顯示,既歌詞
                text = text.substring(pos2 + 1);//[02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
                pos1 = text.indexOf("[");//0
                pos2 = text.indexOf("]");//9
                if (pos2 != -1) {
                    strTime = text.substring(pos1, pos2 + 1);//[02:42.20]
                    times[i] = strTime;//將第二個(gè)時(shí)間戳添加到數(shù)組中
                    if (times[i] == "") {
                        return entryList;
                    }
                }
                i++;
            }

            LrcEntity lrcEntity = new LrcEntity();
            for (int j = 0; j < times.length; j++) {
                if (times[j] != null) {
                    lrcEntity.setText(text);
                    lrcEntity.setTimeLong(Str2Long(times[j]));
                    lrcEntity.setTime(times[j]);
                    entryList.add(lrcEntity);//將歌詞信息添加到集合中
                    lrcEntity = new LrcEntity();
                }
            }
        }
        return entryList;
    }

    //將字符串轉(zhuǎn)換為long類型
    private long Str2Long(String strTime) {
        long showTime = -1;
        try {
            strTime = strTime.substring(1,strTime.length()-1);
            String[] s1 = strTime.split(":");
            String[] s2 = s1[1].split("\\.");
            long min = Long.parseLong(s1[0]);
            long second = Long.parseLong(s2[0]);
            long mil = Long.parseLong(s2[1]);
            showTime = min * 60 * 1000 + second * 1000 + mil * 10;
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        return showTime;
    }

    /**
     * 判斷當(dāng)前行的歌詞播放幾次
     *
     * @param line
     * @return
     */
    private int getCount(String line) {
        String[] split = line.split("\\]");
        return split.length;
    }

由于上述代碼都是重復(fù)的,這里只給出針對(duì)每一句歌詞解析,并存到LrcEntity中的方法,主要是針對(duì)字符串進(jìn)行切割,是字符串的基本操作,相信大家對(duì)這塊都很熟悉。
最后得到了對(duì)歌詞文本解析的集合,其中包括時(shí)間標(biāo)簽,內(nèi)容以及時(shí)間標(biāo)簽轉(zhuǎn)換為long后的數(shù)據(jù),解析這里基本就說完了,關(guān)于歌詞的顯示和繪制,下次再說,謝謝大家。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,837評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,740評(píng)論 18 399
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,766評(píng)論 0 33
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,497評(píng)論 0 17
  • 17.00:吃飯 17.30:打電話, 錯(cuò)過兩件事,輝弟與借書 18.30:回到宿舍,QQ群管理,聽音樂,看得到槽...
    葉鳥app閱讀 217評(píng)論 0 0