@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
|
@@ -16314,6 +16314,14 @@ function isRouterDebugEnabled() {
|
|
|
16314
16314
|
function debugRouter(message) {
|
|
16315
16315
|
if (isRouterDebugEnabled()) console.debug(message);
|
|
16316
16316
|
}
|
|
16317
|
+
var KEYFRAME_OVERLAY_EXTENSION_TYPE = 35585;
|
|
16318
|
+
function keyframeOverlaySsId(header) {
|
|
16319
|
+
const ext = header.extension;
|
|
16320
|
+
if (ext?.type !== KEYFRAME_OVERLAY_EXTENSION_TYPE || ext.data.length !== 4) return void 0;
|
|
16321
|
+
return new DataView(ext.data.buffer, ext.data.byteOffset, 4).getUint32(0);
|
|
16322
|
+
}
|
|
16323
|
+
var OVERLAY_OFFSET_MPUS = 16;
|
|
16324
|
+
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
16317
16325
|
function assertStreamIdentity(role, stream) {
|
|
16318
16326
|
if (stream === void 0 || !Number.isInteger(stream.packetId) || stream.packetId <= MMTP_SIGNALING_PACKET_ID || typeof stream.catalogTrackName !== "string" || stream.catalogTrackName.length === 0) {
|
|
16319
16327
|
throw new Error(
|
|
@@ -16530,6 +16538,26 @@ var MmtpRouter = class {
|
|
|
16530
16538
|
#packetIdRoles = /* @__PURE__ */ new Map();
|
|
16531
16539
|
/** Explicit catalog ownership for dedicated repair-track packet IDs. */
|
|
16532
16540
|
#repairOwnersByPacketId = /* @__PURE__ */ new Map();
|
|
16541
|
+
/** Keyframe-overlay repair PID → the source its FEC instance protects
|
|
16542
|
+
* (draft-ramadan-mmt-fec-02 7.4.1). Never an owner in
|
|
16543
|
+
* #repairOwnersByPacketId: the overlay is decoded as its own instance. */
|
|
16544
|
+
#overlayLegs = /* @__PURE__ */ new Map();
|
|
16545
|
+
/** Keyframe-overlay repair track names, which name a leg too: a catalog
|
|
16546
|
+
* may state no PID for it (learned at subscribe time). */
|
|
16547
|
+
#overlayRepairNames = /* @__PURE__ */ new Map();
|
|
16548
|
+
/** Installed overlay instances, by overlay repair PID (opt-in only). */
|
|
16549
|
+
#overlays = /* @__PURE__ */ new Map();
|
|
16550
|
+
#overlayRecoveredObservers = /* @__PURE__ */ new Set();
|
|
16551
|
+
/** Streams whose recovered sequence the base path ever re-stamped. */
|
|
16552
|
+
#restampedStreams = /* @__PURE__ */ new Set();
|
|
16553
|
+
#overlayStats = {
|
|
16554
|
+
repairDropped: 0,
|
|
16555
|
+
ownerRefused: 0,
|
|
16556
|
+
recovered: 0,
|
|
16557
|
+
restarts: 0,
|
|
16558
|
+
unsequencedDropped: 0,
|
|
16559
|
+
protectedDropped: 0
|
|
16560
|
+
};
|
|
16533
16561
|
/** Callback fired when a delta-track FEC block completes or fails. */
|
|
16534
16562
|
#onDeltaBlockCompleteCb = null;
|
|
16535
16563
|
// Debug counters
|
|
@@ -16726,6 +16754,7 @@ var MmtpRouter = class {
|
|
|
16726
16754
|
this.#fecManager.onSourceDiscontinuity = (packetId, catalogTrackName) => {
|
|
16727
16755
|
this.#parser.markFecDiscontinuity(packetId, catalogTrackName);
|
|
16728
16756
|
this.#requestFecPoke(packetId, catalogTrackName);
|
|
16757
|
+
this.resetKeyframeOverlays(packetId, catalogTrackName);
|
|
16729
16758
|
};
|
|
16730
16759
|
this.#fecManager.onBlockComplete = (sbn, packetId, catalogTrackName) => {
|
|
16731
16760
|
this.#mseAssemblerFor(packetId, catalogTrackName)?.completeBlock(sbn);
|
|
@@ -17048,13 +17077,287 @@ var MmtpRouter = class {
|
|
|
17048
17077
|
/** Replace catalog-derived repair ownership before a catalog reconfiguration. */
|
|
17049
17078
|
clearRepairPacketIdOwners() {
|
|
17050
17079
|
this.#repairOwnersByPacketId.clear();
|
|
17080
|
+
this.#overlayLegs.clear();
|
|
17081
|
+
this.#overlayRepairNames.clear();
|
|
17051
17082
|
}
|
|
17052
|
-
/** Bind one dedicated repair-track PID to its authoritative media PID.
|
|
17083
|
+
/** Bind one dedicated repair-track PID to its authoritative media PID. A
|
|
17084
|
+
* known keyframe-overlay PID is refused, and counted: its symbols belong to
|
|
17085
|
+
* the overlay instance, never to the source's base decoder (mmt-02 7.4.4). */
|
|
17053
17086
|
setRepairPacketIdOwner(repairPacketId, ownerPacketId) {
|
|
17087
|
+
if (this.#overlayLegs.has(repairPacketId)) {
|
|
17088
|
+
this.#overlayStats.ownerRefused++;
|
|
17089
|
+
return;
|
|
17090
|
+
}
|
|
17054
17091
|
const owners = this.#repairOwnersByPacketId.get(repairPacketId) ?? /* @__PURE__ */ new Set();
|
|
17055
17092
|
owners.add(ownerPacketId);
|
|
17056
17093
|
this.#repairOwnersByPacketId.set(repairPacketId, owners);
|
|
17057
17094
|
}
|
|
17095
|
+
/**
|
|
17096
|
+
* Record a keyframe-overlay repair leg (draft-ramadan-mmt-fec-02 7.4): its
|
|
17097
|
+
* PID, the source it protects, and its repair track name. Either one names
|
|
17098
|
+
* the leg, so a catalog that states no PID (learned at subscribe time) is
|
|
17099
|
+
* covered by the name. Overlay repair is then never resolved to a base
|
|
17100
|
+
* owner, on multicast or MoQ. Cleared with the catalog owners
|
|
17101
|
+
* (`clearRepairPacketIdOwners`).
|
|
17102
|
+
*/
|
|
17103
|
+
setKeyframeOverlayLeg(repairPacketId, leg) {
|
|
17104
|
+
if (leg.repairTrackName !== void 0) this.#overlayRepairNames.set(leg.repairTrackName, { ...leg });
|
|
17105
|
+
if (repairPacketId === void 0) return;
|
|
17106
|
+
this.#repairOwnersByPacketId.delete(repairPacketId);
|
|
17107
|
+
this.#overlayLegs.set(repairPacketId, { ...leg });
|
|
17108
|
+
}
|
|
17109
|
+
/** The keyframe-overlay leg registered on `repairPacketId`, if any. */
|
|
17110
|
+
keyframeOverlayLeg(repairPacketId) {
|
|
17111
|
+
return this.#overlayLegs.get(repairPacketId);
|
|
17112
|
+
}
|
|
17113
|
+
/**
|
|
17114
|
+
* Overlay counters: repair dropped for want of an installed instance, base
|
|
17115
|
+
* owners refused on an overlay PID, overlay blocks recovered, overlay
|
|
17116
|
+
* restarts, and recovered packets dropped for want of a wire sequence or
|
|
17117
|
+
* for a MoQ symbol on a protected flow (multicast-02 7.2.7).
|
|
17118
|
+
*/
|
|
17119
|
+
get keyframeOverlayStats() {
|
|
17120
|
+
return { ...this.#overlayStats };
|
|
17121
|
+
}
|
|
17122
|
+
#isOverlayRepair(repairPacketId, repairTrackName) {
|
|
17123
|
+
return this.#overlayLegs.has(repairPacketId) || repairTrackName !== void 0 && this.#overlayRepairNames.has(repairTrackName);
|
|
17124
|
+
}
|
|
17125
|
+
/**
|
|
17126
|
+
* Install the keyframe overlay decoded on `fec` for a known overlay leg
|
|
17127
|
+
* (draft-ramadan-mmt-fec-02 7.4.4: decoded independently of the base). `fec`
|
|
17128
|
+
* holds the overlay stream under `repairPacketId` and `streamName`. Replaces
|
|
17129
|
+
* an earlier install on the same leg; the returned disposer removes only
|
|
17130
|
+
* this one.
|
|
17131
|
+
*/
|
|
17132
|
+
installKeyframeOverlay(repairPacketId, fec, k, streamName) {
|
|
17133
|
+
const leg = this.#overlayLegs.get(repairPacketId);
|
|
17134
|
+
if (!leg) throw new Error(`[MmtpRouter] packetId=${repairPacketId} is no known keyframe-overlay leg`);
|
|
17135
|
+
const inst = {
|
|
17136
|
+
repairPacketId,
|
|
17137
|
+
streamName,
|
|
17138
|
+
leg: { ...leg },
|
|
17139
|
+
fec,
|
|
17140
|
+
k,
|
|
17141
|
+
offsets: /* @__PURE__ */ new Map(),
|
|
17142
|
+
moqBlocks: /* @__PURE__ */ new Set()
|
|
17143
|
+
};
|
|
17144
|
+
this.#overlays.set(repairPacketId, inst);
|
|
17145
|
+
return () => {
|
|
17146
|
+
if (this.#overlays.get(repairPacketId) === inst) this.#overlays.delete(repairPacketId);
|
|
17147
|
+
};
|
|
17148
|
+
}
|
|
17149
|
+
/**
|
|
17150
|
+
* Observe each base block whose RAP MFU fragments a keyframe-overlay
|
|
17151
|
+
* recovery supplied, with the keyframe's FT=0 and FT=1 held
|
|
17152
|
+
* (fec-multicast-00 §6.6). The block is named by the recovered packets'
|
|
17153
|
+
* restored wire sequence through the base SS_ID offset, never by an overlay
|
|
17154
|
+
* coordinate. Called synchronously from the recovered-block route.
|
|
17155
|
+
*/
|
|
17156
|
+
addKeyframeOverlayRecoveredObserver(observer) {
|
|
17157
|
+
this.#overlayRecoveredObservers.add(observer);
|
|
17158
|
+
return () => {
|
|
17159
|
+
this.#overlayRecoveredObservers.delete(observer);
|
|
17160
|
+
};
|
|
17161
|
+
}
|
|
17162
|
+
/** Whether an overlay is installed on `repairPacketId`. */
|
|
17163
|
+
hasKeyframeOverlay(repairPacketId) {
|
|
17164
|
+
return this.#overlays.has(repairPacketId);
|
|
17165
|
+
}
|
|
17166
|
+
/**
|
|
17167
|
+
* Start a new epoch of every overlay protecting `sourcePacketId` (its
|
|
17168
|
+
* source restarted): empty overlay stream, no coverage, no offsets.
|
|
17169
|
+
*/
|
|
17170
|
+
resetKeyframeOverlays(sourcePacketId, catalogTrackName) {
|
|
17171
|
+
for (const inst of this.#overlaysForSource(sourcePacketId, catalogTrackName)) this.#resetOverlay(inst);
|
|
17172
|
+
}
|
|
17173
|
+
#resetOverlay(inst) {
|
|
17174
|
+
inst.fec.resetTrack(inst.repairPacketId, inst.streamName);
|
|
17175
|
+
inst.last = void 0;
|
|
17176
|
+
inst.offsets.clear();
|
|
17177
|
+
inst.moqBlocks.clear();
|
|
17178
|
+
}
|
|
17179
|
+
#overlaysForSource(sourcePacketId, catalogTrackName) {
|
|
17180
|
+
return [...this.#overlays.values()].filter(({ leg }) => leg.sourcePacketId === sourcePacketId && (catalogTrackName === void 0 || leg.sourceCatalogTrackName === void 0 || leg.sourceCatalogTrackName === catalogTrackName));
|
|
17181
|
+
}
|
|
17182
|
+
/**
|
|
17183
|
+
* The overlay instance a repair packet belongs to, by the track it arrived
|
|
17184
|
+
* on (7.4.2: its PID or its repair track name); null for overlay repair
|
|
17185
|
+
* with no instance installed; undefined for repair of no overlay.
|
|
17186
|
+
*/
|
|
17187
|
+
#overlayRepairTarget(repairPacketId, repairTrackName) {
|
|
17188
|
+
const inst = this.#overlays.get(repairPacketId) ?? (repairTrackName === void 0 ? void 0 : [...this.#overlays.values()].find(({ leg }) => leg.repairTrackName === repairTrackName));
|
|
17189
|
+
if (inst) return inst;
|
|
17190
|
+
return this.#isOverlayRepair(repairPacketId, repairTrackName) ? null : void 0;
|
|
17191
|
+
}
|
|
17192
|
+
/** Overlay repair goes to its own instance, never to a base owner, locator
|
|
17193
|
+
* or decoder; with none installed it is dropped and counted. */
|
|
17194
|
+
#routeOverlayRepair(inst, repairPacketId, packet, header, source) {
|
|
17195
|
+
const repair = inst ? parseMoqRepairFrameWithHeader(packet, header) : null;
|
|
17196
|
+
if (!inst || !repair) {
|
|
17197
|
+
this.#overlayStats.repairDropped++;
|
|
17198
|
+
debugRouter(`[MmtpRouter] Dropping keyframe-overlay repair pid=${repairPacketId}: no overlay instance`);
|
|
17199
|
+
return;
|
|
17200
|
+
}
|
|
17201
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
17202
|
+
if (!fec.admitRepairBlockLength(pid, repair.ssbLength, repair.rsbLength, repair.rsId)) return;
|
|
17203
|
+
if (!fec.admitRepairSymbolSize(pid, repair.ssStart, repair.ssbLength, repair.rsId, repair.symbolData.byteLength)) return;
|
|
17204
|
+
const placement = fec.placeIsoRepair(pid, name, repair.ssStart, repair.ssbLength);
|
|
17205
|
+
if (placement.status !== "placed") return;
|
|
17206
|
+
const blockStart = placement.block.start;
|
|
17207
|
+
if (placement.span !== void 0) this.#replayOverlaySources(inst, placement.span.block.start, placement.span.placed);
|
|
17208
|
+
this.#replayOverlaySources(inst, blockStart, placement.placed);
|
|
17209
|
+
if (source === "moq") this.#noteOverlayMoqBlock(inst, blockStart);
|
|
17210
|
+
const alreadyCompleted = fec.isBlockCompleted(blockStart, pid, name);
|
|
17211
|
+
const recovered = fec.feedRepairSymbol(pid, repair.ssStart, repair.ssbLength, repair.rsId, repair.symbolData, name);
|
|
17212
|
+
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, blockStart, recovered);
|
|
17213
|
+
}
|
|
17214
|
+
#replayOverlaySources(inst, blockStart, placed) {
|
|
17215
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
17216
|
+
for (const { ssId, payload } of placed) {
|
|
17217
|
+
if (payload.source === "moq") this.#noteOverlayMoqBlock(inst, blockStart);
|
|
17218
|
+
if (payload.oversized) continue;
|
|
17219
|
+
const alreadyCompleted = fec.isBlockCompleted(blockStart, pid, name);
|
|
17220
|
+
const recovered = fec.feedSourceSymbol(pid, ssId, payload.symbolData, name);
|
|
17221
|
+
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, blockStart, recovered);
|
|
17222
|
+
}
|
|
17223
|
+
}
|
|
17224
|
+
/**
|
|
17225
|
+
* A source packet that carries a 0x8B01 overlay SS_ID is also a symbol of
|
|
17226
|
+
* every overlay installed for its source, at that SS_ID, with the bytes the
|
|
17227
|
+
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
17228
|
+
* included (7.1, 7.4.3).
|
|
17229
|
+
*/
|
|
17230
|
+
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source) {
|
|
17231
|
+
if (this.#overlays.size === 0) return;
|
|
17232
|
+
const ssId = keyframeOverlaySsId(header);
|
|
17233
|
+
if (ssId === void 0) return;
|
|
17234
|
+
for (const inst of this.#overlaysForSource(packetId, catalogTrackName)) {
|
|
17235
|
+
this.#noteOverlaySsId(inst, ssId, sequenceNumber);
|
|
17236
|
+
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
17237
|
+
if (identity !== null) {
|
|
17238
|
+
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
17239
|
+
inst.offsets.set(identity.mpuSequenceNumber, ssId - sequenceNumber >>> 0);
|
|
17240
|
+
if (inst.offsets.size > OVERLAY_OFFSET_MPUS) inst.offsets.delete(inst.offsets.keys().next().value);
|
|
17241
|
+
}
|
|
17242
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
17243
|
+
const symbolData = packet.subarray(0, packet.length - 4);
|
|
17244
|
+
const oversized = !fec.isoSourceFits(pid, symbolData.length);
|
|
17245
|
+
const coords = fec.locateIsoSource(pid, name, ssId);
|
|
17246
|
+
if (coords === null) {
|
|
17247
|
+
fec.holdIsoSource(pid, name, ssId, {
|
|
17248
|
+
symbolData,
|
|
17249
|
+
source,
|
|
17250
|
+
oversized,
|
|
17251
|
+
evidence: sourceSymbolEvidence(packet, header, rapFlag, sequenceNumber)
|
|
17252
|
+
});
|
|
17253
|
+
continue;
|
|
17254
|
+
}
|
|
17255
|
+
if (source === "moq") this.#noteOverlayMoqBlock(inst, coords.sbn);
|
|
17256
|
+
if (oversized) continue;
|
|
17257
|
+
const alreadyCompleted = fec.isBlockCompleted(coords.sbn, pid, name);
|
|
17258
|
+
const recovered = fec.feedSourceSymbol(pid, ssId, symbolData, name);
|
|
17259
|
+
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, coords.sbn, recovered);
|
|
17260
|
+
}
|
|
17261
|
+
}
|
|
17262
|
+
#noteOverlayMoqBlock(inst, blockStart) {
|
|
17263
|
+
inst.moqBlocks.add(blockStart);
|
|
17264
|
+
const newest = inst.last?.ssId;
|
|
17265
|
+
if (newest === void 0) return;
|
|
17266
|
+
for (const start of inst.moqBlocks) {
|
|
17267
|
+
if ((newest - start | 0) > OVERLAY_MOQ_HORIZON_BLOCKS * inst.k) inst.moqBlocks.delete(start);
|
|
17268
|
+
}
|
|
17269
|
+
}
|
|
17270
|
+
/**
|
|
17271
|
+
* An overlay SS_ID inconsistent with the source's packet_sequence_number
|
|
17272
|
+
* advance since the last one is an overlay restart (an FFmpeg muxer start,
|
|
17273
|
+
* a bridge swap that keeps psn continuous): it moved the other way, or
|
|
17274
|
+
* further than the psn advance plus K_overlay. Reordering keeps the two
|
|
17275
|
+
* consistent and is no restart.
|
|
17276
|
+
*/
|
|
17277
|
+
#noteOverlaySsId(inst, ssId, psn) {
|
|
17278
|
+
const last = inst.last;
|
|
17279
|
+
if (last !== void 0) {
|
|
17280
|
+
const dSs = ssId - last.ssId | 0;
|
|
17281
|
+
const dPsn = psn - last.psn | 0;
|
|
17282
|
+
const consistent = dSs === 0 ? dPsn === 0 : Math.sign(dSs) === Math.sign(dPsn) && Math.abs(dSs) <= Math.abs(dPsn) + inst.k;
|
|
17283
|
+
if (!consistent) {
|
|
17284
|
+
this.#overlayStats.restarts++;
|
|
17285
|
+
this.#resetOverlay(inst);
|
|
17286
|
+
} else if (dPsn < 0) {
|
|
17287
|
+
return;
|
|
17288
|
+
}
|
|
17289
|
+
}
|
|
17290
|
+
inst.last = { ssId, psn };
|
|
17291
|
+
}
|
|
17292
|
+
/**
|
|
17293
|
+
* Route an overlay-recovered block's packets as source media of the
|
|
17294
|
+
* overlay's source (7.2: an overlay coordinate yields no base SS_ID). No
|
|
17295
|
+
* base-SS_ID derivation runs: no release-gate anchor, no parse ledger, no
|
|
17296
|
+
* base re-stamp, no block advance. Each packet's wire sequence is restored
|
|
17297
|
+
* from its keyframe's own offset; without one, a stream the base never
|
|
17298
|
+
* re-stamped keeps the header's sequence and any other drops the packet.
|
|
17299
|
+
*/
|
|
17300
|
+
#routeOverlayRecovered(inst, blockStart, recovered) {
|
|
17301
|
+
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
17302
|
+
fec.markBlockCompleted(blockStart, pid, name);
|
|
17303
|
+
const sourcePacketId = inst.leg.sourcePacketId;
|
|
17304
|
+
const sourceName = inst.leg.sourceCatalogTrackName;
|
|
17305
|
+
if (this.#classifyPacketId(sourcePacketId) === null) return;
|
|
17306
|
+
const receiver = this.#provenanceFor(sourcePacketId, sourceName);
|
|
17307
|
+
if (receiver !== void 0 && inst.moqBlocks.has(blockStart)) {
|
|
17308
|
+
this.#overlayStats.protectedDropped++;
|
|
17309
|
+
return;
|
|
17310
|
+
}
|
|
17311
|
+
const blockProfile = fec.blockProfileForPacket(pid);
|
|
17312
|
+
const symbolSize = fec.symbolSizeForPacket(pid);
|
|
17313
|
+
if (blockProfile === void 0 || symbolSize === void 0 || !(symbolSize > 0)) return;
|
|
17314
|
+
const result = processFecBlock(recovered, { symbolSize, ownerPacketId: sourcePacketId, blockProfile });
|
|
17315
|
+
const streamKey = this.#mediaStreamKey(sourcePacketId, sourceName);
|
|
17316
|
+
const restamped = this.#restampedStreams.has(streamKey);
|
|
17317
|
+
const restored = /* @__PURE__ */ new Set();
|
|
17318
|
+
const symbols = result.symbols.flatMap((sym) => {
|
|
17319
|
+
const offset = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
17320
|
+
if (offset !== void 0) {
|
|
17321
|
+
const packetSequenceNumber = serial32Add(blockStart, sym.index) - offset >>> 0;
|
|
17322
|
+
restored.add(packetSequenceNumber);
|
|
17323
|
+
return [{ ...sym, packetSequenceNumber }];
|
|
17324
|
+
}
|
|
17325
|
+
if (!restamped) return [sym];
|
|
17326
|
+
this.#overlayStats.unsequencedDropped++;
|
|
17327
|
+
return [];
|
|
17328
|
+
});
|
|
17329
|
+
this.#overlayStats.recovered++;
|
|
17330
|
+
this.#routeProcessedFecSymbols(blockStart, { ...result, symbols }, sourcePacketId, sourceName, {
|
|
17331
|
+
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
17332
|
+
});
|
|
17333
|
+
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
17334
|
+
}
|
|
17335
|
+
/** fec-multicast-00 §6.6: report each base block whose RAP MFU fragments
|
|
17336
|
+
* the overlay restored, when the keyframe's FT=0 and FT=1 are held too. A
|
|
17337
|
+
* packet whose wire sequence was not restored names no base block. */
|
|
17338
|
+
#noteOverlayRecoveredRaps(packetId, catalogTrackName, streamKey, symbols) {
|
|
17339
|
+
const ssIdSeqOffset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
17340
|
+
if (ssIdSeqOffset === void 0 || this.#overlayRecoveredObservers.size === 0) return;
|
|
17341
|
+
const withMeta = (type) => new Set(symbols.filter((sym) => sym.fragmentType === type).map((sym) => sym.fragment.mpuSequenceNumber));
|
|
17342
|
+
const ft0 = withMeta(0);
|
|
17343
|
+
const ft1 = withMeta(1);
|
|
17344
|
+
const rapMfu = (fi) => new Set(symbols.filter((sym) => sym.fragmentType === 2 && sym.fragment.rapFlag && sym.fragment.fragmentationIndicator === fi).map((sym) => sym.fragment.mpuSequenceNumber));
|
|
17345
|
+
const single = rapMfu(0);
|
|
17346
|
+
const first = rapMfu(1);
|
|
17347
|
+
const last = rapMfu(3);
|
|
17348
|
+
const blocks = /* @__PURE__ */ new Set();
|
|
17349
|
+
for (const sym of symbols) {
|
|
17350
|
+
const mpu = sym.fragment.mpuSequenceNumber;
|
|
17351
|
+
if (sym.fragmentType !== 2 || !sym.fragment.rapFlag || !ft0.has(mpu) || !ft1.has(mpu)) continue;
|
|
17352
|
+
if (!single.has(mpu) && !(first.has(mpu) && last.has(mpu))) continue;
|
|
17353
|
+
const ssId = sym.packetSequenceNumber + ssIdSeqOffset >>> 0;
|
|
17354
|
+
const sbn = this.#fecManager?.locateIsoSource?.(packetId, catalogTrackName, ssId)?.sbn;
|
|
17355
|
+
if (sbn !== void 0) blocks.add(sbn);
|
|
17356
|
+
}
|
|
17357
|
+
for (const sbn of blocks) {
|
|
17358
|
+
for (const observer of this.#overlayRecoveredObservers) observer(packetId, catalogTrackName, sbn);
|
|
17359
|
+
}
|
|
17360
|
+
}
|
|
17058
17361
|
/**
|
|
17059
17362
|
* Register callback fired when a delta-track FEC block completes or fails.
|
|
17060
17363
|
*
|
|
@@ -17478,6 +17781,11 @@ var MmtpRouter = class {
|
|
|
17478
17781
|
return;
|
|
17479
17782
|
}
|
|
17480
17783
|
const repairPacketId = header.packetId;
|
|
17784
|
+
const overlay = this.#overlayRepairTarget(repairPacketId, repairTrackName);
|
|
17785
|
+
if (overlay !== void 0) {
|
|
17786
|
+
this.#routeOverlayRepair(overlay, repairPacketId, packet, header, "moq");
|
|
17787
|
+
return;
|
|
17788
|
+
}
|
|
17481
17789
|
const hintedOwner = ownerPacketIdHint != null && this.#classifyPacketId(ownerPacketIdHint) !== null ? ownerPacketIdHint : null;
|
|
17482
17790
|
const ownerPacketId = hintedOwner ?? (this.#classifyPacketId(repairPacketId) !== null ? repairPacketId : null);
|
|
17483
17791
|
if (ownerPacketId === null) {
|
|
@@ -17767,6 +18075,8 @@ var MmtpRouter = class {
|
|
|
17767
18075
|
}
|
|
17768
18076
|
}
|
|
17769
18077
|
this.#parser.resetTrack(packetId, catalogTrackName);
|
|
18078
|
+
this.resetKeyframeOverlays(packetId, catalogTrackName);
|
|
18079
|
+
this.#restampedStreams.delete(this.#mediaStreamKey(packetId, catalogTrackName));
|
|
17770
18080
|
const assistPrefix = assistKey(packetId, catalogTrackName, 0).slice(0, -1);
|
|
17771
18081
|
for (const key of this.#assistBlocks.keys()) if (key.startsWith(assistPrefix)) this.#assistBlocks.delete(key);
|
|
17772
18082
|
for (const key of this.#assistFailures.keys()) if (key.startsWith(assistPrefix)) this.#assistFailures.delete(key);
|
|
@@ -17853,6 +18163,17 @@ var MmtpRouter = class {
|
|
|
17853
18163
|
this.#packetIdRoles.delete(packetId);
|
|
17854
18164
|
this.#catalogTrackNamesByPacketId.delete(packetId);
|
|
17855
18165
|
this.#repairOwnersByPacketId.delete(packetId);
|
|
18166
|
+
this.#overlayLegs.delete(packetId);
|
|
18167
|
+
for (const [repairPacketId, leg] of [...this.#overlayLegs]) {
|
|
18168
|
+
if (leg.sourcePacketId === packetId) this.#overlayLegs.delete(repairPacketId);
|
|
18169
|
+
}
|
|
18170
|
+
for (const [name, leg] of [...this.#overlayRepairNames]) {
|
|
18171
|
+
if (leg.sourcePacketId === packetId) this.#overlayRepairNames.delete(name);
|
|
18172
|
+
}
|
|
18173
|
+
this.#overlays.delete(packetId);
|
|
18174
|
+
for (const [repairPacketId, inst] of [...this.#overlays]) {
|
|
18175
|
+
if (inst.leg.sourcePacketId === packetId) this.#overlays.delete(repairPacketId);
|
|
18176
|
+
}
|
|
17856
18177
|
delete this.#pidCounts[packetId];
|
|
17857
18178
|
delete this.#repairPidCounts[packetId];
|
|
17858
18179
|
delete this.#moqRepairPidCounts[packetId];
|
|
@@ -18091,6 +18412,9 @@ var MmtpRouter = class {
|
|
|
18091
18412
|
this.#parserDiagnostics.clear();
|
|
18092
18413
|
this.#catalogTrackNamesByPacketId.clear();
|
|
18093
18414
|
this.#repairOwnersByPacketId.clear();
|
|
18415
|
+
this.#overlayLegs.clear();
|
|
18416
|
+
this.#overlayRepairNames.clear();
|
|
18417
|
+
this.#overlays.clear();
|
|
18094
18418
|
for (const { receiver } of this.#provenance.values()) receiver.release();
|
|
18095
18419
|
this.#provenance.clear();
|
|
18096
18420
|
this.#provenanceStreamFlow.clear();
|
|
@@ -18446,6 +18770,11 @@ var MmtpRouter = class {
|
|
|
18446
18770
|
}
|
|
18447
18771
|
}
|
|
18448
18772
|
if (info.isRepair) {
|
|
18773
|
+
const overlay = this.#overlayRepairTarget(info.packetId, catalogTrackNameForPacket(ingress, info.packetId));
|
|
18774
|
+
if (overlay !== void 0) {
|
|
18775
|
+
this.#routeOverlayRepair(overlay, info.packetId, packet, parsed.header, source);
|
|
18776
|
+
return;
|
|
18777
|
+
}
|
|
18449
18778
|
const ownerPacketId = this.#resolveRepairOwnerPacketId(info.packetId, ingress);
|
|
18450
18779
|
if (ownerPacketId != null && this.#fecManager && hasAmbiguousCatalogTrackName(ingress, ownerPacketId)) return;
|
|
18451
18780
|
const repair = this.#readRepairObject(
|
|
@@ -18652,6 +18981,17 @@ var MmtpRouter = class {
|
|
|
18652
18981
|
});
|
|
18653
18982
|
}
|
|
18654
18983
|
}
|
|
18984
|
+
if (!overlapCopy && this.#classifyPacketId(info.packetId) !== null) {
|
|
18985
|
+
this.#feedOverlaySource(
|
|
18986
|
+
info.packetId,
|
|
18987
|
+
catalogTrackName,
|
|
18988
|
+
packet,
|
|
18989
|
+
parsed.header,
|
|
18990
|
+
info.sequenceNumber,
|
|
18991
|
+
info.rapFlag,
|
|
18992
|
+
source
|
|
18993
|
+
);
|
|
18994
|
+
}
|
|
18655
18995
|
}
|
|
18656
18996
|
}
|
|
18657
18997
|
/**
|
|
@@ -19105,12 +19445,12 @@ var MmtpRouter = class {
|
|
|
19105
19445
|
}
|
|
19106
19446
|
return match;
|
|
19107
19447
|
}
|
|
19108
|
-
#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName) {
|
|
19448
|
+
#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, overlay) {
|
|
19109
19449
|
if (ownerPacketId == null) return;
|
|
19110
19450
|
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
19111
|
-
const
|
|
19112
|
-
|
|
19113
|
-
);
|
|
19451
|
+
const fecSbnOf = (index) => overlay ? {} : { fecSbn: this.#fecAnchorForRecovered(blockId, index) };
|
|
19452
|
+
const streamKey = this.#mediaStreamKey(ownerPacketId, catalogTrackName);
|
|
19453
|
+
const ssIdSeqOffset = overlay ? void 0 : this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
19114
19454
|
if (ssIdSeqOffset !== void 0) {
|
|
19115
19455
|
result = {
|
|
19116
19456
|
...result,
|
|
@@ -19118,12 +19458,13 @@ var MmtpRouter = class {
|
|
|
19118
19458
|
const wire = this.#fecAnchorForRecovered(blockId, sym.index) - ssIdSeqOffset >>> 0;
|
|
19119
19459
|
if (wire === sym.packetSequenceNumber) return sym;
|
|
19120
19460
|
this.#restampedRecoveredSeqs++;
|
|
19461
|
+
this.#restampedStreams.add(streamKey);
|
|
19121
19462
|
return { ...sym, packetSequenceNumber: wire };
|
|
19122
19463
|
})
|
|
19123
19464
|
};
|
|
19124
19465
|
}
|
|
19125
|
-
this.#stream(ownerPacketId, catalogTrackName).duplicates.advance(blockId);
|
|
19126
|
-
const deliveryProvenance = this.#fecManager?.blockDeliveryProvenance?.(
|
|
19466
|
+
if (!overlay) this.#stream(ownerPacketId, catalogTrackName).duplicates.advance(blockId);
|
|
19467
|
+
const deliveryProvenance = overlay?.deliveryProvenance ?? this.#fecManager?.blockDeliveryProvenance?.(
|
|
19127
19468
|
blockId,
|
|
19128
19469
|
ownerPacketId,
|
|
19129
19470
|
catalogTrackName
|
|
@@ -19201,7 +19542,7 @@ var MmtpRouter = class {
|
|
|
19201
19542
|
rapFlag: sym.fragment.rapFlag,
|
|
19202
19543
|
recovered: true,
|
|
19203
19544
|
deliveryProvenance,
|
|
19204
|
-
|
|
19545
|
+
...fecSbnOf(sym.index),
|
|
19205
19546
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
19206
19547
|
});
|
|
19207
19548
|
continue;
|
|
@@ -19313,7 +19654,7 @@ var MmtpRouter = class {
|
|
|
19313
19654
|
rapFlag: assembled.rapFlag,
|
|
19314
19655
|
recovered: true,
|
|
19315
19656
|
...assembled.deliveryProvenance === void 0 ? {} : { deliveryProvenance: assembled.deliveryProvenance },
|
|
19316
|
-
|
|
19657
|
+
...fecSbnOf(sym.index),
|
|
19317
19658
|
...recoveredCatalogTrackName === void 0 ? {} : { catalogTrackName: recoveredCatalogTrackName }
|
|
19318
19659
|
});
|
|
19319
19660
|
continue;
|
|
@@ -19450,7 +19791,7 @@ var MmtpRouter = class {
|
|
|
19450
19791
|
rapFlag: sym.fragment.rapFlag,
|
|
19451
19792
|
recovered: true,
|
|
19452
19793
|
deliveryProvenance,
|
|
19453
|
-
|
|
19794
|
+
...fecSbnOf(sym.index),
|
|
19454
19795
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
19455
19796
|
});
|
|
19456
19797
|
} else {
|
|
@@ -19529,7 +19870,7 @@ var MmtpRouter = class {
|
|
|
19529
19870
|
rapFlag: assembled.rapFlag,
|
|
19530
19871
|
recovered: true,
|
|
19531
19872
|
deliveryProvenance,
|
|
19532
|
-
|
|
19873
|
+
...fecSbnOf(sym.index),
|
|
19533
19874
|
...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName }
|
|
19534
19875
|
});
|
|
19535
19876
|
}
|
|
@@ -19564,7 +19905,7 @@ var MmtpRouter = class {
|
|
|
19564
19905
|
...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
|
|
19565
19906
|
recovered: true,
|
|
19566
19907
|
deliveryProvenance,
|
|
19567
|
-
|
|
19908
|
+
...fecSbnOf(sym.index),
|
|
19568
19909
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
19569
19910
|
});
|
|
19570
19911
|
continue;
|
|
@@ -21896,7 +22237,7 @@ function newBlockPolicyStats(packetId, catalogTrackName, epoch) {
|
|
|
21896
22237
|
repairsSkippedRangeNoAnchor: 0,
|
|
21897
22238
|
repairsSkippedGroupCannotComplete: 0,
|
|
21898
22239
|
repairsSkippedMootRap: 0,
|
|
21899
|
-
repairStops: { preempted: 0, reprioritized: 0, expired: 0, resolved: 0, reset: 0, disposed: 0, moot: 0 },
|
|
22240
|
+
repairStops: { preempted: 0, reprioritized: 0, expired: 0, resolved: 0, reset: 0, disposed: 0, moot: 0, "overlay-recovered": 0 },
|
|
21900
22241
|
lastRepairSkip: null,
|
|
21901
22242
|
keyframeRepairFetchesSent: 0,
|
|
21902
22243
|
rangeRepairFetchesSent: 0,
|
|
@@ -22229,9 +22570,7 @@ function repairTrackNamesBySource(tracks) {
|
|
|
22229
22570
|
}
|
|
22230
22571
|
function boundRepairNames(binding) {
|
|
22231
22572
|
if (binding?.status !== "ok") return [];
|
|
22232
|
-
|
|
22233
|
-
if (binding.overlay) names.push(binding.overlay.repairTrackName);
|
|
22234
|
-
return names;
|
|
22573
|
+
return binding.layers.map((layer) => layer.repairTrackName);
|
|
22235
22574
|
}
|
|
22236
22575
|
var MulticastManager = class {
|
|
22237
22576
|
#fec;
|
|
@@ -22245,6 +22584,10 @@ var MulticastManager = class {
|
|
|
22245
22584
|
/** Live joinRepairEndpoints() releases → the repair owners they registered,
|
|
22246
22585
|
* re-applied after configureMultiTrack() clears catalog-derived owners. */
|
|
22247
22586
|
#repairReleases = /* @__PURE__ */ new Map();
|
|
22587
|
+
/** Each source's catalog keyframe overlay, from its `ok` binding. */
|
|
22588
|
+
#overlayBindings = /* @__PURE__ */ new Map();
|
|
22589
|
+
/** Installed overlay decoders (`setKeyframeOverlayDecoder`), by source. */
|
|
22590
|
+
#overlayInstalls = /* @__PURE__ */ new Map();
|
|
22248
22591
|
#reportedFecDiagnosticsFailure = false;
|
|
22249
22592
|
// ── Stream-global FEC timing reduction ──
|
|
22250
22593
|
//
|
|
@@ -22485,6 +22828,63 @@ var MulticastManager = class {
|
|
|
22485
22828
|
this.#configureTrackMapper(packetId, fecConfig);
|
|
22486
22829
|
this.#router.setPacketIdType(packetId, trackType);
|
|
22487
22830
|
}
|
|
22831
|
+
/**
|
|
22832
|
+
* Decode the keyframe overlay of `sourcePacketId` (draft-ramadan-mmt-fec-02
|
|
22833
|
+
* 7.4) with `decoder`, a decoder of the same kind `setFecDecoder` takes.
|
|
22834
|
+
* Opt-in: nothing else installs one, so a receiver that never calls this
|
|
22835
|
+
* holds no overlay state and drops overlay repair. K_overlay, T, scheme
|
|
22836
|
+
* and block profile come from the source's `ok` catalog binding, which
|
|
22837
|
+
* must declare an overlay (`configureMultiTrack` first). The overlay is
|
|
22838
|
+
* decoded on its own FEC state, apart from the base: no repair FETCH, no
|
|
22839
|
+
* repair lifecycle event and no base statistic for its blocks. A second
|
|
22840
|
+
* install on the same source replaces the first; the returned disposer
|
|
22841
|
+
* removes only the install it created. Call it on releasing the overlay
|
|
22842
|
+
* join.
|
|
22843
|
+
*/
|
|
22844
|
+
setKeyframeOverlayDecoder(sourcePacketId, decoder, catalogTrackName) {
|
|
22845
|
+
const binding = this.#overlayBindings.get(sourcePacketId);
|
|
22846
|
+
if (!binding || catalogTrackName !== void 0 && binding.sourceCatalogTrackName !== catalogTrackName) {
|
|
22847
|
+
throw new Error(
|
|
22848
|
+
`[MulticastManager] packetId=${sourcePacketId} has no catalog keyframe overlay to decode`
|
|
22849
|
+
);
|
|
22850
|
+
}
|
|
22851
|
+
this.#overlayInstalls.get(sourcePacketId)?.dispose();
|
|
22852
|
+
let fec;
|
|
22853
|
+
let uninstall;
|
|
22854
|
+
const attach = () => {
|
|
22855
|
+
fec = new FECManager();
|
|
22856
|
+
fec.setFecDecoder(binding.repairPacketId, decoder, binding.k, {
|
|
22857
|
+
blockProfile: binding.blockProfile,
|
|
22858
|
+
k: binding.k,
|
|
22859
|
+
symbolSize: binding.symbolSize,
|
|
22860
|
+
trackKind: "video"
|
|
22861
|
+
}, binding.repairTrackName);
|
|
22862
|
+
uninstall = this.#router.installKeyframeOverlay(
|
|
22863
|
+
binding.repairPacketId,
|
|
22864
|
+
fec,
|
|
22865
|
+
binding.k,
|
|
22866
|
+
binding.repairTrackName
|
|
22867
|
+
);
|
|
22868
|
+
};
|
|
22869
|
+
const detach = () => {
|
|
22870
|
+
uninstall();
|
|
22871
|
+
fec.releasePacket(binding.repairPacketId);
|
|
22872
|
+
};
|
|
22873
|
+
attach();
|
|
22874
|
+
const install = {
|
|
22875
|
+
reattach: () => {
|
|
22876
|
+
detach();
|
|
22877
|
+
attach();
|
|
22878
|
+
},
|
|
22879
|
+
dispose: () => {
|
|
22880
|
+
if (this.#overlayInstalls.get(sourcePacketId) !== install) return;
|
|
22881
|
+
this.#overlayInstalls.delete(sourcePacketId);
|
|
22882
|
+
detach();
|
|
22883
|
+
}
|
|
22884
|
+
};
|
|
22885
|
+
this.#overlayInstalls.set(sourcePacketId, install);
|
|
22886
|
+
return install.dispose;
|
|
22887
|
+
}
|
|
22488
22888
|
/**
|
|
22489
22889
|
* Register a Worker-backed FEC decoder for a specific packetId (track).
|
|
22490
22890
|
* Worker-backed tracks decode asynchronously — recovered frames arrive via
|
|
@@ -22997,7 +23397,11 @@ var MulticastManager = class {
|
|
|
22997
23397
|
console.warn("[MulticastManager] repair group release failed", error);
|
|
22998
23398
|
}
|
|
22999
23399
|
};
|
|
23000
|
-
const repairOwners = endpoints.flatMap((endpoint) => endpoint.repairOwners)
|
|
23400
|
+
const repairOwners = endpoints.flatMap((endpoint) => endpoint.repairOwners.map((owner) => owner.instance !== "keyframe" ? owner : {
|
|
23401
|
+
...owner,
|
|
23402
|
+
ownerTrackName: endpoint.tracks.find((t) => t.packetId === owner.ownerPacketId)?.name,
|
|
23403
|
+
repairTrackName: endpoint.tracks.find((t) => t.packetId === owner.packetId)?.name
|
|
23404
|
+
}));
|
|
23001
23405
|
this.#applyRepairOwners(repairOwners);
|
|
23002
23406
|
for (const endpoint of endpoints) {
|
|
23003
23407
|
const ingress = {
|
|
@@ -23045,9 +23449,17 @@ var MulticastManager = class {
|
|
|
23045
23449
|
return release;
|
|
23046
23450
|
}
|
|
23047
23451
|
#applyRepairOwners(owners) {
|
|
23048
|
-
for (const { packetId, ownerPacketId } of owners) {
|
|
23452
|
+
for (const { packetId, ownerPacketId, instance, ownerTrackName, repairTrackName } of owners) {
|
|
23049
23453
|
this.#router.setPacketIdRole(packetId, "repair");
|
|
23050
|
-
|
|
23454
|
+
if (instance === "keyframe") {
|
|
23455
|
+
this.#router.setKeyframeOverlayLeg(packetId, {
|
|
23456
|
+
sourcePacketId: ownerPacketId,
|
|
23457
|
+
...ownerTrackName === void 0 ? {} : { sourceCatalogTrackName: ownerTrackName },
|
|
23458
|
+
...repairTrackName === void 0 ? {} : { repairTrackName }
|
|
23459
|
+
});
|
|
23460
|
+
} else {
|
|
23461
|
+
this.#router.setRepairPacketIdOwner(packetId, ownerPacketId);
|
|
23462
|
+
}
|
|
23051
23463
|
}
|
|
23052
23464
|
}
|
|
23053
23465
|
// ---------------------------------------------------------------------------
|
|
@@ -23311,8 +23723,8 @@ var MulticastManager = class {
|
|
|
23311
23723
|
this.#fec.setMulticastSymbolDelivery(delivery);
|
|
23312
23724
|
}
|
|
23313
23725
|
/** Process a repair packet from MoQ (reliable QUIC). */
|
|
23314
|
-
processRepairFromMoQ(packet, ownerPacketId, catalogTrackName) {
|
|
23315
|
-
this.#router.feedRepairPacket(packet, ownerPacketId, catalogTrackName);
|
|
23726
|
+
processRepairFromMoQ(packet, ownerPacketId, catalogTrackName, repairTrackName) {
|
|
23727
|
+
this.#router.feedRepairPacket(packet, ownerPacketId, catalogTrackName, repairTrackName);
|
|
23316
23728
|
}
|
|
23317
23729
|
/** Route a Worker-recovered FEC block through the MMTP parser + dedup pipeline. */
|
|
23318
23730
|
routeRecoveredBlock(blockId, data, packetId, catalogTrackName) {
|
|
@@ -23364,7 +23776,10 @@ var MulticastManager = class {
|
|
|
23364
23776
|
const ownedRepairNames = /* @__PURE__ */ new Set();
|
|
23365
23777
|
const claimed = /* @__PURE__ */ new Map();
|
|
23366
23778
|
for (const binding of bindings.values()) {
|
|
23367
|
-
|
|
23779
|
+
const overlay = binding.status === "ok" ? binding.overlay?.repairTrackName : void 0;
|
|
23780
|
+
for (const name of [...boundRepairNames(binding), ...overlay === void 0 ? [] : [overlay]]) {
|
|
23781
|
+
claimed.set(name, (claimed.get(name) ?? 0) + 1);
|
|
23782
|
+
}
|
|
23368
23783
|
}
|
|
23369
23784
|
for (const [name, count] of claimed) if (count === 1) ownedRepairNames.add(name);
|
|
23370
23785
|
const repairOwnerGroup = /* @__PURE__ */ new Map();
|
|
@@ -23467,6 +23882,7 @@ var MulticastManager = class {
|
|
|
23467
23882
|
const catalogTracksByName = new Map(
|
|
23468
23883
|
catalog.tracks.map((track) => [track.name, track])
|
|
23469
23884
|
);
|
|
23885
|
+
const overlayBindings = /* @__PURE__ */ new Map();
|
|
23470
23886
|
for (let i = 0; i < catalog.tracks.length; i++) {
|
|
23471
23887
|
const track = catalog.tracks[i];
|
|
23472
23888
|
const role = resolvedRoles[i];
|
|
@@ -23495,6 +23911,29 @@ var MulticastManager = class {
|
|
|
23495
23911
|
this.#router.setRepairPacketIdOwner(repairTrack.packetId, track.packetId);
|
|
23496
23912
|
}
|
|
23497
23913
|
}
|
|
23914
|
+
const overlayName = binding.overlay?.repairTrackName;
|
|
23915
|
+
const overlayPacketId = overlayName === void 0 ? void 0 : catalogTracksByName.get(overlayName)?.packetId;
|
|
23916
|
+
if (overlayName !== void 0 && ownedRepairNames.has(overlayName)) {
|
|
23917
|
+
this.#router.setKeyframeOverlayLeg(overlayPacketId, {
|
|
23918
|
+
sourcePacketId: track.packetId,
|
|
23919
|
+
sourceCatalogTrackName: track.name,
|
|
23920
|
+
repairTrackName: overlayName
|
|
23921
|
+
});
|
|
23922
|
+
}
|
|
23923
|
+
if (overlayName !== void 0 && overlayPacketId !== void 0 && ownedRepairNames.has(overlayName)) {
|
|
23924
|
+
const geometry2 = binding.overlay.geometry;
|
|
23925
|
+
overlayBindings.set(track.packetId, {
|
|
23926
|
+
repairPacketId: overlayPacketId,
|
|
23927
|
+
repairTrackName: overlayName,
|
|
23928
|
+
sourceCatalogTrackName: track.name,
|
|
23929
|
+
k: binding.overlay.sourceSymbols,
|
|
23930
|
+
symbolSize: fecCfg.symbolSize,
|
|
23931
|
+
blockProfile: parseFecBlockProfile(
|
|
23932
|
+
geometry2.blockProfile,
|
|
23933
|
+
`tracks[${JSON.stringify(track.name)}].fec keyframe overlay blockProfile`
|
|
23934
|
+
)
|
|
23935
|
+
});
|
|
23936
|
+
}
|
|
23498
23937
|
}
|
|
23499
23938
|
const wasmDecoder = fecDecoders.get(i);
|
|
23500
23939
|
if (track.packetId !== void 0 && wasmDecoder !== void 0) {
|
|
@@ -23528,6 +23967,16 @@ var MulticastManager = class {
|
|
|
23528
23967
|
}
|
|
23529
23968
|
}
|
|
23530
23969
|
}
|
|
23970
|
+
for (const [sourcePacketId, install] of [...this.#overlayInstalls]) {
|
|
23971
|
+
const before = this.#overlayBindings.get(sourcePacketId);
|
|
23972
|
+
const after = overlayBindings.get(sourcePacketId);
|
|
23973
|
+
const same = before !== void 0 && after !== void 0 && Object.keys(after).every((key) => after[key] === before[key]);
|
|
23974
|
+
if (!same) install.dispose();
|
|
23975
|
+
else if (this.#router.hasKeyframeOverlay(after.repairPacketId)) this.#router.resetKeyframeOverlays(sourcePacketId);
|
|
23976
|
+
else install.reattach();
|
|
23977
|
+
}
|
|
23978
|
+
this.#overlayBindings.clear();
|
|
23979
|
+
for (const [sourcePacketId, binding] of overlayBindings) this.#overlayBindings.set(sourcePacketId, binding);
|
|
23531
23980
|
}
|
|
23532
23981
|
/**
|
|
23533
23982
|
* Why `configureMultiTrack` consumes the named catalog track without FEC
|
|
@@ -23699,6 +24148,10 @@ var MulticastManager = class {
|
|
|
23699
24148
|
addRapCompleteObserver(observer) {
|
|
23700
24149
|
return this.#router.addFecRapObserver(observer);
|
|
23701
24150
|
}
|
|
24151
|
+
/** Observe each base block whose RAP the keyframe overlay recovered (fec-multicast-00 §6.6). */
|
|
24152
|
+
addKeyframeOverlayRecoveredObserver(observer) {
|
|
24153
|
+
return this.#router.addKeyframeOverlayRecoveredObserver(observer);
|
|
24154
|
+
}
|
|
23702
24155
|
/** Catalog/reset epoch of a packet's streams; gap decisions carry it. */
|
|
23703
24156
|
epochFor(packetId) {
|
|
23704
24157
|
return this.#fec.epochFor(packetId);
|