效果图:
视频:
小程序音频可视化预览视频
先看整体原理图(虽然丑,但通俗易懂):
1、肝个音频播放器 WebAudioContext
-
初始化音频播放器:
创建一个音频上下文:
this.audioCtx = wx.createWebAudioContext();
创建音频分析器节点:
this.analyser = this.audioCtx.createAnalyser();
设置快速傅里叶变换大小(在进行音频数据分析的时候 ,每次使用指定大小进行傅里叶变换,这个值决定了频域分析的分辨率和精确度)
this.analyser.fftSize = 512;
创建一个数组,用于存储格式化后得buffer数据(frequencyBinCount:analyser上自带的一个属性,是fftSize的一半)。
this.data.dataArray = new Uint8Array(this.analyser.frequencyBinCount);
将分析器节点连接到喇叭
this.analyser.connect(this.audioCtx.destination);
-
请求音频的buffer数据
使用wx.request(),将responseType设置为arraybuffer。这里使用wx官方提供的方法。使用方法的时候,直接将音频的url地址传入就可以获得音频的buffer数据。(未格式化)
//获取语音的buffer数据
loadAudio(url) {
return new Promise((resolve) => {
wx.request({
url,
responseType: "arraybuffer",
success: (res) => {
this.audioCtx.decodeAudioData(
res.data,
(buffer) => {
console.log("返回buffer数据");
resolve(buffer);
},
(err) => {
console.error("decodeAudioData fail", err);
reject();
}
);
},
fail: (res) => {
console.error("request fail", res);
reject();
},
});
});
},
-
绑定音频源节点
请求到buffer数据后创建音频源节点source,并将其绑定到source 的buffer字段上。并且将音频源节点连接到分析器节点以及添加播放结束监听事件。
getBuffer() {
this.loadAudio("https://s1.wzznft.com/i/2023/12/07/gwkbz2.mp3").then(
(buffer) => {
this.source = this.audioCtx.createBufferSource();
this.source.connect(this.analyser);
this.source.buffer = buffer;
//执行缓存操作
sourceCache.add(this.source);
//监听音频播放完成
this.source.onended = () => {
sourceCache.delete(this.source);
};
}
);
},
注:getBuffer 这个方法需要在音频播放前被成功调用一次,因为每次播放都需要产生新的source。
音频播放器初始化完成,接下来是加入canvas,但在这之前我们还需要获取到analyser 分析器节点分析后的数据,方便绘图。
2、上 Canvas
-
获取分析器格式化后的数据
直接:
this.analyser.getByteFrequencyData(this.data.dataArray);
这行代码将从分析器格式化后的数据存储到事先定义好的dataArray数组中。拿到格式化的数据之后,直接化身绘画大佬。
-
定义绘制canvas的draw方法
获取分析器格式化后的数据
先xue微肝点绘制代码:
//绘制
draw() {
if (!this.cvs) {
setTimeout(() => {
this.draw();
}, 200);
return;
}
const { width, height } = this.cvs;
this.ctx.clearRect(0, 0, width, height);
this.analyser.getByteFrequencyData(this.data.dataArray);//从分析器获取格式化后数据存入数组
const len = this.data.dataArray.length / 2.5;//截取数据长度
const barWidth = width / len; //柱形条的宽度
this.ctx.fillStyle = "#efa";//颜色
//绘制
for (let i = 0; i < len; i++) {
const data = this.data.dataArray[i];
const barHeight = (data / 255) * height;
const x1 = i * barWidth + width / 2;//x左侧
const x2 = width / 2 - (i + 1) * barWidth;//x右侧
const y = height - barHeight;
this.ctx.fillRect(x1, y, barWidth - 2, barHeight);
this.ctx.fillRect(x2, y, barWidth - 2, barHeight);
}
},
但此时canvas还无法绘制图形,我们需要根据音频的变化实时的绘制。在小程序中需要使用循环定时函数来模拟浏览器API requestAnimationFrame的效果。
上代码:
this.data.voiceTimer = setInterval(() => {
this.draw();
}, 16);//时间设置为16 大约60fps
最后加一点控制的按钮:
<view class="controlBox">
<button class="btn" type="primary" bindtap="play">Play</button>
<button class="btn" type="warn" bindtap="stop">Stop</button>
<button class="btn" bindtap="generate">Gen</button>
</view>
play() {
//
this.data.voiceTimer = setInterval(() => {
this.draw();
}, 16);
this.source.start();
},
generate() {
this.getBuffer();
},
stop() {
this.source.stop();
},
然后,点击Gen之后,播放。
代码片段:https://developers.weixin.qq.com/s/6wcwXhmj74Nr
From limitCG