自定義view解析歌詞文件並同步顯示

最近有朋友問我做一個音樂播放器的難點在哪裏,如果是做一個來面試玩的話,都不怎麼難,開源項目中成熟的音樂播放器網上有很多了,但如果是給公司自己做的話,公司一般都不會使用第三方的sdk,那麼就要進行解碼器的開發了,最難的應該是這個吧。
下面進入今天的主題,歌詞解析:

*本篇文章已授權微信公衆號 guolin_blog (郭霖)獨家發佈

已經有兩個月時間沒有發表新文章了,從開始發表文章以來,常常會有線上的朋友通過QQ和微信與我進行一些技術上溝通和交流,我也收穫良多,幾乎每次到最後,我都會厚着臉皮說:”多多關注哦,近期會有新文章要發表呢!”。奈何,每一次當我熱情高漲準備在近期發表文章的時候,都因各種各樣的事情耽擱了,於是乎,我一次又一次爽約了。的確,最近工作確實比較忙,幾乎每一天都需要加班,難得某個週末的下午有點閒暇時間,我都泡在了球場上,熱愛籃球是其一,最重要的是做我們這行的,由於工作性質,每週一定的運動是非常必要的,身體是革命的本錢不是嘛! 不過,我可並沒有因爲忙碌就會在閒暇時候放縱自己,我依然堅持着自己的夢想,我一直在思考分享一些比較受用的功能效果,我也爭取把每一個Demo做到最好!

這次我要向大家分享的是一個歌詞控件,其實,也是我畢業設計中的一部分。起初我是用ScrollView嵌套TextView,再結合我的上一篇文章SpannableString來實現的,Demo其實我早早地就將放在github上,不知道有沒有朋友有留心看到,但是總感覺用着不是很流暢,而且不容易加入一些自定義內容,所以一直不好下筆,也不好向大家分享Demo。不過,有興趣的朋友可以看一下(下載地址),個人感覺還是挺有創意的,嘻嘻!(害羞臉~)。

用ScrollView嵌套TextView實現LyricView效果圖

效果圖是舊版本哦,記住,是舊版本! 通過自定義View實現的”進階版”LyricView功能更強大,體驗效果更佳,能夠實現歌詞滑動查看,當前播放位置高亮顯示,滑動到指定位置並播放等等,總的來說,大致和網易雲音樂的歌詞顯示效果一樣。

考慮到歌詞顯示控件涉及到歌詞解析,自定義控件的實現等等諸多方面,可能文章的篇幅上會比較冗長,同時也爲了方便”簡友”們能夠根據自己的需求和愛好各取所需。我也就仿着我之前寫的文章《像360懸浮窗那樣,用WindowManager實現炫酷的懸浮迷你音樂盒》那樣,將”用自定義View實現歌詞顯示控件”這篇文章也分成上、下兩篇,分別是《用自定義View實現歌詞顯示控件上篇之實現歌詞文件解析》和《用自定義View實現歌詞顯示控件下篇之自定義LyricView的實現》。而今天將要分享的是上篇,主要講解關於*.lrc文件的解析,內容偏理論,所以本章最後我也不會附上Demo,至於下篇我會盡快整理分享出來,屆時上下篇的Demo我會整合在一起共享出來。好吧,進入正題:

首先,瞭解歌詞文件的組成

寫過音樂播放器的朋友也應該都會去了解過歌詞文件的規範格式,既然是歌詞顯示控件,就必然需要好好了解歌詞文件的組成規範,才能準確無誤的解析歌詞文件,獲得與我有用的信息。

[ti:一個人的北京]
[ar:好妹妹樂隊]
[al:南北]
[by:]
[offset:0]
[00:00.10]一個人的北京 - 好妹妹樂隊
[00:00.20]詞:秦昊
[00:00.30]曲:秦昊
[00:00.40]
[00:30.16]你有多久沒有看到 滿天的繁星
[00:37.34]城市夜晚虛僞的光明 遮住你的眼睛
[00:44.40]連週末的電影 也變得不再有趣
[00:51.71]疲憊的日子裏 有太多的問題
[00:59.21]
[01:00.96]你有多久單身一人 不再去旅行
[01:08.20]習慣下班回到家裏 冷冰冰的空氣
[01:15.58]愛情這東西 你已經不再有勇氣
[01:22.64]情歌有多動聽 你就有多懷疑
[01:30.60]許多人來來去去 相聚又別離
[01:38.29]也有人喝醉哭泣 在一個人的北京
[01:45.16]也許我成功失意 慢慢的老去
[01:52.76]能不能讓我留下片刻的回憶
[01:58.95]
[01:59.67]許多人來來去去 相聚又別離
[02:07.23]也有人匆匆逃離 這一個人的北京
[02:14.30]也許有一天我們 一起離開這裏
[02:21.86]離開了這裏 在晴朗的天氣
[02:28.38]
[02:58.98]你有多久單身一人 不再去旅行
[03:06.36]習慣下班回到家裏 冷冰冰的空氣
[03:13.55]愛情這東西 你已經不再有勇氣
[03:20.69]情歌有多動聽 你就有多懷疑
[03:28.53]許多人來來去去 相聚又別離
[03:36.22]也有人喝醉哭泣 在一個人的北京
[03:43.28]也許我成功失意 慢慢的老去
[03:50.82]能不能讓我留下片刻的回憶
[03:57.64]許多人來來去去 相聚又別離
[04:05.25]也有人匆匆逃離 這一個人的北京
[04:12.31]也許有一天我們 一起離開這裏
[04:19.88]離開了這裏 在晴朗的天氣
[04:26.62]許多人來來去去 相聚又別離
[04:34.24]也有人匆匆逃離 這一個人的北京
[04:41.37]也許有一天我們 一起離開這裏
[04:48.87]離開了這裏 在晴朗的天氣
[04:55.08]
[04:56.27]讓我擁抱你 在晴朗的天氣

