@blockcast/mmt-render 0.1.0 → 0.3.0
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/README.md +5 -4
- package/dist/.build-stamp +0 -0
- 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 +58 -43
- package/dist/pacing/index.js.map +2 -2
- package/dist/pacing/interleave-timing.d.ts +10 -1
- package/dist/pacing/interleave-timing.d.ts.map +1 -1
- package/dist/pacing/interleave-timing.js +15 -4
- package/dist/pacing/interleave-timing.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
- package/src/pacing/interleave-timing.ts +32 -5
package/dist/audio/index.js
CHANGED
|
@@ -1,39 +1,20 @@
|
|
|
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-ring-buffer.ts
|
|
19
|
-
var _buffer, _writeIndex, _readIndex, _stalled, _startupFrames, _resumeFrames, _resumeFollowsStartup, _hasStarted, _capacityHeadroomFrames, _timestampAnchors, _diagEnabled, _diagReads, _diagFillSum, _diagOverflowSamples, _AudioRingBuffer_instances, pruneTimestampAnchors_fn, refillFrames_fn;
|
|
20
2
|
var AudioRingBuffer = class {
|
|
3
|
+
#buffer;
|
|
4
|
+
#writeIndex = 0;
|
|
5
|
+
#readIndex = 0;
|
|
6
|
+
#stalled;
|
|
7
|
+
#startupFrames;
|
|
8
|
+
#resumeFrames;
|
|
9
|
+
#resumeFollowsStartup;
|
|
10
|
+
#hasStarted;
|
|
11
|
+
#capacityHeadroomFrames;
|
|
12
|
+
#timestampAnchors = [];
|
|
13
|
+
#diagEnabled;
|
|
14
|
+
#diagReads = 0;
|
|
15
|
+
#diagFillSum = 0;
|
|
16
|
+
#diagOverflowSamples = 0;
|
|
21
17
|
constructor(config) {
|
|
22
|
-
__privateAdd(this, _AudioRingBuffer_instances);
|
|
23
|
-
__privateAdd(this, _buffer);
|
|
24
|
-
__privateAdd(this, _writeIndex, 0);
|
|
25
|
-
__privateAdd(this, _readIndex, 0);
|
|
26
|
-
__privateAdd(this, _stalled);
|
|
27
|
-
__privateAdd(this, _startupFrames);
|
|
28
|
-
__privateAdd(this, _resumeFrames);
|
|
29
|
-
__privateAdd(this, _resumeFollowsStartup);
|
|
30
|
-
__privateAdd(this, _hasStarted);
|
|
31
|
-
__privateAdd(this, _capacityHeadroomFrames);
|
|
32
|
-
__privateAdd(this, _timestampAnchors, []);
|
|
33
|
-
__privateAdd(this, _diagEnabled);
|
|
34
|
-
__privateAdd(this, _diagReads, 0);
|
|
35
|
-
__privateAdd(this, _diagFillSum, 0);
|
|
36
|
-
__privateAdd(this, _diagOverflowSamples, 0);
|
|
37
18
|
if (!Number.isInteger(config.channels) || config.channels <= 0) {
|
|
38
19
|
throw new Error("invalid channels");
|
|
39
20
|
}
|
|
@@ -59,35 +40,35 @@ var AudioRingBuffer = class {
|
|
|
59
40
|
}
|
|
60
41
|
this.channels = config.channels;
|
|
61
42
|
this.rate = config.rate;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
this.#startupFrames = startupFrames;
|
|
44
|
+
this.#resumeFrames = resumeFrames;
|
|
45
|
+
this.#resumeFollowsStartup = config.resumeFrames === void 0;
|
|
46
|
+
this.#hasStarted = config.startUnstalled ?? false;
|
|
47
|
+
this.#capacityHeadroomFrames = capacityFrames - thresholdFrames;
|
|
48
|
+
this.#stalled = !(config.startUnstalled ?? false);
|
|
49
|
+
this.#diagEnabled = config.diag ?? false;
|
|
50
|
+
this.#buffer = Array.from(
|
|
70
51
|
{ length: config.channels },
|
|
71
52
|
() => new Float32Array(capacityFrames)
|
|
72
|
-
)
|
|
53
|
+
);
|
|
73
54
|
}
|
|
74
55
|
get stalled() {
|
|
75
|
-
return
|
|
56
|
+
return this.#stalled;
|
|
76
57
|
}
|
|
77
58
|
get startupFrames() {
|
|
78
|
-
return
|
|
59
|
+
return this.#startupFrames;
|
|
79
60
|
}
|
|
80
61
|
get capacity() {
|
|
81
|
-
return
|
|
62
|
+
return this.#buffer[0].length;
|
|
82
63
|
}
|
|
83
64
|
get length() {
|
|
84
|
-
return
|
|
65
|
+
return this.#writeIndex - this.#readIndex;
|
|
85
66
|
}
|
|
86
67
|
/** Timestamp of the next media sample after the PCM already emitted. */
|
|
87
68
|
get timestampUs() {
|
|
88
|
-
const anchor =
|
|
89
|
-
if (!anchor || anchor.sampleIndex >
|
|
90
|
-
return anchor.timestampUs + (
|
|
69
|
+
const anchor = this.#timestampAnchors[0];
|
|
70
|
+
if (!anchor || anchor.sampleIndex > this.#readIndex) return void 0;
|
|
71
|
+
return anchor.timestampUs + (this.#readIndex - anchor.sampleIndex) / this.rate * 1e6;
|
|
91
72
|
}
|
|
92
73
|
write(timestampUs, data) {
|
|
93
74
|
if (data.length !== this.channels) throw new Error("wrong channel count");
|
|
@@ -97,68 +78,68 @@ var AudioRingBuffer = class {
|
|
|
97
78
|
if (data.some((channel) => channel.length !== samples)) {
|
|
98
79
|
throw new Error("mismatched channel lengths");
|
|
99
80
|
}
|
|
100
|
-
const start =
|
|
81
|
+
const start = this.#writeIndex;
|
|
101
82
|
const end = start + samples;
|
|
102
|
-
|
|
103
|
-
const overflow = end -
|
|
83
|
+
this.#timestampAnchors.push({ sampleIndex: start, timestampUs });
|
|
84
|
+
const overflow = end - this.#readIndex - this.capacity;
|
|
104
85
|
if (overflow > 0) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
86
|
+
this.#stalled = false;
|
|
87
|
+
this.#hasStarted = true;
|
|
88
|
+
this.#readIndex += overflow;
|
|
89
|
+
if (this.#diagEnabled) this.#diagOverflowSamples += overflow;
|
|
90
|
+
this.#pruneTimestampAnchors();
|
|
110
91
|
}
|
|
111
|
-
const refillFrames =
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
const refillFrames = this.#hasStarted ? this.#resumeFrames : this.#startupFrames;
|
|
93
|
+
if (this.#stalled && end - this.#readIndex >= refillFrames) {
|
|
94
|
+
this.#stalled = false;
|
|
95
|
+
this.#hasStarted = true;
|
|
115
96
|
}
|
|
116
97
|
for (let channel = 0; channel < this.channels; channel++) {
|
|
117
98
|
const src = data[channel];
|
|
118
|
-
const dst =
|
|
99
|
+
const dst = this.#buffer[channel];
|
|
119
100
|
for (let i = 0; i < samples; i++) {
|
|
120
101
|
dst[(start + i) % dst.length] = src[i];
|
|
121
102
|
}
|
|
122
103
|
}
|
|
123
|
-
|
|
104
|
+
this.#writeIndex = end;
|
|
124
105
|
}
|
|
125
106
|
read(output) {
|
|
126
107
|
if (output.length !== this.channels) throw new Error("wrong channel count");
|
|
127
|
-
if (
|
|
108
|
+
if (this.#stalled) return 0;
|
|
128
109
|
const available = this.length;
|
|
129
110
|
if (available === 0) {
|
|
130
|
-
|
|
111
|
+
this.#stalled = true;
|
|
131
112
|
return 0;
|
|
132
113
|
}
|
|
133
114
|
const requested = output[0].length;
|
|
134
115
|
const readSamples = Math.min(available, requested);
|
|
135
116
|
for (let channel = 0; channel < this.channels; channel++) {
|
|
136
117
|
const dst = output[channel];
|
|
137
|
-
const src =
|
|
118
|
+
const src = this.#buffer[channel];
|
|
138
119
|
for (let i = 0; i < readSamples; i++) {
|
|
139
|
-
dst[i] = src[(
|
|
120
|
+
dst[i] = src[(this.#readIndex + i) % src.length];
|
|
140
121
|
}
|
|
141
122
|
for (let i = readSamples; i < requested; i++) dst[i] = 0;
|
|
142
123
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (readSamples < requested)
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
124
|
+
this.#readIndex += readSamples;
|
|
125
|
+
this.#pruneTimestampAnchors();
|
|
126
|
+
if (readSamples < requested) this.#stalled = true;
|
|
127
|
+
if (this.#diagEnabled) {
|
|
128
|
+
this.#diagReads++;
|
|
129
|
+
this.#diagFillSum += available;
|
|
149
130
|
}
|
|
150
131
|
return readSamples;
|
|
151
132
|
}
|
|
152
133
|
/** Clear queued PCM; optionally treat the next fill as post-start recovery. */
|
|
153
134
|
reset(startUnstalled = false, resumeAfterReset = false) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
135
|
+
this.#writeIndex = 0;
|
|
136
|
+
this.#readIndex = 0;
|
|
137
|
+
this.#stalled = !startUnstalled;
|
|
138
|
+
this.#hasStarted = startUnstalled || resumeAfterReset;
|
|
139
|
+
this.#timestampAnchors = [];
|
|
140
|
+
this.#diagReads = 0;
|
|
141
|
+
this.#diagFillSum = 0;
|
|
142
|
+
this.#diagOverflowSamples = 0;
|
|
162
143
|
}
|
|
163
144
|
/**
|
|
164
145
|
* Change the startup latency while preserving the configured storage
|
|
@@ -173,18 +154,18 @@ var AudioRingBuffer = class {
|
|
|
173
154
|
if (!Number.isSafeInteger(newStartupFrames) || newStartupFrames <= 0) {
|
|
174
155
|
throw new Error("invalid latency");
|
|
175
156
|
}
|
|
176
|
-
const newResumeFrames =
|
|
177
|
-
const newCapacity = Math.max(newStartupFrames, newResumeFrames) +
|
|
157
|
+
const newResumeFrames = this.#resumeFollowsStartup ? newStartupFrames : this.#resumeFrames;
|
|
158
|
+
const newCapacity = Math.max(newStartupFrames, newResumeFrames) + this.#capacityHeadroomFrames;
|
|
178
159
|
if (!Number.isSafeInteger(newCapacity) || newCapacity < Math.max(newStartupFrames, newResumeFrames)) {
|
|
179
160
|
throw new Error("invalid capacity frames");
|
|
180
161
|
}
|
|
181
|
-
const wasStalled =
|
|
182
|
-
|
|
183
|
-
|
|
162
|
+
const wasStalled = this.#stalled;
|
|
163
|
+
this.#startupFrames = newStartupFrames;
|
|
164
|
+
this.#resumeFrames = newResumeFrames;
|
|
184
165
|
if (newCapacity === this.capacity) {
|
|
185
|
-
if (wasStalled && this.length >=
|
|
186
|
-
|
|
187
|
-
|
|
166
|
+
if (wasStalled && this.length >= this.#refillFrames()) {
|
|
167
|
+
this.#stalled = false;
|
|
168
|
+
this.#hasStarted = true;
|
|
188
169
|
}
|
|
189
170
|
return;
|
|
190
171
|
}
|
|
@@ -193,61 +174,46 @@ var AudioRingBuffer = class {
|
|
|
193
174
|
() => new Float32Array(newCapacity)
|
|
194
175
|
);
|
|
195
176
|
const samplesToKeep = Math.min(this.length, newCapacity);
|
|
196
|
-
const copyStart =
|
|
177
|
+
const copyStart = this.#writeIndex - samplesToKeep;
|
|
197
178
|
for (let channel = 0; channel < this.channels; channel++) {
|
|
198
|
-
const src =
|
|
179
|
+
const src = this.#buffer[channel];
|
|
199
180
|
const dst = next[channel];
|
|
200
181
|
for (let i = 0; i < samplesToKeep; i++) {
|
|
201
182
|
const sampleIndex = copyStart + i;
|
|
202
183
|
dst[sampleIndex % dst.length] = src[sampleIndex % src.length];
|
|
203
184
|
}
|
|
204
185
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (!
|
|
186
|
+
this.#buffer = next;
|
|
187
|
+
this.#readIndex = copyStart;
|
|
188
|
+
this.#pruneTimestampAnchors();
|
|
189
|
+
this.#stalled = samplesToKeep === 0 || wasStalled && samplesToKeep < this.#refillFrames();
|
|
190
|
+
if (!this.#stalled) this.#hasStarted = true;
|
|
210
191
|
}
|
|
211
192
|
getDiagAndReset() {
|
|
212
|
-
if (!
|
|
193
|
+
if (!this.#diagEnabled || this.#diagReads === 0) return null;
|
|
213
194
|
const result = {
|
|
214
|
-
reads:
|
|
215
|
-
avgFill: Math.round(
|
|
195
|
+
reads: this.#diagReads,
|
|
196
|
+
avgFill: Math.round(this.#diagFillSum / this.#diagReads),
|
|
216
197
|
capacity: this.capacity,
|
|
217
198
|
skips: 0,
|
|
218
199
|
dups: 0,
|
|
219
|
-
overflowSamples:
|
|
200
|
+
overflowSamples: this.#diagOverflowSamples
|
|
220
201
|
};
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
202
|
+
this.#diagReads = 0;
|
|
203
|
+
this.#diagFillSum = 0;
|
|
204
|
+
this.#diagOverflowSamples = 0;
|
|
224
205
|
return result;
|
|
225
206
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
_timestampAnchors = new WeakMap();
|
|
237
|
-
_diagEnabled = new WeakMap();
|
|
238
|
-
_diagReads = new WeakMap();
|
|
239
|
-
_diagFillSum = new WeakMap();
|
|
240
|
-
_diagOverflowSamples = new WeakMap();
|
|
241
|
-
_AudioRingBuffer_instances = new WeakSet();
|
|
242
|
-
pruneTimestampAnchors_fn = function() {
|
|
243
|
-
let keep = 0;
|
|
244
|
-
while (keep + 1 < __privateGet(this, _timestampAnchors).length && __privateGet(this, _timestampAnchors)[keep + 1].sampleIndex <= __privateGet(this, _readIndex)) {
|
|
245
|
-
keep++;
|
|
246
|
-
}
|
|
247
|
-
if (keep > 0) __privateGet(this, _timestampAnchors).splice(0, keep);
|
|
248
|
-
};
|
|
249
|
-
refillFrames_fn = function() {
|
|
250
|
-
return __privateGet(this, _hasStarted) ? __privateGet(this, _resumeFrames) : __privateGet(this, _startupFrames);
|
|
207
|
+
#pruneTimestampAnchors() {
|
|
208
|
+
let keep = 0;
|
|
209
|
+
while (keep + 1 < this.#timestampAnchors.length && this.#timestampAnchors[keep + 1].sampleIndex <= this.#readIndex) {
|
|
210
|
+
keep++;
|
|
211
|
+
}
|
|
212
|
+
if (keep > 0) this.#timestampAnchors.splice(0, keep);
|
|
213
|
+
}
|
|
214
|
+
#refillFrames() {
|
|
215
|
+
return this.#hasStarted ? this.#resumeFrames : this.#startupFrames;
|
|
216
|
+
}
|
|
251
217
|
};
|
|
252
218
|
|
|
253
219
|
// src/audio/audio-buffer-plan.ts
|
|
@@ -283,12 +249,11 @@ function deriveAudioBurstBufferPlan(input) {
|
|
|
283
249
|
import { MmtpDecodePipeline } from "@blockcast/mmt-container/mmtp-decode-pipeline";
|
|
284
250
|
|
|
285
251
|
// src/pacing/frame-pacer.ts
|
|
286
|
-
var _frameCount, _anchorWallMs, _anchored;
|
|
287
252
|
var FrameCountPacer = class {
|
|
253
|
+
#frameCount = 0;
|
|
254
|
+
#anchorWallMs = 0;
|
|
255
|
+
#anchored = false;
|
|
288
256
|
constructor(opts) {
|
|
289
|
-
__privateAdd(this, _frameCount, 0);
|
|
290
|
-
__privateAdd(this, _anchorWallMs, 0);
|
|
291
|
-
__privateAdd(this, _anchored, false);
|
|
292
257
|
if (!(opts.frameDurationMs > 0)) {
|
|
293
258
|
throw new Error("frameDurationMs must be > 0");
|
|
294
259
|
}
|
|
@@ -298,11 +263,11 @@ var FrameCountPacer = class {
|
|
|
298
263
|
}
|
|
299
264
|
/** Call on every emitted decoded frame (AU or picture). Anchors on first call. */
|
|
300
265
|
notifyFrame() {
|
|
301
|
-
if (!
|
|
302
|
-
|
|
303
|
-
|
|
266
|
+
if (!this.#anchored) {
|
|
267
|
+
this.#anchorWallMs = now();
|
|
268
|
+
this.#anchored = true;
|
|
304
269
|
}
|
|
305
|
-
|
|
270
|
+
this.#frameCount++;
|
|
306
271
|
}
|
|
307
272
|
/**
|
|
308
273
|
* Compute sleep ms before reading the next MoQ object.
|
|
@@ -310,41 +275,62 @@ var FrameCountPacer = class {
|
|
|
310
275
|
* - pacing is disabled (NullBlockMapper → threshold=0)
|
|
311
276
|
* - the anchor is not yet set
|
|
312
277
|
* - still in warmup
|
|
278
|
+
* - the caller reports a standing backlog (see `observedDelayMs`)
|
|
313
279
|
* - decoder is not ahead of wall clock by more than threshold
|
|
314
280
|
* Otherwise returns aheadMs - thresholdMs/2 (leaves half the threshold as headroom).
|
|
281
|
+
*
|
|
282
|
+
* `observedDelayMs` is how far this frame is behind the producer's own clock,
|
|
283
|
+
* measured by the caller against a producer-supplied sending instant (for
|
|
284
|
+
* MMTP, the header send-wall timestamp). It is optional because the anchored
|
|
285
|
+
* rate below is blind to it: `#anchorWallMs` is the wall time of the FIRST
|
|
286
|
+
* frame read, so `aheadMs` measures consumption against the join instant and
|
|
287
|
+
* not against the producer. Once `aheadMs` reaches the threshold the loop
|
|
288
|
+
* consumes at exactly the source rate, and recovering latency present at join
|
|
289
|
+
* requires consuming FASTER than the source rate. So without this argument a
|
|
290
|
+
* consumer that joined a live stream mid-block keeps that join latency for the
|
|
291
|
+
* life of the subscription.
|
|
292
|
+
*
|
|
293
|
+
* A caller that can measure the real distance from the producer passes it and
|
|
294
|
+
* gets a drain: while the backlog exceeds the threshold there is nothing to
|
|
295
|
+
* pace against, so return 0 and let the transport set the rate. At the live
|
|
296
|
+
* edge the anchored-rate behavior below applies unchanged.
|
|
297
|
+
*
|
|
298
|
+
* Callers with no producer clock omit it and keep the pure ATSC3-style
|
|
299
|
+
* anchored pacing (see the class doc), where a first-frame anchor is correct
|
|
300
|
+
* because a broadcast push has no live edge to catch up to.
|
|
315
301
|
*/
|
|
316
|
-
computeSleepMs() {
|
|
302
|
+
computeSleepMs(observedDelayMs) {
|
|
317
303
|
if (this.thresholdMs <= 0) return 0;
|
|
318
|
-
if (!
|
|
319
|
-
if (
|
|
320
|
-
|
|
321
|
-
|
|
304
|
+
if (!this.#anchored) return 0;
|
|
305
|
+
if (this.#frameCount <= this.warmupFrames) return 0;
|
|
306
|
+
if (observedDelayMs !== void 0 && Number.isFinite(observedDelayMs) && observedDelayMs > this.thresholdMs) {
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
const mediaMs = this.#frameCount * this.frameDurationMs;
|
|
310
|
+
const wallMs = now() - this.#anchorWallMs;
|
|
322
311
|
const aheadMs = mediaMs - wallMs;
|
|
323
312
|
if (aheadMs <= this.thresholdMs) return 0;
|
|
324
313
|
return aheadMs - this.thresholdMs / 2;
|
|
325
314
|
}
|
|
326
315
|
/** Reset anchor + counter. Call on source switch (MoQ ↔ multicast). */
|
|
327
316
|
reset() {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
317
|
+
this.#frameCount = 0;
|
|
318
|
+
this.#anchorWallMs = 0;
|
|
319
|
+
this.#anchored = false;
|
|
331
320
|
}
|
|
332
321
|
get snapshot() {
|
|
333
|
-
const mediaMs =
|
|
334
|
-
const wallMs =
|
|
322
|
+
const mediaMs = this.#frameCount * this.frameDurationMs;
|
|
323
|
+
const wallMs = this.#anchored ? now() - this.#anchorWallMs : 0;
|
|
335
324
|
return {
|
|
336
|
-
frameCount:
|
|
325
|
+
frameCount: this.#frameCount,
|
|
337
326
|
mediaMs,
|
|
338
327
|
wallMs,
|
|
339
328
|
aheadMs: mediaMs - wallMs,
|
|
340
329
|
thresholdMs: this.thresholdMs,
|
|
341
|
-
anchored:
|
|
330
|
+
anchored: this.#anchored
|
|
342
331
|
};
|
|
343
332
|
}
|
|
344
333
|
};
|
|
345
|
-
_frameCount = new WeakMap();
|
|
346
|
-
_anchorWallMs = new WeakMap();
|
|
347
|
-
_anchored = new WeakMap();
|
|
348
334
|
function now() {
|
|
349
335
|
if (typeof performance !== "undefined" && typeof performance.now === "function") {
|
|
350
336
|
return performance.now();
|
|
@@ -375,260 +361,226 @@ function workletModuleUrl() {
|
|
|
375
361
|
var WORKLET_PROCESSOR_NAME = "mmt-render";
|
|
376
362
|
|
|
377
363
|
// src/audio/audio-render-pipeline.ts
|
|
378
|
-
var _context, _pacer, _mapper, _codec, _packetId, _sampleRate, _channels, _description, _latencyMs, _startupFrames2, _startupThresholdFrames, _capacityFrames, _diag, _warmupFrames, _onState, _onDiag, _onError, _reassemblyPolicy, _decoder, _pipeline, _worklet, _fecClient, _closed, _readyPromise, _workletGeneration, _startupReleased, _AudioRenderPipeline_instances, initialize_fn, onDecoded_fn;
|
|
379
364
|
var AudioRenderPipeline = class {
|
|
365
|
+
#context;
|
|
366
|
+
#pacer;
|
|
367
|
+
#mapper;
|
|
368
|
+
#codec;
|
|
369
|
+
#packetId;
|
|
370
|
+
#sampleRate;
|
|
371
|
+
#channels;
|
|
372
|
+
#description;
|
|
373
|
+
#latencyMs;
|
|
374
|
+
#startupFrames;
|
|
375
|
+
#startupThresholdFrames;
|
|
376
|
+
#capacityFrames;
|
|
377
|
+
#diag;
|
|
378
|
+
#warmupFrames;
|
|
379
|
+
#onState;
|
|
380
|
+
#onDiag;
|
|
381
|
+
#onError;
|
|
382
|
+
#reassemblyPolicy;
|
|
383
|
+
#decoder = null;
|
|
384
|
+
#pipeline = null;
|
|
385
|
+
#worklet = null;
|
|
386
|
+
#fecClient;
|
|
387
|
+
#closed = false;
|
|
388
|
+
#readyPromise = null;
|
|
389
|
+
#workletGeneration = 0;
|
|
390
|
+
#startupReleased = false;
|
|
380
391
|
constructor(opts) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
__privateAdd(this, _reassemblyPolicy);
|
|
400
|
-
__privateAdd(this, _decoder, null);
|
|
401
|
-
__privateAdd(this, _pipeline, null);
|
|
402
|
-
__privateAdd(this, _worklet, null);
|
|
403
|
-
__privateAdd(this, _fecClient);
|
|
404
|
-
__privateAdd(this, _closed, false);
|
|
405
|
-
__privateAdd(this, _readyPromise, null);
|
|
406
|
-
__privateAdd(this, _workletGeneration, 0);
|
|
407
|
-
__privateAdd(this, _startupReleased, false);
|
|
408
|
-
__privateSet(this, _context, opts.context);
|
|
409
|
-
__privateSet(this, _mapper, opts.mapper);
|
|
410
|
-
__privateSet(this, _codec, opts.codec);
|
|
411
|
-
__privateSet(this, _packetId, opts.packetId);
|
|
412
|
-
__privateSet(this, _sampleRate, opts.sampleRate);
|
|
413
|
-
__privateSet(this, _channels, opts.channels);
|
|
414
|
-
__privateSet(this, _description, opts.description);
|
|
415
|
-
__privateSet(this, _latencyMs, opts.latencyMs);
|
|
416
|
-
__privateSet(this, _startupFrames2, opts.startupFrames);
|
|
417
|
-
__privateSet(this, _startupThresholdFrames, opts.startupFrames ?? Math.ceil(opts.sampleRate * (opts.latencyMs / 1e3)));
|
|
418
|
-
__privateSet(this, _capacityFrames, opts.capacityFrames);
|
|
419
|
-
__privateSet(this, _diag, opts.diag ?? false);
|
|
420
|
-
__privateSet(this, _warmupFrames, opts.warmupFrames ?? 0);
|
|
421
|
-
__privateSet(this, _onState, opts.onState);
|
|
422
|
-
__privateSet(this, _onDiag, opts.onDiag);
|
|
423
|
-
__privateSet(this, _onError, opts.onError);
|
|
424
|
-
__privateSet(this, _fecClient, opts.fecClient);
|
|
425
|
-
__privateSet(this, _reassemblyPolicy, opts.reassemblyPolicy);
|
|
392
|
+
this.#context = opts.context;
|
|
393
|
+
this.#mapper = opts.mapper;
|
|
394
|
+
this.#codec = opts.codec;
|
|
395
|
+
this.#packetId = opts.packetId;
|
|
396
|
+
this.#sampleRate = opts.sampleRate;
|
|
397
|
+
this.#channels = opts.channels;
|
|
398
|
+
this.#description = opts.description;
|
|
399
|
+
this.#latencyMs = opts.latencyMs;
|
|
400
|
+
this.#startupFrames = opts.startupFrames;
|
|
401
|
+
this.#startupThresholdFrames = opts.startupFrames ?? Math.ceil(opts.sampleRate * (opts.latencyMs / 1e3));
|
|
402
|
+
this.#capacityFrames = opts.capacityFrames;
|
|
403
|
+
this.#diag = opts.diag ?? false;
|
|
404
|
+
this.#warmupFrames = opts.warmupFrames ?? 0;
|
|
405
|
+
this.#onState = opts.onState;
|
|
406
|
+
this.#onDiag = opts.onDiag;
|
|
407
|
+
this.#onError = opts.onError;
|
|
408
|
+
this.#fecClient = opts.fecClient;
|
|
409
|
+
this.#reassemblyPolicy = opts.reassemblyPolicy;
|
|
426
410
|
const auDurationMs = auDurationMsFromCodec(opts.codec, opts.sampleRate);
|
|
427
|
-
|
|
411
|
+
this.#pacer = new FrameCountPacer({
|
|
428
412
|
frameDurationMs: auDurationMs,
|
|
429
413
|
mapper: opts.mapper,
|
|
430
|
-
warmupFrames:
|
|
431
|
-
})
|
|
414
|
+
warmupFrames: this.#warmupFrames
|
|
415
|
+
});
|
|
432
416
|
}
|
|
433
417
|
/** Load worklet module + create decoder + pipeline. Call once. */
|
|
434
418
|
async ready() {
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
return
|
|
419
|
+
if (this.#readyPromise) return this.#readyPromise;
|
|
420
|
+
this.#readyPromise = this.#initialize();
|
|
421
|
+
return this.#readyPromise;
|
|
422
|
+
}
|
|
423
|
+
async #initialize() {
|
|
424
|
+
await this.#context.audioWorklet.addModule(workletModuleUrl());
|
|
425
|
+
const worklet = new AudioWorkletNode(this.#context, WORKLET_PROCESSOR_NAME, {
|
|
426
|
+
numberOfInputs: 0,
|
|
427
|
+
numberOfOutputs: 1,
|
|
428
|
+
outputChannelCount: [this.#channels]
|
|
429
|
+
});
|
|
430
|
+
const initMsg = {
|
|
431
|
+
type: "init",
|
|
432
|
+
generation: ++this.#workletGeneration,
|
|
433
|
+
channels: this.#channels,
|
|
434
|
+
rate: this.#sampleRate,
|
|
435
|
+
latencyMs: this.#latencyMs,
|
|
436
|
+
startupFrames: this.#startupFrames,
|
|
437
|
+
capacityFrames: this.#capacityFrames,
|
|
438
|
+
holdUntilStart: true,
|
|
439
|
+
diag: this.#diag
|
|
440
|
+
};
|
|
441
|
+
worklet.port.postMessage(initMsg);
|
|
442
|
+
worklet.port.onmessage = (ev) => {
|
|
443
|
+
const m = ev.data;
|
|
444
|
+
if (m.type === "state") {
|
|
445
|
+
if (m.generation !== this.#workletGeneration) return;
|
|
446
|
+
this.#onState?.({
|
|
447
|
+
timestampUs: m.timestamp,
|
|
448
|
+
stalled: m.stalled,
|
|
449
|
+
bufferedFrames: m.bufferedFrames,
|
|
450
|
+
writtenFrames: m.writtenFrames
|
|
451
|
+
});
|
|
452
|
+
} else if (m.type === "ringBufDiag") {
|
|
453
|
+
if (m.generation !== this.#workletGeneration) return;
|
|
454
|
+
this.#onDiag?.({
|
|
455
|
+
reads: m.reads,
|
|
456
|
+
avgFill: m.avgFill,
|
|
457
|
+
capacity: m.capacity,
|
|
458
|
+
skipsPerSec: m.skipsPerSec,
|
|
459
|
+
dupsPerSec: m.dupsPerSec,
|
|
460
|
+
overflowSamplesPerSec: m.overflowSamplesPerSec,
|
|
461
|
+
underflowSamplesPerSec: m.underflowSamplesPerSec
|
|
462
|
+
});
|
|
463
|
+
} else if (m.type === "writeAck") {
|
|
464
|
+
if (m.generation !== this.#workletGeneration || this.#startupReleased || m.bufferedFrames < this.#startupThresholdFrames) return;
|
|
465
|
+
this.#startupReleased = true;
|
|
466
|
+
const start = {
|
|
467
|
+
type: "start",
|
|
468
|
+
generation: this.#workletGeneration
|
|
469
|
+
};
|
|
470
|
+
worklet.port.postMessage(start);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
this.#worklet = worklet;
|
|
474
|
+
const decoder = new AudioDecoder({
|
|
475
|
+
output: (data) => this.#onDecoded(data),
|
|
476
|
+
error: (err) => this.#onError?.(err)
|
|
477
|
+
});
|
|
478
|
+
const config = {
|
|
479
|
+
codec: this.#codec,
|
|
480
|
+
sampleRate: this.#sampleRate,
|
|
481
|
+
numberOfChannels: this.#channels,
|
|
482
|
+
...this.#description ? { description: this.#description } : {}
|
|
483
|
+
};
|
|
484
|
+
decoder.configure(config);
|
|
485
|
+
this.#decoder = decoder;
|
|
486
|
+
const pipeline = new MmtpDecodePipeline();
|
|
487
|
+
pipeline.configure({
|
|
488
|
+
codec: this.#codec,
|
|
489
|
+
packetId: this.#packetId,
|
|
490
|
+
description: this.#description,
|
|
491
|
+
reorderWindow: 0,
|
|
492
|
+
// audio doesn't need reorder — AAC/Opus frames are self-contained
|
|
493
|
+
reassemblyPolicy: this.#reassemblyPolicy
|
|
494
|
+
});
|
|
495
|
+
if (this.#fecClient) pipeline.setFecClient(this.#fecClient);
|
|
496
|
+
pipeline.onFrame((codecData, meta) => {
|
|
497
|
+
if (!this.#decoder || this.#decoder.state !== "configured") return;
|
|
498
|
+
if (codecData.byteLength === 0) return;
|
|
499
|
+
this.#pacer.notifyFrame();
|
|
500
|
+
try {
|
|
501
|
+
this.#decoder.decode(new EncodedAudioChunk({
|
|
502
|
+
type: "key",
|
|
503
|
+
data: codecData,
|
|
504
|
+
timestamp: meta.timestamp
|
|
505
|
+
}));
|
|
506
|
+
} catch (err) {
|
|
507
|
+
this.#onError?.(err);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
this.#pipeline = pipeline;
|
|
438
511
|
}
|
|
439
512
|
/** Feed one MoQ object (raw MMTP packet). */
|
|
440
513
|
feedMoqObject(packet) {
|
|
441
|
-
|
|
514
|
+
this.#pipeline?.feedMoqObject(packet);
|
|
442
515
|
}
|
|
443
516
|
/** ms to sleep before the next object read. 0 when not anchored / not ahead. */
|
|
444
517
|
paceSleepMs() {
|
|
445
|
-
return
|
|
518
|
+
return this.#pacer.computeSleepMs();
|
|
446
519
|
}
|
|
447
520
|
/** Pacer snapshot (for diagnostics / UI). */
|
|
448
521
|
get pacerSnapshot() {
|
|
449
|
-
return
|
|
522
|
+
return this.#pacer.snapshot;
|
|
450
523
|
}
|
|
451
524
|
/** Reset pacer on source switch (MoQ ↔ multicast). */
|
|
452
525
|
resetPacer() {
|
|
453
|
-
|
|
526
|
+
this.#pacer.reset();
|
|
454
527
|
}
|
|
455
528
|
/** Drop queued PCM and its timestamp epoch at an explicit source boundary. */
|
|
456
529
|
resetPlayout(startUnstalled = false) {
|
|
457
|
-
|
|
458
|
-
|
|
530
|
+
this.#pacer.reset();
|
|
531
|
+
this.#startupReleased = true;
|
|
459
532
|
const message = {
|
|
460
533
|
type: "reset",
|
|
461
|
-
generation: ++
|
|
534
|
+
generation: ++this.#workletGeneration,
|
|
462
535
|
startUnstalled
|
|
463
536
|
};
|
|
464
|
-
|
|
537
|
+
this.#worklet?.port.postMessage(message);
|
|
465
538
|
}
|
|
466
539
|
/** Node to connect to destination / gain. */
|
|
467
540
|
get rootNode() {
|
|
468
|
-
if (!
|
|
469
|
-
return
|
|
541
|
+
if (!this.#worklet) throw new Error("AudioRenderPipeline.ready() must resolve first");
|
|
542
|
+
return this.#worklet;
|
|
470
543
|
}
|
|
471
544
|
close() {
|
|
472
|
-
if (
|
|
473
|
-
|
|
545
|
+
if (this.#closed) return;
|
|
546
|
+
this.#closed = true;
|
|
474
547
|
try {
|
|
475
|
-
|
|
548
|
+
this.#decoder?.close();
|
|
476
549
|
} catch {
|
|
477
550
|
}
|
|
478
551
|
try {
|
|
479
|
-
|
|
552
|
+
this.#pipeline?.dispose();
|
|
480
553
|
} catch {
|
|
481
554
|
}
|
|
482
555
|
try {
|
|
483
|
-
|
|
556
|
+
this.#worklet?.disconnect();
|
|
484
557
|
} catch {
|
|
485
558
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
_packetId = new WeakMap();
|
|
496
|
-
_sampleRate = new WeakMap();
|
|
497
|
-
_channels = new WeakMap();
|
|
498
|
-
_description = new WeakMap();
|
|
499
|
-
_latencyMs = new WeakMap();
|
|
500
|
-
_startupFrames2 = new WeakMap();
|
|
501
|
-
_startupThresholdFrames = new WeakMap();
|
|
502
|
-
_capacityFrames = new WeakMap();
|
|
503
|
-
_diag = new WeakMap();
|
|
504
|
-
_warmupFrames = new WeakMap();
|
|
505
|
-
_onState = new WeakMap();
|
|
506
|
-
_onDiag = new WeakMap();
|
|
507
|
-
_onError = new WeakMap();
|
|
508
|
-
_reassemblyPolicy = new WeakMap();
|
|
509
|
-
_decoder = new WeakMap();
|
|
510
|
-
_pipeline = new WeakMap();
|
|
511
|
-
_worklet = new WeakMap();
|
|
512
|
-
_fecClient = new WeakMap();
|
|
513
|
-
_closed = new WeakMap();
|
|
514
|
-
_readyPromise = new WeakMap();
|
|
515
|
-
_workletGeneration = new WeakMap();
|
|
516
|
-
_startupReleased = new WeakMap();
|
|
517
|
-
_AudioRenderPipeline_instances = new WeakSet();
|
|
518
|
-
initialize_fn = async function() {
|
|
519
|
-
await __privateGet(this, _context).audioWorklet.addModule(workletModuleUrl());
|
|
520
|
-
const worklet = new AudioWorkletNode(__privateGet(this, _context), WORKLET_PROCESSOR_NAME, {
|
|
521
|
-
numberOfInputs: 0,
|
|
522
|
-
numberOfOutputs: 1,
|
|
523
|
-
outputChannelCount: [__privateGet(this, _channels)]
|
|
524
|
-
});
|
|
525
|
-
const initMsg = {
|
|
526
|
-
type: "init",
|
|
527
|
-
generation: ++__privateWrapper(this, _workletGeneration)._,
|
|
528
|
-
channels: __privateGet(this, _channels),
|
|
529
|
-
rate: __privateGet(this, _sampleRate),
|
|
530
|
-
latencyMs: __privateGet(this, _latencyMs),
|
|
531
|
-
startupFrames: __privateGet(this, _startupFrames2),
|
|
532
|
-
capacityFrames: __privateGet(this, _capacityFrames),
|
|
533
|
-
holdUntilStart: true,
|
|
534
|
-
diag: __privateGet(this, _diag)
|
|
535
|
-
};
|
|
536
|
-
worklet.port.postMessage(initMsg);
|
|
537
|
-
worklet.port.onmessage = (ev) => {
|
|
538
|
-
var _a, _b;
|
|
539
|
-
const m = ev.data;
|
|
540
|
-
if (m.type === "state") {
|
|
541
|
-
if (m.generation !== __privateGet(this, _workletGeneration)) return;
|
|
542
|
-
(_a = __privateGet(this, _onState)) == null ? void 0 : _a.call(this, {
|
|
543
|
-
timestampUs: m.timestamp,
|
|
544
|
-
stalled: m.stalled,
|
|
545
|
-
bufferedFrames: m.bufferedFrames,
|
|
546
|
-
writtenFrames: m.writtenFrames
|
|
547
|
-
});
|
|
548
|
-
} else if (m.type === "ringBufDiag") {
|
|
549
|
-
if (m.generation !== __privateGet(this, _workletGeneration)) return;
|
|
550
|
-
(_b = __privateGet(this, _onDiag)) == null ? void 0 : _b.call(this, {
|
|
551
|
-
reads: m.reads,
|
|
552
|
-
avgFill: m.avgFill,
|
|
553
|
-
capacity: m.capacity,
|
|
554
|
-
skipsPerSec: m.skipsPerSec,
|
|
555
|
-
dupsPerSec: m.dupsPerSec,
|
|
556
|
-
overflowSamplesPerSec: m.overflowSamplesPerSec,
|
|
557
|
-
underflowSamplesPerSec: m.underflowSamplesPerSec
|
|
558
|
-
});
|
|
559
|
-
} else if (m.type === "writeAck") {
|
|
560
|
-
if (m.generation !== __privateGet(this, _workletGeneration) || __privateGet(this, _startupReleased) || m.bufferedFrames < __privateGet(this, _startupThresholdFrames)) return;
|
|
561
|
-
__privateSet(this, _startupReleased, true);
|
|
562
|
-
const start = {
|
|
563
|
-
type: "start",
|
|
564
|
-
generation: __privateGet(this, _workletGeneration)
|
|
565
|
-
};
|
|
566
|
-
worklet.port.postMessage(start);
|
|
559
|
+
this.#decoder = null;
|
|
560
|
+
this.#pipeline = null;
|
|
561
|
+
this.#worklet = null;
|
|
562
|
+
}
|
|
563
|
+
// ─── Private ─────────────────────────────────────────────────────
|
|
564
|
+
#onDecoded(sample) {
|
|
565
|
+
if (this.#closed || !this.#worklet) {
|
|
566
|
+
sample.close();
|
|
567
|
+
return;
|
|
567
568
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return (_a = __privateGet(this, _onError)) == null ? void 0 : _a.call(this, err);
|
|
569
|
+
const timestamp = sample.timestamp;
|
|
570
|
+
const channelData = [];
|
|
571
|
+
for (let ch = 0; ch < sample.numberOfChannels; ch++) {
|
|
572
|
+
const data = new Float32Array(sample.numberOfFrames);
|
|
573
|
+
sample.copyTo(data, { format: "f32-planar", planeIndex: ch });
|
|
574
|
+
channelData.push(data);
|
|
575
575
|
}
|
|
576
|
-
});
|
|
577
|
-
const config = {
|
|
578
|
-
codec: __privateGet(this, _codec),
|
|
579
|
-
sampleRate: __privateGet(this, _sampleRate),
|
|
580
|
-
numberOfChannels: __privateGet(this, _channels),
|
|
581
|
-
...__privateGet(this, _description) ? { description: __privateGet(this, _description) } : {}
|
|
582
|
-
};
|
|
583
|
-
decoder.configure(config);
|
|
584
|
-
__privateSet(this, _decoder, decoder);
|
|
585
|
-
const pipeline = new MmtpDecodePipeline();
|
|
586
|
-
pipeline.configure({
|
|
587
|
-
codec: __privateGet(this, _codec),
|
|
588
|
-
packetId: __privateGet(this, _packetId),
|
|
589
|
-
description: __privateGet(this, _description),
|
|
590
|
-
reorderWindow: 0,
|
|
591
|
-
// audio doesn't need reorder — AAC/Opus frames are self-contained
|
|
592
|
-
reassemblyPolicy: __privateGet(this, _reassemblyPolicy)
|
|
593
|
-
});
|
|
594
|
-
if (__privateGet(this, _fecClient)) pipeline.setFecClient(__privateGet(this, _fecClient));
|
|
595
|
-
pipeline.onFrame((codecData, meta) => {
|
|
596
|
-
var _a;
|
|
597
|
-
if (!__privateGet(this, _decoder) || __privateGet(this, _decoder).state !== "configured") return;
|
|
598
|
-
if (codecData.byteLength === 0) return;
|
|
599
|
-
__privateGet(this, _pacer).notifyFrame();
|
|
600
|
-
try {
|
|
601
|
-
__privateGet(this, _decoder).decode(new EncodedAudioChunk({
|
|
602
|
-
type: "key",
|
|
603
|
-
data: codecData,
|
|
604
|
-
timestamp: meta.timestamp
|
|
605
|
-
}));
|
|
606
|
-
} catch (err) {
|
|
607
|
-
(_a = __privateGet(this, _onError)) == null ? void 0 : _a.call(this, err);
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
__privateSet(this, _pipeline, pipeline);
|
|
611
|
-
};
|
|
612
|
-
// ─── Private ─────────────────────────────────────────────────────
|
|
613
|
-
onDecoded_fn = function(sample) {
|
|
614
|
-
if (__privateGet(this, _closed) || !__privateGet(this, _worklet)) {
|
|
615
576
|
sample.close();
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
channelData.push(data);
|
|
624
|
-
}
|
|
625
|
-
sample.close();
|
|
626
|
-
const msg = {
|
|
627
|
-
type: "data",
|
|
628
|
-
data: channelData,
|
|
629
|
-
timestamp
|
|
630
|
-
};
|
|
631
|
-
__privateGet(this, _worklet).port.postMessage(msg, channelData.map((d) => d.buffer));
|
|
577
|
+
const msg = {
|
|
578
|
+
type: "data",
|
|
579
|
+
data: channelData,
|
|
580
|
+
timestamp
|
|
581
|
+
};
|
|
582
|
+
this.#worklet.port.postMessage(msg, channelData.map((d) => d.buffer));
|
|
583
|
+
}
|
|
632
584
|
};
|
|
633
585
|
export {
|
|
634
586
|
AudioRenderPipeline,
|