HTML5草案里面其實有原生的字幕標簽(<track> Tag)的,但使用的是vtt格式的文件,非常規的字幕(.sub, .srt)或歌詞文件(.lrc)。
用法如下(代碼來自W3School):
<video width="320" height="240" controls>
<source src="forrest_gump.mp4" type="video/mp4">
<source src="forrest_gump.ogg" type="video/ogg">
<track src="subtitles_en.vtt" kind="subtitles" srclang="en" label="English">
<track src="subtitles_no.vtt" kind="subtitles" srclang="no" label="Norwegian">
</video>
但遺憾的是,使用起來還有不便之處。一是瀏覽器支持情況不太理想,連強大的FireFox(目前28.0)都還沒支持,這你敢信!?。二是格式不兼容現有字幕或歌詞文件,至少得需要個轉換工具吧。
所以在它流行起來之前,考慮另外的實現還是有必要的。
效果預覽
效果預覽頁面:http://wayou.github.io/selected/
如果你網速流暢的話,盡情欣賞我精選的這些歌曲吧(不時更新),只是別忘了star,也可以fork后添加自己喜歡的歌曲。
項目GitHub地址:https://github.com/wayou/selected
具體實現可以前往項目的GitHub頁面下載代碼進行查看,下面介紹思路和簡單的實現。
歌詞文件的格式
實現之前,當然得了解一下歌詞文件的格式了。常規歌詞文件的格式基本是一句一行,每行由兩部分組成,前面是中括號括起來的時間軸,后面緊跟歌詞,像下面這樣:
[ar:文筱芮]
[by:airplay]
[00:00.00]那個
[00:03.00]作詞:文筱芮 作曲:文筱芮
[00:06.00]編曲:于韻非
[00:09.00]制作人:胡海泉 秦天
[00:12.00]演唱:文筱芮
這樣挺有規律的,用正則可以很方便地將時間與歌詞提取分離。
但凡事得多個心眼啊。事后發生的事情證明這句話有多正確。我在整理歌詞時還發現了另外一種形式,像下面這樣:
[ar:庭竹]
[al:愛的九宮格]
[by:airplay]
[00:00.17]庭竹 - 公主的天堂
[00:05.40]作曲:陳嘉唯、Skot Suyama 陶山、庭竹
[00:07.33]作詞:庭竹
[00:15.59]風鈴的音譜 在耳邊打轉
[00:18.62]城堡里 公主也擺脫了黑暗的囚禁
[00:22.82]她一點點地 無聲悄悄地慢慢長大
[00:26.36]期待著 深鎖木門后的世界
[01:38.72][00:29.76]
[01:51.48][00:30.32]樹上 小鳥的輕響 在身邊打轉
[01:55.35][00:34.09]公主已 忘記木制衣櫥背后的惆悵
[01:59.65][00:38.35]她跳舞唱歌天真無邪地尋找屬于自己的光亮和快樂
[02:06.98][00:45.76]
[02:07.41][00:46.06]樹葉一層層撥開了偽裝
[02:11.29][00:50.25]彩虹一步步露出美麗臉龐 無限的光亮
這種形式的歌詞把歌詞內容相同但時間不同的部分合并,節省了篇幅。
所以,現在知道的歌詞其實有兩種寫法了,不過都還算規律,用正則可以搞定,只是對于第二種,處理時得將時間再次分割。
具體思路
- 首先將LRC文件讀取為文本
- 用String.prototype.split('\n');將整個文本以換行符為單位分隔成一行一行的文本,保存到一個數組中
- 然后將開頭部分不屬于歌詞的文本去掉,得到只有時間與歌詞的干凈文件
- 對于每一行,匹配出時間與文字,分別存入數組[time,text],然后將每行得到的這樣的數組存入一個大的數組[[time,text],[time,text]…]
- 利用Audio標簽的ontimeupdate事件,不斷比較當然播放時間audio.currentTime與數組中每個元素中時間,如果當前時間大于某個歌詞中的時間,則顯示該歌詞
文件讀取
在具體處理歌詞前,需要解決一個問題就是如何把歌詞文件讀取到代碼中。對于文件讀取,JavaScript中可以用FileReader,但它需要手動選擇文件,也就是你得在頁面放一個file類型的input或者實現文件拖拽操作,顯示不可能讓用戶聽歌的時候自己去找歌詞然后上傳,多麻煩。但JavaScript是沒有辦法操作本地文件的能力的,那就只能通過XMLHttpRequest(Ajax)發起一個到服務器的請求來獲得文件了,這樣一來,我們的程序就必需得運程在服務器上面。所以當你從GitHub下載了本文的源碼后是無法直接運行的,請掛到本地服務器上觀看效果。
下面展示了如何發起一個Ajax請求來獲得歌詞文件。
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將所有文字分隔成一行行存入數組,然后根據文章開始分析的思路一步一步提取分離。為此寫一個解析歌詞的函數。
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事件,即時更新顯示歌詞到頁面即可。
//獲取頁面上的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];
};
};
};
我在selected項目中使用的是滾動顯示的形式,但顯示形式是可以變的,關鍵是同步的方法,可以多理解一下。
總結
上面的做法處理了多時間共處一行的情況,所以對于大多數歌詞文件來說都是可行的,目前還沒有發現另外形式的歌詞文件。上面介紹的方法同樣適用于video標簽在播放視頻時同步字幕,只是用于匹配的正則表達式需要更改,因為字幕文件的格式較歌詞又不同了。同時字幕文件也分很多種后綴,但實現起來同樣是利用media tag的ontimeupdate事件。
REFERENCE
http://www.html5rocks.com/en/tutorials/track/basics/
文章列表