此次案例的背景是一个通话录音播放下载,大概的流程是向后端请求一个接口,后端返回一个wav格式的文件流,然后使用audio播放/下载录音文件,其实大概一年前做过类似的需求,不过方式是拼接地址直接使用录音的url播放,因为是头一次处理流文件,所以走了很多冤枉路,最后处理的方式其实非常简单,但是因为最初的方向偏了,也是嘎嘎懊悔,南辕北辙啊!下面听我细细道来
请求配置
因为audio是没法直接播放wav的,所以需要处理文件流,前端常用的网络请求库有axios、fetch等,最开始我查到的解决方案是fetch的,fetch(url).then(res => res.blob()).then(blob => xx)
,将返回对象解析为blob格式的promise对象,如果是在axios中,则将responseType(将要响应的数据类型,默认为json)设置为blob即可axios(url, responseType: 'blob')
,然后就是我踩的第一个坑了,正确的做法其实是将blob转换为audio能播放的url,即URL.createObjectURL(blob)
,但是我走了非常愚蠢的一步,我将blob转为了arrayBuffer(这里可以直接请求arrayBuffer),然后使用AudioContext来播放,也不能说这样做是完全错的吧,但是把简单的事情复杂化了,因为使用audioContext想要方便操作的话需要将连接、播放、暂停全部封装方法暴露出去,毕竟blob只需要转换成url,其他全部交给audio即可,以下是我的失智操作
const result = await axios(url, responseType: 'arrayBufer')
const audioCtx = new AudioContext()
const source = audioCtx.createBufferSource()
source.buffer = result
source.connect(audioCtx.destination)
这段代码中的source.buffer是播放的音频,destination属性是音频最终的目标节点,播放使用source.start()
,暂停使用audioCtx.suspend()
,恢复播放使用audioCtx.resume()
,虽然也能达到目的,但是显而易见的要多做很多事
下载资源
播放音频解决后,就是下载音频了,第一种audio播放器上可以直接下载,这种我们不需要太操心,第二种就是用户直接在表格中通过按钮下载音频,需要考虑到播放前后下载,如果是播放前那么直接正常请求即可,如果是播放后,直接从audio的DOM上获取src,就可以少一次网络请求,我是通过设置一个标志来实现的
let mark = null
// 请求时会传入一个id
mark = id
// 下载时判断,也会传入一个id
if (!mark || mark !== id) {
// 没有,直接走请求
// 有标志,但与id不同,意为audio的src是上一个音频的,所以同样走请求
} else {
// 有标志且与id相同,获取audioDOM的src
}
示例demo
const result = await axios(url, responseType: 'blob')
const url = URL.createObjectURL(result)
audio.src = url
播放audio.play()
,暂停audio.pause()
,如此对比,是不是少了很多代码,害,失了智的一次经历