前天尝试了一波那个什么 MediaSourceExtension,结果发现那套API目前限制蛮大的,而且对我来说没什么帮助(audio/x-wav 完全不正常支持,audio/mpeg 也只能在 Chrome 上使用)于是只能放弃折腾了 QAQ

昨天突然想起之前写 nanoPlayer 的时候,使用了一个叫 Audio Context 的接口,nanoPlayer 用了这个 API 里的 createAnalyser 方法,来获得音频的频率数据,进而实现了一个频谱可视化功能。之前就注意到了这个接口中有个自定义 AudioBufferSource 的方法,可以指定若干 Float32Array 并交给浏览器播放,应该是蛮有意思的。

这里就实现一个可制定频率的正弦波音频吧,如果这个实现起来没有什么难度的话,就准备试试浏览器端解码 WAV 音频。

首先是 HTML,我们要准备一个文本框来获得指定的频率,两个按钮分别控制播放的开始与停止:


<!DOCTYPE html>
<html>
  <body>
    <input id="freq" value="440"/>
    <button start>start</button>
    <button stop>stop</button>
    <script>
      // TODO: code here.
    </script>
  </body>
</html>

然后初始化若干变量:


let ctx = new AudioContext();
let frames = ctx.sampleRate;
let start = document.querySelector('button[start]');
let stop = document.querySelector('button[stop]');
let last = null;

其中 ctx.sampleRate 是 AudioContext 的采样率,这里直接将一秒钟的采样数作为帧数,产生一段持续时间为一秒的 AudioSourceBuffer 就足够了,还有 last 是用来保存上一次的 AudioSource,可以随时调用 stop 方法来终止播放。

然后就是为 start 按钮增加一个点击事件:根据文本框里的数值产生一段指定频率的音频采样:


start.onclick = (e) => {
  if (last) last.stop();

  let freq = document.querySelector('#freq').value;
  // 初始化一个单声道,采样率和 AudioCotnext 一致,持续时间为 1 sec 的 AudioBuffer
  let audioBuffer = ctx.createBuffer(1, ctx.sampleRate, ctx.sampleRate);
  // 获得其中第一个声道的数据源,类型是 Float32Array
  let nowBuffering = audioBuffer.getChannelData(0);
  // 填充数据,取值范围是 [-1, 1],直接用正弦函数就行了
  for (let i = 0; i < frames; ++i) {
    nowBuffering[i] = Math.sin((Math.PI * 2 * freq) * (i / ctx.sampleRate));
  }
  // 初始化一个 AudioBufferSource,并将上面的 AudioBuffer 与之绑定,并输出到 AudioContext
  let source = ctx.createBufferSource();
  source.loop = true;
  source.buffer = audioBuffer;
  source.connect(ctx.destination);
  // 开始回放
  source.start();
  // 保存以备后续使用
  last = source;
}

当然还需要对停止按钮绑定一个事件,来停止回放:


stop.onclick = (e) => {
  if (last) last.stop();
  last = null;
};

以上就是所有代码啦,效果的话,我在这里直接放个demo就行: