前言
之前刚好看到Web Audio API方面的内容,因此用了相关api做了个音频可视化的页面。实现:
- 音频播放/暂停
- 音频声量控制
- 音频立体声控制
- 音频频率可视化
- 音频切换
预备知识
Web Audio API中一个关键的对象就是音频上下文(AudioContext),可以类比canvas context,在AudioContext我们进行相关的操作。音频处理的一个典型流程为:
- 创建音频上下文(AudioContext)
- 在音频上下文里创建源 — 例如
(HTMLAudioElement), 音频数据(Ajax 获取的 AudioBufferSourceNode ), 流(MediaStreamAudioSourceNode)
- 创建效果节点,例如混响、双二阶滤波器、平移、压缩
- 为音频选择一个目的地(destination),例如你的系统扬声器
- 连接(connect)源到效果器,对目的地进行效果输出
- 具体建立过程
// 1. 创建上下文
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// 2. 获取音频源(MediaElementAudioSourceNode)
// 这里音频源可以使用 OscillatorNode 创建,也使用麦克风源等
var audioElement = document.querySelector('audio');
var track = audioContext.createMediaElementSource(audioElement);
// ...这里我们已经可以将声音连接到系统扬声器
// track.connect(audioContext.destination);
// 3. 需要先启动音频环境,否则可能会提示需要手势触发的警告
// resume() 和 suspend() 返回的都是异步对象,最好使用then等待异步处理结束
if (audioContext.state === 'suspended') {
audioContext.resume();
}
// 4. 播放控制
audioElement.play();
audioElement.pause();
// 5. 声音控制
const gainNode = audioContext.createGain();
track.connect(gainNode).connect(audioContext.destination);
// 6. 立体声平移控制
const pannerOptions = { pan: 0 };
const panner = new StereoPannerNode(audioContext, pannerOptions);
// 官网使用构造器方法,但是在Edge中会提示错误,可以使用以下的工厂方法代替
this.panner = this.audioContext.createStereoPanner();
this.panner.pan.value = 0.0;
// 7. 连接到系统扬声器
track.connect(gainNode).connect(panner).connect(audioContext.destination)
复制代码
实现细节
- 可视化
// 1. 创建AnalyserNode对象
var analyser = audioContext.createAnalyser();
// 2. 获取柱形图需要的数量,初始化一个dataArray
var bufferLength = analyser.frequencyBinCount;
var dataArray = new Uint8Array(bufferLength);
// 3. 获得绘制输出
var canvasCtx = canvasElement.getContext('2d');
// 4. 获得 canvas 相关参数
var WIDTH = canvasElement.width;
var HEIGHT = canvasElement.height;
// 5. 定义需要的条形数目,可自己定制
count = dataArray.length;
// 6. 绘制canvas
function draw () {
canvasCtx.clearRect(0, 0, WIDTH, HEIGHT);
analyser.getByteFrequencyData(dataArray);
let value = 0,
step = Math.round(dataArray.length / count), // 获得绘制间隔
x = 0,
y = 0,
lineWidth = canvasCtx.lineWidth = WIDTH / count, // 描边宽度
index = count;
canvasCtx.strokeStyle = "#fff"; // 描边颜色
while (index) {
value = dataArray[index * step + step]; // 描边高度相关
x = index * lineWidth;
y = HEIGHT - value * 1.5;
canvasCtx.beginPath(); // 开始绘制
canvasCtx.moveTo(x, HEIGHT); // 从坐标轴x点开始绘制
canvasCtx.lineTo(x, y); // 到需要的高度结束
canvasCtx.stroke();
index -= 2; // 调整绘制间隔
}
requestAnimationFrame(() => draw());
};
复制代码
- 播放控制
function playHandler () {
if (playState === false) {
audioElement.play();
playState = true;
playButton.dataset.playing = 'true';
} else {
audioElement.pause();
playState = false;
playButton.dataset.playing = 'false';
}
}
复制代码
- 音频切换
如果多个音频关联到一个 GainNode 对象,切换前注意断开连接
我们通过 audioContext.createMediaElementSource(audioElement)
已经将上下文对象与一个 HTMLAudioElement
对象关联,我们后面进行操作可以通过切换这个媒体对象的src实现(或者重新new一个AudioContext与新的媒体对象关联,不推荐)
// 假如我们之前有这样的连接
function setTrack () {
track.connect(gainNode)
.connect(panner)
.connect(analyser)
.connect(audioContext.destination);
}
// 那么我们进行音频切换时要进行如下操作
function changeAudio (index) {
let idx = index || 0;
if (playList.length <= 0 || idx > playList.length) return;
playState = false;
track && track.disconnect();
gainNode && gainNode.disconnect();
panner && panner.disconnect();
analyser && analyser.disconnect();
audioElement.src = playList[idx].src;
audioElement.load();
_enableControls();
}
复制代码
- 具体实现效果
常用API
AudioContext.currentTime
以双精度浮点型数字返回硬件调用的秒数 (readonly)AudioContext.state
返回AudioContext当前状态 (readonly)AnalyserNode.frequencyBinCount
一个无符号长整形 (unsigned long) 的值, 值为fftSize的一半。这通常等于将要用于可视化的数据值的数量。AudioContext.createMediaElementSource()
创建一个 MediaElementAudioSourceNode 接口来关联 HTMLMediaElement . 这可以用来播放和处理来自或
元素的音频
AudioContext.resume()
重新启动一个已被暂停的音频环境AudioContext.suspend()
暂停音频内容的进度.暂时停止音频硬件访问和减少在过程中的CPU/电池使用AudioContext.close()
关闭一个音频环境, 释放任何正在使用系统资源的音频AudioContext.decodeAudioData()
从ArrayBuffer对象中异步解码音频文件AudioContext.createGain()
创建一个GainNode,它可以控制音频的总音量AudioContext.createPanner()
创建一个PannerNode, 它为音源创建一个3D音源环境AudioContext.createAnalyser()
创建一个AnalyserNode,它可以用来显示音频时间和频率的数据AudioContext.createStereoPanner()
创建一个使用立体声的音频源 StereoPannerNodeAudioContext.createAnalyser()
创建一个AnalyserNode,可以用来获取音频时间和频率数据,以及实现数据可视化。AnalyserNode.getByteFrequencyData()
将当前频域数据拷贝进Uint8Array数组- 这里注意closed状态是不可逆的(close后要重新new),三种状态操作都是异步操作
new AudioContext() | V +----------+ +------------+ | running | -- suspend() -> | suspended | | | <- resume() --- | | +----------+ +------------+ | | | close() | close() +------------------------------+ | V +-----------+ | closed | +-----------+ 复制代码
不兼容IE
项目预览codepen
项目地址github
参考资料
- AudioContext
- Web_Audio_API
- Using_Web_Audio_API
- BOOM BOOX