HTML5草案里面其实有原生的字幕标签(<track> Tag)的,但使用的是vtt格式的文件,非常规的字幕(.sub, .srt)或歌词文件(.lrc)。
用法如下:
1 | < video width = "320" height = "240" controls> |
2 | < source src = "forrest_gump.mp4" type = "video/mp4" > |
3 | < source src = "forrest_gump.ogg" type = "video/ogg" > |
4 | < track src = "subtitles_en.vtt" kind = "subtitles" srclang = "en" label = "English" > |
5 | < track src = "subtitles_no.vtt" kind = "subtitles" srclang = "no" label = "Norwegian" > |
但遗憾的是,使用起来还有不便之处。一是浏览器支持情况不太理想,连强大的FireFox(目前28.0)都还没支持,这你敢信!?。二是格式不兼容现有字幕或歌词文件,至少得需要个转换工具吧。
所以在它流行起来之前,考虑另外的实现还是有必要的。
歌词文件的格式
实现之前,当然得了解一下歌词文件的格式了。常规歌词文件的格式基本是一句一行,每行由两部分组成,前面是中括号括起来的时间轴,后面紧跟歌词,像下面这样:
这样挺有规律的,用正则可以很方便地将时间与歌词提取分离。
但凡事得多个心眼啊。事后发生的事情证明这句话有多正确。我在整理歌词时还发现了另外一种形式,像下面这样:
05 | [00:05.40]作曲:陈嘉唯、Skot Suyama 陶山、庭竹 |
08 | [00:18.62]城堡里 公主也摆脱了黑暗的囚禁 |
09 | [00:22.82]她一点点地 无声悄悄地慢慢长大 |
12 | [01:51.48][00:30.32]树上 小鸟的轻响 在身边打转 |
13 | [01:55.35][00:34.09]公主已 忘记木制衣橱背后的惆怅 |
14 | [01:59.65][00:38.35]她跳舞唱歌天真无邪地寻找属于自己的光亮和快乐 |
16 | [02:07.41][00:46.06]树叶一层层拨开了伪装 |
17 | [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请求来获得歌词文件。
01 | function getLyric(url) { |
03 | var request = new XMLHttpRequest(); |
05 | request.open( 'GET' , url, true ); |
07 | request.responseType = 'text' ; |
09 | request.onload = function () { |
11 | var lyric = request.response; |
通过上面的代码就可以LRC文件读取成文本,然后就可以进行下一步处理了。
提取分离
因为时间我歌词的分隔是很有规律的,先通过\n将所有文字分隔成一行行存入数组,然后根据文章开始分析的思路一步一步提取分离。为此写一个解析歌词的函数。
01 | function parseLyric(text) { |
03 | var lines = text.split( '\n' ), |
05 | pattern = /\[\d{2}:\d{2}.\d{2}\]/g, |
09 | while (!pattern.test(lines[0])) { |
10 | lines = lines.slice(1); |
13 | lines[lines.length - 1].length === 0 && lines.pop(); |
14 | lines.forEach( function (v , i , a ) { |
16 | var time = v.match(pattern), |
18 | value = v.replace(pattern, '' ); |
20 | time.forEach( function (v1, i1, a1) { |
22 | var t = v1.slice(1, -1).split( ':' ); |
24 | result.push([parseInt(t[0], 10) * 60 + parseFloat(t[1]), value]); |
28 | result.sort( function (a, b) { |
这一步,我们便得到 了一个总的数组,它的元素是一些小的数组,这些小数组包含两个元素,一个是时间,并且这个时间已经由分:秒的形式转化为了秒,一个是时间对应的歌词[['秒数','歌词'], ['秒数','歌词']…]。
歌词同步
接下来就是先把全部歌词显示到页面,进行滚动式显示,或者也可以不全部显示,像电影字幕一样,唱一句显示一句。
下面看如何同步。当歌曲播放时,监听audio标签的ontimeupdate事件,即时更新显示歌词到页面即可。
02 | var audio = document.getElementsByTagName( 'audio' ), |
04 | lyricContainer = document.getElementById( 'lyricContainer' ); |
06 | audio.ontimeupdate = function (e) { |
08 | for ( var i = 0, l = lyric.length; i < l; i++) { |
09 | if ( this .currentTime > lyric[i][0]) { |
11 | lyricContainer.textContent = that.lyric[i][1]; |
我在selected项目中使用的是滚动显示的形式,但显示形式是可以变的,关键是同步的方法,可以多理解一下。
总结
上面的做法处理了多时间共处一行的情况,所以对于大多数歌词文件来说都是可行的,目前还没有发现另外形式的歌词文件。上面介绍的方法同样适用于video标签在播放视频时同步字幕,只是用于匹配的正则表达式需要更改,因为字幕文件的格式较歌词又不同了。同时字幕文件也分很多种后缀,但实现起来同样是利用media tag的ontimeupdate事件。
部分素材资源来源网站,本站提供免费下载,如有侵权请联系站长马上删除!