@blockcast/mmt-container 2.0.1 → 2.1.0-main.5b10b5454faf
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/dist/browser-runtime.js +492 -22
- package/dist/browser-runtime.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +2 -2
- package/dist/mmtp-router.d.ts +69 -2
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +356 -13
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +20 -1
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +475 -22
- package/dist/multicast-manager.js.map +2 -2
- package/dist/repair-fetch.d.ts +10 -3
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +15 -0
- package/dist/repair-fetch.js.map +2 -2
- package/package.json +5 -5
- package/src/index.ts +5 -0
- package/src/mmtp-router.ts +468 -12
- package/src/multicast-manager.ts +163 -17
- package/src/repair-fetch.ts +32 -2
package/dist/browser-runtime.js
CHANGED
|
@@ -22431,6 +22431,14 @@ function isRouterDebugEnabled() {
|
|
|
22431
22431
|
function debugRouter(message) {
|
|
22432
22432
|
if (isRouterDebugEnabled()) console.debug(message);
|
|
22433
22433
|
}
|
|
22434
|
+
var KEYFRAME_OVERLAY_EXTENSION_TYPE = 35585;
|
|
22435
|
+
function keyframeOverlaySsId(header) {
|
|
22436
|
+
const ext = header.extension;
|
|
22437
|
+
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
22438
|
+
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
22439
|
+
}
|
|
22440
|
+
var OVERLAY_OFFSET_MPUS = 16;
|
|
22441
|
+
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
22434
22442
|
function assertStreamIdentity(role, stream) {
|
|
22435
22443
|
if (stream === void 0 || !Number.isInteger(stream.packetId) || stream.packetId <= MMTP_SIGNALING_PACKET_ID || typeof stream.catalogTrackName !== "string" || stream.catalogTrackName.length === 0) {
|
|
22436
22444
|
throw new Error(
|
|
@@ -22647,6 +22655,26 @@ var MmtpRouter = class {
|
|
|
22647
22655
|
#packetIdRoles = /* @__PURE__ */ new Map();
|
|
22648
22656
|
/** Explicit catalog ownership for dedicated repair-track packet IDs. */
|
|
22649
22657
|
#repairOwnersByPacketId = /* @__PURE__ */ new Map();
|
|
22658
|
+
/** Keyframe-overlay repair PID → the source its FEC instance protects
|
|
22659
|
+
* (draft-ramadan-mmt-fec-02 7.4.1). Never an owner in
|
|
22660
|
+
* #repairOwnersByPacketId: the overlay is decoded as its own instance. */
|
|
22661
|
+
#overlayLegs = /* @__PURE__ */ new Map();
|
|
22662
|
+
/** Keyframe-overlay repair track names, which name a leg too: a catalog
|
|
22663
|
+
* may state no PID for it (learned at subscribe time). */
|
|
22664
|
+
#overlayRepairNames = /* @__PURE__ */ new Map();
|
|
22665
|
+
/** Installed overlay instances, by overlay repair PID (opt-in only). */
|
|
22666
|
+
#overlays = /* @__PURE__ */ new Map();
|
|
22667
|
+
#overlayRecoveredObservers = /* @__PURE__ */ new Set();
|
|
22668
|
+
/** Streams whose recovered sequence the base path ever re-stamped. */
|
|
22669
|
+
#restampedStreams = /* @__PURE__ */ new Set();
|
|
22670
|
+
#overlayStats = {
|
|
22671
|
+
repairDropped: 0,
|
|
22672
|
+
ownerRefused: 0,
|
|
22673
|
+
recovered: 0,
|
|
22674
|
+
restarts: 0,
|
|
22675
|
+
unsequencedDropped: 0,
|
|
22676
|
+
protectedDropped: 0
|
|
22677
|
+
};
|
|
22650
22678
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
22651
22679
|
#onDeltaBlockCompleteCb = null;
|
|
22652
22680
|
// Debug counters
|
|
@@ -22843,6 +22871,7 @@ var MmtpRouter = class {
|
|
|
22843
22871
|
this.#fecManager.onSourceDiscontinuity = (packetId, catalogTrackName) => {
|
|
22844
22872
|
this.#parser.markFecDiscontinuity(packetId, catalogTrackName);
|
|
22845
22873
|
this.#requestFecPoke(packetId, catalogTrackName);
|
|
22874
|
+
this.resetKeyframeOverlays(packetId, catalogTrackName);
|
|
22846
22875
|
};
|
|
22847
22876
|
this.#fecManager.onBlockComplete = (sbn, packetId, catalogTrackName) => {
|
|
22848
22877
|
this.#mseAssemblerFor(packetId, catalogTrackName)?.completeBlock(sbn);
|
|
@@ -23165,13 +23194,287 @@ var MmtpRouter = class {
|
|
|
23165
23194
|
/** Replace catalog-derived repair ownership before a catalog reconfiguration. */
|
|
23166
23195
|
clearRepairPacketIdOwners() {
|
|
23167
23196
|
this.#repairOwnersByPacketId.clear();
|
|
23197
|
+
this.#overlayLegs.clear();
|
|
23198
|
+
this.#overlayRepairNames.clear();
|
|
23168
23199
|
}
|
|
23169
|
-
/** Bind one dedicated repair-track PID to its authoritative media PID.
|
|
23200
|
+
/** Bind one dedicated repair-track PID to its authoritative media PID. A
|
|
23201
|
+
* known keyframe-overlay PID is refused, and counted: its symbols belong to
|
|
23202
|
+
* the overlay instance, never to the source's base decoder (mmt-02 7.4.4). */
|
|
23170
23203
|
setRepairPacketIdOwner(repairPacketId, ownerPacketId) {
|
|
23204
|
+
if (this.#overlayLegs.has(repairPacketId)) {
|
|
23205
|
+
this.#overlayStats.ownerRefused++;
|
|
23206
|
+
return;
|
|
23207
|
+
}
|
|
23171
23208
|
const owners = this.#repairOwnersByPacketId.get(repairPacketId) ?? /* @__PURE__ */ new Set();
|
|
23172
23209
|
owners.add(ownerPacketId);
|
|
23173
23210
|
this.#repairOwnersByPacketId.set(repairPacketId, owners);
|
|
23174
23211
|
}
|
|
23212
|
+
/**
|
|
23213
|
+
* Record a keyframe-overlay repair leg (draft-ramadan-mmt-fec-02 7.4): its
|
|
23214
|
+
* PID, the source it protects, and its repair track name. Either one names
|
|
23215
|
+
* the leg, so a catalog that states no PID (learned at subscribe time) is
|
|
23216
|
+
* covered by the name. Overlay repair is then never resolved to a base
|
|
23217
|
+
* owner, on multicast or MoQ. Cleared with the catalog owners
|
|
23218
|
+
* (`clearRepairPacketIdOwners`).
|
|
23219
|
+
*/
|
|
23220
|
+
setKeyframeOverlayLeg(repairPacketId, leg) {
|
|
23221
|
+
if (leg.repairTrackName !== void 0) this.#overlayRepairNames.set(leg.repairTrackName, { ...leg });
|
|
23222
|
+
if (repairPacketId === void 0) return;
|
|
23223
|
+
this.#repairOwnersByPacketId.delete(repairPacketId);
|
|
23224
|
+
this.#overlayLegs.set(repairPacketId, { ...leg });
|
|
23225
|
+
}
|
|
23226
|
+
/** The keyframe-overlay leg registered on `repairPacketId`, if any. */
|
|
23227
|
+
keyframeOverlayLeg(repairPacketId) {
|
|
23228
|
+
return this.#overlayLegs.get(repairPacketId);
|
|
23229
|
+
}
|
|
23230
|
+
/**
|
|
23231
|
+
* Overlay counters: repair dropped for want of an installed instance, base
|
|
23232
|
+
* owners refused on an overlay PID, overlay blocks recovered, overlay
|
|
23233
|
+
* restarts, and recovered packets dropped for want of a wire sequence or
|
|
23234
|
+
* for a MoQ symbol on a protected flow (multicast-02 7.2.7).
|
|
23235
|
+
*/
|
|
23236
|
+
get keyframeOverlayStats() {
|
|
23237
|
+
return { ...this.#overlayStats };
|
|
23238
|
+
}
|
|
23239
|
+
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
23240
|
+
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
23241
|
+
}
|
|
23242
|
+
/**
|
|
23243
|
+
* Install the keyframe overlay decoded on `fec` for a known overlay leg
|
|
23244
|
+
* (draft-ramadan-mmt-fec-02 7.4.4: decoded independently of the base). `fec`
|
|
23245
|
+
* holds the overlay stream under `repairPacketId` and `streamName`. Replaces
|
|
23246
|
+
* an earlier install on the same leg; the returned disposer removes only
|
|
23247
|
+
* this one.
|
|
23248
|
+
*/
|
|
23249
|
+
installKeyframeOverlay(repairPacketId, fec, k, streamName) {
|
|
23250
|
+
const leg = this.#overlayLegs.get(repairPacketId);
|
|
23251
|
+
if (!leg) throw new Error(`[MmtpRouter] packetId=${repairPacketId} is no known keyframe-overlay leg`);
|
|
23252
|
+
const inst = {
|
|
23253
|
+
repairPacketId,
|
|
23254
|
+
streamName,
|
|
23255
|
+
leg: { ...leg },
|
|
23256
|
+
fec,
|
|
23257
|
+
k,
|
|
23258
|
+
offsets: /* @__PURE__ */ new Map(),
|
|
23259
|
+
moqBlocks: /* @__PURE__ */ new Set()
|
|
23260
|
+
};
|
|
23261
|
+
this.#overlays.set(repairPacketId, inst);
|
|
23262
|
+
return () => {
|
|
23263
|
+
if (this.#overlays.get(repairPacketId) === inst) this.#overlays.delete(repairPacketId);
|
|
23264
|
+
};
|
|
23265
|
+
}
|
|
23266
|
+
/**
|
|
23267
|
+
* Observe each base block whose RAP MFU fragments a keyframe-overlay
|
|
23268
|
+
* recovery supplied, with the keyframe's FT=0 and FT=1 held
|
|
23269
|
+
* (fec-multicast-00 §6.6). The block is named by the recovered packets'
|
|
23270
|
+
* restored wire sequence through the base SS_ID offset, never by an overlay
|
|
23271
|
+
* coordinate. Called synchronously from the recovered-block route.
|
|
23272
|
+
*/
|
|
23273
|
+
addKeyframeOverlayRecoveredObserver(observer) {
|
|
23274
|
+
this.#overlayRecoveredObservers.add(observer);
|
|
23275
|
+
return () => {
|
|
23276
|
+
this.#overlayRecoveredObservers.delete(observer);
|
|
23277
|
+
};
|
|
23278
|
+
}
|
|
23279
|
+
/** Whether an overlay is installed on `repairPacketId`. */
|
|
23280
|
+
hasKeyframeOverlay(repairPacketId) {
|
|
23281
|
+
return this.#overlays.has(repairPacketId);
|
|
23282
|
+
}
|
|
23283
|
+
/**
|
|
23284
|
+
* Start a new epoch of every overlay protecting `sourcePacketId` (its
|
|
23285
|
+
* source restarted): empty overlay stream, no coverage, no offsets.
|
|
23286
|
+
*/
|
|
23287
|
+
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
23288
|
+
for (const inst of this.#overlaysForSource(sourcePacketId, catalogTrackName)) this.#resetOverlay(inst);
|
|
23289
|
+
}
|
|
23290
|
+
#resetOverlay(inst) {
|
|
23291
|
+
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
23292
|
+
inst.last = void 0;
|
|
23293
|
+
inst.offsets.clear();
|
|
23294
|
+
inst.moqBlocks.clear();
|
|
23295
|
+
}
|
|
23296
|
+
#overlaysForSource(sourcePacketId, catalogTrackName) {
|
|
23297
|
+
return [...this.#overlays.values()].filter(({ leg }) => leg.sourcePacketId === sourcePacketId && (catalogTrackName === void 0 || leg.sourceCatalogTrackName === void 0 || leg.sourceCatalogTrackName === catalogTrackName));
|
|
23298
|
+
}
|
|
23299
|
+
/**
|
|
23300
|
+
* The overlay instance a repair packet belongs to, by the track it arrived
|
|
23301
|
+
* on (7.4.2: its PID or its repair track name); null for overlay repair
|
|
23302
|
+
* with no instance installed; undefined for repair of no overlay.
|
|
23303
|
+
*/
|
|
23304
|
+
#overlayRepairTarget(repairPacketId, repairTrackName) {
|
|
23305
|
+
const inst = this.#overlays.get(repairPacketId) ?? (repairTrackName === void 0 ? void 0 : [...this.#overlays.values()].find(({ leg }) => leg.repairTrackName === repairTrackName));
|
|
23306
|
+
if (inst) return inst;
|
|
23307
|
+
return this.#isOverlayRepair(repairPacketId, repairTrackName) ? null : void 0;
|
|
23308
|
+
}
|
|
23309
|
+
/** Overlay repair goes to its own instance, never to a base owner, locator
|
|
23310
|
+
* or decoder; with none installed it is dropped and counted. */
|
|
23311
|
+
#routeOverlayRepair(inst, repairPacketId, packet, header, source) {
|
|
23312
|
+
const repair = inst ? parseMoqRepairFrameWithHeader(packet, header) : null;
|
|
23313
|
+
if (!inst || !repair) {
|
|
23314
|
+
this.#overlayStats.repairDropped++;
|
|
23315
|
+
debugRouter(`[MmtpRouter] Dropping keyframe-overlay repair pid=${repairPacketId}: no overlay instance`);
|
|
23316
|
+
return;
|
|
23317
|
+
}
|
|
23318
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
23319
|
+
if (!fec.admitRepairBlockLength(pid, repair.ssbLength, repair.rsbLength, repair.rsId)) return;
|
|
23320
|
+
if (!fec.admitRepairSymbolSize(pid, repair.ssStart, repair.ssbLength, repair.rsId, repair.symbolData.byteLength)) return;
|
|
23321
|
+
const placement = fec.placeIsoRepair(pid, name, repair.ssStart, repair.ssbLength);
|
|
23322
|
+
if (placement.status !== "placed") return;
|
|
23323
|
+
const blockStart = placement.block.start;
|
|
23324
|
+
if (placement.span !== void 0) this.#replayOverlaySources(inst, placement.span.block.start, placement.span.placed);
|
|
23325
|
+
this.#replayOverlaySources(inst, blockStart, placement.placed);
|
|
23326
|
+
if (source === "moq") this.#noteOverlayMoqBlock(inst, blockStart);
|
|
23327
|
+
const alreadyCompleted = fec.isBlockCompleted(blockStart, pid, name);
|
|
23328
|
+
const recovered = fec.feedRepairSymbol(pid, repair.ssStart, repair.ssbLength, repair.rsId, repair.symbolData, name);
|
|
23329
|
+
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, blockStart, recovered);
|
|
23330
|
+
}
|
|
23331
|
+
#replayOverlaySources(inst, blockStart, placed) {
|
|
23332
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
23333
|
+
for (const { ssId, payload } of placed) {
|
|
23334
|
+
if (payload.source === "moq") this.#noteOverlayMoqBlock(inst, blockStart);
|
|
23335
|
+
if (payload.oversized) continue;
|
|
23336
|
+
const alreadyCompleted = fec.isBlockCompleted(blockStart, pid, name);
|
|
23337
|
+
const recovered = fec.feedSourceSymbol(pid, ssId, payload.symbolData, name);
|
|
23338
|
+
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, blockStart, recovered);
|
|
23339
|
+
}
|
|
23340
|
+
}
|
|
23341
|
+
/**
|
|
23342
|
+
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
23343
|
+
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
23344
|
+
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
23345
|
+
* included (7.1, 7.4.3).
|
|
23346
|
+
*/
|
|
23347
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
23348
|
+
if (this.#overlays.size === 0) return;
|
|
23349
|
+
const ssId = keyframeOverlaySsId(header);
|
|
23350
|
+
if (ssId === void 0) return;
|
|
23351
|
+
for (const inst of this.#overlaysForSource(packetId, catalogTrackName)) {
|
|
23352
|
+
this.#noteOverlaySsId(inst, ssId, sequenceNumber);
|
|
23353
|
+
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
23354
|
+
if (identity !== null) {
|
|
23355
|
+
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
23356
|
+
inst.offsets.set(identity.mpuSequenceNumber, ssId - sequenceNumber >>> 0);
|
|
23357
|
+
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
23358
|
+
}
|
|
23359
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
23360
|
+
const symbolData = packet.subarray(0, packet.length - 4);
|
|
23361
|
+
const oversized = !fec.isoSourceFits(pid, symbolData.length);
|
|
23362
|
+
const coords = fec.locateIsoSource(pid, name, ssId);
|
|
23363
|
+
if (coords === null) {
|
|
23364
|
+
fec.holdIsoSource(pid, name, ssId, {
|
|
23365
|
+
symbolData,
|
|
23366
|
+
source,
|
|
23367
|
+
oversized,
|
|
23368
|
+
evidence: sourceSymbolEvidence(packet, header, rapFlag, sequenceNumber)
|
|
23369
|
+
});
|
|
23370
|
+
continue;
|
|
23371
|
+
}
|
|
23372
|
+
if (source === "moq") this.#noteOverlayMoqBlock(inst, coords.sbn);
|
|
23373
|
+
if (oversized) continue;
|
|
23374
|
+
const alreadyCompleted = fec.isBlockCompleted(coords.sbn, pid, name);
|
|
23375
|
+
const recovered = fec.feedSourceSymbol(pid, ssId, symbolData, name);
|
|
23376
|
+
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, coords.sbn, recovered);
|
|
23377
|
+
}
|
|
23378
|
+
}
|
|
23379
|
+
#noteOverlayMoqBlock(inst, blockStart) {
|
|
23380
|
+
inst.moqBlocks.add(blockStart);
|
|
23381
|
+
const newest = inst.last?.ssId;
|
|
23382
|
+
if (newest === void 0) return;
|
|
23383
|
+
for (const start of inst.moqBlocks) {
|
|
23384
|
+
if ((newest - start | 0) > OVERLAY_MOQ_HORIZON_BLOCKS * inst.k) inst.moqBlocks.delete(start);
|
|
23385
|
+
}
|
|
23386
|
+
}
|
|
23387
|
+
/**
|
|
23388
|
+
* An overlay SS_ID inconsistent with the source's packet_sequence_number
|
|
23389
|
+
* advance since the last one is an overlay restart (an FFmpeg muxer start,
|
|
23390
|
+
* a bridge swap that keeps psn continuous): it moved the other way, or
|
|
23391
|
+
* further than the psn advance plus K_overlay. Reordering keeps the two
|
|
23392
|
+
* consistent and is no restart.
|
|
23393
|
+
*/
|
|
23394
|
+
#noteOverlaySsId(inst, ssId, psn) {
|
|
23395
|
+
const last = inst.last;
|
|
23396
|
+
if (last !== void 0) {
|
|
23397
|
+
const dSs = ssId - last.ssId | 0;
|
|
23398
|
+
const dPsn = psn - last.psn | 0;
|
|
23399
|
+
const consistent = dSs === 0 ? dPsn === 0 : Math.sign(dSs) === Math.sign(dPsn) && Math.abs(dSs) <= Math.abs(dPsn) + inst.k;
|
|
23400
|
+
if (!consistent) {
|
|
23401
|
+
this.#overlayStats.restarts++;
|
|
23402
|
+
this.#resetOverlay(inst);
|
|
23403
|
+
} else if (dPsn < 0) {
|
|
23404
|
+
return;
|
|
23405
|
+
}
|
|
23406
|
+
}
|
|
23407
|
+
inst.last = { ssId, psn };
|
|
23408
|
+
}
|
|
23409
|
+
/**
|
|
23410
|
+
* Route an overlay-recovered block's packets as source media of the
|
|
23411
|
+
* overlay's source (7.2: an overlay coordinate yields no base SS_ID). No
|
|
23412
|
+
* base-SS_ID derivation runs: no release-gate anchor, no parse ledger, no
|
|
23413
|
+
* base re-stamp, no block advance. Each packet's wire sequence is restored
|
|
23414
|
+
* from its keyframe's own offset; without one, a stream the base never
|
|
23415
|
+
* re-stamped keeps the header's sequence and any other drops the packet.
|
|
23416
|
+
*/
|
|
23417
|
+
#routeOverlayRecovered(inst, blockStart, recovered) {
|
|
23418
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
23419
|
+
fec.markBlockCompleted(blockStart, pid, name);
|
|
23420
|
+
const sourcePacketId = inst.leg.sourcePacketId;
|
|
23421
|
+
const sourceName = inst.leg.sourceCatalogTrackName;
|
|
23422
|
+
if (this.#classifyPacketId(sourcePacketId) === null) return;
|
|
23423
|
+
const receiver = this.#provenanceFor(sourcePacketId, sourceName);
|
|
23424
|
+
if (receiver !== void 0 && inst.moqBlocks.has(blockStart)) {
|
|
23425
|
+
this.#overlayStats.protectedDropped++;
|
|
23426
|
+
return;
|
|
23427
|
+
}
|
|
23428
|
+
const blockProfile = fec.blockProfileForPacket(pid);
|
|
23429
|
+
const symbolSize = fec.symbolSizeForPacket(pid);
|
|
23430
|
+
if (blockProfile === void 0 || symbolSize === void 0 || !(symbolSize > 0)) return;
|
|
23431
|
+
const result = processFecBlock(recovered, { symbolSize, ownerPacketId: sourcePacketId, blockProfile });
|
|
23432
|
+
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
23433
|
+
const restamped = this.#restampedStreams.has(streamKey);
|
|
23434
|
+
const restored = /* @__PURE__ */ new Set();
|
|
23435
|
+
const symbols = result.symbols.flatMap((sym) => {
|
|
23436
|
+
const offset = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
23437
|
+
if (offset !== void 0) {
|
|
23438
|
+
const packetSequenceNumber = serial32Add(blockStart, sym.index) - offset >>> 0;
|
|
23439
|
+
restored.add(packetSequenceNumber);
|
|
23440
|
+
return [{ ...sym, packetSequenceNumber }];
|
|
23441
|
+
}
|
|
23442
|
+
if (!restamped) return [sym];
|
|
23443
|
+
this.#overlayStats.unsequencedDropped++;
|
|
23444
|
+
return [];
|
|
23445
|
+
});
|
|
23446
|
+
this.#overlayStats.recovered++;
|
|
23447
|
+
this.#routeProcessedFecSymbols(blockStart, { ...result, symbols }, sourcePacketId, sourceName, {
|
|
23448
|
+
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
23449
|
+
});
|
|
23450
|
+
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
23451
|
+
}
|
|
23452
|
+
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
23453
|
+
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
23454
|
+
* packet whose wire sequence was not restored names no base block. */
|
|
23455
|
+
#noteOverlayRecoveredRaps(packetId, catalogTrackName, streamKey, symbols) {
|
|
23456
|
+
const ssIdSeqOffset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
23457
|
+
if (ssIdSeqOffset === void 0 || this.#overlayRecoveredObservers.size === 0) return;
|
|
23458
|
+
const withMeta = (type) => new Set(symbols.filter((sym) => sym.fragmentType === type).map((sym) => sym.fragment.mpuSequenceNumber));
|
|
23459
|
+
const ft0 = withMeta(0);
|
|
23460
|
+
const ft1 = withMeta(1);
|
|
23461
|
+
const rapMfu = (fi) => new Set(symbols.filter((sym) => sym.fragmentType === 2 && sym.fragment.rapFlag && sym.fragment.fragmentationIndicator === fi).map((sym) => sym.fragment.mpuSequenceNumber));
|
|
23462
|
+
const single = rapMfu(0);
|
|
23463
|
+
const first = rapMfu(1);
|
|
23464
|
+
const last = rapMfu(3);
|
|
23465
|
+
const blocks = /* @__PURE__ */ new Set();
|
|
23466
|
+
for (const sym of symbols) {
|
|
23467
|
+
const mpu = sym.fragment.mpuSequenceNumber;
|
|
23468
|
+
if (sym.fragmentType !== 2 || !sym.fragment.rapFlag || !ft0.has(mpu) || !ft1.has(mpu)) continue;
|
|
23469
|
+
if (!single.has(mpu) && !(first.has(mpu) && last.has(mpu))) continue;
|
|
23470
|
+
const ssId = sym.packetSequenceNumber + ssIdSeqOffset >>> 0;
|
|
23471
|
+
const sbn = this.#fecManager?.locateIsoSource?.(packetId, catalogTrackName, ssId)?.sbn;
|
|
23472
|
+
if (sbn !== void 0) blocks.add(sbn);
|
|
23473
|
+
}
|
|
23474
|
+
for (const sbn of blocks) {
|
|
23475
|
+
for (const observer of this.#overlayRecoveredObservers) observer(packetId, catalogTrackName, sbn);
|
|
23476
|
+
}
|
|
23477
|
+
}
|
|
23175
23478
|
/**
|
|
23176
23479
|
* Register callback fired when a delta-track FEC block completes or fails.
|
|
23177
23480
|
*
|
|
@@ -23595,6 +23898,11 @@ var MmtpRouter = class {
|
|
|
23595
23898
|
return;
|
|
23596
23899
|
}
|
|
23597
23900
|
const repairPacketId = header.packetId;
|
|
23901
|
+
const overlay = this.#overlayRepairTarget(repairPacketId, repairTrackName);
|
|
23902
|
+
if (overlay !== void 0) {
|
|
23903
|
+
this.#routeOverlayRepair(overlay, repairPacketId, packet, header, "moq");
|
|
23904
|
+
return;
|
|
23905
|
+
}
|
|
23598
23906
|
const hintedOwner = ownerPacketIdHint != null && this.#classifyPacketId(ownerPacketIdHint) !== null ? ownerPacketIdHint : null;
|
|
23599
23907
|
const ownerPacketId = hintedOwner ?? (this.#classifyPacketId(repairPacketId) !== null ? repairPacketId : null);
|
|
23600
23908
|
if (ownerPacketId === null) {
|
|
@@ -23884,6 +24192,8 @@ var MmtpRouter = class {
|
|
|
23884
24192
|
}
|
|
23885
24193
|
}
|
|
23886
24194
|
this.#parser.resetTrack(packetId, catalogTrackName);
|
|
24195
|
+
this.resetKeyframeOverlays(packetId, catalogTrackName);
|
|
24196
|
+
this.#restampedStreams.delete(this.#mediaStreamKey(packetId, catalogTrackName));
|
|
23887
24197
|
const assistPrefix = assistKey(packetId, catalogTrackName, 0).slice(0, -1);
|
|
23888
24198
|
for (const key of this.#assistBlocks.keys()) if (key.startsWith(assistPrefix)) this.#assistBlocks.delete(key);
|
|
23889
24199
|
for (const key of this.#assistFailures.keys()) if (key.startsWith(assistPrefix)) this.#assistFailures.delete(key);
|
|
@@ -23970,6 +24280,17 @@ var MmtpRouter = class {
|
|
|
23970
24280
|
this.#packetIdRoles.delete(packetId);
|
|
23971
24281
|
this.#catalogTrackNamesByPacketId.delete(packetId);
|
|
23972
24282
|
this.#repairOwnersByPacketId.delete(packetId);
|
|
24283
|
+
this.#overlayLegs.delete(packetId);
|
|
24284
|
+
for (const [repairPacketId, leg] of [...this.#overlayLegs]) {
|
|
24285
|
+
if (leg.sourcePacketId === packetId) this.#overlayLegs.delete(repairPacketId);
|
|
24286
|
+
}
|
|
24287
|
+
for (const [name, leg] of [...this.#overlayRepairNames]) {
|
|
24288
|
+
if (leg.sourcePacketId === packetId) this.#overlayRepairNames.delete(name);
|
|
24289
|
+
}
|
|
24290
|
+
this.#overlays.delete(packetId);
|
|
24291
|
+
for (const [repairPacketId, inst] of [...this.#overlays]) {
|
|
24292
|
+
if (inst.leg.sourcePacketId === packetId) this.#overlays.delete(repairPacketId);
|
|
24293
|
+
}
|
|
23973
24294
|
delete this.#pidCounts[packetId];
|
|
23974
24295
|
delete this.#repairPidCounts[packetId];
|
|
23975
24296
|
delete this.#moqRepairPidCounts[packetId];
|
|
@@ -24208,6 +24529,9 @@ var MmtpRouter = class {
|
|
|
24208
24529
|
this.#parserDiagnostics.clear();
|
|
24209
24530
|
this.#catalogTrackNamesByPacketId.clear();
|
|
24210
24531
|
this.#repairOwnersByPacketId.clear();
|
|
24532
|
+
this.#overlayLegs.clear();
|
|
24533
|
+
this.#overlayRepairNames.clear();
|
|
24534
|
+
this.#overlays.clear();
|
|
24211
24535
|
for (const { receiver } of this.#provenance.values()) receiver.release();
|
|
24212
24536
|
this.#provenance.clear();
|
|
24213
24537
|
this.#provenanceStreamFlow.clear();
|
|
@@ -24563,6 +24887,11 @@ var MmtpRouter = class {
|
|
|
24563
24887
|
}
|
|
24564
24888
|
}
|
|
24565
24889
|
if (info.isRepair) {
|
|
24890
|
+
const overlay = this.#overlayRepairTarget(info.packetId, catalogTrackNameForPacket(ingress, info.packetId));
|
|
24891
|
+
if (overlay !== void 0) {
|
|
24892
|
+
this.#routeOverlayRepair(overlay, info.packetId, packet, parsed.header, source);
|
|
24893
|
+
return;
|
|
24894
|
+
}
|
|
24566
24895
|
const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);
|
|
24567
24896
|
if (ownerPacketId != null && this.#fecManager && hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;
|
|
24568
24897
|
const repair = this.#readRepairObject(
|
|
@@ -24769,6 +25098,17 @@ var MmtpRouter = class {
|
|
|
24769
25098
|
});
|
|
24770
25099
|
}
|
|
24771
25100
|
}
|
|
25101
|
+
if (!overlapCopy && this.#classifyPacketId(info.packetId) !== null) {
|
|
25102
|
+
this.#feedOverlaySource(
|
|
25103
|
+
info.packetId,
|
|
25104
|
+
catalogTrackName,
|
|
25105
|
+
packet,
|
|
25106
|
+
parsed.header,
|
|
25107
|
+
info.sequenceNumber,
|
|
25108
|
+
info.rapFlag,
|
|
25109
|
+
source
|
|
25110
|
+
);
|
|
25111
|
+
}
|
|
24772
25112
|
}
|
|
24773
25113
|
}
|
|
24774
25114
|
/**
|
|
@@ -25222,12 +25562,12 @@ var MmtpRouter = class {
|
|
|
25222
25562
|
}
|
|
25223
25563
|
return match;
|
|
25224
25564
|
}
|
|
25225
|
-
#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName) {
|
|
25565
|
+
#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, overlay) {
|
|
25226
25566
|
if (ownerPacketId == null) return;
|
|
25227
25567
|
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
25228
|
-
const
|
|
25229
|
-
|
|
25230
|
-
);
|
|
25568
|
+
const fecSbnOf = (index) => overlay ? {} : { fecSbn: this.#fecAnchorForRecovered(blockId, index) };
|
|
25569
|
+
const streamKey = this.#mediaStreamKey(ownerPacketId, catalogTrackName);
|
|
25570
|
+
const ssIdSeqOffset = overlay ? void 0 : this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
25231
25571
|
if (ssIdSeqOffset !== void 0) {
|
|
25232
25572
|
result = {
|
|
25233
25573
|
...result,
|
|
@@ -25235,12 +25575,13 @@ var MmtpRouter = class {
|
|
|
25235
25575
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
25236
25576
|
if (wire === sym.packetSequenceNumber) return sym;
|
|
25237
25577
|
this.#restampedRecoveredSeqs++;
|
|
25578
|
+
this.#restampedStreams.add(streamKey);
|
|
25238
25579
|
return { ...sym, packetSequenceNumber: wire };
|
|
25239
25580
|
})
|
|
25240
25581
|
};
|
|
25241
25582
|
}
|
|
25242
|
-
this.#stream(ownerPacketId, catalogTrackName).duplicates.advance(blockId);
|
|
25243
|
-
const deliveryProvenance = this.#fecManager?.blockDeliveryProvenance?.(
|
|
25583
|
+
if (!overlay) this.#stream(ownerPacketId, catalogTrackName).duplicates.advance(blockId);
|
|
25584
|
+
const deliveryProvenance = overlay?.deliveryProvenance ?? this.#fecManager?.blockDeliveryProvenance?.(
|
|
25244
25585
|
blockId,
|
|
25245
25586
|
ownerPacketId,
|
|
25246
25587
|
catalogTrackName
|
|
@@ -25318,7 +25659,7 @@ var MmtpRouter = class {
|
|
|
25318
25659
|
rapFlag: sym.fragment.rapFlag,
|
|
25319
25660
|
recovered: true,
|
|
25320
25661
|
deliveryProvenance,
|
|
25321
|
-
|
|
25662
|
+
...fecSbnOf(sym.index),
|
|
25322
25663
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
25323
25664
|
});
|
|
25324
25665
|
continue;
|
|
@@ -25430,7 +25771,7 @@ var MmtpRouter = class {
|
|
|
25430
25771
|
rapFlag: assembled.rapFlag,
|
|
25431
25772
|
recovered: true,
|
|
25432
25773
|
...assembled.deliveryProvenance === void 0 ? {} : { deliveryProvenance: assembled.deliveryProvenance },
|
|
25433
|
-
|
|
25774
|
+
...fecSbnOf(sym.index),
|
|
25434
25775
|
...recoveredCatalogTrackName === void 0 ? {} : { catalogTrackName: recoveredCatalogTrackName }
|
|
25435
25776
|
});
|
|
25436
25777
|
continue;
|
|
@@ -25567,7 +25908,7 @@ var MmtpRouter = class {
|
|
|
25567
25908
|
rapFlag: sym.fragment.rapFlag,
|
|
25568
25909
|
recovered: true,
|
|
25569
25910
|
deliveryProvenance,
|
|
25570
|
-
|
|
25911
|
+
...fecSbnOf(sym.index),
|
|
25571
25912
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
25572
25913
|
});
|
|
25573
25914
|
} else {
|
|
@@ -25646,7 +25987,7 @@ var MmtpRouter = class {
|
|
|
25646
25987
|
rapFlag: assembled.rapFlag,
|
|
25647
25988
|
recovered: true,
|
|
25648
25989
|
deliveryProvenance,
|
|
25649
|
-
|
|
25990
|
+
...fecSbnOf(sym.index),
|
|
25650
25991
|
...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName }
|
|
25651
25992
|
});
|
|
25652
25993
|
}
|
|
@@ -25681,7 +26022,7 @@ var MmtpRouter = class {
|
|
|
25681
26022
|
...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
|
|
25682
26023
|
recovered: true,
|
|
25683
26024
|
deliveryProvenance,
|
|
25684
|
-
|
|
26025
|
+
...fecSbnOf(sym.index),
|
|
25685
26026
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
25686
26027
|
});
|
|
25687
26028
|
continue;
|
|
@@ -26860,7 +27201,7 @@ function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
|
|
|
26860
27201
|
repairsSkippedRangeNoAnchor: 0,
|
|
26861
27202
|
repairsSkippedGroupCannotComplete: 0,
|
|
26862
27203
|
repairsSkippedMootRap: 0,
|
|
26863
|
-
repairStops: { preempted: 0, reprioritized: 0, expired: 0, resolved: 0, reset: 0, disposed: 0, moot: 0 },
|
|
27204
|
+
repairStops: { preempted: 0, reprioritized: 0, expired: 0, resolved: 0, reset: 0, disposed: 0, moot: 0, "overlay-recovered": 0 },
|
|
26864
27205
|
lastRepairSkip: null,
|
|
26865
27206
|
keyframeRepairFetchesSent: 0,
|
|
26866
27207
|
rangeRepairFetchesSent: 0,
|
|
@@ -27193,9 +27534,7 @@ function repairTrackNamesBySource(tracks) {
|
|
|
27193
27534
|
}
|
|
27194
27535
|
function boundRepairNames(binding) {
|
|
27195
27536
|
if (binding?.status !== "ok") return [];
|
|
27196
|
-
|
|
27197
|
-
if (binding.overlay) names.push(binding.overlay.repairTrackName);
|
|
27198
|
-
return names;
|
|
27537
|
+
return binding.layers.map((layer) => layer.repairTrackName);
|
|
27199
27538
|
}
|
|
27200
27539
|
var MulticastManager = class {
|
|
27201
27540
|
#fec;
|
|
@@ -27209,6 +27548,10 @@ var MulticastManager = class {
|
|
|
27209
27548
|
/** Live joinRepairEndpoints() releases → the repair owners they registered,
|
|
27210
27549
|
* re-applied after configureMultiTrack() clears catalog-derived owners. */
|
|
27211
27550
|
#repairReleases = /* @__PURE__ */ new Map();
|
|
27551
|
+
/** Each source's catalog keyframe overlay, from its `ok` binding. */
|
|
27552
|
+
#overlayBindings = /* @__PURE__ */ new Map();
|
|
27553
|
+
/** Installed overlay decoders (`setKeyframeOverlayDecoder`), by source. */
|
|
27554
|
+
#overlayInstalls = /* @__PURE__ */ new Map();
|
|
27212
27555
|
#reportedFecDiagnosticsFailure = false;
|
|
27213
27556
|
// ── Stream-global FEC timing reduction ──
|
|
27214
27557
|
//
|
|
@@ -27449,6 +27792,63 @@ var MulticastManager = class {
|
|
|
27449
27792
|
this.#configureTrackMapper(packetId, fecConfig);
|
|
27450
27793
|
this.#router.setPacketIdType(packetId, trackType);
|
|
27451
27794
|
}
|
|
27795
|
+
/**
|
|
27796
|
+
* Decode the keyframe overlay of `sourcePacketId` (draft-ramadan-mmt-fec-02
|
|
27797
|
+
* 7.4) with `decoder`, a decoder of the same kind `setFecDecoder` takes.
|
|
27798
|
+
* Opt-in: nothing else installs one, so a receiver that never calls this
|
|
27799
|
+
* holds no overlay state and drops overlay repair. K_overlay, T, scheme
|
|
27800
|
+
* and block profile come from the source's `ok` catalog binding, which
|
|
27801
|
+
* must declare an overlay (`configureMultiTrack` first). The overlay is
|
|
27802
|
+
* decoded on its own FEC state, apart from the base: no repair FETCH, no
|
|
27803
|
+
* repair lifecycle event and no base statistic for its blocks. A second
|
|
27804
|
+
* install on the same source replaces the first; the returned disposer
|
|
27805
|
+
* removes only the install it created. Call it on releasing the overlay
|
|
27806
|
+
* join.
|
|
27807
|
+
*/
|
|
27808
|
+
setKeyframeOverlayDecoder(sourcePacketId, decoder, catalogTrackName) {
|
|
27809
|
+
const binding = this.#overlayBindings.get(sourcePacketId);
|
|
27810
|
+
if (!binding || catalogTrackName !== void 0 && binding.sourceCatalogTrackName !== catalogTrackName) {
|
|
27811
|
+
throw new Error(
|
|
27812
|
+
`[MulticastManager] packetId=${sourcePacketId} has no catalog keyframe overlay to decode`
|
|
27813
|
+
);
|
|
27814
|
+
}
|
|
27815
|
+
this.#overlayInstalls.get(sourcePacketId)?.dispose();
|
|
27816
|
+
let fec;
|
|
27817
|
+
let uninstall;
|
|
27818
|
+
const attach = () => {
|
|
27819
|
+
fec = new FECManager();
|
|
27820
|
+
fec.setFecDecoder(binding.repairPacketId, decoder, binding.k, {
|
|
27821
|
+
blockProfile: binding.blockProfile,
|
|
27822
|
+
k: binding.k,
|
|
27823
|
+
symbolSize: binding.symbolSize,
|
|
27824
|
+
trackKind: "video"
|
|
27825
|
+
}, binding.repairTrackName);
|
|
27826
|
+
uninstall = this.#router.installKeyframeOverlay(
|
|
27827
|
+
binding.repairPacketId,
|
|
27828
|
+
fec,
|
|
27829
|
+
binding.k,
|
|
27830
|
+
binding.repairTrackName
|
|
27831
|
+
);
|
|
27832
|
+
};
|
|
27833
|
+
const detach = () => {
|
|
27834
|
+
uninstall();
|
|
27835
|
+
fec.releasePacket(binding.repairPacketId);
|
|
27836
|
+
};
|
|
27837
|
+
attach();
|
|
27838
|
+
const install = {
|
|
27839
|
+
reattach: () => {
|
|
27840
|
+
detach();
|
|
27841
|
+
attach();
|
|
27842
|
+
},
|
|
27843
|
+
dispose: () => {
|
|
27844
|
+
if (this.#overlayInstalls.get(sourcePacketId) !== install) return;
|
|
27845
|
+
this.#overlayInstalls.delete(sourcePacketId);
|
|
27846
|
+
detach();
|
|
27847
|
+
}
|
|
27848
|
+
};
|
|
27849
|
+
this.#overlayInstalls.set(sourcePacketId, install);
|
|
27850
|
+
return install.dispose;
|
|
27851
|
+
}
|
|
27452
27852
|
/**
|
|
27453
27853
|
* Register a Worker-backed FEC decoder for a specific packetId (track).
|
|
27454
27854
|
* Worker-backed tracks decode asynchronously — recovered frames arrive via
|
|
@@ -27961,7 +28361,11 @@ var MulticastManager = class {
|
|
|
27961
28361
|
console.warn("[MulticastManager] repair group release failed", error);
|
|
27962
28362
|
}
|
|
27963
28363
|
};
|
|
27964
|
-
const repairOwners = endpoints.flatMap((endpoint) => endpoint.repairOwners)
|
|
28364
|
+
const repairOwners = endpoints.flatMap((endpoint) => endpoint.repairOwners.map((owner) => owner.instance !== "keyframe" ? owner : {
|
|
28365
|
+
...owner,
|
|
28366
|
+
ownerTrackName: endpoint.tracks.find((t) => t.packetId === owner.ownerPacketId)?.name,
|
|
28367
|
+
repairTrackName: endpoint.tracks.find((t) => t.packetId === owner.packetId)?.name
|
|
28368
|
+
}));
|
|
27965
28369
|
this.#applyRepairOwners(repairOwners);
|
|
27966
28370
|
for (const endpoint of endpoints) {
|
|
27967
28371
|
const ingress = {
|
|
@@ -28009,9 +28413,17 @@ var MulticastManager = class {
|
|
|
28009
28413
|
return release;
|
|
28010
28414
|
}
|
|
28011
28415
|
#applyRepairOwners(owners) {
|
|
28012
|
-
for (const { packetId, ownerPacketId } of owners) {
|
|
28416
|
+
for (const { packetId, ownerPacketId, instance, ownerTrackName, repairTrackName } of owners) {
|
|
28013
28417
|
this.#router.setPacketIdRole(packetId, "repair");
|
|
28014
|
-
|
|
28418
|
+
if (instance === "keyframe") {
|
|
28419
|
+
this.#router.setKeyframeOverlayLeg(packetId, {
|
|
28420
|
+
sourcePacketId: ownerPacketId,
|
|
28421
|
+
...ownerTrackName === void 0 ? {} : { sourceCatalogTrackName: ownerTrackName },
|
|
28422
|
+
...repairTrackName === void 0 ? {} : { repairTrackName }
|
|
28423
|
+
});
|
|
28424
|
+
} else {
|
|
28425
|
+
this.#router.setRepairPacketIdOwner(packetId, ownerPacketId);
|
|
28426
|
+
}
|
|
28015
28427
|
}
|
|
28016
28428
|
}
|
|
28017
28429
|
// ---------------------------------------------------------------------------
|
|
@@ -28275,8 +28687,8 @@ var MulticastManager = class {
|
|
|
28275
28687
|
this.#fec.setMulticastSymbolDelivery(delivery);
|
|
28276
28688
|
}
|
|
28277
28689
|
/** Process a repair packet from MoQ (reliable QUIC). */
|
|
28278
|
-
processRepairFromMoQ(packet, ownerPacketId, catalogTrackName) {
|
|
28279
|
-
this.#router.feedRepairPacket(packet, ownerPacketId, catalogTrackName);
|
|
28690
|
+
processRepairFromMoQ(packet, ownerPacketId, catalogTrackName, repairTrackName) {
|
|
28691
|
+
this.#router.feedRepairPacket(packet, ownerPacketId, catalogTrackName, repairTrackName);
|
|
28280
28692
|
}
|
|
28281
28693
|
/** Route a Worker-recovered FEC block through the MMTP parser + dedup pipeline. */
|
|
28282
28694
|
routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
|
|
@@ -28328,7 +28740,10 @@ var MulticastManager = class {
|
|
|
28328
28740
|
const ownedRepairNames = /* @__PURE__ */ new Set();
|
|
28329
28741
|
const claimed = /* @__PURE__ */ new Map();
|
|
28330
28742
|
for (const binding of bindings.values()) {
|
|
28331
|
-
|
|
28743
|
+
const overlay = binding.status === "ok" ? binding.overlay?.repairTrackName : void 0;
|
|
28744
|
+
for (const name of [...boundRepairNames(binding), ...overlay === void 0 ? [] : [overlay]]) {
|
|
28745
|
+
claimed.set(name, (claimed.get(name) ?? 0) + 1);
|
|
28746
|
+
}
|
|
28332
28747
|
}
|
|
28333
28748
|
for (const [name, count] of claimed) if (count === 1) ownedRepairNames.add(name);
|
|
28334
28749
|
const repairOwnerGroup = /* @__PURE__ */ new Map();
|
|
@@ -28431,6 +28846,7 @@ var MulticastManager = class {
|
|
|
28431
28846
|
const catalogTracksByName = new Map(
|
|
28432
28847
|
catalog.tracks.map((track) => [track.name, track])
|
|
28433
28848
|
);
|
|
28849
|
+
const overlayBindings = /* @__PURE__ */ new Map();
|
|
28434
28850
|
for (let i = 0; i < catalog.tracks.length; i++) {
|
|
28435
28851
|
const track = catalog.tracks[i];
|
|
28436
28852
|
const role = resolvedRoles[i];
|
|
@@ -28459,6 +28875,29 @@ var MulticastManager = class {
|
|
|
28459
28875
|
this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
|
|
28460
28876
|
}
|
|
28461
28877
|
}
|
|
28878
|
+
const overlayName = binding.overlay?.repairTrackName;
|
|
28879
|
+
const overlayPacketId = overlayName === void 0 ? void 0 : catalogTracksByName.get(overlayName)?.packetId;
|
|
28880
|
+
if (overlayName !== void 0 && ownedRepairNames.has(overlayName)) {
|
|
28881
|
+
this.#router.setKeyframeOverlayLeg(overlayPacketId, {
|
|
28882
|
+
sourcePacketId: track.packetId,
|
|
28883
|
+
sourceCatalogTrackName: track.name,
|
|
28884
|
+
repairTrackName: overlayName
|
|
28885
|
+
});
|
|
28886
|
+
}
|
|
28887
|
+
if (overlayName !== void 0 && overlayPacketId !== void 0 && ownedRepairNames.has(overlayName)) {
|
|
28888
|
+
const geometry2 = binding.overlay.geometry;
|
|
28889
|
+
overlayBindings.set(track.packetId, {
|
|
28890
|
+
repairPacketId: overlayPacketId,
|
|
28891
|
+
repairTrackName: overlayName,
|
|
28892
|
+
sourceCatalogTrackName: track.name,
|
|
28893
|
+
k: binding.overlay.sourceSymbols,
|
|
28894
|
+
symbolSize: fecCfg.symbolSize,
|
|
28895
|
+
blockProfile: parseFecBlockProfile(
|
|
28896
|
+
geometry2.blockProfile,
|
|
28897
|
+
`tracks[${JSON.stringify(track.name)}].fec keyframe overlay blockProfile`
|
|
28898
|
+
)
|
|
28899
|
+
});
|
|
28900
|
+
}
|
|
28462
28901
|
}
|
|
28463
28902
|
const wasmDecoder = fecDecoders.get(i);
|
|
28464
28903
|
if (track.packetId !== void 0 && wasmDecoder !== void 0) {
|
|
@@ -28492,6 +28931,16 @@ var MulticastManager = class {
|
|
|
28492
28931
|
}
|
|
28493
28932
|
}
|
|
28494
28933
|
}
|
|
28934
|
+
for (const [sourcePacketId, install] of [...this.#overlayInstalls]) {
|
|
28935
|
+
const before = this.#overlayBindings.get(sourcePacketId);
|
|
28936
|
+
const after = overlayBindings.get(sourcePacketId);
|
|
28937
|
+
const same = before !== void 0 && after !== void 0 && Object.keys(after).every((key) => after[key] === before[key]);
|
|
28938
|
+
if (!same) install.dispose();
|
|
28939
|
+
else if (this.#router.hasKeyframeOverlay(after.repairPacketId)) this.#router.resetKeyframeOverlays(sourcePacketId);
|
|
28940
|
+
else install.reattach();
|
|
28941
|
+
}
|
|
28942
|
+
this.#overlayBindings.clear();
|
|
28943
|
+
for (const [sourcePacketId, binding] of overlayBindings) this.#overlayBindings.set(sourcePacketId, binding);
|
|
28495
28944
|
}
|
|
28496
28945
|
/**
|
|
28497
28946
|
* Why `configureMultiTrack` consumes the named catalog track without FEC
|
|
@@ -28663,6 +29112,10 @@ var MulticastManager = class {
|
|
|
28663
29112
|
addRapCompleteObserver(observer) {
|
|
28664
29113
|
return this.#router.addFecRapObserver(observer);
|
|
28665
29114
|
}
|
|
29115
|
+
/** Observe each base block whose RAP the keyframe overlay recovered (fec-multicast-00 §6.6). */
|
|
29116
|
+
addKeyframeOverlayRecoveredObserver(observer) {
|
|
29117
|
+
return this.#router.addKeyframeOverlayRecoveredObserver(observer);
|
|
29118
|
+
}
|
|
28666
29119
|
/** Catalog/reset epoch of a packet's streams; gap decisions carry it. */
|
|
28667
29120
|
epochFor(packetId) {
|
|
28668
29121
|
return this.#fec.epochFor(packetId);
|
|
@@ -31754,12 +32207,27 @@ function wireRepairFetch(mc, opts) {
|
|
|
31754
32207
|
dispatch();
|
|
31755
32208
|
});
|
|
31756
32209
|
});
|
|
32210
|
+
const removeOverlayObserver = mc.addKeyframeOverlayRecoveredObserver?.((packetId, catalogTrackName, sbn) => {
|
|
32211
|
+
queueMicrotask(() => {
|
|
32212
|
+
if (disposed) return;
|
|
32213
|
+
for (const entry of [...entries.values()]) {
|
|
32214
|
+
if (entry.range || entry.priority !== 0 || entry.packetId !== packetId || entry.sbn !== sbn) continue;
|
|
32215
|
+
if (catalogTrackName !== void 0 && entry.catalogTrackName !== catalogTrackName) continue;
|
|
32216
|
+
stop(entry, "overlay-recovered");
|
|
32217
|
+
retire(entry);
|
|
32218
|
+
releaseHold(entry);
|
|
32219
|
+
clearEntry(entry);
|
|
32220
|
+
}
|
|
32221
|
+
dispatch();
|
|
32222
|
+
});
|
|
32223
|
+
});
|
|
31757
32224
|
return () => {
|
|
31758
32225
|
disposed = true;
|
|
31759
32226
|
mc.setUnicastRepair?.(false);
|
|
31760
32227
|
removeResetObserver();
|
|
31761
32228
|
removeLifecycleObserver();
|
|
31762
32229
|
removeRapObserver?.();
|
|
32230
|
+
removeOverlayObserver?.();
|
|
31763
32231
|
for (const entry of [...entries.values()]) {
|
|
31764
32232
|
clearEntry(entry);
|
|
31765
32233
|
if (entry.running) releaseHold(entry);
|
|
@@ -31793,6 +32261,7 @@ export {
|
|
|
31793
32261
|
HoldLastTimeline,
|
|
31794
32262
|
JOIN_DEFER_WARN_MS,
|
|
31795
32263
|
KEYFRAME_LOST_TIMEOUT_SLACK,
|
|
32264
|
+
KEYFRAME_OVERLAY_EXTENSION_TYPE,
|
|
31796
32265
|
LOCHeaderExtensionId,
|
|
31797
32266
|
MAX_ENCODING_SYMBOL_ID,
|
|
31798
32267
|
MAX_RAPTORQ_SOURCE_SYMBOLS,
|
|
@@ -31902,6 +32371,7 @@ export {
|
|
|
31902
32371
|
isTrackTimingConfig,
|
|
31903
32372
|
joinedRepairSymbolCount,
|
|
31904
32373
|
keyframeIntervalMediaTicks,
|
|
32374
|
+
keyframeOverlaySsId,
|
|
31905
32375
|
mergeOverrideIntoCmsfTracks,
|
|
31906
32376
|
mergeOverrideIntoHangRenditions,
|
|
31907
32377
|
mfuReassemblyTimeoutMs,
|