@blockcast/mmt-container 2.1.0 → 2.2.0-main.050b52fb9b00
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 +1949 -374
- package/dist/browser-runtime.js.map +4 -4
- package/dist/carp-catalog.js.map +1 -1
- 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/catalog-network-source.js.map +1 -1
- package/dist/cmcd-producer.d.ts +65 -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-state.js.map +1 -1
- 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/held-layer-admission.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -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/mfu-reassembler.js +12 -19
- package/dist/mfu-reassembler.js.map +2 -2
- 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 +219 -45
- package/dist/mmtp-container-parser.js.map +2 -2
- package/dist/mmtp-container-parser.no-modules.js +342 -63
- package/dist/mmtp-container-parser.no-modules.js.map +3 -3
- package/dist/mmtp-decode-pipeline.js +218 -45
- package/dist/mmtp-decode-pipeline.js.map +2 -2
- package/dist/mmtp-fec-session.d.ts +41 -3
- package/dist/mmtp-fec-session.d.ts.map +1 -1
- package/dist/mmtp-fec-session.js +118 -13
- package/dist/mmtp-fec-session.js.map +3 -3
- package/dist/mmtp-router.d.ts +43 -9
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +2006 -95
- package/dist/mmtp-router.js.map +4 -4
- package/dist/multicast-auth-catalog.js.map +1 -1
- package/dist/multicast-input.js.map +1 -1
- package/dist/multicast-manager.d.ts +28 -5
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +16116 -15005
- package/dist/multicast-manager.js.map +4 -4
- package/dist/multicast-transport.d.ts +11 -3
- package/dist/multicast-transport.d.ts.map +1 -1
- package/dist/multicast-transport.js +281 -35
- package/dist/multicast-transport.js.map +4 -4
- package/dist/multicast-unwrap.js +12 -19
- package/dist/multicast-unwrap.js.map +2 -2
- package/dist/no-modules.js +336 -59
- package/dist/no-modules.js.map +3 -3
- 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 +82 -0
- package/dist/repair-layer-subscriptions.d.ts.map +1 -0
- package/dist/repair-layer-subscriptions.js +192 -0
- package/dist/repair-layer-subscriptions.js.map +7 -0
- package/dist/signaling.js.map +2 -2
- package/dist/timestamp-codec.js +12 -19
- package/dist/timestamp-codec.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-mfu-reassembler.js.map +1 -1
- package/dist/wasm-mmtp-container-parser.js +218 -45
- package/dist/wasm-mmtp-container-parser.js.map +2 -2
- package/package.json +8 -5
- package/schemas/catalog.schema.json +4 -0
- package/src/catalog-fec-params.ts +36 -1
- package/src/cmcd-producer.ts +194 -179
- package/src/fec-manager.ts +433 -32
- package/src/fec-worker-setup.ts +2 -2
- package/src/index.ts +20 -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-fec-session.ts +115 -15
- package/src/mmtp-router.ts +629 -54
- package/src/multicast-manager.ts +70 -11
- package/src/multicast-transport.ts +44 -20
- package/src/repair-fetch.ts +363 -49
- package/src/repair-layer-subscriptions.ts +261 -0
- package/src/track-session.ts +4 -1
|
@@ -148,30 +148,26 @@ function isEmptyOrExactLegacyTrailer(payload, start, end) {
|
|
|
148
148
|
const authLength = view2.getUint16(2, false);
|
|
149
149
|
return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
|
|
150
150
|
}
|
|
151
|
-
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options
|
|
151
|
+
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
|
|
152
152
|
if (declaredEnd > payload.length)
|
|
153
153
|
return null;
|
|
154
154
|
if (options?.skipFecId) {
|
|
155
|
-
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false
|
|
155
|
+
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
|
|
156
156
|
}
|
|
157
|
-
const overlay = options?.overlayDeclared === true && rapFlag;
|
|
158
157
|
if (fecType === 1) {
|
|
159
|
-
const fecStart = payload.length -
|
|
158
|
+
const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
160
159
|
if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
|
|
161
160
|
return null;
|
|
162
161
|
}
|
|
163
|
-
return { hasFecId: true
|
|
162
|
+
return { hasFecId: true };
|
|
164
163
|
}
|
|
165
|
-
if (fecType !== 0
|
|
164
|
+
if (fecType !== 0)
|
|
166
165
|
return null;
|
|
167
166
|
if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
|
|
168
|
-
return { hasFecId: false
|
|
167
|
+
return { hasFecId: false };
|
|
169
168
|
}
|
|
170
169
|
return null;
|
|
171
170
|
}
|
|
172
|
-
function sourceFecTrailerSize(overlay) {
|
|
173
|
-
return overlay ? 2 * SOURCE_FEC_PAYLOAD_ID_SIZE : SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
174
|
-
}
|
|
175
171
|
function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
176
172
|
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
|
|
177
173
|
return {
|
|
@@ -224,11 +220,11 @@ function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
|
224
220
|
if ("rejection" in layoutResult)
|
|
225
221
|
return layoutResult;
|
|
226
222
|
const { layout } = layoutResult;
|
|
227
|
-
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options
|
|
223
|
+
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
|
|
228
224
|
if (!trailerLayout) {
|
|
229
225
|
if (!options?.skipFecId && header.fecType === 1) {
|
|
230
226
|
const observed = payload.length - layout.dataEnd;
|
|
231
|
-
const expected =
|
|
227
|
+
const expected = SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
232
228
|
if (observed !== expected) {
|
|
233
229
|
return {
|
|
234
230
|
rejection: {
|
|
@@ -260,13 +256,10 @@ function extractPlannedMfu(mmtpPacket, result) {
|
|
|
260
256
|
const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
|
|
261
257
|
const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
|
|
262
258
|
const data = payload.subarray(mfuOffset, dataEnd);
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
const fecPayloadId = trailerLayout.hasFecId ? ssIdAt(trailerStart) : null;
|
|
268
|
-
const overlayFecPayloadId = trailerLayout.hasOverlayFecId ? ssIdAt(trailerStart + SOURCE_FEC_PAYLOAD_ID_SIZE) : null;
|
|
269
|
-
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId, overlayFecPayloadId };
|
|
259
|
+
const fecPayloadId = trailerLayout.hasFecId ? {
|
|
260
|
+
ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, 4).getUint32(0, false)
|
|
261
|
+
} : null;
|
|
262
|
+
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
|
|
270
263
|
}
|
|
271
264
|
function sourceFecPayloadIdOf(mmtpPacket, header) {
|
|
272
265
|
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
|
|
@@ -4858,9 +4851,21 @@ var IsoBmffTimingResolver = class {
|
|
|
4858
4851
|
}
|
|
4859
4852
|
#track = null;
|
|
4860
4853
|
#fragments = /* @__PURE__ */ new Map();
|
|
4854
|
+
/** First sample of each pending fragment, kept in `#delivered` with its sample count. */
|
|
4855
|
+
#firstSamples = /* @__PURE__ */ new Map();
|
|
4856
|
+
/**
|
|
4857
|
+
* First sample and sample count of the newest fragments whose every slot was
|
|
4858
|
+
* cleared. Abandonment counts: `releaseSample` clears a slot too, which
|
|
4859
|
+
* matches the combiner, since it retires an abandoned sample as well.
|
|
4860
|
+
*/
|
|
4861
|
+
#delivered = /* @__PURE__ */ new Map();
|
|
4861
4862
|
#pendingSamples = 0;
|
|
4862
4863
|
#evictedSamples = 0;
|
|
4863
4864
|
#lastMovieFragmentRejection = null;
|
|
4865
|
+
/** True once an FT=0 has bound the track that FT=1 timing is read against. */
|
|
4866
|
+
get initialized() {
|
|
4867
|
+
return this.#track !== null;
|
|
4868
|
+
}
|
|
4864
4869
|
get pendingSampleCount() {
|
|
4865
4870
|
return this.#pendingSamples;
|
|
4866
4871
|
}
|
|
@@ -4884,7 +4889,14 @@ var IsoBmffTimingResolver = class {
|
|
|
4884
4889
|
this.#track = timing;
|
|
4885
4890
|
return true;
|
|
4886
4891
|
}
|
|
4887
|
-
|
|
4892
|
+
/**
|
|
4893
|
+
* `recovered` marks an FEC-recovered copy. A recovered block hands back
|
|
4894
|
+
* every FT=1 it holds, the received ones included, so a recovered copy of a
|
|
4895
|
+
* fragment whose samples are all done is a duplicate: admitting it again
|
|
4896
|
+
* would charge stale timing and could displace the live MPU's. A source
|
|
4897
|
+
* copy is still admitted again (a rendition switch re-arms timing that way).
|
|
4898
|
+
*/
|
|
4899
|
+
addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
|
|
4888
4900
|
this.#lastMovieFragmentRejection = null;
|
|
4889
4901
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
4890
4902
|
this.#lastMovieFragmentRejection = "invalid";
|
|
@@ -4910,6 +4922,8 @@ var IsoBmffTimingResolver = class {
|
|
|
4910
4922
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
4911
4923
|
);
|
|
4912
4924
|
}
|
|
4925
|
+
const delivered = recovered ? this.#delivered.get(key) : void 0;
|
|
4926
|
+
if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
|
|
4913
4927
|
let freed = 0;
|
|
4914
4928
|
while (this.#fragments.size > 0 && (this.#pendingSamples + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
4915
4929
|
freed += this.#evictOldestFragment();
|
|
@@ -4923,6 +4937,7 @@ var IsoBmffTimingResolver = class {
|
|
|
4923
4937
|
return false;
|
|
4924
4938
|
}
|
|
4925
4939
|
this.#fragments.set(key, [...parsed.samples]);
|
|
4940
|
+
this.#firstSamples.set(key, parsed.samples[0]);
|
|
4926
4941
|
this.#pendingSamples += parsed.samples.length;
|
|
4927
4942
|
return true;
|
|
4928
4943
|
}
|
|
@@ -4980,6 +4995,7 @@ var IsoBmffTimingResolver = class {
|
|
|
4980
4995
|
let live = 0;
|
|
4981
4996
|
for (const sample of samples) if (sample !== null) live++;
|
|
4982
4997
|
this.#fragments.delete(oldest);
|
|
4998
|
+
this.#firstSamples.delete(oldest);
|
|
4983
4999
|
this.#pendingSamples -= live;
|
|
4984
5000
|
this.#evictedSamples += live;
|
|
4985
5001
|
return live;
|
|
@@ -4995,12 +5011,23 @@ var IsoBmffTimingResolver = class {
|
|
|
4995
5011
|
if (!samples || !samples[index]) return false;
|
|
4996
5012
|
samples[index] = null;
|
|
4997
5013
|
this.#pendingSamples--;
|
|
4998
|
-
if (samples.every((value) => value === null))
|
|
5014
|
+
if (samples.every((value) => value === null)) {
|
|
5015
|
+
this.#fragments.delete(key);
|
|
5016
|
+
const first = this.#firstSamples.get(key);
|
|
5017
|
+
this.#firstSamples.delete(key);
|
|
5018
|
+
this.#delivered.delete(key);
|
|
5019
|
+
this.#delivered.set(key, { first, sampleCount: samples.length });
|
|
5020
|
+
if (this.#delivered.size > this.maxPendingSamples) {
|
|
5021
|
+
this.#delivered.delete(this.#delivered.keys().next().value);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
4999
5024
|
return true;
|
|
5000
5025
|
}
|
|
5001
5026
|
reset() {
|
|
5002
5027
|
this.#track = null;
|
|
5003
5028
|
this.#fragments.clear();
|
|
5029
|
+
this.#firstSamples.clear();
|
|
5030
|
+
this.#delivered.clear();
|
|
5004
5031
|
this.#pendingSamples = 0;
|
|
5005
5032
|
}
|
|
5006
5033
|
};
|
|
@@ -5170,6 +5197,9 @@ function authoredGap(before, after) {
|
|
|
5170
5197
|
function ssIdAfter(a, b) {
|
|
5171
5198
|
return (a - b | 0) > 0;
|
|
5172
5199
|
}
|
|
5200
|
+
function holdsRebuilt(stream) {
|
|
5201
|
+
return stream.buf.some(({ meta }) => meta.fecSbnFirst !== void 0);
|
|
5202
|
+
}
|
|
5173
5203
|
var FecReleaseSequencer = class {
|
|
5174
5204
|
#streams = /* @__PURE__ */ new Map();
|
|
5175
5205
|
#gate;
|
|
@@ -5232,6 +5262,7 @@ var FecReleaseSequencer = class {
|
|
|
5232
5262
|
stream.lastSbn = void 0;
|
|
5233
5263
|
stream.discontinuity = false;
|
|
5234
5264
|
stream.rapSsId = void 0;
|
|
5265
|
+
stream.sourced = false;
|
|
5235
5266
|
}
|
|
5236
5267
|
if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
|
|
5237
5268
|
this.#recordStat("droppedStale");
|
|
@@ -5258,6 +5289,7 @@ var FecReleaseSequencer = class {
|
|
|
5258
5289
|
if (compareFrameOrder(stream.buf[mid], frame) < 0) lo = mid + 1;
|
|
5259
5290
|
else hi = mid;
|
|
5260
5291
|
}
|
|
5292
|
+
if (stream.buf[lo]?.passed === true) frame.passed = true;
|
|
5261
5293
|
stream.buf.splice(lo, 0, frame);
|
|
5262
5294
|
this.#drainUnlessDeferred(stream);
|
|
5263
5295
|
}
|
|
@@ -5293,6 +5325,41 @@ var FecReleaseSequencer = class {
|
|
|
5293
5325
|
}
|
|
5294
5326
|
return false;
|
|
5295
5327
|
}
|
|
5328
|
+
/**
|
|
5329
|
+
* These streams' sink is being replaced by one that has seen no source
|
|
5330
|
+
* frame yet. First, from a stream that holds a frame the overlay rebuilt,
|
|
5331
|
+
* release to the sink being replaced the frames the unanchored path had
|
|
5332
|
+
* already released to it (#cursorPassed): there they waited in its queue,
|
|
5333
|
+
* which its replacement flushes. A stream that holds none is not drained:
|
|
5334
|
+
* it holds nothing the anchor kept back, and a drain would still force out
|
|
5335
|
+
* a frame held past the hold limit, which before the anchor waited for the
|
|
5336
|
+
* next push. Then treat the streams as unsourced until a source frame is
|
|
5337
|
+
* released.
|
|
5338
|
+
*/
|
|
5339
|
+
unsource(packetId, catalogTrackName) {
|
|
5340
|
+
for (const stream of this.#streams.values()) {
|
|
5341
|
+
if (stream.packetId === packetId && (catalogTrackName === void 0 || stream.catalogTrackName === catalogTrackName)) {
|
|
5342
|
+
if (stream.draining === 0 && holdsRebuilt(stream)) {
|
|
5343
|
+
if (this.#deferDepth > 0) this.#deferredStreams.add(stream);
|
|
5344
|
+
else this.#drain(stream, true);
|
|
5345
|
+
}
|
|
5346
|
+
stream.sourced = false;
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
/**
|
|
5351
|
+
* Whether a stream of `packetId` holds a frame the keyframe overlay rebuilt
|
|
5352
|
+
* (FrameMeta.fecSbnFirst). Without `catalogTrackName`, every stream of the
|
|
5353
|
+
* packetId.
|
|
5354
|
+
*/
|
|
5355
|
+
holdsRebuilt(packetId, catalogTrackName) {
|
|
5356
|
+
for (const stream of this.#streams.values()) {
|
|
5357
|
+
if (stream.packetId !== packetId) continue;
|
|
5358
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
5359
|
+
if (holdsRebuilt(stream)) return true;
|
|
5360
|
+
}
|
|
5361
|
+
return false;
|
|
5362
|
+
}
|
|
5296
5363
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
5297
5364
|
poke(streamKey) {
|
|
5298
5365
|
if (streamKey !== void 0) {
|
|
@@ -5384,16 +5451,32 @@ var FecReleaseSequencer = class {
|
|
|
5384
5451
|
last: null,
|
|
5385
5452
|
lastSbn: void 0,
|
|
5386
5453
|
discontinuity: false,
|
|
5387
|
-
rapSsId: void 0
|
|
5454
|
+
rapSsId: void 0,
|
|
5455
|
+
sourced: false,
|
|
5456
|
+
draining: 0
|
|
5388
5457
|
};
|
|
5389
5458
|
this.#streams.set(streamKey, stream);
|
|
5390
5459
|
}
|
|
5391
5460
|
return stream;
|
|
5392
5461
|
}
|
|
5393
|
-
#
|
|
5462
|
+
/** `asUnanchored`: release overlay-rebuilt frames when unanchored did (#cursorPassed). */
|
|
5463
|
+
#drain(stream, asUnanchored = false) {
|
|
5464
|
+
stream.draining++;
|
|
5465
|
+
try {
|
|
5466
|
+
this.#drainLoop(stream, asUnanchored);
|
|
5467
|
+
} finally {
|
|
5468
|
+
stream.draining--;
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
#drainLoop(stream, asUnanchored) {
|
|
5394
5472
|
while (stream.buf.length > 0 && stream.last !== null) {
|
|
5395
5473
|
const head = stream.buf[0];
|
|
5396
5474
|
const last = stream.last;
|
|
5475
|
+
if (compareFrameOrder(head, last) <= 0) {
|
|
5476
|
+
stream.buf.shift();
|
|
5477
|
+
this.#release(stream, head, void 0);
|
|
5478
|
+
continue;
|
|
5479
|
+
}
|
|
5397
5480
|
if (stream.discontinuity) {
|
|
5398
5481
|
stream.discontinuity = false;
|
|
5399
5482
|
stream.buf.shift();
|
|
@@ -5407,9 +5490,10 @@ var FecReleaseSequencer = class {
|
|
|
5407
5490
|
}
|
|
5408
5491
|
const headSbn = head.meta.fecSbn;
|
|
5409
5492
|
const from = stream.lastSbn ?? headSbn;
|
|
5410
|
-
const to = headSbn ?? stream.lastSbn;
|
|
5493
|
+
const to = head.meta.fecSbnFirst ?? headSbn ?? stream.lastSbn;
|
|
5411
5494
|
let lost = [];
|
|
5412
5495
|
const unanchored = from === void 0 && to === void 0;
|
|
5496
|
+
let unresolved = false;
|
|
5413
5497
|
if (from !== void 0 && to !== void 0) {
|
|
5414
5498
|
const resolved = this.#gate.resolvedLoss(
|
|
5415
5499
|
stream.packetId,
|
|
@@ -5418,7 +5502,8 @@ var FecReleaseSequencer = class {
|
|
|
5418
5502
|
Math.max(from, to),
|
|
5419
5503
|
from
|
|
5420
5504
|
);
|
|
5421
|
-
|
|
5505
|
+
unresolved = resolved === null;
|
|
5506
|
+
if (unresolved && !this.#cursorPassed(stream, asUnanchored)) {
|
|
5422
5507
|
const heldMs = performance.now() - head.arrivedAtMs;
|
|
5423
5508
|
if (heldMs <= this.#gate.holdLimitMs(stream.packetId, stream.catalogTrackName)) return;
|
|
5424
5509
|
this.#recordStat("forcedWindowFlushes");
|
|
@@ -5426,22 +5511,56 @@ var FecReleaseSequencer = class {
|
|
|
5426
5511
|
this.#release(stream, head, this.#gate.decide(last, head, [], true));
|
|
5427
5512
|
continue;
|
|
5428
5513
|
}
|
|
5429
|
-
lost = resolved;
|
|
5514
|
+
lost = resolved ?? [];
|
|
5430
5515
|
}
|
|
5431
5516
|
stream.buf.shift();
|
|
5432
5517
|
this.#release(
|
|
5433
5518
|
stream,
|
|
5434
5519
|
head,
|
|
5435
|
-
lost.length > 0 || unanchored || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
5520
|
+
lost.length > 0 || unanchored || unresolved || authoredGap(last.meta, head.meta) ? this.#gate.decide(last, head, lost, false) : void 0
|
|
5436
5521
|
);
|
|
5437
5522
|
}
|
|
5438
5523
|
}
|
|
5524
|
+
/**
|
|
5525
|
+
* An overlay-rebuilt frame (fecSbnFirst) waits no longer than the BLO-53342
|
|
5526
|
+
* source cursor holds it unanchored, and neither does a later frame behind
|
|
5527
|
+
* it. Unanchored, a frame that waits on no block (fecSbnFirst set, or no
|
|
5528
|
+
* fecSbn) left the gate once every frame ahead of it had: mark it `passed`,
|
|
5529
|
+
* like each frame that arrived in front of it since (stale there). The head
|
|
5530
|
+
* then goes, decided as it would be unanchored, once a frame the base path
|
|
5531
|
+
* delivered (a source frame, or one base FEC recovered) is buffered after a
|
|
5532
|
+
* marked one: the base path has passed that one, so a frame still missing
|
|
5533
|
+
* before it can only come from a repair. It also goes while no source frame
|
|
5534
|
+
* has been released, when the direct path shows a recovered frame at once.
|
|
5535
|
+
* And it goes at once while a staged switch decides on when frames arrive
|
|
5536
|
+
* (FecReleaseGate.releaseUnanchored), and when unsource releases into a
|
|
5537
|
+
* sink being replaced: unanchored, it had already reached the sink.
|
|
5538
|
+
* The scan also writes: the `passed` marks it sets stay on the frames,
|
|
5539
|
+
* and push marks a frame it inserts in front of a marked one from them.
|
|
5540
|
+
*/
|
|
5541
|
+
#cursorPassed(stream, asUnanchored) {
|
|
5542
|
+
let at = -1;
|
|
5543
|
+
for (let i = 0; i < stream.buf.length; i++) {
|
|
5544
|
+
const frame = stream.buf[i];
|
|
5545
|
+
if (frame.meta.fecSbnFirst !== void 0 || frame.meta.fecSbn === void 0) {
|
|
5546
|
+
frame.passed = true;
|
|
5547
|
+
if (at < 0) at = i;
|
|
5548
|
+
} else if (frame.passed !== true) break;
|
|
5549
|
+
}
|
|
5550
|
+
if (at < 0) return false;
|
|
5551
|
+
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));
|
|
5552
|
+
}
|
|
5439
5553
|
#release(stream, frame, decision) {
|
|
5554
|
+
const stale = stream.last !== null && compareFrameOrder(frame, stream.last) <= 0;
|
|
5555
|
+
if (stale) this.#recordStat("droppedStale");
|
|
5440
5556
|
if (decision) frame.meta.gapBefore = decision;
|
|
5441
5557
|
stream.last = frame;
|
|
5442
|
-
if (frame.meta.fecSbn !== void 0
|
|
5558
|
+
if (frame.meta.fecSbn !== void 0 && (frame.meta.fecSbnFirst === void 0 || stream.lastSbn !== void 0)) {
|
|
5559
|
+
stream.lastSbn = frame.meta.fecSbn;
|
|
5560
|
+
}
|
|
5561
|
+
if (frame.meta.recovered !== true) stream.sourced = true;
|
|
5443
5562
|
this.#gate.onReleased?.(frame.data, frame.meta);
|
|
5444
|
-
this.#emit(frame.data, frame.meta);
|
|
5563
|
+
if (!stale) this.#emit(frame.data, frame.meta);
|
|
5445
5564
|
}
|
|
5446
5565
|
};
|
|
5447
5566
|
var FMPU_BOX_TYPES = /* @__PURE__ */ new Set(["ftyp", "moov", "moof"]);
|
|
@@ -5482,6 +5601,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
5482
5601
|
this.#reclaimedTimingSamples = 0;
|
|
5483
5602
|
this.#evictedTimingSamples = 0;
|
|
5484
5603
|
this.#rejectedMovieFragments = 0;
|
|
5604
|
+
this.#recoveredHeadsAwaitingTiming = {
|
|
5605
|
+
held: 0,
|
|
5606
|
+
expired: 0,
|
|
5607
|
+
discarded: 0,
|
|
5608
|
+
superseded: 0
|
|
5609
|
+
};
|
|
5485
5610
|
this.#sequencers = /* @__PURE__ */ new Map();
|
|
5486
5611
|
this.#fecGate = null;
|
|
5487
5612
|
this.#fecSequencer = null;
|
|
@@ -5590,6 +5715,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
5590
5715
|
#reclaimedTimingSamples;
|
|
5591
5716
|
#evictedTimingSamples;
|
|
5592
5717
|
#rejectedMovieFragments;
|
|
5718
|
+
#recoveredHeadsAwaitingTiming;
|
|
5593
5719
|
#sequencers;
|
|
5594
5720
|
#fecGate;
|
|
5595
5721
|
#fecSequencer;
|
|
@@ -5695,11 +5821,12 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
5695
5821
|
/**
|
|
5696
5822
|
* Typed view of the ISO-BMFF timing-sample budget.
|
|
5697
5823
|
*
|
|
5698
|
-
*
|
|
5699
|
-
*
|
|
5700
|
-
*
|
|
5701
|
-
*
|
|
5702
|
-
*
|
|
5824
|
+
* Each stream admits timing against its own window of `capacity` slots
|
|
5825
|
+
* (BLO-52085). A stream at capacity displaces its own oldest timing to
|
|
5826
|
+
* admit the moof in hand, and `evicted` counts the slots so displaced:
|
|
5827
|
+
* timing a stream declared and never delivered shows up there. `pending`
|
|
5828
|
+
* sums every stream, so it can exceed `capacity`. Per repo policy this is
|
|
5829
|
+
* a producer-owned counter -- never infer budget state by parsing logs.
|
|
5703
5830
|
*/
|
|
5704
5831
|
get timingBudgetStats() {
|
|
5705
5832
|
return {
|
|
@@ -5710,6 +5837,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
5710
5837
|
rejectedMovieFragments: this.#rejectedMovieFragments
|
|
5711
5838
|
};
|
|
5712
5839
|
}
|
|
5840
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
5841
|
+
get recoveredHeadsAwaitingTiming() {
|
|
5842
|
+
let pending = 0;
|
|
5843
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
5844
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
5845
|
+
}
|
|
5846
|
+
return { ...this.#recoveredHeadsAwaitingTiming, pending };
|
|
5847
|
+
}
|
|
5713
5848
|
get reorderStats() {
|
|
5714
5849
|
let bufferedFrames = this.#fecSequencer?.buffered ?? 0;
|
|
5715
5850
|
for (const { sequencer } of this.#sequencers.values()) bufferedFrames += sequencer.buffered;
|
|
@@ -5751,6 +5886,14 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
5751
5886
|
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
5752
5887
|
return this.#fecSequencer?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
|
|
5753
5888
|
}
|
|
5889
|
+
/** See FecReleaseSequencer.unsource. */
|
|
5890
|
+
unsourceFecRelease(packetId, catalogTrackName) {
|
|
5891
|
+
this.#fecSequencer?.unsource(packetId, catalogTrackName);
|
|
5892
|
+
}
|
|
5893
|
+
/** See FecReleaseSequencer.holdsRebuilt. */
|
|
5894
|
+
fecReleaseHoldsRebuilt(packetId, catalogTrackName) {
|
|
5895
|
+
return this.#fecSequencer?.holdsRebuilt(packetId, catalogTrackName) ?? false;
|
|
5896
|
+
}
|
|
5754
5897
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
5755
5898
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
5756
5899
|
if (!this.#fecSequencer) return;
|
|
@@ -5883,7 +6026,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
5883
6026
|
const now = Date.now();
|
|
5884
6027
|
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
5885
6028
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
5886
|
-
awaitingTiming.shift();
|
|
6029
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
5887
6030
|
}
|
|
5888
6031
|
}
|
|
5889
6032
|
}
|
|
@@ -6001,11 +6144,16 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6001
6144
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
6002
6145
|
return this.#drop("fragment-unexpected-fragmentation");
|
|
6003
6146
|
const resolver2 = this.#timingResolver(streamKey, info.packetId, false);
|
|
6004
|
-
if (!resolver2) return this.#drop("fragment-no-resolver");
|
|
6147
|
+
if (!resolver2?.initialized) return this.#drop("fragment-no-resolver");
|
|
6005
6148
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
6006
6149
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
6007
|
-
const remaining = this.#maxPendingTimingSamples -
|
|
6008
|
-
const admitted = resolver2.addMovieFragment(
|
|
6150
|
+
const remaining = this.#maxPendingTimingSamples - resolver2.pendingSampleCount;
|
|
6151
|
+
const admitted = resolver2.addMovieFragment(
|
|
6152
|
+
mpu.mpuSequenceNumber,
|
|
6153
|
+
data,
|
|
6154
|
+
remaining,
|
|
6155
|
+
options?.recovered === true
|
|
6156
|
+
);
|
|
6009
6157
|
this.#pendingTimingSamples += resolver2.pendingSampleCount - pendingBefore;
|
|
6010
6158
|
this.#evictedTimingSamples += resolver2.evictedSampleCount - evictedBefore;
|
|
6011
6159
|
if (!admitted) {
|
|
@@ -6025,7 +6173,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6025
6173
|
};
|
|
6026
6174
|
}
|
|
6027
6175
|
if (fragmentType !== 2) return this.#drop("unknown-fragment-type");
|
|
6028
|
-
const resolver = this.#timingResolver(streamKey, info.packetId,
|
|
6176
|
+
const resolver = this.#timingResolver(streamKey, info.packetId, true);
|
|
6029
6177
|
if (!resolver) return this.#drop("mfu-no-resolver");
|
|
6030
6178
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
6031
6179
|
return this.#drop("mfu-empty-payload");
|
|
@@ -6046,6 +6194,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6046
6194
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
6047
6195
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
6048
6196
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
6197
|
+
sequenceNumber: info.sequenceNumber,
|
|
6049
6198
|
arrivedAt: Date.now()
|
|
6050
6199
|
});
|
|
6051
6200
|
return this.#drop("mfu-sample-timing-missing");
|
|
@@ -6071,7 +6220,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6071
6220
|
info,
|
|
6072
6221
|
resolver,
|
|
6073
6222
|
options?.recovered === true,
|
|
6074
|
-
options?.fecSbn
|
|
6223
|
+
options?.fecSbn,
|
|
6224
|
+
options?.fecSbnFirst
|
|
6075
6225
|
);
|
|
6076
6226
|
if (deferred.record === void 0) this.#countDeferredOutcome(deferred.reason);
|
|
6077
6227
|
}
|
|
@@ -6164,7 +6314,8 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6164
6314
|
info,
|
|
6165
6315
|
resolver,
|
|
6166
6316
|
options?.recovered === true,
|
|
6167
|
-
options?.fecSbn
|
|
6317
|
+
options?.fecSbn,
|
|
6318
|
+
options?.fecSbnFirst
|
|
6168
6319
|
);
|
|
6169
6320
|
if (outcome.record !== void 0) return outcome.record;
|
|
6170
6321
|
return this.#drop(outcome.reason);
|
|
@@ -6184,7 +6335,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6184
6335
|
* attribution exists to keep readable. Leaving the choice at the call site
|
|
6185
6336
|
* is what keeps both true.
|
|
6186
6337
|
*/
|
|
6187
|
-
#emitCombinedSample(combined, info, resolver, recovered, fecSbn) {
|
|
6338
|
+
#emitCombinedSample(combined, info, resolver, recovered, fecSbn, fecSbnFirst) {
|
|
6188
6339
|
const timing = resolver.sampleTiming(
|
|
6189
6340
|
combined.mpuSequenceNumber,
|
|
6190
6341
|
combined.movieFragmentSequenceNumber,
|
|
@@ -6201,6 +6352,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6201
6352
|
);
|
|
6202
6353
|
if (!meta) return { reason: "combined-meta-build-failed" };
|
|
6203
6354
|
if (fecSbn !== void 0) meta.fecSbn = fecSbn;
|
|
6355
|
+
if (fecSbnFirst !== void 0) meta.fecSbnFirst = fecSbnFirst;
|
|
6204
6356
|
if (!resolver.consumeSample(
|
|
6205
6357
|
combined.mpuSequenceNumber,
|
|
6206
6358
|
combined.movieFragmentSequenceNumber,
|
|
@@ -6369,6 +6521,10 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6369
6521
|
}
|
|
6370
6522
|
this.#emitFrame(data, meta);
|
|
6371
6523
|
}
|
|
6524
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
6525
|
+
hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
|
|
6526
|
+
return this.#timingResolver(this.#streamKey(packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
|
|
6527
|
+
}
|
|
6372
6528
|
/**
|
|
6373
6529
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
6374
6530
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
|
@@ -6430,6 +6586,9 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6430
6586
|
for (const { sequencer } of this.#sequencers.values()) sequencer.discard();
|
|
6431
6587
|
this.#sequencers.clear();
|
|
6432
6588
|
this.#fecSequencer?.discard();
|
|
6589
|
+
for (const { awaitingTiming } of this.#timingStreams.values()) {
|
|
6590
|
+
for (const head of awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
6591
|
+
}
|
|
6433
6592
|
this.#timingStreams.clear();
|
|
6434
6593
|
this.#pendingTimingSamples = 0;
|
|
6435
6594
|
this.#reliableMoqStartupGate.clear();
|
|
@@ -6448,10 +6607,23 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6448
6607
|
return resolver;
|
|
6449
6608
|
}
|
|
6450
6609
|
#awaitTiming(streamKey, head) {
|
|
6451
|
-
const
|
|
6452
|
-
if (!
|
|
6610
|
+
const state = this.#timingStreams.get(streamKey);
|
|
6611
|
+
if (!state) return;
|
|
6612
|
+
if (!head.options?.recovered) {
|
|
6613
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
6614
|
+
this.#recoveredHeadsAwaitingTiming.superseded += state.awaitingTiming.length - kept.length;
|
|
6615
|
+
state.awaitingTiming = kept;
|
|
6616
|
+
}
|
|
6617
|
+
const awaitingTiming = state.awaitingTiming;
|
|
6453
6618
|
awaitingTiming.push(head);
|
|
6454
|
-
if (
|
|
6619
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming.held++;
|
|
6620
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
6621
|
+
this.#leaveAwaitingTiming(awaitingTiming.shift(), "expired");
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
6625
|
+
#leaveAwaitingTiming(head, outcome) {
|
|
6626
|
+
if (head.options?.recovered) this.#recoveredHeadsAwaitingTiming[outcome]++;
|
|
6455
6627
|
}
|
|
6456
6628
|
/**
|
|
6457
6629
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -6479,6 +6651,7 @@ var MmtpContainerParser = class _MmtpContainerParser {
|
|
|
6479
6651
|
const state = this.#timingStreams.get(streamKey);
|
|
6480
6652
|
if (!state) return;
|
|
6481
6653
|
this.#timingStreams.delete(streamKey);
|
|
6654
|
+
for (const head of state.awaitingTiming) this.#leaveAwaitingTiming(head, "discarded");
|
|
6482
6655
|
this.#pendingTimingSamples -= state.resolver.pendingSampleCount;
|
|
6483
6656
|
if (this.#pendingTimingSamples < 0) {
|
|
6484
6657
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -6710,6 +6883,7 @@ function findAnnexBStart(data, offset) {
|
|
|
6710
6883
|
}
|
|
6711
6884
|
export {
|
|
6712
6885
|
MmtpContainerParser,
|
|
6886
|
+
authoredAdjacent,
|
|
6713
6887
|
isMfuKeyframeMetadata
|
|
6714
6888
|
};
|
|
6715
6889
|
//# sourceMappingURL=mmtp-container-parser.js.map
|