@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
|
@@ -73,6 +73,8 @@ var BlockcastMmt = (() => {
|
|
|
73
73
|
var PACKET_TYPE_REPAIR = 3;
|
|
74
74
|
var MMTP_SIGNALING_PACKET_ID = 0;
|
|
75
75
|
var MMTP_PACKET_ID_MAX = 65535;
|
|
76
|
+
var FEC_CODE_REED_SOLOMON = 1;
|
|
77
|
+
var FEC_CODE_RAPTORQ = 3;
|
|
76
78
|
function parseMMTPHeader(buffer) {
|
|
77
79
|
if (buffer.length < MMTP_HEADER_SIZE) {
|
|
78
80
|
throw new Error(`MMTP packet too small: ${buffer.length} bytes`);
|
|
@@ -213,30 +215,26 @@ var BlockcastMmt = (() => {
|
|
|
213
215
|
const authLength = view2.getUint16(2, false);
|
|
214
216
|
return start + LEGACY_TRAILER_HEADER_SIZE + authLength === end;
|
|
215
217
|
}
|
|
216
|
-
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options
|
|
218
|
+
function resolveDeclaredTrailers(payload, declaredEnd, fecType, options) {
|
|
217
219
|
if (declaredEnd > payload.length)
|
|
218
220
|
return null;
|
|
219
221
|
if (options?.skipFecId) {
|
|
220
|
-
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false
|
|
222
|
+
return isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length) ? { hasFecId: false } : null;
|
|
221
223
|
}
|
|
222
|
-
const overlay = options?.overlayDeclared === true && rapFlag;
|
|
223
224
|
if (fecType === 1) {
|
|
224
|
-
const fecStart = payload.length -
|
|
225
|
+
const fecStart = payload.length - SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
225
226
|
if (fecStart < declaredEnd || !isEmptyOrExactLegacyTrailer(payload, declaredEnd, fecStart)) {
|
|
226
227
|
return null;
|
|
227
228
|
}
|
|
228
|
-
return { hasFecId: true
|
|
229
|
+
return { hasFecId: true };
|
|
229
230
|
}
|
|
230
|
-
if (fecType !== 0
|
|
231
|
+
if (fecType !== 0)
|
|
231
232
|
return null;
|
|
232
233
|
if (isEmptyOrExactLegacyTrailer(payload, declaredEnd, payload.length)) {
|
|
233
|
-
return { hasFecId: false
|
|
234
|
+
return { hasFecId: false };
|
|
234
235
|
}
|
|
235
236
|
return null;
|
|
236
237
|
}
|
|
237
|
-
function sourceFecTrailerSize(overlay) {
|
|
238
|
-
return overlay ? 2 * SOURCE_FEC_PAYLOAD_ID_SIZE : SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
239
|
-
}
|
|
240
238
|
function planMfuExtraction(mmtpPacket, options, parsedHeader) {
|
|
241
239
|
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE) {
|
|
242
240
|
return {
|
|
@@ -289,11 +287,11 @@ var BlockcastMmt = (() => {
|
|
|
289
287
|
if ("rejection" in layoutResult)
|
|
290
288
|
return layoutResult;
|
|
291
289
|
const { layout } = layoutResult;
|
|
292
|
-
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options
|
|
290
|
+
const trailerLayout = resolveDeclaredTrailers(payload, layout.dataEnd, header.fecType, options);
|
|
293
291
|
if (!trailerLayout) {
|
|
294
292
|
if (!options?.skipFecId && header.fecType === 1) {
|
|
295
293
|
const observed = payload.length - layout.dataEnd;
|
|
296
|
-
const expected =
|
|
294
|
+
const expected = SOURCE_FEC_PAYLOAD_ID_SIZE;
|
|
297
295
|
if (observed !== expected) {
|
|
298
296
|
return {
|
|
299
297
|
rejection: {
|
|
@@ -325,13 +323,10 @@ var BlockcastMmt = (() => {
|
|
|
325
323
|
const mfuDuHeader = mfuDuHeaderPresent && mpu.timedFlag === 1 ? parseTimedMfuDuHeader(payload, MPU_HEADER_SIZE) : null;
|
|
326
324
|
const itemId = mfuDuHeaderPresent && mpu.timedFlag === 0 ? new DataView(payload.buffer, payload.byteOffset + MPU_HEADER_SIZE, 4).getUint32(0, false) : null;
|
|
327
325
|
const data = payload.subarray(mfuOffset, dataEnd);
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
const fecPayloadId = trailerLayout.hasFecId ? ssIdAt(trailerStart) : null;
|
|
333
|
-
const overlayFecPayloadId = trailerLayout.hasOverlayFecId ? ssIdAt(trailerStart + SOURCE_FEC_PAYLOAD_ID_SIZE) : null;
|
|
334
|
-
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId, overlayFecPayloadId };
|
|
326
|
+
const fecPayloadId = trailerLayout.hasFecId ? {
|
|
327
|
+
ssId: new DataView(mmtpPacket.buffer, mmtpPacket.byteOffset + mmtpPacket.length - SOURCE_FEC_PAYLOAD_ID_SIZE, 4).getUint32(0, false)
|
|
328
|
+
} : null;
|
|
329
|
+
return { header, mpu, fragmentType, data, mfuDuHeader, itemId, fecPayloadId };
|
|
335
330
|
}
|
|
336
331
|
function sourceFecPayloadIdOf(mmtpPacket, header) {
|
|
337
332
|
if (mmtpPacket.length < MMTP_HEADER_SIZE + MPU_HEADER_SIZE)
|
|
@@ -423,6 +418,50 @@ var BlockcastMmt = (() => {
|
|
|
423
418
|
var NTP_SHORT_MODULUS = 2 ** NTP_SHORT_BITS;
|
|
424
419
|
var NTP_SHORT_HALF_RANGE = 2 ** (NTP_SHORT_BITS - 1);
|
|
425
420
|
|
|
421
|
+
// ../mmt-base/dist/signalling.js
|
|
422
|
+
var FEC_CODING_STRUCTURE_ONE_STAGE = 1;
|
|
423
|
+
var SSBG_MODE_1 = 1;
|
|
424
|
+
var RAPTORQ_MAX_SOURCE_SYMBOLS = 56403;
|
|
425
|
+
var REPAIR_SYMBOL_ID_MAX = 16777215;
|
|
426
|
+
var MIN_REPAIR_SYMBOL_SIZE = 14;
|
|
427
|
+
var REED_SOLOMON_MAX_BLOCK = 255;
|
|
428
|
+
function alFecRaptorQFlowConfig(flow) {
|
|
429
|
+
if (flow.fecCodingStructure !== FEC_CODING_STRUCTURE_ONE_STAGE || flow.repairFlows.length !== 1)
|
|
430
|
+
return null;
|
|
431
|
+
if (flow.ssbgMode !== SSBG_MODE_1 || flow.ffsrptsFlag !== 0 || flow.fecPayloadIdMode !== 0)
|
|
432
|
+
return null;
|
|
433
|
+
if (flow.packetIds.length !== 1)
|
|
434
|
+
return null;
|
|
435
|
+
const repair = flow.repairFlows[0];
|
|
436
|
+
const { fecCodeId, maximumK: k, maximumP: p } = repair;
|
|
437
|
+
const t = flow.repairSymbolSize;
|
|
438
|
+
if (t < MIN_REPAIR_SYMBOL_SIZE || k < 1 || p < 1 || k + p - 1 > REPAIR_SYMBOL_ID_MAX)
|
|
439
|
+
return null;
|
|
440
|
+
if (fecCodeId === FEC_CODE_RAPTORQ) {
|
|
441
|
+
if (t % 8 !== 0 || k > RAPTORQ_MAX_SOURCE_SYMBOLS)
|
|
442
|
+
return null;
|
|
443
|
+
} else if (fecCodeId === FEC_CODE_REED_SOLOMON) {
|
|
444
|
+
if (k + p > REED_SOLOMON_MAX_BLOCK)
|
|
445
|
+
return null;
|
|
446
|
+
} else {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
if (repair.repairFlowId === MMTP_SIGNALING_PACKET_ID || repair.repairFlowId === flow.packetIds[0])
|
|
450
|
+
return null;
|
|
451
|
+
return {
|
|
452
|
+
sourcePacketIds: [...flow.packetIds],
|
|
453
|
+
repairFlowId: repair.repairFlowId,
|
|
454
|
+
fecCodeId,
|
|
455
|
+
ssbgMode: flow.ssbgMode,
|
|
456
|
+
fecPayloadIdMode: flow.fecPayloadIdMode,
|
|
457
|
+
repairSymbolSize: t,
|
|
458
|
+
maximumK: k,
|
|
459
|
+
maximumP: p,
|
|
460
|
+
protectionWindowTime: repair.protectionWindowTime,
|
|
461
|
+
protectionWindowSize: repair.protectionWindowSize
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
426
465
|
// ../mmt-base/dist/serial-number.js
|
|
427
466
|
var SERIAL32_SPAN = 4294967296;
|
|
428
467
|
var SERIAL32_HALF = 2147483648;
|
|
@@ -5594,13 +5633,21 @@ var BlockcastMmt = (() => {
|
|
|
5594
5633
|
function sameSample(a, b) {
|
|
5595
5634
|
return a.decodeTime === b.decodeTime && a.presentationTime === b.presentationTime && a.duration === b.duration && a.timescale === b.timescale && a.size === b.size && a.bytePosition === b.bytePosition;
|
|
5596
5635
|
}
|
|
5597
|
-
var _track, _fragments, _pendingSamples, _evictedSamples, _lastMovieFragmentRejection, _IsoBmffTimingResolver_instances, evictOldestFragment_fn, clearSlot_fn;
|
|
5636
|
+
var _track, _fragments, _firstSamples, _delivered, _pendingSamples, _evictedSamples, _lastMovieFragmentRejection, _IsoBmffTimingResolver_instances, evictOldestFragment_fn, clearSlot_fn;
|
|
5598
5637
|
var IsoBmffTimingResolver = class {
|
|
5599
5638
|
constructor(maxPendingSamples) {
|
|
5600
5639
|
this.maxPendingSamples = maxPendingSamples;
|
|
5601
5640
|
__privateAdd(this, _IsoBmffTimingResolver_instances);
|
|
5602
5641
|
__privateAdd(this, _track, null);
|
|
5603
5642
|
__privateAdd(this, _fragments, /* @__PURE__ */ new Map());
|
|
5643
|
+
/** First sample of each pending fragment, kept in `#delivered` with its sample count. */
|
|
5644
|
+
__privateAdd(this, _firstSamples, /* @__PURE__ */ new Map());
|
|
5645
|
+
/**
|
|
5646
|
+
* First sample and sample count of the newest fragments whose every slot was
|
|
5647
|
+
* cleared. Abandonment counts: `releaseSample` clears a slot too, which
|
|
5648
|
+
* matches the combiner, since it retires an abandoned sample as well.
|
|
5649
|
+
*/
|
|
5650
|
+
__privateAdd(this, _delivered, /* @__PURE__ */ new Map());
|
|
5604
5651
|
__privateAdd(this, _pendingSamples, 0);
|
|
5605
5652
|
__privateAdd(this, _evictedSamples, 0);
|
|
5606
5653
|
__privateAdd(this, _lastMovieFragmentRejection, null);
|
|
@@ -5608,6 +5655,10 @@ var BlockcastMmt = (() => {
|
|
|
5608
5655
|
throw new Error("[IsoBmffTimingResolver] maxPendingSamples must be a positive integer");
|
|
5609
5656
|
}
|
|
5610
5657
|
}
|
|
5658
|
+
/** True once an FT=0 has bound the track that FT=1 timing is read against. */
|
|
5659
|
+
get initialized() {
|
|
5660
|
+
return __privateGet(this, _track) !== null;
|
|
5661
|
+
}
|
|
5611
5662
|
get pendingSampleCount() {
|
|
5612
5663
|
return __privateGet(this, _pendingSamples);
|
|
5613
5664
|
}
|
|
@@ -5631,7 +5682,14 @@ var BlockcastMmt = (() => {
|
|
|
5631
5682
|
__privateSet(this, _track, timing);
|
|
5632
5683
|
return true;
|
|
5633
5684
|
}
|
|
5634
|
-
|
|
5685
|
+
/**
|
|
5686
|
+
* `recovered` marks an FEC-recovered copy. A recovered block hands back
|
|
5687
|
+
* every FT=1 it holds, the received ones included, so a recovered copy of a
|
|
5688
|
+
* fragment whose samples are all done is a duplicate: admitting it again
|
|
5689
|
+
* would charge stale timing and could displace the live MPU's. A source
|
|
5690
|
+
* copy is still admitted again (a rendition switch re-arms timing that way).
|
|
5691
|
+
*/
|
|
5692
|
+
addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples, recovered = false) {
|
|
5635
5693
|
__privateSet(this, _lastMovieFragmentRejection, null);
|
|
5636
5694
|
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
5637
5695
|
__privateSet(this, _lastMovieFragmentRejection, "invalid");
|
|
@@ -5657,6 +5715,8 @@ var BlockcastMmt = (() => {
|
|
|
5657
5715
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
5658
5716
|
);
|
|
5659
5717
|
}
|
|
5718
|
+
const delivered = recovered ? __privateGet(this, _delivered).get(key) : void 0;
|
|
5719
|
+
if (delivered && delivered.sampleCount === parsed.samples.length && sameSample(delivered.first, parsed.samples[0])) return true;
|
|
5660
5720
|
let freed = 0;
|
|
5661
5721
|
while (__privateGet(this, _fragments).size > 0 && (__privateGet(this, _pendingSamples) + parsed.samples.length > this.maxPendingSamples || parsed.samples.length - freed > additionalSampleLimit)) {
|
|
5662
5722
|
freed += __privateMethod(this, _IsoBmffTimingResolver_instances, evictOldestFragment_fn).call(this);
|
|
@@ -5670,6 +5730,7 @@ var BlockcastMmt = (() => {
|
|
|
5670
5730
|
return false;
|
|
5671
5731
|
}
|
|
5672
5732
|
__privateGet(this, _fragments).set(key, [...parsed.samples]);
|
|
5733
|
+
__privateGet(this, _firstSamples).set(key, parsed.samples[0]);
|
|
5673
5734
|
__privateSet(this, _pendingSamples, __privateGet(this, _pendingSamples) + parsed.samples.length);
|
|
5674
5735
|
return true;
|
|
5675
5736
|
}
|
|
@@ -5716,11 +5777,15 @@ var BlockcastMmt = (() => {
|
|
|
5716
5777
|
reset() {
|
|
5717
5778
|
__privateSet(this, _track, null);
|
|
5718
5779
|
__privateGet(this, _fragments).clear();
|
|
5780
|
+
__privateGet(this, _firstSamples).clear();
|
|
5781
|
+
__privateGet(this, _delivered).clear();
|
|
5719
5782
|
__privateSet(this, _pendingSamples, 0);
|
|
5720
5783
|
}
|
|
5721
5784
|
};
|
|
5722
5785
|
_track = new WeakMap();
|
|
5723
5786
|
_fragments = new WeakMap();
|
|
5787
|
+
_firstSamples = new WeakMap();
|
|
5788
|
+
_delivered = new WeakMap();
|
|
5724
5789
|
_pendingSamples = new WeakMap();
|
|
5725
5790
|
_evictedSamples = new WeakMap();
|
|
5726
5791
|
_lastMovieFragmentRejection = new WeakMap();
|
|
@@ -5739,6 +5804,7 @@ var BlockcastMmt = (() => {
|
|
|
5739
5804
|
let live = 0;
|
|
5740
5805
|
for (const sample of samples) if (sample !== null) live++;
|
|
5741
5806
|
__privateGet(this, _fragments).delete(oldest);
|
|
5807
|
+
__privateGet(this, _firstSamples).delete(oldest);
|
|
5742
5808
|
__privateSet(this, _pendingSamples, __privateGet(this, _pendingSamples) - live);
|
|
5743
5809
|
__privateSet(this, _evictedSamples, __privateGet(this, _evictedSamples) + live);
|
|
5744
5810
|
return live;
|
|
@@ -5754,7 +5820,16 @@ var BlockcastMmt = (() => {
|
|
|
5754
5820
|
if (!samples || !samples[index]) return false;
|
|
5755
5821
|
samples[index] = null;
|
|
5756
5822
|
__privateWrapper(this, _pendingSamples)._--;
|
|
5757
|
-
if (samples.every((value) => value === null))
|
|
5823
|
+
if (samples.every((value) => value === null)) {
|
|
5824
|
+
__privateGet(this, _fragments).delete(key);
|
|
5825
|
+
const first = __privateGet(this, _firstSamples).get(key);
|
|
5826
|
+
__privateGet(this, _firstSamples).delete(key);
|
|
5827
|
+
__privateGet(this, _delivered).delete(key);
|
|
5828
|
+
__privateGet(this, _delivered).set(key, { first, sampleCount: samples.length });
|
|
5829
|
+
if (__privateGet(this, _delivered).size > this.maxPendingSamples) {
|
|
5830
|
+
__privateGet(this, _delivered).delete(__privateGet(this, _delivered).keys().next().value);
|
|
5831
|
+
}
|
|
5832
|
+
}
|
|
5758
5833
|
return true;
|
|
5759
5834
|
};
|
|
5760
5835
|
function fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber) {
|
|
@@ -5930,7 +6005,10 @@ var BlockcastMmt = (() => {
|
|
|
5930
6005
|
function ssIdAfter(a, b) {
|
|
5931
6006
|
return (a - b | 0) > 0;
|
|
5932
6007
|
}
|
|
5933
|
-
|
|
6008
|
+
function holdsRebuilt(stream) {
|
|
6009
|
+
return stream.buf.some(({ meta }) => meta.fecSbnFirst !== void 0);
|
|
6010
|
+
}
|
|
6011
|
+
var _streams2, _gate, _emit2, _recordStat2, _deferDepth, _deferredStreams, _FecReleaseSequencer_instances, drainUnlessDeferred_fn, streamFor_fn2, drain_fn, drainLoop_fn, cursorPassed_fn, release_fn;
|
|
5934
6012
|
var FecReleaseSequencer = class {
|
|
5935
6013
|
constructor(gate, emit, recordStat) {
|
|
5936
6014
|
__privateAdd(this, _FecReleaseSequencer_instances);
|
|
@@ -5990,6 +6068,7 @@ var BlockcastMmt = (() => {
|
|
|
5990
6068
|
stream.lastSbn = void 0;
|
|
5991
6069
|
stream.discontinuity = false;
|
|
5992
6070
|
stream.rapSsId = void 0;
|
|
6071
|
+
stream.sourced = false;
|
|
5993
6072
|
}
|
|
5994
6073
|
if (stream.last !== null && compareFrameOrder(frame, stream.last) <= 0) {
|
|
5995
6074
|
__privateGet(this, _recordStat2).call(this, "droppedStale");
|
|
@@ -6016,6 +6095,7 @@ var BlockcastMmt = (() => {
|
|
|
6016
6095
|
if (compareFrameOrder(stream.buf[mid], frame) < 0) lo = mid + 1;
|
|
6017
6096
|
else hi = mid;
|
|
6018
6097
|
}
|
|
6098
|
+
if (stream.buf[lo]?.passed === true) frame.passed = true;
|
|
6019
6099
|
stream.buf.splice(lo, 0, frame);
|
|
6020
6100
|
__privateMethod(this, _FecReleaseSequencer_instances, drainUnlessDeferred_fn).call(this, stream);
|
|
6021
6101
|
}
|
|
@@ -6051,6 +6131,41 @@ var BlockcastMmt = (() => {
|
|
|
6051
6131
|
}
|
|
6052
6132
|
return false;
|
|
6053
6133
|
}
|
|
6134
|
+
/**
|
|
6135
|
+
* These streams' sink is being replaced by one that has seen no source
|
|
6136
|
+
* frame yet. First, from a stream that holds a frame the overlay rebuilt,
|
|
6137
|
+
* release to the sink being replaced the frames the unanchored path had
|
|
6138
|
+
* already released to it (#cursorPassed): there they waited in its queue,
|
|
6139
|
+
* which its replacement flushes. A stream that holds none is not drained:
|
|
6140
|
+
* it holds nothing the anchor kept back, and a drain would still force out
|
|
6141
|
+
* a frame held past the hold limit, which before the anchor waited for the
|
|
6142
|
+
* next push. Then treat the streams as unsourced until a source frame is
|
|
6143
|
+
* released.
|
|
6144
|
+
*/
|
|
6145
|
+
unsource(packetId, catalogTrackName) {
|
|
6146
|
+
for (const stream of __privateGet(this, _streams2).values()) {
|
|
6147
|
+
if (stream.packetId === packetId && (catalogTrackName === void 0 || stream.catalogTrackName === catalogTrackName)) {
|
|
6148
|
+
if (stream.draining === 0 && holdsRebuilt(stream)) {
|
|
6149
|
+
if (__privateGet(this, _deferDepth) > 0) __privateGet(this, _deferredStreams).add(stream);
|
|
6150
|
+
else __privateMethod(this, _FecReleaseSequencer_instances, drain_fn).call(this, stream, true);
|
|
6151
|
+
}
|
|
6152
|
+
stream.sourced = false;
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
/**
|
|
6157
|
+
* Whether a stream of `packetId` holds a frame the keyframe overlay rebuilt
|
|
6158
|
+
* (FrameMeta.fecSbnFirst). Without `catalogTrackName`, every stream of the
|
|
6159
|
+
* packetId.
|
|
6160
|
+
*/
|
|
6161
|
+
holdsRebuilt(packetId, catalogTrackName) {
|
|
6162
|
+
for (const stream of __privateGet(this, _streams2).values()) {
|
|
6163
|
+
if (stream.packetId !== packetId) continue;
|
|
6164
|
+
if (catalogTrackName !== void 0 && stream.catalogTrackName !== catalogTrackName) continue;
|
|
6165
|
+
if (holdsRebuilt(stream)) return true;
|
|
6166
|
+
}
|
|
6167
|
+
return false;
|
|
6168
|
+
}
|
|
6054
6169
|
/** Re-evaluate held gaps after FEC state changed. */
|
|
6055
6170
|
poke(streamKey) {
|
|
6056
6171
|
if (streamKey !== void 0) {
|
|
@@ -6154,16 +6269,32 @@ var BlockcastMmt = (() => {
|
|
|
6154
6269
|
last: null,
|
|
6155
6270
|
lastSbn: void 0,
|
|
6156
6271
|
discontinuity: false,
|
|
6157
|
-
rapSsId: void 0
|
|
6272
|
+
rapSsId: void 0,
|
|
6273
|
+
sourced: false,
|
|
6274
|
+
draining: 0
|
|
6158
6275
|
};
|
|
6159
6276
|
__privateGet(this, _streams2).set(streamKey, stream);
|
|
6160
6277
|
}
|
|
6161
6278
|
return stream;
|
|
6162
6279
|
};
|
|
6163
|
-
|
|
6280
|
+
/** `asUnanchored`: release overlay-rebuilt frames when unanchored did (#cursorPassed). */
|
|
6281
|
+
drain_fn = function(stream, asUnanchored = false) {
|
|
6282
|
+
stream.draining++;
|
|
6283
|
+
try {
|
|
6284
|
+
__privateMethod(this, _FecReleaseSequencer_instances, drainLoop_fn).call(this, stream, asUnanchored);
|
|
6285
|
+
} finally {
|
|
6286
|
+
stream.draining--;
|
|
6287
|
+
}
|
|
6288
|
+
};
|
|
6289
|
+
drainLoop_fn = function(stream, asUnanchored) {
|
|
6164
6290
|
while (stream.buf.length > 0 && stream.last !== null) {
|
|
6165
6291
|
const head = stream.buf[0];
|
|
6166
6292
|
const last = stream.last;
|
|
6293
|
+
if (compareFrameOrder(head, last) <= 0) {
|
|
6294
|
+
stream.buf.shift();
|
|
6295
|
+
__privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, void 0);
|
|
6296
|
+
continue;
|
|
6297
|
+
}
|
|
6167
6298
|
if (stream.discontinuity) {
|
|
6168
6299
|
stream.discontinuity = false;
|
|
6169
6300
|
stream.buf.shift();
|
|
@@ -6177,9 +6308,10 @@ var BlockcastMmt = (() => {
|
|
|
6177
6308
|
}
|
|
6178
6309
|
const headSbn = head.meta.fecSbn;
|
|
6179
6310
|
const from = stream.lastSbn ?? headSbn;
|
|
6180
|
-
const to = headSbn ?? stream.lastSbn;
|
|
6311
|
+
const to = head.meta.fecSbnFirst ?? headSbn ?? stream.lastSbn;
|
|
6181
6312
|
let lost = [];
|
|
6182
6313
|
const unanchored = from === void 0 && to === void 0;
|
|
6314
|
+
let unresolved = false;
|
|
6183
6315
|
if (from !== void 0 && to !== void 0) {
|
|
6184
6316
|
const resolved = __privateGet(this, _gate).resolvedLoss(
|
|
6185
6317
|
stream.packetId,
|
|
@@ -6188,7 +6320,8 @@ var BlockcastMmt = (() => {
|
|
|
6188
6320
|
Math.max(from, to),
|
|
6189
6321
|
from
|
|
6190
6322
|
);
|
|
6191
|
-
|
|
6323
|
+
unresolved = resolved === null;
|
|
6324
|
+
if (unresolved && !__privateMethod(this, _FecReleaseSequencer_instances, cursorPassed_fn).call(this, stream, asUnanchored)) {
|
|
6192
6325
|
const heldMs = performance.now() - head.arrivedAtMs;
|
|
6193
6326
|
if (heldMs <= __privateGet(this, _gate).holdLimitMs(stream.packetId, stream.catalogTrackName)) return;
|
|
6194
6327
|
__privateGet(this, _recordStat2).call(this, "forcedWindowFlushes");
|
|
@@ -6196,18 +6329,52 @@ var BlockcastMmt = (() => {
|
|
|
6196
6329
|
__privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, __privateGet(this, _gate).decide(last, head, [], true));
|
|
6197
6330
|
continue;
|
|
6198
6331
|
}
|
|
6199
|
-
lost = resolved;
|
|
6332
|
+
lost = resolved ?? [];
|
|
6200
6333
|
}
|
|
6201
6334
|
stream.buf.shift();
|
|
6202
|
-
__privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, lost.length > 0 || unanchored || authoredGap(last.meta, head.meta) ? __privateGet(this, _gate).decide(last, head, lost, false) : void 0);
|
|
6335
|
+
__privateMethod(this, _FecReleaseSequencer_instances, release_fn).call(this, stream, head, lost.length > 0 || unanchored || unresolved || authoredGap(last.meta, head.meta) ? __privateGet(this, _gate).decide(last, head, lost, false) : void 0);
|
|
6203
6336
|
}
|
|
6204
6337
|
};
|
|
6338
|
+
/**
|
|
6339
|
+
* An overlay-rebuilt frame (fecSbnFirst) waits no longer than the BLO-53342
|
|
6340
|
+
* source cursor holds it unanchored, and neither does a later frame behind
|
|
6341
|
+
* it. Unanchored, a frame that waits on no block (fecSbnFirst set, or no
|
|
6342
|
+
* fecSbn) left the gate once every frame ahead of it had: mark it `passed`,
|
|
6343
|
+
* like each frame that arrived in front of it since (stale there). The head
|
|
6344
|
+
* then goes, decided as it would be unanchored, once a frame the base path
|
|
6345
|
+
* delivered (a source frame, or one base FEC recovered) is buffered after a
|
|
6346
|
+
* marked one: the base path has passed that one, so a frame still missing
|
|
6347
|
+
* before it can only come from a repair. It also goes while no source frame
|
|
6348
|
+
* has been released, when the direct path shows a recovered frame at once.
|
|
6349
|
+
* And it goes at once while a staged switch decides on when frames arrive
|
|
6350
|
+
* (FecReleaseGate.releaseUnanchored), and when unsource releases into a
|
|
6351
|
+
* sink being replaced: unanchored, it had already reached the sink.
|
|
6352
|
+
* The scan also writes: the `passed` marks it sets stay on the frames,
|
|
6353
|
+
* and push marks a frame it inserts in front of a marked one from them.
|
|
6354
|
+
*/
|
|
6355
|
+
cursorPassed_fn = function(stream, asUnanchored) {
|
|
6356
|
+
let at = -1;
|
|
6357
|
+
for (let i = 0; i < stream.buf.length; i++) {
|
|
6358
|
+
const frame = stream.buf[i];
|
|
6359
|
+
if (frame.meta.fecSbnFirst !== void 0 || frame.meta.fecSbn === void 0) {
|
|
6360
|
+
frame.passed = true;
|
|
6361
|
+
if (at < 0) at = i;
|
|
6362
|
+
} else if (frame.passed !== true) break;
|
|
6363
|
+
}
|
|
6364
|
+
if (at < 0) return false;
|
|
6365
|
+
return asUnanchored || !stream.sourced || __privateGet(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));
|
|
6366
|
+
};
|
|
6205
6367
|
release_fn = function(stream, frame, decision) {
|
|
6368
|
+
const stale = stream.last !== null && compareFrameOrder(frame, stream.last) <= 0;
|
|
6369
|
+
if (stale) __privateGet(this, _recordStat2).call(this, "droppedStale");
|
|
6206
6370
|
if (decision) frame.meta.gapBefore = decision;
|
|
6207
6371
|
stream.last = frame;
|
|
6208
|
-
if (frame.meta.fecSbn !== void 0
|
|
6372
|
+
if (frame.meta.fecSbn !== void 0 && (frame.meta.fecSbnFirst === void 0 || stream.lastSbn !== void 0)) {
|
|
6373
|
+
stream.lastSbn = frame.meta.fecSbn;
|
|
6374
|
+
}
|
|
6375
|
+
if (frame.meta.recovered !== true) stream.sourced = true;
|
|
6209
6376
|
__privateGet(this, _gate).onReleased?.(frame.data, frame.meta);
|
|
6210
|
-
__privateGet(this, _emit2).call(this, frame.data, frame.meta);
|
|
6377
|
+
if (!stale) __privateGet(this, _emit2).call(this, frame.data, frame.meta);
|
|
6211
6378
|
};
|
|
6212
6379
|
var FMPU_BOX_TYPES = /* @__PURE__ */ new Set(["ftyp", "moov", "moof"]);
|
|
6213
6380
|
function isFmp4Box(data) {
|
|
@@ -6216,7 +6383,7 @@ var BlockcastMmt = (() => {
|
|
|
6216
6383
|
return FMPU_BOX_TYPES.has(type);
|
|
6217
6384
|
}
|
|
6218
6385
|
var MAX_AWAITING_TIMING_HEADS = 8;
|
|
6219
|
-
var _tracks, _defaultTrack, _reassembler, _sampleCombiners, _sampleCombinerOptions, _retiredSampleCombinerStats, _timingStreams, _maxPendingTimingSamples, _pendingTimingSamples, _reclaimedTimingSamples, _evictedTimingSamples, _rejectedMovieFragments, _sequencers, _fecGate, _fecSequencer, _reorderWindow, _reassemblyPolicy, _frameCb, _initCb, _sourceSymbolCb, _trackFrameCbs, _reorderStats, _reliableMoqStartupGate, _disposed, _replayingAwaitingTiming, _fedObjects, _reassemblerFragmentsFed, _reassemblerDataUnitsReturned, _feedOutcomes, _deferredFeedOutcomes, _lastOutcome, _lastOutcomeCount, _MmtpContainerParser_instances, streamKey_fn, sampleCombinerFor_fn, releaseTimingSlot_fn, dropSampleCombiner_fn, countOutcome_fn, countDeferredOutcome_fn, drop_fn, feedCounted_fn, feed_fn, emitCombinedSample_fn, passReliableMoqStartupGate_fn, timingResolver_fn, awaitTiming_fn, replayAwaitingTiming_fn, dropTimingStream_fn, validateReassemblyPolicy_fn, buildFrameMeta_fn, sequencerFor_fn, flushSequencers_fn, emitFrame_fn2, _MmtpContainerParser_static, scanForKeyframe_fn, scanLengthPrefixedHevcForKeyframe_fn, scanAnnexBForKeyframe_fn;
|
|
6386
|
+
var _tracks, _defaultTrack, _reassembler, _sampleCombiners, _sampleCombinerOptions, _retiredSampleCombinerStats, _timingStreams, _maxPendingTimingSamples, _pendingTimingSamples, _reclaimedTimingSamples, _evictedTimingSamples, _rejectedMovieFragments, _recoveredHeadsAwaitingTiming, _sequencers, _fecGate, _fecSequencer, _reorderWindow, _reassemblyPolicy, _frameCb, _initCb, _sourceSymbolCb, _trackFrameCbs, _reorderStats, _reliableMoqStartupGate, _disposed, _replayingAwaitingTiming, _fedObjects, _reassemblerFragmentsFed, _reassemblerDataUnitsReturned, _feedOutcomes, _deferredFeedOutcomes, _lastOutcome, _lastOutcomeCount, _MmtpContainerParser_instances, streamKey_fn, sampleCombinerFor_fn, releaseTimingSlot_fn, dropSampleCombiner_fn, countOutcome_fn, countDeferredOutcome_fn, drop_fn, feedCounted_fn, feed_fn, emitCombinedSample_fn, passReliableMoqStartupGate_fn, timingResolver_fn, awaitTiming_fn, leaveAwaitingTiming_fn, replayAwaitingTiming_fn, dropTimingStream_fn, validateReassemblyPolicy_fn, buildFrameMeta_fn, sequencerFor_fn, flushSequencers_fn, emitFrame_fn2, _MmtpContainerParser_static, scanForKeyframe_fn, scanLengthPrefixedHevcForKeyframe_fn, scanAnnexBForKeyframe_fn;
|
|
6220
6387
|
var _MmtpContainerParser = class _MmtpContainerParser {
|
|
6221
6388
|
constructor(config) {
|
|
6222
6389
|
__privateAdd(this, _MmtpContainerParser_instances);
|
|
@@ -6252,6 +6419,12 @@ var BlockcastMmt = (() => {
|
|
|
6252
6419
|
__privateAdd(this, _reclaimedTimingSamples, 0);
|
|
6253
6420
|
__privateAdd(this, _evictedTimingSamples, 0);
|
|
6254
6421
|
__privateAdd(this, _rejectedMovieFragments, 0);
|
|
6422
|
+
__privateAdd(this, _recoveredHeadsAwaitingTiming, {
|
|
6423
|
+
held: 0,
|
|
6424
|
+
expired: 0,
|
|
6425
|
+
discarded: 0,
|
|
6426
|
+
superseded: 0
|
|
6427
|
+
});
|
|
6255
6428
|
__privateAdd(this, _sequencers, /* @__PURE__ */ new Map());
|
|
6256
6429
|
__privateAdd(this, _fecGate, null);
|
|
6257
6430
|
__privateAdd(this, _fecSequencer, null);
|
|
@@ -6379,11 +6552,12 @@ var BlockcastMmt = (() => {
|
|
|
6379
6552
|
/**
|
|
6380
6553
|
* Typed view of the ISO-BMFF timing-sample budget.
|
|
6381
6554
|
*
|
|
6382
|
-
*
|
|
6383
|
-
*
|
|
6384
|
-
*
|
|
6385
|
-
*
|
|
6386
|
-
*
|
|
6555
|
+
* Each stream admits timing against its own window of `capacity` slots
|
|
6556
|
+
* (BLO-52085). A stream at capacity displaces its own oldest timing to
|
|
6557
|
+
* admit the moof in hand, and `evicted` counts the slots so displaced:
|
|
6558
|
+
* timing a stream declared and never delivered shows up there. `pending`
|
|
6559
|
+
* sums every stream, so it can exceed `capacity`. Per repo policy this is
|
|
6560
|
+
* a producer-owned counter -- never infer budget state by parsing logs.
|
|
6387
6561
|
*/
|
|
6388
6562
|
get timingBudgetStats() {
|
|
6389
6563
|
return {
|
|
@@ -6394,6 +6568,14 @@ var BlockcastMmt = (() => {
|
|
|
6394
6568
|
rejectedMovieFragments: __privateGet(this, _rejectedMovieFragments)
|
|
6395
6569
|
};
|
|
6396
6570
|
}
|
|
6571
|
+
/** Recovered heads parked for FT=1 timing (BLO-53369). Survives dispose(). */
|
|
6572
|
+
get recoveredHeadsAwaitingTiming() {
|
|
6573
|
+
let pending = 0;
|
|
6574
|
+
for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
|
|
6575
|
+
for (const head of awaitingTiming) if (head.options?.recovered) pending++;
|
|
6576
|
+
}
|
|
6577
|
+
return { ...__privateGet(this, _recoveredHeadsAwaitingTiming), pending };
|
|
6578
|
+
}
|
|
6397
6579
|
get reorderStats() {
|
|
6398
6580
|
let bufferedFrames = __privateGet(this, _fecSequencer)?.buffered ?? 0;
|
|
6399
6581
|
for (const { sequencer } of __privateGet(this, _sequencers).values()) bufferedFrames += sequencer.buffered;
|
|
@@ -6435,6 +6617,14 @@ var BlockcastMmt = (() => {
|
|
|
6435
6617
|
fecReleaseRapAbove(packetId, catalogTrackName, ssId) {
|
|
6436
6618
|
return __privateGet(this, _fecSequencer)?.rapAbove(packetId, catalogTrackName, ssId) ?? false;
|
|
6437
6619
|
}
|
|
6620
|
+
/** See FecReleaseSequencer.unsource. */
|
|
6621
|
+
unsourceFecRelease(packetId, catalogTrackName) {
|
|
6622
|
+
__privateGet(this, _fecSequencer)?.unsource(packetId, catalogTrackName);
|
|
6623
|
+
}
|
|
6624
|
+
/** See FecReleaseSequencer.holdsRebuilt. */
|
|
6625
|
+
fecReleaseHoldsRebuilt(packetId, catalogTrackName) {
|
|
6626
|
+
return __privateGet(this, _fecSequencer)?.holdsRebuilt(packetId, catalogTrackName) ?? false;
|
|
6627
|
+
}
|
|
6438
6628
|
/** Re-evaluate held gaps after FEC block state changed. */
|
|
6439
6629
|
pokeFecRelease(packetId, catalogTrackName) {
|
|
6440
6630
|
if (!__privateGet(this, _fecSequencer)) return;
|
|
@@ -6545,7 +6735,7 @@ var BlockcastMmt = (() => {
|
|
|
6545
6735
|
const now = Date.now();
|
|
6546
6736
|
for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
|
|
6547
6737
|
while (awaitingTiming.length > 0 && now - awaitingTiming[0].arrivedAt > timeoutMs) {
|
|
6548
|
-
awaitingTiming.shift();
|
|
6738
|
+
__privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, awaitingTiming.shift(), "expired");
|
|
6549
6739
|
}
|
|
6550
6740
|
}
|
|
6551
6741
|
}
|
|
@@ -6750,6 +6940,10 @@ var BlockcastMmt = (() => {
|
|
|
6750
6940
|
}
|
|
6751
6941
|
__privateMethod(this, _MmtpContainerParser_instances, emitFrame_fn2).call(this, data, meta);
|
|
6752
6942
|
}
|
|
6943
|
+
/** Whether FT=1 metadata already names this sample, without claiming it. */
|
|
6944
|
+
hasAuthoredTimingFor(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, catalogTrackName) {
|
|
6945
|
+
return __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, __privateMethod(this, _MmtpContainerParser_instances, streamKey_fn).call(this, packetId, catalogTrackName), packetId, false)?.sampleTiming(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) != null;
|
|
6946
|
+
}
|
|
6753
6947
|
/**
|
|
6754
6948
|
* Claim and return the authored timing for a recovered sample. Recovery does
|
|
6755
6949
|
* not pass through the ordinary `feed()` completion path, so it must retire
|
|
@@ -6794,6 +6988,9 @@ var BlockcastMmt = (() => {
|
|
|
6794
6988
|
for (const { sequencer } of __privateGet(this, _sequencers).values()) sequencer.discard();
|
|
6795
6989
|
__privateGet(this, _sequencers).clear();
|
|
6796
6990
|
__privateGet(this, _fecSequencer)?.discard();
|
|
6991
|
+
for (const { awaitingTiming } of __privateGet(this, _timingStreams).values()) {
|
|
6992
|
+
for (const head of awaitingTiming) __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, head, "discarded");
|
|
6993
|
+
}
|
|
6797
6994
|
__privateGet(this, _timingStreams).clear();
|
|
6798
6995
|
__privateSet(this, _pendingTimingSamples, 0);
|
|
6799
6996
|
__privateGet(this, _reliableMoqStartupGate).clear();
|
|
@@ -6822,6 +7019,7 @@ var BlockcastMmt = (() => {
|
|
|
6822
7019
|
_reclaimedTimingSamples = new WeakMap();
|
|
6823
7020
|
_evictedTimingSamples = new WeakMap();
|
|
6824
7021
|
_rejectedMovieFragments = new WeakMap();
|
|
7022
|
+
_recoveredHeadsAwaitingTiming = new WeakMap();
|
|
6825
7023
|
_sequencers = new WeakMap();
|
|
6826
7024
|
_fecGate = new WeakMap();
|
|
6827
7025
|
_fecSequencer = new WeakMap();
|
|
@@ -6980,11 +7178,16 @@ var BlockcastMmt = (() => {
|
|
|
6980
7178
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0)
|
|
6981
7179
|
return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-unexpected-fragmentation");
|
|
6982
7180
|
const resolver2 = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, false);
|
|
6983
|
-
if (!resolver2) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-no-resolver");
|
|
7181
|
+
if (!resolver2?.initialized) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "fragment-no-resolver");
|
|
6984
7182
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
6985
7183
|
const evictedBefore = resolver2.evictedSampleCount;
|
|
6986
|
-
const remaining = __privateGet(this, _maxPendingTimingSamples) -
|
|
6987
|
-
const admitted = resolver2.addMovieFragment(
|
|
7184
|
+
const remaining = __privateGet(this, _maxPendingTimingSamples) - resolver2.pendingSampleCount;
|
|
7185
|
+
const admitted = resolver2.addMovieFragment(
|
|
7186
|
+
mpu.mpuSequenceNumber,
|
|
7187
|
+
data,
|
|
7188
|
+
remaining,
|
|
7189
|
+
options?.recovered === true
|
|
7190
|
+
);
|
|
6988
7191
|
__privateSet(this, _pendingTimingSamples, __privateGet(this, _pendingTimingSamples) + (resolver2.pendingSampleCount - pendingBefore));
|
|
6989
7192
|
__privateSet(this, _evictedTimingSamples, __privateGet(this, _evictedTimingSamples) + (resolver2.evictedSampleCount - evictedBefore));
|
|
6990
7193
|
if (!admitted) {
|
|
@@ -7004,7 +7207,7 @@ var BlockcastMmt = (() => {
|
|
|
7004
7207
|
};
|
|
7005
7208
|
}
|
|
7006
7209
|
if (fragmentType !== 2) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "unknown-fragment-type");
|
|
7007
|
-
const resolver = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId,
|
|
7210
|
+
const resolver = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, true);
|
|
7008
7211
|
if (!resolver) return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-no-resolver");
|
|
7009
7212
|
if (!Number.isSafeInteger(data.byteLength) || data.byteLength <= 0)
|
|
7010
7213
|
return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-empty-payload");
|
|
@@ -7025,6 +7228,7 @@ var BlockcastMmt = (() => {
|
|
|
7025
7228
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
7026
7229
|
movieFragmentSequenceNumber: mfuDuHeader.movieFragmentSequenceNumber,
|
|
7027
7230
|
sampleNumber: mfuDuHeader.sampleNumber,
|
|
7231
|
+
sequenceNumber: info.sequenceNumber,
|
|
7028
7232
|
arrivedAt: Date.now()
|
|
7029
7233
|
});
|
|
7030
7234
|
return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, "mfu-sample-timing-missing");
|
|
@@ -7045,7 +7249,7 @@ var BlockcastMmt = (() => {
|
|
|
7045
7249
|
headTiming.size
|
|
7046
7250
|
);
|
|
7047
7251
|
if (readyOnRegister) {
|
|
7048
|
-
const deferred = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, readyOnRegister, info, resolver, options?.recovered === true, options?.fecSbn);
|
|
7252
|
+
const deferred = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, readyOnRegister, info, resolver, options?.recovered === true, options?.fecSbn, options?.fecSbnFirst);
|
|
7049
7253
|
if (deferred.record === void 0) __privateMethod(this, _MmtpContainerParser_instances, countDeferredOutcome_fn).call(this, deferred.reason);
|
|
7050
7254
|
}
|
|
7051
7255
|
}
|
|
@@ -7132,7 +7336,7 @@ var BlockcastMmt = (() => {
|
|
|
7132
7336
|
fragmentCounter: mpu.fragmentCounter
|
|
7133
7337
|
};
|
|
7134
7338
|
}
|
|
7135
|
-
const outcome = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, combined, info, resolver, options?.recovered === true, options?.fecSbn);
|
|
7339
|
+
const outcome = __privateMethod(this, _MmtpContainerParser_instances, emitCombinedSample_fn).call(this, combined, info, resolver, options?.recovered === true, options?.fecSbn, options?.fecSbnFirst);
|
|
7136
7340
|
if (outcome.record !== void 0) return outcome.record;
|
|
7137
7341
|
return __privateMethod(this, _MmtpContainerParser_instances, drop_fn).call(this, outcome.reason);
|
|
7138
7342
|
};
|
|
@@ -7151,7 +7355,7 @@ var BlockcastMmt = (() => {
|
|
|
7151
7355
|
* attribution exists to keep readable. Leaving the choice at the call site
|
|
7152
7356
|
* is what keeps both true.
|
|
7153
7357
|
*/
|
|
7154
|
-
emitCombinedSample_fn = function(combined, info, resolver, recovered, fecSbn) {
|
|
7358
|
+
emitCombinedSample_fn = function(combined, info, resolver, recovered, fecSbn, fecSbnFirst) {
|
|
7155
7359
|
const timing = resolver.sampleTiming(
|
|
7156
7360
|
combined.mpuSequenceNumber,
|
|
7157
7361
|
combined.movieFragmentSequenceNumber,
|
|
@@ -7162,6 +7366,7 @@ var BlockcastMmt = (() => {
|
|
|
7162
7366
|
const meta = __privateMethod(this, _MmtpContainerParser_instances, buildFrameMeta_fn).call(this, combined, info.packetId, info.sequenceNumber, timing, recovered);
|
|
7163
7367
|
if (!meta) return { reason: "combined-meta-build-failed" };
|
|
7164
7368
|
if (fecSbn !== void 0) meta.fecSbn = fecSbn;
|
|
7369
|
+
if (fecSbnFirst !== void 0) meta.fecSbnFirst = fecSbnFirst;
|
|
7165
7370
|
if (!resolver.consumeSample(
|
|
7166
7371
|
combined.mpuSequenceNumber,
|
|
7167
7372
|
combined.movieFragmentSequenceNumber,
|
|
@@ -7206,10 +7411,23 @@ var BlockcastMmt = (() => {
|
|
|
7206
7411
|
return resolver;
|
|
7207
7412
|
};
|
|
7208
7413
|
awaitTiming_fn = function(streamKey, head) {
|
|
7209
|
-
const
|
|
7210
|
-
if (!
|
|
7414
|
+
const state = __privateGet(this, _timingStreams).get(streamKey);
|
|
7415
|
+
if (!state) return;
|
|
7416
|
+
if (!head.options?.recovered) {
|
|
7417
|
+
const kept = state.awaitingTiming.filter((queued) => !queued.options?.recovered || queued.sequenceNumber !== head.sequenceNumber);
|
|
7418
|
+
__privateGet(this, _recoveredHeadsAwaitingTiming).superseded += state.awaitingTiming.length - kept.length;
|
|
7419
|
+
state.awaitingTiming = kept;
|
|
7420
|
+
}
|
|
7421
|
+
const awaitingTiming = state.awaitingTiming;
|
|
7211
7422
|
awaitingTiming.push(head);
|
|
7212
|
-
if (
|
|
7423
|
+
if (head.options?.recovered) __privateGet(this, _recoveredHeadsAwaitingTiming).held++;
|
|
7424
|
+
if (awaitingTiming.length > MAX_AWAITING_TIMING_HEADS) {
|
|
7425
|
+
__privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, awaitingTiming.shift(), "expired");
|
|
7426
|
+
}
|
|
7427
|
+
};
|
|
7428
|
+
/** Count a recovered head leaving the queue other than by replay or its source copy. */
|
|
7429
|
+
leaveAwaitingTiming_fn = function(head, outcome) {
|
|
7430
|
+
if (head.options?.recovered) __privateGet(this, _recoveredHeadsAwaitingTiming)[outcome]++;
|
|
7213
7431
|
};
|
|
7214
7432
|
/**
|
|
7215
7433
|
* Feed again the heads whose timing the FT=1 just admitted declares. That
|
|
@@ -7237,6 +7455,7 @@ var BlockcastMmt = (() => {
|
|
|
7237
7455
|
const state = __privateGet(this, _timingStreams).get(streamKey);
|
|
7238
7456
|
if (!state) return;
|
|
7239
7457
|
__privateGet(this, _timingStreams).delete(streamKey);
|
|
7458
|
+
for (const head of state.awaitingTiming) __privateMethod(this, _MmtpContainerParser_instances, leaveAwaitingTiming_fn).call(this, head, "discarded");
|
|
7240
7459
|
__privateSet(this, _pendingTimingSamples, __privateGet(this, _pendingTimingSamples) - state.resolver.pendingSampleCount);
|
|
7241
7460
|
if (__privateGet(this, _pendingTimingSamples) < 0) {
|
|
7242
7461
|
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
@@ -8217,7 +8436,7 @@ var BlockcastMmt = (() => {
|
|
|
8217
8436
|
// src/mmtp-fec-session.ts
|
|
8218
8437
|
var PATH_BITS = { moq: 1, multicast: 2 };
|
|
8219
8438
|
var MIXED_PATHS = PATH_BITS.moq | PATH_BITS.multicast;
|
|
8220
|
-
var _decoder, _blocks, _settled, _have, _newest, _prunedAt, _repairPacketId, _newestSource, _heldLayers, _anchorHorizon, _stats3, _MmtpFecSession_instances, drop_fn2, noteNewest_fn, blockStartOf_fn, countHeld_fn, noteMixed_fn, behindHorizon_fn, settle_fn, settleClean_fn, settleDecoded_fn, retireBehindHorizon_fn;
|
|
8439
|
+
var _decoder, _repairSymbols, _message, _blocks, _settled, _have, _newest, _prunedAt, _repairPacketId, _newestSource, _heldLayers, _anchorHorizon, _stats3, _MmtpFecSession_instances, trackFor_fn, drop_fn2, noteNewest_fn, blockStartOf_fn, countHeld_fn, noteMixed_fn, behindHorizon_fn, settle_fn, settleClean_fn, settleDecoded_fn, retireBehindHorizon_fn;
|
|
8221
8440
|
var MmtpFecSession = class {
|
|
8222
8441
|
/**
|
|
8223
8442
|
* @throws when the instance lacks or misstates a field the session needs:
|
|
@@ -8227,6 +8446,9 @@ var BlockcastMmt = (() => {
|
|
|
8227
8446
|
constructor(config) {
|
|
8228
8447
|
__privateAdd(this, _MmtpFecSession_instances);
|
|
8229
8448
|
__privateAdd(this, _decoder);
|
|
8449
|
+
__privateAdd(this, _repairSymbols);
|
|
8450
|
+
/** The message entry a catalog-less session was built from; undefined under a catalog. */
|
|
8451
|
+
__privateAdd(this, _message);
|
|
8230
8452
|
/** Blocks a repair has defined and that are not settled yet, by SS_Start. */
|
|
8231
8453
|
__privateAdd(this, _blocks, /* @__PURE__ */ new Map());
|
|
8232
8454
|
/** SS_Starts of settled blocks, so a later repair for one is refused. */
|
|
@@ -8258,6 +8480,24 @@ var BlockcastMmt = (() => {
|
|
|
8258
8480
|
mixedPath: { clean: 0, recovered: 0, failed: 0, released: 0 },
|
|
8259
8481
|
failClosed: null
|
|
8260
8482
|
});
|
|
8483
|
+
if (config && "alFecFlow" in config) {
|
|
8484
|
+
const message = alFecRaptorQFlowConfig(config.alFecFlow);
|
|
8485
|
+
if (!message) {
|
|
8486
|
+
throw new Error("MmtpFecSession: the AL-FEC entry is outside draft-ramadan-moq-mmt-02 8.3 (iso-ssbg1-v1)");
|
|
8487
|
+
}
|
|
8488
|
+
__privateSet(this, _message, message);
|
|
8489
|
+
this.blockProfile = parseFecBlockProfile("iso-ssbg1-v1", "alFecFlow");
|
|
8490
|
+
this.algorithm = message.fecCodeId === FEC_CODE_RAPTORQ ? "raptorq" : "reed-solomon";
|
|
8491
|
+
this.sourceSymbols = message.maximumK;
|
|
8492
|
+
__privateSet(this, _repairSymbols, message.maximumP);
|
|
8493
|
+
this.symbolSize = message.repairSymbolSize;
|
|
8494
|
+
this.retentionBlocks = requirePositiveInteger(config.retentionBlocks, "retentionBlocks");
|
|
8495
|
+
__privateSet(this, _repairPacketId, message.repairFlowId);
|
|
8496
|
+
__privateSet(this, _heldLayers, layerZero(message.maximumP));
|
|
8497
|
+
__privateSet(this, _anchorHorizon, heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols));
|
|
8498
|
+
__privateSet(this, _decoder, buildDecoder(config.Decoder, this));
|
|
8499
|
+
return;
|
|
8500
|
+
}
|
|
8261
8501
|
const fec = config?.fec;
|
|
8262
8502
|
if (!fec || typeof fec !== "object") {
|
|
8263
8503
|
throw new Error("MmtpFecSession: the catalog fec instance is required");
|
|
@@ -8268,7 +8508,7 @@ var BlockcastMmt = (() => {
|
|
|
8268
8508
|
}
|
|
8269
8509
|
this.algorithm = fec.scheme;
|
|
8270
8510
|
this.sourceSymbols = requirePositiveInteger(fec.sourceSymbols, "fec.sourceSymbols");
|
|
8271
|
-
this
|
|
8511
|
+
__privateSet(this, _repairSymbols, requirePositiveInteger(fec.repairSymbols, "fec.repairSymbols"));
|
|
8272
8512
|
this.symbolSize = requirePositiveInteger(fec.symbolSize, "fec.symbolSize");
|
|
8273
8513
|
const interleaveDepthMs = requireNumber(fec.interleaveDepthMs, "fec.interleaveDepthMs");
|
|
8274
8514
|
const reorderToleranceMs = requireNumber(fec.reorderToleranceMs, "fec.reorderToleranceMs");
|
|
@@ -8288,17 +8528,34 @@ var BlockcastMmt = (() => {
|
|
|
8288
8528
|
config.heldLayers
|
|
8289
8529
|
));
|
|
8290
8530
|
__privateSet(this, _anchorHorizon, heldLayerAnchorHorizon(this.retentionBlocks, this.sourceSymbols));
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8531
|
+
__privateSet(this, _decoder, buildDecoder(config.Decoder, this));
|
|
8532
|
+
}
|
|
8533
|
+
/** P_0 = B_0, the held total every repair packet is checked against. */
|
|
8534
|
+
get repairSymbols() {
|
|
8535
|
+
return __privateGet(this, _repairSymbols);
|
|
8536
|
+
}
|
|
8537
|
+
/**
|
|
8538
|
+
* A later version of the AL-FEC message, catalog-less sessions only. A
|
|
8539
|
+
* maximum_p_for_repair_flow above the held total raises it (`raised`): the
|
|
8540
|
+
* anchored check restarts at the new total, so a block generated under the
|
|
8541
|
+
* previous one stays valid before the first block carrying the new total
|
|
8542
|
+
* (fec-core-00 7.3.2, mmt-02 8.3). A field at or below it changes nothing
|
|
8543
|
+
* (`unchanged`): no publisher lowers it, and the check catches a wire that
|
|
8544
|
+
* does. An entry with another asset, repair flow, code point, K or T, or
|
|
8545
|
+
* one outside 8.3, is `refused`: that is a new source encoding, which needs
|
|
8546
|
+
* a new session. A latched fail-closed stays latched either way.
|
|
8547
|
+
*/
|
|
8548
|
+
updateAlFecFlow(flow) {
|
|
8549
|
+
const held = __privateGet(this, _message);
|
|
8550
|
+
if (!held) throw new Error("MmtpFecSession: updateAlFecFlow() needs a session built from an AL-FEC entry");
|
|
8551
|
+
const next = alFecRaptorQFlowConfig(flow);
|
|
8552
|
+
if (!next || next.sourcePacketIds[0] !== held.sourcePacketIds[0] || next.repairFlowId !== held.repairFlowId || next.fecCodeId !== held.fecCodeId || next.maximumK !== held.maximumK || next.repairSymbolSize !== held.repairSymbolSize) {
|
|
8553
|
+
return "refused";
|
|
8554
|
+
}
|
|
8555
|
+
if (next.maximumP <= __privateGet(this, _repairSymbols)) return "unchanged";
|
|
8556
|
+
__privateSet(this, _repairSymbols, next.maximumP);
|
|
8557
|
+
__privateSet(this, _heldLayers, layerZero(next.maximumP));
|
|
8558
|
+
return "raised";
|
|
8302
8559
|
}
|
|
8303
8560
|
/** True once release() ran. */
|
|
8304
8561
|
get released() {
|
|
@@ -8393,7 +8650,7 @@ var BlockcastMmt = (() => {
|
|
|
8393
8650
|
}
|
|
8394
8651
|
if (admission.reject) {
|
|
8395
8652
|
const verdict2 = __privateGet(this, _heldLayers).failsClosed(
|
|
8396
|
-
repairTrackName,
|
|
8653
|
+
__privateMethod(this, _MmtpFecSession_instances, trackFor_fn).call(this, repairTrackName),
|
|
8397
8654
|
frame.ssStart,
|
|
8398
8655
|
frame.rsbLength,
|
|
8399
8656
|
__privateGet(this, _anchorHorizon),
|
|
@@ -8408,7 +8665,7 @@ var BlockcastMmt = (() => {
|
|
|
8408
8665
|
__privateGet(this, _repairPacketId) ?? __privateSet(this, _repairPacketId, frame.packetId);
|
|
8409
8666
|
if (frame.ssbLength > this.sourceSymbols) return __privateMethod(this, _MmtpFecSession_instances, drop_fn2).call(this, "repair:ssb-length");
|
|
8410
8667
|
const verdict = __privateGet(this, _heldLayers).admit(
|
|
8411
|
-
repairTrackName,
|
|
8668
|
+
__privateMethod(this, _MmtpFecSession_instances, trackFor_fn).call(this, repairTrackName),
|
|
8412
8669
|
frame.ssStart,
|
|
8413
8670
|
frame.rsbLength,
|
|
8414
8671
|
__privateGet(this, _anchorHorizon),
|
|
@@ -8469,6 +8726,8 @@ var BlockcastMmt = (() => {
|
|
|
8469
8726
|
}
|
|
8470
8727
|
};
|
|
8471
8728
|
_decoder = new WeakMap();
|
|
8729
|
+
_repairSymbols = new WeakMap();
|
|
8730
|
+
_message = new WeakMap();
|
|
8472
8731
|
_blocks = new WeakMap();
|
|
8473
8732
|
_settled = new WeakMap();
|
|
8474
8733
|
_have = new WeakMap();
|
|
@@ -8480,6 +8739,10 @@ var BlockcastMmt = (() => {
|
|
|
8480
8739
|
_anchorHorizon = new WeakMap();
|
|
8481
8740
|
_stats3 = new WeakMap();
|
|
8482
8741
|
_MmtpFecSession_instances = new WeakSet();
|
|
8742
|
+
/** Catalog-less: layer 0 has no own-track clause (mmt-02 8.3), so no track name is judged. */
|
|
8743
|
+
trackFor_fn = function(repairTrackName) {
|
|
8744
|
+
return __privateGet(this, _message) ? void 0 : repairTrackName;
|
|
8745
|
+
};
|
|
8483
8746
|
drop_fn2 = function(reason) {
|
|
8484
8747
|
__privateGet(this, _stats3).dropped[reason] = (__privateGet(this, _stats3).dropped[reason] ?? 0) + 1;
|
|
8485
8748
|
return [];
|
|
@@ -8600,6 +8863,22 @@ var BlockcastMmt = (() => {
|
|
|
8600
8863
|
if (__privateMethod(this, _MmtpFecSession_instances, behindHorizon_fn).call(this, start)) __privateGet(this, _settled).delete(start);
|
|
8601
8864
|
}
|
|
8602
8865
|
};
|
|
8866
|
+
function layerZero(total) {
|
|
8867
|
+
return new HeldLayerAdmission([{ layer: 0, repairTrackName: "", rsIdOffset: 0, repairSymbols: total }]);
|
|
8868
|
+
}
|
|
8869
|
+
function buildDecoder(Decoder, session) {
|
|
8870
|
+
if (typeof Decoder !== "function") {
|
|
8871
|
+
throw new Error("MmtpFecSession: a decoder constructor (wasm_bindgen.MmtFecDecoder) is required");
|
|
8872
|
+
}
|
|
8873
|
+
const decoder = new Decoder(session.retentionBlocks, session.blockProfile, session.algorithm);
|
|
8874
|
+
try {
|
|
8875
|
+
decoder.configure_params(session.sourceSymbols, session.symbolSize);
|
|
8876
|
+
} catch (error) {
|
|
8877
|
+
decoder.free();
|
|
8878
|
+
throw error;
|
|
8879
|
+
}
|
|
8880
|
+
return decoder;
|
|
8881
|
+
}
|
|
8603
8882
|
function requirePositiveInteger(value, path) {
|
|
8604
8883
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
8605
8884
|
throw new Error(`MmtpFecSession: ${path} must be a positive integer (got ${String(value)})`);
|