@blockcast/fec-worker 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.
Files changed (40) hide show
  1. package/README.md +17 -7
  2. package/dist/block-order.d.ts +38 -0
  3. package/dist/block-order.d.ts.map +1 -0
  4. package/dist/block-order.js +78 -0
  5. package/dist/block-order.js.map +1 -0
  6. package/dist/fec-worker-client.d.ts +20 -7
  7. package/dist/fec-worker-client.d.ts.map +1 -1
  8. package/dist/fec-worker-client.js +106 -30
  9. package/dist/fec-worker-client.js.map +1 -1
  10. package/dist/fec-worker-types.d.ts +153 -25
  11. package/dist/fec-worker-types.d.ts.map +1 -1
  12. package/dist/fec-worker.js +382 -88
  13. package/dist/fec-worker.js.map +4 -4
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/shred-fec-worker.js +23 -2
  18. package/dist/shred-fec-worker.js.map +2 -2
  19. package/dist/shred-worker-types.d.ts +16 -1
  20. package/dist/shred-worker-types.d.ts.map +1 -1
  21. package/dist/shred-worker-types.js +16 -1
  22. package/dist/shred-worker-types.js.map +1 -1
  23. package/package.json +8 -3
  24. package/src/block-order.ts +93 -0
  25. package/src/fec-worker-client.ts +115 -32
  26. package/src/fec-worker-types.ts +152 -21
  27. package/src/fec-worker.ts +583 -129
  28. package/src/index.ts +3 -0
  29. package/src/shred-fec-worker.ts +34 -3
  30. package/src/shred-worker-types.ts +16 -1
  31. package/src/__tests__/fec-worker-alta.test.ts +0 -231
  32. package/src/__tests__/fec-worker-cleanup-integration.test.ts +0 -250
  33. package/src/__tests__/fec-worker-pending-queue.test.ts +0 -315
  34. package/src/__tests__/fec-worker-repair-size.test.ts +0 -1029
  35. package/src/__tests__/fec-worker-wasm-contract.test.ts +0 -84
  36. package/src/__tests__/shred-fec-worker.test.ts +0 -448
  37. package/src/fec-worker-client.test.ts +0 -530
  38. package/src/fec-worker-types.test.ts +0 -400
  39. package/src/shred-worker-client.test.ts +0 -182
  40. package/src/transfer.test.ts +0 -24
@@ -10,6 +10,22 @@
10
10
 
11
11
  // --- Config ---
12
12
 
13
+ /**
14
+ * FEC block profile (BLO-35597, ISO/IEC 23008-1 Annex C): the catalog
15
+ * `fec.blockProfile` token. Both profiles carry identical wire tuples whose
16
+ * block math differs, so it is required and never inferred.
17
+ *
18
+ * - `k-padded-v1` (legacy): SBN = floor(SS_ID / K), ESI = SS_ID % K;
19
+ * SS_Start = SBN * K; repair ESI = K + RS_ID.
20
+ * - `iso-ssbg1-v1` (C.3.3 ssbg_mode1): SS_ID increments by one per source
21
+ * packet mod 2^32; a block is `[SS_Start, SS_Start + SSB_length)` keyed by
22
+ * SS_Start; `k` is only the maximum SSB_length; repair ESI =
23
+ * SSB_length + RS_ID.
24
+ *
25
+ * Mirrors @blockcast/mmt-base `FecBlockProfile`.
26
+ */
27
+ export type FecBlockProfile = "k-padded-v1" | "iso-ssbg1-v1";
28
+
13
29
  /** Per-track FEC configuration. Matches design spec section 3.2 FecTrackConfig. */
14
30
  export interface FecTrackConfig {
15
31
  /** Codec string, e.g. 'avc1.64001f', 'mp4a.40.2' */
@@ -27,13 +43,22 @@ export interface FecTrackConfig {
27
43
 
28
44
  /** FEC algorithm identifier: 'raptor', 'reed-solomon', 'none' */
29
45
  algorithm: string;
30
- /** Source symbols per block (K) */
46
+ /** Block profile; the Worker refuses to configure without a known token. */
47
+ blockProfile: FecBlockProfile;
48
+ /** Source symbols per block (K). Under `iso-ssbg1-v1` the maximum SSB_length (kMax). */
31
49
  k: number;
32
50
  /** Repair symbols per block (P). NOT symbol size. */
33
51
  repairCount: number;
34
52
  /** Bytes per symbol */
35
53
  symbolSize: number;
36
- /** Interleave depth in frames (D) */
54
+ /** Retained decoder horizon as a BLOCK COUNT (SBN distance), not D.
55
+ * `isRetiredSbn` retires blocks more than this many blocks behind
56
+ * `liveSbn` (serial order on SS_ID, BLO-35597 iso:5) and
57
+ * `MmtFecDecoder::new` documents it as "the number of interleaved blocks".
58
+ * Under `iso-ssbg1-v1` the distance is `isoBlocksBehind(SS_ID, live SS_ID, k)`
59
+ * (block-order.ts), the lower bound every ISO receiver layer shares.
60
+ * Never assign `deriveFecInterleaveDepth()` here — that returns media units
61
+ * per block, which is a different quantity (BLO-35597). */
37
62
  interleaveDepth: number;
38
63
  /** Interleave depth in milliseconds (D * frameDurationMs) */
39
64
  interleaveMs: number;
@@ -54,16 +79,22 @@ export interface FecTrackConfig {
54
79
 
55
80
  /** Per-block snapshot for live block visualization and stats. */
56
81
  export interface FecBlockSnapshot {
57
- /** Source block number */
82
+ /** Block key: the source block number under `k-padded-v1`, the block's
83
+ * SS_Start under `iso-ssbg1-v1`. */
58
84
  sbn: number;
59
85
  /** Current block state */
60
- state: "collecting" | "complete" | "recovered" | "failed" | "green-fill";
86
+ state: "collecting" | "complete" | "recovered" | "failed";
61
87
  /** Number of source symbols received */
62
88
  sourceReceived: number;
63
89
  /** Number of repair symbols received */
64
90
  repairReceived: number;
65
- /** Expected source symbols (K) */
91
+ /** Configured source-block geometry (K); under `iso-ssbg1-v1` the block's SSB_length. */
66
92
  k: number;
93
+ /** Authored source symbols (SSB_length) once a repair symbol declared it.
94
+ * Under `iso-ssbg1-v1` a block is only known from a repair symbol, so it is
95
+ * always set — except on a cleanup tombstone for a block the Worker never
96
+ * saw, whose length nothing declared. */
97
+ ssbLength?: number;
67
98
  /** Expected repair symbols (P) */
68
99
  repairCount: number;
69
100
  /** Timestamp of first symbol arrival (ms) */
@@ -97,8 +128,6 @@ export interface FecTrackStats {
97
128
  blocksRecovered: number;
98
129
  /** Blocks that failed (unrecoverable) */
99
130
  blocksFailed: number;
100
- /** Frames replaced with green-fill */
101
- greenFillFrames: number;
102
131
  /** Recovery success rate 0-100 */
103
132
  recoveryRate: number;
104
133
 
@@ -139,11 +168,12 @@ export interface FecTrackStats {
139
168
  /** Blocks with invalid relay signature */
140
169
  relayBlocksFailed: number;
141
170
 
142
- /** Current live source block number */
171
+ /** Live media position: the newest source block number under
172
+ * `k-padded-v1`, the newest accepted SS_ID / SS_Start under `iso-ssbg1-v1`. */
143
173
  liveSbn: number;
144
174
  /** Active block snapshots */
145
175
  blocks: FecBlockSnapshot[];
146
- /** Number of blocks in the interleave window */
176
+ /** Number of blocks in the FEC block span */
147
177
  interleaveWindowBlocks: number;
148
178
 
149
179
  /** Average recovery latency (ms) */
@@ -152,6 +182,51 @@ export interface FecTrackStats {
152
182
  maxRecoveryMs: number;
153
183
  /** Repair symbols rejected before WASM because their byte length did not match T. */
154
184
  repairSymbolSizeMismatch?: number;
185
+ /**
186
+ * Source symbols whose SBN had already fallen below the retained window.
187
+ *
188
+ * This and its two peers below are dropped before WASM and narrow the
189
+ * explanations for a block that settles at K-1 source symbols (BLO-35597
190
+ * defect 2): a late symbol past retention, a symbol for a block that had
191
+ * already settled (complete, recovered *or failed*), or a repeated
192
+ * (SBN, ESI).
193
+ *
194
+ * They narrow; they do not uniquely attribute. A symbol lost in transit
195
+ * never reaches the worker and increments nothing, so all three reading
196
+ * zero on a short block means only that no *receiver-side* drop was
197
+ * recorded — wire loss and a short authored block both remain open. Use
198
+ * `FecBlockSnapshot.ssbLength` to separate them: below `k` is a genuinely
199
+ * short authored block, equal to `k` with fewer accepted is loss-or-late.
200
+ * `ssbLength` is undefined until a repair symbol declares it, in which case
201
+ * both stay open.
202
+ *
203
+ * All three are cumulative across decoder epochs: `configure` clears block
204
+ * tracking but none of them, so they stay comparable against the
205
+ * `sourceSymbols` denominator. Only a reset zeroes them.
206
+ */
207
+ sourceDroppedRetired?: number;
208
+ /** Source symbols for a block that had already completed, recovered or failed. */
209
+ sourceDroppedTerminal?: number;
210
+ /**
211
+ * Source symbols repeating an (SBN, ESI) already accepted in the current
212
+ * epoch; `configure` clears the accepted-ESI sets.
213
+ *
214
+ * Unlike the other two, this one cannot fire once a block settles — the
215
+ * terminal check returns first, and marking a block terminal retires its
216
+ * accepted-ESI set anyway — so a non-zero value means the repeat reached
217
+ * the worker while the block was **still collecting**. It attributes the
218
+ * timing, not the origin: usually a duplicate packet on the wire,
219
+ * sometimes an SBN/ESI derivation collision.
220
+ */
221
+ sourceDroppedDuplicate?: number;
222
+ /**
223
+ * `iso-ssbg1-v1` only: accepted source symbols whose block never became
224
+ * known (no repair symbol named a range containing them) before their
225
+ * SS_ID fell behind the retained horizon. They were already fed to the
226
+ * decoder and routed directly; this counts the bookkeeping retired with
227
+ * them, i.e. evidence that a block's repair symbols never arrived.
228
+ */
229
+ sourceUnplacedRetired?: number;
155
230
  /** Native decoder counters, converted to numbers so browser diagnostics remain JSON-safe. */
156
231
  decoder?: {
157
232
  sourcePackets: number;
@@ -168,7 +243,15 @@ export interface FecTrackStats {
168
243
  export type FecWorkerErrorCode =
169
244
  | "recovered-symbol-size-unconfigured"
170
245
  | "recovered-block-too-short"
246
+ | "recovered-symbol-ssb-length-invalid"
171
247
  | "repair-symbol-size-mismatch"
248
+ | "repair-ssb-length-conflict"
249
+ /** `iso-ssbg1-v1`: a repair's `[SS_Start, SS_Start + SSB_length)` overlaps a different known block. */
250
+ | "repair-block-overlap"
251
+ /** `iso-ssbg1-v1`: a recovered symbol's ssbg_mode1 length field exceeds T-2 or its padding is non-zero. */
252
+ | "recovered-ssbg1-symbol-invalid"
253
+ /** `iso-ssbg1-v1`: the decoder returned a recovered block for a source the Worker could not place in a known block. */
254
+ | "recovered-block-unplaced"
172
255
  | "reset-failed"
173
256
  | "unknown-command"
174
257
  | "command-failed"
@@ -183,10 +266,19 @@ export interface FrameMeta {
183
266
  altaVerified: boolean | null;
184
267
  /** Whether this frame was recovered via FEC (not received directly) */
185
268
  recovered: boolean;
186
- /** Source block number this frame belongs to */
269
+ /** Block key this frame belongs to: SBN (`k-padded-v1`) or SS_Start (`iso-ssbg1-v1`) */
187
270
  sbn: number;
188
- /** Encoding Symbol ID within the recovered source block (0..K-1). */
271
+ /** Encoding Symbol ID within the recovered source block (0..ssbLength-1).
272
+ * Under `iso-ssbg1-v1` a recovered frame is the full T-byte ssbg_mode1
273
+ * symbol (length field, packet, zero padding); the Worker validates but
274
+ * never trims it. */
189
275
  esi?: number;
276
+ /**
277
+ * Authored source-symbol count of the recovered block (the repair
278
+ * symbols' SSB_length, 1..K). Recovered frames are ESI 0..ssbLength-1;
279
+ * ESIs ssbLength..K-1 are encoder zero padding and are never emitted.
280
+ */
281
+ ssbLength?: number;
190
282
  }
191
283
 
192
284
  // --- Worker commands (main -> worker) ---
@@ -209,9 +301,13 @@ export type FecWorkerCommand =
209
301
  | {
210
302
  type: "feedSource";
211
303
  /** Flat 32-bit Source Symbol ID from the MMTP Source FEC Payload ID
212
- * (ISO 23008-1 §C.5.2). The WASM decoder derives SBN = floor(ssId/K)
213
- * and ESI = ssId % K internally — do NOT pre-compute. */
304
+ * (ISO 23008-1 §C.5.2). Under `k-padded-v1` the WASM decoder derives
305
+ * SBN = floor(ssId/K) and ESI = ssId % K internally — do NOT
306
+ * pre-compute. Under `iso-ssbg1-v1` SS_IDs are consecutive per source
307
+ * packet mod 2^32 and the block comes from a repair's range. */
214
308
  ssId: number;
309
+ /** `k-padded-v1`: the source symbol. `iso-ssbg1-v1`: the protected
310
+ * packet exactly, unpadded and without its SS_ID (at most T-2 bytes). */
215
311
  data: ArrayBuffer;
216
312
  ts: number;
217
313
  /** Optional ALTA authenticator bytes (detached mode — 08-07 MMTP
@@ -229,18 +325,30 @@ export type FecWorkerCommand =
229
325
  }
230
326
  | {
231
327
  type: "feedRepair";
232
- /** SS_Start: SS_ID of the first source symbol in the block
233
- * (= SBN * SSB_length), from the MMTP Repair FEC Payload ID
234
- * (ISO 23008-1 §C.5.3). */
328
+ /** SS_Start: SS_ID of the first source symbol in the block, from the
329
+ * MMTP Repair FEC Payload ID (ISO 23008-1 §C.5.3). `k-padded-v1`:
330
+ * = SBN * configured K. `iso-ssbg1-v1`: any uint32; the block key. */
235
331
  ssStart: number;
236
- /** SSB_length: K for this block (variable-K allowed per spec). */
332
+ /** Authored source-symbol count for this block; 1..configured K. */
237
333
  ssbLength: number;
238
- /** RS_ID: repair symbol index within the block; WASM maps to
239
- * repair ESI = SSB_length + RS_ID. */
334
+ /** RS_ID: repair symbol index within the block; WASM maps to repair
335
+ * ESI = configured K + RS_ID (`k-padded-v1`) or SSB_length + RS_ID
336
+ * (`iso-ssbg1-v1`). */
240
337
  rsId: number;
241
338
  data: ArrayBuffer;
242
339
  ts: number;
243
340
  }
341
+ | {
342
+ /**
343
+ * Every source and repair symbol the page fed in one task, in feed
344
+ * order, sharing one transfer list. The client sends symbols this
345
+ * way instead of one message per symbol (BLO-36599); the Worker
346
+ * handles each record exactly like the single-symbol command of the
347
+ * same `type`, and a record that fails does not stop the others.
348
+ */
349
+ type: "feedSymbols";
350
+ symbols: FecSymbolCommand[];
351
+ }
244
352
  | {
245
353
  type: "relayBlockSig";
246
354
  sbn: number;
@@ -249,12 +357,26 @@ export type FecWorkerCommand =
249
357
  }
250
358
  | {
251
359
  type: "cleanup";
252
- /** Every timed-out block covered by this coalesced queue-drain barrier. */
360
+ /** Every timed-out block covered by this coalesced queue-drain barrier,
361
+ * by block key: SBN (`k-padded-v1`) or SS_Start (`iso-ssbg1-v1`). */
253
362
  sbns: number[];
254
363
  }
255
364
  | { type: "reset" }
365
+ /**
366
+ * Ask the Worker to post a blockUpdate on every block transition. Without
367
+ * it the Worker posts none: each one is a structured clone and a task on
368
+ * the page thread, and the player does not consume them (BLO-36599).
369
+ * Sent only to a Worker that announced it in `capabilities`.
370
+ */
371
+ | { type: "subscribeBlockUpdates" }
256
372
  | { type: "dispose" };
257
373
 
374
+ /** One symbol record of a `feedSymbols` batch. */
375
+ export type FecSymbolCommand = Extract<
376
+ FecWorkerCommand,
377
+ { type: "feedSource" } | { type: "feedRepair" }
378
+ >;
379
+
258
380
  // --- Worker events (worker -> main) ---
259
381
 
260
382
  /** Discriminated union of all events emitted from Worker to main thread. */
@@ -262,7 +384,16 @@ export type FecWorkerEvent =
262
384
  | { type: "frame"; data: ArrayBuffer; meta: FrameMeta }
263
385
  | { type: "snapshot"; stats: FecTrackStats }
264
386
  | { type: "blockUpdate"; block: FecBlockSnapshot }
265
- | { type: "greenFill"; sbn: number; data: ArrayBuffer }
266
387
  | { type: "cleanupComplete"; sbns: number[] }
267
388
  | { type: "resetComplete" }
389
+ /**
390
+ * Optional commands this Worker accepts, posted once it is configured.
391
+ * The client uses `feedSymbols` only after the Worker names it, so a page
392
+ * that meets an older cached Worker bundle keeps sending one command per
393
+ * symbol instead of a batch that Worker would reject (BLO-36599).
394
+ */
395
+ | { type: "capabilities"; commands: FecWorkerOptionalCommand[] }
268
396
  | { type: "error"; message: string; code?: FecWorkerErrorCode };
397
+
398
+ /** Commands a client may send only after the Worker announced them. */
399
+ export type FecWorkerOptionalCommand = "feedSymbols" | "subscribeBlockUpdates";