@blockcast/mmt-container 2.2.0-main.efb4fa4521af → 2.2.0-main.fb5c7dfbe2de
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -17
- package/dist/browser-runtime.js +1336 -302
- package/dist/browser-runtime.js.map +3 -3
- package/dist/catalog-fec-params.d.ts +17 -1
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +15 -0
- package/dist/catalog-fec-params.js.map +2 -2
- package/dist/cmcd-producer.d.ts +60 -47
- package/dist/cmcd-producer.d.ts.map +1 -1
- package/dist/cmcd-producer.js +138 -109
- package/dist/cmcd-producer.js.map +2 -2
- package/dist/delivery-catalog.js +26 -16
- package/dist/delivery-catalog.js.map +2 -2
- package/dist/delta-fallback-config.js.map +1 -1
- package/dist/fec-manager.d.ts +70 -12
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +403 -58
- package/dist/fec-manager.js.map +2 -2
- package/dist/fec-worker-setup.d.ts +1 -1
- package/dist/fec-worker-setup.js +1 -1
- package/dist/fec-worker-setup.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +2 -2
- package/dist/iso-block-locator.d.ts +48 -13
- package/dist/iso-block-locator.d.ts.map +1 -1
- package/dist/iso-block-locator.js +97 -25
- package/dist/iso-block-locator.js.map +2 -2
- package/dist/iso-bmff-timing.d.ts +10 -1
- package/dist/iso-bmff-timing.d.ts.map +1 -1
- package/dist/iso-bmff-timing.js +36 -2
- package/dist/iso-bmff-timing.js.map +2 -2
- package/dist/media-router.js.map +1 -1
- package/dist/mmtp-container-parser.d.ts +79 -10
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +207 -26
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +209 -29
- package/dist/mmtp-container-parser.no-modules.js.map +2 -2
- package/dist/mmtp-decode-pipeline.js +206 -26
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-router.d.ts +34 -8
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +555 -79
- 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 +980 -146
- package/dist/multicast-manager.js.map +2 -2
- package/dist/multicast-transport.js +28 -18
- package/dist/multicast-transport.js.map +2 -2
- package/dist/no-modules.js +206 -26
- 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/repair-layer-subscriptions.d.ts +6 -2
- package/dist/repair-layer-subscriptions.d.ts.map +1 -1
- package/dist/repair-layer-subscriptions.js +6 -3
- package/dist/repair-layer-subscriptions.js.map +2 -2
- package/dist/track-session.d.ts +1 -1
- package/dist/track-session.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 +206 -26
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +8 -5
- package/src/catalog-fec-params.ts +36 -1
- package/src/cmcd-producer.ts +189 -179
- package/src/fec-manager.ts +433 -32
- package/src/fec-worker-setup.ts +2 -2
- package/src/index.ts +9 -2
- package/src/iso-block-locator.ts +133 -31
- package/src/iso-bmff-timing.ts +40 -1
- package/src/mmtp-container-parser.ts +306 -29
- package/src/mmtp-router.ts +548 -38
- package/src/multicast-manager.ts +44 -6
- package/src/repair-fetch.ts +363 -49
- package/src/repair-layer-subscriptions.ts +13 -5
- package/src/track-session.ts +4 -1
package/dist/no-modules.js
CHANGED
|
@@ -5568,9 +5568,21 @@ var IsoBmffTimingResolver = class {
|
|
|
5568
5568
|
}
|
|
5569
5569
|
#track = null;
|
|
5570
5570
|
#fragments = /* @__PURE__ */ new Map();
|
|
5571
|
+
/** First sample of each pending fragment, kept in `#delivered` with its sample count. */
|
|
5572
|
+
#firstSamples = /* @__PURE__ */ new Map();
|
|
5573
|
+
/**
|
|
5574
|
+
* First sample and sample count of the newest fragments whose every slot was
|
|
5575
|
+
* cleared. Abandonment counts: `releaseSample` clears a slot too, which
|
|
5576
|
+
* matches the combiner, since it retires an abandoned sample as well.
|
|
5577
|
+
*/
|
|
5578
|
+
#delivered = /* @__PURE__ */ new Map();
|
|
5571
5579
|
#pendingSamples = 0;
|
|
5572
5580
|
#evictedSamples = 0;
|
|
5573
5581
|
#lastMovieFragmentRejection = null;
|
|
5582
|
+
/** True once an FT=0 has bound the track that FT=1 timing is read against. */
|
|
5583
|
+
get initialized() {
|
|
5584
|
+
return this.#track !== null;
|
|
5585
|
+
}
|
|
5574
5586
|
get pendingSampleCount() {
|
|
5575
5587
|
return this.#pendingSamples;
|
|
5576
5588
|
}
|
|
@@ -5594,7 +5606,14 @@ var IsoBmffTimingResolver = class {
|
|
|
5594
5606
|
this.#track = timing;
|
|
5595
5607
|
return true;
|
|
5596
5608
|
}
|
|
5597
|
-
|
|
5609
|
+
/**
|
|
5610
|
+
* `recovered` marks an FEC-recovered copy. A recovered block hands back
|
|
5611
|
+
* every FT=1 it holds, the received ones included, so a recovered copy of a
|
|
5612
|
+
* fragment whose samples are all done is a duplicate: admitting it again
|
|
5613
|
+
* would charge stale timing and could displace the live MPU's. A source
|
|
5614
|
+
* copy is still admitted again (a rendition switch re-arms timing that way).
|
|
5615
|
+
*/
|
|
5616
|
+
addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
|
|
5598
5617
|
this.#lastMovieFragmentRejection = null;
|
|
5599
5618
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
5600
5619
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -5620,6 +5639,8 @@ var IsoBmffTimingResolver = class {
|
|
|
5620
5639
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
5621
5640
|
);
|
|
5622
5641
|
}
|
|
5642
|
+
const delivered = recovered ? this.#delivered.get(key) : void 0;
|
|
5643
|
+
if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
|
|
5623
5644
|
let freed = 0;
|
|
5624
5645
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
5625
5646
|
freed += this.#evictOldestFragment();
|
|
@@ -5633,6 +5654,7 @@ var IsoBmffTimingResolver = class {
|
|
|
5633
5654
|
return false;
|
|
5634
5655
|
}
|
|
5635
5656
|
this.#fragments.set(key, [...parsed.samples]);
|
|
5657
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
5636
5658
|
this.#pendingSamples += parsed.samples.length;
|
|
5637
5659
|
return true;
|
|
5638
5660
|
}
|
|
@@ -5690,6 +5712,7 @@ var IsoBmffTimingResolver = class {
|
|
|
5690
5712
|
let live = 0;
|
|
5691
5713
|
for (const sample of samples) if (sample !== null) live++;
|
|
5692
5714
|
this.#fragments.delete(oldest);
|
|
5715
|
+
this.#firstSamples.delete(oldest);
|
|
5693
5716
|
this.#pendingSamples -= live;
|
|
5694
5717
|
this.#evictedSamples += live;
|
|
5695
5718
|
return live;
|
|
@@ -5705,12 +5728,23 @@ var IsoBmffTimingResolver = class {
|
|
|
5705
5728
|
if (!samples || !samples[index]) return false;
|
|
5706
5729
|
samples[index] = null;
|
|
5707
5730
|
this.#pendingSamples--;
|
|
5708
|
-
if (samples.every((value) => value === null))
|
|
5731
|
+
if (samples.every((value) => value === null)) {
|
|
5732
|
+
this.#fragments.delete(key);
|
|
5733
|
+
const first = this.#firstSamples.get(key);
|
|
5734
|
+
this.#firstSamples.delete(key);
|
|
5735
|
+
this.#delivered.delete(key);
|
|
5736
|
+
this.#delivered.set(key, { first, sampleCount: samples.length });
|
|
5737
|
+
if (this.#delivered.size > this.maxPendingSamples) {
|
|
5738
|
+
this.#delivered.delete(this.#delivered.keys().next().value);
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5709
5741
|
return true;
|
|
5710
5742
|
}
|
|
5711
5743
|
reset() {
|
|
5712
5744
|
this.#track = null;
|
|
5713
5745
|
this.#fragments.clear();
|
|
5746
|
+
this.#firstSamples.clear();
|
|
5747
|
+
this.#delivered.clear();
|
|
5714
5748
|
this.#pendingSamples = 0;
|
|
5715
5749
|
}
|
|
5716
5750
|
};
|
|
@@ -5880,6 +5914,9 @@ function authoredGap(before, after) {
|
|
|
5880
5914
|
function ssIdAfter(a, b) {
|
|
5881
5915
|
return (a - b | 0) > 0;
|
|
5882
5916
|
}
|
|
5917
|
+
function holdsRebuilt(stream) {
|
|
5918
|
+
return stream.buf.some(({ meta }) => meta.fecSbnFirst !== void 0);
|
|
5919
|
+
}
|
|
5883
5920
|
var FecReleaseSequencer = class {
|
|
5884
5921
|
#streams = /* @__PURE__ */ new Map();
|
|
5885
5922
|
#gate;
|
|
@@ -5942,6 +5979,7 @@ var FecReleaseSequencer = class {
|
|
|
5942
5979
|
stream.lastSbn = void 0;
|
|
5943
5980
|
stream.discontinuity = false;
|
|
5944
5981
|
stream.rapSsId = void 0;
|
|
5982
|
+
stream.sourced = false;
|
|
5945
5983
|
}
|
|
5946
5984
|
if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
|
|
5947
5985
|
this.#recordStat("droppedStale");
|
|
@@ -5968,6 +6006,7 @@ var FecReleaseSequencer = class {
|
|
|
5968
6006
|
if (compareFrameOrder(stream.buf[mid], frame) < 0) lo = mid + 1;
|
|
5969
6007
|
else hi = mid;
|
|
5970
6008
|
}
|
|
6009
|
+
if (stream.buf[lo]?.passed === true) frame.passed = true;
|
|
5971
6010
|
stream.buf.splice(lo, 0, frame);
|
|
5972
6011
|
this.#drainUnlessDeferred(stream);
|
|
5973
6012
|
}
|
|
@@ -6003,6 +6042,41 @@ var FecReleaseSequencer = class {
|
|
|
6003
6042
|
}
|
|
6004
6043
|
return false;
|
|
6005
6044
|
}
|
|
6045
|
+
/**
|
|
6046
|
+
* These streams' sink is being replaced by one that has seen no source
|
|
6047
|
+
* frame yet. First, from a stream that holds a frame the overlay rebuilt,
|
|
6048
|
+
* release to the sink being replaced the frames the unanchored path had
|
|
6049
|
+
* already released to it (#cursorPassed): there they waited in its queue,
|
|
6050
|
+
* which its replacement flushes. A stream that holds none is not drained:
|
|
6051
|
+
* it holds nothing the anchor kept back, and a drain would still force out
|
|
6052
|
+
* a frame held past the hold limit, which before the anchor waited for the
|
|
6053
|
+
* next push. Then treat the streams as unsourced until a source frame is
|
|
6054
|
+
* released.
|
|
6055
|
+
*/
|
|
6056
|
+
unsource(packetId, catalogTrackName) {
|
|
6057
|
+
for (const stream of this.#streams.values()) {
|
|
6058
|
+
if (stream.packetId === packetId && (catalogTrackName === void 0 || stream.catalogTrackName === catalogTrackName)) {
|
|
6059
|
+
if (stream.draining === 0 && holdsRebuilt(stream)) {
|
|
6060
|
+
if (this.#deferDepth > 0) this.#deferredStreams.add(stream);
|
|
6061
|
+
else this.#drain(stream, true);
|
|
6062
|
+
}
|
|
6063
|
+
stream.sourced = false;
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
/**
|
|
6068
|
+
* Whether a stream of `packetId` holds a frame the keyframe overlay rebuilt
|
|
6069
|
+
* (FrameMeta.fecSbnFirst). Without `catalogTrackName`, every stream of the
|
|
6070
|
+
* packetId.
|
|
6071
|
+
*/
|
|
6072
|
+
holdsRebuilt(packetId, catalogTrackName) {
|
|
6073
|
+
for (const stream of this.#streams.values()) {
|
|
6074
|
+
if (stream.packetId !== packetId) continue;
|
|
6075
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
6076
|
+
if (holdsRebuilt(stream)) return true;
|
|
6077
|
+
}
|
|
6078
|
+
return false;
|
|
6079
|
+
}
|
|
6006
6080
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
6007
6081
|
poke(streamKey) {
|
|
6008
6082
|
if (streamKey !== void 0) {
|
|
@@ -6094,16 +6168,32 @@ var FecReleaseSequencer = class {
|
|
|
6094
6168
|
last: null,
|
|
6095
6169
|
lastSbn: void 0,
|
|
6096
6170
|
discontinuity: false,
|
|
6097
|
-
rapSsId: void 0
|
|
6171
|
+
rapSsId: void 0,
|
|
6172
|
+
sourced: false,
|
|
6173
|
+
draining: 0
|
|
6098
6174
|
};
|
|
6099
6175
|
this.#streams.set(streamKey, stream);
|
|
6100
6176
|
}
|
|
6101
6177
|
return stream;
|
|
6102
6178
|
}
|
|
6103
|
-
#
|
|
6179
|
+
/** `asUnanchored`: release overlay-rebuilt frames when unanchored did (#cursorPassed). */
|
|
6180
|
+
#drain(stream, asUnanchored = false) {
|
|
6181
|
+
stream.draining++;
|
|
6182
|
+
try {
|
|
6183
|
+
this.#drainLoop(stream, asUnanchored);
|
|
6184
|
+
} finally {
|
|
6185
|
+
stream.draining--;
|
|
6186
|
+
}
|
|
6187
|
+
}
|
|
6188
|
+
#drainLoop(stream, asUnanchored) {
|
|
6104
6189
|
while (stream.buf.length > 0 && stream.last !== null) {
|
|
6105
6190
|
const head = stream.buf[0];
|
|
6106
6191
|
const last = stream.last;
|
|
6192
|
+
if (compareFrameOrder(head, last) <= 0) {
|
|
6193
|
+
stream.buf.shift();
|
|
6194
|
+
this.#release(stream, head, void 0);
|
|
6195
|
+
continue;
|
|
6196
|
+
}
|
|
6107
6197
|
if (stream.discontinuity) {
|
|
6108
6198
|
stream.discontinuity = false;
|
|
6109
6199
|
stream.buf.shift();
|
|
@@ -6117,9 +6207,10 @@ var FecReleaseSequencer = class {
|
|
|
6117
6207
|
}
|
|
6118
6208
|
const headSbn = head.meta.fecSbn;
|
|
6119
6209
|
const from = stream.lastSbn ?? headSbn;
|
|
6120
|
-
const to = headSbn ?? stream.lastSbn;
|
|
6210
|
+
const to = head.meta.fecSbnFirst ?? headSbn ?? stream.lastSbn;
|
|
6121
6211
|
let lost = [];
|
|
6122
6212
|
const unanchored = from === void 0 && to === void 0;
|
|
6213
|
+
let unresolved = false;
|
|
6123
6214
|
if (from !== void 0 && to !== void 0) {
|
|
6124
6215
|
const resolved = this.#gate.resolvedLoss(
|
|
6125
6216
|
stream.packetId,
|
|
@@ -6128,7 +6219,8 @@ var FecReleaseSequencer = class {
|
|
|
6128
6219
|
Math.max(from, to),
|
|
6129
6220
|
from
|
|
6130
6221
|
);
|
|
6131
|
-
|
|
6222
|
+
unresolved = resolved === null;
|
|
6223
|
+
if (unresolved && !this.#cursorPassed(stream, asUnanchored)) {
|
|
6132
6224
|
const heldMs = performance.now() - head.arrivedAtMs;
|
|
6133
6225
|
if (heldMs <= this.#gate.holdLimitMs(stream.packetId, stream.catalogTrackName)) return;
|
|
6134
6226
|
this.#recordStat("forcedWindowFlushes");
|
|
@@ -6136,22 +6228,56 @@ var FecReleaseSequencer = class {
|
|
|
6136
6228
|
this.#release(stream, head, this.#gate.decide(last, head, [], true));
|
|
6137
6229
|
continue;
|
|
6138
6230
|
}
|
|
6139
|
-
lost = resolved;
|
|
6231
|
+
lost = resolved ?? [];
|
|
6140
6232
|
}
|
|
6141
6233
|
stream.buf.shift();
|
|
6142
6234
|
this.#release(
|
|
6143
6235
|
stream,
|
|
6144
6236
|
head,
|
|
6145
|
-
lost.length > 0 || unanchored || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
6237
|
+
lost.length > 0 || unanchored || unresolved || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
6146
6238
|
);
|
|
6147
6239
|
}
|
|
6148
6240
|
}
|
|
6241
|
+
/**
|
|
6242
|
+
* An overlay-rebuilt frame (fecSbnFirst) waits no longer than the BLO-53342
|
|
6243
|
+
* source cursor holds it unanchored, and neither does a later frame behind
|
|
6244
|
+
* it. Unanchored, a frame that waits on no block (fecSbnFirst set, or no
|
|
6245
|
+
* fecSbn) left the gate once every frame ahead of it had: mark it `passed`,
|
|
6246
|
+
* like each frame that arrived in front of it since (stale there). The head
|
|
6247
|
+
* then goes, decided as it would be unanchored, once a frame the base path
|
|
6248
|
+
* delivered (a source frame, or one base FEC recovered) is buffered after a
|
|
6249
|
+
* marked one: the base path has passed that one, so a frame still missing
|
|
6250
|
+
* before it can only come from a repair. It also goes while no source frame
|
|
6251
|
+
* has been released, when the direct path shows a recovered frame at once.
|
|
6252
|
+
* And it goes at once while a staged switch decides on when frames arrive
|
|
6253
|
+
* (FecReleaseGate.releaseUnanchored), and when unsource releases into a
|
|
6254
|
+
* sink being replaced: unanchored, it had already reached the sink.
|
|
6255
|
+
* The scan also writes: the `passed` marks it sets stay on the frames,
|
|
6256
|
+
* and push marks a frame it inserts in front of a marked one from them.
|
|
6257
|
+
*/
|
|
6258
|
+
#cursorPassed(stream, asUnanchored) {
|
|
6259
|
+
let at = -1;
|
|
6260
|
+
for (let i = 0; i < stream.buf.length; i++) {
|
|
6261
|
+
const frame = stream.buf[i];
|
|
6262
|
+
if (frame.meta.fecSbnFirst !== void 0 || frame.meta.fecSbn === void 0) {
|
|
6263
|
+
frame.passed = true;
|
|
6264
|
+
if (at < 0) at = i;
|
|
6265
|
+
} else if (frame.passed !== true) break;
|
|
6266
|
+
}
|
|
6267
|
+
if (at < 0) return false;
|
|
6268
|
+
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));
|
|
6269
|
+
}
|
|
6149
6270
|
#release(stream, frame, decision) {
|
|
6271
|
+
const stale = stream.last !== null && compareFrameOrder(frame, stream.last) <= 0;
|
|
6272
|
+
if (stale) this.#recordStat("droppedStale");
|
|
6150
6273
|
if (decision) frame.meta.gapBefore = decision;
|
|
6151
6274
|
stream.last = frame;
|
|
6152
|
-
if (frame.meta.fecSbn !== void 0
|
|
6275
|
+
if (frame.meta.fecSbn !== void 0 && (frame.meta.fecSbnFirst === void 0 || stream.lastSbn !== void 0)) {
|
|
6276
|
+
stream.lastSbn = frame.meta.fecSbn;
|
|
6277
|
+
}
|
|
6278
|
+
if (frame.meta.recovered !== true) stream.sourced = true;
|
|
6153
6279
|
this.#gate.onReleased?.(frame.data, frame.meta);
|
|
6154
|
-
this.#emit(frame.data, frame.meta);
|
|
6280
|
+
if (!stale) this.#emit(frame.data, frame.meta);
|
|
6155
6281
|
}
|
|
6156
6282
|
};
|
|
6157
6283
|
var FMPU_BOX_TYPES = /* @__PURE__ */ new Set(["ftyp", "moov", "moof"]);
|
|
@@ -6192,6 +6318,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6192
6318
|
this.#reclaimedTimingSamples = 0;
|
|
6193
6319
|
this.#evictedTimingSamples = 0;
|
|
6194
6320
|
this.#rejectedMovieFragments = 0;
|
|
6321
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
6322
|
+
held: 0,
|
|
6323
|
+
expired: 0,
|
|
6324
|
+
discarded: 0,
|
|
6325
|
+
superseded: 0
|
|
6326
|
+
};
|
|
6195
6327
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
6196
6328
|
this.#fecGate = null;
|
|
6197
6329
|
this.#fecSequencer = null;
|
|
@@ -6300,6 +6432,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6300
6432
|
#reclaimedTimingSamples;
|
|
6301
6433
|
#evictedTimingSamples;
|
|
6302
6434
|
#rejectedMovieFragments;
|
|
6435
|
+
#recoveredHeadsAwaitingTiming;
|
|
6303
6436
|
#sequencers;
|
|
6304
6437
|
#fecGate;
|
|
6305
6438
|
#fecSequencer;
|
|
@@ -6405,11 +6538,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6405
6538
|
/**
|
|
6406
6539
|
* Typed view of the ISO-BMFF timing-sample budget.
|
|
6407
6540
|
*
|
|
6408
|
-
*
|
|
6409
|
-
*
|
|
6410
|
-
*
|
|
6411
|
-
*
|
|
6412
|
-
*
|
|
6541
|
+
* Each stream admits timing against its own window of `capacity` slots
|
|
6542
|
+
* (BLO-52085). A stream at capacity displaces its own oldest timing to
|
|
6543
|
+
* admit the moof in hand, and `evicted` counts the slots so displaced:
|
|
6544
|
+
* timing a stream declared and never delivered shows up there. `pending`
|
|
6545
|
+
* sums every stream, so it can exceed `capacity`. Per repo policy this is
|
|
6546
|
+
* a producer-owned counter -- never infer budget state by parsing logs.
|
|
6413
6547
|
*/
|
|
6414
6548
|
get timingBudgetStats() {
|
|
6415
6549
|
return {
|
|
@@ -6420,6 +6554,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6420
6554
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
6421
6555
|
};
|
|
6422
6556
|
}
|
|
6557
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
6558
|
+
get recoveredHeadsAwaitingTiming() {
|
|
6559
|
+
let pending = 0;
|
|
6560
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
6561
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
6562
|
+
}
|
|
6563
|
+
return { ...this.#recoveredHeadsAwaitingTiming, pending };
|
|
6564
|
+
}
|
|
6423
6565
|
get reorderStats() {
|
|
6424
6566
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
6425
6567
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -6461,6 +6603,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6461
6603
|
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
6462
6604
|
return this.#fecSequencer?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
|
|
6463
6605
|
}
|
|
6606
|
+
/** See FecReleaseSequencer.unsource. */
|
|
6607
|
+
unsourceFecRelease(packetId, catalogTrackName) {
|
|
6608
|
+
this.#fecSequencer?.unsource(packetId, catalogTrackName);
|
|
6609
|
+
}
|
|
6610
|
+
/** See FecReleaseSequencer.holdsRebuilt. */
|
|
6611
|
+
fecReleaseHoldsRebuilt(packetId, catalogTrackName) {
|
|
6612
|
+
return this.#fecSequencer?.holdsRebuilt(packetId, catalogTrackName) ?? false;
|
|
6613
|
+
}
|
|
6464
6614
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
6465
6615
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
6466
6616
|
if (!this.#fecSequencer) return;
|
|
@@ -6593,7 +6743,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6593
6743
|
const now = Date.now();
|
|
6594
6744
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
6595
6745
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
6596
|
-
awaitingTiming.shift();
|
|
6746
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
6597
6747
|
}
|
|
6598
6748
|
}
|
|
6599
6749
|
}
|
|
@@ -6711,11 +6861,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6711
6861
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
6712
6862
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
6713
6863
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
6714
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
6864
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
6715
6865
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
6716
6866
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
6717
|
-
const remaining = this.#maxPendingTimingSamples -
|
|
6718
|
-
const admitted = resolver2.addMovieFragment(
|
|
6867
|
+
const remaining = this.#maxPendingTimingSamples - resolver2.pendingSampleCount;
|
|
6868
|
+
const admitted = resolver2.addMovieFragment(
|
|
6869
|
+
mpu.mpuSequenceNumber,
|
|
6870
|
+
data,
|
|
6871
|
+
remaining,
|
|
6872
|
+
options?.recovered === true
|
|
6873
|
+
);
|
|
6719
6874
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
6720
6875
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
6721
6876
|
if (!admitted) {
|
|
@@ -6735,7 +6890,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6735
6890
|
};
|
|
6736
6891
|
}
|
|
6737
6892
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
6738
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
6893
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
6739
6894
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
6740
6895
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
6741
6896
|
return this.#drop("mfu-empty-payload");
|
|
@@ -6756,6 +6911,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6756
6911
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
6757
6912
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
6758
6913
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
6914
|
+
sequenceNumber: info.sequenceNumber,
|
|
6759
6915
|
arrivedAt: Date.now()
|
|
6760
6916
|
});
|
|
6761
6917
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -6781,7 +6937,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6781
6937
|
info,
|
|
6782
6938
|
resolver,
|
|
6783
6939
|
options?.recovered === true,
|
|
6784
|
-
options?.fecSbn
|
|
6940
|
+
options?.fecSbn,
|
|
6941
|
+
options?.fecSbnFirst
|
|
6785
6942
|
);
|
|
6786
6943
|
if (deferred.record === void 0) this.#countDeferredOutcome(deferred.reason);
|
|
6787
6944
|
}
|
|
@@ -6874,7 +7031,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6874
7031
|
info,
|
|
6875
7032
|
resolver,
|
|
6876
7033
|
options?.recovered === true,
|
|
6877
|
-
options?.fecSbn
|
|
7034
|
+
options?.fecSbn,
|
|
7035
|
+
options?.fecSbnFirst
|
|
6878
7036
|
);
|
|
6879
7037
|
if (outcome.record !== void 0) return outcome.record;
|
|
6880
7038
|
return this.#drop(outcome.reason);
|
|
@@ -6894,7 +7052,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6894
7052
|
* attribution exists to keep readable. Leaving the choice at the call site
|
|
6895
7053
|
* is what keeps both true.
|
|
6896
7054
|
*/
|
|
6897
|
-
#emitCombinedSample(combined, info, resolver, recovered, fecSbn) {
|
|
7055
|
+
#emitCombinedSample(combined, info, resolver, recovered, fecSbn, fecSbnFirst) {
|
|
6898
7056
|
const timing = resolver.sampleTiming(
|
|
6899
7057
|
combined.mpuSequenceNumber,
|
|
6900
7058
|
combined.movieFragmentSequenceNumber,
|
|
@@ -6911,6 +7069,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6911
7069
|
);
|
|
6912
7070
|
if (!meta) return { reason: "combined-meta-build-failed" };
|
|
6913
7071
|
if (fecSbn !== void 0) meta.fecSbn = fecSbn;
|
|
7072
|
+
if (fecSbnFirst !== void 0) meta.fecSbnFirst = fecSbnFirst;
|
|
6914
7073
|
if (!resolver.consumeSample(
|
|
6915
7074
|
combined.mpuSequenceNumber,
|
|
6916
7075
|
combined.movieFragmentSequenceNumber,
|
|
@@ -7079,6 +7238,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
7079
7238
|
}
|
|
7080
7239
|
this.#emitFrame(data, meta);
|
|
7081
7240
|
}
|
|
7241
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
7242
|
+
hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
|
|
7243
|
+
return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
|
|
7244
|
+
}
|
|
7082
7245
|
/**
|
|
7083
7246
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
7084
7247
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
|
@@ -7140,6 +7303,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
7140
7303
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
7141
7304
|
this.#sequencers.clear();
|
|
7142
7305
|
this.#fecSequencer?.discard();
|
|
7306
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
7307
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
7308
|
+
}
|
|
7143
7309
|
this.#timingStreams.clear();
|
|
7144
7310
|
this.#pendingTimingSamples = 0;
|
|
7145
7311
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -7158,10 +7324,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
7158
7324
|
return resolver;
|
|
7159
7325
|
}
|
|
7160
7326
|
#awaitTiming(streamKey, head) {
|
|
7161
|
-
const
|
|
7162
|
-
if (!
|
|
7327
|
+
const state = this.#timingStreams.get(streamKey);
|
|
7328
|
+
if (!state) return;
|
|
7329
|
+
if (!head.options?.recovered) {
|
|
7330
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
7331
|
+
this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
|
|
7332
|
+
state.awaitingTiming = kept;
|
|
7333
|
+
}
|
|
7334
|
+
const awaitingTiming = state.awaitingTiming;
|
|
7163
7335
|
awaitingTiming.push(head);
|
|
7164
|
-
if (
|
|
7336
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
|
|
7337
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
7338
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
7339
|
+
}
|
|
7340
|
+
}
|
|
7341
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
7342
|
+
#leaveAwaitingTiming(head, outcome) {
|
|
7343
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
|
|
7165
7344
|
}
|
|
7166
7345
|
/**
|
|
7167
7346
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -7189,6 +7368,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
7189
7368
|
const state = this.#timingStreams.get(streamKey);
|
|
7190
7369
|
if (!state) return;
|
|
7191
7370
|
this.#timingStreams.delete(streamKey);
|
|
7371
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
7192
7372
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
7193
7373
|
if (this.#pendingTimingSamples < 0) {
|
|
7194
7374
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|