這如上述文本顯示,是我在QQ音樂中下載的歌詞文件,並用文本方式打開看到的一個結果。其實,所有歌詞文件(*.lrc)都是以一個標準來進行製作的,就如同上述文件一樣由以”[ti:”開頭的標題、以”[ar:”開頭的歌手、以”[al:”開頭的專輯、以”[by:”開頭的製作、以”[offset:”開頭的時間偏移量和以”[mm:ss.ms]”開頭的歌詞信息組成,歌詞信息則是由開始時間(分:秒.毫秒)和歌詞內容兩部分組成。

接着,解析歌詞文件

既然瞭解了歌詞文件的組成部分,那麼解析歌詞文件也就不是一件困難的事情了,就是簡單的文件內容讀取:首先獲取*.lrc歌詞文件的二進制流InputStream,再又轉換成字符流(注意:轉化成字符流的時候需要選擇編碼,經過我多次嘗試,發現好像QQ音樂的歌詞文件需要用”GBK”解碼,也不清楚會不會有具體的判別方式,有了解的朋友希望可以在留言區和大夥兒分享),然後再調用BufferedReader的readLine()方法逐行讀取文件內容,就能獲得文件內容了,在這裏有一點需要注意的是,各種流在使用結束後一定要調用close()方法關閉。下面就是實現歌詞文件的解析工作:

首先,需要準備兩個類主要用於歌詞解析結果的緩存:LyricInfo(歌詞信息:包含標題、歌手、專輯等等)和LineInfo(歌詞行信息:包含行開始時間和歌詞行內容):

LyricInfo 歌詞文件信息

class LyricInfo {
    List<LineInfo> song_lines;  

    String song_artist;  // 歌手
    String song_title;  // 標題
    String song_album;  // 專輯

    long song_offset;  // 偏移量
}

LineInfo 歌詞行信息

class LineInfo {
    String content;  // 歌詞內容
    long start;  // 開始時間
}

解析歌詞文件源碼

/**
 * 初始化歌詞信息
 * @param inputStream  歌詞文件的流信息
 * */
  private void setupLyricResource(InputStream inputStream, String charsetName) {
    if(inputStream != null) {
        try {
            LyricInfo lyricInfo = new LyricInfo();
            lyricInfo.song_lines = new ArrayList<>();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);
            BufferedReader reader = new BufferedReader(inputStreamReader);
            String line = null;
            while((line = reader.readLine()) != null) {
                analyzeLyric(lyricInfo, line);
            }
            reader.close();
            inputStream.close();
            inputStreamReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 /**
 * 逐行解析歌詞內容
 * */
private void analyzeLyric(LyricInfo lyricInfo, String line) {
    int index = line.lastIndexOf("]");
    if(line != null && line.startsWith("[offset:")) {
        // 時間偏移量
        String string = line.substring(8, index).trim();
        lyricInfo.song_offset = Long.parseLong(string);
        return;
    }
    if(line != null && line.startsWith("[ti:")) {
        // title 標題
        String string = line.substring(4, index).trim();
        lyricInfo.song_title = string;
        return;
    }
    if(line != null && line.startsWith("[ar:")) {
        // artist 作者
        String string = line.substring(4, index).trim();
        lyricInfo.song_artist = string;
        return;
    }
    if(line != null && line.startsWith("[al:")) {
        // album 所屬專輯
        String string = line.substring(4, index).trim();
        lyricInfo.song_album = string;
        return;
    }
    if(line != null && line.startsWith("[by:")) {
        return;
    }
    if(line != null && index == 9 && line.trim().length() > 10) {
        // 歌詞內容
        LineInfo lineInfo = new LineInfo();
        lineInfo.content = line.substring(10, line.length());
        lineInfo.start = measureStartTimeMillis(line.substring(0, 10));
        lyricInfo.song_lines.add(lineInfo);
    }
}


/**
 * 從字符串中獲得時間值
 * */
private long measureStartTimeMillis(String str) {
    long minute = Long.parseLong(str.substring(1, 3));
    long second = Long.parseLong(str.substring(4, 6));
    long millisecond = Long.parseLong(str.substring(7, 9));
    return millisecond + second * 1000 + minute * 60 * 1000;
}
最後,驗證解析效果

完成歌詞解析,接下來就是驗證歌詞解析的一個實際效果的時候了:

File file = new File(Constant.lyricPath + "一個人的北京 - 好妹妹樂隊.lrc");
    if (file != null && file.exists()) {
        try {
            setupLyricResource(new FileInputStream(file), "GBK");
            StringBuffer stringBuffer = new StringBuffer();
            if(lyricInfo != null && lyricInfo.song_lines != null) {
                int size = lyricInfo.song_lines.size();
                for (int i = 0; i < size; i ++) {
                    stringBuffer.append(lyricInfo.song_lines.get(i).content + "\n");
                }
                textView.setText(stringBuffer.toString());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

歌詞解析效果圖

關於歌詞解析的內容到這裏就結束了,下篇《用自定義View實現歌詞顯示控件下篇之自定義LyricView的實現》我也會盡快完稿發表,希望大家能夠多多關注哦!

作者申明:如果文中有表述不當或闡述錯誤的地方,還望正在看文章的您可以幫忙指出,有疑惑也可以在評論區提問或者私信,期待您的意見和建議,歡迎關注交流。

轉載自:http://blog.csdn.net/mario_0824/article/details/52124321
demo 下載地址:https://github.com/WuLiFei/LyricViewDemo
歡迎start,歡迎評論,歡迎指正

發佈了61 篇原創文章 · 獲贊 102 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章