初探<audio>标签实现同步显示歌词

今天群里的小胖说用<audio>标签写了个听评书的网页给父母用,技术实在是低级真心非常有孝心的说!然后我也顺带去了解了一下audio标签现在的支持情况,一不小心就脑补了一些新知识。看到了audio标签有duration属性和currentTime属性,分别用来读取歌曲总时间和当前播放时间,正好弥补了我之前想写播放器同步显示歌词一直卡在的如何获取播放时间的问题上。所以就开始操刀了。

歌曲和歌词的准备

我暂时是写到了之前的虾米搜索页面里面,实现搜索获得歌曲用audio标签播放并显示歌词。所以歌曲和歌词文件自然就来自盗版帝国虾米了。由于虾米的歌曲文件都是MP3格式的,所以悲剧如FireFox等虽然支持了HTML5但是不支持MP3格式的文件这让我也是感到很捉急的,所以暂时是推荐大家使用Chrome浏览器制作并访问的。关于目前各浏览器的支持情况可以看这里:居然连IE都支持了!这里利用了一下虾米的API:http://www.xiami.com/song/playlist/id/歌曲ID 获取歌曲的相关信息,其中就有歌曲地址和歌词。获取到的XML文件中,歌曲地址是加密放置的,至于解析原理,大家可以参见虾米音乐文件绝对地址解析。详细的解释一下就是:

获取到的字符串第一个字母是矩阵的行数,除去第一个字母的字符总数除以行数得到的整数是每行放置多少个字符,余数则是前余数行要在末尾多放置一个字符。这样就能组成引文中的矩阵了。按照竖行读取,^替换为0的处理后就可以获得绝对地址了。

懒惰如我为了方便自己,我把解析的过程写成了函数。最开始我是将字符串打散成为二维数组,然后嵌套循环连接字符串。后来觉得打散成一位数组应该会更方便一点,所以为了算获取数组索引顺序的规律,死了好多个脑细胞啊,果然数学已经忘到姥姥家了。

歌词由于后期要用js处理,js又不能跨域获取什么的。所以只能劳烦PHP获取到歌词地址后远程抓取过来了。以上解析得到绝对地址和歌词都是在服务器端完成的。我写成了API的形式,http://imnerd.org/lab/search/api.php?id=歌曲ID可以查看获取到的内容格式。

audio歌曲播放与暂停

果然我还是对解码什么的比较感兴趣,一不小心就偏题了呢。好啦好啦,以上都不应该是本文的重点啦,下面我们开始好好的蹂躏学习一下audio标签啦。audio标签的简单写法为

<audio src="歌曲地址"></audio>

控制歌曲的播放和暂停也是非常简单的,只要利用play()和pause()事件就好了,代码如下:

m = document.getElementsByTagName('audio')[0];
m.play(); //歌曲的播放
m.pause(); //歌曲的暂停

想要了解更多audio标签的属性和事件的话可以点击这里:HTML 5 视频/音频参考手册

同步显示歌词的实现

我在开头已经讲了,audio的currentTime属性可以获取当前播放时间。又如果你刚才有去点击上面的参考手册的链接的话,你就会发现audio还有一个神奇的timeupdate事件,目的是当目前的播放位置已更改时做出动作。也就是说我们可以利用timeupdate事件在currentTime变化的时候做出判断,然后放置相应的歌词。

基本方法大概就是这个样子,播放器的当前播放时间我们已经获取到了,剩下的就是获取歌词的播放时间的问题了。由于标准的歌词每行应该是“[你好我是播放时间,我的格式是分:秒]你好我是该播放时间下的歌词”这种格式的,想必大家早就了解了吧。利用正则获取split分割等一系列方法将歌词分割成一个二维数组,一纬为每行歌词,二维为每行歌词的时间和每行歌词的内容。其中歌词的时间由于是分钟:秒的形式,需要转化为单位为秒的数字。下面给出歌词转化为二维数组的方法。方法可能坑爹不是最好的,但是基本凑合,这一部分大家可以随意发挥:

