一、格式化歌词
接口返回的歌曲歌词是字符串形式,需要把格式格式化后提取相关信息用于实现滚动歌词。
歌词示例:
[00:00.000] 作词 : 于正\n[00:01.000] 作曲 : 谭旋\n[00:27.129]天生我才必有用\n
格式化后的数据示例:
[
{time: 0.000, text: '作词 : 于正'},
{time: 1.000, text: '作曲 : 谭旋'},
{time: 27.129, text: '天生我才必有用'},
]
格式化使用的函数:
/**
* 格式化时间字符串为时间,时间单位为秒
* @param timeString 时间字符串,格式为: mm:ss:ss, 如: 00:01:404
* @returns
*/
export const formatTimeToNumber = (timeString: string) => {
let time = 0;
if (timeString) {
const splitArr = timeString.split(":").map((item) => Number(item));
return splitArr[0] * 60 + splitArr[1];
}
return time;
};
/**
* 格式化歌词字符串为"时间-歌词"格式的数组
* @param lyric 歌词字符串
* @returns
*/
export const formatLyric = (lyric: string) => {
const lyricParts = lyric.split("\n").filter((item) => item);
return lyricParts.map((item) => {
const splitItems = item.split("]");
const lyricItem: ILyric = {
time: formatTimeToNumber(splitItems[0].slice(1)),
text: splitItems[1],
};
return lyricItem;
});
};
二、歌词滚动
实现歌词滚动需要知道当前歌曲播放的实时时间,然后根据实时时间和歌词数组里面歌词的 time
字段进行比较,获取到当前歌曲播放到了那句歌词,然后根据歌词去计算滚动的距离。
获取歌曲当前播放的时间,需要在播放器组件里处理,存储到状态管理里面供全局使用,我使用的是vuex。代码如下:
// 获取当前播放时间
ele.ontimeupdate = () => {
_.debounce(() => {
store.commit("player/setCurrentTime", ele.currentTime.toFixed(3));
}, 1000)();
};
歌曲播放的时候,状态currentTime
会不停地更新。但是即便如此currentTime
也并不一定会和歌词数组里面的time
字段相等,因为此处currentTime
是一个6位小数的浮点数,事件ontimeupdate
的触发也不是一定的,所以currentTime
和歌词中的歌词时间不一定完全匹配。
所以在比较的时候,只需要比较到秒级,不需要精确到毫秒级。另外,currentTime
和歌词时间的比较有2种情况:
currentTime
和某句歌词时间相等,歌词的索引就是当前播放的歌词;currentTime
时间小于某句歌词时间,上一句歌词就是当前播放的歌词;
通过遍历获取到当前播放的歌曲的歌词的索引后,就可以获取到当前播放歌曲到顶部的距离。具体逻辑是:
- 通过
document.querySelectorAll(".lyric-item")
获取所有歌词的元素,根据每个元素的clientHeight
获取元素的高度; - 根据当前播放歌词的索引,获取当前歌词元素到顶部的距离,并设置滚动到顶部的距离;
- 同时根据当前歌词的索引,就可以设置歌词的高亮样式;
- 同时,根据元素的高度还可以设置当前歌词居中;
三、项目地址和代码
歌词滚动的功能用文字描述不太方便,想要了解更多建议直接查看GitHub上的源码。
项目在线访问地址:music-player.immortalboy.cn/
github项目地址:github.com/programmerm…
滚动歌词组件github地址:github.com/programmerm…
注意:
- 项目的前端页面部署到了netlify,国内访问可能会卡顿,请谅解;
- 滚动歌词的功能在歌曲详情里,请点击在线访问地址的页面底部音乐播放器的歌曲封面或者歌曲名,进入到歌曲详情们可以观看滚动歌曲效果(歌曲播放后才会滚动);