@blockcast/mmt-fec 0.1.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.
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Container-Agnostic FEC Client
3
+ *
4
+ * Provides RaptorQ-based Forward Error Correction (RFC 6330) that works with:
5
+ * - MMTP (MMT Protocol) containers
6
+ * - LOC (Low Overhead Container)
7
+ * - Raw NAL units with FEC_CONFIG
8
+ * - MoQ (Media over QUIC)
9
+ *
10
+ * Transport independent - works over both:
11
+ * - Unicast (MOQ/QUIC/WebTransport)
12
+ * - Multicast (SSM/AMT via window.Multicast API)
13
+ *
14
+ * Based on draft-ramadan-moq-fec-00
15
+ */
16
+ interface WasmFecDecoder {
17
+ configure(oti: Uint8Array): void;
18
+ add_repair(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | undefined;
19
+ add_source(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | undefined;
20
+ cleanup(current_time: number): Uint8Array;
21
+ get_stats(): WasmFecStats;
22
+ reset_stats(): void;
23
+ flush(): void;
24
+ set_timeout(timeout_ms: number): void;
25
+ set_green_fill(enabled: boolean): void;
26
+ pending_blocks(): number;
27
+ }
28
+ interface WasmFecStats {
29
+ source_packets: bigint;
30
+ repair_packets: bigint;
31
+ blocks_complete: bigint;
32
+ blocks_recovered: bigint;
33
+ blocks_failed: bigint;
34
+ recovery_rate(): number;
35
+ }
36
+ interface WasmFecPayloadId {
37
+ sbn: number;
38
+ esi: number;
39
+ }
40
+ /**
41
+ * FEC processing statistics (transport & container independent)
42
+ */
43
+ export interface FecProcessingStats {
44
+ /** Source packets received */
45
+ sourcePackets: number;
46
+ /** Repair packets received */
47
+ repairPackets: number;
48
+ /** Blocks completed (received all source symbols) */
49
+ blocksComplete: number;
50
+ /** Blocks recovered via FEC */
51
+ blocksRecovered: number;
52
+ /** Blocks that failed to recover */
53
+ blocksFailed: number;
54
+ /** Recovery rate (0-100%) */
55
+ recoveryRate: number;
56
+ /** Green-fill frames (unrecoverable data replaced with placeholder) */
57
+ greenFillFrames: number;
58
+ }
59
+ /**
60
+ * FEC configuration parameters (RFC 6330 OTI)
61
+ */
62
+ export interface FecConfig {
63
+ /** Symbol size in bytes (T) */
64
+ symbolSize: number;
65
+ /** Number of source blocks (Z) */
66
+ sourceBlocks: number;
67
+ /** Number of sub-blocks (N) - used for large blocks */
68
+ subBlocks: number;
69
+ /** Symbol alignment (Al) - typically 8 for byte-aligned */
70
+ alignment: number;
71
+ /** Interleave depth - number of blocks that can be "in flight" */
72
+ interleaveDepth: number;
73
+ }
74
+ /**
75
+ * Options for FecClient
76
+ */
77
+ export interface FecClientOptions {
78
+ /** Interleave depth for FEC (default: 30 for ~1s at 30fps) */
79
+ interleaveDepth?: number;
80
+ /** Block timeout in milliseconds (default: 2000) */
81
+ blockTimeoutMs?: number;
82
+ /** Enable green-fill for failed frames (default: true) */
83
+ greenFillEnabled?: boolean;
84
+ /** Callback when data is recovered via FEC */
85
+ onRecovered?: (data: Uint8Array, timestamp: bigint) => void;
86
+ /** Callback for green-fill event */
87
+ onGreenFill?: (timestamp: bigint) => void;
88
+ /** Callback for stats update */
89
+ onStatsUpdate?: (stats: FecProcessingStats) => void;
90
+ }
91
+ /**
92
+ * WASM module interface for FecClient
93
+ * Matches mmt-wasm module exports
94
+ */
95
+ export interface FecWasmModule {
96
+ init: () => Promise<unknown>;
97
+ MmtFecDecoder: new (interleaveDepth?: number | null) => WasmFecDecoder;
98
+ parse_fec_payload_id: (data: Uint8Array) => WasmFecPayloadId;
99
+ is_simd_enabled: () => boolean;
100
+ }
101
+ /**
102
+ * Container-Agnostic FEC Client
103
+ *
104
+ * Provides RaptorQ FEC decoding for any container format.
105
+ * The actual container parsing (MMTP, LOC, etc.) is handled separately.
106
+ */
107
+ export declare class FecClient {
108
+ private fecDecoder;
109
+ private options;
110
+ private initialized;
111
+ private greenFillCount;
112
+ private cleanupInterval;
113
+ private wasmModule;
114
+ constructor(options?: FecClientOptions);
115
+ /**
116
+ * Set WASM module (call before init)
117
+ */
118
+ setWasmModule(module: FecWasmModule): void;
119
+ /**
120
+ * Initialize the WASM FEC decoder
121
+ */
122
+ init(wasmModule?: FecWasmModule): Promise<void>;
123
+ /**
124
+ * Check if initialized
125
+ */
126
+ isInitialized(): boolean;
127
+ /**
128
+ * Configure FEC parameters from OTI (Object Transmission Information)
129
+ * Per RFC 6330 (RaptorQ)
130
+ */
131
+ configure(oti: Uint8Array): void;
132
+ /**
133
+ * Add a source symbol to the FEC decoder
134
+ *
135
+ * @param sbn - Source Block Number
136
+ * @param esi - Encoding Symbol ID (< K for source symbols)
137
+ * @param data - Symbol data
138
+ * @param timestamp - Packet timestamp for ordering
139
+ * @returns Decoded data if block is complete, null otherwise
140
+ */
141
+ addSource(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | null;
142
+ /**
143
+ * Add a repair symbol to the FEC decoder
144
+ *
145
+ * @param sbn - Source Block Number
146
+ * @param esi - Encoding Symbol ID (>= K for repair symbols)
147
+ * @param data - Repair symbol data
148
+ * @param timestamp - Packet timestamp for ordering
149
+ * @returns Recovered data if FEC decoding succeeded, null otherwise
150
+ */
151
+ addRepair(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | null;
152
+ /**
153
+ * Process a repair packet with FEC payload ID header
154
+ * Per draft-ramadan-moq-fec-00 Section 6.1
155
+ *
156
+ * @param payload - Repair packet payload (4-byte header + repair symbols)
157
+ * @param timestamp - Packet timestamp
158
+ * @returns Recovered data if FEC decoding succeeded, null otherwise
159
+ */
160
+ processRepairPacket(payload: Uint8Array, timestamp: number): Uint8Array | null;
161
+ /**
162
+ * Cleanup expired blocks
163
+ * Returns green-fill frame data for expired blocks if green-fill is enabled
164
+ */
165
+ cleanup(): void;
166
+ /**
167
+ * Get number of pending blocks
168
+ */
169
+ getPendingBlocks(): number;
170
+ /**
171
+ * Get current FEC statistics
172
+ */
173
+ getStats(): FecProcessingStats;
174
+ /**
175
+ * Get current FEC configuration
176
+ * Returns the configured interleave depth (OTI params set via configure())
177
+ */
178
+ getConfig(): FecConfig;
179
+ /**
180
+ * Reset statistics
181
+ */
182
+ resetStats(): void;
183
+ /**
184
+ * Dispose of resources
185
+ */
186
+ dispose(): void;
187
+ }
188
+ export declare function getSharedFecClient(wasmModule: FecWasmModule, options?: FecClientOptions): Promise<FecClient>;
189
+ export declare function resetSharedFecClient(): void;
190
+ export {};
191
+ //# sourceMappingURL=fec-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fec-client.d.ts","sourceRoot":"","sources":["../src/fec-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Container-Agnostic FEC Client
3
+ *
4
+ * Provides RaptorQ-based Forward Error Correction (RFC 6330) that works with:
5
+ * - MMTP (MMT Protocol) containers
6
+ * - LOC (Low Overhead Container)
7
+ * - Raw NAL units with FEC_CONFIG
8
+ * - MoQ (Media over QUIC)
9
+ *
10
+ * Transport independent - works over both:
11
+ * - Unicast (MOQ/QUIC/WebTransport)
12
+ * - Multicast (SSM/AMT via window.Multicast API)
13
+ *
14
+ * Based on draft-ramadan-moq-fec-00
15
+ */
16
+ /**
17
+ * Container-Agnostic FEC Client
18
+ *
19
+ * Provides RaptorQ FEC decoding for any container format.
20
+ * The actual container parsing (MMTP, LOC, etc.) is handled separately.
21
+ */
22
+ export class FecClient {
23
+ fecDecoder = null;
24
+ options;
25
+ initialized = false;
26
+ greenFillCount = 0;
27
+ cleanupInterval = null;
28
+ // WASM module references (set via setWasmModule or init)
29
+ wasmModule = null;
30
+ constructor(options = {}) {
31
+ this.options = {
32
+ interleaveDepth: options.interleaveDepth ?? 30,
33
+ blockTimeoutMs: options.blockTimeoutMs ?? 2000,
34
+ greenFillEnabled: options.greenFillEnabled ?? true,
35
+ onRecovered: options.onRecovered ?? (() => { }),
36
+ onGreenFill: options.onGreenFill ?? (() => { }),
37
+ onStatsUpdate: options.onStatsUpdate ?? (() => { }),
38
+ };
39
+ }
40
+ /**
41
+ * Set WASM module (call before init)
42
+ */
43
+ setWasmModule(module) {
44
+ this.wasmModule = module;
45
+ }
46
+ /**
47
+ * Initialize the WASM FEC decoder
48
+ */
49
+ async init(wasmModule) {
50
+ if (this.initialized)
51
+ return;
52
+ if (wasmModule) {
53
+ this.wasmModule = wasmModule;
54
+ }
55
+ if (!this.wasmModule) {
56
+ throw new Error("WASM module not set. Call setWasmModule() or pass module to init()");
57
+ }
58
+ await this.wasmModule.init();
59
+ this.fecDecoder = new this.wasmModule.MmtFecDecoder(this.options.interleaveDepth);
60
+ this.fecDecoder.set_timeout(this.options.blockTimeoutMs);
61
+ this.fecDecoder.set_green_fill(this.options.greenFillEnabled);
62
+ // Start periodic cleanup
63
+ this.cleanupInterval = setInterval(() => {
64
+ this.cleanup();
65
+ }, 500);
66
+ this.initialized = true;
67
+ console.log(`[FecClient] Initialized (SIMD: ${this.wasmModule.is_simd_enabled()})`);
68
+ }
69
+ /**
70
+ * Check if initialized
71
+ */
72
+ isInitialized() {
73
+ return this.initialized;
74
+ }
75
+ /**
76
+ * Configure FEC parameters from OTI (Object Transmission Information)
77
+ * Per RFC 6330 (RaptorQ)
78
+ */
79
+ configure(oti) {
80
+ if (!this.fecDecoder) {
81
+ throw new Error("FecClient not initialized");
82
+ }
83
+ this.fecDecoder.configure(oti);
84
+ }
85
+ /**
86
+ * Add a source symbol to the FEC decoder
87
+ *
88
+ * @param sbn - Source Block Number
89
+ * @param esi - Encoding Symbol ID (< K for source symbols)
90
+ * @param data - Symbol data
91
+ * @param timestamp - Packet timestamp for ordering
92
+ * @returns Decoded data if block is complete, null otherwise
93
+ */
94
+ addSource(sbn, esi, data, timestamp) {
95
+ if (!this.fecDecoder)
96
+ return null;
97
+ return this.fecDecoder.add_source(sbn, esi, data, timestamp) ?? null;
98
+ }
99
+ /**
100
+ * Add a repair symbol to the FEC decoder
101
+ *
102
+ * @param sbn - Source Block Number
103
+ * @param esi - Encoding Symbol ID (>= K for repair symbols)
104
+ * @param data - Repair symbol data
105
+ * @param timestamp - Packet timestamp for ordering
106
+ * @returns Recovered data if FEC decoding succeeded, null otherwise
107
+ */
108
+ addRepair(sbn, esi, data, timestamp) {
109
+ if (!this.fecDecoder)
110
+ return null;
111
+ try {
112
+ const decoded = this.fecDecoder.add_repair(sbn, esi, data, timestamp);
113
+ if (decoded && decoded.length > 0) {
114
+ const recovered = new Uint8Array(decoded);
115
+ this.options.onRecovered(recovered, BigInt(timestamp));
116
+ return recovered;
117
+ }
118
+ }
119
+ catch (e) {
120
+ console.warn("[FecClient] Repair processing error:", e);
121
+ }
122
+ return null;
123
+ }
124
+ /**
125
+ * Process a repair packet with FEC payload ID header
126
+ * Per draft-ramadan-moq-fec-00 Section 6.1
127
+ *
128
+ * @param payload - Repair packet payload (4-byte header + repair symbols)
129
+ * @param timestamp - Packet timestamp
130
+ * @returns Recovered data if FEC decoding succeeded, null otherwise
131
+ */
132
+ processRepairPacket(payload, timestamp) {
133
+ if (!this.fecDecoder || !this.wasmModule || payload.length < 4)
134
+ return null;
135
+ try {
136
+ const { sbn, esi } = this.wasmModule.parse_fec_payload_id(payload.subarray(0, 4));
137
+ const data = payload.subarray(4);
138
+ return this.addRepair(sbn, esi, data, timestamp);
139
+ }
140
+ catch (e) {
141
+ console.warn("[FecClient] FEC payload parse error:", e);
142
+ return null;
143
+ }
144
+ }
145
+ /**
146
+ * Cleanup expired blocks
147
+ * Returns green-fill frame data for expired blocks if green-fill is enabled
148
+ */
149
+ cleanup() {
150
+ if (!this.fecDecoder)
151
+ return;
152
+ const greenFillData = this.fecDecoder.cleanup(performance.now());
153
+ // greenFillData is a Uint8Array returned by WASM for expired blocks
154
+ if (greenFillData && greenFillData.length > 0 && this.options.greenFillEnabled) {
155
+ // Count green-fills based on data returned
156
+ const greenFillsTriggered = Math.ceil(greenFillData.length / 8);
157
+ if (greenFillsTriggered > 0) {
158
+ this.greenFillCount += greenFillsTriggered;
159
+ this.options.onGreenFill(BigInt(performance.now()));
160
+ }
161
+ }
162
+ }
163
+ /**
164
+ * Get number of pending blocks
165
+ */
166
+ getPendingBlocks() {
167
+ return this.fecDecoder?.pending_blocks() ?? 0;
168
+ }
169
+ /**
170
+ * Get current FEC statistics
171
+ */
172
+ getStats() {
173
+ const fecStats = this.fecDecoder?.get_stats();
174
+ return {
175
+ sourcePackets: Number(fecStats?.source_packets ?? 0n),
176
+ repairPackets: Number(fecStats?.repair_packets ?? 0n),
177
+ blocksComplete: Number(fecStats?.blocks_complete ?? 0n),
178
+ blocksRecovered: Number(fecStats?.blocks_recovered ?? 0n),
179
+ blocksFailed: Number(fecStats?.blocks_failed ?? 0n),
180
+ recoveryRate: fecStats?.recovery_rate() ?? 100,
181
+ greenFillFrames: this.greenFillCount,
182
+ };
183
+ }
184
+ /**
185
+ * Get current FEC configuration
186
+ * Returns the configured interleave depth (OTI params set via configure())
187
+ */
188
+ getConfig() {
189
+ return {
190
+ symbolSize: 1280, // Default, configured via OTI
191
+ sourceBlocks: 1, // Default, configured via OTI
192
+ subBlocks: 1, // Default, configured via OTI
193
+ alignment: 8, // Default, configured via OTI
194
+ interleaveDepth: this.options.interleaveDepth,
195
+ };
196
+ }
197
+ /**
198
+ * Reset statistics
199
+ */
200
+ resetStats() {
201
+ this.fecDecoder?.reset_stats();
202
+ this.greenFillCount = 0;
203
+ }
204
+ /**
205
+ * Dispose of resources
206
+ */
207
+ dispose() {
208
+ if (this.cleanupInterval) {
209
+ clearInterval(this.cleanupInterval);
210
+ this.cleanupInterval = null;
211
+ }
212
+ this.fecDecoder?.flush();
213
+ this.initialized = false;
214
+ }
215
+ }
216
+ /**
217
+ * Create a shared FEC client instance (singleton pattern)
218
+ */
219
+ let sharedFecClient = null;
220
+ export async function getSharedFecClient(wasmModule, options) {
221
+ if (!sharedFecClient) {
222
+ sharedFecClient = new FecClient(options);
223
+ await sharedFecClient.init(wasmModule);
224
+ }
225
+ return sharedFecClient;
226
+ }
227
+ export function resetSharedFecClient() {
228
+ if (sharedFecClient) {
229
+ sharedFecClient.dispose();
230
+ sharedFecClient = null;
231
+ }
232
+ }
233
+ //# sourceMappingURL=fec-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fec-client.js","sourceRoot":"","sources":["../src/fec-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @mmt/fec - Container-agnostic FEC client for RaptorQ (RFC 6330)
3
+ *
4
+ * This package provides a TypeScript wrapper around the mmt-wasm WASM bindings
5
+ * for Forward Error Correction using RaptorQ (RFC 6330).
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * import { FecClient } from '@mmt/fec';
10
+ * import * as wasmModule from '@mmt/wasm';
11
+ *
12
+ * const client = new FecClient({ interleaveDepth: 30 });
13
+ * await client.init(wasmModule);
14
+ *
15
+ * // Configure from OTI
16
+ * client.configure(otiBytes);
17
+ *
18
+ * // Add source/repair symbols
19
+ * const recovered = client.addRepair(sbn, esi, data, timestamp);
20
+ * ```
21
+ */
22
+ export { FecClient, getSharedFecClient, resetSharedFecClient, type FecConfig, type FecClientOptions, type FecProcessingStats, type FecWasmModule, } from "./fec-client.js";
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACN,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GAClB,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @mmt/fec - Container-agnostic FEC client for RaptorQ (RFC 6330)
3
+ *
4
+ * This package provides a TypeScript wrapper around the mmt-wasm WASM bindings
5
+ * for Forward Error Correction using RaptorQ (RFC 6330).
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * import { FecClient } from '@mmt/fec';
10
+ * import * as wasmModule from '@mmt/wasm';
11
+ *
12
+ * const client = new FecClient({ interleaveDepth: 30 });
13
+ * await client.init(wasmModule);
14
+ *
15
+ * // Configure from OTI
16
+ * client.configure(otiBytes);
17
+ *
18
+ * // Add source/repair symbols
19
+ * const recovered = client.addRepair(sbn, esi, data, timestamp);
20
+ * ```
21
+ */
22
+ export { FecClient, getSharedFecClient, resetSharedFecClient, } from "./fec-client.js";
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACN,SAAS,EACT,kBAAkB,EAClB,oBAAoB,GAKpB,MAAM,iBAAiB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@blockcast/mmt-fec",
3
+ "version": "0.1.0",
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
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "clean": "rm -rf dist",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "dependencies": {
27
+ "mmt-wasm": "file:../../mmt-wasm/pkg"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.3.0",
31
+ "vitest": "^1.0.0"
32
+ },
33
+ "keywords": [
34
+ "fec",
35
+ "raptorq",
36
+ "rfc6330",
37
+ "wasm",
38
+ "forward-error-correction",
39
+ "streaming"
40
+ ],
41
+ "license": "Apache-2.0",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/Blockcast/libmmt.git",
45
+ "directory": "packages/fec"
46
+ }
47
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Tests for shared FecClient
3
+ *
4
+ * Note: Full WASM integration is tested in mmt-wasm.
5
+ * These tests cover the TypeScript wrapper behavior.
6
+ */
7
+
8
+ import { describe, expect, it, beforeEach } from "vitest";
9
+ import { FecClient, type FecConfig, type FecProcessingStats } from "./fec-client.js";
10
+
11
+ describe("FecConfig interface", () => {
12
+ it("should have correct shape for RFC 6330 OTI parameters", () => {
13
+ const config: FecConfig = {
14
+ symbolSize: 1280,
15
+ sourceBlocks: 1,
16
+ subBlocks: 1,
17
+ alignment: 8,
18
+ interleaveDepth: 30,
19
+ };
20
+
21
+ expect(config.symbolSize).toBe(1280);
22
+ expect(config.sourceBlocks).toBe(1);
23
+ expect(config.subBlocks).toBe(1);
24
+ expect(config.alignment).toBe(8);
25
+ expect(config.interleaveDepth).toBe(30);
26
+ });
27
+ });
28
+
29
+ describe("FecProcessingStats interface", () => {
30
+ it("should have correct shape", () => {
31
+ const stats: FecProcessingStats = {
32
+ sourcePackets: 100,
33
+ repairPackets: 30,
34
+ blocksComplete: 10,
35
+ blocksRecovered: 2,
36
+ blocksFailed: 0,
37
+ recoveryRate: 100,
38
+ greenFillFrames: 0,
39
+ };
40
+
41
+ expect(stats.sourcePackets).toBe(100);
42
+ expect(stats.repairPackets).toBe(30);
43
+ expect(stats.blocksComplete).toBe(10);
44
+ expect(stats.blocksRecovered).toBe(2);
45
+ expect(stats.blocksFailed).toBe(0);
46
+ expect(stats.recoveryRate).toBe(100);
47
+ expect(stats.greenFillFrames).toBe(0);
48
+ });
49
+ });
50
+
51
+ describe("FecClient", () => {
52
+ let client: FecClient;
53
+
54
+ beforeEach(() => {
55
+ client = new FecClient({
56
+ interleaveDepth: 30,
57
+ blockTimeoutMs: 2000,
58
+ greenFillEnabled: true,
59
+ });
60
+ });
61
+
62
+ it("should create with default options", () => {
63
+ const defaultClient = new FecClient();
64
+ expect(defaultClient.isInitialized()).toBe(false);
65
+ });
66
+
67
+ it("should not be initialized without calling init()", () => {
68
+ expect(client.isInitialized()).toBe(false);
69
+ });
70
+
71
+ it("should throw when configuring without initialization", () => {
72
+ expect(() => client.configure(new Uint8Array(12))).toThrow(
73
+ "FecClient not initialized"
74
+ );
75
+ });
76
+
77
+ it("should return default stats when not initialized", () => {
78
+ const stats = client.getStats();
79
+
80
+ expect(stats.sourcePackets).toBe(0);
81
+ expect(stats.repairPackets).toBe(0);
82
+ expect(stats.blocksComplete).toBe(0);
83
+ expect(stats.blocksRecovered).toBe(0);
84
+ expect(stats.blocksFailed).toBe(0);
85
+ expect(stats.recoveryRate).toBe(100);
86
+ expect(stats.greenFillFrames).toBe(0);
87
+ });
88
+
89
+ it("should return default config when not initialized", () => {
90
+ const config = client.getConfig();
91
+
92
+ expect(config.symbolSize).toBe(1280);
93
+ expect(config.sourceBlocks).toBe(1);
94
+ expect(config.subBlocks).toBe(1);
95
+ expect(config.alignment).toBe(8);
96
+ expect(config.interleaveDepth).toBe(30);
97
+ });
98
+
99
+ it("should no-op setInterleaveDepth when not initialized", () => {
100
+ client.setInterleaveDepth(10);
101
+ const config = client.getConfig();
102
+ expect(config.interleaveDepth).toBe(30); // Unchanged since not init'd
103
+ });
104
+
105
+ it("should return empty array from cleanupByInterleave when not initialized", () => {
106
+ const expired = client.cleanupByInterleave(100);
107
+ expect(expired).toEqual([]);
108
+ });
109
+
110
+ it("should return null from addRepair when not initialized", () => {
111
+ const result = client.addRepair(0, 0, new Uint8Array(100), 0);
112
+ expect(result).toBeNull();
113
+ });
114
+
115
+ it("should return null from addSource when not initialized", () => {
116
+ const result = client.addSource(0, 0, new Uint8Array(100), 0);
117
+ expect(result).toBeNull();
118
+ });
119
+
120
+ it("should return null from processRepairPacket when not initialized", () => {
121
+ const result = client.processRepairPacket(new Uint8Array(10), 0);
122
+ expect(result).toBeNull();
123
+ });
124
+
125
+ it("should return null from processRepairPacket with short payload", () => {
126
+ const result = client.processRepairPacket(new Uint8Array(3), 0);
127
+ expect(result).toBeNull();
128
+ });
129
+
130
+ it("should dispose cleanly without initialization", () => {
131
+ client.dispose();
132
+ expect(client.isInitialized()).toBe(false);
133
+ });
134
+
135
+ it("should reset stats when not initialized", () => {
136
+ client.resetStats();
137
+ const stats = client.getStats();
138
+ expect(stats.greenFillFrames).toBe(0);
139
+ });
140
+
141
+ it("should throw when init without WASM module", async () => {
142
+ await expect(client.init()).rejects.toThrow(
143
+ "WASM module not set"
144
+ );
145
+ });
146
+ });
147
+
148
+ describe("FecClient callbacks", () => {
149
+ it("should store callbacks from options", () => {
150
+ const client = new FecClient({
151
+ onRecovered: () => {},
152
+ onGreenFill: () => {},
153
+ onStatsUpdate: () => {},
154
+ });
155
+
156
+ expect(client.isInitialized()).toBe(false);
157
+ });
158
+ });
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Container-Agnostic FEC Client
3
+ *
4
+ * Provides RaptorQ-based Forward Error Correction (RFC 6330) that works with:
5
+ * - MMTP (MMT Protocol) containers
6
+ * - LOC (Low Overhead Container)
7
+ * - Raw NAL units with FEC_CONFIG
8
+ * - MoQ (Media over QUIC)
9
+ *
10
+ * Transport independent - works over both:
11
+ * - Unicast (MOQ/QUIC/WebTransport)
12
+ * - Multicast (SSM/AMT via window.Multicast API)
13
+ *
14
+ * Based on draft-ramadan-moq-fec-00
15
+ */
16
+
17
+ // WASM module types - matches mmt-wasm MmtFecDecoder
18
+ interface WasmFecDecoder {
19
+ configure(oti: Uint8Array): void;
20
+ add_repair(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | undefined;
21
+ add_source(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | undefined;
22
+ cleanup(current_time: number): Uint8Array;
23
+ get_stats(): WasmFecStats;
24
+ reset_stats(): void;
25
+ flush(): void;
26
+ set_timeout(timeout_ms: number): void;
27
+ set_green_fill(enabled: boolean): void;
28
+ pending_blocks(): number;
29
+ }
30
+
31
+ interface WasmFecStats {
32
+ source_packets: bigint;
33
+ repair_packets: bigint;
34
+ blocks_complete: bigint;
35
+ blocks_recovered: bigint;
36
+ blocks_failed: bigint;
37
+ recovery_rate(): number;
38
+ }
39
+
40
+ interface WasmFecPayloadId {
41
+ sbn: number;
42
+ esi: number;
43
+ }
44
+
45
+ /**
46
+ * FEC processing statistics (transport & container independent)
47
+ */
48
+ export interface FecProcessingStats {
49
+ /** Source packets received */
50
+ sourcePackets: number;
51
+ /** Repair packets received */
52
+ repairPackets: number;
53
+ /** Blocks completed (received all source symbols) */
54
+ blocksComplete: number;
55
+ /** Blocks recovered via FEC */
56
+ blocksRecovered: number;
57
+ /** Blocks that failed to recover */
58
+ blocksFailed: number;
59
+ /** Recovery rate (0-100%) */
60
+ recoveryRate: number;
61
+ /** Green-fill frames (unrecoverable data replaced with placeholder) */
62
+ greenFillFrames: number;
63
+ }
64
+
65
+ /**
66
+ * FEC configuration parameters (RFC 6330 OTI)
67
+ */
68
+ export interface FecConfig {
69
+ /** Symbol size in bytes (T) */
70
+ symbolSize: number;
71
+ /** Number of source blocks (Z) */
72
+ sourceBlocks: number;
73
+ /** Number of sub-blocks (N) - used for large blocks */
74
+ subBlocks: number;
75
+ /** Symbol alignment (Al) - typically 8 for byte-aligned */
76
+ alignment: number;
77
+ /** Interleave depth - number of blocks that can be "in flight" */
78
+ interleaveDepth: number;
79
+ }
80
+
81
+ /**
82
+ * Options for FecClient
83
+ */
84
+ export interface FecClientOptions {
85
+ /** Interleave depth for FEC (default: 30 for ~1s at 30fps) */
86
+ interleaveDepth?: number;
87
+ /** Block timeout in milliseconds (default: 2000) */
88
+ blockTimeoutMs?: number;
89
+ /** Enable green-fill for failed frames (default: true) */
90
+ greenFillEnabled?: boolean;
91
+ /** Callback when data is recovered via FEC */
92
+ onRecovered?: (data: Uint8Array, timestamp: bigint) => void;
93
+ /** Callback for green-fill event */
94
+ onGreenFill?: (timestamp: bigint) => void;
95
+ /** Callback for stats update */
96
+ onStatsUpdate?: (stats: FecProcessingStats) => void;
97
+ }
98
+
99
+ /**
100
+ * WASM module interface for FecClient
101
+ * Matches mmt-wasm module exports
102
+ */
103
+ export interface FecWasmModule {
104
+ init: () => Promise<unknown>;
105
+ MmtFecDecoder: new (interleaveDepth?: number | null) => WasmFecDecoder;
106
+ parse_fec_payload_id: (data: Uint8Array) => WasmFecPayloadId;
107
+ is_simd_enabled: () => boolean;
108
+ }
109
+
110
+ /**
111
+ * Container-Agnostic FEC Client
112
+ *
113
+ * Provides RaptorQ FEC decoding for any container format.
114
+ * The actual container parsing (MMTP, LOC, etc.) is handled separately.
115
+ */
116
+ export class FecClient {
117
+ private fecDecoder: WasmFecDecoder | null = null;
118
+ private options: Required<FecClientOptions>;
119
+ private initialized = false;
120
+ private greenFillCount = 0;
121
+ private cleanupInterval: ReturnType<typeof setInterval> | null = null;
122
+
123
+ // WASM module references (set via setWasmModule or init)
124
+ private wasmModule: FecWasmModule | null = null;
125
+
126
+ constructor(options: FecClientOptions = {}) {
127
+ this.options = {
128
+ interleaveDepth: options.interleaveDepth ?? 30,
129
+ blockTimeoutMs: options.blockTimeoutMs ?? 2000,
130
+ greenFillEnabled: options.greenFillEnabled ?? true,
131
+ onRecovered: options.onRecovered ?? (() => {}),
132
+ onGreenFill: options.onGreenFill ?? (() => {}),
133
+ onStatsUpdate: options.onStatsUpdate ?? (() => {}),
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Set WASM module (call before init)
139
+ */
140
+ setWasmModule(module: FecWasmModule): void {
141
+ this.wasmModule = module;
142
+ }
143
+
144
+ /**
145
+ * Initialize the WASM FEC decoder
146
+ */
147
+ async init(wasmModule?: FecWasmModule): Promise<void> {
148
+ if (this.initialized) return;
149
+
150
+ if (wasmModule) {
151
+ this.wasmModule = wasmModule;
152
+ }
153
+
154
+ if (!this.wasmModule) {
155
+ throw new Error("WASM module not set. Call setWasmModule() or pass module to init()");
156
+ }
157
+
158
+ await this.wasmModule.init();
159
+
160
+ this.fecDecoder = new this.wasmModule.MmtFecDecoder(this.options.interleaveDepth);
161
+ this.fecDecoder.set_timeout(this.options.blockTimeoutMs);
162
+ this.fecDecoder.set_green_fill(this.options.greenFillEnabled);
163
+
164
+ // Start periodic cleanup
165
+ this.cleanupInterval = setInterval(() => {
166
+ this.cleanup();
167
+ }, 500);
168
+
169
+ this.initialized = true;
170
+ console.log(`[FecClient] Initialized (SIMD: ${this.wasmModule.is_simd_enabled()})`);
171
+ }
172
+
173
+ /**
174
+ * Check if initialized
175
+ */
176
+ isInitialized(): boolean {
177
+ return this.initialized;
178
+ }
179
+
180
+ /**
181
+ * Configure FEC parameters from OTI (Object Transmission Information)
182
+ * Per RFC 6330 (RaptorQ)
183
+ */
184
+ configure(oti: Uint8Array): void {
185
+ if (!this.fecDecoder) {
186
+ throw new Error("FecClient not initialized");
187
+ }
188
+ this.fecDecoder.configure(oti);
189
+ }
190
+
191
+ /**
192
+ * Add a source symbol to the FEC decoder
193
+ *
194
+ * @param sbn - Source Block Number
195
+ * @param esi - Encoding Symbol ID (< K for source symbols)
196
+ * @param data - Symbol data
197
+ * @param timestamp - Packet timestamp for ordering
198
+ * @returns Decoded data if block is complete, null otherwise
199
+ */
200
+ addSource(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | null {
201
+ if (!this.fecDecoder) return null;
202
+ return this.fecDecoder.add_source(sbn, esi, data, timestamp) ?? null;
203
+ }
204
+
205
+ /**
206
+ * Add a repair symbol to the FEC decoder
207
+ *
208
+ * @param sbn - Source Block Number
209
+ * @param esi - Encoding Symbol ID (>= K for repair symbols)
210
+ * @param data - Repair symbol data
211
+ * @param timestamp - Packet timestamp for ordering
212
+ * @returns Recovered data if FEC decoding succeeded, null otherwise
213
+ */
214
+ addRepair(sbn: number, esi: number, data: Uint8Array, timestamp: number): Uint8Array | null {
215
+ if (!this.fecDecoder) return null;
216
+
217
+ try {
218
+ const decoded = this.fecDecoder.add_repair(sbn, esi, data, timestamp);
219
+
220
+ if (decoded && decoded.length > 0) {
221
+ const recovered = new Uint8Array(decoded);
222
+ this.options.onRecovered(recovered, BigInt(timestamp));
223
+ return recovered;
224
+ }
225
+ } catch (e) {
226
+ console.warn("[FecClient] Repair processing error:", e);
227
+ }
228
+
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * Process a repair packet with FEC payload ID header
234
+ * Per draft-ramadan-moq-fec-00 Section 6.1
235
+ *
236
+ * @param payload - Repair packet payload (4-byte header + repair symbols)
237
+ * @param timestamp - Packet timestamp
238
+ * @returns Recovered data if FEC decoding succeeded, null otherwise
239
+ */
240
+ processRepairPacket(payload: Uint8Array, timestamp: number): Uint8Array | null {
241
+ if (!this.fecDecoder || !this.wasmModule || payload.length < 4) return null;
242
+
243
+ try {
244
+ const { sbn, esi } = this.wasmModule.parse_fec_payload_id(payload.subarray(0, 4));
245
+ const data = payload.subarray(4);
246
+
247
+ return this.addRepair(sbn, esi, data, timestamp);
248
+ } catch (e) {
249
+ console.warn("[FecClient] FEC payload parse error:", e);
250
+ return null;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Cleanup expired blocks
256
+ * Returns green-fill frame data for expired blocks if green-fill is enabled
257
+ */
258
+ cleanup(): void {
259
+ if (!this.fecDecoder) return;
260
+
261
+ const greenFillData = this.fecDecoder.cleanup(performance.now());
262
+
263
+ // greenFillData is a Uint8Array returned by WASM for expired blocks
264
+ if (greenFillData && greenFillData.length > 0 && this.options.greenFillEnabled) {
265
+ // Count green-fills based on data returned
266
+ const greenFillsTriggered = Math.ceil(greenFillData.length / 8);
267
+ if (greenFillsTriggered > 0) {
268
+ this.greenFillCount += greenFillsTriggered;
269
+ this.options.onGreenFill(BigInt(performance.now()));
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Get number of pending blocks
276
+ */
277
+ getPendingBlocks(): number {
278
+ return this.fecDecoder?.pending_blocks() ?? 0;
279
+ }
280
+
281
+ /**
282
+ * Get current FEC statistics
283
+ */
284
+ getStats(): FecProcessingStats {
285
+ const fecStats = this.fecDecoder?.get_stats();
286
+
287
+ return {
288
+ sourcePackets: Number(fecStats?.source_packets ?? 0n),
289
+ repairPackets: Number(fecStats?.repair_packets ?? 0n),
290
+ blocksComplete: Number(fecStats?.blocks_complete ?? 0n),
291
+ blocksRecovered: Number(fecStats?.blocks_recovered ?? 0n),
292
+ blocksFailed: Number(fecStats?.blocks_failed ?? 0n),
293
+ recoveryRate: fecStats?.recovery_rate() ?? 100,
294
+ greenFillFrames: this.greenFillCount,
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Get current FEC configuration
300
+ * Returns the configured interleave depth (OTI params set via configure())
301
+ */
302
+ getConfig(): FecConfig {
303
+ return {
304
+ symbolSize: 1280, // Default, configured via OTI
305
+ sourceBlocks: 1, // Default, configured via OTI
306
+ subBlocks: 1, // Default, configured via OTI
307
+ alignment: 8, // Default, configured via OTI
308
+ interleaveDepth: this.options.interleaveDepth,
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Reset statistics
314
+ */
315
+ resetStats(): void {
316
+ this.fecDecoder?.reset_stats();
317
+ this.greenFillCount = 0;
318
+ }
319
+
320
+ /**
321
+ * Dispose of resources
322
+ */
323
+ dispose(): void {
324
+ if (this.cleanupInterval) {
325
+ clearInterval(this.cleanupInterval);
326
+ this.cleanupInterval = null;
327
+ }
328
+ this.fecDecoder?.flush();
329
+ this.initialized = false;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Create a shared FEC client instance (singleton pattern)
335
+ */
336
+ let sharedFecClient: FecClient | null = null;
337
+
338
+ export async function getSharedFecClient(
339
+ wasmModule: FecWasmModule,
340
+ options?: FecClientOptions
341
+ ): Promise<FecClient> {
342
+ if (!sharedFecClient) {
343
+ sharedFecClient = new FecClient(options);
344
+ await sharedFecClient.init(wasmModule);
345
+ }
346
+ return sharedFecClient;
347
+ }
348
+
349
+ export function resetSharedFecClient(): void {
350
+ if (sharedFecClient) {
351
+ sharedFecClient.dispose();
352
+ sharedFecClient = null;
353
+ }
354
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @mmt/fec - Container-agnostic FEC client for RaptorQ (RFC 6330)
3
+ *
4
+ * This package provides a TypeScript wrapper around the mmt-wasm WASM bindings
5
+ * for Forward Error Correction using RaptorQ (RFC 6330).
6
+ *
7
+ * Usage:
8
+ * ```typescript
9
+ * import { FecClient } from '@mmt/fec';
10
+ * import * as wasmModule from '@mmt/wasm';
11
+ *
12
+ * const client = new FecClient({ interleaveDepth: 30 });
13
+ * await client.init(wasmModule);
14
+ *
15
+ * // Configure from OTI
16
+ * client.configure(otiBytes);
17
+ *
18
+ * // Add source/repair symbols
19
+ * const recovered = client.addRepair(sbn, esi, data, timestamp);
20
+ * ```
21
+ */
22
+
23
+ export {
24
+ FecClient,
25
+ getSharedFecClient,
26
+ resetSharedFecClient,
27
+ type FecConfig,
28
+ type FecClientOptions,
29
+ type FecProcessingStats,
30
+ type FecWasmModule,
31
+ } from "./fec-client.js";