歌詞顯示控件的實現上——歌詞解析

最近打算仿網易雲音樂的音樂播放器,除了網絡框架、接口數據等這些外,最核心的就是音樂的播放和歌詞的顯示。json

考慮到歌詞顯示控件涉及到歌詞解析,自定義控件的實現等等諸多方面,可能文章的篇幅上會比較冗長,同時也爲了方便本身和碼友們可以根據本身的需求和愛好各取所需,將《歌詞顯示控件的實現上》這篇文章分紅上、下兩篇,分別是《歌詞顯示控件的實現上——歌詞解析》和《歌詞顯示控件的實現下——歌詞展現自定義View》。而今天將要分享的是上篇,主要講解關於*.lrc文件的解析。緩存

咱們本文的目的是將lrc格式的歌詞文件進行解析,並能將其展現到界面。bash

先看下效果: 微信

這裏寫圖片描述

ok,開始切入正題網絡

1、瞭解歌詞文件結構

寫過音樂播放器的朋友可能都瞭解過歌詞文件的規範格式,既然是歌詞顯示的控件,就必然須要清楚地瞭解歌詞文件的組成規範,才能準確無誤的解析歌詞文件,獲得咱們想要的信息。數據結構

那咱們先看一個最普通的歌詞文件:app

[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]

複製代碼

全部歌詞文件——*.lrc文件 都是以一個標準來進行製做的(如上)。框架

從上述內容能夠得出:工具

  • 全部標籤(包括時間標籤)所有由「[」和「]」兩個符合標識,其對應各自內容
  • 一行只表達一條信息
  • "ti"表示標題、"ar"表示歌手、"al"表示專輯、"by"表示製做、"offset:"表示時間偏移量
  • "[mm:ss.ms]"表示歌詞時間和內容

對比json和xml結構的數據,歌詞這樣的數據結構更加簡單和清晰。ui

瞭解清楚歌詞文件結構,咱們就能對症下藥:

2、開始解析

既然瞭解了歌詞文件的組成部分,那麼解析歌詞文件也就不難,就是簡單的文件內容讀取:

  • 一、首先獲取*.lrc歌詞文件的二進制流InputStream,
  • 二、再又轉換成字符流
  • 三、而後再調用BufferedReader的readLine()方法逐行讀取文件內容

就能得到文件內容了,在這裏有一點須要注意的是,各類流在使用結束後必定要調用close()方法關閉。

下面就是實現歌詞文件的解析工做:

一、實體類

首先,須要準備兩個類主要用於歌詞解析結果的緩存:

LineInfo:歌詞行信息:包含行開始時間和歌詞行內容 LyricInfo:歌詞信息:包含標題、歌手、專輯等等

/**
 * Description: 歌詞  每行信息 實體類
 * Created by jia on 2017/10/31.
 * 人之因此能,是相信能
 */
public class LineInfo {

    private String content;

    private long startTime;

	// 對應的get/set方法略
	
}
複製代碼
/**
 * Description: 歌詞信息  實體類
 * Created by jia on 2017/10/31.
 * 人之因此能,是相信能
 */
public class LyricInfo {

    // 偏移量
    private long offset;
    // 名字
    private String title;
    // 做者
    private String artist;
    // 專輯
    private String album;
    // 製做
    private String by;

    // 歌詞
    private List<LineInfo> lines;

	// 對應的get/set方法略
}
複製代碼

二、解析工具類

首先由於在實體類中,包括之後自定義View時的時間都是以毫秒爲單位的long類型,因此咱們須要一方法將時間標籤中的內容轉爲long類型的毫秒值:

/**
 * 從字符串中得到時間值
 */
private static 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;
}
複製代碼

而後就是逐行解析:

