前言

最近电视剧《点燃我,和煦你》中的一个桥段,李峋写的爱心代码火了 ❤️❤️❤️,相似的表白神器每年都会在特定的工夫(这些日期你都晓得吗)出圈几个。

当各路大佬开始晒各种语言各种类型的爱心代码,我一个顶级CV工程师,在这塌实的环境下天然也想着夸耀一番,于是在这片汪洋大海中通过我的层层筛选,这款爱心代码被我挑中并对他进行了小小的革新。

插件地址

对于插件:

这款插件的作者从代码来看曾经无从讲究了,感激大佬栽树。

作为前端实现爱心代码的酷炫成果,尤其是3d成果应用的技术栈根本都是以three.js+canvas为主

这款插件也是应用three.js + canvas 开发,具体的开发过程不做过多的剖析,cv工程师不太会three.js,见笑了

重点认识一下
Web Audio API:

Web Audio API 为管制 Web 上的音频提供了一个弱小且通用的零碎,容许开发人员抉择音频源、为音频增加成果、创立音频可视化、利用空间成果(如平移)等等。

声音远近,大小,触动等等这些成果的可视化都须要Web Audio API的帮忙

接口:这里简略列举了几个插件中用到的接口,更多接口可返回MDN查看

AudioListener

AudioListener 用一个虚构的listener示意在场景中所有的地位和非地位相干的音效.
一个three.js程序通常创立一个AudioListener. 它是音频实体构造函数的必须参数,比方 Audio and PositionalAudio.
大多数状况下, listener对象是camera的子对象. Camera的3D变换示意了listener的3D变换.

AudioLoader

用来加载 AudioBuffer的一个类。
外部默认应用FileLoader> > 来加载文件。(我的项目中用来加载音频文件)

AudioAnalyser

创立AudioAnalyser对象, 应用AnalyserNode 去剖析音频数据.

开始

插件动画成果曾经完满了,合乎装要求。

去掉边框更显大气;

bgm像铃声换成我周董的更能俘获人心;

周董咬字不清加个歌词显示更加人性化(开玩笑的啦,喜爱的就是这个味)。

动画追随旋律的成果插件曾经有了,配上歌词那不就是一个现成的播放器吗

思路一:

实现辨认音频显示歌词,相似视频播放显示字幕的成果。

一通百度失去的后果次要分两种

  • 上传语音到服务端,服务端利用解析语音的API,返回文字;
  • 利用原生api或各种库调取浏览器麦克风,辨认语音转文字

办法一在我这个捣鼓着玩的我的项目里显著不实用,办法二延时太久,辨认精确度太低

思路二:√

获取歌词,实现歌词与音频的同步,这种计划是可行的

实现

获取歌词

网上找到LRC格局的歌词,肯定要是LRC格局,放到一个文本域中,设置为暗藏

<textarea id="lrc_content" cols="30" style="display: none">    [ti:花海]    [ar:周杰伦]    [00:03.63]花海      [00:06.35]周杰伦      [00:14.49]...    [00:18.56]...    [00:26.90]静止了 所有的花开      [00:33.31]边远了 清晰了爱      [00:39.53]情侣们 爱却更喜爱      [00:45.99]那时候 我不懂 这场爱      [00:51.55]    [00:52.50]你喜爱 站在那窗台      [00:58.47]你良久 都没再来      [01:05.12]黑白的 世界染上空白      [01:12.18]是你流的泪晕开      [01:16.14]    [01:17.26]不要你来到 间隔隔不开      [01:23.65]怀念变成海 在窗外进不来      [01:29.82]原谅说太快 爱成了妨碍      [01:36.24]手中的风筝放太快回不来      [01:42.22]    [01:42.74]不要你来到 泪已化不开      [01:48.96]经验的妨碍 我在期待重来      [01:55.51]天空仍璀璨 她爱着大海      [02:01.79]情歌被战胜 爱已不存在      [02:09.83]    [02:34.80]你喜爱 站在那窗台      [02:41.14]你良久 都没再来      [02:47.40]黑白的 世界染上空白      [02:54.66]是你流的泪晕开      [02:59.87]    [03:00.24]不要你来到 间隔隔不开      [03:05.75]怀念变成海 在窗外进不来      [03:12.16]原谅说太快 爱成了妨碍      [03:18.56]手中的风筝放太快回不来      [03:24.53]    [03:25.07]不要你来到 泪已化不开      [03:31.37]经验的妨碍 我在期待重来      [03:37.81]天空仍璀璨 她爱着大海      [03:44.12]情歌被战胜 爱已不存在      [03:54.85]</textarea>

js读取歌词

var lrc = document.getElementById("lrc_content").innerHTML;

将LRC歌词解析为JS对象

