文章出處

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]彩虹一步步露出美麗臉龐 無限的光亮

這種形式的歌詞把歌詞內容相同但時間不同的部分合并,節省了篇幅。

所以,現在知道的歌詞其實有兩種寫法了,不過都還算規律,用正則可以搞定,只是對于第二種,處理時得將時間再次分割。

具體思路

  1. 首先將LRC文件讀取為文本
  2. String.prototype.split('\n');將整個文本以換行符為單位分隔成一行一行的文本,保存到一個數組中
  3. 然后將開頭部分不屬于歌詞的文本去掉,得到只有時間與歌詞的干凈文件
  4. 對于每一行,匹配出時間與文字,分別存入數組[time,text],然后將每行得到的這樣的數組存入一個大的數組[[time,text],[time,text]…]
  5. 利用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 tagontimeupdate事件。

REFERENCE

http://www.html5rocks.com/en/tutorials/track/basics/

http://www.w3schools.com/tags/ref_av_dom.asp

http://www.w3schools.com/tags/tag_track.asp


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()