@94ai/nf-audio 8.3.16 → 8.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/nf-audio.cjs.js +62 -23
- package/lib/nf-audio.esm-bundler.js +62 -23
- package/package/audio-safari.html +146 -0
- package/package/nf-audio.vue +63 -23
- package/package.json +4 -4
- /package/package/{audio.html → audio-google.html} +0 -0
package/lib/nf-audio.cjs.js
CHANGED
|
@@ -121,7 +121,61 @@ const _sfc_main = {
|
|
|
121
121
|
},
|
|
122
122
|
data() {
|
|
123
123
|
return {
|
|
124
|
-
audioWorkletProcessor: `
|
|
124
|
+
audioWorkletProcessor: /Safari/i.test(navigator.userAgent) ? `
|
|
125
|
+
class TickWorkletProcessor extends AudioWorkletProcessor {
|
|
126
|
+
constructor() {
|
|
127
|
+
super();
|
|
128
|
+
this.port.onmessage = this.handleMessage.bind(this);
|
|
129
|
+
this.end = true;
|
|
130
|
+
this.sampleCount = 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
handleMessage(event) {
|
|
134
|
+
const { command, start, end } = event.data;
|
|
135
|
+
if (command === 'start') {
|
|
136
|
+
this.end = false;
|
|
137
|
+
} else if (command === 'end') {
|
|
138
|
+
this.end = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
process(inputs, outputs) {
|
|
143
|
+
// 1. 发送心跳消息(让主线程知道我还活着)
|
|
144
|
+
if (!this.end) {
|
|
145
|
+
this.port.postMessage({ command: 'tick' });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const input = inputs[0];
|
|
149
|
+
const output = outputs[0];
|
|
150
|
+
|
|
151
|
+
for (let channel = 0; channel < output.length; ++channel) {
|
|
152
|
+
const outputChannel = output[channel];
|
|
153
|
+
|
|
154
|
+
// 2. 【Safari 修复核心】
|
|
155
|
+
// 即使没有输入,也要产生极其微弱的“静音噪声”
|
|
156
|
+
// 这会强制 Safari 保持 AudioWorklet 的渲染管线开启
|
|
157
|
+
// 如果不加这个,Safari 可能会在几帧后停止调用 process
|
|
158
|
+
for (let i = 0; i < outputChannel.length; ++i) {
|
|
159
|
+
// 生成 -100dB 到 -120dB 左右的随机噪声,人耳听不见
|
|
160
|
+
// 但足以让 Safari 认为音频流是“活跃”的
|
|
161
|
+
const noise = (Math.random() * 2 - 1) * 0.00001;
|
|
162
|
+
|
|
163
|
+
// 如果有输入信号,则混合输入;如果没有输入(静音),则只输出噪声
|
|
164
|
+
if (input.length > 0 && input[channel]) {
|
|
165
|
+
outputChannel[i] = input[channel][i] + noise;
|
|
166
|
+
} else {
|
|
167
|
+
outputChannel[i] = noise;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
this.sampleCount += output[0].length;
|
|
173
|
+
// 3. 始终返回 true,保持节点存活
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
registerProcessor('tick', TickWorkletProcessor);
|
|
178
|
+
` : `
|
|
125
179
|
class TickWorkletProcessor extends AudioWorkletProcessor {
|
|
126
180
|
constructor() {
|
|
127
181
|
super();
|
|
@@ -296,33 +350,18 @@ const _sfc_main = {
|
|
|
296
350
|
return window.location.protocol === "https:" || window.location.hostname === "localhost";
|
|
297
351
|
},
|
|
298
352
|
async startAudioCtx() {
|
|
299
|
-
var _a, _b
|
|
353
|
+
var _a, _b;
|
|
300
354
|
if (this.isHighPrecision()) {
|
|
301
355
|
try {
|
|
302
356
|
if (!this.audioCtx) {
|
|
303
|
-
|
|
304
|
-
this.audioCtx = new AudioCtx();
|
|
357
|
+
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
305
358
|
if (this.audioCtx.state === "suspended") {
|
|
306
359
|
await this.audioCtx.resume();
|
|
307
360
|
}
|
|
361
|
+
await ((_a = this.audioCtx.audioWorklet) == null ? void 0 : _a.addModule("data:application/javascript," + encodeURIComponent(this.audioWorkletProcessor)));
|
|
362
|
+
this.processor = new AudioWorkletNode(this.audioCtx, "tick");
|
|
308
363
|
this.audioSource = this.audioCtx.createMediaElementSource(this.audio);
|
|
309
|
-
|
|
310
|
-
type: "application/javascript"
|
|
311
|
-
});
|
|
312
|
-
const workletUrl = URL.createObjectURL(blob);
|
|
313
|
-
try {
|
|
314
|
-
await ((_a = this.audioCtx.audioWorklet) == null ? void 0 : _a.addModule(workletUrl));
|
|
315
|
-
this.processor = new AudioWorkletNode(this.audioCtx, "tick");
|
|
316
|
-
this.audioSource.connect(this.processor).connect(this.audioCtx.destination);
|
|
317
|
-
} catch (workletErr) {
|
|
318
|
-
if (this.debuglog) {
|
|
319
|
-
console.warn("AudioWorklet unavailable, falling back to direct output:", workletErr);
|
|
320
|
-
}
|
|
321
|
-
this.audioSource.connect(this.audioCtx.destination);
|
|
322
|
-
this.processor = null;
|
|
323
|
-
} finally {
|
|
324
|
-
URL.revokeObjectURL(workletUrl);
|
|
325
|
-
}
|
|
364
|
+
this.audioSource.connect(this.processor).connect(this.audioCtx.destination);
|
|
326
365
|
if ((_b = this.processor) == null ? void 0 : _b.port) {
|
|
327
366
|
this.processor.port.onmessage = (event) => {
|
|
328
367
|
if (event.data.command === "tick") {
|
|
@@ -340,12 +379,12 @@ const _sfc_main = {
|
|
|
340
379
|
};
|
|
341
380
|
}
|
|
342
381
|
} else {
|
|
343
|
-
|
|
382
|
+
this.audioCtx.resume();
|
|
344
383
|
}
|
|
345
384
|
} catch (e) {
|
|
346
385
|
console.log(e);
|
|
347
386
|
}
|
|
348
|
-
|
|
387
|
+
this.processor.port.postMessage({
|
|
349
388
|
command: "start"
|
|
350
389
|
});
|
|
351
390
|
}
|
|
@@ -119,7 +119,61 @@ const _sfc_main = {
|
|
|
119
119
|
},
|
|
120
120
|
data() {
|
|
121
121
|
return {
|
|
122
|
-
audioWorkletProcessor: `
|
|
122
|
+
audioWorkletProcessor: /Safari/i.test(navigator.userAgent) ? `
|
|
123
|
+
class TickWorkletProcessor extends AudioWorkletProcessor {
|
|
124
|
+
constructor() {
|
|
125
|
+
super();
|
|
126
|
+
this.port.onmessage = this.handleMessage.bind(this);
|
|
127
|
+
this.end = true;
|
|
128
|
+
this.sampleCount = 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
handleMessage(event) {
|
|
132
|
+
const { command, start, end } = event.data;
|
|
133
|
+
if (command === 'start') {
|
|
134
|
+
this.end = false;
|
|
135
|
+
} else if (command === 'end') {
|
|
136
|
+
this.end = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
process(inputs, outputs) {
|
|
141
|
+
// 1. 发送心跳消息(让主线程知道我还活着)
|
|
142
|
+
if (!this.end) {
|
|
143
|
+
this.port.postMessage({ command: 'tick' });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const input = inputs[0];
|
|
147
|
+
const output = outputs[0];
|
|
148
|
+
|
|
149
|
+
for (let channel = 0; channel < output.length; ++channel) {
|
|
150
|
+
const outputChannel = output[channel];
|
|
151
|
+
|
|
152
|
+
// 2. 【Safari 修复核心】
|
|
153
|
+
// 即使没有输入,也要产生极其微弱的“静音噪声”
|
|
154
|
+
// 这会强制 Safari 保持 AudioWorklet 的渲染管线开启
|
|
155
|
+
// 如果不加这个,Safari 可能会在几帧后停止调用 process
|
|
156
|
+
for (let i = 0; i < outputChannel.length; ++i) {
|
|
157
|
+
// 生成 -100dB 到 -120dB 左右的随机噪声,人耳听不见
|
|
158
|
+
// 但足以让 Safari 认为音频流是“活跃”的
|
|
159
|
+
const noise = (Math.random() * 2 - 1) * 0.00001;
|
|
160
|
+
|
|
161
|
+
// 如果有输入信号,则混合输入;如果没有输入(静音),则只输出噪声
|
|
162
|
+
if (input.length > 0 && input[channel]) {
|
|
163
|
+
outputChannel[i] = input[channel][i] + noise;
|
|
164
|
+
} else {
|
|
165
|
+
outputChannel[i] = noise;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.sampleCount += output[0].length;
|
|
171
|
+
// 3. 始终返回 true,保持节点存活
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
registerProcessor('tick', TickWorkletProcessor);
|
|
176
|
+
` : `
|
|
123
177
|
class TickWorkletProcessor extends AudioWorkletProcessor {
|
|
124
178
|
constructor() {
|
|
125
179
|
super();
|
|
@@ -294,33 +348,18 @@ const _sfc_main = {
|
|
|
294
348
|
return window.location.protocol === "https:" || window.location.hostname === "localhost";
|
|
295
349
|
},
|
|
296
350
|
async startAudioCtx() {
|
|
297
|
-
var _a, _b
|
|
351
|
+
var _a, _b;
|
|
298
352
|
if (this.isHighPrecision()) {
|
|
299
353
|
try {
|
|
300
354
|
if (!this.audioCtx) {
|
|
301
|
-
|
|
302
|
-
this.audioCtx = new AudioCtx();
|
|
355
|
+
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
303
356
|
if (this.audioCtx.state === "suspended") {
|
|
304
357
|
await this.audioCtx.resume();
|
|
305
358
|
}
|
|
359
|
+
await ((_a = this.audioCtx.audioWorklet) == null ? void 0 : _a.addModule("data:application/javascript," + encodeURIComponent(this.audioWorkletProcessor)));
|
|
360
|
+
this.processor = new AudioWorkletNode(this.audioCtx, "tick");
|
|
306
361
|
this.audioSource = this.audioCtx.createMediaElementSource(this.audio);
|
|
307
|
-
|
|
308
|
-
type: "application/javascript"
|
|
309
|
-
});
|
|
310
|
-
const workletUrl = URL.createObjectURL(blob);
|
|
311
|
-
try {
|
|
312
|
-
await ((_a = this.audioCtx.audioWorklet) == null ? void 0 : _a.addModule(workletUrl));
|
|
313
|
-
this.processor = new AudioWorkletNode(this.audioCtx, "tick");
|
|
314
|
-
this.audioSource.connect(this.processor).connect(this.audioCtx.destination);
|
|
315
|
-
} catch (workletErr) {
|
|
316
|
-
if (this.debuglog) {
|
|
317
|
-
console.warn("AudioWorklet unavailable, falling back to direct output:", workletErr);
|
|
318
|
-
}
|
|
319
|
-
this.audioSource.connect(this.audioCtx.destination);
|
|
320
|
-
this.processor = null;
|
|
321
|
-
} finally {
|
|
322
|
-
URL.revokeObjectURL(workletUrl);
|
|
323
|
-
}
|
|
362
|
+
this.audioSource.connect(this.processor).connect(this.audioCtx.destination);
|
|
324
363
|
if ((_b = this.processor) == null ? void 0 : _b.port) {
|
|
325
364
|
this.processor.port.onmessage = (event) => {
|
|
326
365
|
if (event.data.command === "tick") {
|
|
@@ -338,12 +377,12 @@ const _sfc_main = {
|
|
|
338
377
|
};
|
|
339
378
|
}
|
|
340
379
|
} else {
|
|
341
|
-
|
|
380
|
+
this.audioCtx.resume();
|
|
342
381
|
}
|
|
343
382
|
} catch (e) {
|
|
344
383
|
console.log(e);
|
|
345
384
|
}
|
|
346
|
-
|
|
385
|
+
this.processor.port.postMessage({
|
|
347
386
|
command: "start"
|
|
348
387
|
});
|
|
349
388
|
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Play Audio in Specified Range with AudioWorklet</title>
|
|
5
|
+
</head>
|
|
6
|
+
<body>
|
|
7
|
+
<div style="display: flex;align-items: center">
|
|
8
|
+
<audio id="myAudio" src="./20170206081849_A100187_34f7e6b8-thumb.mp3" controls></audio>
|
|
9
|
+
<button onclick="playAudioInSpecifiedRange(1, 12)">Play Audio in Range 1-20 seconds</button>
|
|
10
|
+
<button onclick="endAudioInSpecifiedRange()">End Audio</button>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<script>
|
|
14
|
+
let playAudioInSpecifiedRange
|
|
15
|
+
let endAudioInSpecifiedRange
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
addEventListener("DOMContentLoaded", async (event) => {
|
|
19
|
+
const audioWorkletProcessor = `
|
|
20
|
+
class TickWorkletProcessor extends AudioWorkletProcessor {
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
this.port.onmessage = this.handleMessage.bind(this);
|
|
24
|
+
this.end = true;
|
|
25
|
+
this.sampleCount = 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
handleMessage(event) {
|
|
29
|
+
const { command, start, end } = event.data;
|
|
30
|
+
if (command === 'start') {
|
|
31
|
+
this.end = false;
|
|
32
|
+
} else if (command === 'end') {
|
|
33
|
+
this.end = true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process(inputs, outputs) {
|
|
38
|
+
// 1. 发送心跳消息(让主线程知道我还活着)
|
|
39
|
+
this.port.postMessage({ command: 'tick' });
|
|
40
|
+
|
|
41
|
+
const input = inputs[0];
|
|
42
|
+
const output = outputs[0];
|
|
43
|
+
|
|
44
|
+
for (let channel = 0; channel < output.length; ++channel) {
|
|
45
|
+
const outputChannel = output[channel];
|
|
46
|
+
|
|
47
|
+
// 2. 【Safari 修复核心】
|
|
48
|
+
// 即使没有输入,也要产生极其微弱的“静音噪声”
|
|
49
|
+
// 这会强制 Safari 保持 AudioWorklet 的渲染管线开启
|
|
50
|
+
// 如果不加这个,Safari 可能会在几帧后停止调用 process
|
|
51
|
+
for (let i = 0; i < outputChannel.length; ++i) {
|
|
52
|
+
// 生成 -100dB 到 -120dB 左右的随机噪声,人耳听不见
|
|
53
|
+
// 但足以让 Safari 认为音频流是“活跃”的
|
|
54
|
+
const noise = (Math.random() * 2 - 1) * 0.00001;
|
|
55
|
+
|
|
56
|
+
// 如果有输入信号,则混合输入;如果没有输入(静音),则只输出噪声
|
|
57
|
+
if (input.length > 0 && input[channel]) {
|
|
58
|
+
outputChannel[i] = input[channel][i] + noise;
|
|
59
|
+
} else {
|
|
60
|
+
outputChannel[i] = noise;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this.sampleCount += output[0].length;
|
|
66
|
+
// 3. 始终返回 true,保持节点存活
|
|
67
|
+
return !this.end;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
registerProcessor('tick', TickWorkletProcessor);
|
|
71
|
+
`
|
|
72
|
+
|
|
73
|
+
let ctx
|
|
74
|
+
let processor
|
|
75
|
+
let source
|
|
76
|
+
|
|
77
|
+
endAudioInSpecifiedRange = async () => {
|
|
78
|
+
myAudio.pause()
|
|
79
|
+
if (processor) {
|
|
80
|
+
processor.port.postMessage({ command: 'end' });
|
|
81
|
+
}
|
|
82
|
+
if (ctx && ctx.state !== 'closed') {
|
|
83
|
+
// Safari 中 suspend 有时会导致状态异常,视情况使用
|
|
84
|
+
// ctx.suspend()
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
playAudioInSpecifiedRange = async (start, end) => {
|
|
89
|
+
// 1. 初始化 AudioContext (包含之前的 Safari 唤醒修复)
|
|
90
|
+
if (!ctx) {
|
|
91
|
+
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
92
|
+
ctx = new AudioContext();
|
|
93
|
+
|
|
94
|
+
// 必须在用户交互栈中立即 resume
|
|
95
|
+
if (ctx.state === 'suspended') {
|
|
96
|
+
await ctx.resume();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
await ctx.audioWorklet.addModule('data:application/javascript,' + encodeURIComponent(audioWorkletProcessor));
|
|
100
|
+
|
|
101
|
+
processor = new AudioWorkletNode(ctx, 'tick');
|
|
102
|
+
// 设置 bufferSize 为最小值,减少延迟,提高 process 调用频率
|
|
103
|
+
processor.port.onmessage = (event) => {
|
|
104
|
+
if (event.data.command === 'tick') {
|
|
105
|
+
|
|
106
|
+
// 检测是否超出范围
|
|
107
|
+
if (myAudio.currentTime > end) {
|
|
108
|
+
endAudioInSpecifiedRange();
|
|
109
|
+
}
|
|
110
|
+
// 调试用:每隔一段时间打印,避免刷屏
|
|
111
|
+
console.log(myAudio.currentTime);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
source = ctx.createMediaElementSource(myAudio);
|
|
116
|
+
source.connect(processor).connect(ctx.destination);
|
|
117
|
+
} else {
|
|
118
|
+
if (ctx.state === 'suspended') {
|
|
119
|
+
await ctx.resume();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// 发送 start 命令给 Worklet
|
|
123
|
+
processor.port.postMessage({ command: 'start' });
|
|
124
|
+
// 2. 重置状态并开始
|
|
125
|
+
myAudio.currentTime = start;
|
|
126
|
+
// 3. 播放音频
|
|
127
|
+
await myAudio.play();
|
|
128
|
+
|
|
129
|
+
// // 4. 【额外保险】使用 requestAnimationFrame 监控时间
|
|
130
|
+
// // 既然 process 在 Safari 可能不可靠,我们用 RAF 作为兜底来检测结束时间
|
|
131
|
+
// const monitorLoop = () => {
|
|
132
|
+
// if (myAudio.paused || myAudio.ended) return;
|
|
133
|
+
//
|
|
134
|
+
// if (myAudio.currentTime > end) {
|
|
135
|
+
// endAudioInSpecifiedRange();
|
|
136
|
+
// } else {
|
|
137
|
+
// requestAnimationFrame(monitorLoop);
|
|
138
|
+
// }
|
|
139
|
+
// };
|
|
140
|
+
// requestAnimationFrame(monitorLoop);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
</body>
|
|
146
|
+
</html>
|
package/package/nf-audio.vue
CHANGED
|
@@ -299,7 +299,61 @@ export default {
|
|
|
299
299
|
},
|
|
300
300
|
data() {
|
|
301
301
|
return {
|
|
302
|
-
audioWorkletProcessor: `
|
|
302
|
+
audioWorkletProcessor: /Safari/i.test(navigator.userAgent) ? `
|
|
303
|
+
class TickWorkletProcessor extends AudioWorkletProcessor {
|
|
304
|
+
constructor() {
|
|
305
|
+
super();
|
|
306
|
+
this.port.onmessage = this.handleMessage.bind(this);
|
|
307
|
+
this.end = true;
|
|
308
|
+
this.sampleCount = 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
handleMessage(event) {
|
|
312
|
+
const { command, start, end } = event.data;
|
|
313
|
+
if (command === 'start') {
|
|
314
|
+
this.end = false;
|
|
315
|
+
} else if (command === 'end') {
|
|
316
|
+
this.end = true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
process(inputs, outputs) {
|
|
321
|
+
// 1. 发送心跳消息(让主线程知道我还活着)
|
|
322
|
+
if (!this.end) {
|
|
323
|
+
this.port.postMessage({ command: 'tick' });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const input = inputs[0];
|
|
327
|
+
const output = outputs[0];
|
|
328
|
+
|
|
329
|
+
for (let channel = 0; channel < output.length; ++channel) {
|
|
330
|
+
const outputChannel = output[channel];
|
|
331
|
+
|
|
332
|
+
// 2. 【Safari 修复核心】
|
|
333
|
+
// 即使没有输入,也要产生极其微弱的“静音噪声”
|
|
334
|
+
// 这会强制 Safari 保持 AudioWorklet 的渲染管线开启
|
|
335
|
+
// 如果不加这个,Safari 可能会在几帧后停止调用 process
|
|
336
|
+
for (let i = 0; i < outputChannel.length; ++i) {
|
|
337
|
+
// 生成 -100dB 到 -120dB 左右的随机噪声,人耳听不见
|
|
338
|
+
// 但足以让 Safari 认为音频流是“活跃”的
|
|
339
|
+
const noise = (Math.random() * 2 - 1) * 0.00001;
|
|
340
|
+
|
|
341
|
+
// 如果有输入信号,则混合输入;如果没有输入(静音),则只输出噪声
|
|
342
|
+
if (input.length > 0 && input[channel]) {
|
|
343
|
+
outputChannel[i] = input[channel][i] + noise;
|
|
344
|
+
} else {
|
|
345
|
+
outputChannel[i] = noise;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.sampleCount += output[0].length;
|
|
351
|
+
// 3. 始终返回 true,保持节点存活
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
registerProcessor('tick', TickWorkletProcessor);
|
|
356
|
+
` : `
|
|
303
357
|
class TickWorkletProcessor extends AudioWorkletProcessor {
|
|
304
358
|
constructor() {
|
|
305
359
|
super();
|
|
@@ -484,29 +538,15 @@ export default {
|
|
|
484
538
|
if (this.isHighPrecision()) {
|
|
485
539
|
try {
|
|
486
540
|
if (!this.audioCtx) {
|
|
487
|
-
|
|
488
|
-
|
|
541
|
+
this.audioCtx = new (window.AudioContext || window.webkitAudioContext)()
|
|
542
|
+
// 必须在用户交互栈中立即 resume
|
|
489
543
|
if (this.audioCtx.state === 'suspended') {
|
|
490
|
-
await this.audioCtx.resume()
|
|
544
|
+
await this.audioCtx.resume();
|
|
491
545
|
}
|
|
546
|
+
await this.audioCtx.audioWorklet?.addModule('data:application/javascript,' + encodeURIComponent(this.audioWorkletProcessor))
|
|
547
|
+
this.processor = new AudioWorkletNode(this.audioCtx, 'tick')
|
|
492
548
|
this.audioSource = this.audioCtx.createMediaElementSource(this.audio)
|
|
493
|
-
|
|
494
|
-
const blob = new Blob([this.audioWorkletProcessor], { type: 'application/javascript' })
|
|
495
|
-
const workletUrl = URL.createObjectURL(blob)
|
|
496
|
-
try {
|
|
497
|
-
await this.audioCtx.audioWorklet?.addModule(workletUrl)
|
|
498
|
-
this.processor = new AudioWorkletNode(this.audioCtx, 'tick')
|
|
499
|
-
this.audioSource.connect(this.processor).connect(this.audioCtx.destination)
|
|
500
|
-
} catch (workletErr) {
|
|
501
|
-
if (this.debuglog) {
|
|
502
|
-
console.warn('AudioWorklet unavailable, falling back to direct output:', workletErr)
|
|
503
|
-
}
|
|
504
|
-
// AudioWorklet 不可用时,直接连接到 destination,保证有声音
|
|
505
|
-
this.audioSource.connect(this.audioCtx.destination)
|
|
506
|
-
this.processor = null
|
|
507
|
-
} finally {
|
|
508
|
-
URL.revokeObjectURL(workletUrl)
|
|
509
|
-
}
|
|
549
|
+
this.audioSource.connect(this.processor).connect(this.audioCtx.destination)
|
|
510
550
|
if (this.processor?.port) {
|
|
511
551
|
this.processor.port.onmessage = (event) => {
|
|
512
552
|
if (event.data.command === 'tick') {
|
|
@@ -524,12 +564,12 @@ export default {
|
|
|
524
564
|
}
|
|
525
565
|
}
|
|
526
566
|
} else {
|
|
527
|
-
|
|
567
|
+
this.audioCtx.resume()
|
|
528
568
|
}
|
|
529
569
|
} catch (e) {
|
|
530
570
|
console.log(e)
|
|
531
571
|
}
|
|
532
|
-
this.processor
|
|
572
|
+
this.processor.port.postMessage({ command: 'start' })
|
|
533
573
|
}
|
|
534
574
|
},
|
|
535
575
|
pause() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@94ai/nf-audio",
|
|
3
|
-
"version": "8.3.
|
|
3
|
+
"version": "8.3.19",
|
|
4
4
|
"description": "> TODO: description",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "liuxiangxiang <liuxiangxiang@94ai.com>",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"url": "http://94ai.gitlab.com/zoujiahe/common-ui.git"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@94ai/nf-message": "^8.3.
|
|
18
|
-
"@94ai/nf-theme-chalk": "^8.3.
|
|
17
|
+
"@94ai/nf-message": "^8.3.19",
|
|
18
|
+
"@94ai/nf-theme-chalk": "^8.3.19",
|
|
19
19
|
"vue-demi": "^0.14.5"
|
|
20
20
|
},
|
|
21
21
|
"peerDependenciesMeta": {
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
"types": "lib/index.d.ts",
|
|
32
32
|
"main": "lib/nf-audio.cjs.js",
|
|
33
33
|
"module": "lib/nf-audio.esm-bundler.js",
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "ec206eadc7ca1a279e552ac22b51c29a5e71b326"
|
|
35
35
|
}
|
|
File without changes
|