@blockcast/mmt-render 0.1.0-main.dd5d220269e4 → 0.1.0-main.e43a31ef704b

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 CHANGED
@@ -8,7 +8,7 @@ Shared render pipeline for MMT players: FEC-aware wall-clock pacing, per-AU pres
8
8
  pnpm add @blockcast/mmt-render
9
9
  ```
10
10
 
11
- Peer-installs through normal deps: `@blockcast/mmt-container` (provides `MmtpDecodePipeline` and the FEC-aware MFU reassembler) and `@blockcast/mmt-fec` (provides `FecBlockMapper`, which carries `pacingThresholdMs`, `K`, `D`, `symbolsPerGroup`).
11
+ Peer-installs through normal deps: `@blockcast/mmt-container` (provides `MmtpDecodePipeline` and the FEC-aware MFU reassembler) and `@blockcast/mmt-fec` (provides `FecBlockMapper`, which carries `pacingThresholdMs`, `K`, `D` and the catalog's required `fec.blockProfile`).
12
12
 
13
13
  ## Quick start (audio)
14
14
 
@@ -17,7 +17,8 @@ import { AudioRenderPipeline } from "@blockcast/mmt-render/audio"
17
17
  import { FecBlockMapper } from "@blockcast/mmt-fec"
18
18
 
19
19
  const ctx = new AudioContext({ sampleRate: 48000 })
20
- const mapper = new FecBlockMapper({ interleaveDepthMs: 66, gopDurationMs: 21.33, k: 32 })
20
+ // blockProfile is the catalog `fec.blockProfile`: required, never defaulted.
21
+ const mapper = new FecBlockMapper({ blockProfile: "k-padded-v1", interleaveDepthMs: 66, gopDurationMs: 21.33, k: 32 })
21
22
 
22
23
  const audio = new AudioRenderPipeline({
23
24
  context: ctx,
@@ -103,7 +104,7 @@ Media-agnostic wall-clock pacer. Anchors on the first `notifyFrame()` call; comp
103
104
 
104
105
  ### `InterleaveTiming` (`src/pacing/interleave-timing.ts`)
105
106
 
106
- Per-AU presentation-time reconstruction from FEC coordinates. Implements `draft-ramadan-moq-fec-00 §8.3`:
107
+ Per-AU presentation-time reconstruction from FEC coordinates: the `k-padded-v1` K-stride pacing model of `draft-ramadan-moq-fec-00 §8.3`, with `symbolsPerGroup = ceil(K / D)` computed here. It is pacing arithmetic, not an FEC block locator, and refuses a mapper under any other `fec.blockProfile`:
107
108
 
108
109
  ```
109
110
  au_index_in_block = groupInBlock × symbolsPerGroup + objectId
@@ -189,7 +190,7 @@ There is **no CMCD `x-bc-*` emission, no catalog (CMSF) inspector, and no MPU/MF
189
190
  ## Related packages
190
191
 
191
192
  - `@blockcast/mmt-container` — `MmtpDecodePipeline` (consumed for AU framing), MFU reassembly, catalog parser
192
- - `@blockcast/mmt-fec` — `FecBlockMapper` (provides `pacingThresholdMs`, `K`, `D`, `symbolsPerGroup`); `NullBlockMapper` disables pacing
193
+ - `@blockcast/mmt-fec` — `FecBlockMapper` (provides `pacingThresholdMs`, `K`, `D`, `blockProfile`); `NullBlockMapper` disables pacing
193
194
  - `@blockcast/mmt-transport` — produces the MoQ objects that feed `feedMoqObject()`
194
195
 
195
196
  ## Testing
@@ -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 (!__privateGet(this, _anchored)) {
38
- __privateSet(this, _anchorWallMs, now());
39
- __privateSet(this, _anchored, true);
19
+ if (!this.#anchored) {
20
+ this.#anchorWallMs = now();
21
+ this.#anchored = true;
40
22
  }
41
- __privateWrapper(this, _frameCount)._++;
23
+ this.#frameCount++;
42
24
  }