//写成函数的形式大家可能会更好理解一点
function parseLyric(text) {
lyric = text.split('\r\n'); //先按行分割
var _l = lyric.length; //获取歌词行数
lrc = new Array(); //新建一个数组存放最后结果
for(i=0;i<_l;i++) {
    var d = lyric[i].match(/\[\d{2}:\d{2}((\.|\:)\d{2})\]/g);  //正则匹配播放时间
    var t = lyric[i].split(d); //以时间为分割点分割每行歌词,数组最后一个为歌词正文
    if(d != null) { //过滤掉空行等非歌词正文部分
        //换算时间,保留两位小数
        var dt = String(d).split(':'); //不知道为什么一定要转换时间为字符串之后才能split,难道之前正则获取的时间已经不是字符串了么? 
        var _t = Math.round(parseInt(dt[0].split('[')[1])*60+parseFloat(dt[1].split(']')[0])*100)/100; //这一步我自己都觉得甚是坑爹啊!
        lrc.push([_t, t[1]]);
    }
return lrc;
}

这样我们只要匹配歌词每行的时间和crrentTime就行了,如果当前播放时间超过了这一句歌词的时间,则显示这一句歌词,否则不显示。这是我们下面代码的关键判断。另外,这里我使用了一个自我感觉还不错的小技巧:显示完歌词之后立即将这行歌词从歌词数组中剔除出去,这样我们就永远只要将currentTime和lrc[1][

m.addEventListener('timeupdate', function() {
    if(audio.currentTime > lrc[1][0]) {
        document.getElementById('lrc').innerHTML = lrc[1][1];
        lrc.shift();
    }
},false);
//addEventListener等同于m.timeupdate(function() {...});

歌曲播放并显示歌词的思路大概就这个样子了,具体的代码可以去看我的DEMO。DEMO只是给出了大概的功能,UI方面都还木有美化,大家习惯就好,这个是在是力不从心啊!本人一向也不是很在意UI的。最后的最后,感谢一下Jclyn帮我做了测试并推荐了本文开头的歌曲。哦,又突然想起来了,DEMO的范例是用JQuery操作的,虽然跟Javascript有些不同,但是对于思路什么的是没有问题的,有不懂的可以留言提问哦,我会看心情耐心解答的。

终极同步大法

随着微博的热门,已经有很多的微博程序纷纷出现了,而我们该如何挑选这些微博程序呢?首先我们要考虑的是选择单用户还是多用户的。但是如果像我这种注重功能的人,可能第一个想到的是它的同步功能如何?今天就介绍一种方法,为你的PageCookery的同步功能做拓展。

这里主要是利用了月光博客制作的GAE同步程序制作的一个针对PageCookery的简易教程。总所周知,Twitter现如今是微博界的老大,虽然被G.F.W了,但是仍然不能阻挡它的光芒!我们可以利用Twitter这个跳板,将其他地方的消息同步到Twitter中,同时也可以利用Twitter的多方式发布消息将自己的消息发布到Twitter中,然后将Twitter的消息同步到别的地方去!而月光的这个GAE程序就能够实现将Twitter的信息分发到各大微博网站去,包括新浪微博/嘀咕/网易微博/9911/51follow等等微博门户。而PageCookery微博程序又提供了对嘀咕的双向同步,而且也提供了RSS导入这么一个极佳的功能。

有人可能会说了,为什么我不直接用Twitter的RSS导入到PageCookery来呢?这是因为直接导入RSS的话,会有很多的RSS垃圾产生,比如你@别人的消息,以及RT别人的消息也都会同步过来,这个还算是小事,重要的是Twitter的RSS格式包括了用户名,所以每次导入Twitter的消息时都会带上"username:"这样的前缀,让人看起来甚是不爽,所以我们要对RSS进行过滤!而月光博客的GAE同步程序也正是做到了这一点。在此,要感谢月光博客为我们带来了这么好的一个工具。而且,利用GAE的高效工作性,我们基本能够实时的同步我们的消息到PageCookery中(我说的是几乎)……

好了,优点讲完了就改说怎么捣鼓了,首先呢,你要有申请一个GAE,如果有,则可跳过此不,如果没有,请参照如何申请GAE

申请好GAE并成功建立一个项目之后,我们要做的就是将月光博客的GAE程序给下载下来,下载方法,使用TortoiseSVN检出这个SVN地址,然后,编辑 app.yaml,修改为自己的appspot应用名,接着,修改twitter.py文件的最后一行,将自己的Twitter用户名填入,修改从 ret = send_sina_msgs("username","password",text) 语句开始的 username 和 password ,将相应微博客的用户名和密码分别填入,不需要同步的服务请使用#号注释掉,cron.yaml文件里是计划任务设置。至于如何使用TortoiseSVN下载,请参考:教你如何用SVN下载源码

千万不要忘记,记得修改最后一样你要同步的Twitter帐号!编辑保存后,我们就可以把程序上传到GAE主机上去了,至于如何上传么,请参考:如何上传文件到GAE image

好了,前半部分工作就做好了,下面我们就要做另外一部分工作了,现在假设你将Twitter上的信息同步到了嘀咕,由于PageCookery的嘀咕同步有个限制,就是嘀咕没有认证的API(也就是嘀咕页面消息显示是来自API)的消息PageCookery是不会同步过来的,所以我们只能通过RSS导入的法子,获取嘀咕微博的RSS地址,然后导入过来就可以了。(不能使用RSS导入功能的童鞋表示灰常抱歉了。)有人说了,如果我填写的是新浪微博的帐号怎么办?恩,我们知道新浪微博还没有开放RSS功能,不过这个也不要紧,http://imnerd.org/sinarss.php+"你的ID号"就是你的RSS地址了,这里要再次感谢月光博客提供的程序,有需要的也可以去他博客下载哦~~

其实个人认为吧,我们应该能从月光的GAE程序中直接获取到RSS地址的,那么就可以直接导入进PageCookery了,而不用使用其他微博做跳板了,只是python实在不是很了解,就没心思看他的代码了,希望有心人能够发现啊,呵呵~~最后就讲讲这个同步的时间问题,从Twitter同步到嘀咕等微博网站的时间是可以设置的,最短可以是1分钟(修改地方在cron.yaml文件最后一行的every * minutes,将*换成同步间隔时间即可),基本上可以做到实时同步,然后是嘀咕等RSS同步到PageCookery,经过我的初步观察,最迟15min~20min应该会同步过来的,如果没有同步过来的话我就告诉你一句:反正迟早的事,何必这么焦急呢,呵呵!

最后要提醒大家一句,请注意不要开启对某个微博的双向同步,一面发生消息同步死循环的状况……到时候估计删消息会删的你手软的!哈哈!