@blockcast/fec-worker 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.
- package/README.md +213 -0
- package/dist/.build-stamp +0 -0
- package/dist/fec-worker-client.d.ts +80 -0
- package/dist/fec-worker-client.d.ts.map +1 -0
- package/dist/fec-worker-client.js +270 -0
- package/dist/fec-worker-client.js.map +1 -0
- package/dist/fec-worker-types.d.ts +252 -0
- package/dist/fec-worker-types.d.ts.map +1 -0
- package/dist/fec-worker-types.js +11 -0
- package/dist/fec-worker-types.js.map +1 -0
- package/dist/fec-worker.d.ts +14 -0
- package/dist/fec-worker.d.ts.map +1 -0
- package/dist/fec-worker.js +565 -0
- package/dist/fec-worker.js.map +7 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/shred-fec-worker.d.ts +20 -0
- package/dist/shred-fec-worker.d.ts.map +1 -0
- package/dist/shred-fec-worker.js +349 -0
- package/dist/shred-fec-worker.js.map +7 -0
- package/dist/shred-worker-client.d.ts +104 -0
- package/dist/shred-worker-client.d.ts.map +1 -0
- package/dist/shred-worker-client.js +181 -0
- package/dist/shred-worker-client.js.map +1 -0
- package/dist/shred-worker-types.d.ts +179 -0
- package/dist/shred-worker-types.d.ts.map +1 -0
- package/dist/shred-worker-types.js +47 -0
- package/dist/shred-worker-types.js.map +1 -0
- package/dist/transfer.d.ts +9 -0
- package/dist/transfer.d.ts.map +1 -0
- package/dist/transfer.js +13 -0
- package/dist/transfer.js.map +1 -0
- package/package.json +54 -0
- package/src/__tests__/fec-worker-alta.test.ts +231 -0
- package/src/__tests__/fec-worker-cleanup-integration.test.ts +250 -0
- package/src/__tests__/fec-worker-pending-queue.test.ts +315 -0
- package/src/__tests__/fec-worker-repair-size.test.ts +1029 -0
- package/src/__tests__/fec-worker-wasm-contract.test.ts +84 -0
- package/src/__tests__/shred-fec-worker.test.ts +448 -0
- package/src/fec-worker-client.test.ts +530 -0
- package/src/fec-worker-client.ts +309 -0
- package/src/fec-worker-types.test.ts +400 -0
- package/src/fec-worker-types.ts +268 -0
- package/src/fec-worker.ts +1048 -0
- package/src/index.ts +32 -0
- package/src/shred-fec-worker.ts +558 -0
- package/src/shred-worker-client.test.ts +182 -0
- package/src/shred-worker-client.ts +222 -0
- package/src/shred-worker-types.ts +194 -0
- package/src/transfer.test.ts +24 -0
- package/src/transfer.ts +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# @blockcast/fec-worker
|
|
2
|
+
|
|
3
|
+
A Web Worker wrapper around `@blockcast/mmt-fec` that moves the RaptorQ decode off the main thread. The main-thread client posts source and repair symbols to a dedicated worker via `postMessage` with `Transferable` `ArrayBuffer` ownership transfer, so packet payloads cross the thread boundary with **zero copies**. Decoded / FEC-recovered frames travel back the same way. Critical for IWA / web players where main-thread blocking on FEC decode would cause frame drops at the renderer.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @blockcast/fec-worker
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The package is dual-shaped: the main-thread side (`FecWorkerClient`, types) is the default export; the worker entry is exposed as the `./worker` subpath so consumers can hand its URL to `new Worker(...)` (or to the `FecWorkerClient` constructor, which spawns it for you).
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { FecWorkerClient, type FecTrackConfig } from "@blockcast/fec-worker"
|
|
17
|
+
|
|
18
|
+
// Worker URL resolved against your bundler. ?worker / new URL() / import.meta —
|
|
19
|
+
// any pattern that gives you a module-Worker URL works.
|
|
20
|
+
const workerUrl = new URL("./fec-worker-entry.js", import.meta.url)
|
|
21
|
+
|
|
22
|
+
const client = new FecWorkerClient(workerUrl)
|
|
23
|
+
|
|
24
|
+
client.onFrame((data, meta) => {
|
|
25
|
+
// data is an ArrayBuffer transferred from the worker (zero-copy).
|
|
26
|
+
// meta tells you whether this frame was recovered via FEC and (if ALTA is
|
|
27
|
+
// configured) whether its source-packet trailer verified.
|
|
28
|
+
decoder.decode(data) // hand off to WebCodecs / MSE
|
|
29
|
+
})
|
|
30
|
+
client.onSnapshot((stats) => fecPanel.render(stats))
|
|
31
|
+
client.onError((msg) => console.error("[fec]", msg))
|
|
32
|
+
|
|
33
|
+
// Configure with a pre-compiled mmt-wasm Module from your loader's importmap.
|
|
34
|
+
const config: FecTrackConfig = {
|
|
35
|
+
codec: "avc1.64001f",
|
|
36
|
+
trackName: "video/base",
|
|
37
|
+
trackType: "video",
|
|
38
|
+
resolution: { w: 1920, h: 1080 },
|
|
39
|
+
framerate: 30,
|
|
40
|
+
algorithm: "raptor",
|
|
41
|
+
k: 32,
|
|
42
|
+
repairCount: 8,
|
|
43
|
+
symbolSize: 1312,
|
|
44
|
+
interleaveDepth: 30,
|
|
45
|
+
interleaveMs: 1000, // 30 frames @ 30 fps
|
|
46
|
+
deliveryWindowMs: 2000, // computeFecDeliveryWindowMs(...) from the catalog
|
|
47
|
+
fecMode: "subframe",
|
|
48
|
+
altaEnabled: false,
|
|
49
|
+
relayBlockSigEnabled: false,
|
|
50
|
+
}
|
|
51
|
+
client.configure(config, mmtWasmModule)
|
|
52
|
+
|
|
53
|
+
// Feed packets as they arrive from transport. ArrayBuffers are transferred —
|
|
54
|
+
// caller MUST NOT touch `data` after the call.
|
|
55
|
+
client.feedSource(ssId, packetBuffer, captureTs)
|
|
56
|
+
client.feedRepair(ssStart, ssbLength, rsId, repairBuffer, captureTs)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`FecWorkerClient` accepts either a `URL` (it spawns the worker for you with `{ type: "module" }`) or a pre-created `Worker` instance (used in tests for constructor injection).
|
|
60
|
+
|
|
61
|
+
## Why a Worker
|
|
62
|
+
|
|
63
|
+
A 1080p30 stream with `K=32, P=8, symbolSize=1312` produces ~1 RaptorQ source-block decode every second per track. Decode time is amortized across SIMD lanes inside the WASM, but the *worst-case* block — one with the maximum recoverable loss pattern — can spike past 16 ms. Run that on the main thread and you've blown a frame; the renderer drops, the user sees a stutter, and CMCD reports a pacing violation.
|
|
64
|
+
|
|
65
|
+
Pushing the decode into a dedicated Worker keeps the main thread free for input handling, MSE buffer maintenance, and frame pacing. The same module compiled into both contexts decodes identically; what changes is who's blocked when a worst-case block hits.
|
|
66
|
+
|
|
67
|
+
This is the **Worker-per-track FEC pipeline** architectural decision tracked in the parent monorepo as a v2.0 milestone (`pim-multicast-gateway/.planning/MILESTONES.md` — "Worker-per-track FEC pipeline with zero-copy ArrayBuffer transfers"). Each track (base / delta / repair / audio) runs in its own Worker, so a stall on one track's decode never starves the others.
|
|
68
|
+
|
|
69
|
+
## Architecture
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Main thread │ FEC Worker (per track)
|
|
73
|
+
│
|
|
74
|
+
transport packet │
|
|
75
|
+
│ │
|
|
76
|
+
▼ │
|
|
77
|
+
FecWorkerClient.feedSource(...) │
|
|
78
|
+
│ │
|
|
79
|
+
│ postMessage(cmd, [data, ...]) │
|
|
80
|
+
│ ─ data: ArrayBuffer (transferred, neutered on sender)
|
|
81
|
+
│ ─ wasmModule: WebAssembly.Module (structured-cloned)
|
|
82
|
+
├──────────────────────────────────►│ self.onmessage
|
|
83
|
+
│ │ │
|
|
84
|
+
│ │ ▼
|
|
85
|
+
│ │ WasmFecDecoder.add_source(...)
|
|
86
|
+
│ │ │
|
|
87
|
+
│ │ ▼ (recovered or pass-through)
|
|
88
|
+
│ │ postMessage({type:"frame", data}, [data])
|
|
89
|
+
│◄──────────────────────────────────┤
|
|
90
|
+
│ │
|
|
91
|
+
▼ │
|
|
92
|
+
FecWorkerClient.onFrame(data, meta) │
|
|
93
|
+
│ │
|
|
94
|
+
▼ │
|
|
95
|
+
WebCodecs / MSE │
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Buffers cross the boundary via the `Transferable` list. After `postMessage`, the sender's `ArrayBuffer` is detached — touching it throws. This is the contract that makes zero-copy safe: there is no aliased view that could be mutated mid-decode.
|
|
99
|
+
|
|
100
|
+
`WebAssembly.Module` is structured-cloneable (not transferable) and is *cloned* (cheap — the underlying compiled module is shared across realms). The ALTA public key, on the other hand, is a regular `ArrayBuffer` and *is* transferred.
|
|
101
|
+
|
|
102
|
+
Errors that escape the worker's `try/catch` (in either the sync command path or the async configure path) are emitted as `{type: "error", message}` events rather than thrown — the main thread surfaces them through `onError`.
|
|
103
|
+
|
|
104
|
+
## Message protocol
|
|
105
|
+
|
|
106
|
+
All messages are typed as discriminated unions in `fec-worker-types.ts`. Both the worker and the client import the same definitions, so adding a message variant is a single-file change that the compiler enforces on both sides.
|
|
107
|
+
|
|
108
|
+
### Inbound — `FecWorkerCommand` (main → worker)
|
|
109
|
+
|
|
110
|
+
| `type` | Payload | Transferred |
|
|
111
|
+
|--------|---------|-------------|
|
|
112
|
+
| `configure` | `config: FecTrackConfig`, `wasmModule: WebAssembly.Module`, `altaPublicKey?: ArrayBuffer`, `altaWasmModule?: WebAssembly.Module` | `altaPublicKey` only — WASM modules are structured-cloned |
|
|
113
|
+
| `feedSource` | `ssId: number`, `data: ArrayBuffer`, `ts: number`, optional `altaAuth: ArrayBuffer` + `altaPayloadEnd: number` | `data` and `altaAuth` |
|
|
114
|
+
| `feedRepair` | `ssStart: number`, `ssbLength: number`, `rsId: number`, `data: ArrayBuffer`, `ts: number` | `data` |
|
|
115
|
+
| `relayBlockSig` | `sbn: number`, `sig: ArrayBuffer`, `hash: ArrayBuffer` | `sig`, `hash` |
|
|
116
|
+
| `cleanup` | `sbns: number[]` (all timed-out blocks covered by this coalesced barrier) | none |
|
|
117
|
+
| `dispose` | — | none |
|
|
118
|
+
|
|
119
|
+
`feedSource` carries the **flat 32-bit Source Symbol ID** from the MMTP Source FEC Payload ID (ISO 23008-1 §C.5.2). The WASM decoder derives `SBN = floor(ssId / K)` and `ESI = ssId % K` internally — do not pre-compute these.
|
|
120
|
+
|
|
121
|
+
`feedRepair` carries the wire-level Repair FEC Payload ID fields directly (ISO 23008-1 §C.5.3): `ssStart` is the SS_ID of the first source symbol in the block, `ssbLength` must equal the track's configured K, and `rsId` is the repair index within the block. A different K starts a new configured decoder epoch; it is never inferred from repair traffic.
|
|
122
|
+
|
|
123
|
+
### Outbound — `FecWorkerEvent` (worker → main)
|
|
124
|
+
|
|
125
|
+
| `type` | Payload | Transferred back |
|
|
126
|
+
|--------|---------|------------------|
|
|
127
|
+
| `frame` | `data: ArrayBuffer`, `meta: FrameMeta` | `data` |
|
|
128
|
+
| `snapshot` | `stats: FecTrackStats` | none (structured clone) |
|
|
129
|
+
| `blockUpdate` | `block: FecBlockSnapshot` | none |
|
|
130
|
+
| `greenFill` | `sbn: number`, `data: ArrayBuffer` | `data` |
|
|
131
|
+
| `cleanupComplete` | `sbns: number[]` | none |
|
|
132
|
+
| `error` | `message: string` | none |
|
|
133
|
+
|
|
134
|
+
`FrameMeta` carries `recovered: boolean` (this frame came out of the FEC decode, not the direct path), `sbn: number`, and `altaVerified: boolean | null` (signature outcome, or `null` when ALTA is not configured / verification is pending).
|
|
135
|
+
|
|
136
|
+
## Lifecycle
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
new FecWorkerClient(url)
|
|
140
|
+
│ worker spawned with { type: "module" }
|
|
141
|
+
▼
|
|
142
|
+
client.configure(config, wasmModule [, altaPublicKey, altaWasmModule])
|
|
143
|
+
│ worker initSyncs WASM, instantiates MmtFecDecoder(interleaveDepth, videoFill),
|
|
144
|
+
│ immediately configures exact catalog K/T (no decoder defaults),
|
|
145
|
+
│ derives pendingVerifyCap = ceil(D × 1.5) and TTL = ceil(K × interleaveMs × 1.5)
|
|
146
|
+
│ from catalog values, throws if any of K / D / interleaveMs ≤ 0,
|
|
147
|
+
│ starts the 200 ms snapshot timer, emits initial snapshot
|
|
148
|
+
▼
|
|
149
|
+
client.feedSource(...) / feedRepair(...) ◄── steady state
|
|
150
|
+
│ every transferred ArrayBuffer detaches on the main thread
|
|
151
|
+
│ decoded / recovered frames flow back as {type:"frame"}
|
|
152
|
+
│ every 200 ms a {type:"snapshot"} carries FecTrackStats
|
|
153
|
+
▼
|
|
154
|
+
client.cleanup(sbn) ◄── per-block inactivity deadline
|
|
155
|
+
│ coalesces queued SBNs behind one worker queue-drain barrier
|
|
156
|
+
│ evicts blocks relative to max(sbns) − D × 3
|
|
157
|
+
│ emits green-fill with the true SBN from WASM cleanup_blocks_detailed()
|
|
158
|
+
│ acknowledges every SBN in {type:"cleanupComplete", sbns}
|
|
159
|
+
│ any block still in "collecting" at eviction time is counted as failed
|
|
160
|
+
▼
|
|
161
|
+
client.dispose()
|
|
162
|
+
sends {type:"dispose"}, terminates worker. WASM decoder and ALTA
|
|
163
|
+
verifier are explicitly free()'d before terminate so their linear
|
|
164
|
+
memory is released (FinalizationRegistry would not run between
|
|
165
|
+
dispose and terminate). Idempotent.
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Backpressure
|
|
169
|
+
|
|
170
|
+
`postMessage` is fire-and-forget at the JS level — there is no built-in flow control. If the main thread can't drain `frame` events fast enough, the worker's outbound queue grows. The remedy is upstream of this package: pace your input (`feedSource` / `feedRepair`) against actual decode capacity by watching the `avgRecoveryMs` / `maxRecoveryMs` fields of each `snapshot`. Spikes in `maxRecoveryMs` past your frame budget are the early signal.
|
|
171
|
+
|
|
172
|
+
## Stats
|
|
173
|
+
|
|
174
|
+
The `snapshot` event delivers a `FecTrackStats` every 200 ms (catalog-derived; not configurable from this layer). Key fields:
|
|
175
|
+
|
|
176
|
+
| Field | Meaning |
|
|
177
|
+
|-------|---------|
|
|
178
|
+
| `sourceSymbols`, `repairSymbols` | Raw ingress counters |
|
|
179
|
+
| `blocksComplete` | Block had all K source symbols without needing repair |
|
|
180
|
+
| `blocksRecovered` | Block was recovered using ≥1 repair symbol |
|
|
181
|
+
| `blocksFailed` | Block aged out before reaching K |
|
|
182
|
+
| `greenFillFrames` | Frames substituted with green-fill on unrecoverable loss |
|
|
183
|
+
| `recoveryRate` | `recovered / (recovered + failed)`, 0–100 |
|
|
184
|
+
| `avgRecoveryMs`, `maxRecoveryMs` | Rolling decode latency (capped at 1000 samples) |
|
|
185
|
+
| `liveSbn` | Highest source-block number observed |
|
|
186
|
+
| `blocks` | Up to `max(D × 3, 24)` recent `FecBlockSnapshot`s for live visualization |
|
|
187
|
+
| `interleaveWindowBlocks` | `D` from config |
|
|
188
|
+
|
|
189
|
+
When ALTA is enabled, the snapshot also carries the four-state verification accounting — `altaVerified` (crypto passed), `altaFailed` (tamper-confirmed), `altaPending` (unverifiable, e.g. missing trailer / late-joiner anchor gap), `altaError` (verifier threw — infrastructure flake, not tamper) — plus `altaInitFailed` (true if the ALTA WASM didn't initialize), `altaPendingEvictedCap` / `altaPendingEvictedTtl` (deferred-queue eviction reasons), and a bounded `altaFailedDetails[]` ring with the last ≤16 tamper events. The four-way split exists so ops alerting can distinguish a network problem (`altaPending` rising) from an attack (`altaFailed` rising) from a runtime bug (`altaError` rising).
|
|
190
|
+
|
|
191
|
+
`FecTrackStats` is also the shape consumed downstream by `@blockcast/mmt-container`'s `CmcdProducer` and the diagnostics `fec-panel` — keep it stable when you extend it.
|
|
192
|
+
|
|
193
|
+
## Related packages
|
|
194
|
+
|
|
195
|
+
- **`@blockcast/mmt-fec`** — the wrapped library. Container-agnostic RaptorQ decoder + block manager + reorder buffer. Lives on the main thread when you don't need a Worker; this package is the off-thread variant of the same decoder.
|
|
196
|
+
- **`@blockcast/mmt-container`** — the consumer that wires this Worker into the data path. `FecManager` instantiates one `FecWorkerClient` per track, routes MMTP source/repair payloads in, and pumps recovered frames into the MFU reassembler / fmp4 segmenter.
|
|
197
|
+
- **`@blockcast/mmt-alta-parse`** — ALTA trailer extraction. Used inside the Worker to parse the `[0xF0A1][len][auth]` trailer off recovered source symbols (publisher-stripped trailers don't appear on direct-source packets, which carry the trailer in band).
|
|
198
|
+
|
|
199
|
+
## Testing
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pnpm test # vitest run
|
|
203
|
+
pnpm test:watch # vitest in watch mode
|
|
204
|
+
pnpm typecheck # tsc --noEmit
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Tests cover:
|
|
208
|
+
- The message-protocol type narrowing (`fec-worker-types.test.ts`)
|
|
209
|
+
- The main-thread client's transfer-list construction, callback dispatch, and dispose idempotence (`fec-worker-client.test.ts`) — the Worker is injected via constructor as a stub so the tests don't need a real WASM build
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
Apache-2.0 — see `LICENSE`.
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @blockcast/fec-worker — Main-Thread Client
|
|
3
|
+
*
|
|
4
|
+
* Wraps one Worker per track instance. Provides typed API for the
|
|
5
|
+
* FecWorkerCommand/FecWorkerEvent protocol with zero-copy ArrayBuffer transfers.
|
|
6
|
+
*
|
|
7
|
+
* Constructor accepts either a URL (creates Worker internally) or a pre-created
|
|
8
|
+
* Worker instance (for testing via constructor injection).
|
|
9
|
+
*/
|
|
10
|
+
import type { FecTrackConfig, FecTrackStats, FecBlockSnapshot, FrameMeta, FecWorkerErrorCode } from "./fec-worker-types.js";
|
|
11
|
+
export declare class FecWorkerClient {
|
|
12
|
+
#private;
|
|
13
|
+
/**
|
|
14
|
+
* Create a FecWorkerClient.
|
|
15
|
+
* @param workerOrUrl - Either a Worker instance (for testing) or a URL to create one
|
|
16
|
+
*/
|
|
17
|
+
constructor(workerOrUrl: Worker | URL);
|
|
18
|
+
/**
|
|
19
|
+
* Configure the Worker with FEC parameters and WASM module.
|
|
20
|
+
* wasmModule is transferred (not neutered — WebAssembly.Module is shareable).
|
|
21
|
+
* altaPublicKey is PKCS#8 SPKI DER bytes (self-describing — algorithm detected from OID).
|
|
22
|
+
* Transferred (detached from sender).
|
|
23
|
+
* altaWasmModule is structured-cloneable (NOT transferable — do not add to transfer list).
|
|
24
|
+
*/
|
|
25
|
+
configure(config: FecTrackConfig, wasmModule: WebAssembly.Module, altaPublicKey?: ArrayBuffer, altaWasmModule?: WebAssembly.Module): void;
|
|
26
|
+
/**
|
|
27
|
+
* Feed a source symbol to the Worker. data ArrayBuffer is transferred (zero-copy).
|
|
28
|
+
* Caller must not access data after this call — it is detached.
|
|
29
|
+
*
|
|
30
|
+
* Optional `alta` carries the detached ALTA authenticator bytes and the
|
|
31
|
+
* byte offset in `data` where the signed payload ends. The auth buffer is
|
|
32
|
+
* transferred zero-copy alongside `data`. Use {@link getMmtpAltaTrailer}
|
|
33
|
+
* upstream (mmtp-router) to extract it from the wire packet.
|
|
34
|
+
*/
|
|
35
|
+
feedSource(ssId: number, data: ArrayBuffer, ts: number, alta?: {
|
|
36
|
+
auth: ArrayBuffer;
|
|
37
|
+
payloadEnd: number;
|
|
38
|
+
}): void;
|
|
39
|
+
/**
|
|
40
|
+
* Feed a repair symbol to the Worker. data ArrayBuffer is transferred (zero-copy).
|
|
41
|
+
* Caller must not access data after this call — it is detached.
|
|
42
|
+
*
|
|
43
|
+
* Carries the wire-level Repair FEC Payload ID fields (ISO 23008-1 §C.5.3):
|
|
44
|
+
* - ssStart: SS_ID of first source symbol in the block (= SBN * SSB_length)
|
|
45
|
+
* - ssbLength: K for this block
|
|
46
|
+
* - rsId: repair symbol index within the block
|
|
47
|
+
*/
|
|
48
|
+
feedRepair(ssStart: number, ssbLength: number, rsId: number, data: ArrayBuffer, ts: number): void;
|
|
49
|
+
/**
|
|
50
|
+
* Submit relay block signature for verification. sig and hash are transferred.
|
|
51
|
+
*/
|
|
52
|
+
relayBlockSig(sbn: number, sig: ArrayBuffer, hash: ArrayBuffer): void;
|
|
53
|
+
/**
|
|
54
|
+
* Trigger cleanup after a block's inactivity deadline.
|
|
55
|
+
*
|
|
56
|
+
* While one queue-drain barrier is in flight, later SBNs are coalesced into
|
|
57
|
+
* the next command without losing their individual acknowledgement identity.
|
|
58
|
+
*/
|
|
59
|
+
cleanup(currentSbn: number): void;
|
|
60
|
+
/** Reset this track's decoder epoch while preserving Worker configuration. */
|
|
61
|
+
reset(): void;
|
|
62
|
+
/** Register callback for decoded/recovered frames (FEC-03). */
|
|
63
|
+
onFrame(cb: (data: ArrayBuffer, meta: FrameMeta) => void): void;
|
|
64
|
+
/** Register callback for periodic stats snapshots (FEC-04). */
|
|
65
|
+
onSnapshot(cb: (stats: FecTrackStats) => void): void;
|
|
66
|
+
/** Register callback for individual block state changes. */
|
|
67
|
+
onBlockUpdate(cb: (block: FecBlockSnapshot) => void): void;
|
|
68
|
+
/** Register callback for green-fill frames. */
|
|
69
|
+
onGreenFill(cb: (sbn: number, data: ArrayBuffer) => void): void;
|
|
70
|
+
/** Register callback fired after the Worker drains all commands queued before cleanup. */
|
|
71
|
+
onCleanupComplete(cb: (sbn: number) => void): void;
|
|
72
|
+
/** Register callback for Worker errors. */
|
|
73
|
+
onError(cb: (message: string, code?: FecWorkerErrorCode) => void): void;
|
|
74
|
+
/**
|
|
75
|
+
* Dispose the Worker. Sends dispose command then terminates.
|
|
76
|
+
* Idempotent — safe to call multiple times.
|
|
77
|
+
*/
|
|
78
|
+
dispose(): void;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=fec-worker-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fec-worker-client.d.ts","sourceRoot":"","sources":["../src/fec-worker-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAGX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,kBAAkB,EAClB,MAAM,uBAAuB,CAAC;AAE/B,qBAAa,eAAe;;IAc3B;;;OAGG;gBACS,WAAW,EAAE,MAAM,GAAG,GAAG;IAgBrC;;;;;;OAMG;IACH,SAAS,CACR,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,WAAW,CAAC,MAAM,EAC9B,aAAa,CAAC,EAAE,WAAW,EAC3B,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,GACjC,IAAI;IAkBP;;;;;;;;OAQG;IACH,UAAU,CACT,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAC9C,IAAI;IAgBP;;;;;;;;OAQG;IACH,UAAU,CACT,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,MAAM,GACR,IAAI;IAeP;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI;IAQrE;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAgBjC,8EAA8E;IAC9E,KAAK,IAAI,IAAI;IAgBb,+DAA+D;IAC/D,OAAO,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAI/D,+DAA+D;IAC/D,UAAU,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAIpD,4DAA4D;IAC5D,aAAa,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI;IAI1D,+CAA+C;IAC/C,WAAW,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,IAAI,GAAG,IAAI;IAI/D,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIlD,2CAA2C;IAC3C,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI;IAIvE;;;OAGG;IACH,OAAO,IAAI,IAAI;CAmDf"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @blockcast/fec-worker — Main-Thread Client
|
|
3
|
+
*
|
|
4
|
+
* Wraps one Worker per track instance. Provides typed API for the
|
|
5
|
+
* FecWorkerCommand/FecWorkerEvent protocol with zero-copy ArrayBuffer transfers.
|
|
6
|
+
*
|
|
7
|
+
* Constructor accepts either a URL (creates Worker internally) or a pre-created
|
|
8
|
+
* Worker instance (for testing via constructor injection).
|
|
9
|
+
*/
|
|
10
|
+
export class FecWorkerClient {
|
|
11
|
+
#worker;
|
|
12
|
+
#onFrame = null;
|
|
13
|
+
#onSnapshot = null;
|
|
14
|
+
#onBlockUpdate = null;
|
|
15
|
+
#onGreenFill = null;
|
|
16
|
+
#onCleanupComplete = null;
|
|
17
|
+
#onError = null;
|
|
18
|
+
#cleanupInFlight = null;
|
|
19
|
+
#queuedCleanupSbns = new Set();
|
|
20
|
+
#pendingResets = 0;
|
|
21
|
+
#resetFailed = false;
|
|
22
|
+
#disposed = false;
|
|
23
|
+
/**
|
|
24
|
+
* Create a FecWorkerClient.
|
|
25
|
+
* @param workerOrUrl - Either a Worker instance (for testing) or a URL to create one
|
|
26
|
+
*/
|
|
27
|
+
constructor(workerOrUrl) {
|
|
28
|
+
if (workerOrUrl instanceof URL) {
|
|
29
|
+
this.#worker = new Worker(workerOrUrl, { type: "module" });
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
this.#worker = workerOrUrl;
|
|
33
|
+
}
|
|
34
|
+
this.#worker.onmessage = (e) => this.#handleEvent(e.data);
|
|
35
|
+
this.#worker.onerror = (e) => {
|
|
36
|
+
this.#onError?.(formatWorkerError(e), "worker-runtime");
|
|
37
|
+
};
|
|
38
|
+
this.#worker.onmessageerror = (e) => {
|
|
39
|
+
this.#onError?.(formatWorkerMessageError(e), "worker-message");
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Configure the Worker with FEC parameters and WASM module.
|
|
44
|
+
* wasmModule is transferred (not neutered — WebAssembly.Module is shareable).
|
|
45
|
+
* altaPublicKey is PKCS#8 SPKI DER bytes (self-describing — algorithm detected from OID).
|
|
46
|
+
* Transferred (detached from sender).
|
|
47
|
+
* altaWasmModule is structured-cloneable (NOT transferable — do not add to transfer list).
|
|
48
|
+
*/
|
|
49
|
+
configure(config, wasmModule, altaPublicKey, altaWasmModule) {
|
|
50
|
+
if (this.#disposed)
|
|
51
|
+
throw new Error("[FecWorkerClient] disposed");
|
|
52
|
+
const cmd = {
|
|
53
|
+
type: "configure",
|
|
54
|
+
config,
|
|
55
|
+
wasmModule,
|
|
56
|
+
altaPublicKey,
|
|
57
|
+
altaWasmModule,
|
|
58
|
+
};
|
|
59
|
+
// WebAssembly.Module is structured-cloneable, NOT transferable.
|
|
60
|
+
// Pass it in the message body (Chrome clones it automatically).
|
|
61
|
+
// Only ArrayBuffers (like altaPublicKey) go in the transfer list.
|
|
62
|
+
const transferList = [];
|
|
63
|
+
if (altaPublicKey)
|
|
64
|
+
transferList.push(altaPublicKey);
|
|
65
|
+
// altaWasmModule is structured-cloneable — NOT added to transferList
|
|
66
|
+
this.#worker.postMessage(cmd, transferList);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Feed a source symbol to the Worker. data ArrayBuffer is transferred (zero-copy).
|
|
70
|
+
* Caller must not access data after this call — it is detached.
|
|
71
|
+
*
|
|
72
|
+
* Optional `alta` carries the detached ALTA authenticator bytes and the
|
|
73
|
+
* byte offset in `data` where the signed payload ends. The auth buffer is
|
|
74
|
+
* transferred zero-copy alongside `data`. Use {@link getMmtpAltaTrailer}
|
|
75
|
+
* upstream (mmtp-router) to extract it from the wire packet.
|
|
76
|
+
*/
|
|
77
|
+
feedSource(ssId, data, ts, alta) {
|
|
78
|
+
if (this.#disposed)
|
|
79
|
+
return;
|
|
80
|
+
const cmd = alta
|
|
81
|
+
? {
|
|
82
|
+
type: "feedSource",
|
|
83
|
+
ssId,
|
|
84
|
+
data,
|
|
85
|
+
ts,
|
|
86
|
+
altaAuth: alta.auth,
|
|
87
|
+
altaPayloadEnd: alta.payloadEnd,
|
|
88
|
+
}
|
|
89
|
+
: { type: "feedSource", ssId, data, ts };
|
|
90
|
+
const transfer = alta ? [data, alta.auth] : [data];
|
|
91
|
+
this.#worker.postMessage(cmd, transfer);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Feed a repair symbol to the Worker. data ArrayBuffer is transferred (zero-copy).
|
|
95
|
+
* Caller must not access data after this call — it is detached.
|
|
96
|
+
*
|
|
97
|
+
* Carries the wire-level Repair FEC Payload ID fields (ISO 23008-1 §C.5.3):
|
|
98
|
+
* - ssStart: SS_ID of first source symbol in the block (= SBN * SSB_length)
|
|
99
|
+
* - ssbLength: K for this block
|
|
100
|
+
* - rsId: repair symbol index within the block
|
|
101
|
+
*/
|
|
102
|
+
feedRepair(ssStart, ssbLength, rsId, data, ts) {
|
|
103
|
+
if (this.#disposed)
|
|
104
|
+
return;
|
|
105
|
+
this.#worker.postMessage({
|
|
106
|
+
type: "feedRepair",
|
|
107
|
+
ssStart,
|
|
108
|
+
ssbLength,
|
|
109
|
+
rsId,
|
|
110
|
+
data,
|
|
111
|
+
ts,
|
|
112
|
+
}, [data]);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Submit relay block signature for verification. sig and hash are transferred.
|
|
116
|
+
*/
|
|
117
|
+
relayBlockSig(sbn, sig, hash) {
|
|
118
|
+
if (this.#disposed)
|
|
119
|
+
return;
|
|
120
|
+
this.#worker.postMessage({ type: "relayBlockSig", sbn, sig, hash }, [sig, hash]);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Trigger cleanup after a block's inactivity deadline.
|
|
124
|
+
*
|
|
125
|
+
* While one queue-drain barrier is in flight, later SBNs are coalesced into
|
|
126
|
+
* the next command without losing their individual acknowledgement identity.
|
|
127
|
+
*/
|
|
128
|
+
cleanup(currentSbn) {
|
|
129
|
+
if (this.#disposed)
|
|
130
|
+
return;
|
|
131
|
+
if (!Number.isInteger(currentSbn) ||
|
|
132
|
+
currentSbn < 0 ||
|
|
133
|
+
currentSbn > 0xffff_ffff) {
|
|
134
|
+
throw new Error("[FecWorkerClient] cleanup: SBN must be a uint32");
|
|
135
|
+
}
|
|
136
|
+
if (this.#cleanupInFlight) {
|
|
137
|
+
this.#queuedCleanupSbns.add(currentSbn);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.#postCleanup(new Set([currentSbn]));
|
|
141
|
+
}
|
|
142
|
+
/** Reset this track's decoder epoch while preserving Worker configuration. */
|
|
143
|
+
reset() {
|
|
144
|
+
if (this.#disposed)
|
|
145
|
+
return;
|
|
146
|
+
this.#pendingResets++;
|
|
147
|
+
this.#cleanupInFlight = null;
|
|
148
|
+
this.#queuedCleanupSbns.clear();
|
|
149
|
+
this.#worker.postMessage({ type: "reset" });
|
|
150
|
+
}
|
|
151
|
+
#postCleanup(sbns) {
|
|
152
|
+
this.#cleanupInFlight = sbns;
|
|
153
|
+
const orderedSbns = [...sbns].sort((a, b) => a - b);
|
|
154
|
+
this.#worker.postMessage({ type: "cleanup", sbns: orderedSbns });
|
|
155
|
+
}
|
|
156
|
+
/** Register callback for decoded/recovered frames (FEC-03). */
|
|
157
|
+
onFrame(cb) {
|
|
158
|
+
this.#onFrame = cb;
|
|
159
|
+
}
|
|
160
|
+
/** Register callback for periodic stats snapshots (FEC-04). */
|
|
161
|
+
onSnapshot(cb) {
|
|
162
|
+
this.#onSnapshot = cb;
|
|
163
|
+
}
|
|
164
|
+
/** Register callback for individual block state changes. */
|
|
165
|
+
onBlockUpdate(cb) {
|
|
166
|
+
this.#onBlockUpdate = cb;
|
|
167
|
+
}
|
|
168
|
+
/** Register callback for green-fill frames. */
|
|
169
|
+
onGreenFill(cb) {
|
|
170
|
+
this.#onGreenFill = cb;
|
|
171
|
+
}
|
|
172
|
+
/** Register callback fired after the Worker drains all commands queued before cleanup. */
|
|
173
|
+
onCleanupComplete(cb) {
|
|
174
|
+
this.#onCleanupComplete = cb;
|
|
175
|
+
}
|
|
176
|
+
/** Register callback for Worker errors. */
|
|
177
|
+
onError(cb) {
|
|
178
|
+
this.#onError = cb;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Dispose the Worker. Sends dispose command then terminates.
|
|
182
|
+
* Idempotent — safe to call multiple times.
|
|
183
|
+
*/
|
|
184
|
+
dispose() {
|
|
185
|
+
if (this.#disposed)
|
|
186
|
+
return;
|
|
187
|
+
this.#disposed = true;
|
|
188
|
+
this.#cleanupInFlight = null;
|
|
189
|
+
this.#queuedCleanupSbns.clear();
|
|
190
|
+
this.#worker.postMessage({ type: "dispose" });
|
|
191
|
+
this.#worker.terminate();
|
|
192
|
+
}
|
|
193
|
+
#handleEvent(event) {
|
|
194
|
+
if (event.type === "resetComplete") {
|
|
195
|
+
this.#pendingResets = Math.max(0, this.#pendingResets - 1);
|
|
196
|
+
this.#resetFailed = false;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (event.type === "error") {
|
|
200
|
+
if (event.code === "reset-failed") {
|
|
201
|
+
this.#pendingResets = Math.max(0, this.#pendingResets - 1);
|
|
202
|
+
this.#resetFailed = true;
|
|
203
|
+
}
|
|
204
|
+
if (event.code)
|
|
205
|
+
this.#onError?.(event.message, event.code);
|
|
206
|
+
else
|
|
207
|
+
this.#onError?.(event.message);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (this.#pendingResets > 0 || this.#resetFailed)
|
|
211
|
+
return;
|
|
212
|
+
switch (event.type) {
|
|
213
|
+
case "frame":
|
|
214
|
+
this.#onFrame?.(event.data, event.meta);
|
|
215
|
+
break;
|
|
216
|
+
case "snapshot":
|
|
217
|
+
this.#onSnapshot?.(event.stats);
|
|
218
|
+
break;
|
|
219
|
+
case "blockUpdate":
|
|
220
|
+
this.#onBlockUpdate?.(event.block);
|
|
221
|
+
break;
|
|
222
|
+
case "greenFill":
|
|
223
|
+
this.#onGreenFill?.(event.sbn, event.data);
|
|
224
|
+
break;
|
|
225
|
+
case "cleanupComplete":
|
|
226
|
+
for (const sbn of event.sbns) {
|
|
227
|
+
this.#onCleanupComplete?.(sbn);
|
|
228
|
+
}
|
|
229
|
+
this.#cleanupInFlight = null;
|
|
230
|
+
if (this.#queuedCleanupSbns.size > 0) {
|
|
231
|
+
const next = this.#queuedCleanupSbns;
|
|
232
|
+
this.#queuedCleanupSbns = new Set();
|
|
233
|
+
this.#postCleanup(next);
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function formatWorkerError(event) {
|
|
240
|
+
const parts = ["Worker error"];
|
|
241
|
+
if (event.message)
|
|
242
|
+
parts.push(event.message);
|
|
243
|
+
if (event.filename) {
|
|
244
|
+
const location = [event.filename];
|
|
245
|
+
if (event.lineno)
|
|
246
|
+
location.push(String(event.lineno));
|
|
247
|
+
if (event.colno)
|
|
248
|
+
location.push(String(event.colno));
|
|
249
|
+
parts.push(`at ${location.join(":")}`);
|
|
250
|
+
}
|
|
251
|
+
if (event.error instanceof Error) {
|
|
252
|
+
if (event.error.message && event.error.message !== event.message) {
|
|
253
|
+
parts.push(event.error.message);
|
|
254
|
+
}
|
|
255
|
+
if (event.error.stack)
|
|
256
|
+
parts.push(event.error.stack);
|
|
257
|
+
}
|
|
258
|
+
else if (event.error != null) {
|
|
259
|
+
parts.push(String(event.error));
|
|
260
|
+
}
|
|
261
|
+
return parts.join(": ");
|
|
262
|
+
}
|
|
263
|
+
function formatWorkerMessageError(event) {
|
|
264
|
+
const data = event.data;
|
|
265
|
+
const detail = data == null ? "" :
|
|
266
|
+
data instanceof Error ? `: ${data.message}` :
|
|
267
|
+
`: ${Object.prototype.toString.call(data)}`;
|
|
268
|
+
return `Worker messageerror: failed to deserialize worker message${detail}`;
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=fec-worker-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fec-worker-client.js","sourceRoot":"","sources":["../src/fec-worker-client.ts"],"names":[],"mappings":"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"}
|