43
25
  /**
44
26
  * Compute sleep ms before reading the next MoQ object.
@@ -72,39 +54,36 @@ var FrameCountPacer = class {
72
54
  */
73
55
  computeSleepMs(observedDelayMs) {
74
56
  if (this.thresholdMs <= 0) return 0;
75
- if (!__privateGet(this, _anchored)) return 0;
76
- if (__privateGet(this, _frameCount) <= this.warmupFrames) return 0;
57
+ if (!this.#anchored) return 0;
58
+ if (this.#frameCount <= this.warmupFrames) return 0;
77
59
  if (observedDelayMs !== void 0 && Number.isFinite(observedDelayMs) && observedDelayMs > this.thresholdMs) {
78
60
  return 0;
79
61
  }
80
- const mediaMs = __privateGet(this, _frameCount) * this.frameDurationMs;
81
- const wallMs = now() - __privateGet(this, _anchorWallMs);
62
+ const mediaMs = this.#frameCount * this.frameDurationMs;
63
+ const wallMs = now() - this.#anchorWallMs;
82
64
  const aheadMs = mediaMs - wallMs;
83
65
  if (aheadMs <= this.thresholdMs) return 0;
84
66
  return aheadMs - this.thresholdMs / 2;
85
67
  }
86
68
  /** Reset anchor + counter. Call on source switch (MoQ ↔ multicast). */
87
69
  reset() {
88
- __privateSet(this, _frameCount, 0);
89
- __privateSet(this, _anchorWallMs, 0);
90
- __privateSet(this, _anchored, false);
70
+ this.#frameCount = 0;
71
+ this.#anchorWallMs = 0;
72
+ this.#anchored = false;
91
73
  }
92
74
  get snapshot() {
93
- const mediaMs = __privateGet(this, _frameCount) * this.frameDurationMs;
94
- const wallMs = __privateGet(this, _anchored) ? now() - __privateGet(this, _anchorWallMs) : 0;
75
+ const mediaMs = this.#frameCount * this.frameDurationMs;
76
+ const wallMs = this.#anchored ? now() - this.#anchorWallMs : 0;
95
77
  return {
96
- frameCount: __privateGet(this, _frameCount),
78
+ frameCount: this.#frameCount,
97
79
  mediaMs,
98
80
  wallMs,
99
81
  aheadMs: mediaMs - wallMs,
100
82
  thresholdMs: this.thresholdMs,
101
- anchored: __privateGet(this, _anchored)
83
+ anchored: this.#anchored
102
84
  };
103
85
  }
104
86
  };
105
- _frameCount = new WeakMap();
106
- _anchorWallMs = new WeakMap();
107
- _anchored = new WeakMap();
108
87
  function now() {
109
88
  if (typeof performance !== "undefined" && typeof performance.now === "function") {
110
89
  return performance.now();
@@ -135,260 +114,226 @@ function workletModuleUrl() {
135
114
  var WORKLET_PROCESSOR_NAME = "mmt-render";
136
115
 
137
116
  // src/audio/audio-render-pipeline.ts
138
- 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;
139
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;
140
144
  constructor(opts) {
141
- __privateAdd(this, _AudioRenderPipeline_instances);
142
- __privateAdd(this, _context);
143
- __privateAdd(this, _pacer);
144
- __privateAdd(this, _mapper);
145
- __privateAdd(this, _codec);
146
- __privateAdd(this, _packetId);
147
- __privateAdd(this, _sampleRate);
148
- __privateAdd(this, _channels);
149
- __privateAdd(this, _description);
150
- __privateAdd(this, _latencyMs);
151
- __privateAdd(this, _startupFrames);
152
- __privateAdd(this, _startupThresholdFrames);
153
- __privateAdd(this, _capacityFrames);
154
- __privateAdd(this, _diag);
155
- __privateAdd(this, _warmupFrames);
156
- __privateAdd(this, _onState);
157
- __privateAdd(this, _onDiag);
158
- __privateAdd(this, _onError);
159
- __privateAdd(this, _reassemblyPolicy);
160
- __privateAdd(this, _decoder, null);
161
- __privateAdd(this, _pipeline, null);
162
- __privateAdd(this, _worklet, null);
163
- __privateAdd(this, _fecClient);
164
- __privateAdd(this, _closed, false);
165
- __privateAdd(this, _readyPromise, null);
166
- __privateAdd(this, _workletGeneration, 0);
167
- __privateAdd(this, _startupReleased, false);
168
- __privateSet(this, _context, opts.context);
169
- __privateSet(this, _mapper, opts.mapper);
170
- __privateSet(this, _codec, opts.codec);
171
- __privateSet(this, _packetId, opts.packetId);
172
- __privateSet(this, _sampleRate, opts.sampleRate);
173
- __privateSet(this, _channels, opts.channels);
174
- __privateSet(this, _description, opts.description);
175
- __privateSet(this, _latencyMs, opts.latencyMs);
176
- __privateSet(this, _startupFrames, opts.startupFrames);
177
- __privateSet(this, _startupThresholdFrames, opts.startupFrames ?? Math.ceil(opts.sampleRate * (opts.latencyMs / 1e3)));
178
- __privateSet(this, _capacityFrames, opts.capacityFrames);
179
- __privateSet(this, _diag, opts.diag ?? false);
180
- __privateSet(this, _warmupFrames, opts.warmupFrames ?? 0);
181
- __privateSet(this, _onState, opts.onState);
182
- __privateSet(this, _onDiag, opts.onDiag);
183
- __privateSet(this, _onError, opts.onError);
184
- __privateSet(this, _fecClient, opts.fecClient);
185
- __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;
186
163
  const auDurationMs = auDurationMsFromCodec(opts.codec, opts.sampleRate);
187
- __privateSet(this, _pacer, new FrameCountPacer({
164
+ this.#pacer = new FrameCountPacer({
188
165
  frameDurationMs: auDurationMs,
189
166
  mapper: opts.mapper,
190
- warmupFrames: __privateGet(this, _warmupFrames)
191
- }));
167
+ warmupFrames: this.#warmupFrames
168
+ });
192
169
  }
193
170
  /** Load worklet module + create decoder + pipeline. Call once. */
194
171
  async ready() {
195
- if (__privateGet(this, _readyPromise)) return __privateGet(this, _readyPromise);
196
- __privateSet(this, _readyPromise, __privateMethod(this, _AudioRenderPipeline_instances, initialize_fn).call(this));
197
- return __privateGet(this, _readyPromise);
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;
198
264
  }
199
265
  /** Feed one MoQ object (raw MMTP packet). */
200
266
  feedMoqObject(packet) {
201
- __privateGet(this, _pipeline)?.feedMoqObject(packet);
267
+ this.#pipeline?.feedMoqObject(packet);
202
268
  }
203
269
  /** ms to sleep before the next object read. 0 when not anchored / not ahead. */
204
270
  paceSleepMs() {
205
- return __privateGet(this, _pacer).computeSleepMs();
271
+ return this.#pacer.computeSleepMs();
206
272
  }
207
273
  /** Pacer snapshot (for diagnostics / UI). */
208
274
  get pacerSnapshot() {
209
- return __privateGet(this, _pacer).snapshot;
275
+ return this.#pacer.snapshot;
210
276
  }
211
277
  /** Reset pacer on source switch (MoQ ↔ multicast). */
212
278
  resetPacer() {
213
- __privateGet(this, _pacer).reset();
279
+ this.#pacer.reset();
214
280
  }
215
281
  /** Drop queued PCM and its timestamp epoch at an explicit source boundary. */
216
282
  resetPlayout(startUnstalled = false) {
217
- __privateGet(this, _pacer).reset();
218
- __privateSet(this, _startupReleased, true);
283
+ this.#pacer.reset();
284
+ this.#startupReleased = true;
219
285
  const message = {
220
286
  type: "reset",
221
- generation: ++__privateWrapper(this, _workletGeneration)._,
287
+ generation: ++this.#workletGeneration,
222
288
  startUnstalled
223
289
  };
224
- __privateGet(this, _worklet)?.port.postMessage(message);
290
+ this.#worklet?.port.postMessage(message);
225
291
  }
226
292
  /** Node to connect to destination / gain. */
227
293
  get rootNode() {
228
- if (!__privateGet(this, _worklet)) throw new Error("AudioRenderPipeline.ready() must resolve first");
229
- return __privateGet(this, _worklet);
294
+ if (!this.#worklet) throw new Error("AudioRenderPipeline.ready() must resolve first");
295
+ return this.#worklet;
230
296
  }
231
297
  close() {
232
- if (__privateGet(this, _closed)) return;
233
- __privateSet(this, _closed, true);
298
+ if (this.#closed) return;
299
+ this.#closed = true;
234
300
  try {
235
- __privateGet(this, _decoder)?.close();
301
+ this.#decoder?.close();
236
302
  } catch {
237
303
  }
238
304
  try {
239
- __privateGet(this, _pipeline)?.dispose();
305
+ this.#pipeline?.dispose();
240
306
  } catch {
241
307
  }
242
308
  try {
243
- __privateGet(this, _worklet)?.disconnect();
309
+ this.#worklet?.disconnect();
244
310
  } catch {
245
311
  }
246
- __privateSet(this, _decoder, null);
247
- __privateSet(this, _pipeline, null);
248
- __privateSet(this, _worklet, null);
312
+ this.#decoder = null;
313
+ this.#pipeline = null;
314
+ this.#worklet = null;
249
315
  }
250
- };
251
- _context = new WeakMap();
252
- _pacer = new WeakMap();
253
- _mapper = new WeakMap();
254
- _codec = new WeakMap();
255
- _packetId = new WeakMap();
256
- _sampleRate = new WeakMap();
257
- _channels = new WeakMap();
258
- _description = new WeakMap();
259
- _latencyMs = new WeakMap();
260
- _startupFrames = new WeakMap();
261
- _startupThresholdFrames = new WeakMap();
262
- _capacityFrames = new WeakMap();
263
- _diag = new WeakMap();
264
- _warmupFrames = new WeakMap();
265
- _onState = new WeakMap();
266
- _onDiag = new WeakMap();
267
- _onError = new WeakMap();
268
- _reassemblyPolicy = new WeakMap();
269
- _decoder = new WeakMap();
270
- _pipeline = new WeakMap();
271
- _worklet = new WeakMap();
272
- _fecClient = new WeakMap();
273
- _closed = new WeakMap();
274
- _readyPromise = new WeakMap();
275
- _workletGeneration = new WeakMap();
276
- _startupReleased = new WeakMap();
277
- _AudioRenderPipeline_instances = new WeakSet();
278
- initialize_fn = async function() {
279
- await __privateGet(this, _context).audioWorklet.addModule(workletModuleUrl());
280
- const worklet = new AudioWorkletNode(__privateGet(this, _context), WORKLET_PROCESSOR_NAME, {
281
- numberOfInputs: 0,
282
- numberOfOutputs: 1,
283
- outputChannelCount: [__privateGet(this, _channels)]
284
- });
285
- const initMsg = {
286
- type: "init",
287
- generation: ++__privateWrapper(this, _workletGeneration)._,
288
- channels: __privateGet(this, _channels),
289
- rate: __privateGet(this, _sampleRate),
290
- latencyMs: __privateGet(this, _latencyMs),
291
- startupFrames: __privateGet(this, _startupFrames),
292
- capacityFrames: __privateGet(this, _capacityFrames),
293
- holdUntilStart: true,
294
- diag: __privateGet(this, _diag)
295
- };
296
- worklet.port.postMessage(initMsg);
297
- worklet.port.onmessage = (ev) => {
298
- var _a, _b;
299
- const m = ev.data;
300
- if (m.type === "state") {
301
- if (m.generation !== __privateGet(this, _workletGeneration)) return;
302
- (_a = __privateGet(this, _onState)) == null ? void 0 : _a.call(this, {
303
- timestampUs: m.timestamp,
304
- stalled: m.stalled,
305
- bufferedFrames: m.bufferedFrames,
306
- writtenFrames: m.writtenFrames
307
- });
308
- } else if (m.type === "ringBufDiag") {
309
- if (m.generation !== __privateGet(this, _workletGeneration)) return;
310
- (_b = __privateGet(this, _onDiag)) == null ? void 0 : _b.call(this, {
311
- reads: m.reads,
312
- avgFill: m.avgFill,
313
- capacity: m.capacity,
314
- skipsPerSec: m.skipsPerSec,
315
- dupsPerSec: m.dupsPerSec,
316
- overflowSamplesPerSec: m.overflowSamplesPerSec,
317
- underflowSamplesPerSec: m.underflowSamplesPerSec
318
- });
319
- } else if (m.type === "writeAck") {
320
- if (m.generation !== __privateGet(this, _workletGeneration) || __privateGet(this, _startupReleased) || m.bufferedFrames < __privateGet(this, _startupThresholdFrames)) return;
321
- __privateSet(this, _startupReleased, true);
322
- const start = {
323
- type: "start",
324
- generation: __privateGet(this, _workletGeneration)
325
- };
326
- worklet.port.postMessage(start);
327
- }
328
- };
329
- __privateSet(this, _worklet, worklet);
330
- const decoder = new AudioDecoder({
331
- output: (data) => __privateMethod(this, _AudioRenderPipeline_instances, onDecoded_fn).call(this, data),
332
- error: (err) => {
333
- var _a;
334
- return (_a = __privateGet(this, _onError)) == null ? void 0 : _a.call(this, err);
316
+ // ─── Private ─────────────────────────────────────────────────────
317
+ #onDecoded(sample) {
318
+ if (this.#closed || !this.#worklet) {
319
+ sample.close();
320
+ return;
335
321
  }
336
- });
337
- const config = {
338
- codec: __privateGet(this, _codec),
339
- sampleRate: __privateGet(this, _sampleRate),
340
- numberOfChannels: __privateGet(this, _channels),
341
- ...__privateGet(this, _description) ? { description: __privateGet(this, _description) } : {}
342
- };
343
- decoder.configure(config);
344
- __privateSet(this, _decoder, decoder);
345
- const pipeline = new MmtpDecodePipeline();
346
- pipeline.configure({
347
- codec: __privateGet(this, _codec),
348
- packetId: __privateGet(this, _packetId),
349
- description: __privateGet(this, _description),
350
- reorderWindow: 0,
351
- // audio doesn't need reorder — AAC/Opus frames are self-contained
352
- reassemblyPolicy: __privateGet(this, _reassemblyPolicy)
353
- });
354
- if (__privateGet(this, _fecClient)) pipeline.setFecClient(__privateGet(this, _fecClient));
355
- pipeline.onFrame((codecData, meta) => {
356
- var _a;
357
- if (!__privateGet(this, _decoder) || __privateGet(this, _decoder).state !== "configured") return;
358
- if (codecData.byteLength === 0) return;
359
- __privateGet(this, _pacer).notifyFrame();
360
- try {
361
- __privateGet(this, _decoder).decode(new EncodedAudioChunk({
362
- type: "key",
363
- data: codecData,
364
- timestamp: meta.timestamp
365
- }));
366
- } catch (err) {
367
- (_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);
368
328
  }
369
- });
370
- __privateSet(this, _pipeline, pipeline);
371
- };
372
- // ─── Private ─────────────────────────────────────────────────────
373
- onDecoded_fn = function(sample) {
374
- if (__privateGet(this, _closed) || !__privateGet(this, _worklet)) {
375
329
  sample.close();
376
- return;
377
- }
378
- const timestamp = sample.timestamp;
379
- const channelData = [];
380
- for (let ch = 0; ch < sample.numberOfChannels; ch++) {
381
- const data = new Float32Array(sample.numberOfFrames);
382
- sample.copyTo(data, { format: "f32-planar", planeIndex: ch });
383
- 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));
384
336
  }
385
- sample.close();
386
- const msg = {
387
- type: "data",
388
- data: channelData,
389
- timestamp
390
- };
391
- __privateGet(this, _worklet).port.postMessage(msg, channelData.map((d) => d.buffer));
392
337
  };
393
338
  export {
394
339
  AudioRenderPipeline