@blockcast/mmt-container 0.1.0-main.dd5ce397d768 → 0.1.0-main.e6500013301e
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 +44 -3
- package/dist/align.d.ts +6 -2
- package/dist/align.d.ts.map +1 -1
- package/dist/align.js.map +2 -2
- package/dist/audio-ring-buffer.js +23 -36
- package/dist/audio-ring-buffer.js.map +1 -1
- package/dist/block-policy.d.ts +314 -0
- package/dist/block-policy.d.ts.map +1 -0
- package/dist/block-policy.js +466 -0
- package/dist/block-policy.js.map +7 -0
- package/dist/browser-runtime.js +9972 -6703
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.d.ts +4 -3
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js +53 -25
- package/dist/carp-catalog.js.map +4 -4
- package/dist/catalog-fec-params.d.ts +43 -6
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +51 -4
- package/dist/catalog-fec-params.js.map +4 -4
- package/dist/cmaf-block-assembler.d.ts +67 -11
- package/dist/cmaf-block-assembler.d.ts.map +1 -1
- package/dist/cmaf-block-assembler.js +520 -614
- package/dist/cmaf-block-assembler.js.map +3 -3
- package/dist/cmaf-group-batcher.js +27 -46
- package/dist/cmaf-group-batcher.js.map +2 -2
- package/dist/cmcd-producer.js +45 -69
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/conceal-timeline.d.ts +123 -0
- package/dist/conceal-timeline.d.ts.map +1 -0
- package/dist/conceal-timeline.js +122 -0
- package/dist/conceal-timeline.js.map +7 -0
- package/dist/duplicate-ledger.d.ts +197 -0
- package/dist/duplicate-ledger.d.ts.map +1 -0
- package/dist/duplicate-ledger.js +226 -0
- package/dist/duplicate-ledger.js.map +7 -0
- package/dist/fec-manager.d.ts +157 -20
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +2157 -920
- package/dist/fec-manager.js.map +4 -4
- package/dist/fec-starvation-detector.js +25 -40
- package/dist/fec-starvation-detector.js.map +1 -1
- package/dist/fec-worker-setup.d.ts +8 -0
- package/dist/fec-worker-setup.d.ts.map +1 -1
- package/dist/fec-worker-setup.js +241 -230
- package/dist/fec-worker-setup.js.map +3 -3
- package/dist/index.d.ts +12 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -3
- package/dist/index.js.map +2 -2
- package/dist/iso-bmff-timing.d.ts +12 -6
- package/dist/iso-bmff-timing.d.ts.map +1 -1
- package/dist/iso-bmff-timing.js +79 -72
- package/dist/iso-bmff-timing.js.map +2 -2
- package/dist/loc.js +7 -4
- package/dist/loc.js.map +1 -1
- package/dist/manifest-auth-plan.js +293 -294
- package/dist/manifest-auth-plan.js.map +2 -2
- package/dist/media-router.d.ts +5 -101
- package/dist/media-router.d.ts.map +1 -1
- package/dist/media-router.js +282 -504
- package/dist/media-router.js.map +3 -3
- package/dist/mfu-reassembler.js +653 -607
- package/dist/mfu-reassembler.js.map +3 -3
- package/dist/mmtp-container-parser.d.ts +223 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +1770 -1305
- package/dist/mmtp-container-parser.js.map +3 -3
- package/dist/mmtp-container-parser.no-modules.js +900 -328
- package/dist/mmtp-container-parser.no-modules.js.map +3 -3
- package/dist/mmtp-decode-pipeline.d.ts +7 -1
- package/dist/mmtp-decode-pipeline.d.ts.map +1 -1
- package/dist/mmtp-decode-pipeline.js +1885 -1435
- package/dist/mmtp-decode-pipeline.js.map +3 -3
- package/dist/mmtp-router.d.ts +122 -12
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +4513 -3247
- package/dist/mmtp-router.js.map +4 -4
- package/dist/mmtp-to-cmaf.d.ts +18 -15
- package/dist/mmtp-to-cmaf.d.ts.map +1 -1
- package/dist/mmtp-to-cmaf.js +123 -199
- package/dist/mmtp-to-cmaf.js.map +2 -2
- package/dist/multicast-input.d.ts +7 -35
- package/dist/multicast-input.d.ts.map +1 -1
- package/dist/multicast-input.js +21 -2
- package/dist/multicast-input.js.map +4 -4
- package/dist/multicast-manager.d.ts +187 -20
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +8051 -4923
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +44 -1
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +560 -429
- package/dist/multicast-transport.js.map +4 -4
- package/dist/multicast-unwrap.js +53 -30
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +2358 -1865
- package/dist/no-modules.js.map +3 -3
- package/dist/repair-fetch.d.ts +172 -23
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +235 -85
- package/dist/repair-fetch.js.map +4 -4
- package/dist/sample-combiner.js +118 -139
- package/dist/sample-combiner.js.map +1 -1
- package/dist/signaling.js.map +2 -2
- package/dist/source-auth.js +355 -366
- package/dist/source-auth.js.map +2 -2
- package/dist/switching-set-alignment.js +14 -29
- package/dist/switching-set-alignment.js.map +1 -1
- package/dist/timestamp-codec.js +17 -11
- package/dist/timestamp-codec.js.map +2 -2
- package/dist/track-pipeline-setup.js +105 -125
- package/dist/track-pipeline-setup.js.map +1 -1
- package/dist/track-pipeline.js +107 -127
- package/dist/track-pipeline.js.map +1 -1
- package/dist/track-session.d.ts +106 -1
- package/dist/track-session.d.ts.map +1 -1
- package/dist/track-session.js +298 -214
- package/dist/track-session.js.map +2 -2
- package/dist/transport-pool.js +24 -38
- package/dist/transport-pool.js.map +1 -1
- package/dist/wasm-mfu-reassembler.d.ts +6 -1
- package/dist/wasm-mfu-reassembler.d.ts.map +1 -1
- package/dist/wasm-mfu-reassembler.js +183 -144
- package/dist/wasm-mfu-reassembler.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +1906 -1441
- package/dist/wasm-mmtp-container-parser.js.map +3 -3
- package/node_modules/@blockcast/fec-worker/README.md +10 -6
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +10 -6
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +90 -25
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts +53 -14
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-types.d.ts.map +1 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js +62 -44
- package/node_modules/@blockcast/fec-worker/dist/fec-worker.js.map +2 -2
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts +1 -1
- package/node_modules/@blockcast/fec-worker/dist/index.d.ts.map +1 -1
- package/node_modules/@blockcast/fec-worker/dist/index.js.map +1 -1
- package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +97 -26
- package/node_modules/@blockcast/fec-worker/src/fec-worker-types.ts +51 -10
- package/node_modules/@blockcast/fec-worker/src/fec-worker.ts +99 -67
- package/node_modules/@blockcast/fec-worker/src/index.ts +2 -0
- package/node_modules/@blockcast/mmt-base/README.md +1 -0
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +58 -5
- package/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -1
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js +147 -41
- package/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -1
- package/node_modules/@blockcast/mmt-base/src/mmtp.ts +164 -42
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts +3 -1
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.d.ts.map +1 -1
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js +3 -1
- package/node_modules/@blockcast/mmt-fec/dist/block-manager.js.map +1 -1
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.d.ts +2 -2
- package/node_modules/@blockcast/mmt-fec/dist/fec-block-mapper.js +2 -2
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts +5 -7
- package/node_modules/@blockcast/mmt-fec/dist/oti.d.ts.map +1 -1
- package/node_modules/@blockcast/mmt-fec/dist/oti.js +4 -6
- package/node_modules/@blockcast/mmt-fec/dist/oti.js.map +1 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/README.md +1 -0
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts +58 -5
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.d.ts.map +1 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js +147 -41
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/dist/mmtp.js.map +1 -1
- package/node_modules/@blockcast/mmt-fec/node_modules/@blockcast/mmt-base/src/mmtp.ts +164 -42
- package/node_modules/@blockcast/mmt-fec/src/block-manager.ts +3 -1
- package/node_modules/@blockcast/mmt-fec/src/fec-block-mapper.ts +2 -2
- package/node_modules/@blockcast/mmt-fec/src/oti.ts +5 -7
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts +10 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.d.ts.map +1 -1
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js +40 -10
- package/node_modules/@blockcast/mmt-manifest-verify/dist/verifier.js.map +1 -1
- package/node_modules/@blockcast/mmt-manifest-verify/src/verifier.ts +41 -8
- package/package.json +15 -6
- package/schemas/catalog.schema.json +6 -1
- package/src/align.ts +6 -2
- package/src/block-policy.ts +903 -0
- package/src/carp-catalog.ts +47 -32
- package/src/catalog-fec-params.ts +93 -11
- package/src/cmaf-block-assembler.ts +149 -231
- package/src/cmcd-producer.ts +5 -5
- package/src/conceal-timeline.ts +255 -0
- package/src/duplicate-ledger.ts +372 -0
- package/src/fec-manager.ts +1607 -304
- package/src/fec-worker-setup.ts +11 -1
- package/src/index.ts +62 -3
- package/src/iso-bmff-timing.ts +62 -7
- package/src/media-router.ts +22 -401
- package/src/mfu-reassembler.ts +95 -0
- package/src/mmtp-container-parser.ts +799 -67
- package/src/mmtp-decode-pipeline.ts +23 -6
- package/src/mmtp-router.ts +967 -184
- package/src/mmtp-to-cmaf.ts +46 -110
- package/src/multicast-input.ts +23 -3
- package/src/multicast-manager.ts +805 -132
- package/src/multicast-transport.ts +287 -36
- package/src/repair-fetch.ts +491 -153
- package/src/track-session.ts +152 -2
- package/src/wasm-mfu-reassembler.ts +40 -24
- package/test-vectors/catalog/README.md +2 -0
- package/test-vectors/catalog/negative/multicast-packet-id-signaling.json +26 -0
- package/test-vectors/catalog-validation.json +478 -23
- package/test-vectors/source-contiguity-v1.json +131 -0
package/README.md
CHANGED
|
@@ -65,6 +65,8 @@ The package exposes a wide subpath surface so consumers can tree-shake aggressiv
|
|
|
65
65
|
| `./loc` | LOC FEC extension headers (`FecSourceInfo`, `AuthTag`) |
|
|
66
66
|
| `./codec-utils` | H.264 helpers — Annex-B ↔ AVCC, SPS/PPS extraction, `mmtpTimestampToMicros`, ADTS wrap |
|
|
67
67
|
| `./fec-manager` | `FECManager` — per-track FEC decoder lifecycle (sync WASM + Worker variants) |
|
|
68
|
+
| `./block-policy` | Keyframe-protected repair policy — `BlockClassifier`, `repairPriorityForClass`, `decideGap` |
|
|
69
|
+
| `./conceal-timeline` | Sink concealment — `videoConcealModeForCodec`, `HoldLastTimeline`, `AudioGapConcealer` |
|
|
68
70
|
| `./mmtp-router` | `MmtpRouter` — packet routing with two-phase FEC feed |
|
|
69
71
|
| `./multicast-transport` | `MulticastTransport` — `window.multicast` gateway `subscribe({ consume: 'bytes' })` wrapper |
|
|
70
72
|
| `./multicast-manager` | `MulticastManager` — backwards-compat facade composing transport + router + FEC |
|
|
@@ -82,7 +84,7 @@ The package exposes a wide subpath surface so consumers can tree-shake aggressiv
|
|
|
82
84
|
| `./phase14-catalog-override` | Test-harness catalog override hook (`globalThis.__PHASE14_CATALOG_OVERRIDE`) |
|
|
83
85
|
| `./align` | Group-id derivation (`groupIdForTicks`, `groupIdFor`, `driftCheck`) — shared with Go sender + Rust relay |
|
|
84
86
|
| `./mmtp-decode-pipeline` | `MmtpDecodePipeline` — unified MoQ + multicast decode path |
|
|
85
|
-
| `./mmtp-to-cmaf` | `MmtpToCmaf` + standalone segment builders (`encodeMultiSampleSegment`, `buildVideoInitSegment`, `
|
|
87
|
+
| `./mmtp-to-cmaf` | `MmtpToCmaf` + standalone segment builders (`encodeMultiSampleSegment`, `buildVideoInitSegment`, `generateSilenceFrame`) |
|
|
86
88
|
| `./track-pipeline-setup` | `setupTrackPipeline` — shared wiring for both reference players |
|
|
87
89
|
| `./track-session` | `TrackSession` + `SessionManager` — per-track state machine (Patent Claim 9) |
|
|
88
90
|
|
|
@@ -263,6 +265,47 @@ class FECManager {
|
|
|
263
265
|
|
|
264
266
|
`interleaveDepthMs` is the full FEC block span in **milliseconds** per CARP §5.1, valid range 33 ms (interactive / gaming, `MIN_INTERLEAVE_DEPTH_MS`) to 10 000 ms (broadcast ATSC 3.0). `configure()` warns if a positive value below `MIN_INTERLEAVE_DEPTH_MS` (33) is passed — that is almost certainly a raw frame count, not milliseconds — and such sub-floor values never drive the block timeout (the established timeout is preserved instead of collapsing to ~0).
|
|
265
267
|
|
|
268
|
+
### Keyframe-protected repair (`./block-policy`, `./conceal-timeline`)
|
|
269
|
+
|
|
270
|
+
Every FEC block still gets in-band FEC decode. On top of that:
|
|
271
|
+
|
|
272
|
+
- **Classification.** Each block is `keyframe | delta | unknown | audio`, from
|
|
273
|
+
first-arrival network source evidence only (MMTP R bit, MPU number, MFU
|
|
274
|
+
DU-header sample position) against catalog geometry (framerate,
|
|
275
|
+
`interleaveDepthMs`, keyframe interval). Geometry that cannot be derived
|
|
276
|
+
exactly, or any violated invariant, runs the track `all-protected` (every
|
|
277
|
+
video block `unknown`). Vanished blocks are materialised from SBN evidence.
|
|
278
|
+
A frame split across ADJACENT blocks (a K-close) is expected; only source
|
|
279
|
+
packets whose authored order and SBN order disagree (a round-robin
|
|
280
|
+
interleave) trip `block-not-contiguous`. Pass the catalog track name to
|
|
281
|
+
`configureTrackFec(packetId, config, catalogTrackName)` so ABR rungs that
|
|
282
|
+
share a packetId each classify with their own framerate and GOP; a rung on
|
|
283
|
+
such a packetId without its own geometry runs `stream-geometry-absent`.
|
|
284
|
+
- **Repair priority.** `wireRepairFetch` dispatches P0 (`keyframe`/`unknown`)
|
|
285
|
+
and P1 (`audio`) immediately; P2 (`delta`) only while no P0/P1 is queued or
|
|
286
|
+
in flight, earliest deadline first, preempted by any new P0/P1. No timers.
|
|
287
|
+
- **FETCH priority (draft-ramadan-moq-fec §11.4.5).** The transport gets one
|
|
288
|
+
`RepairFetchRequest { repairTrack, sbn, packetId, signal,
|
|
289
|
+
subscriberPriority }` and puts `subscriberPriority` on the FETCH
|
|
290
|
+
(`undefined` = omit the parameter; the publisher then uses 128,
|
|
291
|
+
draft-ietf-moq-transport-16 §9.2.2.3). P0 requests carry
|
|
292
|
+
`min(sourcePriority, 191)`; P1/P2 carry `max(repairPriority, 192)`. Both
|
|
293
|
+
priorities come from `ResolveRepairTarget` (`catalogRepairTarget` reads
|
|
294
|
+
them from a catalog). A missing source priority omits the parameter; a
|
|
295
|
+
missing repair priority declines the delta/audio FETCH; each emits a
|
|
296
|
+
`repair-priority` lifecycle event. Abort MUST send FETCH_CANCEL, from the
|
|
297
|
+
abort handler.
|
|
298
|
+
- **Stall vs conceal.** FEC tracks release frames in authored order; a frame
|
|
299
|
+
after a gap waits until every block that could hold the missing frames
|
|
300
|
+
resolved. When media was lost, the frame carries `gapBefore`
|
|
301
|
+
(`decideGap`): video stalls only when a RAP may be inside the gap; audio
|
|
302
|
+
never stalls. The same object is the manager's `gap` lifecycle event.
|
|
303
|
+
- **Concealment** is hold-last-good-frame (`HoldLastTimeline`) plus exact
|
|
304
|
+
audio sizing (`AudioGapConcealer`). No bitstream is ever synthesised.
|
|
305
|
+
|
|
306
|
+
Typed evidence is `RepairLifecycleEvent` version 2 and
|
|
307
|
+
`MulticastManager.getBlockPolicyStats()`.
|
|
308
|
+
|
|
266
309
|
This package depends on the FEC primitives from:
|
|
267
310
|
- **`@blockcast/mmt-fec`** — RaptorQ WASM decoder + OTI codec + `computeFecTimeout`
|
|
268
311
|
- **`@blockcast/fec-worker`** — Web Worker host, `FecWorkerClient`, `FecTrackStats`
|
|
@@ -293,7 +336,6 @@ Standalone helpers exported from the same module:
|
|
|
293
336
|
| `encodeMultiSampleSegment(...)` | Multi-sample moof+mdat (one per FEC block — used by `CmafBlockAssembler`) |
|
|
294
337
|
| `buildVideoInitSegment(avcC, w, h)` | ftyp + moov for AVC |
|
|
295
338
|
| `buildAudioInitSegment(sampleRate, channels, asc?, timescale?)` | ftyp + moov for AAC / Opus |
|
|
296
|
-
| `generateGreenFillNAL(avcC, w, h)` | Green-fill NAL when a FEC block fails (loss visible) |
|
|
297
339
|
| `generateSilenceFrame(codec, sampleRate, channels)` | Audio silence frame for FEC-failed audio blocks |
|
|
298
340
|
|
|
299
341
|
Per-track timestamp rebase (`#videoBaseTs` / `#audioBaseTs` at `mmtp-to-cmaf.ts:550-562`) prevents the negative-uint64 `tfdt` bug that kills Chromium MediaSource (`CHUNK_DEMUXER_ERROR_APPEND_FAILED`).
|
|
@@ -322,7 +364,6 @@ Vendor keys emitted (BLO-1044):
|
|
|
322
364
|
| `x-bc-fec-algo` | FEC config | `none` / `raptorq` / `reed-solomon` |
|
|
323
365
|
| `x-bc-fec-k`, `x-bc-fec-p` | FEC config | source / repair counts |
|
|
324
366
|
| `x-bc-fec-recovery` | `recoveryRate` | Block-level recovery, 4 decimal places |
|
|
325
|
-
| `x-bc-scu` | green-fill count | Omitted when 0 |
|
|
326
367
|
| `x-bc-alta-verified`, `x-bc-alta-failures` | ALTA stats | Omitted when ALTA disabled (D-09) |
|
|
327
368
|
| `x-bc-transport` | `TransportSnapshot.transport` | `native` / `amt` / `moq` |
|
|
328
369
|
| `x-bc-multicast-group`, `x-bc-multicast-source`, `x-bc-amt-relay` | endpoint | Omitted when null |
|
package/dist/align.d.ts
CHANGED
|
@@ -101,8 +101,12 @@ export declare function groupIdForTicks(ticks: number, groupDurationTicks: numbe
|
|
|
101
101
|
* the quantized duration under-counts and `expected` slips a whole group.
|
|
102
102
|
* See the divergence fixture in `__tests__/align.test.ts`.
|
|
103
103
|
*
|
|
104
|
-
* No non-test caller remains in this package: `MediaRouter`
|
|
105
|
-
*
|
|
104
|
+
* No non-test caller remains in this package: `MediaRouter` took tick-space
|
|
105
|
+
* inputs from PCL-2211, and its group-alignment observer was then removed
|
|
106
|
+
* outright as unwired telemetry (BLO-34141). Removal of this wrapper is
|
|
107
|
+
* tracked by BLO-21176; the exported `groupIdForTicks` / `driftCheck` stay
|
|
108
|
+
* regardless — they are the formula shared with the Go sender and the Rust
|
|
109
|
+
* relay, not internals of the deleted observer.
|
|
106
110
|
*/
|
|
107
111
|
export declare function groupIdFor(mediaTimeSec: Seconds | number, groupDurationSec: number): number;
|
|
108
112
|
/**
|
package/dist/align.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"align.d.ts","sourceRoot":"","sources":["../src/align.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;CAAE,CAAA;AAE9D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAU1E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAK5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAajF;AAED
|
|
1
|
+
{"version":3,"file":"align.d.ts","sourceRoot":"","sources":["../src/align.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;CAAE,CAAA;AAE9D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAU1E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAK5C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CAajF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,UAAU,CACxB,YAAY,EAAE,OAAO,GAAG,MAAM,EAC9B,gBAAgB,EAAE,MAAM,GACvB,MAAM,CA+BR;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;CAAE,GACrD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAA;AAE9D;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAS7E;AAED,MAAM,MAAM,oBAAoB,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GACjC;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAA;IAC5C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3D;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAgBlE"}
|
package/dist/align.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/align.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Shared group-alignment primitive for MMT over MoQ switching sets.\n *\n * Pure math \u2014 no MMT wire parsing, no CMCD, no I/O. Callers wire the output\n * into their own drift-observation plumbing (see media-router.ts for the\n * libmmt wiring).\n *\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Contract (integer-ticks form, authoritative):\n *\n * groupId = ticks < 0 ? 0 : floor(ticks / groupDurationTicks)\n *\n * Where `ticks` is a media timestamp expressed at the catalog's `timescale`\n * (Hz), and `groupDurationTicks = groupDurationSec * timescale`.\n *\n * Integer math means there is no epsilon and no float rounding to reason\n * about. The seconds-form wrapper `groupIdFor(Seconds, seconds)` exists for\n * legacy callers but converts to ticks internally.\n *\n * Every sender/receiver that emits or consumes MoQ groups from an MMT or\n * CMAF switching set MUST derive groupId from this formula so that base,\n * delta, and repair tracks agree on the group boundary for a given media\n * timestamp across languages and processes.\n *\n * Test vectors that exercise the contract live at\n * `packages/container/test-vectors/align.json`; the Go and Rust siblings\n * load the same fixture so cross-language agreement is executable, not\n * prose. **Formula changes require updating the fixture AND all three\n * sibling implementations in the same release** \u2014 divergence between\n * sender/relay/receiver is worse than any formula change.\n *\n * Canonical implementations:\n * Go: Blockcast/multicast `cmd/caddy/sender/group_align.go`\n * (commit 104eeff) \u2014 sender-side groupId stamping on egress.\n * Rust: moqtail/moqtail `apps/relay/src/server/abr.rs`\n * (commit 39accbf; experimental branch, may be rebased \u2014 rely\n * on the SHA, not the branch name) \u2014 relay-side check.\n * TS: THIS FILE (libmmt) \u2014 consumed by moq-lib + moqtail-ts via\n * @blockcast/mmt-container.\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n */\n\n/**\n * Branded seconds type. Construct only via `secondsFromTicks(ticks, timescale)`\n * or `asSeconds(n)` (the latter exists solely for tests and bring-up). The\n * brand prevents accidentally passing a raw tick count \u2014 or a Date.now()\n * millisecond \u2014 to an API that expects a media-time in seconds.\n */\nexport type Seconds = number & { readonly __brand: 'Seconds' }\n\n/**\n * Explicit conversion constructor: ticks at `timescale` Hz \u2192 Seconds.\n *\n * Call this at the wire/catalog boundary so the brand flows with the value\n * through the rest of the system. Throws on non-finite inputs or\n * non-positive timescale (CLAUDE.md \u00A7Architectural Principles: error on\n * bad init).\n */\nexport function secondsFromTicks(ticks: number, timescale: number): Seconds {\n if (!Number.isFinite(ticks)) {\n throw new Error(`[align] secondsFromTicks: ticks must be finite, got ${ticks}`)\n }\n if (!Number.isFinite(timescale) || timescale <= 0) {\n throw new Error(\n `[align] secondsFromTicks: timescale must be positive and finite, got ${timescale}`,\n )\n }\n return (ticks / timescale) as Seconds\n}\n\n/**\n * Escape hatch for tests / bring-up code that holds a raw seconds number\n * (e.g. a catalog field that ships as float seconds). Production code\n * should prefer `secondsFromTicks` so the conversion site is explicit.\n */\nexport function asSeconds(n: number): Seconds {\n if (!Number.isFinite(n)) {\n throw new Error(`[align] asSeconds: value must be finite, got ${n}`)\n }\n return n as Seconds\n}\n\n/**\n * Deterministic groupId from integer ticks. Authoritative form of the\n * contract (see module docstring).\n *\n * @param ticks \u2014 media presentation time in ticks (integer).\n * Negative values clamp to group 0 (matches\n * the Go sibling's base-clamp).\n * @param groupDurationTicks \u2014 group duration in ticks (positive integer).\n * @returns non-negative integer groupId.\n *\n * Throws on non-integer/non-finite inputs or non-positive duration. These\n * are init-time errors, not packet-hot-path errors \u2014 callers ingesting\n * wire data should validate once at catalog load, not once per packet.\n */\nexport function groupIdForTicks(ticks: number, groupDurationTicks: number): number {\n if (!Number.isInteger(ticks)) {\n throw new Error(\n `[align] groupIdForTicks: ticks must be an integer, got ${ticks}`,\n )\n }\n if (!Number.isInteger(groupDurationTicks) || groupDurationTicks <= 0) {\n throw new Error(\n `[align] groupIdForTicks: groupDurationTicks must be a positive integer, got ${groupDurationTicks}`,\n )\n }\n if (ticks < 0) return 0\n return Math.floor(ticks / groupDurationTicks)\n}\n\n/**\n * Seconds-form wrapper around `groupIdForTicks`. Kept for backwards\n * compatibility with existing callers that hold `Seconds` (or a raw\n * `number` that semantically represents seconds).\n *\n * Internally converts to ticks using a synthetic 1e9 Hz timescale so the\n * integer-math contract is the single source of truth. The `| number`\n * branch of the parameter type is a migration ramp \u2014 new code should\n * pass `Seconds` explicitly (via `secondsFromTicks` or `asSeconds`).\n *\n * @param mediaTimeSec \u2014 finite number of seconds; negatives clamp to 0.\n * @param groupDurationSec \u2014 positive finite seconds.\n *\n * @deprecated Use `groupIdForTicks(ticks, groupDurationTicks)` with\n * tick-space inputs from the catalog. The seconds wrapper rounds to the\n * nearest nanosecond and throws above ~104 days of media time; the\n * integer-ticks form has no rounding and a much wider safe range.\n *\n * The rounding is not benign: when `groupDurationTicks / timescale` is not\n * ns-exact (33333@90k, 1001@30k, 1024@44.1k \u2014 all real catalog cadences)\n * the quantized duration under-counts and `expected` slips a whole group.\n * See the divergence fixture in `__tests__/align.test.ts`.\n *\n * No non-test caller remains in this package: `MediaRouter` now takes\n * `GroupBoundaryTicks` (PCL-2211). Removal is tracked by BLO-21176.\n */\nexport function groupIdFor(\n mediaTimeSec: Seconds | number,\n groupDurationSec: number,\n): number {\n if (!Number.isFinite(mediaTimeSec as number)) {\n throw new Error(\n `[align] groupIdFor: mediaTimeSec must be a finite number, got ${mediaTimeSec}`,\n )\n }\n if (!Number.isFinite(groupDurationSec) || groupDurationSec <= 0) {\n throw new Error(\n `[align] groupIdFor: groupDurationSec must be a positive finite number, got ${groupDurationSec}`,\n )\n }\n if ((mediaTimeSec as number) < 0) return 0\n // 1e9 Hz keeps both operands inside Number.MAX_SAFE_INTEGER (2^53-1)\n // for ~104 days of media time. Beyond that, Math.round returns a\n // non-safe-integer float that Number.isInteger still accepts \u2014 throw\n // explicitly so the caller picks a narrower timescale rather than\n // silently quantizing.\n const TS = 1_000_000_000\n const ticks = Math.round((mediaTimeSec as number) * TS)\n const durTicks = Math.round(groupDurationSec * TS)\n if (durTicks <= 0) {\n throw new Error(\n `[align] groupIdFor: groupDurationSec=${groupDurationSec} rounds to <1 tick at 1e9 Hz; pass groupIdForTicks directly for sub-nanosecond durations`,\n )\n }\n if (!Number.isSafeInteger(ticks)) {\n throw new Error(\n `[align] groupIdFor: mediaTimeSec=${mediaTimeSec} overflows safe-integer ticks at 1e9 Hz (>~104 days); use groupIdForTicks with a narrower timescale`,\n )\n }\n return groupIdForTicks(ticks, durTicks)\n}\n\n/**\n * Result of a drift check between expected and actual groupIds.\n *\n * Discriminated on `kind` so callers must handle both outcomes explicitly\n * (the previous `{ drifted: boolean, deltaGroups: number }` shape let a\n * drifted result slip through with a silent `if (result.drifted)` gate).\n */\nexport type DriftCheckResult =\n | { readonly kind: 'aligned'; readonly deltaGroups: 0 }\n | { readonly kind: 'drifted'; readonly deltaGroups: number }\n\n/**\n * Compare a packet-stamped groupId against the formula-derived expectation.\n *\n * Pure observer \u2014 does not throw on drift, does not mutate anything.\n * Callers decide how to react (e.g. bump a CMCD counter, log, ignore).\n *\n * @param expected \u2014 groupId as derived via `groupIdForTicks` or `groupIdFor`.\n * @param actual \u2014 groupId as stamped on the incoming packet/group.\n */\nexport function driftCheck(expected: number, actual: number): DriftCheckResult {\n if (!Number.isInteger(expected) || !Number.isInteger(actual)) {\n throw new Error(\n `[align] driftCheck: both arguments must be integer groupIds, got expected=${expected} actual=${actual}`,\n )\n }\n const deltaGroups = actual - expected\n if (deltaGroups === 0) return { kind: 'aligned', deltaGroups: 0 }\n return { kind: 'drifted', deltaGroups }\n}\n\nexport type MmtpSubgroupKeyInput =\n | { readonly kind: 'mpu-metadata' }\n | {\n readonly kind: 'timed'\n readonly movieFragmentSequenceNumber: number\n readonly sampleNumber: number\n }\n | { readonly kind: 'non-timed'; readonly itemId: number }\n\n/**\n * Canonical, domain-separated key for MMTP payloads sharing one MoQ subgroup.\n * This is a map/storage key, not the scalar MoQ subgroup ID. MPU metadata is\n * carried in subgroup 0; media identity remains explicit and collision-free.\n */\nexport function subgroupKeyFor(input: MmtpSubgroupKeyInput): string {\n if (input.kind === 'mpu-metadata') return 'metadata:0'\n\n const values =\n input.kind === 'timed'\n ? [input.movieFragmentSequenceNumber, input.sampleNumber]\n : [input.itemId]\n for (const value of values) {\n if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {\n throw new Error(`[align] subgroup key coordinates must be uint32, got ${value}`)\n }\n }\n\n return input.kind === 'timed'\n ? `timed:${input.movieFragmentSequenceNumber}:${input.sampleNumber}`\n : `non-timed:${input.itemId}`\n}\n"],
|
|
5
|
-
"mappings": ";AA0DO,SAAS,iBAAiB,OAAe,WAA4B;AAC1E,MAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,UAAM,IAAI,MAAM,uDAAuD,KAAK,EAAE;AAAA,EAChF;AACA,MAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,GAAG;AACjD,UAAM,IAAI;AAAA,MACR,wEAAwE,SAAS;AAAA,IACnF;AAAA,EACF;AACA,SAAQ,QAAQ;AAClB;AAOO,SAAS,UAAU,GAAoB;AAC5C,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,UAAM,IAAI,MAAM,gDAAgD,CAAC,EAAE;AAAA,EACrE;AACA,SAAO;AACT;AAgBO,SAAS,gBAAgB,OAAe,oBAAoC;AACjF,MAAI,CAAC,OAAO,UAAU,KAAK,GAAG;AAC5B,UAAM,IAAI;AAAA,MACR,0DAA0D,KAAK;AAAA,IACjE;AAAA,EACF;AACA,MAAI,CAAC,OAAO,UAAU,kBAAkB,KAAK,sBAAsB,GAAG;AACpE,UAAM,IAAI;AAAA,MACR,+EAA+E,kBAAkB;AAAA,IACnG;AAAA,EACF;AACA,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,KAAK,MAAM,QAAQ,kBAAkB;AAC9C;
|
|
4
|
+
"sourcesContent": ["/**\n * Shared group-alignment primitive for MMT over MoQ switching sets.\n *\n * Pure math \u2014 no MMT wire parsing, no CMCD, no I/O. Callers wire the output\n * into their own drift-observation plumbing (see media-router.ts for the\n * libmmt wiring).\n *\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n * Contract (integer-ticks form, authoritative):\n *\n * groupId = ticks < 0 ? 0 : floor(ticks / groupDurationTicks)\n *\n * Where `ticks` is a media timestamp expressed at the catalog's `timescale`\n * (Hz), and `groupDurationTicks = groupDurationSec * timescale`.\n *\n * Integer math means there is no epsilon and no float rounding to reason\n * about. The seconds-form wrapper `groupIdFor(Seconds, seconds)` exists for\n * legacy callers but converts to ticks internally.\n *\n * Every sender/receiver that emits or consumes MoQ groups from an MMT or\n * CMAF switching set MUST derive groupId from this formula so that base,\n * delta, and repair tracks agree on the group boundary for a given media\n * timestamp across languages and processes.\n *\n * Test vectors that exercise the contract live at\n * `packages/container/test-vectors/align.json`; the Go and Rust siblings\n * load the same fixture so cross-language agreement is executable, not\n * prose. **Formula changes require updating the fixture AND all three\n * sibling implementations in the same release** \u2014 divergence between\n * sender/relay/receiver is worse than any formula change.\n *\n * Canonical implementations:\n * Go: Blockcast/multicast `cmd/caddy/sender/group_align.go`\n * (commit 104eeff) \u2014 sender-side groupId stamping on egress.\n * Rust: moqtail/moqtail `apps/relay/src/server/abr.rs`\n * (commit 39accbf; experimental branch, may be rebased \u2014 rely\n * on the SHA, not the branch name) \u2014 relay-side check.\n * TS: THIS FILE (libmmt) \u2014 consumed by moq-lib + moqtail-ts via\n * @blockcast/mmt-container.\n * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n */\n\n/**\n * Branded seconds type. Construct only via `secondsFromTicks(ticks, timescale)`\n * or `asSeconds(n)` (the latter exists solely for tests and bring-up). The\n * brand prevents accidentally passing a raw tick count \u2014 or a Date.now()\n * millisecond \u2014 to an API that expects a media-time in seconds.\n */\nexport type Seconds = number & { readonly __brand: 'Seconds' }\n\n/**\n * Explicit conversion constructor: ticks at `timescale` Hz \u2192 Seconds.\n *\n * Call this at the wire/catalog boundary so the brand flows with the value\n * through the rest of the system. Throws on non-finite inputs or\n * non-positive timescale (CLAUDE.md \u00A7Architectural Principles: error on\n * bad init).\n */\nexport function secondsFromTicks(ticks: number, timescale: number): Seconds {\n if (!Number.isFinite(ticks)) {\n throw new Error(`[align] secondsFromTicks: ticks must be finite, got ${ticks}`)\n }\n if (!Number.isFinite(timescale) || timescale <= 0) {\n throw new Error(\n `[align] secondsFromTicks: timescale must be positive and finite, got ${timescale}`,\n )\n }\n return (ticks / timescale) as Seconds\n}\n\n/**\n * Escape hatch for tests / bring-up code that holds a raw seconds number\n * (e.g. a catalog field that ships as float seconds). Production code\n * should prefer `secondsFromTicks` so the conversion site is explicit.\n */\nexport function asSeconds(n: number): Seconds {\n if (!Number.isFinite(n)) {\n throw new Error(`[align] asSeconds: value must be finite, got ${n}`)\n }\n return n as Seconds\n}\n\n/**\n * Deterministic groupId from integer ticks. Authoritative form of the\n * contract (see module docstring).\n *\n * @param ticks \u2014 media presentation time in ticks (integer).\n * Negative values clamp to group 0 (matches\n * the Go sibling's base-clamp).\n * @param groupDurationTicks \u2014 group duration in ticks (positive integer).\n * @returns non-negative integer groupId.\n *\n * Throws on non-integer/non-finite inputs or non-positive duration. These\n * are init-time errors, not packet-hot-path errors \u2014 callers ingesting\n * wire data should validate once at catalog load, not once per packet.\n */\nexport function groupIdForTicks(ticks: number, groupDurationTicks: number): number {\n if (!Number.isInteger(ticks)) {\n throw new Error(\n `[align] groupIdForTicks: ticks must be an integer, got ${ticks}`,\n )\n }\n if (!Number.isInteger(groupDurationTicks) || groupDurationTicks <= 0) {\n throw new Error(\n `[align] groupIdForTicks: groupDurationTicks must be a positive integer, got ${groupDurationTicks}`,\n )\n }\n if (ticks < 0) return 0\n return Math.floor(ticks / groupDurationTicks)\n}\n\n/**\n * Seconds-form wrapper around `groupIdForTicks`. Kept for backwards\n * compatibility with existing callers that hold `Seconds` (or a raw\n * `number` that semantically represents seconds).\n *\n * Internally converts to ticks using a synthetic 1e9 Hz timescale so the\n * integer-math contract is the single source of truth. The `| number`\n * branch of the parameter type is a migration ramp \u2014 new code should\n * pass `Seconds` explicitly (via `secondsFromTicks` or `asSeconds`).\n *\n * @param mediaTimeSec \u2014 finite number of seconds; negatives clamp to 0.\n * @param groupDurationSec \u2014 positive finite seconds.\n *\n * @deprecated Use `groupIdForTicks(ticks, groupDurationTicks)` with\n * tick-space inputs from the catalog. The seconds wrapper rounds to the\n * nearest nanosecond and throws above ~104 days of media time; the\n * integer-ticks form has no rounding and a much wider safe range.\n *\n * The rounding is not benign: when `groupDurationTicks / timescale` is not\n * ns-exact (33333@90k, 1001@30k, 1024@44.1k \u2014 all real catalog cadences)\n * the quantized duration under-counts and `expected` slips a whole group.\n * See the divergence fixture in `__tests__/align.test.ts`.\n *\n * No non-test caller remains in this package: `MediaRouter` took tick-space\n * inputs from PCL-2211, and its group-alignment observer was then removed\n * outright as unwired telemetry (BLO-34141). Removal of this wrapper is\n * tracked by BLO-21176; the exported `groupIdForTicks` / `driftCheck` stay\n * regardless \u2014 they are the formula shared with the Go sender and the Rust\n * relay, not internals of the deleted observer.\n */\nexport function groupIdFor(\n mediaTimeSec: Seconds | number,\n groupDurationSec: number,\n): number {\n if (!Number.isFinite(mediaTimeSec as number)) {\n throw new Error(\n `[align] groupIdFor: mediaTimeSec must be a finite number, got ${mediaTimeSec}`,\n )\n }\n if (!Number.isFinite(groupDurationSec) || groupDurationSec <= 0) {\n throw new Error(\n `[align] groupIdFor: groupDurationSec must be a positive finite number, got ${groupDurationSec}`,\n )\n }\n if ((mediaTimeSec as number) < 0) return 0\n // 1e9 Hz keeps both operands inside Number.MAX_SAFE_INTEGER (2^53-1)\n // for ~104 days of media time. Beyond that, Math.round returns a\n // non-safe-integer float that Number.isInteger still accepts \u2014 throw\n // explicitly so the caller picks a narrower timescale rather than\n // silently quantizing.\n const TS = 1_000_000_000\n const ticks = Math.round((mediaTimeSec as number) * TS)\n const durTicks = Math.round(groupDurationSec * TS)\n if (durTicks <= 0) {\n throw new Error(\n `[align] groupIdFor: groupDurationSec=${groupDurationSec} rounds to <1 tick at 1e9 Hz; pass groupIdForTicks directly for sub-nanosecond durations`,\n )\n }\n if (!Number.isSafeInteger(ticks)) {\n throw new Error(\n `[align] groupIdFor: mediaTimeSec=${mediaTimeSec} overflows safe-integer ticks at 1e9 Hz (>~104 days); use groupIdForTicks with a narrower timescale`,\n )\n }\n return groupIdForTicks(ticks, durTicks)\n}\n\n/**\n * Result of a drift check between expected and actual groupIds.\n *\n * Discriminated on `kind` so callers must handle both outcomes explicitly\n * (the previous `{ drifted: boolean, deltaGroups: number }` shape let a\n * drifted result slip through with a silent `if (result.drifted)` gate).\n */\nexport type DriftCheckResult =\n | { readonly kind: 'aligned'; readonly deltaGroups: 0 }\n | { readonly kind: 'drifted'; readonly deltaGroups: number }\n\n/**\n * Compare a packet-stamped groupId against the formula-derived expectation.\n *\n * Pure observer \u2014 does not throw on drift, does not mutate anything.\n * Callers decide how to react (e.g. bump a CMCD counter, log, ignore).\n *\n * @param expected \u2014 groupId as derived via `groupIdForTicks` or `groupIdFor`.\n * @param actual \u2014 groupId as stamped on the incoming packet/group.\n */\nexport function driftCheck(expected: number, actual: number): DriftCheckResult {\n if (!Number.isInteger(expected) || !Number.isInteger(actual)) {\n throw new Error(\n `[align] driftCheck: both arguments must be integer groupIds, got expected=${expected} actual=${actual}`,\n )\n }\n const deltaGroups = actual - expected\n if (deltaGroups === 0) return { kind: 'aligned', deltaGroups: 0 }\n return { kind: 'drifted', deltaGroups }\n}\n\nexport type MmtpSubgroupKeyInput =\n | { readonly kind: 'mpu-metadata' }\n | {\n readonly kind: 'timed'\n readonly movieFragmentSequenceNumber: number\n readonly sampleNumber: number\n }\n | { readonly kind: 'non-timed'; readonly itemId: number }\n\n/**\n * Canonical, domain-separated key for MMTP payloads sharing one MoQ subgroup.\n * This is a map/storage key, not the scalar MoQ subgroup ID. MPU metadata is\n * carried in subgroup 0; media identity remains explicit and collision-free.\n */\nexport function subgroupKeyFor(input: MmtpSubgroupKeyInput): string {\n if (input.kind === 'mpu-metadata') return 'metadata:0'\n\n const values =\n input.kind === 'timed'\n ? [input.movieFragmentSequenceNumber, input.sampleNumber]\n : [input.itemId]\n for (const value of values) {\n if (!Number.isInteger(value) || value < 0 || value > 0xffff_ffff) {\n throw new Error(`[align] subgroup key coordinates must be uint32, got ${value}`)\n }\n }\n\n return input.kind === 'timed'\n ? `timed:${input.movieFragmentSequenceNumber}:${input.sampleNumber}`\n : `non-timed:${input.itemId}`\n}\n"],
|
|
5
|
+
"mappings": ";AA0DO,SAAS,iBAAiB,OAAe,WAA4B;AAC1E,MAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,UAAM,IAAI,MAAM,uDAAuD,KAAK,EAAE;AAAA,EAChF;AACA,MAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,GAAG;AACjD,UAAM,IAAI;AAAA,MACR,wEAAwE,SAAS;AAAA,IACnF;AAAA,EACF;AACA,SAAQ,QAAQ;AAClB;AAOO,SAAS,UAAU,GAAoB;AAC5C,MAAI,CAAC,OAAO,SAAS,CAAC,GAAG;AACvB,UAAM,IAAI,MAAM,gDAAgD,CAAC,EAAE;AAAA,EACrE;AACA,SAAO;AACT;AAgBO,SAAS,gBAAgB,OAAe,oBAAoC;AACjF,MAAI,CAAC,OAAO,UAAU,KAAK,GAAG;AAC5B,UAAM,IAAI;AAAA,MACR,0DAA0D,KAAK;AAAA,IACjE;AAAA,EACF;AACA,MAAI,CAAC,OAAO,UAAU,kBAAkB,KAAK,sBAAsB,GAAG;AACpE,UAAM,IAAI;AAAA,MACR,+EAA+E,kBAAkB;AAAA,IACnG;AAAA,EACF;AACA,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO,KAAK,MAAM,QAAQ,kBAAkB;AAC9C;AAgCO,SAAS,WACd,cACA,kBACQ;AACR,MAAI,CAAC,OAAO,SAAS,YAAsB,GAAG;AAC5C,UAAM,IAAI;AAAA,MACR,iEAAiE,YAAY;AAAA,IAC/E;AAAA,EACF;AACA,MAAI,CAAC,OAAO,SAAS,gBAAgB,KAAK,oBAAoB,GAAG;AAC/D,UAAM,IAAI;AAAA,MACR,8EAA8E,gBAAgB;AAAA,IAChG;AAAA,EACF;AACA,MAAK,eAA0B,EAAG,QAAO;AAMzC,QAAM,KAAK;AACX,QAAM,QAAQ,KAAK,MAAO,eAA0B,EAAE;AACtD,QAAM,WAAW,KAAK,MAAM,mBAAmB,EAAE;AACjD,MAAI,YAAY,GAAG;AACjB,UAAM,IAAI;AAAA,MACR,wCAAwC,gBAAgB;AAAA,IAC1D;AAAA,EACF;AACA,MAAI,CAAC,OAAO,cAAc,KAAK,GAAG;AAChC,UAAM,IAAI;AAAA,MACR,oCAAoC,YAAY;AAAA,IAClD;AAAA,EACF;AACA,SAAO,gBAAgB,OAAO,QAAQ;AACxC;AAsBO,SAAS,WAAW,UAAkB,QAAkC;AAC7E,MAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,CAAC,OAAO,UAAU,MAAM,GAAG;AAC5D,UAAM,IAAI;AAAA,MACR,6EAA6E,QAAQ,WAAW,MAAM;AAAA,IACxG;AAAA,EACF;AACA,QAAM,cAAc,SAAS;AAC7B,MAAI,gBAAgB,EAAG,QAAO,EAAE,MAAM,WAAW,aAAa,EAAE;AAChE,SAAO,EAAE,MAAM,WAAW,YAAY;AACxC;AAgBO,SAAS,eAAe,OAAqC;AAClE,MAAI,MAAM,SAAS,eAAgB,QAAO;AAE1C,QAAM,SACJ,MAAM,SAAS,UACX,CAAC,MAAM,6BAA6B,MAAM,YAAY,IACtD,CAAC,MAAM,MAAM;AACnB,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,KAAK,QAAQ,YAAa;AAChE,YAAM,IAAI,MAAM,wDAAwD,KAAK,EAAE;AAAA,IACjF;AAAA,EACF;AAEA,SAAO,MAAM,SAAS,UAClB,SAAS,MAAM,2BAA2B,IAAI,MAAM,YAAY,KAChE,aAAa,MAAM,MAAM;AAC/B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,36 +1,27 @@
|
|
|
1
|
-
var __typeError = (msg) => {
|
|
2
|
-
throw TypeError(msg);
|
|
3
|
-
};
|
|
4
|
-
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
5
|
-
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
6
|
-
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
7
|
-
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
8
|
-
|
|
9
1
|
// src/audio-ring-buffer.ts
|
|
10
|
-
var _buffer, _writeIndex, _readIndex, _stalled;
|
|
11
2
|
var AudioRingBuffer = class {
|
|
3
|
+
#buffer;
|
|
4
|
+
#writeIndex = 0;
|
|
5
|
+
#readIndex = 0;
|
|
6
|
+
#stalled;
|
|
12
7
|
constructor(config) {
|
|
13
|
-
__privateAdd(this, _buffer);
|
|
14
|
-
__privateAdd(this, _writeIndex, 0);
|
|
15
|
-
__privateAdd(this, _readIndex, 0);
|
|
16
|
-
__privateAdd(this, _stalled);
|
|
17
8
|
this.channels = config.channels;
|
|
18
9
|
this.rate = config.rate;
|
|
19
|
-
|
|
10
|
+
this.#stalled = !(config.startUnstalled ?? false);
|
|
20
11
|
const samples = Math.ceil(config.rate * (config.latencyMs / 1e3));
|
|
21
|
-
|
|
12
|
+
this.#buffer = [];
|
|
22
13
|
for (let i = 0; i < config.channels; i++) {
|
|
23
|
-
|
|
14
|
+
this.#buffer[i] = new Float32Array(samples);
|
|
24
15
|
}
|
|
25
16
|
}
|
|
26
17
|
get stalled() {
|
|
27
|
-
return
|
|
18
|
+
return this.#stalled;
|
|
28
19
|
}
|
|
29
20
|
get capacity() {
|
|
30
|
-
return
|
|
21
|
+
return this.#buffer[0].length;
|
|
31
22
|
}
|
|
32
23
|
get length() {
|
|
33
|
-
return
|
|
24
|
+
return this.#writeIndex - this.#readIndex;
|
|
34
25
|
}
|
|
35
26
|
/**
|
|
36
27
|
* Append decoded audio samples to the FIFO.
|
|
@@ -39,24 +30,24 @@ var AudioRingBuffer = class {
|
|
|
39
30
|
*/
|
|
40
31
|
write(_timestampUs, data) {
|
|
41
32
|
const samples = data[0].length;
|
|
42
|
-
const start =
|
|
33
|
+
const start = this.#writeIndex;
|
|
43
34
|
const end = start + samples;
|
|
44
|
-
const overflow = end -
|
|
35
|
+
const overflow = end - this.#readIndex - this.#buffer[0].length;
|
|
45
36
|
if (overflow >= 0) {
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
this.#stalled = false;
|
|
38
|
+
this.#readIndex += overflow;
|
|
48
39
|
}
|
|
49
|
-
if (
|
|
50
|
-
|
|
40
|
+
if (this.#stalled && end - this.#readIndex >= Math.floor(this.#buffer[0].length / 2)) {
|
|
41
|
+
this.#stalled = false;
|
|
51
42
|
}
|
|
52
43
|
for (let ch = 0; ch < this.channels; ch++) {
|
|
53
44
|
const src = data[ch];
|
|
54
|
-
const dst =
|
|
45
|
+
const dst = this.#buffer[ch];
|
|
55
46
|
for (let i = 0; i < samples; i++) {
|
|
56
47
|
dst[(start + i) % dst.length] = src[i];
|
|
57
48
|
}
|
|
58
49
|
}
|
|
59
|
-
|
|
50
|
+
this.#writeIndex = end;
|
|
60
51
|
}
|
|
61
52
|
/**
|
|
62
53
|
* Read samples into output buffers — plain FIFO, no clock recovery.
|
|
@@ -64,29 +55,25 @@ var AudioRingBuffer = class {
|
|
|
64
55
|
* Underrun → zero-fill.
|
|
65
56
|
*/
|
|
66
57
|
read(output) {
|
|
67
|
-
if (
|
|
68
|
-
const available =
|
|
58
|
+
if (this.#stalled) return 0;
|
|
59
|
+
const available = this.#writeIndex - this.#readIndex;
|
|
69
60
|
if (available === 0) return 0;
|
|
70
61
|
const requested = output[0].length;
|
|
71
62
|
const readSamples = Math.min(available, requested);
|
|
72
63
|
for (let ch = 0; ch < this.channels; ch++) {
|
|
73
64
|
const dst = output[ch];
|
|
74
|
-
const src =
|
|
65
|
+
const src = this.#buffer[ch];
|
|
75
66
|
for (let i = 0; i < readSamples; i++) {
|
|
76
|
-
dst[i] = src[(
|
|
67
|
+
dst[i] = src[(this.#readIndex + i) % src.length];
|
|
77
68
|
}
|
|
78
69
|
for (let i = readSamples; i < requested; i++) {
|
|
79
70
|
dst[i] = 0;
|
|
80
71
|
}
|
|
81
72
|
}
|
|
82
|
-
|
|
73
|
+
this.#readIndex += readSamples;
|
|
83
74
|
return readSamples;
|
|
84
75
|
}
|
|
85
76
|
};
|
|
86
|
-
_buffer = new WeakMap();
|
|
87
|
-
_writeIndex = new WeakMap();
|
|
88
|
-
_readIndex = new WeakMap();
|
|
89
|
-
_stalled = new WeakMap();
|
|
90
77
|
export {
|
|
91
78
|
AudioRingBuffer
|
|
92
79
|
};
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/audio-ring-buffer.ts"],
|
|
4
4
|
"sourcesContent": ["/**\n * AudioRingBuffer \u2014 plain FIFO audio buffer (no clock recovery).\n *\n * Source of truth for the audio buffer algorithm used in both moq-lib and\n * moqtail AudioWorklet processors. AudioWorklet isolation requires the\n * worklet copy to be self-contained (no imports), so changes here must be\n * manually synced to:\n * - moqtail-private/apps/client-js/src/lib/audio-worklet-processor.js\n * - hang-mmt-fec/js/watch/src/audio/ring-buffer.ts (uses @moq/lite Time types)\n *\n * Rationale: FEC guarantees gap-free, ordered audio on multicast; MoQ\n * delivers ordered audio without drops. The decoder output rate matches\n * the AudioContext destination rate closely enough that skip/dup clock\n * recovery is unnecessary and harmful \u2014 a single sample skip at 48 kHz is\n * a ~21 \u00B5s phase glitch, and repeated skip/dup produces audible pitch\n * wobble on pure tones (measured: 16 Hz offset + \u00B111 Hz spread on a 1 kHz\n * test tone before this refactor).\n */\n\nexport interface AudioRingBufferConfig {\n /** Number of audio channels */\n channels: number;\n /** Sample rate in Hz */\n rate: number;\n /** Buffer latency in milliseconds */\n latencyMs: number;\n /** Start in unstalled state (for source switching) */\n startUnstalled?: boolean;\n}\n\nexport class AudioRingBuffer {\n readonly channels: number;\n readonly rate: number;\n\n #buffer: Float32Array[];\n #writeIndex = 0;\n #readIndex = 0;\n #stalled: boolean;\n\n constructor(config: AudioRingBufferConfig) {\n this.channels = config.channels;\n this.rate = config.rate;\n this.#stalled = !(config.startUnstalled ?? false);\n\n const samples = Math.ceil(config.rate * (config.latencyMs / 1000));\n this.#buffer = [];\n for (let i = 0; i < config.channels; i++) {\n this.#buffer[i] = new Float32Array(samples);\n }\n }\n\n get stalled(): boolean { return this.#stalled; }\n get capacity(): number { return this.#buffer[0].length; }\n get length(): number { return this.#writeIndex - this.#readIndex; }\n\n /**\n * Append decoded audio samples to the FIFO.\n * Timestamp parameter retained for API compatibility but is ignored \u2014\n * FIFO is sequence-based, not time-based.\n */\n write(_timestampUs: number, data: Float32Array[]): void {\n const samples = data[0].length;\n\n const start = this.#writeIndex;\n const end = start + samples;\n\n // Overflow: discard oldest samples (protects against encoder/AudioContext\n // clock drift when one runs faster than the other).\n const overflow = end - this.#readIndex - this.#buffer[0].length;\n if (overflow >= 0) {\n this.#stalled = false;\n this.#readIndex += overflow;\n }\n\n // Unstall at half capacity \u2014 enough prebuffer for bounded jitter.\n if (this.#stalled && (end - this.#readIndex) >= Math.floor(this.#buffer[0].length / 2)) {\n this.#stalled = false;\n }\n\n // Append samples sequentially\n for (let ch = 0; ch < this.channels; ch++) {\n const src = data[ch];\n const dst = this.#buffer[ch];\n for (let i = 0; i < samples; i++) {\n dst[(start + i) % dst.length] = src[i];\n }\n }\n this.#writeIndex = end;\n }\n\n /**\n * Read samples into output buffers \u2014 plain FIFO, no clock recovery.\n * FEC + MoQ delivery guarantees no gaps, so skip/dup is unnecessary.\n * Underrun \u2192 zero-fill.\n */\n read(output: Float32Array[]): number {\n if (this.#stalled) return 0;\n\n const available = this.#writeIndex - this.#readIndex;\n if (available === 0) return 0;\n\n const requested = output[0].length;\n const readSamples = Math.min(available, requested);\n\n for (let ch = 0; ch < this.channels; ch++) {\n const dst = output[ch];\n const src = this.#buffer[ch];\n for (let i = 0; i < readSamples; i++) {\n dst[i] = src[(this.#readIndex + i) % src.length];\n }\n // Zero-fill remainder on underrun\n for (let i = readSamples; i < requested; i++) {\n dst[i] = 0;\n }\n }\n\n this.#readIndex += readSamples;\n return readSamples;\n }\n}\n"],
|
|
5
|
-
"mappings": "
|
|
5
|
+
"mappings": ";AA8BO,IAAM,kBAAN,MAAsB;AAAA,EAI3B;AAAA,EACA,cAAc;AAAA,EACd,aAAa;AAAA,EACb;AAAA,EAEA,YAAY,QAA+B;AACzC,SAAK,WAAW,OAAO;AACvB,SAAK,OAAO,OAAO;AACnB,SAAK,WAAW,EAAE,OAAO,kBAAkB;AAE3C,UAAM,UAAU,KAAK,KAAK,OAAO,QAAQ,OAAO,YAAY,IAAK;AACjE,SAAK,UAAU,CAAC;AAChB,aAAS,IAAI,GAAG,IAAI,OAAO,UAAU,KAAK;AACxC,WAAK,QAAQ,CAAC,IAAI,IAAI,aAAa,OAAO;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,IAAI,UAAmB;AAAE,WAAO,KAAK;AAAA,EAAU;AAAA,EAC/C,IAAI,WAAmB;AAAE,WAAO,KAAK,QAAQ,CAAC,EAAE;AAAA,EAAQ;AAAA,EACxD,IAAI,SAAiB;AAAE,WAAO,KAAK,cAAc,KAAK;AAAA,EAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOlE,MAAM,cAAsB,MAA4B;AACtD,UAAM,UAAU,KAAK,CAAC,EAAE;AAExB,UAAM,QAAQ,KAAK;AACnB,UAAM,MAAM,QAAQ;AAIpB,UAAM,WAAW,MAAM,KAAK,aAAa,KAAK,QAAQ,CAAC,EAAE;AACzD,QAAI,YAAY,GAAG;AACjB,WAAK,WAAW;AAChB,WAAK,cAAc;AAAA,IACrB;AAGA,QAAI,KAAK,YAAa,MAAM,KAAK,cAAe,KAAK,MAAM,KAAK,QAAQ,CAAC,EAAE,SAAS,CAAC,GAAG;AACtF,WAAK,WAAW;AAAA,IAClB;AAGA,aAAS,KAAK,GAAG,KAAK,KAAK,UAAU,MAAM;AACzC,YAAM,MAAM,KAAK,EAAE;AACnB,YAAM,MAAM,KAAK,QAAQ,EAAE;AAC3B,eAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,aAAK,QAAQ,KAAK,IAAI,MAAM,IAAI,IAAI,CAAC;AAAA,MACvC;AAAA,IACF;AACA,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAK,QAAgC;AACnC,QAAI,KAAK,SAAU,QAAO;AAE1B,UAAM,YAAY,KAAK,cAAc,KAAK;AAC1C,QAAI,cAAc,EAAG,QAAO;AAE5B,UAAM,YAAY,OAAO,CAAC,EAAE;AAC5B,UAAM,cAAc,KAAK,IAAI,WAAW,SAAS;AAEjD,aAAS,KAAK,GAAG,KAAK,KAAK,UAAU,MAAM;AACzC,YAAM,MAAM,OAAO,EAAE;AACrB,YAAM,MAAM,KAAK,QAAQ,EAAE;AAC3B,eAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,YAAI,CAAC,IAAI,KAAK,KAAK,aAAa,KAAK,IAAI,MAAM;AAAA,MACjD;AAEA,eAAS,IAAI,aAAa,IAAI,WAAW,KAAK;AAC5C,YAAI,CAAC,IAAI;AAAA,MACX;AAAA,IACF;AAEA,SAAK,cAAc;AACnB,WAAO;AAAA,EACT;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyframe-protected FEC repair policy.
|
|
3
|
+
*
|
|
4
|
+
* Every FEC block still gets in-band FEC decode. What this module decides is
|
|
5
|
+
* (1) which blocks may hold a keyframe (RAP), so a block short of K at its
|
|
6
|
+
* repair deadline can be repaired over QUIC with the right priority, and
|
|
7
|
+
* (2) what a sink must do with the first frame released after a gap: stall
|
|
8
|
+
* until the next RAP, or conceal and keep playing.
|
|
9
|
+
*
|
|
10
|
+
* The two decisions are deliberately independent. Classification only sets
|
|
11
|
+
* repair priority. The stall/conceal decision is made again per released gap
|
|
12
|
+
* from the media that is actually missing, so a wrong classification can only
|
|
13
|
+
* lower a block's repair priority; it can never make playback wrong.
|
|
14
|
+
*
|
|
15
|
+
* Everything here is derived from catalog-authored timing and in-band MMTP
|
|
16
|
+
* evidence. There are no timers, no cadence heuristics, and no defaults: a
|
|
17
|
+
* track whose geometry cannot be derived exactly runs in the defined
|
|
18
|
+
* fail-closed mode `all-protected`, where every block is treated as possibly
|
|
19
|
+
* keyframe-bearing.
|
|
20
|
+
*
|
|
21
|
+
* All three players (moqtail, hang, Shaka) consume this one implementation.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Repair class of one FEC block.
|
|
25
|
+
*
|
|
26
|
+
* - `keyframe`: the block holds (or held) a RAP / MPU start.
|
|
27
|
+
* - `delta`: the block provably holds only non-RAP video frames.
|
|
28
|
+
* - `unknown`: neither could be proven; treated exactly like `keyframe`.
|
|
29
|
+
* - `audio`: an audio block.
|
|
30
|
+
*/
|
|
31
|
+
export type BlockClass = 'keyframe' | 'delta' | 'unknown' | 'audio';
|
|
32
|
+
/** Repair priority. Lower is more urgent. */
|
|
33
|
+
export type RepairPriority = 0 | 1 | 2;
|
|
34
|
+
export type TrackKind = 'video' | 'audio';
|
|
35
|
+
/** P0 = keyframe/unknown (stall-preventing), P1 = audio, P2 = delta. */
|
|
36
|
+
export declare function repairPriorityForClass(blockClass: BlockClass): RepairPriority;
|
|
37
|
+
export type ClassifierMode = 'classifying' | 'all-protected';
|
|
38
|
+
/** Why a track runs `all-protected`. */
|
|
39
|
+
export type ClassifierReason = 'framerate-absent' | 'keyframe-cadence-absent' | 'keyframe-interval-not-integral' | 'block-spans-gop'
|
|
40
|
+
/**
|
|
41
|
+
* The catalog keyframe interval is not an exact integer number of ticks
|
|
42
|
+
* in the media track's own timescale (ISO-BMFF `mdhd`), so released RAP
|
|
43
|
+
* spacing cannot be verified and hold-last cannot predict the next RAP.
|
|
44
|
+
* Nothing is wrong with the stream; the cadence is simply unprovable.
|
|
45
|
+
*/
|
|
46
|
+
| 'keyframe-interval-not-media-integral'
|
|
47
|
+
/**
|
|
48
|
+
* Another stream on this packetId was configured with its own geometry
|
|
49
|
+
* and this one was not, so no geometry provably belongs to it (ABR rungs
|
|
50
|
+
* sharing a packetId).
|
|
51
|
+
*/
|
|
52
|
+
| 'stream-geometry-absent' | 'rap-not-mpu-start' | 'mpu-longer-than-gop' | 'block-not-contiguous' | 'rap-spacing-mismatch' | 'mpu-multiple-fragments' | 'block-wider-than-interleave';
|
|
53
|
+
/** Raw catalog timing for one track. Never a resolved cadence. */
|
|
54
|
+
export interface BlockClassifierGeometryInput {
|
|
55
|
+
readonly trackKind: TrackKind;
|
|
56
|
+
/** Catalog `framerate` (frames per second). */
|
|
57
|
+
readonly framerate?: number;
|
|
58
|
+
/** Catalog `fec.interleaveDepthMs`: the full FEC block span (§5.1). */
|
|
59
|
+
readonly interleaveDepthMs: number;
|
|
60
|
+
/** Catalog track `timescale` for `keyframeIntervalTicks`. */
|
|
61
|
+
readonly timescale?: number;
|
|
62
|
+
/** Catalog `keyframeIntervalTicks` (preferred when the timescale is present). */
|
|
63
|
+
readonly keyframeIntervalTicks?: number;
|
|
64
|
+
/** Catalog `keyframeIntervalMs`. */
|
|
65
|
+
readonly keyframeIntervalMs?: number;
|
|
66
|
+
}
|
|
67
|
+
/** Exact keyframe interval as a rational number of seconds: ticks / timescale. */
|
|
68
|
+
export interface KeyframeInterval {
|
|
69
|
+
readonly ticks: number;
|
|
70
|
+
readonly timescale: number;
|
|
71
|
+
}
|
|
72
|
+
export type BlockClassifierGeometry = {
|
|
73
|
+
readonly trackKind: 'audio';
|
|
74
|
+
} | {
|
|
75
|
+
readonly trackKind: 'video';
|
|
76
|
+
readonly mode: 'classifying';
|
|
77
|
+
/** F: one frame's duration in milliseconds (1000 / framerate). */
|
|
78
|
+
readonly frameDurationMs: number;
|
|
79
|
+
/** D: media units per FEC block (deriveFecInterleaveDepth). */
|
|
80
|
+
readonly interleaveUnits: number;
|
|
81
|
+
/** G: frames per GOP. */
|
|
82
|
+
readonly gopFrames: number;
|
|
83
|
+
readonly keyframeInterval: KeyframeInterval;
|
|
84
|
+
} | {
|
|
85
|
+
readonly trackKind: 'video';
|
|
86
|
+
readonly mode: 'all-protected';
|
|
87
|
+
readonly reason: ClassifierReason;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Derive the per-track classifier geometry from raw catalog fields.
|
|
91
|
+
*
|
|
92
|
+
* `all-protected` is the policy's defined fail-closed state, not a default:
|
|
93
|
+
* it is returned with the exact reason. The only throw is for a value the
|
|
94
|
+
* catalog validator already rejects (a non-positive block span).
|
|
95
|
+
*/
|
|
96
|
+
export declare function deriveBlockClassifierGeometry(input: BlockClassifierGeometryInput): BlockClassifierGeometry;
|
|
97
|
+
/**
|
|
98
|
+
* The keyframe interval expressed in a media track's timescale, or null when
|
|
99
|
+
* the geometry is not `classifying` or the interval is not an exact integer
|
|
100
|
+
* number of media ticks.
|
|
101
|
+
*/
|
|
102
|
+
export declare function keyframeIntervalMediaTicks(geometry: BlockClassifierGeometry, mediaTimescale: number): bigint | null;
|
|
103
|
+
/**
|
|
104
|
+
* In-band evidence from ONE first-arrival source symbol received from the
|
|
105
|
+
* network. Recovered symbols and repair symbols never contribute, and the
|
|
106
|
+
* MFU `priority > 0x80` hint is not evidence.
|
|
107
|
+
*/
|
|
108
|
+
export interface SourceSymbolEvidence {
|
|
109
|
+
/** The MMTP header R (RAP) bit. */
|
|
110
|
+
readonly rap: boolean;
|
|
111
|
+
/** MPU_sequence_number, when parsed from this packet. */
|
|
112
|
+
readonly mpu?: number;
|
|
113
|
+
/**
|
|
114
|
+
* 0-based sample position within the MPU: the MFU DU-header
|
|
115
|
+
* `sample_number` minus one (ISO/IEC 23008-1 numbers samples from 1, and
|
|
116
|
+
* the parser rejects `sample_number` 0). Present only when this packet
|
|
117
|
+
* carried a DU header.
|
|
118
|
+
*/
|
|
119
|
+
readonly idx?: number;
|
|
120
|
+
/** movie_fragment_sequence_number from the DU header, when present. */
|
|
121
|
+
readonly movieFragment?: number;
|
|
122
|
+
/**
|
|
123
|
+
* DU-header `offset`: the data unit's authored byte position within its
|
|
124
|
+
* sample (0 for a whole-sample data unit, the partition boundary for a
|
|
125
|
+
* subsample data unit). Present only with `idx`; absent reads as 0.
|
|
126
|
+
*/
|
|
127
|
+
readonly offset?: number;
|
|
128
|
+
/**
|
|
129
|
+
* The MMTP header packet_sequence_number (contiguous per packet_id). Not
|
|
130
|
+
* classification evidence: FECManager uses it to bound how many blocks the
|
|
131
|
+
* producer can have started between two source symbols, so an outage is
|
|
132
|
+
* never mistaken for a source restart.
|
|
133
|
+
*/
|
|
134
|
+
readonly packetSequenceNumber?: number;
|
|
135
|
+
}
|
|
136
|
+
export type ReclassificationEvidence = 'rap' | 'mpu-span' | 'window' | 'classifier-disabled';
|
|
137
|
+
export type ClassifierChange = {
|
|
138
|
+
readonly kind: 'reclassified';
|
|
139
|
+
readonly sbn: number;
|
|
140
|
+
readonly from: BlockClass;
|
|
141
|
+
readonly to: BlockClass;
|
|
142
|
+
readonly evidence: ReclassificationEvidence;
|
|
143
|
+
} | {
|
|
144
|
+
readonly kind: 'mode';
|
|
145
|
+
readonly mode: 'all-protected';
|
|
146
|
+
readonly reason: ClassifierReason;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Per-stream block classifier.
|
|
150
|
+
*
|
|
151
|
+
* Classes only move toward more protection or narrower evidence:
|
|
152
|
+
* `unknown→delta` and `unknown→keyframe` / `delta→keyframe` on evidence, and
|
|
153
|
+
* `delta→unknown` when an invariant disables classification. `keyframe` is
|
|
154
|
+
* terminal.
|
|
155
|
+
*
|
|
156
|
+
* The delta window rule rests on three premises, each guarded by an
|
|
157
|
+
* invariant that fires on positive evidence only:
|
|
158
|
+
*
|
|
159
|
+
* - P1: the source packets of one block are one contiguous run of the
|
|
160
|
+
* stream's authored order, and blocks follow each other in SBN order
|
|
161
|
+
* (`block-not-contiguous`). The producer assigns SS_IDs in authored order
|
|
162
|
+
* and a block is the SS_ID range [SBN*K, SBN*K + K), so this holds for a
|
|
163
|
+
* block that closes at K symbols in the middle of a frame: that frame's
|
|
164
|
+
* fragments then land in two ADJACENT blocks, which is still one run each.
|
|
165
|
+
* A frame split across adjacent blocks is not a violation.
|
|
166
|
+
* - P2: that run touches at most D media units, counting a frame continued
|
|
167
|
+
* from the previous block (`block-wider-than-interleave`). The producer
|
|
168
|
+
* counts a media-unit boundary only once the open block holds a symbol.
|
|
169
|
+
* - P3: an MPU starts with its only RAP and holds at most G frames
|
|
170
|
+
* (`rap-not-mpu-start`, `mpu-longer-than-gop`, `rap-spacing-mismatch`), in
|
|
171
|
+
* one movie fragment (`mpu-multiple-fragments`).
|
|
172
|
+
*
|
|
173
|
+
* P1 and P2 are what make a block's frames contiguous and lie within
|
|
174
|
+
* [idxMax − D + 1, idxMin + D − 1] of its MPU. What refutes P1 is a pair of
|
|
175
|
+
* source packets whose wire-stated authored order and SBN order disagree:
|
|
176
|
+
* a later MPU in an earlier block, a later data unit of one MPU (by
|
|
177
|
+
* movie_fragment_sequence_number, sample_number, then DU offset) in an
|
|
178
|
+
* earlier block, or one data unit in two blocks. That is the signature of a
|
|
179
|
+
* round-robin interleave, which scatters one run over several concurrent
|
|
180
|
+
* blocks. Non-adjacency alone is not: a sample larger than K packets
|
|
181
|
+
* legitimately spans blocks SBN..SBN+n.
|
|
182
|
+
*/
|
|
183
|
+
export declare class BlockClassifier {
|
|
184
|
+
#private;
|
|
185
|
+
constructor(geometry: BlockClassifierGeometry);
|
|
186
|
+
get geometry(): BlockClassifierGeometry;
|
|
187
|
+
get mode(): ClassifierMode;
|
|
188
|
+
get reason(): ClassifierReason | null;
|
|
189
|
+
/**
|
|
190
|
+
* Whether "same MPU on both sides of a gap" still proves the gap held no
|
|
191
|
+
* RAP. It stops holding for the rest of the epoch once a RAP was received
|
|
192
|
+
* inside an MPU or an MPU ran longer than a GOP.
|
|
193
|
+
*/
|
|
194
|
+
get mpuBoundsRaps(): boolean;
|
|
195
|
+
/** Begin a fresh epoch (catalog change or track reset). */
|
|
196
|
+
reset(geometry?: BlockClassifierGeometry): void;
|
|
197
|
+
/**
|
|
198
|
+
* Retire this classifier (its stream moves to a new catalog epoch). Every
|
|
199
|
+
* block it had classified `delta` loses the evidence behind that class and
|
|
200
|
+
* reads `unknown` from now on; the returned changes say so.
|
|
201
|
+
*/
|
|
202
|
+
retire(): ClassifierChange[];
|
|
203
|
+
/**
|
|
204
|
+
* The stream's SBN sequence restarted (FECManager source discontinuity:
|
|
205
|
+
* a publisher restart, or the SS_ID space wrapped). Authored order and SBN
|
|
206
|
+
* order are comparable only within one continuous sequence, so the P1
|
|
207
|
+
* ledger starts again.
|
|
208
|
+
*/
|
|
209
|
+
noteSourceDiscontinuity(): void;
|
|
210
|
+
/** Register a block that exists without evidence (a vanished block). */
|
|
211
|
+
touch(sbn: number): void;
|
|
212
|
+
/** Forget a resolved block. */
|
|
213
|
+
forget(sbn: number): void;
|
|
214
|
+
classOf(sbn: number): BlockClass;
|
|
215
|
+
/** Record one first-arrival source symbol's evidence for block `sbn`. */
|
|
216
|
+
observe(sbn: number, evidence: SourceSymbolEvidence): ClassifierChange[];
|
|
217
|
+
/**
|
|
218
|
+
* Record a RAP frame released in order. Released MPU-start RAPs of MPUs m
|
|
219
|
+
* and m+n must be exactly n keyframe intervals apart in the media
|
|
220
|
+
* timescale, compared as integers.
|
|
221
|
+
*
|
|
222
|
+
* Comparing only consecutive MPUs would miss exactly the case the window
|
|
223
|
+
* rule cannot survive: a short MPU whose successor's RAP was lost (that
|
|
224
|
+
* RAP-bearing block is the one a wrong G calls delta), because the next
|
|
225
|
+
* released RAP then belongs to m+2. Every MPU is one interval long, so the
|
|
226
|
+
* span to any later released RAP proves or refutes all MPUs in between.
|
|
227
|
+
*/
|
|
228
|
+
noteReleasedRap(mpu: number, presentationTime: bigint, mediaTimescale: number): ClassifierChange[];
|
|
229
|
+
}
|
|
230
|
+
export interface GapStreamIdentity {
|
|
231
|
+
readonly packetId: number;
|
|
232
|
+
/** Non-empty catalog identity, or null when the producer has no track name. */
|
|
233
|
+
readonly catalogTrackName: string | null;
|
|
234
|
+
}
|
|
235
|
+
/** What the sink knows about one side of a released gap. */
|
|
236
|
+
export interface GapFrameEvidence {
|
|
237
|
+
/**
|
|
238
|
+
* Codec-aware RAP check: `MmtpContainerParser.isKeyframe(data, rapFlag,
|
|
239
|
+
* codec)`, bitstream-only for recovered frames. Never the priority hint.
|
|
240
|
+
*/
|
|
241
|
+
readonly rap: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* MPU_sequence_number of the frame. Omit it once the stream showed that
|
|
244
|
+
* an MPU boundary does not bound its RAPs (`BlockClassifier.mpuBoundsRaps`):
|
|
245
|
+
* same-MPU then no longer proves a gap lost only deltas.
|
|
246
|
+
*/
|
|
247
|
+
readonly mpu?: number;
|
|
248
|
+
/** 0-based sample position within the MPU, when the geometry verifies it. */
|
|
249
|
+
readonly idx?: number;
|
|
250
|
+
/** Authored presentation time in `timescale` ticks. */
|
|
251
|
+
readonly pts?: bigint;
|
|
252
|
+
/** Authored sample duration in `timescale` ticks. */
|
|
253
|
+
readonly durationTicks?: number;
|
|
254
|
+
/** The media timescale of `pts` and `durationTicks`. */
|
|
255
|
+
readonly timescale?: number;
|
|
256
|
+
}
|
|
257
|
+
export type FrameGapAction = 'stall' | 'conceal';
|
|
258
|
+
export type FrameGapReason = 'rap-lost' | 'rap-unknown' | 'discontinuity' | 'delta-lost' | 'rap-next' | 'audio-gap' | 'audio-discontinuity';
|
|
259
|
+
/**
|
|
260
|
+
* The decision attached to the first frame released after a gap
|
|
261
|
+
* (`RoutedFrameMeta.gapBefore`). It is the same frozen object the manager
|
|
262
|
+
* emits as its `gap` repair lifecycle event, so a sink that acts on the frame
|
|
263
|
+
* and an observer that counts events see one fact.
|
|
264
|
+
*/
|
|
265
|
+
export interface FrameGapDecision {
|
|
266
|
+
readonly version: 2;
|
|
267
|
+
readonly type: 'gap';
|
|
268
|
+
readonly stream: GapStreamIdentity;
|
|
269
|
+
readonly trackKind: TrackKind;
|
|
270
|
+
readonly action: FrameGapAction;
|
|
271
|
+
readonly reason: FrameGapReason;
|
|
272
|
+
/** FEC blocks in the gap whose media was lost (failed or vanished). */
|
|
273
|
+
readonly blocks: readonly number[];
|
|
274
|
+
/** Missing video frames or audio access units, when exactly known. */
|
|
275
|
+
readonly missingFrames: number | null;
|
|
276
|
+
/** Authored duration of the missing media in milliseconds, when known. */
|
|
277
|
+
readonly missingMs: number | null;
|
|
278
|
+
/** Manager epoch of the stream when the decision was made. */
|
|
279
|
+
readonly epoch: number;
|
|
280
|
+
}
|
|
281
|
+
export interface DecideGapInput {
|
|
282
|
+
readonly stream: GapStreamIdentity;
|
|
283
|
+
readonly trackKind: TrackKind;
|
|
284
|
+
/** Last frame released before the gap. */
|
|
285
|
+
readonly before: GapFrameEvidence;
|
|
286
|
+
/** First frame released after the gap. */
|
|
287
|
+
readonly after: GapFrameEvidence;
|
|
288
|
+
/** The stream restarted (FEC SBN went backwards or the publisher reset). */
|
|
289
|
+
readonly discontinuity: boolean;
|
|
290
|
+
readonly blocks: readonly number[];
|
|
291
|
+
/**
|
|
292
|
+
* The most media units the gap can hold: the FEC blocks spanned from the
|
|
293
|
+
* frame before to the frame after, times D (media units per block,
|
|
294
|
+
* ceil(interleaveDepthMs / unit duration), §5.1). A block touches at most D
|
|
295
|
+
* units whether it closes at D or at K symbols. Null when the span or D is
|
|
296
|
+
* unknown. An audio PTS delta that implies more AUs than this is a clock
|
|
297
|
+
* jump, not loss, and re-anchors instead of concealing every slot.
|
|
298
|
+
*/
|
|
299
|
+
readonly maxMissingUnits: number | null;
|
|
300
|
+
readonly epoch: number;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Pure stall/conceal decision for the first frame released after a gap.
|
|
304
|
+
*
|
|
305
|
+
* Video stalls only when a RAP may be inside the gap: the frame after it is
|
|
306
|
+
* not a RAP and either sits in another MPU (`rap-lost`) or the MPU cannot be
|
|
307
|
+
* proven on both sides (`rap-unknown`), or the stream restarted
|
|
308
|
+
* (`discontinuity`). A gap inside one MPU lost only deltas (`delta-lost`) and
|
|
309
|
+
* a RAP after the gap resynchronises by itself (`rap-next`). Audio never
|
|
310
|
+
* stalls: an exact gap is concealed per access unit (`audio-gap`), anything
|
|
311
|
+
* else re-anchors at the next frame (`audio-discontinuity`).
|
|
312
|
+
*/
|
|
313
|
+
export declare function decideGap(input: DecideGapInput): FrameGapDecision;
|
|
314
|
+
//# sourceMappingURL=block-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-policy.d.ts","sourceRoot":"","sources":["../src/block-policy.ts"],"names":[],"mappings":"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"}
|