论HTML5 Audio 标签歌词同步的实现
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[])) {
lines = lines.slice();
};
//上面用'\n'生成生成数组时,结果中最后一个为空元素,这里将去掉
lines[lines.length - ].length === && 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(, -).split(':');
//将结果压入最终数组
result.push([parseInt(t[], ) * + parseFloat(t[]), value]);
});
});
//最后将结果数组中的元素按时间大小排序,以便保存之后正常显示歌词
result.sort(function(a, b) {
return a[] - b[];
});
return result;
}
这一步,我们便得到 了一个总的数组,它的元素是一些小的数组,这些小数组包含两个元素,一个是时间,并且这个时间已经由分:秒的形式转化为了秒,一个是时间对应的歌词[['秒数','歌词'], ['秒数','歌词']…]。
歌词同步
接下来就是先把全部歌词显示到页面,进行滚动式显示,或者也可以不全部显示,像电影字幕一样,唱一句显示一句。
下面看如何同步。当歌曲播放时,监听audio标签的ontimeupdate事件,即时更新显示歌词到页面即可。
//获取页面上的audio标签
var audio = document.getElementsByTagName('audio'),
//显示歌词的元素
lyricContainer = document.getElementById('lyricContainer');
//监听ontimeupdate事件
audio.ontimeupdate = function(e) {
//遍历所有歌词,看哪句歌词的时间与当然时间吻合
for (var i = , l = lyric.length; i < l; i++) {
if (this.currentTime /*当前播放的时间*/ > lyric[i][]) {
//显示到页面
lyricContainer.textContent = that.lyric[i][];
};
};
};
我在selected项目中使用的是滚动显示的形式,但显示形式是可以变的,关键是同步的方法,可以多理解一下。
总结
上面的做法处理了多时间共处一行的情况,所以对于大多数歌词文件来说都是可行的,目前还没有发现另外形式的歌词文件。上面介绍的方法同样适用于video标签在播放视频时同步字幕,只是用于匹配的正则表达式需要更改,因为字幕文件的格式较歌词又不同了。同时字幕文件也分很多种后缀,但实现起来同样是利用media tag的ontimeupdate事件。
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
论HTML5 Audio 标签歌词同步的实现的更多相关文章
- HTML5 <Audio/>标签Api整理(二)
1.实例2: 相对较完整 Html代码: <style> #volumeSlider .slider-selection { background:#bababa; } </styl ...
- HTML5 <Audio>标签API整理(三)
一.浏览器支持 Internet Explorer 9+, Firefox, Opera, Chrome, 和 Safari 都支持 <audio> 元素. 注意: Internet Ex ...
- HTML5 Audio标签方法和函数API介绍
问说网 > 文章教程 > 网页制作 > HTML5 Audio标签方法和函数API介绍 Audio APIHTML5HTML5 Audio预加载 HTML5 Audio标签方法和函数 ...
- CEF3 HTML5 audio标签为什么不能播放mp3格式的音频文件
CEF3 HTML5 audio标签 为什么不能播放mp3格式的音频文件 原因略. 解决方法: 找一个最新版的chrome ,我用的是24版本.路径 C:\Documents and Sett ...
- html5 audio标签微信部分苹果手机不能自动播放音乐终极解决方案
html5 audio标签微信部分苹果手机不能自动播放音乐终极解决方案 大家都知道需要在点击时候后 播放 ps:如果点击ajax 回来播放也不行,必须点击立即播放 要背景自动播放只能采取下面方案< ...
- html5 audio标签切换播放音乐的方法
html5 audio标签切换播放音乐的方法<pre><audio id="music1" preload loop="loop">&l ...
- ios加载html5 audio标签用js无法自动播放
html5 audio标签在ios 微信浏览器中是无法自动播放的,最近在做一个小的项目遇到这个问题,安卓和pc都是正常的,唯独ios不行,查阅了很多资料,找到了以下方法,也许不是最好用的方法,如果有更 ...
- HTML5 audio标签自制音乐播放器
相关技能 HTML5+CSS3(实现页面布局和动态效果) Iconfont(使用矢量图标库添加播放器相关图标) LESS (动态CSS编写) jQuery(快速编写js脚本) gulp+webpack ...
- HTML5实践之歌词同步播放器
歌曲播放我们会发现他的兼容性不是很好,譬如IE上能播放的flash播放器,再firfox或者chrome上就不是很好的应用了,因为有插件的阻碍!HTML5的出现让这一切成为了可能,但是播放器虽然播放了 ...
随机推荐
- F#之旅8 - 图片处理应用之动画二维码
首先,先介绍下什么是动画二维码.前些天在网上闲逛,突然看到一个开源项目,发现一种二维码的新玩法.https://github.com/sylnsfar/qrcode/blob/master/READM ...
- UWP学习记录3-设计和UI之样式
UWP学习记录3-设计和UI之样式 1.颜色 在系统的“设置”>“个性化”>“颜色”里,提供了主题色选择.选定主题色后,会根据颜色亮度的 HSB 值创建浅色和深色的主题色. 应用可使用阴影 ...
- notes
http://www.cnblogs.com/titicia/p/4388318.html http://blog.csdn.net/kanosword/article/details/5258679 ...
- Ubuntu菜鸟入门(八)—— QQ安装
一.下载 http://www.ubuntukylin.com/application/show.php?lang=cn&id=279 二.安装流程 1.解压,到解压文件夹中运行 sudo d ...
- Python 学习第十八天 js 正则及其它前端知识
一,js 正则表达式 test 判断制度串是否符合规定的正则 (1)定义正则表达式匹配规则 js 中定义正则表达式为rep=/\d+/,两个//之间为正则模式 (2)rep.test( ...
- python strip()函数 介绍
python strip()函数 介绍,需要的朋友可以参考一下 函数原型 声明:s为字符串,rm为要删除的字符序列 s.strip(rm) 删除s字符串中开头.结尾处,位于 rm删除 ...
- 解决java.beans.Introspector导致的内存泄漏
解决方案: 在WEB.XML 中配置监听器: <listener> <listener-class> org.springframework.web.util.Introspe ...
- PDA手持机 移动开单进销存系统 现场出打印凭据和扫码 新的亮点
传统车销模式弊端:1.手写开单,效率低,动作慢2.现场手写开单明细不能打印,产品明细不规范3.电脑办公人员及车销人员对车上的库存情况掌握不清楚,销售人员对每种产品销售价格不清楚4.老板对员工工作的管控 ...
- 搜索技巧<转>
平时工作,搜索引擎是少不了的,作为程序员,当然首推 Google.这里简单介绍下几个 Google 搜索的小技巧,方便别人也方便自己查阅. ps:以下所有操作,均可以在 「谷歌搜索首页 -> 设 ...
- java方法重载(overload)、重写(override);this、super关键简介
一.方法重载: 条件:必须在一个类中,方法名称相同,参数列表不同(包括:数据类型.顺序.个数),典型案例构 造方重载. 注意:与返回值无关 二.方法重写: 条件: (1)继承某个类或实现某接口 (2 ...