如果想要了解如何编写的请继续往下看,如果只需要代码,请点击这里Github
lrc歌词文件介绍
来先看一下以下歌词 Heart To Heart.lrc
[ti:Heart To Heart]
[ar:James Blunt]
[al:Heart To Heart]
[by:]
[offset:0]
[00:00.00]Heart To Heart (诚恳的) - James Blunt (詹姆仕·布朗特)
[00:13.72]There are times when
[00:14.57]I don't know where I stand
[00:16.73][00:22.40](oh sometimes)
[00:18.49]You make me feel like
[00:19.68]I'm a boy and not a man
其中大致包含两类标签 标识标签 与 时间标签
标识标签:主要标识歌曲的信息(作者,作词者…) 其格式为:[标识名:值]
[ar:歌手名]
[ti:歌曲名]
[al:专辑名]
[by:编辑者(指lrc歌词的制作人)]
[offset:时间补偿值]
歌词整体的偏移量
时间标签:表示啥时候出现歌词 标准格式:[分钟:秒.毫秒] 歌词
除此之外还有其他格式:[分钟:秒] 歌词
,[分钟:秒:毫秒] 歌词
,[分钟:秒:毫秒][分钟:秒:毫秒] 歌词
.
大致了解了lrc的格式就可以开始编写代码了
首先定个大致流程
- 先解析标识标签
- 再解析时间标签
-
封装两个解析部件(目标已经完成)
- 优化添加axios异步获取歌词
1. 先解析标识标签
标识标签格式已经得到那么可以用正则匹配就行了/\[[A-Za-z]+\:[^\[\]]*\]/g
,
匹配完了之后当做对象返回 格式为{标签名:标签值}
function getFlagTags (text) {
// 解析标记标签 text lrc文本
// return {标签名:标签值} 例如: {by:xiaohuohu}
let res = {};
let find = text.match(/\[[A-Za-z]+\:[^\[\]]*\]/g);// 匹标志标签
for (let findKey in find) {
let textArrayItem = find[findKey];
let tagName = textArrayItem.substring(textArrayItem.indexOf('[') + 1, textArrayItem.indexOf(':'));
let tagText = textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.indexOf(']'));
res[tagName] = tagText;
}
return res;
}
2. 再解析时间标签
这里工作就大了起来,我们再具体分析一下解析要求:
- 有多种标准:
[分钟:秒.毫秒] 歌词
,[分钟:秒] 歌词
,[分钟:秒:毫秒] 歌词
.需要兼容
-
[offset:时间补偿值]
会影响时间
-
[01:02.03]
与 [1:2.3]
无论前面是否添加0
都是符合要求的
-
[分钟:秒:毫秒][分钟:秒:毫秒]歌词
可以多个时间对应一个歌词
然后我们就可以开始编写代码了
- 根据lrc一行一个标签的特点我们先新建lrc进行切分
var text='歌词源文件内容';
var textArray=text.split('\n');// 切割后的数组
- 我们依据
要求1
编写一个筛选结构过滤掉标识标签以及一些不合规矩的文字,一样的正则走起/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/
for (let textArrayKey in textArray) {
let textArrayItem = textArray[textArrayKey];// 子项
if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
// 符合要求的
}
}
- 由于
要求4
多个之间的问题,那我们不如先解决歌词
其思路是将时间标签替换为 ‘空’ 剩下的就是歌词啦.
let findWord = textArrayItem.replace(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+/g, '').trim();// 歌去除时间保留歌词,并去除两端多余空格
if (findWord) {// 去除歌词为空的项
// ......
}
- 匹配完歌词剩下的就是时间
先匹配多个时间块:/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g
歌词无视
再对每一个时间快转为为毫秒 [m:s.ms] m * 60000 + s * 1000 + ms
,
为了拆解时方便计算我们将每一位的进率单独提出来timeRule = [60000, 1000, 1]
let timeRule = [60000, 1000, 1];// // 对应位数的毫秒数
let res={};// 结果对象 {时间:歌词}
let findTime = textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g);// 匹配多个时间 例如 [1:2.3][4:2.4]hello world
for (let findTimeKey in findTime) {
let findTimeItem = findTime[findTimeKey].match(/\d+/g);// 切割每一个时间的m s ms部分
let nowTime = 0;// 初始为0;
for (let x = 0; x < findTimeItem.length; x++) {
nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
}
res[nowTime] = findWord;// 添加结果 {时间:歌词}
}
- 解决
要求2[offset:0]
前面步骤2
不是在过滤符合要求的歌词?那我们就在他下面再过滤[offset:0]
/^\[offset\:\-?[1-9]\d+\]$/
let addTime = 0;// 偏移量
let textArrayItem = textArray[textArrayKey];
if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
// 歌词
} else if (textArrayItem.match(/^\[offset\:\-?[1-9]\d+\]$/)) {// 匹配偏移时间 解决[offset:-232]操作
// [offset:-232]正值表示整体提前,负值相反
addTime -= parseInt(textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.length));
}
- 将
addTime
应用上去
let nowTime = addTime;// 初始由0变为addTime;
for (let x = 0; x < findTimeItem.length; x++) {
nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
}
- 最后整理一下
function getTimeTags (text) {
// 解析歌词标签 text lrc全部文本
// return {time:word} 例如{12300:'hello world'}
// [m:s.ms] or [m:s:ms] or [m:s], [1:2.3] or [01:02.03] or [01:02] ...
let res = {};// 结果
if (typeof (text) != 'string') { return res; }// 检查参数
let textArray = text.split('\n');// 歌词拆分
let timeRule = [60000, 1000, 1];// 对应位数的毫秒数
let addTime = 0;// 时间补偿
for (let textArrayKey in textArray) {
let textArrayItem = textArray[textArrayKey];
if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
let findWord = textArrayItem.replace(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+/g, '').trim();// 歌去除时间保留歌词,并去除两端多余空格
if (findWord) {// 去除歌词为空的项
let findTime = textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g);// 匹配多个时间 例如 [1:2.3][4:2.4]hello world
for (let findTimeKey in findTime) {
let findTimeItem = findTime[findTimeKey].match(/\d+/g);// 切割每一个时间的m s ms部分
let nowTime = addTime;// 初始为偏移时间 解决[offset:-232]操作
for (let x = 0; x < findTimeItem.length; x++) {
nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
}
res[nowTime > 0 ? nowTime : 0] = findWord;// 限制下线时间 0
}
}
} else if (textArrayItem.match(/^\[offset\:\-?[1-9]\d+\]$/)) {// 匹配偏移时间 解决[offset:-232]操作
// [offset:-232]正值表示整体提前,负值相反
addTime -= parseInt(textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.length));
}
}
return res;
}
3. 到此为止大部分功能已经完成
接下来就是添加axios
然后再封装一下就大功告成(以下代码就可以直接使用,记得添加axios哟)
function Lrc(requestUrl) {
// lrc歌词解析器 依赖axios库
this.flagTags = {};// 解析的标志标签 [by:xiaohuohu]
this.timeTags = {};// 解析的歌词 [3:20.3]hello world
this.requestText = '';// 请求的lrc内容
this.canPlay = 0;// 当前状态 0 未加载 1 获取失败 2 解析失败 3 解析成功
this.canPlayInf = ['外星人正在搜寻歌词,请稍后', '外星人未找到歌词', '歌词外星人无法解析'];// 与canPlay 对应提示 除3之外
this.lastWord = '';// 上一次歌词
this.getLrc = function () {
// 异步获取歌词
let _this = this;
_this.canPlay = 0;
axios.get(requestUrl).then(function (response) {
let data = response.data;// 获取数据
_this.requestText = data;
_this.flagTags = _this.getFlagTags(data);// 解析标志
_this.timeTags = _this.getTimeTags(data);// 解析歌词
// 判断是否成功解析,不为空.
_this.canPlay = (Object.keys(_this.timeTags).length == 0) ? 2 : 3;// 设置当前状态
}).catch(function (error) {
_this.canPlay = 1;// 设置错误状态
console.log(error);
});
}
this.getRequestText = function () {
// 获取lrc源文件
return this.requestText;
}
this.getFlagTags = function (text) {
// 解析标记标签 text lrc文本
// return {标签名:标签值} 例如: {by:xiaohuohu}
let res = {};
if (typeof (text) !== 'string') { return res; }// 检查参数
let find = text.match(/\[[A-Za-z]+\:[^\[\]]*\]/g);// 匹标志标签
for (let findKey in find) {
let textArrayItem = find[findKey];
let tagName = textArrayItem.substring(textArrayItem.indexOf('[') + 1, textArrayItem.indexOf(':'));
let tagText = textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.indexOf(']'));
res[tagName] = tagText;
}
return res;
}
this.getWord = function (time, flag = true, timeDeviation = 50) {
// 获取解析歌词 time: 毫秒 flag:是否模糊匹配 若为 false 且找不到时返回 ''
// timeDeviation:模糊时间 (time - timeDeviation< time < time + timeDeviation),flag 为true 时生效
if (this.canPlay == 3) {// 解析成功
if (flag) {// 模糊匹配
for (let key in this.timeTags) {
// 获取大概区间的第一个歌词
if ((key >= time - timeDeviation) && (key <= time + timeDeviation)) {
this.lastWord = this.timeTags[key];
return this.timeTags[key];
} else if (key > time + timeDeviation) {// 未找到返回上一次的歌词
return this.lastWord;
}
}
} else {// 精确匹配
let res = this.timeTags[time];
return res ? res : '';
}
} else {// 其它状态
return this.canPlayInf[this.canPlay];
}
}
this.getTag = function () {
// 获取歌曲信息
return this.flagTags;
}
this.getTimeTags = function (text) {
// 解析歌词标签 text lrc全部文本
// return {time:word} 例如{12300:'hello world'}
// [m:s.ms] or [m:s:ms] or [m:s], [1:2.3] or [01:02.03] or [01:02] ...
let res = {};// 结果
if (typeof (text) != 'string') { return res; }// 检查参数
let textArray = text.split('\n');// 歌词拆分
let timeRule = [60000, 1000, 1];// 对应位数的毫秒数
let addTime = 0;// 时间补偿
for (let textArrayKey in textArray) {
let textArrayItem = textArray[textArrayKey];
if (textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+.*/)) {// 判断是否符合歌词的规则
let findWord = textArrayItem.replace(/(?:\[\d+\:\d+(?:[.:]\d+)?\])+/g, '').trim();// 歌去除时间保留歌词,并去除两端多余空格
if (findWord) {// 去除歌词为空的项
let findTime = textArrayItem.match(/(?:\[\d+\:\d+(?:[.:]\d+)?\])/g);// 匹配多个时间 例如 [1:2.3][4:2.4]hello world
for (let findTimeKey in findTime) {
let findTimeItem = findTime[findTimeKey].match(/\d+/g);// 切割每一个时间的m s ms部分
let nowTime = addTime;// 初始为偏移时间 解决[offset:-232]操作
for (let x = 0; x < findTimeItem.length; x++) {
nowTime += parseInt(findTimeItem[x]) * timeRule[x];// 分钟,秒,毫秒转换为转毫秒之后累加
}
res[nowTime > 0 ? nowTime : 0] = findWord;// 限制下线时间 0
}
}
} else if (textArrayItem.match(/^\[offset\:\-?[1-9]\d+\]$/)) {// 匹配偏移时间 解决[offset:-232]操作
// [offset:-232]正值表示整体提前,负值相反
addTime -= parseInt(textArrayItem.substring(textArrayItem.indexOf(':') + 1, textArrayItem.length));
}
}
return res;
}
}
4.其它
为了节省内存,可以将Lrc
的方法改进Lrc.prototype.functionName=function(){...}
5. 使用
let lrcob = new Lrc('./Counting Stars.lrc');
lrcob.getLrc();// 加载lrc歌词文件
let time = 0;
setInterval(() => {// 模仿audio,video时间进度条
lrcob.getWord(time,true,50);// 模糊获取歌词 50为模糊值
// lrcob.getWord(time,false); // 精确获取歌词
time += 100;
}, 100)
其中的 getWord
,getRequestText
,getTag
方法请自行了解
6.最后
资源奉上Github