var oLRC = {    ti: "", //歌曲名    ar: "", //演唱者    al: "", //专辑名    by: "", //歌词制作人    offset: 0, //工夫弥补值,单位毫秒,用于调整歌词整体地位    ms: [] //歌词数组{t:工夫,c:歌词}};function createLrcObj(lrc) {    if(lrc.length==0) return;    var lrcs = lrc.split('\n');//用回车拆分成数组    for(var i in lrcs) {//遍历歌词数组        lrcs[i] = lrcs[i].replace(/(^\s*)|(\s*$)/g, ""); //去除前后空格        var t = lrcs[i].substring(lrcs[i].indexOf("[") + 1, lrcs[i].indexOf("]"));//取[]间的内容        var s = t.split(":");//拆散:前后文字        if(isNaN(parseInt(s[0]))) { //不是数值            for (var i in oLRC) {                if (i != "ms" && i == s[0].toLowerCase()) {                    oLRC[i] = s[1];                }            }        }else { //是数值            var arr = lrcs[i].match(/[(\d+:.+?)]/g);//提取工夫字段,可能有多个            var start = 0;            for(var k in arr){                start += arr[k].length; //计算歌词地位            }            var content = lrcs[i].substring(start);//获取歌词内容            for (var k in arr){                var t = arr[k].substring(1, arr[k].length-1);//取[]间的内容                var s = t.split(":");//拆散:前后文字                oLRC.ms.push({//对象{t:工夫,c:歌词}退出ms数组                    t: (parseFloat(s[0])*60+parseFloat(s[1])).toFixed(3),                    c: content                });            }        }    }    oLRC.ms.sort(function (a, b) {//按工夫程序排序        return a.t-b.t;    });}createLrcObj(lrc);

将解析后的歌词出现在页面上

创立一个容器展现歌词

    <ul id="lyric"><!--        <li>花海</li>-->    </ul>

用JS把歌词写到标签外面

注:如果歌词显示成果是全副展现,高亮以后歌词须要这一步操作;如果只显示以后歌词不展现其余歌词,可省略这一步

function showLRC() {    var s="";    for(var i in oLRC.ms){//遍历ms数组,把歌词退出列表        s+='<li>'+oLRC.ms[i].c+'</li>';    }    document.getElementById("lyric").innerHTML = s;    console.log(s)}showLRC();

JS实现歌词与播放音乐同步

歌词滚动成果

CSS

.lyric_area{/*歌词显示区域*/    height: 300px; /*歌词区域高度*/    overflow: hidden; /*暗藏超出局部*/    margin-top: 15px;}#lyric{/*歌词列表*/    line-height: 20px;/*行高,这个值要用在歌词滚动间隔上*/    transition-duration: 600ms;/*滚动速度*/ }#lyric .lineHigh{/*高亮行*/    color: red;}

JS

var lineNo = 0; //以后行var C_pos = 6; //C位var offset = -20; //滚动间隔(应等于行高)var audio = document.getElementById("audio");//播放器var ul = document.getElementById("lyric"); //歌词容器列表//高亮显示歌词以后行及文字滚动管制,行号为lineNofunction lineHigh() {    var lis = ul.getElementsByTagName("li");//歌词数组    if(lineNo>0){         lis[lineNo-1].removeAttribute("class");//去掉上一行的高亮款式    }    lis[lineNo].className = "lineHigh";//高亮显示以后行    //文字滚动    if(lineNo>C_pos){        ul.style.transform = "translateY("+(lineNo-C_pos)*offset+"px)"; //整体向上滚动一行高度    }}//滚回到结尾,用于播放完结时function goback() {    document.querySelector("#lyric .lineHigh").removeAttribute("class");    ul.style.transform = "translateY(0)";    lineNo = 0;}//监听播放器的timeupdate事件,实现文字与音频播放同步audio.ontimeupdate = function () {    if(lineNo==oLRC.ms.length)        return;    var curTime = audio.currentTime; //播放器工夫    if(parseFloat(oLRC.ms[lineNo].t)<=curTime){        lineHigh();//高亮以后行        lineNo++;    }};//监听播放器的ended事件,播放完结时回滚歌词audio.onended = function () {    goback(); //回滚歌词};
单句歌词切换成果

var lineNo = 0; //以后行var audio = document.getElementById("auo");//播放器//滚回到结尾,用于播放完结时function goback() {    // document.getElementById('lyric').innerText = '花海'    lineNo = 0;}//监听播放器的timeupdate事件,实现文字与音频播放同步audio.ontimeupdate = function () {    if(lineNo==oLRC.ms.length)        return;    var curTime = audio.currentTime; //播放器工夫    if(parseFloat(oLRC.ms[lineNo].t)<=curTime){        //歌词切换成果        document.getElementById('lyric').innerText = oLRC.ms[lineNo].c        lineNo++;    }};//监听播放器的ended事件,播放完结时回滚歌词audio.onended = function () {    goback(); //回滚歌词};

问题

插件自身用web audio api加载的音频,而歌词追随音频显示是读取的audio标签,这里两者有抵触,利用web audio api中的属性是能够实现文字与音频的播放同步的,这里我没有学习并进行对立。
而是利用audio标签加载,防止抵触点击播放之后将audio绑定的音频音量静音

有须要代码的大家可自行下载预览(我在这里)