前言
我们尝试用原生 js 写一个音乐播放器,对音频做一个简单的可视化。
最终的效果如下图:
思路还是比较明显的,第一,我们要拿到音频的什么数据进行可视化,如何获取?第二,如何可视化,第二个问题就比较简单了,用 canvas 来操作。
获取音频相关的数据可以用 Web Audio API , 这里我们可以去 MDN 或者 W3C 上查看相关的文档。
audio API
我们都知道在 web 中播放音频用 audio
这个标签来操作。Web Audio API 是对 audio
标签功能上的补充,它可以:
- 一秒内同时发出多个声音
- 把音频流独立出来,进行复杂操作
- 将音频输出到多个地方,比如 canvas,从而实现可视化
audio node 有入口和出口,多个节点构成类似链表一样的结构,从一个或者多个音源出发,经过一个或者多个处理节点,最终输出到输出节点。web audio 的一个简单处理过程如下:
- 创建 AudioContext 对象
- 在 AudioContext 对象内设置音源,例如
- 创建 affcet node(效果节点)
- 选择音频的最终输出节点
- 音频经过效果节点处理后,然后输入到下一个节点
// 获取AudioContext对象
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
// 创建AudioContext对象实例
var actx = new AudioContext();
// 创建AuidoSource,此处的audios为<audio>标签
var audioSrc = actx.createMediaElementSource(audios);
var analyser = actx.createAnalyser();
// 输入的连接
audioSrc.connect(analyser);
// 输出的连接
analyser.connect(actx.destination);
// 本例子中我们获取音高数据来进行可视化
var voiceHigh = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(voiceHigh);
canvas
用 canvas 对音高数据进行可视化展示。
function draw() {
var step=Math.round(voiceHigh.length/num);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
for(var i=0; i<num; i++) {
var value = voiceHigh[step*i];
ctx.fillColor = color;
ctx.fillRect(i * 10 + canvas.width * .5, 250, 7, -value + 1);
ctx.fillRect(canvas.width * .5 - (i - 1) * 10, 250, 7, -value + 1);
ctx.fill();
ctx.fillStyle = colort;
ctx.fillRect(i * 10 + canvas.width * .5, 250, 7, value + 1);
ctx.fillRect(canvas.width * .5 - (i - 1) * 10, 250, 7, value + 1);
}
requestAnimationFrame(draw);
}
进度条
进度条算是比较简单好做的了,我们用一个背景 div 填充,另外一个 div 表示当前进度,宽度是动态变化的,根据音频播放的当前时间除以总时间,然后乘背景 div 的宽度,计算得到结果即可。
var bglen = document.getElementById('bgline').offsetWidth;
var fillbar = document.getElementById('fillbar');
fillbar.style.width = parseFloat(cur / duration) * bglen + 'px';
滚动歌词
歌词这一块,首先要找好思路,如何解析歌词?如何控制歌词滚动?如何判断当前是哪句歌词?弄清楚这几个问题后,事情就变得不困难了。
解析歌词
这一块可能是这部分内容里面最复杂的,需要一些简单的算法,这里用到了递归。
解析前的 lrc 歌词是这样的:
[ti:不要在我寂寞的时候说爱我] [ar:郑源] [by:] [00:21.57]柔柔的晚风轻轻吹过 [00:25.39]我的心情平静而寂寞 [00:29.96]当我想忘记爱情去勇敢生活 [00:34.11]是谁到我身边唱起了情歌 [01:48.86][00:39.01]当初的爱情勿匆走过 [01:52.69][00:42.79]除了伤口没留下什么 [01:57.27][00:47.40]你总是在我寂寞流泪的时候 [02:01.67][00:51.80]用你的双臂紧紧抱着我 [02:59.75][02:05.19][00:55.27]不要在我寂寞的时候说爱我 [03:04.15][02:09.69][00:59.96]除非你真的能给予我快乐 [03:09.30][02:14.78][01:04.97]那过去的伤总在随时提醒我 [03:13.94][02:19.40][01:09.65]别再被那爱情折磨 [03:17.17][02:22.65][01:12.82]不要在我哭泣的时候说爱我 [03:21.62][02:27.11][01:17.32]除非你真的不让我难过 [03:25.65][02:31.13][01:21.33]我不想听太多那虚假的承诺 [03:30.00][02:35.44][01:25.69]让我为爱再次后悔犯下的错!
解析处理后是这样的:
function getnext(y, lrc) {
var rs = '';
var i = y + 1;
if(lrc[i]) {
t = lrc[i].split(']');
if(t[1] == '') {
rs = getnext(i, lrc);
}else {
rs = t[1]
}
}
return rs;
}
歌词滚动
歌词滚动最简单的一种方法,让包含歌词的盒子的 top 值不断减少,歌词就会一直往上走。通过当前播放的时间和歌词的时间对个比较,判断当前时间对应的歌词,给对应的 dom 添加标识。
cur = parseInt(audio.currentTime);
min = parseInt(cur / 60);
sec = parseInt(cur % 60);
sec = sec < 10 ? '0' + sec : sec;
curTime.innerHTML = min + ':' + sec;
fillbar.style.width = parseFloat(cur / duration) * bglen + 'px';
if(lrcTimeArr.indexOf(cur) > -1) {
document.querySelectorAll('#lrc li').forEach(function(e) {
e.className = '';
})
document.querySelector('#lrc li[time="'+cur+'"]').className = 'active';
lrcEl.style.top = lrcOffsetTop - lrcTimeArr.indexOf(cur) * 16 + 'px';
}
写在最后
以上是在音乐播放器以及可视化方面的一些实验。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于