@blockcast/mmt-render 0.1.0 → 0.3.0-main.36c5f6abe867

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.
@@ -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
- __privateSet(this, _startupFrames, startupFrames);
63
- __privateSet(this, _resumeFrames, resumeFrames);
64
- __privateSet(this, _resumeFollowsStartup, config.resumeFrames === void 0);
65
- __privateSet(this, _hasStarted, config.startUnstalled ?? false);
66
- __privateSet(this, _capacityHeadroomFrames, capacityFrames - thresholdFrames);
67
- __privateSet(this, _stalled, !(config.startUnstalled ?? false));
68
- __privateSet(this, _diagEnabled, config.diag ?? false);
69
- __privateSet(this, _buffer, Array.from(
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 __privateGet(this, _stalled);
56
+ return this.#stalled;
76
57
  }
77
58
  get startupFrames() {
78
- return __privateGet(this, _startupFrames);
59
+ return this.#startupFrames;
79
60
  }
80
61
  get capacity() {
81
- return __privateGet(this, _buffer)[0].length;
62
+ return this.#buffer[0].length;
82
63
  }
83
64
  get length() {
84
- return __privateGet(this, _writeIndex) - __privateGet(this, _readIndex);
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 = __privateGet(this, _timestampAnchors)[0];
89
- if (!anchor || anchor.sampleIndex > __privateGet(this, _readIndex)) return void 0;
90
- return anchor.timestampUs + (__privateGet(this, _readIndex) - anchor.sampleIndex) / this.rate * 1e6;
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 = __privateGet(this, _writeIndex);
81
+ const start = this.#writeIndex;
101
82
  const end = start + samples;
102
- __privateGet(this, _timestampAnchors).push({ sampleIndex: start, timestampUs });
103
- const overflow = end - __privateGet(this, _readIndex) - this.capacity;
83
+ this.#timestampAnchors.push({ sampleIndex: start, timestampUs });
84
+ const overflow = end - this.#readIndex - this.capacity;
104
85
  if (overflow > 0) {
105
- __privateSet(this, _stalled, false);
106
- __privateSet(this, _hasStarted, true);
107
- __privateSet(this, _readIndex, __privateGet(this, _readIndex) + overflow);
108
- if (__privateGet(this, _diagEnabled)) __privateSet(this, _diagOverflowSamples, __privateGet(this, _diagOverflowSamples) + overflow);
109
- __privateMethod(this, _AudioRingBuffer_instances, pruneTimestampAnchors_fn).call(this);
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 = __privateGet(this, _hasStarted) ? __privateGet(this, _resumeFrames) : __privateGet(this, _startupFrames);
112
- if (__privateGet(this, _stalled) && end - __privateGet(this, _readIndex) >= refillFrames) {
113
- __privateSet(this, _stalled, false);
114
- __privateSet(this, _hasStarted, true);
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 = __privateGet(this, _buffer)[channel];
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
- __privateSet(this, _writeIndex, end);
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 (__privateGet(this, _stalled)) return 0;
108
+ if (this.#stalled) return 0;
128
109
  const available = this.length;
129
110
  if (available === 0) {
130
- __privateSet(this, _stalled, true);
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 = __privateGet(this, _buffer)[channel];
118
+ const src = this.#buffer[channel];
138
119
  for (let i = 0; i < readSamples; i++) {
139
- dst[i] = src[(__privateGet(this, _readIndex) + i) % src.length];
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
- __privateSet(this, _readIndex, __privateGet(this, _readIndex) + readSamples);
144
- __privateMethod(this, _AudioRingBuffer_instances, pruneTimestampAnchors_fn).call(this);
145
- if (readSamples < requested) __privateSet(this, _stalled, true);
146
- if (__privateGet(this, _diagEnabled)) {
147
- __privateWrapper(this, _diagReads)._++;
148
- __privateSet(this, _diagFillSum, __privateGet(this, _diagFillSum) + available);
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
- __privateSet(this, _writeIndex, 0);
155
- __privateSet(this, _readIndex, 0);
156
- __privateSet(this, _stalled, !startUnstalled);
157
- __privateSet(this, _hasStarted, startUnstalled || resumeAfterReset);
158
- __privateSet(this, _timestampAnchors, []);
159
- __privateSet(this, _diagReads, 0);
160
- __privateSet(this, _diagFillSum, 0);
161
- __privateSet(this, _diagOverflowSamples, 0);
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 = __privateGet(this, _resumeFollowsStartup) ? newStartupFrames : __privateGet(this, _resumeFrames);
177
- const newCapacity = Math.max(newStartupFrames, newResumeFrames) + __privateGet(this, _capacityHeadroomFrames);
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 = __privateGet(this, _stalled);
182
- __privateSet(this, _startupFrames, newStartupFrames);
183
- __privateSet(this, _resumeFrames, newResumeFrames);
162
+ const wasStalled = this.#stalled;
163
+ this.#startupFrames = newStartupFrames;
164
+ this.#resumeFrames = newResumeFrames;
184
165
  if (newCapacity === this.capacity) {
185
- if (wasStalled && this.length >= __privateMethod(this, _AudioRingBuffer_instances, refillFrames_fn).call(this)) {
186
- __privateSet(this, _stalled, false);
187
- __privateSet(this, _hasStarted, true);
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 = __privateGet(this, _writeIndex) - samplesToKeep;
177
+ const copyStart = this.#writeIndex - samplesToKeep;
197
178
  for (let channel = 0; channel < this.channels; channel++) {
198
- const src = __privateGet(this, _buffer)[channel];
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
- __privateSet(this, _buffer, next);
206
- __privateSet(this, _readIndex, copyStart);
207
- __privateMethod(this, _AudioRingBuffer_instances, pruneTimestampAnchors_fn).call(this);
208
- __privateSet(this, _stalled, samplesToKeep === 0 || wasStalled && samplesToKeep < __privateMethod(this, _AudioRingBuffer_instances, refillFrames_fn).call(this));
209
- if (!__privateGet(this, _stalled)) __privateSet(this, _hasStarted, true);
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 (!__privateGet(this, _diagEnabled) || __privateGet(this, _diagReads) === 0) return null;
193
+ if (!this.#diagEnabled || this.#diagReads === 0) return null;
213
194
  const result = {
214
- reads: __privateGet(this, _diagReads),
215
- avgFill: Math.round(__privateGet(this, _diagFillSum) / __privateGet(this, _diagReads)),
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: __privateGet(this, _diagOverflowSamples)
200
+ overflowSamples: this.#diagOverflowSamples
220
201
  };
221
- __privateSet(this, _diagReads, 0);
222
- __privateSet(this, _diagFillSum, 0);
223
- __privateSet(this, _diagOverflowSamples, 0);
202
+ this.#diagReads = 0;
203
+ this.#diagFillSum = 0;
204
+ this.#diagOverflowSamples = 0;
224
205
  return result;
225
206
  }
226
- };
227
- _buffer = new WeakMap();
228
- _writeIndex = new WeakMap();
229
- _readIndex = new WeakMap();
230
- _stalled = new WeakMap();
231
- _startupFrames = new WeakMap();
232
- _resumeFrames = new WeakMap();
233
- _resumeFollowsStartup = new WeakMap();
234
- _hasStarted = new WeakMap();
235
- _capacityHeadroomFrames = new WeakMap();
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 (!__privateGet(this, _anchored)) {
302
- __privateSet(this, _anchorWallMs, now());
303
- __privateSet(this, _anchored, true);
266
+ if (!this.#anchored) {
267
+ this.#anchorWallMs = now();
268
+ this.#anchored = true;
304
269
  }
305
- __privateWrapper(this, _frameCount)._++;
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 (!__privateGet(this, _anchored)) return 0;
319
- if (__privateGet(this, _frameCount) <= this.warmupFrames) return 0;
320
- const mediaMs = __privateGet(this, _frameCount) * this.frameDurationMs;
321
- const wallMs = now() - __privateGet(this, _anchorWallMs);
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
- __privateSet(this, _frameCount, 0);
329
- __privateSet(this, _anchorWallMs, 0);
330
- __privateSet(this, _anchored, false);
317
+ this.#frameCount = 0;
318
+ this.#anchorWallMs = 0;
319
+ this.#anchored = false;
331
320
  }
332
321
  get snapshot() {
333
- const mediaMs = __privateGet(this, _frameCount) * this.frameDurationMs;
334
- const wallMs = __privateGet(this, _anchored) ? now() - __privateGet(this, _anchorWallMs) : 0;
322
+ const mediaMs = this.#frameCount * this.frameDurationMs;
323
+ const wallMs = this.#anchored ? now() - this.#anchorWallMs : 0;
335
324
  return {
336
- frameCount: __privateGet(this, _frameCount),
325
+ frameCount: this.#frameCount,
337
326
  mediaMs,
338
327
  wallMs,
339
328
  aheadMs: mediaMs - wallMs,
340
329
  thresholdMs: this.thresholdMs,
341
- anchored: __privateGet(this, _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
- __privateAdd(this, _AudioRenderPipeline_instances);
382
- __privateAdd(this, _context);
383
- __privateAdd(this, _pacer);
384
- __privateAdd(this, _mapper);
385
- __privateAdd(this, _codec);
386
- __privateAdd(this, _packetId);
387
- __privateAdd(this, _sampleRate);
388
- __privateAdd(this, _channels);
389
- __privateAdd(this, _description);
390
- __privateAdd(this, _latencyMs);
391
- __privateAdd(this, _startupFrames2);
392
- __privateAdd(this, _startupThresholdFrames);
393
- __privateAdd(this, _capacityFrames);
394
- __privateAdd(this, _diag);
395
- __privateAdd(this, _warmupFrames);
396
- __privateAdd(this, _onState);
397
- __privateAdd(this, _onDiag);
398
- __privateAdd(this, _onError);
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
- __privateSet(this, _pacer, new FrameCountPacer({
411
+ this.#pacer = new FrameCountPacer({
428
412
  frameDurationMs: auDurationMs,
429
413
  mapper: opts.mapper,
430
- warmupFrames: __privateGet(this, _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 (__privateGet(this, _readyPromise)) return __privateGet(this, _readyPromise);
436
- __privateSet(this, _readyPromise, __privateMethod(this, _AudioRenderPipeline_instances, initialize_fn).call(this));
437
- return __privateGet(this, _readyPromise);
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
- __privateGet(this, _pipeline)?.feedMoqObject(packet);
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 __privateGet(this, _pacer).computeSleepMs();
518
+ return this.#pacer.computeSleepMs();
446
519
  }
447
520
  /** Pacer snapshot (for diagnostics / UI). */
448
521
  get pacerSnapshot() {
449
- return __privateGet(this, _pacer).snapshot;
522
+ return this.#pacer.snapshot;
450
523
  }
451
524
  /** Reset pacer on source switch (MoQ ↔ multicast). */
452
525
  resetPacer() {
453
- __privateGet(this, _pacer).reset();
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
- __privateGet(this, _pacer).reset();
458
- __privateSet(this, _startupReleased, true);
530
+ this.#pacer.reset();
531
+ this.#startupReleased = true;
459
532
  const message = {
460
533
  type: "reset",
461
- generation: ++__privateWrapper(this, _workletGeneration)._,
534
+ generation: ++this.#workletGeneration,
462
535
  startUnstalled
463
536
  };
464
- __privateGet(this, _worklet)?.port.postMessage(message);
537
+ this.#worklet?.port.postMessage(message);
465
538
  }
466
539
  /** Node to connect to destination / gain. */
467
540
  get rootNode() {
468
- if (!__privateGet(this, _worklet)) throw new Error("AudioRenderPipeline.ready() must resolve first");
469
- return __privateGet(this, _worklet);
541
+ if (!this.#worklet) throw new Error("AudioRenderPipeline.ready() must resolve first");
542
+ return this.#worklet;
470
543
  }
471
544
  close() {
472
- if (__privateGet(this, _closed)) return;
473
- __privateSet(this, _closed, true);
545
+ if (this.#closed) return;
546
+ this.#closed = true;
474
547
  try {
475
- __privateGet(this, _decoder)?.close();
548
+ this.#decoder?.close();
476
549
  } catch {
477
550
  }
478
551
  try {
479
- __privateGet(this, _pipeline)?.dispose();
552
+ this.#pipeline?.dispose();
480
553
  } catch {
481
554
  }
482
555
  try {
483
- __privateGet(this, _worklet)?.disconnect();
556
+ this.#worklet?.disconnect();
484
557
  } catch {
485
558
  }
486
- __privateSet(this, _decoder, null);
487
- __privateSet(this, _pipeline, null);
488
- __privateSet(this, _worklet, null);
489
- }
490
- };
491
- _context = new WeakMap();
492
- _pacer = new WeakMap();
493
- _mapper = new WeakMap();
494
- _codec = new WeakMap();
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
- __privateSet(this, _worklet, worklet);
570
- const decoder = new AudioDecoder({
571
- output: (data) => __privateMethod(this, _AudioRenderPipeline_instances, onDecoded_fn).call(this, data),
572
- error: (err) => {
573
- var _a;
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
- return;
617
- }
618
- const timestamp = sample.timestamp;
619
- const channelData = [];
620
- for (let ch = 0; ch < sample.numberOfChannels; ch++) {
621
- const data = new Float32Array(sample.numberOfFrames);
622
- sample.copyTo(data, { format: "f32-planar", planeIndex: ch });
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,