演示地址:KKBlog在線音樂
歌詞文件的格式
實現之前,當然得了解一下歌詞文件的格式了。常規歌詞文件的格式基本是一句一行,每行由兩部分組成,前面是中括號括起來的時間軸,后面緊跟歌詞,像下面這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
[ti:記蘭生襄鈴]
[ar:HITA]
[al:在線熱搜(華語)系列30]
[offset:0]
[00:05.91]記蘭生襄鈴
[00:07.54]填詞:三日月
[00:09.41]原曲:夏川里美
[00:11.48]演唱:HITA 肉肉
[00:13.35]后期:HITA
[00:27.16]青石路 青石橋 書生哼着江南調
[00:33.60]誰家女兒顏色嬌 穿花撲蝶尚年少
[00:40.17]金鈴響 金鈴搖 黃衣少女拍手笑
[00:46.66]呆瓜呆瓜瞧一瞧 天邊大鷹正飛高
[00:53.35]都說當時年紀小 無憂無慮樂逍遙
[00:59.73]芳梅林邊花盛放 珍珠灘旁看江潮
[01:06.60]江都城外茶水香 再嘗一口桂花糕
[01:12.85]清風吹入夢一遭 曾有仙人上九霄
|
這樣挺有規律的,用正則可以很方便地將時間與歌詞提取分離。
但凡事得多個心眼啊。事后發生的事情證明這句話有多正確。我在整理歌詞時還發現了另外一種形式,像下面這樣:
1
2
3
4
5
6
7
8
9
10
11
|
[00:02.08]偏愛
[00:04.94]作詞:葛大為 作曲:陳偉
[00:06.35]演唱:張芸京
[00:08.25]
[00:14.37][01:36.77]把昨天都作廢
[00:18.21][01:40.35]現在你在我眼前
[00:21.94][01:44.15]我想愛 請給我機會
[00:28.38][01:50.35]如果我錯了也承擔
[00:32.35][01:54.54]認定你就是答案
[00:36.96][01:59.22]我不怕誰嘲笑我極端
[00:40.60][02:02.80]
|
這種形式的歌詞把歌詞內容相同但時間不同的部分合並,節省了篇幅。
所以,現在知道的歌詞其實有兩種寫法了,不過都還算規律,用正則可以搞定,只是對於第二種,處理時得將時間再次分割。
具體思路
-
首先將LRC文件讀取為文本
-
用String.prototype.split('\n');將整個文本以換行符為單位分隔成一行一行的文本,保存到一個數組中
-
然后將開頭部分不屬於歌詞的文本去掉,得到只有時間與歌詞的干凈文件
-
對於每一行,匹配出時間與文字,分別存入數組[time,text],然后將每行得到的這樣的數組存入一個大的數組[[time,text],[time,text]…]
-
利用Audio標簽的ontimeupdate事件,不斷比較當然播放時間audio.currentTime與數組中每個元素中時間,如果當前時間大於某個歌詞中的時間,則顯示該歌詞
文件讀取
在具體處理歌詞前,需要解決一個問題就是如何把歌詞文件讀取到代碼中。對於文件讀取,JavaScript中可以用FileReader,但它需要手動選擇文件,也就是你得在頁面放一個file類型的input或者實現文件拖拽操作,顯示不可能讓用戶聽歌的時候自己去找歌詞然后上傳,多麻煩。但JavaScript是沒有辦法操作本地文件的能力的,那就只能通過XMLHttpRequest(Ajax)發起一個到服務器的請求來獲得文件了,這樣一來,我們的程序就必需得運程在服務器上面。所以當你從GitHub下載了本文的源碼后是無法直接運行的,請掛到本地服務器上觀看效果。
下面展示了如何發起一個Ajax請求來獲得歌詞文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function
getLyric(url) {
//建立一個XMLHttpRequest請求
var
request =
new
XMLHttpRequest();
//配置, url為歌詞地址,比如:'./content/songs/foo.lrc'
request.open(
'GET'
, url,
true
);
//因為我們需要的歌詞是純文本形式的,所以設置返回類型為文本
request.responseType =
'text'
;
//一旦請求成功,但得到了想要的歌詞了
request.onload =
function
() {
//這里獲得歌詞文件
var
lyric = request.response;
};
//向服務器發送請求
request.send();}
|
通過上面的代碼就可以LRC文件讀取成文本,然后就可以進行下一步處理了。
提取分離
因為時間我歌詞的分隔是很有規律的,先通過\n將所有文字分隔成一行行存入數組,然后根據文章開始分析的思路一步一步提取分離。為此寫一個解析歌詞的函數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
function
parseLyric(text) {
//將文本分隔成一行一行,存入數組
var
lines = text.split(
'\n'
),
//用於匹配時間的正則表達式,匹配的結果類似[xx:xx.xx]
pattern = /\[\d{2}:\d{2}.\d{2}\]/g,
//保存最終結果的數組
result = [];
//去掉不含時間的行
while
(!pattern.test(lines[0])) {
lines = lines.slice(1);
};
//上面用'\n'生成生成數組時,結果中最后一個為空元素,這里將去掉
lines[lines.length - 1].length === 0 && lines.pop();
lines.forEach(
function
(v
/*數組元素值*/
, i
/*元素索引*/
, a
/*數組本身*/
) {
//提取出時間[xx:xx.xx]
var
time = v.match(pattern),
//提取歌詞
value = v.replace(pattern,
''
);
//因為一行里面可能有多個時間,所以time有可能是[xx:xx.xx][xx:xx.xx][xx:xx.xx]的形式,需要進一步分隔
time.forEach(
function
(v1, i1, a1) {
//去掉時間里的中括號得到xx:xx.xx
var
t = v1.slice(1, -1).split(
':'
);
//將結果壓入最終數組
result.push([parseInt(t[0], 10) * 60 + parseFloat(t[1]), value]);
});
});
//最后將結果數組中的元素按時間大小排序,以便保存之后正常顯示歌詞
result.sort(
function
(a, b) {
return
a[0] - b[0];
});
return
result;}
|
這一步,我們便得到 了一個總的數組,它的元素是一些小的數組,這些小數組包含兩個元素,一個是時間,並且這個時間已經由分:秒的形式轉化為了秒,一個是時間對應的歌詞[['秒數','歌詞'], ['秒數','歌詞']…]。
歌詞同步
接下來就是先把全部歌詞顯示到頁面,進行滾動式顯示,或者也可以不全部顯示,像電影字幕一樣,唱一句顯示一句。
下面看如何同步。當歌曲播放時,監聽audio標簽的ontimeupdate事件,即時更新顯示歌詞到頁面即可。
1
2
3
4
5
6
7
8
9
10
|
//獲取頁面上的audio標簽var audio = document.getElementsByTagName('audio'),
//顯示歌詞的元素
lyricContainer = document.getElementById(
'lyricContainer'
);
//監聽ontimeupdate事件audio.ontimeupdate = function(e) {
//遍歷所有歌詞,看哪句歌詞的時間與當然時間吻合
for
(
var
i = 0, l = lyric.length; i < l; i++) {
if
(
this
.currentTime
/*當前播放的時間*/
> lyric[i][0]) {
//顯示到頁面
lyricContainer.textContent = that.lyric[i][1];
};
};};
|
上面介紹的方法同樣適用於video標簽在播放視頻時同步字幕,只是用於匹配的正則表達式需要更改,因為字幕文件的格式較歌詞又不同了。同時字幕文件也分很多種后綴,但實現起來同樣是利用media tag的ontimeupdate事件。
文章轉載地址:HTML5音樂播放器同步顯示歌詞