最近被选中做音视频,挺幸运的吧,一直在接触新的项目,每次都能被分到新的项目组,干好多费头发的事情
上周五肝到12点半,总算是把音频编辑上了线
总结了一下,决定写一写,也盘点一下遇到的坑
web audio API是 HTML5新增的API,提供了在web上控制音频的一个有效通用的系统,开发者可以自选音频源,对音频添加特效,添加空间效果,使音频可视化,等等。
注:目前这个API浏览器支持度并不高,pc浏览器支持较好的有firefox、Chrome和safari,Safari上也依然有不少兼容问题,移动端支持android5.0及以上,iOS端是6.1以上版本支持;
web audio 从获取数据到播放的过程大概如下图:
Inputs: audio的输入节点,可以是buffer,也可以是audio对象;
Effects:操控音频的节点(目前就用到这个:GainNode, 后续用到其他的再补充)GainNode可以用来控制音量大小,默认为静音,即0,也可以用来设置音频播放的淡入淡出;
Destination:音频播放节点,负责把声音传输给扬声器或耳机;
完整的web audio流程如下:
1. 获取音频文件,实例化一个音频对象;
2. 将获取到的音频文件转成ArrayBuffer;
3. 用ArrayBuffer ,通过AudioContext 实例化一个AudioBuffer对象;
4. 用createBufferSource() 创建一个bufferSource对象,将AudioBuffer 赋值给bufferSource.buffer;
5. 用connect()把bufferSource和GainNode连接,然后再连接到音频播放节点Destination,开始播放音频;
具体代码分析如下:
1. 实例化一个音频对象;
const AudioContext = window.AudioContext || (window.webkitAudioContext as AudioContext)
this.audioContext = new AudioContext()
2. 将获取到的音频文件转成ArrayBuffer;
上传音频文件可以直接通过input file来获取,将上传的音频文件解析后上传至服务器,获取到audioUrl,上传音频这里就不多做介绍了;
async function getAudioArrayBuffer(audioUrl: string): Promise {
const res = await fetch(audioUrl)
return res.arrayBuffer()
}
const arrayBuffer = await getAudioArrayBuffer(audioUrl)
3. 用ArrayBuffer ,通过AudioContext 实例化一个AudioBuffer对象;
该方法涉及到一个兼容问题:iOS6上报错:Not enough arguments,iOS不知道是基于 promise 的decodeAudioData,所以此处需使用回调;
async function getAudioBuffer(arrayBuffer: ArrayBuffer, audioContext: AudioContext): Promise {
let resolveFn
const promise = new Promise(resolve => resolveFn = resolve)
audioContext.decodeAudioData(arrayBuffer, resolveFn)
return promise as Promise
}
this.audioBuffer = await getAudioBuffer(arrayBuffer, this.audioContext)
4. 创建一个bufferSource对象,将AudioBuffer 赋值给bufferSource.buffer;然后用connect连接;
this.bufferSourceNode = this.audioContext.createBufferSource()
this.volumeGainNode = this.audioContext.createGain()
this.fadeGainNode = this.audioContext.createGain()
this.bufferSourceNode.buffer = this.audioBuffer
this.bufferSourceNode.connect(this.volumeGainNode)
this.volumeGainNode.connect(this.fadeGainNode)
this.fadeGainNode.connect(this.audioContext.destination)
5. 接下来就可以开始播放了,播放前需设置音量和淡入淡出效果(如果是以默认值播放的话就不用,我这里涉及到操作,所以有预设值)
setVolume(value?: number) {
this.volume = value ?? this.volume
this.volumeGainNode?.gain?.value = this.volume / 100
}
setFadein() {
this.fadein = Math.min(this.fadein, this.audioDuration / 2, 5)
if (this.fadein === 0) return
const waveArray = new Float32Array(2)
waveArray[0] = 0.001
waveArray[1] = 1
this.fadeGainNode?.gain?.setValueCurveAtTime(waveArray, this.currentTime, this.fadein)
}
setFadeout() {
this.fadeout = Math.min(this.fadeout, this.audioDuration / 2, 5)
if (this.fadeout === 0) return
const waveArray = new Float32Array(2)
waveArray[0] = 1
waveArray[1] = 0.001
this.fadeGainNode?.gain?.setValueCurveAtTime(waveArray, this.currentTime + this.audioDuration - this.fadeout + 0.001, this.fadeout)
}
play() {
if (!this.audioContext) return
this.setVolume()
this.setFadein()
this.setFadeout()
// 暂停后的播放
this.audioContext.resume()
// 设置播放开始时间startTime,有就设置,没有就不设置,默认为0,如需跳转至某个时间点播放,则需设置startTime值
this.bufferSourceNode?.start(this.currentTime, this.startTime, this.audioDuration);
}
pause() {
if (!this.isPlaying) return
// 暂停音频上下文中的时间进程,暂停音频硬件访问并减少进程中的CPU/电池使用
this.audioContext?.suspend()
}
resume() {
if (this.isPlaying) return
this.audioContext?.resume()
this.isPlaying = true
}
这样的话一个基础的播放就实现了。