standardized-audio-context
一个旨在严格遵循标准的跨浏览器 Web Audio API 包装器。
这个包提供了 Web Audio API 的一个子集(尽管几乎是完整的),可以在每个支持的浏览器中以可靠和一致的方式工作。与其他流行的 polyfill 不同,standardized-audio-context
不会在全局作用域上修补或修改任何内容。换句话说,它不会造成任何副作用。因此,它可以在库中安全使用。这就是所谓的 ponyfill。
standardized-audio-context
的目标之一是只实现缺失的功能,并尽可能避免重写内置功能。请查看下面关于浏览器支持的段落以获取更多信息。
有些功能无法以使其像原生实现那样高效的方式模拟。其中最突出的是 AudioWorklet
。请查看下面的所有支持方法列表以获取更详细的信息。
使用方法
standardized-audio-context
可在 npm 上获得,可以按常规方式安装。
npm install standardized-audio-context
然后你可以这样导入 AudioContext
和 OfflineAudioContext
:
import { AudioContext, OfflineAudioContext } from 'standardized-audio-context';
也可以使用 jspm 等服务加载 standardized-audio-context
。此时上面的导入语句需要改为指向一个 URL。
import { AudioContext, OfflineAudioContext } from 'https://jspm.dev/standardized-audio-context';
一旦导入了 AudioContext
和/或 OfflineAudioContext
,就可以像使用它们的原生对应物一样使用它们。例如,以下代码片段将产生一个干净(且烦人)的正弦波。
import { AudioContext } from 'standardized-audio-context';
const audioContext = new AudioContext();
const oscillatorNode = audioContext.createOscillator();
oscillatorNode.connect(audioContext.destination);
oscillatorNode.start();
另一种方法是使用 AudioNode 构造函数(在本例中是 OscillatorNode 构造函数)而不是工厂方法。
import { AudioContext, OscillatorNode } from 'standardized-audio-context';
const audioContext = new AudioContext();
const oscillatorNode = new OscillatorNode(audioContext);
oscillatorNode.connect(audioContext.destination);
oscillatorNode.start();
API
AudioContext
这是 AudioContext
接口的几乎完整的实现。它只缺少 createScriptProcessor()
方法,而这个方法已经被废弃了。
⚠️ Safari 目前不支持设置 sampleRate。
⚠️ Safari 一次只允许运行 4 个 AudioContext。创建第五个 AudioContext 将抛出 UnknownError
。
AudioContext 实现了以下 TypeScript 接口。
interface IAudioContext extends EventTarget {
readonly audioWorklet?: IAudioWorklet;
readonly baseLatency: number;
readonly currentTime: number;
readonly destination: IAudioDestinationNode<IAudioContext>;
readonly listener: IAudioListener;
onstatechange: null | TEventHandler<IAudioContext>;
readonly sampleRate: number;
readonly state: TAudioContextState;
close(): Promise<void>;
createAnalyser(): IAnalyserNode<IAudioContext>;
createBiquadFilter(): IBiquadFilterNode<IAudioContext>;
createBuffer(numberOfChannels: number, length: number, sampleRate: number): IAudioBuffer;
createBufferSource(): IAudioBufferSourceNode<IAudioContext>;
createChannelMerger(numberOfInputs?: number): IAudioNode<IAudioContext>;
createChannelSplitter(numberOfOutputs?: number): IAudioNode<IAudioContext>;
createConstantSource(): IConstantSourceNode<IAudioContext>;
createConvolver(): IConvolverNode<IAudioContext>;
createDelay(maxDelayTime?: number): IDelayNode<IAudioContext>;
createDynamicsCompressor(): IDynamicsCompressorNode<IAudioContext>;
createGain(): IGainNode<IAudioContext>;
createIIRFilter(feedforward: number[], feedback: number[]): IIIRFilterNode<IAudioContext>;
createMediaElementSource(mediaElement: HTMLMediaElement): IMediaElementAudioSourceNode<IAudioContext>;
createMediaStreamDestination(): IMediaElementAudioDestinationNode<IAudioContext>;
createMediaStreamSource(mediaStream: MediaStream): IMediaStreamAudioSourceNode<IAudioContext>;
createMediaStreamTrackSource(mediaStreamTrack: MediaStreamTrack): IMediaStreamTrackAudioSourceNode<IAudioContext>;
createOscillator(): IOscillatorNode<IAudioContext>;
createPanner(): IPannerNode<IAudioContext>;
createPeriodicWave(real: number[], imag: number[], constraints?: Partial<IPeriodicWaveConstraints>): IPeriodicWave;
createStereoPanner(): IStereoPannerNode<IAudioContext>;
createWaveShaper(): IWaveShaperNode<IAudioContext>;
decodeAudioData(
audioData: ArrayBuffer,
successCallback?: TDecodeSuccessCallback,
errorCallback?: TDecodeErrorCallback
): Promise<IAudioBuffer>;
resume(): Promise<void>;
suspend(): Promise<void>;
}
下面将更详细地描述这些属性和方法。
OfflineAudioContext
这是 OfflineAudioContext
接口的几乎完整的实现。它只缺少 createScriptProcessor()
方法,而这个方法已经被废弃了。
⚠️ Safari 不支持创建超过 10 个通道或采样率低于 44100 Hz 的 OfflineAudioContext。
它实现了以下 TypeScript 接口。
interface IOfflineAudioContext extends EventTarget {
readonly audioWorklet?: IAudioWorklet;
readonly baseLatency: number;
readonly currentTime: number;
readonly destination: IAudioDestinationNode<IOfflineAudioContext>;
readonly length: number;
readonly listener: IAudioListener;
onstatechange: null | TEventHandler<IOfflineAudioContext>;
readonly sampleRate: number;
readonly state: TAudioContextState;
createAnalyser(): IAnalyserNode<IOfflineAudioContext>;
createBiquadFilter(): IBiquadFilterNode<IOfflineAudioContext>;
createBuffer(numberOfChannels: number, length: number, sampleRate: number): IAudioBuffer;
createBufferSource(): IAudioBufferSourceNode<IOfflineAudioContext>;
createChannelMerger(numberOfInputs?: number): IAudioNode<IOfflineAudioContext>;
createChannelSplitter(numberOfOutputs?: number): IAudioNode<IOfflineAudioContext>;
createConstantSource(): IConstantSourceNode<IOfflineAudioContext>;
createConvolver(): IConvolverNode<IOfflineAudioContext>;
createDelay(maxDelayTime?: number): IDelayNode<IOfflineAudioContext>;
createDynamicsCompressor(): IDynamicsCompressorNode<IOfflineAudioContext>;
createGain(): IGainNode<IOfflineAudioContext>;
createIIRFilter(feedforward: number[], feedback: number[]): IIIRFilterNode<IOfflineAudioContext>;
createOscillator(): IOscillatorNode<IOfflineAudioContext>;
createPanner(): IPannerNode<IOfflineAudioContext>;
createPeriodicWave(real: number[], imag: number[], constraints?: Partial<IPeriodicWaveConstraints>): IPeriodicWave;
createStereoPanner(): IStereoPannerNode<IOfflineAudioContext>;
createWaveShaper(): IWaveShaperNode<IOfflineAudioContext>;
decodeAudioData(
audioData: ArrayBuffer,
successCallback?: TDecodeSuccessCallback,
errorCallback?: TDecodeErrorCallback
): Promise<IAudioBuffer>;
startRendering(): Promise<IAudioBuffer>;
}
下面将更详细地描述这些属性和方法。
audioWorklet
⚠️ AudioWorklet
可作为 AudioContext 或 OfflineAudioContext 的一个属性访问。它在 Safari 中内部使用 ScriptProcessorNode 来创建 AudioWorkletProcessor
。这意味着它只会在 Chrome、Edge 和 Firefox 中提供你通常期望从使用 AudioWorklet
中获得的性能改进。
⚠️ 内部实现依赖于 ScriptProcessorNode 这一事实也意味着 channelCountMode
目前只能是 'explicit'
。这也意味着所有输入的总通道数加上所有参数的数量不能超过六个。
⚠️ 另一点需要注意的是,fallback 将在全局作用域上评估 AudioWorkletProcessor
。它以基本的方式被隔离以模仿 AudioWorkletGlobalScope
,但这无法以使攻击者无法突破该沙箱的方式来完成。除非你从不受信任的来源加载 AudioWorklet,否则这应该不是问题。
listener
⚠️ Firefox 和 Safari 14.0.1 及之前的版本不支持通过 AudioParams 修改监听器。因此,在 OfflineAudioContext 的监听器的 AudioParams 上调用任何调度函数都会抛出 NotSupportedError
。
createAnalyser() / AnalyserNode
这是 createAnalyser()
工厂方法的实现。AnalyserNode
构造函数可以作为替代方案使用。
createBiquadFilter() / BiquadFilterNode
这是 createBiquadFilter()
工厂方法的实现。BiquadFilterNode
构造函数可以作为替代方案使用。
createBuffer() / AudioBuffer
这是 createBuffer()
工厂方法的实现。也可以使用 AudioBuffer
构造函数作为替代。
⚠️ Safari 不支持采样率低于 22050 Hz 的 AudioBuffer。
createBufferSource() / AudioBufferSourceNode
这是 createBufferSource()
工厂方法的实现。也可以使用 AudioBufferSourceNode
构造函数作为替代。
⚠️ 目前尚未实现 detune AudioParam。
⚠️ Safari 14.0.1 及之前版本不支持将信号连接到 playbackRate AudioParam。因此,在这些版本的 Safari 中,尝试连接任何其他 AudioNode
到它将抛出 NotSupportedError
。
createChannelMerger() / ChannelMergerNode
这是 createChannelMerger()
工厂方法的实现。也可以使用 ChannelMergerNode
构造函数作为替代。
createChannelSplitter() / ChannelSplitterNode
这是 createChannelSplitter()
工厂方法的实现。也可以使用 ChannelSplitterNode
构造函数作为替代。
createConstantSource() / ConstantSourceNode
这是 createConstantSource()
工厂方法的实现。也可以使用 ConstantSourceNode
构造函数作为替代。
createConvolver() / ConvolverNode
这是 createConvolver()
工厂方法的实现。也可以使用 ConvolverNode
构造函数作为替代。
createDelay() / DelayNode
这是 createDelay()
工厂方法的实现。也可以使用 DelayNode
构造函数作为替代。
⚠️ 除 Firefox 外的所有浏览器中,delayTime AudioParam 无法设置为非常小的值。
createDynamicsCompressor() / DynamicsCompressorNode
这是 createDynamicsCompressor()
工厂方法的实现。也可以使用 DynamicsCompressorNode
构造函数作为替代。
createGain() / GainNode
这是 createGain()
工厂方法的实现。也可以使用 GainNode
构造函数作为替代。
createIIRFilter() / IIRFilterNode
这是 createIIRFilter()
工厂方法的实现。也可以使用 IIRFilterNode
构造函数作为替代。
⚠️ 在 Safari 中,它必须在内部使用 ScriptProcessorNode 模拟,这意味着它的性能不如其他原生支持的浏览器。
createMediaElementSource() / MediaElementAudioSourceNode
这是 createMediaElementSource()
工厂方法的实现。也可以使用 MediaElementAudioSourceNode
构造函数作为替代。
它只适用于 AudioContext,不适用于 OfflineAudioContext。
createMediaStreamDestination() / MediaStreamAudioDestinationNode
这是 createMediaStreamDestination()
工厂方法的实现。也可以使用 MediaStreamAudioDestinationNode
构造函数作为替代。
它只适用于 AudioContext,不适用于 OfflineAudioContext。
createMediaStreamSource() / MediaStreamAudioSourceNode
这是 createMediaStreamSource()
工厂方法的实现。也可以使用 MediaStreamAudioSourceNode
构造函数作为替代。
它只适用于 AudioContext,不适用于 OfflineAudioContext。
⚠️ 如果 MediaStreamAudioSourceNode 断开连接约两秒钟,Safari 会输出静音。
createMediaStreamTrackSource() / MediaStreamTrackAudioSourceNode
这是 createMediaStreamTrackSource()
工厂方法的实现。也可以使用 MediaStreamTrackAudioSourceNode
构造函数作为替代。
它只适用于 AudioContext,不适用于 OfflineAudioContext。
createOscillator() / OscillatorNode
这是 createOscillator()
工厂方法的实现。也可以使用 OscillatorNode
构造函数作为替代。
createPanner() / PannerNode
这是 createPanner()
工厂方法的实现。也可以使用 PannerNode
构造函数作为替代。
createPeriodicWave() / PeriodicWave
这是 createPeriodicWave()
工厂方法的实现。也可以使用 PeriodicWave
构造函数作为替代。
createStereoPanner() / StereoPannerNode
这是 createStereoPanner()
工厂方法的实现。也可以使用 StereoPannerNode
构造函数作为替代。
除非 Safari 提供原生实现,否则 channelCountMode 只能是 'explicit'
。
createWaveShaper() / WaveShaperNode
这是 createWaveShaper()
工厂方法的实现。也可以使用 WaveShaperNode
构造函数作为替代。
decodeAudioData()
这是 decodeAudioData()
方法的实现。下面还描述了一个具有类似接口的独立方法。
decodeAudioData()
这是一个独立的包装器,可以以类似于同名实例方法的方式使用。最显著的区别是它需要一个使用此库创建的 (Offline)AudioContext 作为第一个参数。但它也可以处理原生的 (webkit)(Offline)AudioContext。另一个区别是它只返回一个 promise。它不会调用任何回调。
import { decodeAudioData } from 'standardized-audio-context';
// 假设你在 Safari 中运行这段代码
const nativeAudioContext = new webkitAudioContextContext();
const response = await fetch('/a-super-cool-audio-file');
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await decodeAudioData(nativeAudioContext, arrayBuffer);
isAnyAudioContext()
这是一个实用函数,用于判断给定值是否为 AudioContext。它不区分由 standardized-audio-context
创建的 AudioContext 还是原生的 AudioContext。但对于 OfflineAudioContext,它将返回 false。
import { AudioContext, isAnyAudioContext } from 'standardized-audio-context';
// 这将创建一个来自 standardized-audio-context 的 AudioContext
const audioContext = new AudioContext();
isAnyAudioContext(audioContext); // true
// 这将创建一个原生的 AudioContext
const nativeAudioContext = new window.AudioContext();
isAnyAudioContext(nativeAudioContext); // true
isAnyAudioNode()
这是一个辅助函数,允许在不进行自定义 instanceof 或属性检查的情况下识别 AudioNode。如果给定值是 AudioNode,则返回 true,否则返回 false。无论给定值是否是使用 standardized-audio-context
创建的 AudioNode 都无关紧要。
import { OfflineAudioContext, isAnyAudioNode } from 'standardized-audio-context';
// 这将创建一个原生的 AudioContext
const nativeAudioContext = new AudioContext();
isAnyAudioNode(nativeAudioContext.createGain()); // true
// 这将创建一个来自 standardized-audio-context 的 OfflineAudioContext
const offlineAudioContext = new OfflineAudioContext({ length: 10, sampleRate: 44100 });
isAnyAudioNode(offlineAudioContext.createGain()); // true
isAnyAudioParam()
这是一个类似于 isAnyAudioNode()
的辅助函数,但用于 AudioParam。如果给定值是 AudioParam,则返回 true,否则返回 false。无论给定值是否是使用 standardized-audio-context
创建的 AudioParam 都无关紧要。
import { OfflineAudioContext, isAnyAudioParam } from 'standardized-audio-context';
// 这将创建一个原生的 AudioContext
const nativeAudioContext = new AudioContext();
isAnyAudioParam(nativeAudioContext.createGain().gain); // true
// 这将创建一个来自 standardized-audio-context 的 OfflineAudioContext。 const offlineAudioContext = new OfflineAudioContext({ length: 10, sampleRate: 44100 });
isAnyAudioParam(offlineAudioContext.createGain().gain); // true
### isAnyOfflineAudioContext()
这是一个实用函数,用于判断给定值是否为 OfflineAudioContext。它不区分由 `standardized-audio-context` 创建的 OfflineAudioContext 和原生的 OfflineAudioContext。
```js
import { OfflineAudioContext, isAnyOfflineAudioContext } from 'standardized-audio-context';
// 这将创建一个来自 standardized-audio-context 的 OfflineAudioContext。
const offlineAudioContext = new OfflineAudioContext({ length: 10, sampleRate: 44100 });
isAnyOfflineAudioContext(offlineAudioContext); // true
// 这将创建一个原生的 OfflineAudioContext。
const nativeOfflineAudioContext = new window.OfflineAudioContext(1, 10, 44100);
isAnyOfflineAudioContext(nativeOfflineAudioContext); // true
isSupported()
standardized-audio-context
还导出了一个可通过调用 isSupported()
访问的 Promise。这个 Promise 解析为一个布尔值,表示当前使用的浏览器是否支持该功能。这不是规范的一部分。
import { isSupported } from 'standardized-audio-context';
isSupported().then((isSupported) => {
if (isSupported) {
// 太好了,一切都应该能正常工作
} else {
// 哦不,这个浏览器似乎已经过时了
}
});
浏览器支持
这个包的目标是提供一个一致的 API。但同时,这个包不应无限增长直到在 IE 6 中一切都能正常工作。当每个受支持的浏览器都实现了之前需要 polyfill 才能正常工作的功能时,该 polyfill 就会从这个包中移除。希望在未来某个时候,这个包可以简化为一个只重新导出内置对象的文件。
但在那之前,我们会非常小心地避免任何不必要的膨胀。这意味着每当为某个特定浏览器添加一个解决方案时,都会附带一个测试来检查是否仍然需要这个特定的解决方案。我称这些测试为期望测试,因为它们测试浏览器是否按预期行为。期望测试的设计目的是在浏览器最终修复问题时失败。一旦发生这种情况,解决方案和相应的期望测试就会被移除。然而,期望测试会被重新利用,作为调用 isSupported()
时执行的浏览器检查的一部分。
当前支持的浏览器列表包括 Chrome v81+、Edge v81+、Firefox v70+ 和 Safari v12.1+。请注意,测试仅在每个浏览器的当前版本和即将发布的版本中运行。
支持一个浏览器仅意味着在功能层面上支持它。完全有可能需要像 Babel 这样的转译器来在每个受支持的浏览器中使用这个包而不会遇到任何语法错误。
这个包不适用于 Node.js。
TypeScript
这个包是用 TypeScript 编写的,这意味着它可以无缝地用于任何 TypeScript 项目。但这完全是可选的。
与 TypeScript 开箱即用提供的 Web Audio API 类型相比,standardized-audio-context
导出的类型实际上与具体实现相匹配。TypeScript 从 Web Audio API 的 Web IDL 定义生成其类型,而这并不总是与实际可用的实现相匹配。
测试
所有实现的方法都由大量测试覆盖,这些测试在上述所有浏览器中运行。非常感谢 BrowserStack 和 Sauce Labs 允许使用他们的服务测试这个模块。
安全联系信息
要报告安全漏洞,请使用 Tidelift 安全联系方式。Tidelift 将协调修复和披露。