/**
     * 逐行解析歌詞
     *
     * @param info 實體類
     * @param line 每行內容
     */
    private static void analyzeLyricByLine(LyricInfo info, String line) {

        int index = line.lastIndexOf("]");

        // 標題
        if (!TextUtils.isEmpty(line) && line.startsWith("[ti:")) {
            info.setTitle(line.substring(4, index).trim());
            return;
        }

        // 歌手
        if (!TextUtils.isEmpty(line) && line.startsWith("[ar:")) {
            info.setArtist(line.substring(4, index).trim());
            return;
        }

        // 專輯
        if (!TextUtils.isEmpty(line) && line.startsWith("[al:")) {
            info.setAlbum(line.substring(4, index).trim());
            return;
        }

        // 製做
        if (!TextUtils.isEmpty(line) && line.startsWith("[by:")) {
            info.setBy(line.substring(4, index).trim());
            return;
        }

        // 偏移量
        if (!TextUtils.isEmpty(line) && line.startsWith("[offset:")) {
            info.setOffset(Long.parseLong(line.substring(8, index).trim()));
            return;
        }

        // 歌詞內容
        if (line!=null && index == 9 && line.trim().length() >= 10) {
            LineInfo lineInfo = new LineInfo();
            lineInfo.setStartTime(measureStartTimeMillis(line.substring(0, 10)));
            if(line.length()==10){
                lineInfo.setContent("");
            }else{
                lineInfo.setContent(line.substring(10, line.length()));
            }
            info.getLines().add(lineInfo);// 添加到歌詞集合中
            return;
        }

        return;
    }
複製代碼

咱們須要對各類標籤進行判斷和解析,而後賦值給實體類對象。

首先拿到"]"字符的索引,而後截取對應標籤的內容進行匹配,分別進行賦值。

特別的想說一句:解析歌詞時,可能會遇到某行有時間但沒有歌詞內容,就作了這樣一個處理:if(line.length()==10) lineInfo.setContent("");

三、從輸入流中讀取,並調用步驟2中方法逐行解析

/**
     * 解析歌詞
     *
     * @param ins
     * @param charsetName
     */
    public static LyricInfo initLyric(InputStream ins, String charsetName) {

        if (ins == null) return null;

        try {
            LyricInfo lyricInfo = new LyricInfo();
            lyricInfo.setLines(new ArrayList<LineInfo>());

            InputStreamReader inputStreamReader = new InputStreamReader(ins, charsetName);
            BufferedReader reader = new BufferedReader(inputStreamReader);
            String line = null;
            // 逐行解析
            while ((line = reader.readLine()) != null) {
                analyzeLyricByLine(lyricInfo, line);
            }
            reader.close();
            ins.close();
            inputStreamReader.close();

            return lyricInfo;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
複製代碼

由於歌詞文件不論在assets下仍是在SD卡上,咱們必須都得獲取輸入流,設置編碼格式,而後調用analyzeLyricByLine逐行解析,將解析完的數據設置給新建的實體類並返回。

這裏咱們核心使用的是**BufferedReader 的 readLine()**方法。

3、解析驗證

這裏爲了方便,我將歌詞文件放在了assets下

try {
	InputStream is = getAssets().open("beijing.lrc");
	LyricInfo lyricInfo = LyricParser.initLyric(is, "utf-8");
	StringBuffer stringBuffer = new StringBuffer();
	if(lyricInfo != null && lyricInfo.getLines() != null) {
		int size = lyricInfo.getLines().size();
		for (int i = 0; i < size; i ++) {
			stringBuffer.append(lyricInfo.getLines().get(i).getContent() + "\n");
		}
		tv_lyric.setText(stringBuffer.toString());
	}else{
		tv_lyric.setText("解析失敗");
	}

} catch (IOException e) {
	e.printStackTrace();
	tv_lyric.setText("解析失敗");
}
複製代碼

這裏就很簡單了,再也不累贅,注意一下使用StringBuilder拼接每行的歌詞內容,每次拼接完成後加換行,才能出現咱們想要的結果。

再看下效果:

這裏寫圖片描述

下一篇,關於展現歌詞的自定義View的文章,我會抓緊時間發佈,敬請期待!

想要獲取更多精彩內容,您還能夠關注個人微信公衆號——Android機動車

相關文章
相關標籤/搜索