最近被选中做音视频,挺幸运的吧,一直在接触新的项目,每次都能被分到新的项目组,干好多费头发的事情😂
上周五肝到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<ArrayBuffer> {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<AudioBuffer> {let resolveFnconst promise = new Promise(resolve => resolveFn = resolve)audioContext.decodeAudioData(arrayBuffer, resolveFn)return promise as Promise<AudioBuffer>}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.audioBufferthis.bufferSourceNode.connect(this.volumeGainNode)this.volumeGainNode.connect(this.fadeGainNode)this.fadeGainNode.connect(this.audioContext.destination)
5. 接下来就可以开始播放了,播放前需设置音量和淡入淡出效果(如果是以默认值播放的话就不用,我这里涉及到操作,所以有预设值)
setVolume(value?: number) {this.volume = value ?? this.volumethis.volumeGainNode?.gain?.value = this.volume / 100}setFadein() {this.fadein = Math.min(this.fadein, this.audioDuration / 2, 5)if (this.fadein === 0) returnconst waveArray = new Float32Array(2)waveArray[0] = 0.001waveArray[1] = 1this.fadeGainNode?.gain?.setValueCurveAtTime(waveArray, this.currentTime, this.fadein)}setFadeout() {this.fadeout = Math.min(this.fadeout, this.audioDuration / 2, 5)if (this.fadeout === 0) returnconst waveArray = new Float32Array(2)waveArray[0] = 1waveArray[1] = 0.001this.fadeGainNode?.gain?.setValueCurveAtTime(waveArray, this.currentTime + this.audioDuration - this.fadeout + 0.001, this.fadeout)}play() {if (!this.audioContext) returnthis.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) returnthis.audioContext?.resume()this.isPlaying = true}
这样的话一个基础的播放就实现了。