@blockcast/mmt-render 0.1.0-main.dd5ce397d768 → 0.1.0-main.e6500013301e
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/dist/audio/audio-render-pipeline.js +217 -248
- package/dist/audio/audio-render-pipeline.js.map +2 -2
- package/dist/audio/audio-ring-buffer.js +93 -127
- package/dist/audio/audio-ring-buffer.js.map +1 -1
- package/dist/audio/index.js +313 -361
- package/dist/audio/index.js.map +3 -3
- package/dist/pacing/frame-pacer.d.ts +22 -1
- package/dist/pacing/frame-pacer.d.ts.map +1 -1
- package/dist/pacing/frame-pacer.js +43 -39
- package/dist/pacing/frame-pacer.js.map +2 -2
- package/dist/pacing/index.js +43 -39
- package/dist/pacing/index.js.map +2 -2
- package/dist/video/index.js +129 -139
- package/dist/video/index.js.map +2 -2
- package/dist/video/keyframe-gate.js +5 -17
- package/dist/video/keyframe-gate.js.map +1 -1
- package/dist/video/video-render-pipeline.js +129 -139
- package/dist/video/video-render-pipeline.js.map +2 -2
- package/dist/worklet/render-worklet.js +171 -219
- package/dist/worklet/render-worklet.js.map +2 -2
- package/package.json +3 -3
- package/src/pacing/frame-pacer.ts +28 -1
|
@@ -1,30 +1,12 @@
|
|
|
1
|
-
var __typeError = (msg) => {
|
|
2
|
-
throw TypeError(msg);
|
|
3
|
-
};
|
|
4
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
-
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
-
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
9
|
-
var __privateWrapper = (obj, member, setter, getter) => ({
|
|
10
|
-
set _(value) {
|
|
11
|
-
__privateSet(obj, member, value, setter);
|
|
12
|
-
},
|
|
13
|
-
get _() {
|
|
14
|
-
return __privateGet(obj, member, getter);
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
1
|
// src/audio/audio-render-pipeline.ts
|
|
19
2
|
import { MmtpDecodePipeline } from "@blockcast/mmt-container/mmtp-decode-pipeline";
|
|
20
3
|
|
|
21
4
|
// src/pacing/frame-pacer.ts
|
|
22
|
-
var _frameCount, _anchorWallMs, _anchored;
|
|
23
5
|
var FrameCountPacer = class {
|
|
6
|
+
#frameCount = 0;
|
|
7
|
+
#anchorWallMs = 0;
|
|
8
|
+
#anchored = false;
|
|
24
9
|
constructor(opts) {
|
|
25
|
-
__privateAdd(this, _frameCount, 0);
|
|
26
|
-
__privateAdd(this, _anchorWallMs, 0);
|
|
27
|
-
__privateAdd(this, _anchored, false);
|
|
28
10
|
if (!(opts.frameDurationMs > 0)) {
|
|
29
11
|
throw new Error("frameDurationMs must be > 0");
|
|
30
12
|
}
|
|
@@ -34,11 +16,11 @@ var FrameCountPacer = class {
|
|
|
34
16
|
}
|
|
35
17
|
/** Call on every emitted decoded frame (AU or picture). Anchors on first call. */
|
|
36
18
|
notifyFrame() {
|
|
37
|
-
if (!
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
if (!this.#anchored) {
|
|
20
|
+
this.#anchorWallMs = now();
|
|
21
|
+
this.#anchored = true;
|
|
40
22
|
}
|
|
41
|
-
|
|
23
|
+
this.#frameCount++;
|
|
42
24
|
}
|
|
43
25
|
/**
|
|
44
26
|
* Compute sleep ms before reading the next MoQ object.
|
|
@@ -46,41 +28,62 @@ var FrameCountPacer = class {
|
|
|
46
28
|
* - pacing is disabled (NullBlockMapper → threshold=0)
|
|
47
29
|
* - the anchor is not yet set
|
|
48
30
|
* - still in warmup
|
|
31
|
+
* - the caller reports a standing backlog (see `observedDelayMs`)
|
|
49
32
|
* - decoder is not ahead of wall clock by more than threshold
|
|
50
33
|
* Otherwise returns aheadMs - thresholdMs/2 (leaves half the threshold as headroom).
|
|
34
|
+
*
|
|
35
|
+
* `observedDelayMs` is how far this frame is behind the producer's own clock,
|
|
36
|
+
* measured by the caller against a producer-supplied sending instant (for
|
|
37
|
+
* MMTP, the header send-wall timestamp). It is optional because the anchored
|
|
38
|
+
* rate below is blind to it: `#anchorWallMs` is the wall time of the FIRST
|
|
39
|
+
* frame read, so `aheadMs` measures consumption against the join instant and
|
|
40
|
+
* not against the producer. Once `aheadMs` reaches the threshold the loop
|
|
41
|
+
* consumes at exactly the source rate, and recovering latency present at join
|
|
42
|
+
* requires consuming FASTER than the source rate. So without this argument a
|
|
43
|
+
* consumer that joined a live stream mid-block keeps that join latency for the
|
|
44
|
+
* life of the subscription.
|
|
45
|
+
*
|
|
46
|
+
* A caller that can measure the real distance from the producer passes it and
|
|
47
|
+
* gets a drain: while the backlog exceeds the threshold there is nothing to
|
|
48
|
+
* pace against, so return 0 and let the transport set the rate. At the live
|
|
49
|
+
* edge the anchored-rate behavior below applies unchanged.
|
|
50
|
+
*
|
|
51
|
+
* Callers with no producer clock omit it and keep the pure ATSC3-style
|
|
52
|
+
* anchored pacing (see the class doc), where a first-frame anchor is correct
|
|
53
|
+
* because a broadcast push has no live edge to catch up to.
|
|
51
54
|
*/
|
|
52
|
-
computeSleepMs() {
|
|
55
|
+
computeSleepMs(observedDelayMs) {
|
|
53
56
|
if (this.thresholdMs <= 0) return 0;
|
|
54
|
-
if (!
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if (!this.#anchored) return 0;
|
|
58
|
+
if (this.#frameCount <= this.warmupFrames) return 0;
|
|
59
|
+
if (observedDelayMs !== void 0 && Number.isFinite(observedDelayMs) && observedDelayMs > this.thresholdMs) {
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
const mediaMs = this.#frameCount * this.frameDurationMs;
|
|
63
|
+
const wallMs = now() - this.#anchorWallMs;
|
|
58
64
|
const aheadMs = mediaMs - wallMs;
|
|
59
65
|
if (aheadMs <= this.thresholdMs) return 0;
|
|
60
66
|
return aheadMs - this.thresholdMs / 2;
|
|
61
67
|
}
|
|
62
68
|
/** Reset anchor + counter. Call on source switch (MoQ ↔ multicast). */
|
|
63
69
|
reset() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
this.#frameCount = 0;
|
|
71
|
+
this.#anchorWallMs = 0;
|
|
72
|
+
this.#anchored = false;
|
|
67
73
|
}
|
|
68
74
|
get snapshot() {
|
|
69
|
-
const mediaMs =
|
|
70
|
-
const wallMs =
|
|
75
|
+
const mediaMs = this.#frameCount * this.frameDurationMs;
|
|
76
|
+
const wallMs = this.#anchored ? now() - this.#anchorWallMs : 0;
|
|
71
77
|
return {
|
|
72
|
-
frameCount:
|
|
78
|
+
frameCount: this.#frameCount,
|
|
73
79
|
mediaMs,
|
|
74
80
|
wallMs,
|
|
75
81
|
aheadMs: mediaMs - wallMs,
|
|
76
82
|
thresholdMs: this.thresholdMs,
|
|
77
|
-
anchored:
|
|
83
|
+
anchored: this.#anchored
|
|
78
84
|
};
|
|
79
85
|
}
|
|
80
86
|
};
|
|
81
|
-
_frameCount = new WeakMap();
|
|
82
|
-
_anchorWallMs = new WeakMap();
|
|
83
|
-
_anchored = new WeakMap();
|
|
84
87
|
function now() {
|
|
85
88
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
86
89
|
return performance.now();
|
|
@@ -111,260 +114,226 @@ function workletModuleUrl() {
|
|
|
111
114
|
var WORKLET_PROCESSOR_NAME = "mmt-render";
|
|
112
115
|
|
|
113
116
|
// src/audio/audio-render-pipeline.ts
|
|
114
|
-
var _context, _pacer, _mapper, _codec, _packetId, _sampleRate, _channels, _description, _latencyMs, _startupFrames, _startupThresholdFrames, _capacityFrames, _diag, _warmupFrames, _onState, _onDiag, _onError, _reassemblyPolicy, _decoder, _pipeline, _worklet, _fecClient, _closed, _readyPromise, _workletGeneration, _startupReleased, _AudioRenderPipeline_instances, initialize_fn, onDecoded_fn;
|
|
115
117
|
var AudioRenderPipeline = class {
|
|
118
|
+
#context;
|
|
119
|
+
#pacer;
|
|
120
|
+
#mapper;
|
|
121
|
+
#codec;
|
|
122
|
+
#packetId;
|
|
123
|
+
#sampleRate;
|
|
124
|
+
#channels;
|
|
125
|
+
#description;
|
|
126
|
+
#latencyMs;
|
|
127
|
+
#startupFrames;
|
|
128
|
+
#startupThresholdFrames;
|
|
129
|
+
#capacityFrames;
|
|
130
|
+
#diag;
|
|
131
|
+
#warmupFrames;
|
|
132
|
+
#onState;
|
|
133
|
+
#onDiag;
|
|
134
|
+
#onError;
|
|
135
|
+
#reassemblyPolicy;
|
|
136
|
+
#decoder = null;
|
|
137
|
+
#pipeline = null;
|
|
138
|
+
#worklet = null;
|
|
139
|
+
#fecClient;
|
|
140
|
+
#closed = false;
|
|
141
|
+
#readyPromise = null;
|
|
142
|
+
#workletGeneration = 0;
|
|
143
|
+
#startupReleased = false;
|
|
116
144
|
constructor(opts) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
__privateAdd(this, _reassemblyPolicy);
|
|
136
|
-
__privateAdd(this, _decoder, null);
|
|
137
|
-
__privateAdd(this, _pipeline, null);
|
|
138
|
-
__privateAdd(this, _worklet, null);
|
|
139
|
-
__privateAdd(this, _fecClient);
|
|
140
|
-
__privateAdd(this, _closed, false);
|
|
141
|
-
__privateAdd(this, _readyPromise, null);
|
|
142
|
-
__privateAdd(this, _workletGeneration, 0);
|
|
143
|
-
__privateAdd(this, _startupReleased, false);
|
|
144
|
-
__privateSet(this, _context, opts.context);
|
|
145
|
-
__privateSet(this, _mapper, opts.mapper);
|
|
146
|
-
__privateSet(this, _codec, opts.codec);
|
|
147
|
-
__privateSet(this, _packetId, opts.packetId);
|
|
148
|
-
__privateSet(this, _sampleRate, opts.sampleRate);
|
|
149
|
-
__privateSet(this, _channels, opts.channels);
|
|
150
|
-
__privateSet(this, _description, opts.description);
|
|
151
|
-
__privateSet(this, _latencyMs, opts.latencyMs);
|
|
152
|
-
__privateSet(this, _startupFrames, opts.startupFrames);
|
|
153
|
-
__privateSet(this, _startupThresholdFrames, opts.startupFrames ?? Math.ceil(opts.sampleRate * (opts.latencyMs / 1e3)));
|
|
154
|
-
__privateSet(this, _capacityFrames, opts.capacityFrames);
|
|
155
|
-
__privateSet(this, _diag, opts.diag ?? false);
|
|
156
|
-
__privateSet(this, _warmupFrames, opts.warmupFrames ?? 0);
|
|
157
|
-
__privateSet(this, _onState, opts.onState);
|
|
158
|
-
__privateSet(this, _onDiag, opts.onDiag);
|
|
159
|
-
__privateSet(this, _onError, opts.onError);
|
|
160
|
-
__privateSet(this, _fecClient, opts.fecClient);
|
|
161
|
-
__privateSet(this, _reassemblyPolicy, opts.reassemblyPolicy);
|
|
145
|
+
this.#context = opts.context;
|
|
146
|
+
this.#mapper = opts.mapper;
|
|
147
|
+
this.#codec = opts.codec;
|
|
148
|
+
this.#packetId = opts.packetId;
|
|
149
|
+
this.#sampleRate = opts.sampleRate;
|
|
150
|
+
this.#channels = opts.channels;
|
|
151
|
+
this.#description = opts.description;
|
|
152
|
+
this.#latencyMs = opts.latencyMs;
|
|
153
|
+
this.#startupFrames = opts.startupFrames;
|
|
154
|
+
this.#startupThresholdFrames = opts.startupFrames ?? Math.ceil(opts.sampleRate * (opts.latencyMs / 1e3));
|
|
155
|
+
this.#capacityFrames = opts.capacityFrames;
|
|
156
|
+
this.#diag = opts.diag ?? false;
|
|
157
|
+
this.#warmupFrames = opts.warmupFrames ?? 0;
|
|
158
|
+
this.#onState = opts.onState;
|
|
159
|
+
this.#onDiag = opts.onDiag;
|
|
160
|
+
this.#onError = opts.onError;
|
|
161
|
+
this.#fecClient = opts.fecClient;
|
|
162
|
+
this.#reassemblyPolicy = opts.reassemblyPolicy;
|
|
162
163
|
const auDurationMs = auDurationMsFromCodec(opts.codec, opts.sampleRate);
|
|
163
|
-
|
|
164
|
+
this.#pacer = new FrameCountPacer({
|
|
164
165
|
frameDurationMs: auDurationMs,
|
|
165
166
|
mapper: opts.mapper,
|
|
166
|
-
warmupFrames:
|
|
167
|
-
})
|
|
167
|
+
warmupFrames: this.#warmupFrames
|
|
168
|
+
});
|
|
168
169
|
}
|
|
169
170
|
/** Load worklet module + create decoder + pipeline. Call once. */
|
|
170
171
|
async ready() {
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
return
|
|
172
|
+
if (this.#readyPromise) return this.#readyPromise;
|
|
173
|
+
this.#readyPromise = this.#initialize();
|
|
174
|
+
return this.#readyPromise;
|
|
175
|
+
}
|
|
176
|
+
async #initialize() {
|
|
177
|
+
await this.#context.audioWorklet.addModule(workletModuleUrl());
|
|
178
|
+
const worklet = new AudioWorkletNode(this.#context, WORKLET_PROCESSOR_NAME, {
|
|
179
|
+
numberOfInputs: 0,
|
|
180
|
+
numberOfOutputs: 1,
|
|
181
|
+
outputChannelCount: [this.#channels]
|
|
182
|
+
});
|
|
183
|
+
const initMsg = {
|
|
184
|
+
type: "init",
|
|
185
|
+
generation: ++this.#workletGeneration,
|
|
186
|
+
channels: this.#channels,
|
|
187
|
+
rate: this.#sampleRate,
|
|
188
|
+
latencyMs: this.#latencyMs,
|
|
189
|
+
startupFrames: this.#startupFrames,
|
|
190
|
+
capacityFrames: this.#capacityFrames,
|
|
191
|
+
holdUntilStart: true,
|
|
192
|
+
diag: this.#diag
|
|
193
|
+
};
|
|
194
|
+
worklet.port.postMessage(initMsg);
|
|
195
|
+
worklet.port.onmessage = (ev) => {
|
|
196
|
+
const m = ev.data;
|
|
197
|
+
if (m.type === "state") {
|
|
198
|
+
if (m.generation !== this.#workletGeneration) return;
|
|
199
|
+
this.#onState?.({
|
|
200
|
+
timestampUs: m.timestamp,
|
|
201
|
+
stalled: m.stalled,
|
|
202
|
+
bufferedFrames: m.bufferedFrames,
|
|
203
|
+
writtenFrames: m.writtenFrames
|
|
204
|
+
});
|
|
205
|
+
} else if (m.type === "ringBufDiag") {
|
|
206
|
+
if (m.generation !== this.#workletGeneration) return;
|
|
207
|
+
this.#onDiag?.({
|
|
208
|
+
reads: m.reads,
|
|
209
|
+
avgFill: m.avgFill,
|
|
210
|
+
capacity: m.capacity,
|
|
211
|
+
skipsPerSec: m.skipsPerSec,
|
|
212
|
+
dupsPerSec: m.dupsPerSec,
|
|
213
|
+
overflowSamplesPerSec: m.overflowSamplesPerSec,
|
|
214
|
+
underflowSamplesPerSec: m.underflowSamplesPerSec
|
|
215
|
+
});
|
|
216
|
+
} else if (m.type === "writeAck") {
|
|
217
|
+
if (m.generation !== this.#workletGeneration || this.#startupReleased || m.bufferedFrames < this.#startupThresholdFrames) return;
|
|
218
|
+
this.#startupReleased = true;
|
|
219
|
+
const start = {
|
|
220
|
+
type: "start",
|
|
221
|
+
generation: this.#workletGeneration
|
|
222
|
+
};
|
|
223
|
+
worklet.port.postMessage(start);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
this.#worklet = worklet;
|
|
227
|
+
const decoder = new AudioDecoder({
|
|
228
|
+
output: (data) => this.#onDecoded(data),
|
|
229
|
+
error: (err) => this.#onError?.(err)
|
|
230
|
+
});
|
|
231
|
+
const config = {
|
|
232
|
+
codec: this.#codec,
|
|
233
|
+
sampleRate: this.#sampleRate,
|
|
234
|
+
numberOfChannels: this.#channels,
|
|
235
|
+
...this.#description ? { description: this.#description } : {}
|
|
236
|
+
};
|
|
237
|
+
decoder.configure(config);
|
|
238
|
+
this.#decoder = decoder;
|
|
239
|
+
const pipeline = new MmtpDecodePipeline();
|
|
240
|
+
pipeline.configure({
|
|
241
|
+
codec: this.#codec,
|
|
242
|
+
packetId: this.#packetId,
|
|
243
|
+
description: this.#description,
|
|
244
|
+
reorderWindow: 0,
|
|
245
|
+
// audio doesn't need reorder — AAC/Opus frames are self-contained
|
|
246
|
+
reassemblyPolicy: this.#reassemblyPolicy
|
|
247
|
+
});
|
|
248
|
+
if (this.#fecClient) pipeline.setFecClient(this.#fecClient);
|
|
249
|
+
pipeline.onFrame((codecData, meta) => {
|
|
250
|
+
if (!this.#decoder || this.#decoder.state !== "configured") return;
|
|
251
|
+
if (codecData.byteLength === 0) return;
|
|
252
|
+
this.#pacer.notifyFrame();
|
|
253
|
+
try {
|
|
254
|
+
this.#decoder.decode(new EncodedAudioChunk({
|
|
255
|
+
type: "key",
|
|
256
|
+
data: codecData,
|
|
257
|
+
timestamp: meta.timestamp
|
|
258
|
+
}));
|
|
259
|
+
} catch (err) {
|
|
260
|
+
this.#onError?.(err);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
this.#pipeline = pipeline;
|
|
174
264
|
}
|
|
175
265
|
/** Feed one MoQ object (raw MMTP packet). */
|
|
176
266
|
feedMoqObject(packet) {
|
|
177
|
-
|
|
267
|
+
this.#pipeline?.feedMoqObject(packet);
|
|
178
268
|
}
|
|
179
269
|
/** ms to sleep before the next object read. 0 when not anchored / not ahead. */
|
|
180
270
|
paceSleepMs() {
|
|
181
|
-
return
|
|
271
|
+
return this.#pacer.computeSleepMs();
|
|
182
272
|
}
|
|
183
273
|
/** Pacer snapshot (for diagnostics / UI). */
|
|
184
274
|
get pacerSnapshot() {
|
|
185
|
-
return
|
|
275
|
+
return this.#pacer.snapshot;
|
|
186
276
|
}
|
|
187
277
|
/** Reset pacer on source switch (MoQ ↔ multicast). */
|
|
188
278
|
resetPacer() {
|
|
189
|
-
|
|
279
|
+
this.#pacer.reset();
|
|
190
280
|
}
|
|
191
281
|
/** Drop queued PCM and its timestamp epoch at an explicit source boundary. */
|
|
192
282
|
resetPlayout(startUnstalled = false) {
|
|
193
|
-
|
|
194
|
-
|
|
283
|
+
this.#pacer.reset();
|
|
284
|
+
this.#startupReleased = true;
|
|
195
285
|
const message = {
|
|
196
286
|
type: "reset",
|
|
197
|
-
generation: ++
|
|
287
|
+
generation: ++this.#workletGeneration,
|
|
198
288
|
startUnstalled
|
|
199
289
|
};
|
|
200
|
-
|
|
290
|
+
this.#worklet?.port.postMessage(message);
|
|
201
291
|
}
|
|
202
292
|
/** Node to connect to destination / gain. */
|
|
203
293
|
get rootNode() {
|
|
204
|
-
if (!
|
|
205
|
-
return
|
|
294
|
+
if (!this.#worklet) throw new Error("AudioRenderPipeline.ready() must resolve first");
|
|
295
|
+
return this.#worklet;
|
|
206
296
|
}
|
|
207
297
|
close() {
|
|
208
|
-
if (
|
|
209
|
-
|
|
298
|
+
if (this.#closed) return;
|
|
299
|
+
this.#closed = true;
|
|
210
300
|
try {
|
|
211
|
-
|
|
301
|
+
this.#decoder?.close();
|
|
212
302
|
} catch {
|
|
213
303
|
}
|
|
214
304
|
try {
|
|
215
|
-
|
|
305
|
+
this.#pipeline?.dispose();
|
|
216
306
|
} catch {
|
|
217
307
|
}
|
|
218
308
|
try {
|
|
219
|
-
|
|
309
|
+
this.#worklet?.disconnect();
|
|
220
310
|
} catch {
|
|
221
311
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
312
|
+
this.#decoder = null;
|
|
313
|
+
this.#pipeline = null;
|
|
314
|
+
this.#worklet = null;
|
|
225
315
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
_packetId = new WeakMap();
|
|
232
|
-
_sampleRate = new WeakMap();
|
|
233
|
-
_channels = new WeakMap();
|
|
234
|
-
_description = new WeakMap();
|
|
235
|
-
_latencyMs = new WeakMap();
|
|
236
|
-
_startupFrames = new WeakMap();
|
|
237
|
-
_startupThresholdFrames = new WeakMap();
|
|
238
|
-
_capacityFrames = new WeakMap();
|
|
239
|
-
_diag = new WeakMap();
|
|
240
|
-
_warmupFrames = new WeakMap();
|
|
241
|
-
_onState = new WeakMap();
|
|
242
|
-
_onDiag = new WeakMap();
|
|
243
|
-
_onError = new WeakMap();
|
|
244
|
-
_reassemblyPolicy = new WeakMap();
|
|
245
|
-
_decoder = new WeakMap();
|
|
246
|
-
_pipeline = new WeakMap();
|
|
247
|
-
_worklet = new WeakMap();
|
|
248
|
-
_fecClient = new WeakMap();
|
|
249
|
-
_closed = new WeakMap();
|
|
250
|
-
_readyPromise = new WeakMap();
|
|
251
|
-
_workletGeneration = new WeakMap();
|
|
252
|
-
_startupReleased = new WeakMap();
|
|
253
|
-
_AudioRenderPipeline_instances = new WeakSet();
|
|
254
|
-
initialize_fn = async function() {
|
|
255
|
-
await __privateGet(this, _context).audioWorklet.addModule(workletModuleUrl());
|
|
256
|
-
const worklet = new AudioWorkletNode(__privateGet(this, _context), WORKLET_PROCESSOR_NAME, {
|
|
257
|
-
numberOfInputs: 0,
|
|
258
|
-
numberOfOutputs: 1,
|
|
259
|
-
outputChannelCount: [__privateGet(this, _channels)]
|
|
260
|
-
});
|
|
261
|
-
const initMsg = {
|
|
262
|
-
type: "init",
|
|
263
|
-
generation: ++__privateWrapper(this, _workletGeneration)._,
|
|
264
|
-
channels: __privateGet(this, _channels),
|
|
265
|
-
rate: __privateGet(this, _sampleRate),
|
|
266
|
-
latencyMs: __privateGet(this, _latencyMs),
|
|
267
|
-
startupFrames: __privateGet(this, _startupFrames),
|
|
268
|
-
capacityFrames: __privateGet(this, _capacityFrames),
|
|
269
|
-
holdUntilStart: true,
|
|
270
|
-
diag: __privateGet(this, _diag)
|
|
271
|
-
};
|
|
272
|
-
worklet.port.postMessage(initMsg);
|
|
273
|
-
worklet.port.onmessage = (ev) => {
|
|
274
|
-
var _a, _b;
|
|
275
|
-
const m = ev.data;
|
|
276
|
-
if (m.type === "state") {
|
|
277
|
-
if (m.generation !== __privateGet(this, _workletGeneration)) return;
|
|
278
|
-
(_a = __privateGet(this, _onState)) == null ? void 0 : _a.call(this, {
|
|
279
|
-
timestampUs: m.timestamp,
|
|
280
|
-
stalled: m.stalled,
|
|
281
|
-
bufferedFrames: m.bufferedFrames,
|
|
282
|
-
writtenFrames: m.writtenFrames
|
|
283
|
-
});
|
|
284
|
-
} else if (m.type === "ringBufDiag") {
|
|
285
|
-
if (m.generation !== __privateGet(this, _workletGeneration)) return;
|
|
286
|
-
(_b = __privateGet(this, _onDiag)) == null ? void 0 : _b.call(this, {
|
|
287
|
-
reads: m.reads,
|
|
288
|
-
avgFill: m.avgFill,
|
|
289
|
-
capacity: m.capacity,
|
|
290
|
-
skipsPerSec: m.skipsPerSec,
|
|
291
|
-
dupsPerSec: m.dupsPerSec,
|
|
292
|
-
overflowSamplesPerSec: m.overflowSamplesPerSec,
|
|
293
|
-
underflowSamplesPerSec: m.underflowSamplesPerSec
|
|
294
|
-
});
|
|
295
|
-
} else if (m.type === "writeAck") {
|
|
296
|
-
if (m.generation !== __privateGet(this, _workletGeneration) || __privateGet(this, _startupReleased) || m.bufferedFrames < __privateGet(this, _startupThresholdFrames)) return;
|
|
297
|
-
__privateSet(this, _startupReleased, true);
|
|
298
|
-
const start = {
|
|
299
|
-
type: "start",
|
|
300
|
-
generation: __privateGet(this, _workletGeneration)
|
|
301
|
-
};
|
|
302
|
-
worklet.port.postMessage(start);
|
|
316
|
+
// ─── Private ─────────────────────────────────────────────────────
|
|
317
|
+
#onDecoded(sample) {
|
|
318
|
+
if (this.#closed || !this.#worklet) {
|
|
319
|
+
sample.close();
|
|
320
|
+
return;
|
|
303
321
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
return (_a = __privateGet(this, _onError)) == null ? void 0 : _a.call(this, err);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
const config = {
|
|
314
|
-
codec: __privateGet(this, _codec),
|
|
315
|
-
sampleRate: __privateGet(this, _sampleRate),
|
|
316
|
-
numberOfChannels: __privateGet(this, _channels),
|
|
317
|
-
...__privateGet(this, _description) ? { description: __privateGet(this, _description) } : {}
|
|
318
|
-
};
|
|
319
|
-
decoder.configure(config);
|
|
320
|
-
__privateSet(this, _decoder, decoder);
|
|
321
|
-
const pipeline = new MmtpDecodePipeline();
|
|
322
|
-
pipeline.configure({
|
|
323
|
-
codec: __privateGet(this, _codec),
|
|
324
|
-
packetId: __privateGet(this, _packetId),
|
|
325
|
-
description: __privateGet(this, _description),
|
|
326
|
-
reorderWindow: 0,
|
|
327
|
-
// audio doesn't need reorder — AAC/Opus frames are self-contained
|
|
328
|
-
reassemblyPolicy: __privateGet(this, _reassemblyPolicy)
|
|
329
|
-
});
|
|
330
|
-
if (__privateGet(this, _fecClient)) pipeline.setFecClient(__privateGet(this, _fecClient));
|
|
331
|
-
pipeline.onFrame((codecData, meta) => {
|
|
332
|
-
var _a;
|
|
333
|
-
if (!__privateGet(this, _decoder) || __privateGet(this, _decoder).state !== "configured") return;
|
|
334
|
-
if (codecData.byteLength === 0) return;
|
|
335
|
-
__privateGet(this, _pacer).notifyFrame();
|
|
336
|
-
try {
|
|
337
|
-
__privateGet(this, _decoder).decode(new EncodedAudioChunk({
|
|
338
|
-
type: "key",
|
|
339
|
-
data: codecData,
|
|
340
|
-
timestamp: meta.timestamp
|
|
341
|
-
}));
|
|
342
|
-
} catch (err) {
|
|
343
|
-
(_a = __privateGet(this, _onError)) == null ? void 0 : _a.call(this, err);
|
|
322
|
+
const timestamp = sample.timestamp;
|
|
323
|
+
const channelData = [];
|
|
324
|
+
for (let ch = 0; ch < sample.numberOfChannels; ch++) {
|
|
325
|
+
const data = new Float32Array(sample.numberOfFrames);
|
|
326
|
+
sample.copyTo(data, { format: "f32-planar", planeIndex: ch });
|
|
327
|
+
channelData.push(data);
|
|
344
328
|
}
|
|
345
|
-
});
|
|
346
|
-
__privateSet(this, _pipeline, pipeline);
|
|
347
|
-
};
|
|
348
|
-
// ─── Private ─────────────────────────────────────────────────────
|
|
349
|
-
onDecoded_fn = function(sample) {
|
|
350
|
-
if (__privateGet(this, _closed) || !__privateGet(this, _worklet)) {
|
|
351
329
|
sample.close();
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
sample.copyTo(data, { format: "f32-planar", planeIndex: ch });
|
|
359
|
-
channelData.push(data);
|
|
330
|
+
const msg = {
|
|
331
|
+
type: "data",
|
|
332
|
+
data: channelData,
|
|
333
|
+
timestamp
|
|
334
|
+
};
|
|
335
|
+
this.#worklet.port.postMessage(msg, channelData.map((d) => d.buffer));
|
|
360
336
|
}
|
|
361
|
-
sample.close();
|
|
362
|
-
const msg = {
|
|
363
|
-
type: "data",
|
|
364
|
-
data: channelData,
|
|
365
|
-
timestamp
|
|
366
|
-
};
|
|
367
|
-
__privateGet(this, _worklet).port.postMessage(msg, channelData.map((d) => d.buffer));
|
|
368
337
|
};
|
|
369
338
|
export {
|
|
370
339
|
AudioRenderPipeline
|