@blockcast/mmt-container 2.2.0-main.c4fa878f4ff4 → 2.2.0-main.da2e7cd1dd5d
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 +29 -17
- package/dist/browser-runtime.js +241 -149
- package/dist/browser-runtime.js.map +3 -3
- package/dist/catalog-fec-params.d.ts +17 -1
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +15 -0
- package/dist/catalog-fec-params.js.map +2 -2
- package/dist/cmcd-producer.d.ts +60 -47
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js +138 -109
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/delivery-catalog.js +24 -15
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +3 -1
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +26 -4
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-worker-setup.d.ts +1 -1
- package/dist/fec-worker-setup.js +1 -1
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +2 -2
- package/dist/media-router.js.map +1 -1
- package/dist/mmtp-container-parser.d.ts +2 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +4 -0
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +4 -0
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +4 -0
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +38 -15
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.js +64 -19
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js +24 -15
- package/dist/multicast-transport.js.map +2 -2
- package/dist/no-modules.js +4 -0
- package/dist/no-modules.js.map +2 -2
- package/dist/repair-layer-subscriptions.d.ts +6 -2
- package/dist/repair-layer-subscriptions.d.ts.map +1 -1
- package/dist/repair-layer-subscriptions.js +6 -3
- package/dist/repair-layer-subscriptions.js.map +2 -2
- package/dist/track-session.d.ts +1 -1
- package/dist/track-session.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js +4 -0
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +8 -5
- package/schemas/catalog.schema.json +4 -0
- package/src/catalog-fec-params.ts +36 -1
- package/src/cmcd-producer.ts +189 -179
- package/src/fec-manager.ts +26 -4
- package/src/fec-worker-setup.ts +2 -2
- package/src/index.ts +6 -2
- package/src/mmtp-container-parser.ts +12 -0
- package/src/mmtp-router.ts +21 -0
- package/src/repair-layer-subscriptions.ts +13 -5
- package/src/track-session.ts +1 -1
package/src/fec-manager.ts
CHANGED
|
@@ -644,6 +644,10 @@ export class FECManager {
|
|
|
644
644
|
// ── Keyframe-protected repair policy (block-policy.ts) ──
|
|
645
645
|
// One classifier per physical stream, built from that stream's geometry.
|
|
646
646
|
#classifiers = new Map<string, BlockClassifier>()
|
|
647
|
+
// A stream without catalog timing has no classifier, so the last RAP it
|
|
648
|
+
// released stands in for one: "an MPU boundary bounds its RAPs" holds until
|
|
649
|
+
// a second RAP is released inside one MPU (BLO-52085).
|
|
650
|
+
#noSpanRaps = new Map<string, { mpu: number; presentationTime: bigint; inside: boolean }>()
|
|
647
651
|
// Geometry a stream-scoped configuration gave one stream (streamKey). ABR
|
|
648
652
|
// rungs can share a packetId with a different framerate or GOP, so the
|
|
649
653
|
// packet-level geometry is only the geometry of a packet whose streams
|
|
@@ -1706,6 +1710,7 @@ export class FECManager {
|
|
|
1706
1710
|
// The classifier compares authored order with SBN order only within
|
|
1707
1711
|
// one continuous SBN sequence.
|
|
1708
1712
|
this.#classifiers.get(streamKey)?.noteSourceDiscontinuity()
|
|
1713
|
+
this.#noSpanRaps.delete(streamKey)
|
|
1709
1714
|
this.#highestSbn.set(streamKey, { sbn, atMs: now })
|
|
1710
1715
|
this.#sourceFrontiers.set(streamKey, { sbn, atMs: now })
|
|
1711
1716
|
if (psn === undefined) this.#sourceAnchors.delete(streamKey)
|
|
@@ -2289,6 +2294,9 @@ export class FECManager {
|
|
|
2289
2294
|
// sibling's change, but the first stream-scoped geometry on a packet
|
|
2290
2295
|
// does change the siblings that have none (stream-geometry-absent).
|
|
2291
2296
|
this.#bumpEpoch(packetId)
|
|
2297
|
+
for (const key of [...this.#noSpanRaps.keys()]) {
|
|
2298
|
+
if (this.#belongsToPacket(key, packetId)) this.#noSpanRaps.delete(key)
|
|
2299
|
+
}
|
|
2292
2300
|
for (const [key, classifier] of [...this.#classifiers]) {
|
|
2293
2301
|
if (!this.#belongsToPacket(key, packetId)) continue
|
|
2294
2302
|
const stream = this.#parseStreamKey(key)
|
|
@@ -2565,6 +2573,7 @@ export class FECManager {
|
|
|
2565
2573
|
resetMap(this.#isoLocators)
|
|
2566
2574
|
resetMap(this.#sourceAnchors)
|
|
2567
2575
|
this.#classifiers.delete(this.#streamKey(packetId, catalogTrackName))
|
|
2576
|
+
this.#noSpanRaps.delete(this.#streamKey(packetId, catalogTrackName))
|
|
2568
2577
|
this.#bumpEpoch(packetId)
|
|
2569
2578
|
for (const [sbn, keys] of this.#blockKeysBySbn) {
|
|
2570
2579
|
for (const key of keys.keys()) {
|
|
@@ -2679,6 +2688,7 @@ export class FECManager {
|
|
|
2679
2688
|
dropMap(this.#isoLocators)
|
|
2680
2689
|
dropMap(this.#sourceAnchors)
|
|
2681
2690
|
dropMap(this.#classifiers)
|
|
2691
|
+
dropMap(this.#noSpanRaps)
|
|
2682
2692
|
dropMap(this.#streamGeometries)
|
|
2683
2693
|
dropSet(this.#pendingWorkerCleanups)
|
|
2684
2694
|
dropSet(this.#completedBlocks)
|
|
@@ -3470,11 +3480,14 @@ export class FECManager {
|
|
|
3470
3480
|
/**
|
|
3471
3481
|
* Whether an MPU boundary still bounds this stream's RAPs (see
|
|
3472
3482
|
* `BlockClassifier.mpuBoundsRaps`). Gap decisions drop MPU evidence once
|
|
3473
|
-
* it does not. A stream with no classifier yet has shown nothing against it
|
|
3483
|
+
* it does not. A stream with no classifier yet has shown nothing against it,
|
|
3484
|
+
* and neither has a stream without catalog timing until it releases two
|
|
3485
|
+
* RAPs inside one MPU.
|
|
3474
3486
|
*/
|
|
3475
3487
|
mpuBoundsRapsFor(packetId: number, catalogTrackName?: string): boolean {
|
|
3476
|
-
|
|
3477
|
-
return this.#
|
|
3488
|
+
const streamKey = this.#streamKey(packetId, catalogTrackName)
|
|
3489
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) return this.#noSpanRaps.get(streamKey)?.inside !== true
|
|
3490
|
+
return this.#classifiers.get(streamKey)?.mpuBoundsRaps ?? true
|
|
3478
3491
|
}
|
|
3479
3492
|
|
|
3480
3493
|
/** A block's class now, or frozen at its resolution. */
|
|
@@ -3510,7 +3523,15 @@ export class FECManager {
|
|
|
3510
3523
|
presentationTime: bigint,
|
|
3511
3524
|
mediaTimescale: number,
|
|
3512
3525
|
): void {
|
|
3513
|
-
if (!this.#trackConfigs.has(packetId)
|
|
3526
|
+
if (!this.#trackConfigs.has(packetId)) return
|
|
3527
|
+
if (!this.#hasCatalogTimingForPacket(packetId)) {
|
|
3528
|
+
const streamKey = this.#streamKey(packetId, catalogTrackName)
|
|
3529
|
+
const last = this.#noSpanRaps.get(streamKey)
|
|
3530
|
+
const inside = last?.inside === true ||
|
|
3531
|
+
(last?.mpu === mpu && last.presentationTime !== presentationTime)
|
|
3532
|
+
this.#noSpanRaps.set(streamKey, { mpu, presentationTime, inside })
|
|
3533
|
+
return
|
|
3534
|
+
}
|
|
3514
3535
|
this.#applyClassifierChanges(
|
|
3515
3536
|
packetId,
|
|
3516
3537
|
catalogTrackName,
|
|
@@ -4883,6 +4904,7 @@ export class FECManager {
|
|
|
4883
4904
|
this.#blockAnchorsMs.clear()
|
|
4884
4905
|
this.#blockCatalogTrackNames.clear()
|
|
4885
4906
|
this.#classifiers.clear()
|
|
4907
|
+
this.#noSpanRaps.clear()
|
|
4886
4908
|
this.#streamGeometries.clear()
|
|
4887
4909
|
this.#failedBlocks.clear()
|
|
4888
4910
|
this.#resolvedBlockClasses.clear()
|
package/src/fec-worker-setup.ts
CHANGED
|
@@ -64,7 +64,7 @@ export interface FecWorkerSetupOptions {
|
|
|
64
64
|
/**
|
|
65
65
|
* Repair symbols per block this receiver actually joined: the sum of
|
|
66
66
|
* `repairSymbols` over the joined BASE layers (draft-ramadan-moq-fec 6.3).
|
|
67
|
-
* Reported as P by the worker snapshot and CMCD `
|
|
67
|
+
* Reported as P by the worker snapshot and CMCD `com.blockcast-fp`, because the
|
|
68
68
|
* catalog total is not what this receiver joined. Falls back to
|
|
69
69
|
* `fecRepairCount`, which is the same value on a single-layer track.
|
|
70
70
|
*/
|
|
@@ -252,7 +252,7 @@ export async function setupFecWorkerForTrack(
|
|
|
252
252
|
blockProfile,
|
|
253
253
|
k: opts.fecK,
|
|
254
254
|
// P as this receiver joined it, not the catalog total: the worker snapshot
|
|
255
|
-
// and CMCD `
|
|
255
|
+
// and CMCD `com.blockcast-fp` are read from this. Identity on a single layer.
|
|
256
256
|
repairCount: opts.joinedRepairCount ?? opts.fecRepairCount,
|
|
257
257
|
symbolSize: opts.fecSymbolSize,
|
|
258
258
|
interleaveDepth: interleaveDepthBlocks,
|
package/src/index.ts
CHANGED
|
@@ -526,10 +526,13 @@ export {
|
|
|
526
526
|
type FecStarvationDetectorConfig,
|
|
527
527
|
} from './fec-starvation-detector.js'
|
|
528
528
|
|
|
529
|
-
// CMCD
|
|
529
|
+
// CMCD v2 Event-mode producer (CTA-5004-B, com.blockcast-* custom keys)
|
|
530
530
|
export {
|
|
531
531
|
CmcdProducer,
|
|
532
|
-
|
|
532
|
+
encodeCmcdRecord,
|
|
533
|
+
type CmcdRecord,
|
|
534
|
+
type CmcdToken,
|
|
535
|
+
type CmcdValue,
|
|
533
536
|
type CmcdProducerOptions,
|
|
534
537
|
type SnapshotData,
|
|
535
538
|
type TransportSnapshot,
|
|
@@ -546,6 +549,7 @@ export {
|
|
|
546
549
|
type TrackFecParams,
|
|
547
550
|
type TrackFecUnavailable,
|
|
548
551
|
catalogTrackFecUnavailable,
|
|
552
|
+
catalogTrackDeliveryMode,
|
|
549
553
|
catalogGroupDurationMs,
|
|
550
554
|
catalogKeyframeCadenceMs,
|
|
551
555
|
catalogFecMediaUnitsPerSecond,
|
|
@@ -2146,6 +2146,18 @@ export class MmtpContainerParser {
|
|
|
2146
2146
|
this.#emitFrame(data, meta)
|
|
2147
2147
|
}
|
|
2148
2148
|
|
|
2149
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
2150
|
+
hasAuthoredTimingFor(
|
|
2151
|
+
packetId: number,
|
|
2152
|
+
mpuSequenceNumber: number,
|
|
2153
|
+
movieFragmentSequenceNumber: number,
|
|
2154
|
+
sampleNumber: number,
|
|
2155
|
+
catalogTrackName?: string,
|
|
2156
|
+
): boolean {
|
|
2157
|
+
return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)
|
|
2158
|
+
?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2149
2161
|
/**
|
|
2150
2162
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
2151
2163
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
package/src/mmtp-router.ts
CHANGED
|
@@ -5206,6 +5206,27 @@ export class MmtpRouter {
|
|
|
5206
5206
|
);
|
|
5207
5207
|
}
|
|
5208
5208
|
|
|
5209
|
+
// A recovered head whose sample FT=1 has not named yet (an overlay that
|
|
5210
|
+
// outran the source path) would complete its data unit untimed: the unit
|
|
5211
|
+
// is dropped, marked completed, and the timed source copy is then refused
|
|
5212
|
+
// (BLO-53342). Leave it out; its recovered continuations park and complete
|
|
5213
|
+
// with the source head. A head that is truly lost ends as it did before.
|
|
5214
|
+
result = {
|
|
5215
|
+
...result,
|
|
5216
|
+
symbols: result.symbols.filter((sym) =>
|
|
5217
|
+
sym.fragmentType !== 2 ||
|
|
5218
|
+
sym.fragment.fragmentationIndicator !== 1 ||
|
|
5219
|
+
sym.fragment.movieFragmentSequenceNumber === undefined ||
|
|
5220
|
+
sym.fragment.sampleNumber === undefined ||
|
|
5221
|
+
this.#parser.hasAuthoredTimingFor(
|
|
5222
|
+
ownerPacketId,
|
|
5223
|
+
sym.fragment.mpuSequenceNumber,
|
|
5224
|
+
sym.fragment.movieFragmentSequenceNumber,
|
|
5225
|
+
sym.fragment.sampleNumber,
|
|
5226
|
+
catalogTrackName,
|
|
5227
|
+
)),
|
|
5228
|
+
};
|
|
5229
|
+
|
|
5209
5230
|
// MSE assembler path: route recovered block as pre-assembled frames (skip FT=0)
|
|
5210
5231
|
// BLO-3035 layer 3: recovered symbols are RAW MFU fragments, not complete
|
|
5211
5232
|
// frames. A keyframe can span multiple packets (FI=1/2/3); pushing each
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
* base (`MmtpRouter.feedRepairPacket(..., repairTrackName)`).
|
|
16
16
|
*
|
|
17
17
|
* `stream` delivery means QUIC retransmits: no repair track is ever
|
|
18
|
-
* subscribed (FEC only for unreliable transports).
|
|
18
|
+
* subscribed (FEC only for unreliable transports). The mode is the source's
|
|
19
|
+
* `deliveryMode` (`catalogTrackDeliveryMode`); absent is `datagram`. It is
|
|
20
|
+
* read from the current source, so on a catalog refresh re-feed the updated
|
|
21
|
+
* source through `switchSource` for a changed `deliveryMode` to take effect.
|
|
19
22
|
*
|
|
20
23
|
* ABR switch R -> R' (`switchSource`): R's subscriptions are closed, the layer
|
|
21
24
|
* count carries to R', and any packet R's subscriptions deliver after the
|
|
@@ -35,6 +38,8 @@ export interface RepairLayerSource {
|
|
|
35
38
|
repairLayers?: readonly RepairLayerParams[]
|
|
36
39
|
/** Keyframe overlays (`TrackFecParams.repairOverlays`); at most one is used. */
|
|
37
40
|
repairOverlays?: readonly RepairOverlayParams[]
|
|
41
|
+
/** The publisher's MoQ delivery mode; absent is `datagram`. */
|
|
42
|
+
deliveryMode?: MoqRepairDelivery
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
export interface RepairTrackSubscription {
|
|
@@ -43,7 +48,6 @@ export interface RepairTrackSubscription {
|
|
|
43
48
|
|
|
44
49
|
export interface RepairLayerSubscriptionsOptions {
|
|
45
50
|
source: RepairLayerSource
|
|
46
|
-
delivery: MoqRepairDelivery
|
|
47
51
|
/** Subscribe one repair track; every object payload goes to `onPacket`. */
|
|
48
52
|
subscribe(
|
|
49
53
|
repairTrackName: string,
|
|
@@ -110,7 +114,7 @@ export class RepairLayerSubscriptions {
|
|
|
110
114
|
* or interleaved sequences read as loss.
|
|
111
115
|
*/
|
|
112
116
|
noteSourceSequence(seq: number): void {
|
|
113
|
-
if (this.#disposed || this.#
|
|
117
|
+
if (this.#disposed || this.#delivery() === 'stream') return
|
|
114
118
|
seq >>>= 0
|
|
115
119
|
if (this.#lastSeq === undefined) {
|
|
116
120
|
this.#lastSeq = seq
|
|
@@ -152,13 +156,13 @@ export class RepairLayerSubscriptions {
|
|
|
152
156
|
this.#expected = 0
|
|
153
157
|
this.#lost = 0
|
|
154
158
|
this.#wantFewer = 0
|
|
155
|
-
this.#layerCount = Math.min(this.#layerCount, this.#layers().length)
|
|
159
|
+
this.#layerCount = this.#delivery() === 'stream' ? 0 : Math.min(this.#layerCount, this.#layers().length)
|
|
156
160
|
this.#reconcile()
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
snapshot(): RepairLayerSubscriptionsSnapshot {
|
|
160
164
|
return {
|
|
161
|
-
delivery: this.#
|
|
165
|
+
delivery: this.#delivery(),
|
|
162
166
|
layerCount: this.#layerCount,
|
|
163
167
|
subscribedTracks: [...this.#subs.keys()],
|
|
164
168
|
repairPackets: this.#repairPackets,
|
|
@@ -174,6 +178,10 @@ export class RepairLayerSubscriptions {
|
|
|
174
178
|
this.#disposed = true
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
#delivery(): MoqRepairDelivery {
|
|
182
|
+
return this.#source.deliveryMode === 'stream' ? 'stream' : 'datagram'
|
|
183
|
+
}
|
|
184
|
+
|
|
177
185
|
#layers(): readonly RepairLayerParams[] {
|
|
178
186
|
return this.#source.repairLayers ?? []
|
|
179
187
|
}
|
package/src/track-session.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface TrackSessionConfig {
|
|
|
58
58
|
* This is runtime join state, not catalog geometry: a receiver on a clean
|
|
59
59
|
* path joins layer 0 only while a lossy one adds layers, so the catalog's
|
|
60
60
|
* total P is not what this receiver is receiving. Diagnostics that report P
|
|
61
|
-
* (the worker snapshot, CMCD `
|
|
61
|
+
* (the worker snapshot, CMCD `com.blockcast-fp`) must read this.
|
|
62
62
|
*
|
|
63
63
|
* Absent when the caller joins no layers explicitly, in which case P_0 is
|
|
64
64
|
* what was joined. That is an identity on a single-layer track, not a
|