从部分响应播放音频并在完成后保存缓冲区

Jan*_*nkt 6 javascript streaming html5-audio web-audio-api

背景

我正在编写一个服务来以块的形式提供静态音频文件,以支持带宽较低的用户的流畅体验。一旦文件完全流式传输(块序列是线性的,用户将无法“跳转”范围),我想将文件保存到本地缓存中(使用 localforage,但这不是这个问题的一部分)到稍后从那里加载缓存文件并节省带宽。

问题

根据我目前的知识/代码/工具状态,只能执行以下操作之一:

A) 使用HTMLAudioElement流式传输音频

const audio = new Audio()
audio.src = url
audio.preload = 'auto'
audio.load()
Run Code Online (Sandbox Code Playgroud)

HTML5 音频在内部处理部分响应,本身工作正常,但一旦完全加载,我就无法访问底层缓冲区来保存文件。因此,如果不单独下载(在另一个请求中),我将无法在本地缓存文件。

B) 下载/获取整个文件,然后播放

fetch(url, options) // set content header to array buffer
  .then((response) => {
    var blob = new Blob([response.value], { type: 'audio/mp3' })
    var url = window.URL.createObjectURL(blob)
    const audio = new Audio()
    audio.src = url
    audio.play()
  })
Run Code Online (Sandbox Code Playgroud)

这使我可以访问数据,以便我可以缓存它以供离线重用。但是我放弃了流媒体选项,这使得几乎不可能在没有长时间等待的情况下播放更大的文件。

C) 使用自定义加载器并使用 WebAudio API 播放每个块

由于 A 和 B 不够用,我编写了一个自定义 loader,它加载块(工作正常),并使用当前块(作为 ArrayBuffer)作为数据分派事件。它还在结束时调度另一个事件,该事件返回所有块,因此我可以从中创建一个 blob:

const chunkSize = 2048
const audioContext = new AudioContext()
const loader = new StreamLoader(url,  {step: chunkSize})

loader.on(StreamLoader.event.response, (data) => {
  // data.response is of type ArrayBuffer
  const floats = new Float32Array(data.response)
  const audioBuffer = audioContext.createBuffer(1, chunkSize, 4410)
  audioBuffer.getChannelData(0).set(floats)
  const sourceNode = new AudioBufferSourceNode(audioContext, {buffer: audioBuffer})
  sourceNode.connect(audioContext.destination)
  sourceNode.start(0)
})

loader.once(StreamLoader.event.complete, (data) => {
  const blob = new Blob(Object.values(data), {type: fileType})
  const objectURL = URL.createObjectURL(blob)
  const audio = new Audio();
  audio.src = url;
  audio.play();
})

loader.load()
Run Code Online (Sandbox Code Playgroud)

这里的问题是它只产生奇怪的巨响。但是,一旦完成,从 blob 播放就可以正常工作。

I am aware of the issue, that the header needs to be present in order to decode the audio correctly, so I am curious how the HTMLAudioElement or MediaStream actually internally resolve this.

D) Using MediaStream or MediaRecorder with custom loader

Since I am not recording from a local device, I did not got any of these to work with streaming the chunks, that I loaded using xhr. Are they restricted to be used with local media sources?

E) Using tools like Howler, SoundManager, SoundJS.

I found the above mentioned tools, but unfortunately came back to issue A or B using them.

Expected Solution

What I expect to find is a solution, that allows me to play the audio from stream (partial response) and to access the buffered data in order to cache it.

Related SO resources:

如何播放用 WebRTC 录制的音频流块?

HTML5 录制音频到文件

来自缓存的 HTML 音频元素源