官方文档

BaseAudioContext.createOscillator() - Web API | MDN

🧠 什么是 AudioContext?

AudioContext 是 Web Audio API 的核心接口,它提供了一个音频处理的上下文环境,允许你创建、处理和播放音频。

创建方式非常简单:

1
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();

兼容 Safari 的老写法也要带上。

🎛 核心概念

在 AudioContext 中,音频通过一个音频图(Audio Graph)进行处理。每个节点(AudioNode)都可以接收、处理或输出声音。

流程大概是这样的:

image-20250417003045575

🔧 常用 AudioNode 和 API

1. 创建音频源

(1) 播放音频文件

1
2
3
4
5
6
7
8
9
fetch('your-audio-file.mp3')
.then(res => res.arrayBuffer())
.then(arrayBuffer => audioCtx.decodeAudioData(arrayBuffer))
.then(audioBuffer => {
const source = audioCtx.createBufferSource();
source.buffer = audioBuffer;
source.connect(audioCtx.destination);
source.start();
});

(2) 生成声音(OscillatorNode)

1
2
3
4
5
6
const oscillator = audioCtx.createOscillator();
oscillator.type = 'sine'; // 类型:sine, square, sawtooth, triangle
oscillator.frequency.setValueAtTime(440, audioCtx.currentTime); // A4音
oscillator.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + 2); // 播放2秒

2. 音量控制(GainNode)

1
2
3
4
const gainNode = audioCtx.createGain();
gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime); // 设置音量

source.connect(gainNode).connect(audioCtx.destination); // source 是音频源

3. 创建音频分析器(AnalyserNode)

如果你要做音频可视化,比如音波动画,就需要用它:

1
2
3
4
5
6
7
const analyser = audioCtx.createAnalyser();
source.connect(analyser);
analyser.connect(audioCtx.destination);

// 获取频率数据
const dataArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(dataArray);

4. 音效处理(BiquadFilterNode)

可以加滤波效果,比如低通滤波器:

1
2
3
4
5
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.setValueAtTime(1000, audioCtx.currentTime);

source.connect(filter).connect(audioCtx.destination);

5. 控制播放时间

可以用 start(when)stop(when) 控制播放时间:

1
2
source.start(audioCtx.currentTime + 1); // 1秒后开始
source.stop(audioCtx.currentTime + 5); // 5秒后停止

⚠️ 注意事项

  1. AudioContext 默认是暂停状态:用户必须有操作(如点击按钮)后才能播放。

    1
    2
    3
    document.querySelector('button').addEventListener('click', () => {
    audioCtx.resume();
    });
  2. 资源释放:播放完后最好 stop() 并断开连接,释放资源。

  3. 跨域音频注意 CORS:加载外部音频文件时要保证服务端设置了 CORS 头。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
const context = new AudioContext();

const osc = context.createOscillator();
osc.frequency.value = 220;
osc.type = "square";
osc.start();

const volume = context.createGain();
volume.gain.value = 0.5;

const out = context.destination;

const nodes = new Map();

nodes.set("a", osc);
nodes.set("b", volume);
nodes.set("c", out);

/**
* @description context.state是否在运行
* @returns {boolean} 是否运行
*/
export function inRunning() {
return context.state === "running";
}

/**
* @description 在音频正在运行时暂停它,不在运行时恢复它如果在运行,
*/
export function toggleAudio() {
return inRunning() ? context.suspend() : context.resume();
}

/**
* @description 传入id和data更新对应的Node参数
* @param {string} id
* @param {Record<string, any>} data
*/
export function updateAudioNode(id: string, data: Record<string, any>) {
const node = nodes.get(id);

for (const [key, val] of Object.entries(data)) {
if (node[key] instanceof AudioParam) {
node[key].value = val;
} else {
node[key] = val;
}
}
}

/**
* @description 传入id,断开节点连接,暂停节点状态,删除节点
* @param {string} id
*/
export function removeAudioNode(id: string) {
const node = nodes.get(id);

node.disconnect();
node.stop?.();
nodes.delete(id);
}

/**
* @description 传入源节点和目标节点的id,将他们连接起来
* @param {string} sourceId
* @param {string} targetId
*/
export function connect(sourceId: string, targetId: string) {
const source = nodes.get(sourceId);
const target = nodes.get(targetId);

source.connect(target);
}

/**
* @description 传入源节点和目标节点的id,将他们的联系断开
* @param {string} sourceId
* @param {string} targetId
*/
export function disconnect(sourceId: string, targetId: string) {
const source = nodes.get(sourceId);
const target = nodes.get(targetId);

source.disconnect(target);
}

/**
* @description 传入类型,根据类型生成对应的AudioNode
* @param {string} id
* @param {string} type
* @param {Record<string, any>} data
*/
export function createAudioNode(
id: string,
type: string,
data: Record<string, any>
) {
switch (type) {
case "osc": {
const node = context.createOscillator();
node.frequency.value = data.frequency;
node.type = data.type;
node.start();

nodes.set(id, node);
break;
}

case "volume": {
const node = context.createGain();
node.gain.value = data.gain;

nodes.set(id, node);
break;
}
}
}