@blockcast/mmt-container 2.2.0-main.75b39a2cdddf → 2.2.0-main.7e8a9d13c01c
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 +634 -129
- package/dist/browser-runtime.js.map +3 -3
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +37 -11
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +181 -52
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.d.ts +39 -13
- package/dist/iso-block-locator.d.ts.map +1 -1
- package/dist/iso-block-locator.js +80 -25
- package/dist/iso-block-locator.js.map +2 -2
- package/dist/media-router.js.map +1 -1
- package/dist/mmtp-container-parser.d.ts +21 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +114 -11
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +113 -12
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +114 -11
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +18 -9
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +254 -44
- package/dist/mmtp-router.js.map +2 -2
- package/dist/multicast-manager.d.ts +13 -3
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +455 -103
- package/dist/multicast-manager.js.map +2 -2
- package/dist/no-modules.js +114 -11
- package/dist/no-modules.js.map +2 -2
- package/dist/repair-fetch.d.ts +36 -8
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +285 -26
- package/dist/repair-fetch.js.map +3 -3
- package/dist/track-session.d.ts.map +1 -1
- package/dist/track-session.js +1 -0
- package/dist/track-session.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +114 -11
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +5 -5
- package/src/fec-manager.ts +156 -26
- package/src/index.ts +2 -0
- package/src/iso-block-locator.ts +114 -31
- package/src/mmtp-container-parser.ts +181 -10
- package/src/mmtp-router.ts +231 -58
- package/src/multicast-manager.ts +44 -6
- package/src/repair-fetch.ts +363 -49
- package/src/track-session.ts +3 -0
package/dist/mmtp-router.js
CHANGED
|
@@ -8023,6 +8023,9 @@ function authoredGap(before, after) {
|
|
|
8023
8023
|
function ssIdAfter(a, b) {
|
|
8024
8024
|
return (a - b | 0) > 0;
|
|
8025
8025
|
}
|
|
8026
|
+
function holdsRebuilt(stream) {
|
|
8027
|
+
return stream.buf.some(({ meta }) => meta.fecSbnFirst !== void 0);
|
|
8028
|
+
}
|
|
8026
8029
|
var FecReleaseSequencer = class {
|
|
8027
8030
|
#streams = /* @__PURE__ */ new Map();
|
|
8028
8031
|
#gate;
|
|
@@ -8085,6 +8088,7 @@ var FecReleaseSequencer = class {
|
|
|
8085
8088
|
stream.lastSbn = void 0;
|
|
8086
8089
|
stream.discontinuity = false;
|
|
8087
8090
|
stream.rapSsId = void 0;
|
|
8091
|
+
stream.sourced = false;
|
|
8088
8092
|
}
|
|
8089
8093
|
if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
|
|
8090
8094
|
this.#recordStat("droppedStale");
|
|
@@ -8111,6 +8115,7 @@ var FecReleaseSequencer = class {
|
|
|
8111
8115
|
if (compareFrameOrder(stream.buf[mid], frame) < 0) lo = mid + 1;
|
|
8112
8116
|
else hi = mid;
|
|
8113
8117
|
}
|
|
8118
|
+
if (stream.buf[lo]?.passed === true) frame.passed = true;
|
|
8114
8119
|
stream.buf.splice(lo, 0, frame);
|
|
8115
8120
|
this.#drainUnlessDeferred(stream);
|
|
8116
8121
|
}
|
|
@@ -8146,6 +8151,41 @@ var FecReleaseSequencer = class {
|
|
|
8146
8151
|
}
|
|
8147
8152
|
return false;
|
|
8148
8153
|
}
|
|
8154
|
+
/**
|
|
8155
|
+
* These streams' sink is being replaced by one that has seen no source
|
|
8156
|
+
* frame yet. First, from a stream that holds a frame the overlay rebuilt,
|
|
8157
|
+
* release to the sink being replaced the frames the unanchored path had
|
|
8158
|
+
* already released to it (#cursorPassed): there they waited in its queue,
|
|
8159
|
+
* which its replacement flushes. A stream that holds none is not drained:
|
|
8160
|
+
* it holds nothing the anchor kept back, and a drain would still force out
|
|
8161
|
+
* a frame held past the hold limit, which before the anchor waited for the
|
|
8162
|
+
* next push. Then treat the streams as unsourced until a source frame is
|
|
8163
|
+
* released.
|
|
8164
|
+
*/
|
|
8165
|
+
unsource(packetId, catalogTrackName) {
|
|
8166
|
+
for (const stream of this.#streams.values()) {
|
|
8167
|
+
if (stream.packetId === packetId && (catalogTrackName === void 0 || stream.catalogTrackName === catalogTrackName)) {
|
|
8168
|
+
if (stream.draining === 0 && holdsRebuilt(stream)) {
|
|
8169
|
+
if (this.#deferDepth > 0) this.#deferredStreams.add(stream);
|
|
8170
|
+
else this.#drain(stream, true);
|
|
8171
|
+
}
|
|
8172
|
+
stream.sourced = false;
|
|
8173
|
+
}
|
|
8174
|
+
}
|
|
8175
|
+
}
|
|
8176
|
+
/**
|
|
8177
|
+
* Whether a stream of `packetId` holds a frame the keyframe overlay rebuilt
|
|
8178
|
+
* (FrameMeta.fecSbnFirst). Without `catalogTrackName`, every stream of the
|
|
8179
|
+
* packetId.
|
|
8180
|
+
*/
|
|
8181
|
+
holdsRebuilt(packetId, catalogTrackName) {
|
|
8182
|
+
for (const stream of this.#streams.values()) {
|
|
8183
|
+
if (stream.packetId !== packetId) continue;
|
|
8184
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
8185
|
+
if (holdsRebuilt(stream)) return true;
|
|
8186
|
+
}
|
|
8187
|
+
return false;
|
|
8188
|
+
}
|
|
8149
8189
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
8150
8190
|
poke(streamKey) {
|
|
8151
8191
|
if (streamKey !== void 0) {
|
|
@@ -8237,16 +8277,32 @@ var FecReleaseSequencer = class {
|
|
|
8237
8277
|
last: null,
|
|
8238
8278
|
lastSbn: void 0,
|
|
8239
8279
|
discontinuity: false,
|
|
8240
|
-
rapSsId: void 0
|
|
8280
|
+
rapSsId: void 0,
|
|
8281
|
+
sourced: false,
|
|
8282
|
+
draining: 0
|
|
8241
8283
|
};
|
|
8242
8284
|
this.#streams.set(streamKey, stream);
|
|
8243
8285
|
}
|
|
8244
8286
|
return stream;
|
|
8245
8287
|
}
|
|
8246
|
-
#
|
|
8288
|
+
/** `asUnanchored`: release overlay-rebuilt frames when unanchored did (#cursorPassed). */
|
|
8289
|
+
#drain(stream, asUnanchored = false) {
|
|
8290
|
+
stream.draining++;
|
|
8291
|
+
try {
|
|
8292
|
+
this.#drainLoop(stream, asUnanchored);
|
|
8293
|
+
} finally {
|
|
8294
|
+
stream.draining--;
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
8297
|
+
#drainLoop(stream, asUnanchored) {
|
|
8247
8298
|
while (stream.buf.length > 0 && stream.last !== null) {
|
|
8248
8299
|
const head = stream.buf[0];
|
|
8249
8300
|
const last = stream.last;
|
|
8301
|
+
if (compareFrameOrder(head, last) <= 0) {
|
|
8302
|
+
stream.buf.shift();
|
|
8303
|
+
this.#release(stream, head, void 0);
|
|
8304
|
+
continue;
|
|
8305
|
+
}
|
|
8250
8306
|
if (stream.discontinuity) {
|
|
8251
8307
|
stream.discontinuity = false;
|
|
8252
8308
|
stream.buf.shift();
|
|
@@ -8260,9 +8316,10 @@ var FecReleaseSequencer = class {
|
|
|
8260
8316
|
}
|
|
8261
8317
|
const headSbn = head.meta.fecSbn;
|
|
8262
8318
|
const from = stream.lastSbn ?? headSbn;
|
|
8263
|
-
const to = headSbn ?? stream.lastSbn;
|
|
8319
|
+
const to = head.meta.fecSbnFirst ?? headSbn ?? stream.lastSbn;
|
|
8264
8320
|
let lost = [];
|
|
8265
8321
|
const unanchored = from === void 0 && to === void 0;
|
|
8322
|
+
let unresolved = false;
|
|
8266
8323
|
if (from !== void 0 && to !== void 0) {
|
|
8267
8324
|
const resolved = this.#gate.resolvedLoss(
|
|
8268
8325
|
stream.packetId,
|
|
@@ -8271,7 +8328,8 @@ var FecReleaseSequencer = class {
|
|
|
8271
8328
|
Math.max(from, to),
|
|
8272
8329
|
from
|
|
8273
8330
|
);
|
|
8274
|
-
|
|
8331
|
+
unresolved = resolved === null;
|
|
8332
|
+
if (unresolved && !this.#cursorPassed(stream, asUnanchored)) {
|
|
8275
8333
|
const heldMs = performance.now() - head.arrivedAtMs;
|
|
8276
8334
|
if (heldMs <= this.#gate.holdLimitMs(stream.packetId, stream.catalogTrackName)) return;
|
|
8277
8335
|
this.#recordStat("forcedWindowFlushes");
|
|
@@ -8279,22 +8337,56 @@ var FecReleaseSequencer = class {
|
|
|
8279
8337
|
this.#release(stream, head, this.#gate.decide(last, head, [], true));
|
|
8280
8338
|
continue;
|
|
8281
8339
|
}
|
|
8282
|
-
lost = resolved;
|
|
8340
|
+
lost = resolved ?? [];
|
|
8283
8341
|
}
|
|
8284
8342
|
stream.buf.shift();
|
|
8285
8343
|
this.#release(
|
|
8286
8344
|
stream,
|
|
8287
8345
|
head,
|
|
8288
|
-
lost.length > 0 || unanchored || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
8346
|
+
lost.length > 0 || unanchored || unresolved || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
8289
8347
|
);
|
|
8290
8348
|
}
|
|
8291
8349
|
}
|
|
8350
|
+
/**
|
|
8351
|
+
* An overlay-rebuilt frame (fecSbnFirst) waits no longer than the BLO-53342
|
|
8352
|
+
* source cursor holds it unanchored, and neither does a later frame behind
|
|
8353
|
+
* it. Unanchored, a frame that waits on no block (fecSbnFirst set, or no
|
|
8354
|
+
* fecSbn) left the gate once every frame ahead of it had: mark it `passed`,
|
|
8355
|
+
* like each frame that arrived in front of it since (stale there). The head
|
|
8356
|
+
* then goes, decided as it would be unanchored, once a frame the base path
|
|
8357
|
+
* delivered (a source frame, or one base FEC recovered) is buffered after a
|
|
8358
|
+
* marked one: the base path has passed that one, so a frame still missing
|
|
8359
|
+
* before it can only come from a repair. It also goes while no source frame
|
|
8360
|
+
* has been released, when the direct path shows a recovered frame at once.
|
|
8361
|
+
* And it goes at once while a staged switch decides on when frames arrive
|
|
8362
|
+
* (FecReleaseGate.releaseUnanchored), and when unsource releases into a
|
|
8363
|
+
* sink being replaced: unanchored, it had already reached the sink.
|
|
8364
|
+
* The scan also writes: the `passed` marks it sets stay on the frames,
|
|
8365
|
+
* and push marks a frame it inserts in front of a marked one from them.
|
|
8366
|
+
*/
|
|
8367
|
+
#cursorPassed(stream, asUnanchored) {
|
|
8368
|
+
let at = -1;
|
|
8369
|
+
for (let i = 0; i < stream.buf.length; i++) {
|
|
8370
|
+
const frame = stream.buf[i];
|
|
8371
|
+
if (frame.meta.fecSbnFirst !== void 0 || frame.meta.fecSbn === void 0) {
|
|
8372
|
+
frame.passed = true;
|
|
8373
|
+
if (at < 0) at = i;
|
|
8374
|
+
} else if (frame.passed !== true) break;
|
|
8375
|
+
}
|
|
8376
|
+
if (at < 0) return false;
|
|
8377
|
+
return asUnanchored || !stream.sourced || this.#gate.releaseUnanchored?.(stream.packetId, stream.catalogTrackName) === true || stream.buf.some(({ meta }, i) => i > at && meta.fecSbnFirst === void 0 && (meta.recovered !== true || meta.fecSbn !== void 0));
|
|
8378
|
+
}
|
|
8292
8379
|
#release(stream, frame, decision) {
|
|
8380
|
+
const stale = stream.last !== null && compareFrameOrder(frame, stream.last) <= 0;
|
|
8381
|
+
if (stale) this.#recordStat("droppedStale");
|
|
8293
8382
|
if (decision) frame.meta.gapBefore = decision;
|
|
8294
8383
|
stream.last = frame;
|
|
8295
|
-
if (frame.meta.fecSbn !== void 0
|
|
8384
|
+
if (frame.meta.fecSbn !== void 0 && (frame.meta.fecSbnFirst === void 0 || stream.lastSbn !== void 0)) {
|
|
8385
|
+
stream.lastSbn = frame.meta.fecSbn;
|
|
8386
|
+
}
|
|
8387
|
+
if (frame.meta.recovered !== true) stream.sourced = true;
|
|
8296
8388
|
this.#gate.onReleased?.(frame.data, frame.meta);
|
|
8297
|
-
this.#emit(frame.data, frame.meta);
|
|
8389
|
+
if (!stale) this.#emit(frame.data, frame.meta);
|
|
8298
8390
|
}
|
|
8299
8391
|
};
|
|
8300
8392
|
var FMPU_BOX_TYPES = /* @__PURE__ */ new Set(["ftyp", "moov", "moof"]);
|
|
@@ -8620,6 +8712,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8620
8712
|
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
8621
8713
|
return this.#fecSequencer?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
|
|
8622
8714
|
}
|
|
8715
|
+
/** See FecReleaseSequencer.unsource. */
|
|
8716
|
+
unsourceFecRelease(packetId, catalogTrackName) {
|
|
8717
|
+
this.#fecSequencer?.unsource(packetId, catalogTrackName);
|
|
8718
|
+
}
|
|
8719
|
+
/** See FecReleaseSequencer.holdsRebuilt. */
|
|
8720
|
+
fecReleaseHoldsRebuilt(packetId, catalogTrackName) {
|
|
8721
|
+
return this.#fecSequencer?.holdsRebuilt(packetId, catalogTrackName) ?? false;
|
|
8722
|
+
}
|
|
8623
8723
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
8624
8724
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
8625
8725
|
if (!this.#fecSequencer) return;
|
|
@@ -8946,7 +9046,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
8946
9046
|
info,
|
|
8947
9047
|
resolver,
|
|
8948
9048
|
options?.recovered === true,
|
|
8949
|
-
options?.fecSbn
|
|
9049
|
+
options?.fecSbn,
|
|
9050
|
+
options?.fecSbnFirst
|
|
8950
9051
|
);
|
|
8951
9052
|
if (deferred.record === void 0) this.#countDeferredOutcome(deferred.reason);
|
|
8952
9053
|
}
|
|
@@ -9039,7 +9140,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9039
9140
|
info,
|
|
9040
9141
|
resolver,
|
|
9041
9142
|
options?.recovered === true,
|
|
9042
|
-
options?.fecSbn
|
|
9143
|
+
options?.fecSbn,
|
|
9144
|
+
options?.fecSbnFirst
|
|
9043
9145
|
);
|
|
9044
9146
|
if (outcome.record !== void 0) return outcome.record;
|
|
9045
9147
|
return this.#drop(outcome.reason);
|
|
@@ -9059,7 +9161,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9059
9161
|
* attribution exists to keep readable. Leaving the choice at the call site
|
|
9060
9162
|
* is what keeps both true.
|
|
9061
9163
|
*/
|
|
9062
|
-
#emitCombinedSample(combined, info, resolver, recovered, fecSbn) {
|
|
9164
|
+
#emitCombinedSample(combined, info, resolver, recovered, fecSbn, fecSbnFirst) {
|
|
9063
9165
|
const timing = resolver.sampleTiming(
|
|
9064
9166
|
combined.mpuSequenceNumber,
|
|
9065
9167
|
combined.movieFragmentSequenceNumber,
|
|
@@ -9076,6 +9178,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
9076
9178
|
);
|
|
9077
9179
|
if (!meta) return { reason: "combined-meta-build-failed" };
|
|
9078
9180
|
if (fecSbn !== void 0) meta.fecSbn = fecSbn;
|
|
9181
|
+
if (fecSbnFirst !== void 0) meta.fecSbnFirst = fecSbnFirst;
|
|
9079
9182
|
if (!resolver.consumeSample(
|
|
9080
9183
|
combined.mpuSequenceNumber,
|
|
9081
9184
|
combined.movieFragmentSequenceNumber,
|
|
@@ -9926,6 +10029,7 @@ function unstampedBaseSsId(offset, unstampedOffset, headerPsn) {
|
|
|
9926
10029
|
var OVERLAY_ROUTED_LIMIT = 1024;
|
|
9927
10030
|
var OVERLAY_PENDING_LIMIT = 256;
|
|
9928
10031
|
var OVERLAY_OFFSET_MPUS = 16;
|
|
10032
|
+
var OVERLAY_REBUILT_LIMIT = 256;
|
|
9929
10033
|
var OVERLAY_MOQ_HORIZON_BLOCKS = 64;
|
|
9930
10034
|
function assertStreamIdentity(role, stream) {
|
|
9931
10035
|
if (stream === void 0 || !Number.isInteger(stream.packetId) || stream.packetId <= MMTP_SIGNALING_PACKET_ID || typeof stream.catalogTrackName !== "string" || stream.catalogTrackName.length === 0) {
|
|
@@ -10102,6 +10206,17 @@ var MmtpRouter = class {
|
|
|
10102
10206
|
* an MPU no member of which arrived).
|
|
10103
10207
|
*/
|
|
10104
10208
|
#unstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
10209
|
+
/**
|
|
10210
|
+
* The same, shown by the overlay alone, before any base recovery did: a
|
|
10211
|
+
* packet it rebuilt whose base copy carries its protected psn
|
|
10212
|
+
* (#feedOverlaySource), or whose protected psn passes overlayBaseSsId's
|
|
10213
|
+
* test at that offset (#routeOverlayRecovered). A base SS_ID placed by it
|
|
10214
|
+
* anchors the release gate only (BLO-52085 D3): fed to the base, it would
|
|
10215
|
+
* make the base drop a late base copy of a keyframe the overlay never
|
|
10216
|
+
* finished, and lift a block's late-source cut, which nothing before
|
|
10217
|
+
* this offset did.
|
|
10218
|
+
*/
|
|
10219
|
+
#overlayUnstampedSsIdSeqOffsetByStream = /* @__PURE__ */ new Map();
|
|
10105
10220
|
/**
|
|
10106
10221
|
* Streams whose multicast leg delivered a RAP-flagged source packet that was
|
|
10107
10222
|
* not parsed, typically because its overlap copy was parsed first
|
|
@@ -10379,13 +10494,18 @@ var MmtpRouter = class {
|
|
|
10379
10494
|
this.#onDeltaBlockCompleteCb?.(packetId, false);
|
|
10380
10495
|
}
|
|
10381
10496
|
};
|
|
10382
|
-
this.#fecManager.onBlockFailed = (sbn, packetId, catalogTrackName, blockClass) => {
|
|
10497
|
+
this.#fecManager.onBlockFailed = (sbn, packetId, catalogTrackName, blockClass, span) => {
|
|
10383
10498
|
this.#mseAssemblerFor(packetId, catalogTrackName)?.failBlock(sbn);
|
|
10384
10499
|
if (this.#isCatalogTrackUnambiguous(packetId, catalogTrackName) && this.#packetIdRoles.get(packetId) === "delta") {
|
|
10385
10500
|
this.#onDeltaBlockCompleteCb?.(packetId, true);
|
|
10386
10501
|
}
|
|
10387
|
-
|
|
10388
|
-
|
|
10502
|
+
if (span === void 0) {
|
|
10503
|
+
onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
10504
|
+
this.#onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass);
|
|
10505
|
+
} else {
|
|
10506
|
+
onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span);
|
|
10507
|
+
this.#onBlockFailed?.(sbn, packetId, catalogTrackName, blockClass, span);
|
|
10508
|
+
}
|
|
10389
10509
|
};
|
|
10390
10510
|
}
|
|
10391
10511
|
}
|
|
@@ -10430,8 +10550,9 @@ var MmtpRouter = class {
|
|
|
10430
10550
|
}
|
|
10431
10551
|
/**
|
|
10432
10552
|
* FEC-recovered source symbols whose reconstructed header named a
|
|
10433
|
-
* packet_sequence_number other than the wire sequence their SS_ID maps to
|
|
10434
|
-
*
|
|
10553
|
+
* packet_sequence_number other than the wire sequence their SS_ID maps to,
|
|
10554
|
+
* or, rebuilt by the overlay, than their base copy carries: a hop
|
|
10555
|
+
* re-stamped the sequence after the sender protected the packet.
|
|
10435
10556
|
*/
|
|
10436
10557
|
get restampedRecoveredSequenceCount() {
|
|
10437
10558
|
return this.#restampedRecoveredSeqs;
|
|
@@ -10610,6 +10731,7 @@ var MmtpRouter = class {
|
|
|
10610
10731
|
enableMseAssembler(packetId, config, catalogTrackName) {
|
|
10611
10732
|
const assemblerKey = this.#mseAssemblerKey(packetId, catalogTrackName);
|
|
10612
10733
|
const previous = this.#mseAssemblers.get(assemblerKey);
|
|
10734
|
+
this.#parser.unsourceFecRelease(packetId, catalogTrackName);
|
|
10613
10735
|
if (previous) {
|
|
10614
10736
|
previous.flush();
|
|
10615
10737
|
previous.dispose();
|
|
@@ -10760,7 +10882,8 @@ var MmtpRouter = class {
|
|
|
10760
10882
|
fec,
|
|
10761
10883
|
k,
|
|
10762
10884
|
offsets: /* @__PURE__ */ new Map(),
|
|
10763
|
-
moqBlocks: /* @__PURE__ */ new Set()
|
|
10885
|
+
moqBlocks: /* @__PURE__ */ new Set(),
|
|
10886
|
+
rebuilt: /* @__PURE__ */ new Map()
|
|
10764
10887
|
};
|
|
10765
10888
|
this.#overlays.set(repairPacketId, inst);
|
|
10766
10889
|
return () => {
|
|
@@ -10801,6 +10924,7 @@ var MmtpRouter = class {
|
|
|
10801
10924
|
inst.last = void 0;
|
|
10802
10925
|
inst.offsets.clear();
|
|
10803
10926
|
inst.moqBlocks.clear();
|
|
10927
|
+
inst.rebuilt.clear();
|
|
10804
10928
|
}
|
|
10805
10929
|
#overlaysForSource(sourcePacketId, catalogTrackName) {
|
|
10806
10930
|
return [...this.#overlays.values()].filter(({ leg }) => leg.sourcePacketId === sourcePacketId && (catalogTrackName === void 0 || leg.sourceCatalogTrackName === void 0 || leg.sourceCatalogTrackName === catalogTrackName));
|
|
@@ -10853,13 +10977,23 @@ var MmtpRouter = class {
|
|
|
10853
10977
|
* base protects: the packet minus its 4-byte SS_ID trailer, extension
|
|
10854
10978
|
* included (7.1, 7.4.3). `baseSsId` is that trailer's SS_ID, when the base
|
|
10855
10979
|
* FEC reads it.
|
|
10980
|
+
*
|
|
10981
|
+
* The base copy of a packet an overlay rebuilt with no base SS_ID shows
|
|
10982
|
+
* what a base recovery of it would (#routeProcessedFecSymbols, BLO-52085):
|
|
10983
|
+
* its wire packet_sequence_number equal to the protected one rebuilt shows
|
|
10984
|
+
* the stream unstamped at the SS_ID offset it states, for the release gate
|
|
10985
|
+
* (#overlayUnstampedSsIdSeqOffsetByStream); any other shows a hop that
|
|
10986
|
+
* re-stamps it, which is counted (restampedRecoveredSequenceCount).
|
|
10856
10987
|
*/
|
|
10857
10988
|
#feedOverlaySource(packetId, catalogTrackName, packet, header, sequenceNumber, rapFlag, source, baseSsId) {
|
|
10858
10989
|
if (this.#overlays.size === 0) return;
|
|
10859
10990
|
const ssId = keyframeOverlaySsId(header);
|
|
10860
10991
|
if (ssId === void 0) return;
|
|
10992
|
+
let rebuiltPsn;
|
|
10861
10993
|
for (const inst of this.#overlaysForSource(packetId, catalogTrackName)) {
|
|
10862
10994
|
this.#noteOverlaySsId(inst, ssId, sequenceNumber);
|
|
10995
|
+
rebuiltPsn ??= inst.rebuilt.get(ssId);
|
|
10996
|
+
inst.rebuilt.delete(ssId);
|
|
10863
10997
|
const identity = MmtpContainerParser.sourcePacketIdentity(packet, header);
|
|
10864
10998
|
if (identity !== null) {
|
|
10865
10999
|
inst.offsets.delete(identity.mpuSequenceNumber);
|
|
@@ -10889,6 +11023,14 @@ var MmtpRouter = class {
|
|
|
10889
11023
|
const recovered = fec.feedSourceSymbol(pid, ssId, symbolData, name);
|
|
10890
11024
|
if (recovered && !alreadyCompleted) this.#routeOverlayRecovered(inst, coords.sbn, recovered);
|
|
10891
11025
|
}
|
|
11026
|
+
if (rebuiltPsn === void 0 || this.#classifyPacketId(packetId) === null) return;
|
|
11027
|
+
const streamKey = this.#mediaStreamKey(packetId, catalogTrackName);
|
|
11028
|
+
if (rebuiltPsn !== sequenceNumber) {
|
|
11029
|
+
this.#restampedRecoveredSeqs++;
|
|
11030
|
+
} else if (baseSsId !== void 0) {
|
|
11031
|
+
const offset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
11032
|
+
if (offset === baseSsId - sequenceNumber >>> 0) this.#overlayUnstampedSsIdSeqOffsetByStream.set(streamKey, offset);
|
|
11033
|
+
}
|
|
10892
11034
|
}
|
|
10893
11035
|
#noteOverlayMoqBlock(inst, blockStart) {
|
|
10894
11036
|
inst.moqBlocks.add(blockStart);
|
|
@@ -10922,11 +11064,16 @@ var MmtpRouter = class {
|
|
|
10922
11064
|
}
|
|
10923
11065
|
/**
|
|
10924
11066
|
* Route an overlay-recovered block's packets as source media of the
|
|
10925
|
-
* overlay's source (7.2: an overlay coordinate yields no base SS_ID).
|
|
10926
|
-
*
|
|
10927
|
-
*
|
|
10928
|
-
*
|
|
10929
|
-
*
|
|
11067
|
+
* overlay's source (7.2: an overlay coordinate yields no base SS_ID). A
|
|
11068
|
+
* packet whose base SS_ID is determined independently (overlayBaseSsId,
|
|
11069
|
+
* unstampedBaseSsId) enters the release gate at that SS_ID, as a packet
|
|
11070
|
+
* base FEC recovered does, so it waits behind base frames still in flight,
|
|
11071
|
+
* though not for its own MPU's packets (FrameMeta.fecSbnFirst) and no
|
|
11072
|
+
* longer than unanchored; one whose base SS_ID is undetermined has no
|
|
11073
|
+
* release-gate anchor. No parse ledger, no base re-stamp, no block
|
|
11074
|
+
* advance. Each packet's wire sequence is restored from its keyframe's own
|
|
11075
|
+
* offset; without one, a stream the base never re-stamped keeps the
|
|
11076
|
+
* header's sequence and any other drops the packet.
|
|
10930
11077
|
*/
|
|
10931
11078
|
#routeOverlayRecovered(inst, blockStart, recovered) {
|
|
10932
11079
|
const { fec, repairPacketId: pid, streamName: name } = inst;
|
|
@@ -10947,40 +11094,65 @@ var MmtpRouter = class {
|
|
|
10947
11094
|
const restamped = this.#restampedStreams.has(streamKey);
|
|
10948
11095
|
const restored = /* @__PURE__ */ new Set();
|
|
10949
11096
|
const baseSsIds = /* @__PURE__ */ new Map();
|
|
11097
|
+
const gateOnly = /* @__PURE__ */ new Set();
|
|
10950
11098
|
const symbols = result.symbols.flatMap((sym) => {
|
|
11099
|
+
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
10951
11100
|
const anchor = inst.offsets.get(sym.fragment.mpuSequenceNumber);
|
|
10952
11101
|
if (anchor !== void 0) {
|
|
10953
|
-
const overlaySsId = serial32Add(blockStart, sym.index);
|
|
10954
11102
|
const packetSequenceNumber = overlaySsId - anchor.psnOffset >>> 0;
|
|
10955
11103
|
restored.add(packetSequenceNumber);
|
|
10956
11104
|
const out = { ...sym, packetSequenceNumber };
|
|
10957
11105
|
const baseSsId = overlayBaseSsId(anchor, overlaySsId, packetSequenceNumber, sym.packetSequenceNumber);
|
|
10958
|
-
if (baseSsId !== void 0)
|
|
11106
|
+
if (baseSsId !== void 0) {
|
|
11107
|
+
baseSsIds.set(sym.index, baseSsId);
|
|
11108
|
+
const offset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
11109
|
+
if (!this.#sourceSeqReceived(sourcePacketId, packetSequenceNumber, sourceName) && offset === baseSsId - packetSequenceNumber >>> 0) {
|
|
11110
|
+
this.#overlayUnstampedSsIdSeqOffsetByStream.set(streamKey, offset);
|
|
11111
|
+
}
|
|
11112
|
+
}
|
|
10959
11113
|
return [out];
|
|
10960
11114
|
}
|
|
10961
11115
|
if (!restamped) {
|
|
10962
|
-
const
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
11116
|
+
const offset = this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
11117
|
+
const fedSsId = unstampedBaseSsId(offset, this.#unstampedSsIdSeqOffsetByStream.get(streamKey), sym.packetSequenceNumber);
|
|
11118
|
+
const baseSsId = fedSsId ?? unstampedBaseSsId(offset, this.#overlayUnstampedSsIdSeqOffsetByStream.get(streamKey), sym.packetSequenceNumber);
|
|
11119
|
+
if (baseSsId !== void 0) {
|
|
11120
|
+
baseSsIds.set(sym.index, baseSsId);
|
|
11121
|
+
if (fedSsId === void 0) gateOnly.add(sym.index);
|
|
11122
|
+
} else {
|
|
11123
|
+
inst.rebuilt.set(overlaySsId, sym.packetSequenceNumber);
|
|
11124
|
+
if (inst.rebuilt.size > OVERLAY_REBUILT_LIMIT) inst.rebuilt.delete(inst.rebuilt.keys().next().value);
|
|
11125
|
+
}
|
|
10968
11126
|
return [sym];
|
|
10969
11127
|
}
|
|
10970
11128
|
this.#overlayStats.unsequencedDropped++;
|
|
10971
11129
|
return [];
|
|
10972
11130
|
});
|
|
10973
11131
|
this.#overlayStats.recovered++;
|
|
11132
|
+
const mpuFirst = /* @__PURE__ */ new Map();
|
|
11133
|
+
for (const sym of symbols) {
|
|
11134
|
+
const ssId = baseSsIds.get(sym.index);
|
|
11135
|
+
const mpu = sym.fragment.mpuSequenceNumber;
|
|
11136
|
+
const first = mpuFirst.get(mpu);
|
|
11137
|
+
if (ssId !== void 0 && (first === void 0 || serial32Precedes(ssId, first))) mpuFirst.set(mpu, ssId);
|
|
11138
|
+
}
|
|
11139
|
+
const firstSsIds = /* @__PURE__ */ new Map();
|
|
11140
|
+
for (const sym of symbols) {
|
|
11141
|
+
if (baseSsIds.has(sym.index)) firstSsIds.set(sym.index, mpuFirst.get(sym.fragment.mpuSequenceNumber));
|
|
11142
|
+
}
|
|
10974
11143
|
this.#routeProcessedFecSymbols(blockStart, { ...result, symbols }, sourcePacketId, sourceName, {
|
|
10975
|
-
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast"
|
|
11144
|
+
deliveryProvenance: inst.moqBlocks.has(blockStart) ? "mixed" : "multicast",
|
|
11145
|
+
baseSsIds,
|
|
11146
|
+
firstSsIds
|
|
10976
11147
|
});
|
|
10977
11148
|
this.#noteOverlayRecoveredRaps(sourcePacketId, sourceName, streamKey, symbols.filter((sym) => restored.has(sym.packetSequenceNumber)));
|
|
10978
|
-
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, baseSsIds);
|
|
11149
|
+
this.#feedOverlayRecoveredToBase(inst, blockStart, symbols, gateOnly.size === 0 ? baseSsIds : new Map([...baseSsIds].filter(([index]) => !gateOnly.has(index))));
|
|
10979
11150
|
}
|
|
10980
11151
|
/**
|
|
10981
11152
|
* Joint decoding (BLO-52085). A packet the overlay rebuilt is a source of
|
|
10982
11153
|
* its base block too, once its base SS_ID is determined (overlayBaseSsId,
|
|
10983
|
-
* from a received member of its MPU, else unstampedBaseSsId
|
|
11154
|
+
* from a received member of its MPU, else unstampedBaseSsId at an offset a
|
|
11155
|
+
* base recovery showed; `baseSsIds` holds no other),
|
|
10984
11156
|
* so it is fed to the base decoder as a rebuilt source
|
|
10985
11157
|
* (FECManager.feedRebuiltSourceSymbol): the base block has one erasure
|
|
10986
11158
|
* fewer to solve, and its repair can recover the block's other packets.
|
|
@@ -11006,7 +11178,7 @@ var MmtpRouter = class {
|
|
|
11006
11178
|
const feeds = [];
|
|
11007
11179
|
for (const sym of symbols) {
|
|
11008
11180
|
if (duplicates.sourceSeqs.has(sym.packetSequenceNumber)) continue;
|
|
11009
|
-
const ssId = baseSsIds.get(sym);
|
|
11181
|
+
const ssId = baseSsIds.get(sym.index);
|
|
11010
11182
|
if (ssId === void 0) {
|
|
11011
11183
|
this.#overlayStats.baseUndetermined++;
|
|
11012
11184
|
continue;
|
|
@@ -11735,7 +11907,7 @@ var MmtpRouter = class {
|
|
|
11735
11907
|
catalogTrackName
|
|
11736
11908
|
)) continue;
|
|
11737
11909
|
if (fec.isTrackConfigured?.(ownerPacketId) !== false) {
|
|
11738
|
-
fec.trackBlock(blockStart, ownerPacketId, catalogTrackName, esi, payload.evidence);
|
|
11910
|
+
fec.trackBlock(blockStart, ownerPacketId, catalogTrackName, esi, payload.evidence, true);
|
|
11739
11911
|
this.#requestFecPoke(ownerPacketId, catalogTrackName);
|
|
11740
11912
|
}
|
|
11741
11913
|
if (payload.oversized) continue;
|
|
@@ -11823,6 +11995,7 @@ var MmtpRouter = class {
|
|
|
11823
11995
|
this.#latestVideoSeqByStream.delete(streamKey);
|
|
11824
11996
|
this.#ssIdSeqOffsetByStream.delete(streamKey);
|
|
11825
11997
|
this.#unstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
11998
|
+
this.#overlayUnstampedSsIdSeqOffsetByStream.delete(streamKey);
|
|
11826
11999
|
this.#multicastRapTwinByStream.delete(streamKey);
|
|
11827
12000
|
this.#lastDirectSourceVideoTsByStream.delete(streamKey);
|
|
11828
12001
|
this.#lastDirectEmittedVideoTsByStream.delete(streamKey);
|
|
@@ -11949,6 +12122,12 @@ var MmtpRouter = class {
|
|
|
11949
12122
|
* the outgoing stream stops. The hold is bounded by the incoming stream's
|
|
11950
12123
|
* own RAP cadence, so no timer is involved.
|
|
11951
12124
|
*
|
|
12125
|
+
* While the switch is staged, the FEC release gate lets a keyframe the
|
|
12126
|
+
* overlay rebuilt reach it, on either side, when the keyframe did before
|
|
12127
|
+
* the gate anchored it (FecReleaseGate.releaseUnanchored): the switch
|
|
12128
|
+
* decides on what each side delivered, and the commit discards the gate's
|
|
12129
|
+
* frames of the outgoing stream.
|
|
12130
|
+
*
|
|
11952
12131
|
* The commit is one synchronous transition inside the frame path: the
|
|
11953
12132
|
* outgoing stream's already-admitted frames are released (its queued FEC
|
|
11954
12133
|
* gap fills only up to the RAP, since recovery can queue a frame ahead of
|
|
@@ -11999,6 +12178,9 @@ var MmtpRouter = class {
|
|
|
11999
12178
|
droppedIncomingFrames: 0
|
|
12000
12179
|
};
|
|
12001
12180
|
this.#staged = state;
|
|
12181
|
+
if (this.#parser.fecReleaseHoldsRebuilt(from.packetId, from.catalogTrackName)) {
|
|
12182
|
+
this.#requestFecPoke(from.packetId, from.catalogTrackName);
|
|
12183
|
+
}
|
|
12002
12184
|
return {
|
|
12003
12185
|
cancel: () => {
|
|
12004
12186
|
if (this.#staged !== state) return false;
|
|
@@ -12081,7 +12263,17 @@ var MmtpRouter = class {
|
|
|
12081
12263
|
*/
|
|
12082
12264
|
#routeParsedFrame(data, meta) {
|
|
12083
12265
|
if (this.#classifyPacketId(meta.packetId) === null) return;
|
|
12084
|
-
if (this.#staged !== null
|
|
12266
|
+
if (this.#staged !== null) {
|
|
12267
|
+
let consumed = false;
|
|
12268
|
+
this.#fecPokeDepth++;
|
|
12269
|
+
try {
|
|
12270
|
+
consumed = this.#interceptSwitchFrame(data, meta);
|
|
12271
|
+
} finally {
|
|
12272
|
+
this.#fecPokeDepth--;
|
|
12273
|
+
this.#flushPendingFecPokes();
|
|
12274
|
+
}
|
|
12275
|
+
if (consumed) return;
|
|
12276
|
+
}
|
|
12085
12277
|
this.#deliverParsedFrame(data, meta);
|
|
12086
12278
|
}
|
|
12087
12279
|
#commitStreamSwitch(state, rap, reason) {
|
|
@@ -12233,12 +12425,16 @@ var MmtpRouter = class {
|
|
|
12233
12425
|
this.#parser.deferFecRelease(work);
|
|
12234
12426
|
} finally {
|
|
12235
12427
|
this.#fecPokeDepth--;
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12428
|
+
this.#flushPendingFecPokes();
|
|
12429
|
+
}
|
|
12430
|
+
}
|
|
12431
|
+
/** Run the pokes requested while #fecPokeDepth was raised, once it is 0. */
|
|
12432
|
+
#flushPendingFecPokes() {
|
|
12433
|
+
if (this.#fecPokeDepth === 0 && this.#pendingFecPokes.size > 0 && !this.#disposed) {
|
|
12434
|
+
const pokes = [...this.#pendingFecPokes.values()];
|
|
12435
|
+
this.#pendingFecPokes.clear();
|
|
12436
|
+
for (const poke of pokes) {
|
|
12437
|
+
this.#parser.pokeFecRelease(poke.packetId, poke.catalogTrackName);
|
|
12242
12438
|
}
|
|
12243
12439
|
}
|
|
12244
12440
|
}
|
|
@@ -12316,6 +12512,14 @@ var MmtpRouter = class {
|
|
|
12316
12512
|
onRap: (packetId, catalogTrackName, ssId) => {
|
|
12317
12513
|
for (const observer of this.#fecRapObservers) observer(packetId, catalogTrackName, ssId);
|
|
12318
12514
|
},
|
|
12515
|
+
// A staged switch commits on when each side's frames reach it
|
|
12516
|
+
// (#interceptSwitchFrame): both sides' overlay keyframes reach it when
|
|
12517
|
+
// they did unanchored.
|
|
12518
|
+
releaseUnanchored: (packetId, catalogTrackName) => {
|
|
12519
|
+
const staged = this.#staged;
|
|
12520
|
+
const stream = { packetId, ...catalogTrackName === void 0 ? {} : { catalogTrackName } };
|
|
12521
|
+
return staged !== null && (isStream(stream, staged.from) || isStream(stream, staged.to));
|
|
12522
|
+
},
|
|
12319
12523
|
onReleased: (data, meta) => {
|
|
12320
12524
|
if (meta.trackType !== "video" || meta.mpuSequenceNumber === void 0) return;
|
|
12321
12525
|
const timing = meta.authoredTiming;
|
|
@@ -12766,7 +12970,8 @@ var MmtpRouter = class {
|
|
|
12766
12970
|
source: parserOptions?.source,
|
|
12767
12971
|
deliveryProvenance: parserOptions?.deliveryProvenance,
|
|
12768
12972
|
catalogTrackName: parserOptions?.catalogTrackName,
|
|
12769
|
-
...parserOptions?.fecSbn === void 0 ? {} : { fecSbn: parserOptions.fecSbn }
|
|
12973
|
+
...parserOptions?.fecSbn === void 0 ? {} : { fecSbn: parserOptions.fecSbn },
|
|
12974
|
+
...parserOptions?.fecSbnFirst === void 0 ? {} : { fecSbnFirst: parserOptions.fecSbnFirst }
|
|
12770
12975
|
};
|
|
12771
12976
|
if (parsed) this.#parser.feedParsed(packet, parsed, feedOptions);
|
|
12772
12977
|
else this.#parser.feed(packet, feedOptions);
|
|
@@ -13232,7 +13437,12 @@ var MmtpRouter = class {
|
|
|
13232
13437
|
#routeProcessedFecSymbols(blockId, result, ownerPacketId, catalogTrackName, overlay) {
|
|
13233
13438
|
if (ownerPacketId == null) return;
|
|
13234
13439
|
if (this.#classifyPacketId(ownerPacketId) === null) return;
|
|
13235
|
-
const fecSbnOf = (index) =>
|
|
13440
|
+
const fecSbnOf = (index) => {
|
|
13441
|
+
if (!overlay) return { fecSbn: this.#fecAnchorForRecovered(blockId, index) };
|
|
13442
|
+
const fecSbn = overlay.baseSsIds.get(index);
|
|
13443
|
+
const fecSbnFirst = overlay.firstSsIds.get(index);
|
|
13444
|
+
return fecSbn === void 0 || fecSbnFirst === void 0 ? {} : { fecSbn, fecSbnFirst };
|
|
13445
|
+
};
|
|
13236
13446
|
const streamKey = this.#mediaStreamKey(ownerPacketId, catalogTrackName);
|
|
13237
13447
|
const ssIdSeqOffset = overlay ? void 0 : this.#ssIdSeqOffsetByStream.get(streamKey);
|
|
13238
13448
|
if (ssIdSeqOffset !== void 0) {
|