HTML5音乐播放器同步显示歌词

姜俊民
2023-12-01

演示地址:KKBlog在线音乐

歌词文件的格式

实现之前,当然得了解一下歌词文件的格式了。常规歌词文件的格式基本是一句一行,每行由两部分组成,前面是中括号括起来的时间轴,后面紧跟歌词,像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[ti:记兰生襄铃]
[ar:HITA]
[al:在线热搜(华语)系列30]
[offset:0]
[00:05.91]记兰生襄铃
[00:07.54]填词:三日月
[00:09.41]原曲:夏川里美
[00:11.48]演唱:HITA 肉肉
[00:13.35]后期:HITA
[00:27.16]青石路 青石桥 书生哼着江南调
[00:33.60]谁家女儿颜色娇 穿花扑蝶尚年少
[00:40.17]金铃响 金铃摇 黄衣少女拍手笑
[00:46.66]呆瓜呆瓜瞧一瞧 天边大鹰正飞高
[00:53.35]都说当时年纪小 无忧无虑乐逍遥
[00:59.73]芳梅林边花盛放 珍珠滩旁看江潮
[01:06.60]江都城外茶水香 再尝一口桂花糕
[01:12.85]清风吹入梦一遭 曾有仙人上九霄

这样挺有规律的,用正则可以很方便地将时间与歌词提取分离。

但凡事得多个心眼啊。事后发生的事情证明这句话有多正确。我在整理歌词时还发现了另外一种形式,像下面这样:

1
2
3
4
5
6
7
8
9
10
11
[00:02.08]偏爱
[00:04.94]作词:葛大为 作曲:陈伟
[00:06.35]演唱:张芸京
[00:08.25]
[00:14.37][01:36.77]把昨天都作废 
[00:18.21][01:40.35]现在你在我眼前
[00:21.94][01:44.15]我想爱 请给我机会
[00:28.38][01:50.35]如果我错了也承担 
[00:32.35][01:54.54]认定你就是答案
[00:36.96][01:59.22]我不怕谁嘲笑我极端
[00:40.60][02:02.80]

这种形式的歌词把歌词内容相同但时间不同的部分合并,节省了篇幅。

所以,现在知道的歌词其实有两种写法了,不过都还算规律,用正则可以搞定,只是对于第二种,处理时得将时间再次分割。

具体思路

  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请求来获得歌词文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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将所有文字分隔成一行行存入数组,然后根据文章开始分析的思路一步一步提取分离。为此写一个解析歌词的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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事件,即时更新显示歌词到页面即可。

1
2
3
4
5
6
7
8
9
10
//获取页面上的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];
         };
     };};

 

上面介绍的方法同样适用于video标签在播放视频时同步字幕,只是用于匹配的正则表达式需要更改,因为字幕文件的格式较歌词又不同了。同时字幕文件也分很多种后缀,但实现起来同样是利用media tag的ontimeupdate事件。

 

文章转载地址:HTML5音乐播放器同步显示歌词

转载于:https://www.cnblogs.com/jianfangkk/articles/4984145.html

 类似资料: