@blockcast/mmt-fec 0.1.0-main.0399efef1f07

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 (69) hide show
  1. package/README.md +270 -0
  2. package/dist/.build-stamp +0 -0
  3. package/dist/block-manager.d.ts +82 -0
  4. package/dist/block-manager.d.ts.map +1 -0
  5. package/dist/block-manager.js +296 -0
  6. package/dist/block-manager.js.map +1 -0
  7. package/dist/block-processor.d.ts +72 -0
  8. package/dist/block-processor.d.ts.map +1 -0
  9. package/dist/block-processor.js +192 -0
  10. package/dist/block-processor.js.map +1 -0
  11. package/dist/cmaf-recovery.d.ts +46 -0
  12. package/dist/cmaf-recovery.d.ts.map +1 -0
  13. package/dist/cmaf-recovery.js +96 -0
  14. package/dist/cmaf-recovery.js.map +1 -0
  15. package/dist/cmaf-symbols.d.ts +33 -0
  16. package/dist/cmaf-symbols.d.ts.map +1 -0
  17. package/dist/cmaf-symbols.js +57 -0
  18. package/dist/cmaf-symbols.js.map +1 -0
  19. package/dist/fec-block-mapper.d.ts +185 -0
  20. package/dist/fec-block-mapper.d.ts.map +1 -0
  21. package/dist/fec-block-mapper.js +273 -0
  22. package/dist/fec-block-mapper.js.map +1 -0
  23. package/dist/index.d.ts +22 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +22 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/loc-symbols.d.ts +20 -0
  28. package/dist/loc-symbols.d.ts.map +1 -0
  29. package/dist/loc-symbols.js +28 -0
  30. package/dist/loc-symbols.js.map +1 -0
  31. package/dist/oti.d.ts +172 -0
  32. package/dist/oti.d.ts.map +1 -0
  33. package/dist/oti.js +254 -0
  34. package/dist/oti.js.map +1 -0
  35. package/dist/reorder-buffer.d.ts +41 -0
  36. package/dist/reorder-buffer.d.ts.map +1 -0
  37. package/dist/reorder-buffer.js +81 -0
  38. package/dist/reorder-buffer.js.map +1 -0
  39. package/node_modules/@blockcast/mmt-alta-parse/README.md +181 -0
  40. package/node_modules/@blockcast/mmt-alta-parse/dist/.build-stamp +0 -0
  41. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts +57 -0
  42. package/node_modules/@blockcast/mmt-alta-parse/dist/index.d.ts.map +1 -0
  43. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js +115 -0
  44. package/node_modules/@blockcast/mmt-alta-parse/dist/index.js.map +1 -0
  45. package/node_modules/@blockcast/mmt-alta-parse/package.json +50 -0
  46. package/node_modules/@blockcast/mmt-alta-parse/src/index.ts +136 -0
  47. package/node_modules/@blockcast/mmt-base/README.md +120 -0
  48. package/node_modules/@blockcast/mmt-base/dist/.build-stamp +0 -0
  49. package/node_modules/@blockcast/mmt-base/dist/index.d.ts +2 -0
  50. package/node_modules/@blockcast/mmt-base/dist/index.d.ts.map +1 -0
  51. package/node_modules/@blockcast/mmt-base/dist/index.js +2 -0
  52. package/node_modules/@blockcast/mmt-base/dist/index.js.map +1 -0
  53. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +276 -0
  54. package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -0
  55. package/node_modules/@blockcast/mmt-base/dist/mmtp.js +645 -0
  56. package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -0
  57. package/node_modules/@blockcast/mmt-base/package.json +49 -0
  58. package/node_modules/@blockcast/mmt-base/src/index.ts +1 -0
  59. package/node_modules/@blockcast/mmt-base/src/mmtp.ts +899 -0
  60. package/package.json +79 -0
  61. package/src/block-manager.ts +371 -0
  62. package/src/block-processor.ts +277 -0
  63. package/src/cmaf-recovery.ts +127 -0
  64. package/src/cmaf-symbols.ts +68 -0
  65. package/src/fec-block-mapper.ts +332 -0
  66. package/src/index.ts +77 -0
  67. package/src/loc-symbols.ts +34 -0
  68. package/src/oti.ts +317 -0
  69. package/src/reorder-buffer.ts +101 -0
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@blockcast/mmt-fec",
3
+ "version": "0.1.0-main.0399efef1f07",
4
+ "description": "Container-agnostic FEC client for RaptorQ (RFC 6330) decoding via WASM",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./block-manager": {
14
+ "import": "./dist/block-manager.js",
15
+ "types": "./dist/block-manager.d.ts"
16
+ },
17
+ "./block-processor": {
18
+ "import": "./dist/block-processor.js",
19
+ "types": "./dist/block-processor.d.ts"
20
+ },
21
+ "./reorder-buffer": {
22
+ "import": "./dist/reorder-buffer.js",
23
+ "types": "./dist/reorder-buffer.d.ts"
24
+ },
25
+ "./cmaf": {
26
+ "import": "./dist/cmaf-symbols.js",
27
+ "types": "./dist/cmaf-symbols.d.ts"
28
+ },
29
+ "./cmaf-recovery": {
30
+ "import": "./dist/cmaf-recovery.js",
31
+ "types": "./dist/cmaf-recovery.d.ts"
32
+ },
33
+ "./loc": {
34
+ "import": "./dist/loc-symbols.js",
35
+ "types": "./dist/loc-symbols.d.ts"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "src",
41
+ "!src/**/*.test.ts",
42
+ "!src/**/*.spec.ts",
43
+ "!src/**/__tests__",
44
+ "!dist/**/*.test.*",
45
+ "!dist/**/*.spec.*"
46
+ ],
47
+ "scripts": {
48
+ "build": "tsc",
49
+ "dev": "tsc --watch",
50
+ "clean": "rm -rf dist",
51
+ "typecheck": "tsc --noEmit",
52
+ "test": "vitest run",
53
+ "test:watch": "vitest"
54
+ },
55
+ "dependencies": {
56
+ "@blockcast/mmt-base": "0.1.0-main.0399efef1f07"
57
+ },
58
+ "bundledDependencies": [
59
+ "@blockcast/mmt-base"
60
+ ],
61
+ "devDependencies": {
62
+ "typescript": "^5.3.0",
63
+ "vitest": "^1.0.0"
64
+ },
65
+ "keywords": [
66
+ "fec",
67
+ "raptorq",
68
+ "rfc6330",
69
+ "wasm",
70
+ "forward-error-correction",
71
+ "streaming"
72
+ ],
73
+ "license": "Apache-2.0",
74
+ "repository": {
75
+ "type": "git",
76
+ "url": "https://github.com/Blockcast/libmmt.git",
77
+ "directory": "packages/fec"
78
+ }
79
+ }
@@ -0,0 +1,371 @@
1
+ /**
2
+ * FEC Block State Machine for Multicast
3
+ *
4
+ * Manages FEC block collection, timeout-based fallback, and WebTransport
5
+ * repair integration. Tracks source and repair symbols per block, triggering
6
+ * decode when complete or requesting repairs when timed out.
7
+ *
8
+ * State Machine:
9
+ * EMPTY → COLLECTING → COMPLETE → DECODED
10
+ * ↓
11
+ * FALLBACK (timeout) → WebTransport repair → DECODED
12
+ *
13
+ * Per draft-ramadan-moq-fec-00 Section 4.2:
14
+ * - k source symbols complete a block (no FEC decode needed)
15
+ * - After timeout, missing symbols are requested via WebTransport
16
+ * - With k symbols (source + repair), FEC decode recovers block
17
+ */
18
+
19
+ export const FEC_STATE = {
20
+ EMPTY: 0,
21
+ COLLECTING: 1,
22
+ COMPLETE: 2,
23
+ FALLBACK: 3,
24
+ DECODED: 4,
25
+ FAILED: 5,
26
+ } as const;
27
+
28
+ export type FecState = (typeof FEC_STATE)[keyof typeof FEC_STATE];
29
+
30
+ export interface FecBlockManagerOptions {
31
+ /** Block timeout (ms). Derive from catalog: interleaveDepthMs or frameDurationMs * K. Required. */
32
+ timeoutMs: number;
33
+ /** K — source symbols per block from catalog fec.sourceSymbols. Required for interleaved mode.
34
+ * For object FEC (§9.1), this is the max K — actual K varies per block via addSourcePacket/addRepairPacket blockK param. */
35
+ k: number;
36
+ /** Max concurrent blocks in flight. Resource bound, not timing — default 64. */
37
+ maxBlocks?: number;
38
+ }
39
+
40
+ export interface FecBlockStats {
41
+ sourcePackets: number;
42
+ repairPackets: number;
43
+ blocksComplete: number;
44
+ blocksRecovered: number;
45
+ blocksFailed: number;
46
+ fallbackRequests: number;
47
+ activeBlocks: number;
48
+ recoveryRate: number;
49
+ }
50
+
51
+ /**
52
+ * Individual FEC Block.
53
+ * Tracks symbols and state for a single source block.
54
+ */
55
+ class FecBlock {
56
+ blockId: number;
57
+ state: FecState = FEC_STATE.EMPTY;
58
+ sourceSymbols = new Map<number, Uint8Array>();
59
+ repairSymbols = new Map<number, Uint8Array>();
60
+ k: number;
61
+ timeoutHandle: ReturnType<typeof setTimeout> | undefined;
62
+ createdAt: number;
63
+ decodedAt: number | undefined;
64
+
65
+ constructor(blockId: number, k: number) {
66
+ this.blockId = blockId;
67
+ this.k = k;
68
+ this.createdAt = performance.now();
69
+ }
70
+
71
+ addSourceSymbol(esi: number, data: Uint8Array): boolean {
72
+ if (this.state >= FEC_STATE.COMPLETE) return false;
73
+
74
+ this.sourceSymbols.set(esi, data);
75
+ this.state = FEC_STATE.COLLECTING;
76
+
77
+ if (this.sourceSymbols.size >= this.k) {
78
+ this.state = FEC_STATE.COMPLETE;
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+
84
+ addRepairSymbol(esi: number, data: Uint8Array): boolean {
85
+ if (this.state >= FEC_STATE.DECODED) return false;
86
+
87
+ this.repairSymbols.set(esi, data);
88
+
89
+ const totalSymbols = this.sourceSymbols.size + this.repairSymbols.size;
90
+ if (totalSymbols >= this.k) {
91
+ return true;
92
+ }
93
+ return false;
94
+ }
95
+
96
+ getMissingEsis(): number[] {
97
+ const missing: number[] = [];
98
+ for (let esi = 0; esi < this.k; esi++) {
99
+ if (!this.sourceSymbols.has(esi) && !this.repairSymbols.has(esi)) {
100
+ missing.push(esi);
101
+ }
102
+ }
103
+ return missing;
104
+ }
105
+
106
+ isDecodable(): boolean {
107
+ return this.sourceSymbols.size + this.repairSymbols.size >= this.k;
108
+ }
109
+
110
+ clearTimeout(): void {
111
+ if (this.timeoutHandle !== undefined) {
112
+ clearTimeout(this.timeoutHandle);
113
+ this.timeoutHandle = undefined;
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * FEC Block Manager
120
+ *
121
+ * Manages multiple FEC blocks, handling:
122
+ * - Source symbol registration
123
+ * - Repair symbol registration
124
+ * - Timeout-based WebTransport fallback
125
+ * - Block completion and decoding
126
+ */
127
+ export class FecBlockManager {
128
+ #blocks = new Map<number, FecBlock>();
129
+ #timeoutMs: number;
130
+ #k: number;
131
+ maxBlocks: number;
132
+ #repairCallback: ((blockId: number, missingEsis: number[]) => void) | null = null;
133
+ #decodeCallback: ((blockId: number, data: Uint8Array) => void) | null = null;
134
+
135
+ #stats = {
136
+ sourcePackets: 0,
137
+ repairPackets: 0,
138
+ blocksComplete: 0,
139
+ blocksRecovered: 0,
140
+ blocksFailed: 0,
141
+ fallbackRequests: 0,
142
+ };
143
+
144
+ constructor(options: FecBlockManagerOptions) {
145
+ if (options.timeoutMs <= 0) throw new Error('[FecBlockManager] timeoutMs must be > 0 (derive from catalog FEC config)')
146
+ if (options.k <= 0) throw new Error('[FecBlockManager] k must be > 0 (from catalog fec.sourceSymbols)')
147
+ this.#timeoutMs = options.timeoutMs;
148
+ this.#k = options.k;
149
+ this.maxBlocks = options.maxBlocks ?? 64;
150
+ }
151
+
152
+ setRepairCallback(callback: (blockId: number, missingEsis: number[]) => void): void {
153
+ this.#repairCallback = callback;
154
+ }
155
+
156
+ setDecodeCallback(callback: (blockId: number, data: Uint8Array) => void): void {
157
+ this.#decodeCallback = callback;
158
+ }
159
+
160
+ updateParams(params: { k?: number; timeoutMs?: number }): void {
161
+ if (params.k !== undefined) this.#k = params.k;
162
+ if (params.timeoutMs !== undefined) this.#timeoutMs = params.timeoutMs;
163
+ }
164
+
165
+ /**
166
+ * Add a source symbol to a block.
167
+ * @param blockK — per-block K for object FEC (§9.1). When provided, overrides
168
+ * the global K for this block. For interleaved mode, omit (uses global K).
169
+ */
170
+ addSourcePacket(blockId: number, esi: number, data: Uint8Array, blockK?: number): Uint8Array | null {
171
+ this.#stats.sourcePackets++;
172
+
173
+ let block = this.#blocks.get(blockId);
174
+ if (!block) {
175
+ block = this.#createBlock(blockId, blockK);
176
+ }
177
+
178
+ const complete = block.addSourceSymbol(esi, data);
179
+ if (complete) {
180
+ return this.#completeBlock(block);
181
+ }
182
+
183
+ return null;
184
+ }
185
+
186
+ /**
187
+ * Add a repair symbol to a block.
188
+ * @param blockK — per-block K from SSB_length in the Repair FEC Payload ID
189
+ * (ISO 23008-1 C.5.3). RSB_length is a different field: the repair symbol
190
+ * count, which under layered repair is the base-layer total P, not K.
191
+ * Required for object FEC; omit for interleaved mode.
192
+ */
193
+ addRepairPacket(blockId: number, esi: number, data: Uint8Array, blockK?: number): Uint8Array | null {
194
+ this.#stats.repairPackets++;
195
+
196
+ let block = this.#blocks.get(blockId);
197
+ if (!block) {
198
+ block = this.#createBlock(blockId, blockK);
199
+ } else if (blockK && block.k !== blockK) {
200
+ // Object FEC: repair reveals actual K — update block if first repair arrives
201
+ // before enough source symbols to infer K.
202
+ block.k = blockK;
203
+ }
204
+
205
+ const canDecode = block.addRepairSymbol(esi, data);
206
+ if (canDecode && block.state !== FEC_STATE.DECODED) {
207
+ return this.#decodeBlock(block);
208
+ }
209
+
210
+ return null;
211
+ }
212
+
213
+ getStats(): FecBlockStats {
214
+ return {
215
+ ...this.#stats,
216
+ activeBlocks: this.#blocks.size,
217
+ recoveryRate:
218
+ this.#stats.blocksComplete > 0
219
+ ? (this.#stats.blocksRecovered / this.#stats.blocksComplete) * 100
220
+ : 100,
221
+ };
222
+ }
223
+
224
+ reset(): void {
225
+ for (const block of this.#blocks.values()) {
226
+ block.clearTimeout();
227
+ }
228
+ this.#blocks.clear();
229
+ this.#stats = {
230
+ sourcePackets: 0,
231
+ repairPackets: 0,
232
+ blocksComplete: 0,
233
+ blocksRecovered: 0,
234
+ blocksFailed: 0,
235
+ fallbackRequests: 0,
236
+ };
237
+ }
238
+
239
+ /**
240
+ * @param blockK — per-block K for object FEC (§9.1). Falls back to global #k.
241
+ */
242
+ #createBlock(blockId: number, blockK?: number): FecBlock {
243
+ if (this.#blocks.size >= this.maxBlocks) {
244
+ this.#evictOldestBlock();
245
+ }
246
+
247
+ const k = blockK ?? this.#k;
248
+ if (k <= 0) {
249
+ throw new Error(`[FecBlockManager] Cannot create block ${blockId}: K=${k}. Derive K from catalog (interleaved) or frame size (object §9.1).`)
250
+ }
251
+ const block = new FecBlock(blockId, k);
252
+ this.#blocks.set(blockId, block);
253
+
254
+ block.timeoutHandle = setTimeout(() => {
255
+ this.#onBlockTimeout(blockId);
256
+ }, this.#timeoutMs);
257
+
258
+ return block;
259
+ }
260
+
261
+ #onBlockTimeout(blockId: number): void {
262
+ const block = this.#blocks.get(blockId);
263
+ if (!block || block.state >= FEC_STATE.COMPLETE) {
264
+ return;
265
+ }
266
+
267
+ block.state = FEC_STATE.FALLBACK;
268
+ this.#stats.fallbackRequests++;
269
+
270
+ const missingEsis = block.getMissingEsis();
271
+ if (missingEsis.length > 0 && this.#repairCallback) {
272
+ console.log(
273
+ `[FecBlockManager] Block ${blockId} timeout, requesting ${missingEsis.length} repair symbols`,
274
+ );
275
+ this.#repairCallback(blockId, missingEsis);
276
+ }
277
+
278
+ block.timeoutHandle = setTimeout(() => {
279
+ if (block.state === FEC_STATE.FALLBACK) {
280
+ block.state = FEC_STATE.FAILED;
281
+ this.#stats.blocksFailed++;
282
+ // Throttle: log only every 50th failure to avoid console flood
283
+ if (this.#stats.blocksFailed <= 3 || this.#stats.blocksFailed % 50 === 0) {
284
+ console.warn(
285
+ `[FecBlockManager] Block ${blockId} failed - insufficient symbols (total: ${this.#stats.blocksFailed})`,
286
+ );
287
+ }
288
+ this.#blocks.delete(blockId);
289
+ }
290
+ }, this.#timeoutMs * 2);
291
+ }
292
+
293
+ #completeBlock(block: FecBlock): Uint8Array {
294
+ block.clearTimeout();
295
+ block.state = FEC_STATE.DECODED;
296
+ block.decodedAt = performance.now();
297
+ this.#stats.blocksComplete++;
298
+
299
+ const data = this.#assembleBlock(block);
300
+
301
+ if (this.#decodeCallback) {
302
+ this.#decodeCallback(block.blockId, data);
303
+ }
304
+
305
+ // GC: block reached terminal state — release symbols immediately.
306
+ // No delay needed: callers already have the assembled data.
307
+ this.#blocks.delete(block.blockId);
308
+
309
+ return data;
310
+ }
311
+
312
+ #decodeBlock(block: FecBlock): Uint8Array {
313
+ block.clearTimeout();
314
+ block.state = FEC_STATE.DECODED;
315
+ block.decodedAt = performance.now();
316
+ this.#stats.blocksComplete++;
317
+ this.#stats.blocksRecovered++;
318
+
319
+ console.log(
320
+ `[FecBlockManager] Block ${block.blockId} recovered via FEC ` +
321
+ `(${block.sourceSymbols.size} source + ${block.repairSymbols.size} repair)`,
322
+ );
323
+
324
+ // TODO: Integrate with RaptorQ WASM decoder for actual FEC recovery
325
+ const data = this.#assembleBlock(block);
326
+
327
+ if (this.#decodeCallback) {
328
+ this.#decodeCallback(block.blockId, data);
329
+ }
330
+
331
+ // GC: block reached terminal state — release symbols immediately.
332
+ this.#blocks.delete(block.blockId);
333
+
334
+ return data;
335
+ }
336
+
337
+ #assembleBlock(block: FecBlock): Uint8Array {
338
+ const esis = [...block.sourceSymbols.keys()].sort((a, b) => a - b);
339
+ const totalSize = esis.reduce((sum, esi) => sum + block.sourceSymbols.get(esi)!.length, 0);
340
+ const result = new Uint8Array(totalSize);
341
+
342
+ let offset = 0;
343
+ for (const esi of esis) {
344
+ const symbol = block.sourceSymbols.get(esi)!;
345
+ result.set(symbol, offset);
346
+ offset += symbol.length;
347
+ }
348
+
349
+ return result;
350
+ }
351
+
352
+ #evictOldestBlock(): void {
353
+ let oldest: FecBlock | null = null;
354
+ let oldestTime = Infinity;
355
+
356
+ for (const block of this.#blocks.values()) {
357
+ if (block.createdAt < oldestTime) {
358
+ oldestTime = block.createdAt;
359
+ oldest = block;
360
+ }
361
+ }
362
+
363
+ if (oldest) {
364
+ oldest.clearTimeout();
365
+ if (oldest.state < FEC_STATE.DECODED) {
366
+ this.#stats.blocksFailed++;
367
+ }
368
+ this.#blocks.delete(oldest.blockId);
369
+ }
370
+ }
371
+ }