使用 Web Audio API
让我们来看看如何开始使用 Web Audio API。我们将简要介绍一些概念,然后研究一个简单的 Boombox 示例,该示例允许我们加载一个音频轨道,播放和暂停它,并更改其音量和立体声声像。
Web Audio API 不会取代 <audio> 媒体元素,而是对其进行补充,就像 <canvas> 与 <img> 元素并存一样。您的用例将决定您使用何种工具来实现音频。如果您想控制音频轨道的播放,<audio> 媒体元素比 Web Audio API 提供了更好、更快的解决方案。如果您想执行更复杂的音频处理以及播放,Web Audio API 提供了更强大的功能和控制。
Web Audio API 的一个强大功能是它没有严格的“声音调用限制”。例如,一次没有 32 或 64 次声音调用的上限。一些处理器可能能够同时播放超过 1000 种声音而不会出现卡顿。
示例代码
我们的 Boombox 看起来像这样

请注意带有播放按钮的复古磁带播放器,以及音量和声像滑块,让您能够更改音量和立体声声像。我们可以使其复杂得多,但在现阶段,这对于简单的学习来说是理想的。
在此处查看最终的在线演示,或查看 GitHub 上的源代码。
音频图
Web Audio API 中的一切都基于音频图的概念,而音频图由节点组成。
Web Audio API 在 **音频上下文 (audio context)** 中处理音频操作,并被设计为允许 **模块化路由 (modular routing)**。基本音频操作通过 **音频节点 (audio nodes)** 执行,这些节点相互连接形成一个 **音频路由图 (audio routing graph)**。您有输入节点,它们是您正在操作的声音的来源;修改节点,它们会根据需要更改这些声音;以及输出节点(目的地),它们允许您保存或听到这些声音。
即使在单个上下文中,也支持具有不同通道布局的多个音频源。由于这种模块化设计,您可以创建具有动态效果的复杂音频功能。
音频上下文
为了能够使用 Web Audio API 执行任何操作,我们需要创建一个音频上下文实例。然后,这将使我们能够访问 API 的所有功能。
const audioContext = new AudioContext();
那么,当我们这样做时,发生了什么?一个 BaseAudioContext 会自动为我们创建,并扩展为一个在线音频上下文。我们需要这个,因为我们要播放实时声音。
注意:如果您只想处理音频数据,例如,缓冲和流式传输它但不播放它,您可能需要创建 OfflineAudioContext。
加载声音
现在,我们创建的音频上下文需要一些声音来播放。使用 API 有几种方法可以做到这一点。让我们从一个简单的方法开始——正如我们有一个 Boombox,我们很可能想要播放一首完整的歌曲。此外,为了可访问性,最好在 DOM 中公开该音轨。我们将使用 <audio> 元素在页面上公开歌曲。
<audio src="myCoolTrack.mp3"></audio>
注意:如果加载的声音文件位于不同的域上,您将需要使用 crossorigin 属性;有关更多信息,请参阅 跨域资源共享 (CORS)。
要使用 Web Audio API 提供的所有便利功能,我们需要从该元素获取源并将其“管道”到我们创建的上下文中。幸运的是,有一个方法可以让我们做到这一点——AudioContext.createMediaElementSource。
// get the audio element
const audioElement = document.querySelector("audio");
// pass it into the audio context
const track = audioContext.createMediaElementSource(audioElement);
注意:上面的 <audio> 元素在 DOM 中由 HTMLMediaElement 类型对象表示,它带有一系列自己的功能。所有这些都已保留;我们只是允许 Web Audio API 使用声音。
控制声音
在网页上播放声音时,允许用户控制它很重要。根据用例,有多种选择,但我们将提供播放/暂停声音、更改音轨音量以及从左到右进行声像的功能。
通过 JavaScript 代码以编程方式控制声音受浏览器的自动播放支持策略的约束,因此在未获得用户许可(或允许列表)的情况下很可能会被阻止。自动播放策略通常要求在脚本触发音频播放之前获得明确许可或用户与页面的交互。
这些特殊要求的存在主要是因为意外的声音可能令人讨厌且具有侵扰性,并可能导致可访问性问题。您可以在我们的文章 媒体和 Web Audio API 的自动播放指南 中了解更多关于此的信息。
由于我们的脚本是在响应用户输入事件(例如,单击播放按钮)时播放音频,所以我们处于有利地位,不应遇到自动播放阻止问题。因此,让我们开始看看我们的播放和暂停功能。我们有一个播放按钮,在音轨播放时会变成暂停按钮。
<button data-playing="false" role="switch" aria-checked="false">
<span>Play/Pause</span>
</button>
在播放音轨之前,我们需要将音频图从音频源/输入节点连接到目的地。
我们已经通过将音频元素传递到 API 来创建了一个输入节点。在大多数情况下,您不需要创建输出节点,只需将其他节点连接到 BaseAudioContext.destination 即可,它会为您处理这种情况。
track.connect(audioContext.destination);
可视化这些节点的最佳方法是绘制音频图,这样您就可以可视化它。这就是我们当前的音频图的样子。

