如何实现歌词同步显示

前言

最近在做一个仿网易云音乐的web app,其中需要全屏显示同步歌词,然后查了些资料;歌词同步显示最主要的部分就是歌词文件的解析以及随时间轴同步滚动显示。

解析歌词文件

歌词文件

目前最流行的歌词文件就是.lrc形式的,每一行对应一句歌词信息,文本格式有两种:

1
[mm:ss.ms] lyric content

1
[mm:ss.ms][mm:ss.ms] lyric content

其中第一种格式代表该句歌词以及其对应的时间轴起点,而第二种格式则是该句歌词对应多个时间轴的起点;

解析方法

根据.lrc文件格式的特性,可以按照如下步骤进行解析:

  1. 按行拆分文本,逐行解析;
1
let arr = lyric.split('\n') // lyric为.lrc歌词文本
  1. 使用正则表达式对每行歌词进行分析匹配;若只有一个时间轴起点则添加一个歌词信息,有多个时间轴起点则添加相应数量的歌词信息;
1
/\]\[/.test(line) // 判断某行歌词是否有多个时间轴起点
  1. 歌词信息结构如下:
1
2
3
4
lyricInfo = {
time: xx.yy, // (某行)歌词的时间轴起点,单位为秒
content: 'str' // (某行)歌词内容
}

歌词同步显示

要想使歌词随当前播放的进度同步显示,首先要知道当前歌曲播放的进度,以及在播放进度更新时找到对应时间段的歌词。

timeupdate事件

可以利用WebAudio API中的timeupdate事件来监听audio元素,该事件在audio元素的currentTime属性改变时触发,而currentTime属性即当前播放的进度,单位为秒;因此可以在timeupdate事件中查找当前进度对应的歌词信息进行同步显示。

查找对应歌词

  1. 将歌词信息数组按照时间顺序(升序)进行排列;
1
lyricArr.sort((a, b) => a.time - b.time)
  1. 根据当前播放时间(currentTime属性值),利用二分法(有序查找)在歌词信息数组查找不大于当前播放时间且差值最小的时间轴起点,该时间轴对应的歌词即当前播放的歌词。

垂直居中显示

一般音乐app的全屏歌词都是可以上下滚动,且当前播放歌词高亮垂直居中显示,歌词切换时有滚动过渡效果(也许是跳到了下一句,也许是歌曲进度人为改变时);

高亮只需要给当前播放歌词那一行加个类就可以了,主要是高亮的歌词需要垂直居中(在上下可以滚动的状态下)显示;我的做法是将每行歌词放入一个滚动容器中,然后在timeupdate事件中获取高亮歌词那一行元素在容器中的offsetTop设置容器的scrollTop,使得当前行正好滚到垂直中间的位置。

mark

参考文档

  1. 媒体相关事件 | MDN
  2. 论HTML5 Audio 标签歌词同步的实现