最近有朋友問我做一個音樂播放器的難點在哪裏,如果是做一個來面試玩的話,都不怎麼難,開源項目中成熟的音樂播放器網上有很多了,但如果是給公司自己做的話,公司一般都不會使用第三方的sdk,那麼就要進行解碼器的開發了,最難的應該是這個吧。
下面進入今天的主題,歌詞解析:
*本篇文章已授權微信公衆號 guolin_blog (郭霖)獨家發佈
已經有兩個月時間沒有發表新文章了,從開始發表文章以來,常常會有線上的朋友通過QQ和微信與我進行一些技術上溝通和交流,我也收穫良多,幾乎每次到最後,我都會厚着臉皮說:”多多關注哦,近期會有新文章要發表呢!”。奈何,每一次當我熱情高漲準備在近期發表文章的時候,都因各種各樣的事情耽擱了,於是乎,我一次又一次爽約了。的確,最近工作確實比較忙,幾乎每一天都需要加班,難得某個週末的下午有點閒暇時間,我都泡在了球場上,熱愛籃球是其一,最重要的是做我們這行的,由於工作性質,每週一定的運動是非常必要的,身體是革命的本錢不是嘛! 不過,我可並沒有因爲忙碌就會在閒暇時候放縱自己,我依然堅持着自己的夢想,我一直在思考分享一些比較受用的功能效果,我也爭取把每一個Demo做到最好!
這次我要向大家分享的是一個歌詞控件,其實,也是我畢業設計中的一部分。起初我是用ScrollView嵌套TextView,再結合我的上一篇文章SpannableString來實現的,Demo其實我早早地就將放在github上,不知道有沒有朋友有留心看到,但是總感覺用着不是很流暢,而且不容易加入一些自定義內容,所以一直不好下筆,也不好向大家分享Demo。不過,有興趣的朋友可以看一下(下載地址),個人感覺還是挺有創意的,嘻嘻!(害羞臉~)。
效果圖是舊版本哦,記住,是舊版本! 通過自定義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,歡迎評論,歡迎指正