现在我们可以添加播放和暂停功能。
// Select our play button
const playButton = document.querySelector("button");
playButton.addEventListener("click", () => {
// Check if context is in suspended state (autoplay policy)
if (audioContext.state === "suspended") {
audioContext.resume();
}
// Play or pause track depending on state
if (playButton.dataset.playing === "false") {
audioElement.play();
playButton.dataset.playing = "true";
} else if (playButton.dataset.playing === "true") {
audioElement.pause();
playButton.dataset.playing = "false";
}
});
我们还需要考虑音轨播放完毕后该怎么做。当 HTMLMediaElement 播放完毕时,它会触发一个 ended 事件,因此我们可以侦听该事件并相应地运行代码。
audioElement.addEventListener("ended", () => {
playButton.dataset.playing = "false";
});
修改声音
让我们深入研究一些基本的修改节点,以更改我们拥有的声音。这就是 Web Audio API 真正派上用场的地方。首先,让我们更改音量。这可以通过 GainNode 来完成,它表示我们的声波有多大。
您可以通过两种方式使用 Web Audio API 创建节点。您可以使用上下文本身的工厂方法(例如,audioContext.createGain()),或者通过节点的构造函数(例如,new GainNode())。我们将在代码中使用工厂方法。
const gainNode = audioContext.createGain();
现在我们必须更新之前的音频图,以便将输入连接到增益,然后将增益节点连接到目的地。
track.connect(gainNode).connect(audioContext.destination);
这将使我们的音频图看起来像这样。

增益的默认值为 1;这会使当前音量保持不变。增益可以设置为最小值约为 -3.4028235E38,最大值约为 3.4028235E38(JavaScript 中的浮点数范围)。在这里,我们将允许 Boombox 将增益提高到 2(原始音量的两倍),并将音量降低到 0(这将有效地静音我们的声音)。
让我们让用户能够控制这一点——我们将使用一个 范围输入 (range input)。
<input type="range" id="volume" min="0" max="2" value="1" step="0.01" />
注意:范围输入是更新音频节点值的非常方便的输入类型。您可以指定范围的值,并直接将其与音频节点的参数一起使用。
因此,让我们获取此输入的 قيمة,并在用户更改输入节点的值时更新增益值。
const volumeControl = document.querySelector("#volume");
volumeControl.addEventListener("input", () => {
gainNode.gain.value = volumeControl.value;
});
注意:节点对象的值(例如,GainNode.gain)不是简单值;它们实际上是 AudioParam 类型的对象——它们称为参数。这就是为什么我们必须设置 GainNode.gain 的 value 属性,而不是直接在 gain 上设置值。这使它们更加灵活,例如允许将一组特定的值传递给参数,以便在设定的时间段内进行更改。
太好了,现在用户可以更新音轨的音量了!如果您想添加静音功能,增益节点是 perfect 的节点。
为我们的应用添加立体声声像
让我们再添加一个修改节点来练习我们刚才学到的东西。
有一个 StereoPannerNode 节点,它可以在左右扬声器之间更改声音的平衡,前提是用户具有立体声功能。
注意: StereoPannerNode 适用于您只想实现左右立体声声像的简单情况。还有一个 PannerNode,它允许对 3D 空间或声音空间化进行大量控制,以创建更复杂的效果。这在游戏和 3D 应用中使用,例如创建鸟在头顶飞过,或声音从用户身后传来。
为了可视化,我们将使我们的音频图看起来像这样。

这次我们使用构造函数方法来创建节点。当我们以这种方式进行时,我们必须传入上下文和该特定节点可能采用的任何选项。
const pannerOptions = { pan: 0 };
const panner = new StereoPannerNode(audioContext, pannerOptions);
注意:当前并非所有浏览器都支持使用构造函数方法创建节点。旧的工厂方法得到了更广泛的支持。
在这里,我们的值从 -1(最左边)到 1(最右边)不等。同样,让我们使用范围类型的输入来改变这个参数。
<input type="range" id="panner" min="-1" max="1" value="0" step="0.01" />
我们使用该输入的 قيمة 来调整我们的声像值,方式与我们之前一样。
const pannerControl = document.querySelector("#panner");
pannerControl.addEventListener("input", () => {
panner.pan.value = pannerControl.value;
});
让我们再次调整我们的音频图,将所有节点连接在一起。
track.connect(gainNode).connect(panner).connect(audioContext.destination);
剩下的就是尝试一下这个应用程序:在此处查看最终的在线演示。
总结
太棒了!我们有了一个可以播放“磁带”的 Boombox,我们可以调整音量和立体声声像,这使我们拥有了一个相当基础的、可用的音频图。
这构成了您开始在网站或 Web 应用中添加音频所需的基础知识。Web Audio API 还有更多功能,但一旦您掌握了节点和组装音频图的概念,我们就可以继续研究更复杂的功能。
更多示例
还有其他示例可供学习 Web Audio API。
Voice-change-O-matic 是一个有趣的语音处理器和声音可视化 Web 应用,它允许您选择不同的效果和可视化。该应用程序相当基础,但它演示了 Web Audio API 功能的同时使用。(实时运行 Voice-change-O-matic)。

另一个专门用于演示 Web Audio API 的应用程序是 Violent Theremin,一个简单的 Web 应用程序,允许您通过移动鼠标指针来改变音高和音量。它还提供迷幻的灯光秀(查看 Violent Theremin 源代码)。

还可以查看我们的 webaudio-examples 仓库 以获取更多示例。