@blockcast/mmt-container 0.1.0-main.149f78a4adae → 0.1.0-main.1fa0d55ca4da
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-runtime.js +1859 -934
- package/dist/browser-runtime.js.map +3 -3
- package/dist/carp-catalog.d.ts +2 -5
- package/dist/carp-catalog.d.ts.map +1 -1
- package/dist/carp-catalog.js.map +2 -2
- package/dist/catalog-fec-params.d.ts +39 -0
- package/dist/catalog-fec-params.d.ts.map +1 -1
- package/dist/catalog-fec-params.js +70 -1
- package/dist/catalog-fec-params.js.map +3 -3
- package/dist/cmaf-block-assembler.d.ts +20 -2
- package/dist/cmaf-block-assembler.d.ts.map +1 -1
- package/dist/cmaf-block-assembler.js +12 -8
- package/dist/cmaf-block-assembler.js.map +2 -2
- package/dist/codec-utils.d.ts +13 -0
- package/dist/codec-utils.d.ts.map +1 -1
- package/dist/codec-utils.js +44 -1
- package/dist/codec-utils.js.map +2 -2
- package/dist/fec-manager.d.ts +46 -1
- package/dist/fec-manager.d.ts.map +1 -1
- package/dist/fec-manager.js +129 -34
- package/dist/fec-manager.js.map +3 -3
- package/dist/fec-worker-setup.d.ts +6 -2
- package/dist/fec-worker-setup.d.ts.map +1 -1
- package/dist/fec-worker-setup.js +45 -36
- package/dist/fec-worker-setup.js.map +3 -3
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +2 -2
- package/dist/iso-bmff-timing.d.ts +25 -0
- package/dist/iso-bmff-timing.d.ts.map +1 -1
- package/dist/iso-bmff-timing.js +75 -15
- package/dist/iso-bmff-timing.js.map +2 -2
- package/dist/media-router.d.ts +12 -0
- package/dist/media-router.d.ts.map +1 -1
- package/dist/media-router.js +5 -19
- package/dist/media-router.js.map +2 -2
- package/dist/mfu-reassembler.d.ts +135 -7
- package/dist/mfu-reassembler.d.ts.map +1 -1
- package/dist/mfu-reassembler.js +1123 -758
- package/dist/mfu-reassembler.js.map +3 -3
- package/dist/mmtp-container-parser.d.ts +29 -0
- package/dist/mmtp-container-parser.d.ts.map +1 -1
- package/dist/mmtp-container-parser.js +1387 -786
- package/dist/mmtp-container-parser.js.map +3 -3
- package/dist/mmtp-container-parser.no-modules.js +1385 -787
- package/dist/mmtp-container-parser.no-modules.js.map +3 -3
- package/dist/mmtp-decode-pipeline.d.ts +36 -0
- package/dist/mmtp-decode-pipeline.d.ts.map +1 -1
- package/dist/mmtp-decode-pipeline.js +1452 -813
- package/dist/mmtp-decode-pipeline.js.map +3 -3
- package/dist/mmtp-router.d.ts +13 -6
- package/dist/mmtp-router.d.ts.map +1 -1
- package/dist/mmtp-router.js +1432 -800
- package/dist/mmtp-router.js.map +3 -3
- package/dist/multicast-manager.d.ts +30 -3
- package/dist/multicast-manager.d.ts.map +1 -1
- package/dist/multicast-manager.js +1622 -854
- package/dist/multicast-manager.js.map +3 -3
- package/dist/no-modules.js +1385 -787
- package/dist/no-modules.js.map +3 -3
- package/dist/repair-fetch.d.ts.map +1 -1
- package/dist/repair-fetch.js +10 -0
- package/dist/repair-fetch.js.map +2 -2
- package/dist/sample-combiner.d.ts +30 -0
- package/dist/sample-combiner.d.ts.map +1 -1
- package/dist/sample-combiner.js +47 -2
- package/dist/sample-combiner.js.map +2 -2
- package/dist/track-session.d.ts +64 -24
- package/dist/track-session.d.ts.map +1 -1
- package/dist/track-session.js +137 -65
- package/dist/track-session.js.map +2 -2
- package/dist/wasm-mfu-reassembler.d.ts +11 -0
- package/dist/wasm-mfu-reassembler.d.ts.map +1 -1
- package/dist/wasm-mfu-reassembler.js +803 -635
- package/dist/wasm-mfu-reassembler.js.map +2 -2
- package/dist/wasm-mmtp-container-parser.js +1385 -787
- package/dist/wasm-mmtp-container-parser.js.map +3 -3
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts +7 -0
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.d.ts.map +1 -1
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js +14 -5
- package/node_modules/@blockcast/fec-worker/dist/fec-worker-client.js.map +1 -1
- package/node_modules/@blockcast/fec-worker/src/fec-worker-client.ts +15 -5
- package/package.json +6 -6
- package/src/carp-catalog.ts +2 -5
- package/src/catalog-fec-params.ts +76 -0
- package/src/cmaf-block-assembler.ts +51 -1
- package/src/codec-utils.ts +73 -1
- package/src/fec-manager.ts +168 -18
- package/src/fec-worker-setup.ts +37 -11
- package/src/index.ts +11 -0
- package/src/iso-bmff-timing.ts +77 -7
- package/src/media-router.ts +13 -6
- package/src/mfu-reassembler.ts +1732 -940
- package/src/mmtp-container-parser.ts +111 -9
- package/src/mmtp-decode-pipeline.ts +98 -24
- package/src/mmtp-router.ts +76 -16
- package/src/multicast-manager.ts +106 -22
- package/src/repair-fetch.ts +21 -0
- package/src/sample-combiner.ts +93 -1
- package/src/track-session.ts +198 -119
- package/src/wasm-mfu-reassembler.ts +18 -1
package/dist/browser-runtime.js
CHANGED
|
@@ -126,7 +126,7 @@ var init_fec_worker_client = __esm({
|
|
|
126
126
|
*/
|
|
127
127
|
feedSource(ssId, data, ts, alta) {
|
|
128
128
|
if (__privateGet(this, _disposed5))
|
|
129
|
-
|
|
129
|
+
throw new Error("[FecWorkerClient] feedSource on a disposed client");
|
|
130
130
|
const cmd = alta ? {
|
|
131
131
|
type: "feedSource",
|
|
132
132
|
ssId,
|
|
@@ -149,7 +149,7 @@ var init_fec_worker_client = __esm({
|
|
|
149
149
|
*/
|
|
150
150
|
feedRepair(ssStart, ssbLength, rsId, data, ts) {
|
|
151
151
|
if (__privateGet(this, _disposed5))
|
|
152
|
-
|
|
152
|
+
throw new Error("[FecWorkerClient] feedRepair on a disposed client");
|
|
153
153
|
__privateGet(this, _worker).postMessage({
|
|
154
154
|
type: "feedRepair",
|
|
155
155
|
ssStart,
|
|
@@ -164,7 +164,7 @@ var init_fec_worker_client = __esm({
|
|
|
164
164
|
*/
|
|
165
165
|
relayBlockSig(sbn, sig, hash) {
|
|
166
166
|
if (__privateGet(this, _disposed5))
|
|
167
|
-
|
|
167
|
+
throw new Error("[FecWorkerClient] relayBlockSig on a disposed client");
|
|
168
168
|
__privateGet(this, _worker).postMessage({ type: "relayBlockSig", sbn, sig, hash }, [sig, hash]);
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
@@ -175,7 +175,7 @@ var init_fec_worker_client = __esm({
|
|
|
175
175
|
*/
|
|
176
176
|
cleanup(currentSbn) {
|
|
177
177
|
if (__privateGet(this, _disposed5))
|
|
178
|
-
|
|
178
|
+
throw new Error("[FecWorkerClient] cleanup on a disposed client");
|
|
179
179
|
if (!Number.isInteger(currentSbn) || currentSbn < 0 || currentSbn > 4294967295) {
|
|
180
180
|
throw new Error("[FecWorkerClient] cleanup: SBN must be a uint32");
|
|
181
181
|
}
|
|
@@ -188,7 +188,7 @@ var init_fec_worker_client = __esm({
|
|
|
188
188
|
/** Reset this track's decoder epoch while preserving Worker configuration. */
|
|
189
189
|
reset() {
|
|
190
190
|
if (__privateGet(this, _disposed5))
|
|
191
|
-
|
|
191
|
+
throw new Error("[FecWorkerClient] reset on a disposed client");
|
|
192
192
|
__privateWrapper(this, _pendingResets)._++;
|
|
193
193
|
__privateSet(this, _cleanupInFlight, null);
|
|
194
194
|
__privateGet(this, _queuedCleanupSbns).clear();
|
|
@@ -218,6 +218,15 @@ var init_fec_worker_client = __esm({
|
|
|
218
218
|
onError(cb) {
|
|
219
219
|
__privateSet(this, _onError, cb);
|
|
220
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* True once dispose() ran. A disposed client cannot decode, fill, or
|
|
223
|
+
* acknowledge anything, so routing symbols to it discards them; every
|
|
224
|
+
* command method (feedSource, feedRepair, relayBlockSig, cleanup, reset)
|
|
225
|
+
* throws rather than pretend otherwise. Callers check this first.
|
|
226
|
+
*/
|
|
227
|
+
get disposed() {
|
|
228
|
+
return __privateGet(this, _disposed5);
|
|
229
|
+
}
|
|
221
230
|
/**
|
|
222
231
|
* Dispose the Worker. Sends dispose command then terminates.
|
|
223
232
|
* Idempotent — safe to call multiple times.
|
|
@@ -920,7 +929,8 @@ var MFU_REASSEMBLY_ERROR_CODES = [
|
|
|
920
929
|
"data-unit-identity-mismatch",
|
|
921
930
|
"offset-overflow",
|
|
922
931
|
"active-data-unit-limit",
|
|
923
|
-
"incomplete-terminal-fragment"
|
|
932
|
+
"incomplete-terminal-fragment",
|
|
933
|
+
"ambiguous-continuation"
|
|
924
934
|
];
|
|
925
935
|
var mfuReassemblyErrorCodes = new Set(MFU_REASSEMBLY_ERROR_CODES);
|
|
926
936
|
function isMfuReassemblyErrorCode(value) {
|
|
@@ -933,6 +943,11 @@ var MfuReassemblyError = class extends Error {
|
|
|
933
943
|
this.name = "MfuReassemblyError";
|
|
934
944
|
}
|
|
935
945
|
};
|
|
946
|
+
function isMfuReassemblyError(error) {
|
|
947
|
+
if (error instanceof MfuReassemblyError) return true;
|
|
948
|
+
if (typeof error !== "object" || error === null) return false;
|
|
949
|
+
return Reflect.get(error, "name") === "MfuReassemblyError" && isMfuReassemblyErrorCode(Reflect.get(error, "code"));
|
|
950
|
+
}
|
|
936
951
|
function assembledPacketSequences(fragments) {
|
|
937
952
|
const packetSequenceNumbers = [];
|
|
938
953
|
const repairedPacketSequenceNumbers = [];
|
|
@@ -962,15 +977,11 @@ function streamScope(fragment) {
|
|
|
962
977
|
function isU32(value) {
|
|
963
978
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 4294967295;
|
|
964
979
|
}
|
|
965
|
-
function
|
|
966
|
-
if (!isU32(fragment.packetSequenceNumber)) {
|
|
967
|
-
throw new Error("[MFUReassembler] fragmented value has no validated packet sequence");
|
|
968
|
-
}
|
|
969
|
-
const terminalPacketSequence = (fragment.packetSequenceNumber + fragment.fragmentCounter) % 4294967296;
|
|
980
|
+
function dataUnitAddress(fragment, packetSequenceNumber) {
|
|
970
981
|
return JSON.stringify([
|
|
971
982
|
...streamScope(fragment),
|
|
972
983
|
fragment.mpuSequenceNumber,
|
|
973
|
-
|
|
984
|
+
packetSequenceNumber + fragment.fragmentCounter >>> 0
|
|
974
985
|
]);
|
|
975
986
|
}
|
|
976
987
|
function hasAnySampleIdentityField(fragment) {
|
|
@@ -991,12 +1002,7 @@ function fragmentCounterOrder(fragment) {
|
|
|
991
1002
|
function objectKey(fragment) {
|
|
992
1003
|
const scope = streamScope(fragment);
|
|
993
1004
|
if (fragment.itemId !== void 0) {
|
|
994
|
-
return JSON.stringify([
|
|
995
|
-
...scope,
|
|
996
|
-
fragment.mpuSequenceNumber,
|
|
997
|
-
"item",
|
|
998
|
-
fragment.itemId
|
|
999
|
-
]);
|
|
1005
|
+
return JSON.stringify([...scope, fragment.mpuSequenceNumber, "item", fragment.itemId]);
|
|
1000
1006
|
}
|
|
1001
1007
|
if (hasSampleIdentity(fragment)) {
|
|
1002
1008
|
return JSON.stringify([
|
|
@@ -1013,11 +1019,7 @@ function objectKey(fragment) {
|
|
|
1013
1019
|
fragment.sampleOffset
|
|
1014
1020
|
]);
|
|
1015
1021
|
}
|
|
1016
|
-
return JSON.stringify([
|
|
1017
|
-
...scope,
|
|
1018
|
-
fragment.mpuSequenceNumber,
|
|
1019
|
-
"unidentified"
|
|
1020
|
-
]);
|
|
1022
|
+
return JSON.stringify([...scope, fragment.mpuSequenceNumber, "unidentified"]);
|
|
1021
1023
|
}
|
|
1022
1024
|
function ordersByOffset(fragments) {
|
|
1023
1025
|
return fragments.every((fragment) => hasSampleIdentity(fragment));
|
|
@@ -1026,26 +1028,43 @@ function checkedSampleEnd(offset, byteLength) {
|
|
|
1026
1028
|
const end = offset + byteLength;
|
|
1027
1029
|
return Number.isSafeInteger(end) && end <= 4294967295 ? end : null;
|
|
1028
1030
|
}
|
|
1029
|
-
var
|
|
1031
|
+
var COMPLETED_ADDRESS_WINDOW_MULTIPLE = 2;
|
|
1032
|
+
function parkedRunHoldsASlot(hasOpenRun, fragmentCount) {
|
|
1033
|
+
return !hasOpenRun && fragmentCount > 0;
|
|
1034
|
+
}
|
|
1035
|
+
var _buffers, _timeouts, _activeDataUnits, _pendingContinuations, _emitted, _emittedOrder, _completed, _completedOrder, _timeoutMs, _maxBuffered, _nextAdmissionSeq, _MFUReassembler_instances, addAssociatedFragment_fn, parkContinuation_fn, drainPendingContinuations_fn, unopenedParkedRunCount_fn, incompleteRunCount_fn, evictLeastRecentlyAdmitted_fn, releaseSlot_fn, reject_fn, associateDataUnit_fn, tryReassemble_fn, assemble_fn, setTimeout_fn, clearPendingTimeout_fn, armHeadlessPendingTimeout_fn, deletePendingRun_fn, onPendingTimeout_fn, clearTimeout_fn, markEmitted_fn, markCompleted_fn, orderValue_fn, removeActiveDataUnitForBuffer_fn, openRunAt_fn, onTimeout_fn, pendingDebugSamples_fn;
|
|
1030
1036
|
var MFUReassembler = class {
|
|
1031
1037
|
constructor(options) {
|
|
1032
1038
|
__privateAdd(this, _MFUReassembler_instances);
|
|
1033
|
-
// Fragment buffers use
|
|
1034
|
-
//
|
|
1035
|
-
//
|
|
1036
|
-
//
|
|
1037
|
-
// separated and incomplete. Continuations may wait in a counter-indexed queue
|
|
1038
|
-
// until FI=1 supplies intrinsic identity; MMTP packet send time is never an
|
|
1039
|
-
// association key.
|
|
1039
|
+
// Fragment buffers and active runs use the authored MFU DU identity.
|
|
1040
|
+
// Continuations may wait in one counter-indexed queue per data-unit address
|
|
1041
|
+
// until FI=1 supplies that identity. The address is transport association
|
|
1042
|
+
// only; the object key stays the intrinsic DU identity.
|
|
1040
1043
|
__privateAdd(this, _buffers, /* @__PURE__ */ new Map());
|
|
1041
1044
|
__privateAdd(this, _timeouts, /* @__PURE__ */ new Map());
|
|
1042
1045
|
__privateAdd(this, _activeDataUnits, /* @__PURE__ */ new Map());
|
|
1043
1046
|
__privateAdd(this, _pendingContinuations, /* @__PURE__ */ new Map());
|
|
1044
1047
|
__privateAdd(this, _emitted, /* @__PURE__ */ new Set());
|
|
1045
|
-
// same sample keys
|
|
1048
|
+
// same sample keys, so FEC-recovered duplicate heads are dropped
|
|
1046
1049
|
__privateAdd(this, _emittedOrder, []);
|
|
1050
|
+
// Addresses of emitted data units. A continuation addressed to one of them
|
|
1051
|
+
// is a late copy of a fragment the run already consumed (FEC recovery
|
|
1052
|
+
// supplied it first), so it is rejected as stale rather than parked as a
|
|
1053
|
+
// headless data unit that would hold a slot until reset. Bounded at
|
|
1054
|
+
// COMPLETED_ADDRESS_WINDOW_MULTIPLE x maxBuffered, the same window the Rust
|
|
1055
|
+
// engine keeps, so both engines forget an address after the same number of
|
|
1056
|
+
// completions. (#emitted is a separate, per-engine window: this engine keeps
|
|
1057
|
+
// maxBuffered sample keys where Rust keeps 2x; the completed set does not
|
|
1058
|
+
// derive from it.)
|
|
1059
|
+
__privateAdd(this, _completed, /* @__PURE__ */ new Set());
|
|
1060
|
+
__privateAdd(this, _completedOrder, []);
|
|
1047
1061
|
__privateAdd(this, _timeoutMs);
|
|
1048
1062
|
__privateAdd(this, _maxBuffered);
|
|
1063
|
+
/**
|
|
1064
|
+
* Monotonic admission ordinal. Counts slot admissions, never reads a clock,
|
|
1065
|
+
* so eviction order survives on hosts that run `timeoutMs: null`.
|
|
1066
|
+
*/
|
|
1067
|
+
__privateAdd(this, _nextAdmissionSeq, 0);
|
|
1049
1068
|
/** Statistics for debugging */
|
|
1050
1069
|
this.stats = {
|
|
1051
1070
|
totalFragments: 0,
|
|
@@ -1076,20 +1095,14 @@ var MFUReassembler = class {
|
|
|
1076
1095
|
if (state.timeoutId !== void 0) clearTimeout(state.timeoutId);
|
|
1077
1096
|
delete state.timeoutId;
|
|
1078
1097
|
if (ms !== null) {
|
|
1079
|
-
state.timeoutId = setTimeout(
|
|
1080
|
-
() => __privateMethod(this, _MFUReassembler_instances, onTimeout_fn).call(this, bufferKey),
|
|
1081
|
-
Math.max(0, state.startedAt + ms - now)
|
|
1082
|
-
);
|
|
1098
|
+
state.timeoutId = setTimeout(() => __privateMethod(this, _MFUReassembler_instances, onTimeout_fn).call(this, bufferKey), Math.max(0, state.startedAt + ms - now));
|
|
1083
1099
|
}
|
|
1084
1100
|
}
|
|
1085
|
-
for (const [
|
|
1101
|
+
for (const [address, pending] of __privateGet(this, _pendingContinuations)) {
|
|
1086
1102
|
if (pending.timeoutId !== void 0) clearTimeout(pending.timeoutId);
|
|
1087
1103
|
delete pending.timeoutId;
|
|
1088
|
-
if (ms !== null &&
|
|
1089
|
-
pending.timeoutId = setTimeout(
|
|
1090
|
-
() => __privateMethod(this, _MFUReassembler_instances, onPendingTimeout_fn).call(this, runKey),
|
|
1091
|
-
Math.max(0, pending.startedAt + ms - now)
|
|
1092
|
-
);
|
|
1104
|
+
if (ms !== null && parkedRunHoldsASlot(__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0, pending.fragments.size)) {
|
|
1105
|
+
pending.timeoutId = setTimeout(() => __privateMethod(this, _MFUReassembler_instances, onPendingTimeout_fn).call(this, address), Math.max(0, pending.startedAt + ms - now));
|
|
1093
1106
|
}
|
|
1094
1107
|
}
|
|
1095
1108
|
}
|
|
@@ -1139,38 +1152,50 @@ var MFUReassembler = class {
|
|
|
1139
1152
|
catalogTrackName: fragment.catalogTrackName
|
|
1140
1153
|
};
|
|
1141
1154
|
}
|
|
1142
|
-
|
|
1155
|
+
const sequence = fragment.packetSequenceNumber;
|
|
1156
|
+
if (!isU32(sequence)) {
|
|
1143
1157
|
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "missing-packet-sequence", "fragmented MFU has no MMTP packet sequence number");
|
|
1144
1158
|
}
|
|
1145
|
-
const
|
|
1146
|
-
if (fragmentationIndicator === 1 && (hasSampleIdentity(fragment) || isU32(fragment.itemId)) && __privateGet(this, _emitted).has(objectKey(fragment))) {
|
|
1147
|
-
__privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, runKey);
|
|
1148
|
-
return null;
|
|
1149
|
-
}
|
|
1159
|
+
const address = dataUnitAddress(fragment, sequence);
|
|
1150
1160
|
if (fragmentationIndicator !== 1) {
|
|
1151
|
-
const
|
|
1152
|
-
if (
|
|
1153
|
-
|
|
1161
|
+
const open = __privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address);
|
|
1162
|
+
if (open === void 0) {
|
|
1163
|
+
if (__privateGet(this, _completed).has(address)) {
|
|
1164
|
+
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "fragment-counter-mismatch", "MFU continuation names a data unit that already completed");
|
|
1165
|
+
}
|
|
1166
|
+
__privateMethod(this, _MFUReassembler_instances, parkContinuation_fn).call(this, address, fragment);
|
|
1167
|
+
return null;
|
|
1154
1168
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1169
|
+
const [runKey2, active] = open;
|
|
1170
|
+
if (active.nextFragmentCounter > fragment.fragmentCounter) {
|
|
1171
|
+
__privateMethod(this, _MFUReassembler_instances, parkContinuation_fn).call(this, address, fragment);
|
|
1157
1172
|
return null;
|
|
1158
1173
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
} else {
|
|
1162
|
-
const pending = __privateGet(this, _pendingContinuations).get(runKey);
|
|
1163
|
-
if (pending !== void 0 && [...pending.fragments.keys()].some(
|
|
1164
|
-
(counter) => counter >= fragment.fragmentCounter
|
|
1165
|
-
)) {
|
|
1166
|
-
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "fragment-counter-mismatch", "queued continuation counter is not below the authored first-fragment counter");
|
|
1174
|
+
if (active.nextFragmentCounter < fragment.fragmentCounter) {
|
|
1175
|
+
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "fragment-counter-mismatch", "MFU continuation is behind the authored fragment-counter countdown");
|
|
1167
1176
|
}
|
|
1177
|
+
const result2 = __privateMethod(this, _MFUReassembler_instances, addAssociatedFragment_fn).call(this, fragment, runKey2, address);
|
|
1178
|
+
if (result2 !== null) return result2;
|
|
1179
|
+
return __privateMethod(this, _MFUReassembler_instances, drainPendingContinuations_fn).call(this, address, runKey2);
|
|
1168
1180
|
}
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1171
|
-
|
|
1181
|
+
const runKey = objectKey(fragment);
|
|
1182
|
+
if ((hasSampleIdentity(fragment) || isU32(fragment.itemId)) && __privateGet(this, _emitted).has(runKey)) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
if (__privateGet(this, _activeDataUnits).has(runKey)) {
|
|
1186
|
+
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "overlapping-data-unit", "fragmented MFU overlaps an active data unit");
|
|
1187
|
+
}
|
|
1188
|
+
if (__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0) {
|
|
1189
|
+
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "overlapping-data-unit", "fragmented MFU opens on an address held by an open data unit");
|
|
1190
|
+
}
|
|
1191
|
+
const pending = __privateGet(this, _pendingContinuations).get(address);
|
|
1192
|
+
if (pending !== void 0 && [...pending.fragments.keys()].some((counter) => counter >= fragment.fragmentCounter)) {
|
|
1193
|
+
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "fragment-counter-mismatch", "queued continuation counter is not below the authored first-fragment counter");
|
|
1194
|
+
}
|
|
1195
|
+
const result = __privateMethod(this, _MFUReassembler_instances, addAssociatedFragment_fn).call(this, fragment, runKey, address, pending?.startedAt);
|
|
1196
|
+
__privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this, address);
|
|
1172
1197
|
if (result !== null) return result;
|
|
1173
|
-
return __privateMethod(this, _MFUReassembler_instances, drainPendingContinuations_fn).call(this, runKey);
|
|
1198
|
+
return __privateMethod(this, _MFUReassembler_instances, drainPendingContinuations_fn).call(this, address, runKey);
|
|
1174
1199
|
}
|
|
1175
1200
|
/**
|
|
1176
1201
|
* Flush all buffered fragments
|
|
@@ -1182,12 +1207,14 @@ var MFUReassembler = class {
|
|
|
1182
1207
|
__privateGet(this, _buffers).clear();
|
|
1183
1208
|
__privateGet(this, _timeouts).clear();
|
|
1184
1209
|
__privateGet(this, _activeDataUnits).clear();
|
|
1185
|
-
for (const
|
|
1186
|
-
__privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this,
|
|
1210
|
+
for (const address of __privateGet(this, _pendingContinuations).keys()) {
|
|
1211
|
+
__privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this, address);
|
|
1187
1212
|
}
|
|
1188
1213
|
__privateGet(this, _pendingContinuations).clear();
|
|
1189
1214
|
__privateGet(this, _emitted).clear();
|
|
1190
1215
|
__privateGet(this, _emittedOrder).length = 0;
|
|
1216
|
+
__privateGet(this, _completed).clear();
|
|
1217
|
+
__privateGet(this, _completedOrder).length = 0;
|
|
1191
1218
|
}
|
|
1192
1219
|
/** Drop one catalog stream's incomplete and dedup state. */
|
|
1193
1220
|
resetStream(catalogTrackName) {
|
|
@@ -1205,18 +1232,15 @@ var MFUReassembler = class {
|
|
|
1205
1232
|
discarded.add(key);
|
|
1206
1233
|
}
|
|
1207
1234
|
}
|
|
1208
|
-
for (const [
|
|
1235
|
+
for (const [objectKey2, active] of __privateGet(this, _activeDataUnits)) {
|
|
1209
1236
|
if (active.catalogTrackName === catalogTrackName) {
|
|
1210
1237
|
discarded.add(active.bufferKey);
|
|
1211
|
-
__privateGet(this, _activeDataUnits).delete(
|
|
1238
|
+
__privateGet(this, _activeDataUnits).delete(objectKey2);
|
|
1212
1239
|
}
|
|
1213
1240
|
}
|
|
1214
|
-
for (const [
|
|
1215
|
-
if ([...pending.fragments.values()].some(
|
|
1216
|
-
(
|
|
1217
|
-
)) {
|
|
1218
|
-
__privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, runKey);
|
|
1219
|
-
}
|
|
1241
|
+
for (const [address, pending] of __privateGet(this, _pendingContinuations)) {
|
|
1242
|
+
if ([...pending.fragments.values()].some((fragment) => fragment.catalogTrackName === catalogTrackName) || belongsToStream(address))
|
|
1243
|
+
__privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, address);
|
|
1220
1244
|
}
|
|
1221
1245
|
for (const key of __privateGet(this, _emitted)) {
|
|
1222
1246
|
if (belongsToStream(key)) discarded.add(key);
|
|
@@ -1227,6 +1251,10 @@ var MFUReassembler = class {
|
|
|
1227
1251
|
__privateGet(this, _emitted).delete(key);
|
|
1228
1252
|
}
|
|
1229
1253
|
__privateSet(this, _emittedOrder, __privateGet(this, _emittedOrder).filter((key) => !discarded.has(key)));
|
|
1254
|
+
for (const address of __privateGet(this, _completed)) {
|
|
1255
|
+
if (belongsToStream(address)) __privateGet(this, _completed).delete(address);
|
|
1256
|
+
}
|
|
1257
|
+
__privateSet(this, _completedOrder, __privateGet(this, _completedOrder).filter((address) => __privateGet(this, _completed).has(address)));
|
|
1230
1258
|
}
|
|
1231
1259
|
/** Reset all state — flush buffers and zero stats */
|
|
1232
1260
|
reset() {
|
|
@@ -1239,14 +1267,26 @@ var MFUReassembler = class {
|
|
|
1239
1267
|
this.stats.outOfOrderFragments = 0;
|
|
1240
1268
|
}
|
|
1241
1269
|
/**
|
|
1242
|
-
* Get number of pending MFUs
|
|
1270
|
+
* Get number of pending MFUs: engine buffers plus every headless parked data
|
|
1271
|
+
* unit that holds a slot.
|
|
1272
|
+
*
|
|
1273
|
+
* This is not one of the four charging sites, but it shares their second term,
|
|
1274
|
+
* so it takes the same rule — by delegating to `#unopenedParkedRunCount`, the
|
|
1275
|
+
* one site that applies it. Rust's public `buffered_count()` is
|
|
1276
|
+
* `inner.buffered_count() + unopened_parked_run_count()`, and that count is
|
|
1277
|
+
* `parked_run_holds_a_slot` — so filtering here on the open run alone would
|
|
1278
|
+
* report a phantom slot for an empty published queue in TypeScript and not in
|
|
1279
|
+
* Rust, out of a *host-observable* counter (`getStatus().bufferedMFUs`). Same
|
|
1280
|
+
* state, two numbers, decided by which engine the host loaded: the divergence
|
|
1281
|
+
* class this file exists to close, so the public counters have to agree too.
|
|
1282
|
+
*
|
|
1283
|
+
* Distinct from `#incompleteRunCount`, which sums `#activeDataUnits` rather
|
|
1284
|
+
* than `#buffers`. Those are different quantities by design and are not being
|
|
1285
|
+
* merged; only the parked term they share is required to agree, which is what
|
|
1286
|
+
* routing both through one method makes structural rather than conventional.
|
|
1243
1287
|
*/
|
|
1244
1288
|
get pendingCount() {
|
|
1245
|
-
|
|
1246
|
-
for (const runKey of __privateGet(this, _pendingContinuations).keys()) {
|
|
1247
|
-
if (!__privateGet(this, _activeDataUnits).has(runKey)) count++;
|
|
1248
|
-
}
|
|
1249
|
-
return count;
|
|
1289
|
+
return __privateGet(this, _buffers).size + __privateMethod(this, _MFUReassembler_instances, unopenedParkedRunCount_fn).call(this);
|
|
1250
1290
|
}
|
|
1251
1291
|
/**
|
|
1252
1292
|
* Get current buffer status
|
|
@@ -1273,11 +1313,14 @@ _activeDataUnits = new WeakMap();
|
|
|
1273
1313
|
_pendingContinuations = new WeakMap();
|
|
1274
1314
|
_emitted = new WeakMap();
|
|
1275
1315
|
_emittedOrder = new WeakMap();
|
|
1316
|
+
_completed = new WeakMap();
|
|
1317
|
+
_completedOrder = new WeakMap();
|
|
1276
1318
|
_timeoutMs = new WeakMap();
|
|
1277
1319
|
_maxBuffered = new WeakMap();
|
|
1320
|
+
_nextAdmissionSeq = new WeakMap();
|
|
1278
1321
|
_MFUReassembler_instances = new WeakSet();
|
|
1279
|
-
addAssociatedFragment_fn = function(fragment, runKey, startedAt) {
|
|
1280
|
-
const association = __privateMethod(this, _MFUReassembler_instances, associateDataUnit_fn).call(this, fragment, runKey);
|
|
1322
|
+
addAssociatedFragment_fn = function(fragment, runKey, address, startedAt) {
|
|
1323
|
+
const association = __privateMethod(this, _MFUReassembler_instances, associateDataUnit_fn).call(this, fragment, runKey, address);
|
|
1281
1324
|
const { bufferKey, first, terminal } = association;
|
|
1282
1325
|
let fragments = __privateGet(this, _buffers).get(bufferKey);
|
|
1283
1326
|
if (first) {
|
|
@@ -1299,66 +1342,160 @@ addAssociatedFragment_fn = function(fragment, runKey, startedAt) {
|
|
|
1299
1342
|
__privateGet(this, _buffers).delete(bufferKey);
|
|
1300
1343
|
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "incomplete-terminal-fragment", "validated terminal fragment did not complete its data unit");
|
|
1301
1344
|
}
|
|
1345
|
+
__privateMethod(this, _MFUReassembler_instances, markCompleted_fn).call(this, address);
|
|
1302
1346
|
return result;
|
|
1303
1347
|
};
|
|
1304
|
-
parkContinuation_fn = function(
|
|
1305
|
-
let pending = __privateGet(this, _pendingContinuations).get(
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1348
|
+
parkContinuation_fn = function(address, fragment) {
|
|
1349
|
+
let pending = __privateGet(this, _pendingContinuations).get(address);
|
|
1350
|
+
const open = __privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address);
|
|
1351
|
+
const alreadyCharged = open !== void 0 || parkedRunHoldsASlot(open !== void 0, pending?.fragments.size ?? 0);
|
|
1352
|
+
if (!alreadyCharged && __privateMethod(this, _MFUReassembler_instances, incompleteRunCount_fn).call(this) >= __privateGet(this, _maxBuffered)) {
|
|
1353
|
+
if (!__privateMethod(this, _MFUReassembler_instances, evictLeastRecentlyAdmitted_fn).call(this)) {
|
|
1309
1354
|
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "active-data-unit-limit", "active MFU data-unit limit reached");
|
|
1310
1355
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1356
|
+
}
|
|
1357
|
+
let reAnchored = false;
|
|
1358
|
+
if (pending === void 0) {
|
|
1359
|
+
pending = {
|
|
1360
|
+
fragments: /* @__PURE__ */ new Map(),
|
|
1361
|
+
startedAt: Date.now(),
|
|
1362
|
+
admissionSeq: open !== void 0 ? open[1].admissionSeq : __privateWrapper(this, _nextAdmissionSeq)._++
|
|
1363
|
+
};
|
|
1364
|
+
if (open === void 0 && __privateGet(this, _timeoutMs) !== null) {
|
|
1365
|
+
pending.timeoutId = setTimeout(() => __privateMethod(this, _MFUReassembler_instances, onPendingTimeout_fn).call(this, address), __privateGet(this, _timeoutMs));
|
|
1315
1366
|
}
|
|
1316
|
-
__privateGet(this, _pendingContinuations).set(
|
|
1367
|
+
__privateGet(this, _pendingContinuations).set(address, pending);
|
|
1368
|
+
} else if (!alreadyCharged) {
|
|
1369
|
+
pending.admissionSeq = __privateWrapper(this, _nextAdmissionSeq)._++;
|
|
1370
|
+
pending.startedAt = Date.now();
|
|
1371
|
+
__privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this, address);
|
|
1372
|
+
reAnchored = true;
|
|
1317
1373
|
}
|
|
1318
1374
|
if (pending.fragments.has(fragment.fragmentCounter)) {
|
|
1319
1375
|
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "fragment-counter-mismatch", "duplicate queued continuation counter would replace retained media");
|
|
1320
1376
|
}
|
|
1321
1377
|
pending.fragments.set(fragment.fragmentCounter, fragment);
|
|
1378
|
+
if (reAnchored) __privateMethod(this, _MFUReassembler_instances, armHeadlessPendingTimeout_fn).call(this, address);
|
|
1322
1379
|
};
|
|
1323
|
-
drainPendingContinuations_fn = function(runKey) {
|
|
1380
|
+
drainPendingContinuations_fn = function(address, runKey) {
|
|
1324
1381
|
while (true) {
|
|
1325
1382
|
const active = __privateGet(this, _activeDataUnits).get(runKey);
|
|
1383
|
+
const pending = __privateGet(this, _pendingContinuations).get(address);
|
|
1326
1384
|
if (active === void 0) {
|
|
1327
|
-
__privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this,
|
|
1385
|
+
if (pending?.fragments.size === 0) __privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, address);
|
|
1328
1386
|
return null;
|
|
1329
1387
|
}
|
|
1330
|
-
const pending = __privateGet(this, _pendingContinuations).get(runKey);
|
|
1331
1388
|
const fragment = pending?.fragments.get(active.nextFragmentCounter);
|
|
1332
|
-
if (fragment === void 0)
|
|
1389
|
+
if (fragment === void 0) {
|
|
1390
|
+
if (pending?.fragments.size === 0) __privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, address);
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1333
1393
|
pending.fragments.delete(active.nextFragmentCounter);
|
|
1334
|
-
if (pending.fragments.size === 0) __privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this,
|
|
1335
|
-
const result = __privateMethod(this, _MFUReassembler_instances, addAssociatedFragment_fn).call(this, fragment, runKey);
|
|
1394
|
+
if (pending.fragments.size === 0) __privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, address);
|
|
1395
|
+
const result = __privateMethod(this, _MFUReassembler_instances, addAssociatedFragment_fn).call(this, fragment, runKey, address);
|
|
1336
1396
|
if (result !== null) {
|
|
1337
|
-
__privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, runKey);
|
|
1338
1397
|
return result;
|
|
1339
1398
|
}
|
|
1340
1399
|
}
|
|
1341
1400
|
};
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1401
|
+
/** Parked data units that hold a `maxBuffered` slot of their own. Membership
|
|
1402
|
+
* is `parkedRunHoldsASlot`, the same rule #evictLeastRecentlyAdmitted offers
|
|
1403
|
+
* as candidates, so nothing can be counted here that eviction cannot reclaim.
|
|
1404
|
+
*
|
|
1405
|
+
* One site, two callers — `#incompleteRunCount` and `pendingCount` — mirroring
|
|
1406
|
+
* Rust's `unopened_parked_run_count`, which `incomplete_run_count` and
|
|
1407
|
+
* `buffered_count` both delegate to. Both callers used to inline this loop
|
|
1408
|
+
* verbatim, so converging the *rule* moved one copy and left the other as the
|
|
1409
|
+
* divergence (Ally's review of #400). The rule can no longer drift now that
|
|
1410
|
+
* every site routes through `parkedRunHoldsASlot`; keeping the loop itself at
|
|
1411
|
+
* one site is what stops the scaffolding drifting the next time this question
|
|
1412
|
+
* grows an argument. */
|
|
1413
|
+
unopenedParkedRunCount_fn = function() {
|
|
1414
|
+
let count = 0;
|
|
1415
|
+
for (const [address, pending] of __privateGet(this, _pendingContinuations)) {
|
|
1416
|
+
if (parkedRunHoldsASlot(__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0, pending.fragments.size)) {
|
|
1417
|
+
count++;
|
|
1418
|
+
}
|
|
1346
1419
|
}
|
|
1347
1420
|
return count;
|
|
1348
1421
|
};
|
|
1422
|
+
/** Every open run plus every headless parked data unit that holds a slot,
|
|
1423
|
+
* one slot each. This is the quantity maxBuffered bounds. The parked term is
|
|
1424
|
+
* `#unopenedParkedRunCount`, whose membership is `parkedRunHoldsASlot` — the
|
|
1425
|
+
* same rule #evictLeastRecentlyAdmitted offers as candidates, so nothing can
|
|
1426
|
+
* be counted here that eviction cannot reclaim. */
|
|
1427
|
+
incompleteRunCount_fn = function() {
|
|
1428
|
+
return __privateGet(this, _activeDataUnits).size + __privateMethod(this, _MFUReassembler_instances, unopenedParkedRunCount_fn).call(this);
|
|
1429
|
+
};
|
|
1430
|
+
/**
|
|
1431
|
+
* Drop the incomplete data unit admitted longest ago so a newly arriving one
|
|
1432
|
+
* can take its slot. Selection is by monotonic admission ordinal, never by
|
|
1433
|
+
* wall clock, so this is a bounded-capacity replacement policy and not a
|
|
1434
|
+
* timeout: it behaves identically on hosts that never drive `cleanup_timeouts`
|
|
1435
|
+
* (`timeoutMs: null`). Only incomplete data units are candidates — a data
|
|
1436
|
+
* unit that completed has already left both maps.
|
|
1437
|
+
*
|
|
1438
|
+
* Returns false only if no slot is charged, which cannot happen at the bound
|
|
1439
|
+
* (`maxBuffered` is a positive integer); admission then falls back to the
|
|
1440
|
+
* fail-closed `active-data-unit-limit` reject, which stays a stable host code.
|
|
1441
|
+
* That holds because the candidate set here is built from the same
|
|
1442
|
+
* `parkedRunHoldsASlot` rule `#incompleteRunCount` counts by, so reaching the
|
|
1443
|
+
* bound implies at least one candidate. Filtering one and not the other would
|
|
1444
|
+
* make the claim false: a queue counted but not offered charges a slot no
|
|
1445
|
+
* eviction can reclaim, and the receiver refuses every later data unit
|
|
1446
|
+
* permanently.
|
|
1447
|
+
*/
|
|
1448
|
+
evictLeastRecentlyAdmitted_fn = function() {
|
|
1449
|
+
let victimAddress;
|
|
1450
|
+
let victimSeq = Number.POSITIVE_INFINITY;
|
|
1451
|
+
for (const [, active] of __privateGet(this, _activeDataUnits)) {
|
|
1452
|
+
if (active.admissionSeq < victimSeq) {
|
|
1453
|
+
victimSeq = active.admissionSeq;
|
|
1454
|
+
victimAddress = active.address;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
for (const [address, pending] of __privateGet(this, _pendingContinuations)) {
|
|
1458
|
+
if (!parkedRunHoldsASlot(__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0, pending.fragments.size)) {
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
if (pending.admissionSeq < victimSeq) {
|
|
1462
|
+
victimSeq = pending.admissionSeq;
|
|
1463
|
+
victimAddress = address;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (victimAddress === void 0) return false;
|
|
1467
|
+
__privateMethod(this, _MFUReassembler_instances, releaseSlot_fn).call(this, victimAddress);
|
|
1468
|
+
this.stats.evictedMemoryLimit++;
|
|
1469
|
+
return true;
|
|
1470
|
+
};
|
|
1471
|
+
/**
|
|
1472
|
+
* Release every form of state one address holds: its open run, that run's
|
|
1473
|
+
* buffered fragments and deadline, and its continuation queue. The address is
|
|
1474
|
+
* deliberately not marked completed — the data unit never emitted, so a later
|
|
1475
|
+
* fragment of it may open a fresh slot rather than being rejected as stale.
|
|
1476
|
+
*/
|
|
1477
|
+
releaseSlot_fn = function(address) {
|
|
1478
|
+
const open = __privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address);
|
|
1479
|
+
if (open !== void 0) {
|
|
1480
|
+
const [runKey, active] = open;
|
|
1481
|
+
__privateGet(this, _activeDataUnits).delete(runKey);
|
|
1482
|
+
__privateMethod(this, _MFUReassembler_instances, clearTimeout_fn).call(this, active.bufferKey);
|
|
1483
|
+
__privateGet(this, _buffers).delete(active.bufferKey);
|
|
1484
|
+
}
|
|
1485
|
+
__privateMethod(this, _MFUReassembler_instances, deletePendingRun_fn).call(this, address);
|
|
1486
|
+
};
|
|
1349
1487
|
reject_fn = function(code, message) {
|
|
1350
1488
|
this.stats.rejectedFragments++;
|
|
1351
1489
|
throw new MfuReassemblyError(code, message);
|
|
1352
1490
|
};
|
|
1353
|
-
associateDataUnit_fn = function(fragment, runKey) {
|
|
1491
|
+
associateDataUnit_fn = function(fragment, runKey, address) {
|
|
1354
1492
|
const fi = fragment.fragmentationIndicator;
|
|
1355
1493
|
if (fi === 1) {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1494
|
+
const parked = __privateGet(this, _pendingContinuations).get(address);
|
|
1495
|
+
const inheritsParkedSlot = parkedRunHoldsASlot(
|
|
1496
|
+
__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0,
|
|
1497
|
+
parked?.fragments.size ?? 0
|
|
1498
|
+
);
|
|
1362
1499
|
const sampleIdentity = hasSampleIdentity(fragment);
|
|
1363
1500
|
const hasSampleFields = hasAnySampleIdentityField(fragment);
|
|
1364
1501
|
const hasItemIdentity = fragment.itemId !== void 0;
|
|
@@ -1378,29 +1515,42 @@ associateDataUnit_fn = function(fragment, runKey) {
|
|
|
1378
1515
|
if (__privateGet(this, _buffers).has(bufferKey)) {
|
|
1379
1516
|
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "overlapping-data-unit", "intrinsic data-unit buffer already exists");
|
|
1380
1517
|
}
|
|
1518
|
+
let sampleEnd = null;
|
|
1519
|
+
if (sampleIdentity) {
|
|
1520
|
+
sampleEnd = checkedSampleEnd(fragment.sampleOffset, fragment.data.byteLength);
|
|
1521
|
+
if (sampleEnd === null) __privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "offset-overflow", "sample byte offset overflow");
|
|
1522
|
+
}
|
|
1523
|
+
if (!inheritsParkedSlot && __privateMethod(this, _MFUReassembler_instances, incompleteRunCount_fn).call(this) >= __privateGet(this, _maxBuffered)) {
|
|
1524
|
+
if (!__privateMethod(this, _MFUReassembler_instances, evictLeastRecentlyAdmitted_fn).call(this)) {
|
|
1525
|
+
__privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "active-data-unit-limit", "active MFU data-unit limit reached");
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
const admissionSeq = inheritsParkedSlot ? parked.admissionSeq : __privateWrapper(this, _nextAdmissionSeq)._++;
|
|
1381
1529
|
let active2;
|
|
1382
1530
|
if (sampleIdentity) {
|
|
1383
|
-
const nextOffset = checkedSampleEnd(fragment.sampleOffset, fragment.data.byteLength);
|
|
1384
|
-
if (nextOffset === null) __privateMethod(this, _MFUReassembler_instances, reject_fn).call(this, "offset-overflow", "sample byte offset overflow");
|
|
1385
1531
|
active2 = {
|
|
1532
|
+
address,
|
|
1386
1533
|
kind: "sample",
|
|
1387
1534
|
bufferKey,
|
|
1388
1535
|
nextFragmentCounter: fragment.fragmentCounter - 1,
|
|
1536
|
+
admissionSeq,
|
|
1389
1537
|
movieFragmentSequenceNumber: fragment.movieFragmentSequenceNumber,
|
|
1390
1538
|
sampleNumber: fragment.sampleNumber,
|
|
1391
|
-
nextOffset,
|
|
1539
|
+
nextOffset: sampleEnd,
|
|
1392
1540
|
...fragment.catalogTrackName === void 0 ? {} : { catalogTrackName: fragment.catalogTrackName }
|
|
1393
1541
|
};
|
|
1394
1542
|
} else {
|
|
1395
1543
|
active2 = {
|
|
1544
|
+
address,
|
|
1396
1545
|
kind: "item",
|
|
1397
1546
|
bufferKey,
|
|
1398
1547
|
nextFragmentCounter: fragment.fragmentCounter - 1,
|
|
1548
|
+
admissionSeq,
|
|
1399
1549
|
itemId: fragment.itemId,
|
|
1400
1550
|
...fragment.catalogTrackName === void 0 ? {} : { catalogTrackName: fragment.catalogTrackName }
|
|
1401
1551
|
};
|
|
1402
1552
|
}
|
|
1403
|
-
__privateGet(this, _activeDataUnits).set(
|
|
1553
|
+
__privateGet(this, _activeDataUnits).set(objectKey(fragment), active2);
|
|
1404
1554
|
return { bufferKey, first: true, terminal: false };
|
|
1405
1555
|
}
|
|
1406
1556
|
const active = __privateGet(this, _activeDataUnits).get(runKey);
|
|
@@ -1506,25 +1656,49 @@ setTimeout_fn = function(bufferKey, startedAt = Date.now()) {
|
|
|
1506
1656
|
}
|
|
1507
1657
|
__privateGet(this, _timeouts).set(bufferKey, state);
|
|
1508
1658
|
};
|
|
1509
|
-
clearPendingTimeout_fn = function(
|
|
1510
|
-
const pending = __privateGet(this, _pendingContinuations).get(
|
|
1659
|
+
clearPendingTimeout_fn = function(address) {
|
|
1660
|
+
const pending = __privateGet(this, _pendingContinuations).get(address);
|
|
1511
1661
|
if (pending?.timeoutId !== void 0) clearTimeout(pending.timeoutId);
|
|
1512
1662
|
if (pending !== void 0) {
|
|
1513
1663
|
delete pending.timeoutId;
|
|
1514
1664
|
}
|
|
1515
1665
|
};
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1666
|
+
/**
|
|
1667
|
+
* A queue whose run closed without completing is headless again and expires
|
|
1668
|
+
* from its own first arrival, exactly as it would had the head never come.
|
|
1669
|
+
*
|
|
1670
|
+
* An empty queue is not armed: it holds no data unit that could fail to
|
|
1671
|
+
* complete, so there is nothing for a deadline to expire. Both arming sites
|
|
1672
|
+
* take that test through `parkedRunHoldsASlot` rather than filtering on the
|
|
1673
|
+
* open run alone, which is how this engine reaches Rust's answer — its
|
|
1674
|
+
* `next_timeout_at` derives candidate deadlines from the queue's live
|
|
1675
|
+
* entries, so an empty queue yields none (BLO-32420).
|
|
1676
|
+
*/
|
|
1677
|
+
armHeadlessPendingTimeout_fn = function(address) {
|
|
1678
|
+
const pending = __privateGet(this, _pendingContinuations).get(address);
|
|
1679
|
+
if (pending === void 0 || pending.timeoutId !== void 0 || __privateGet(this, _timeoutMs) === null) return;
|
|
1680
|
+
if (!parkedRunHoldsASlot(__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0, pending.fragments.size)) return;
|
|
1681
|
+
pending.timeoutId = setTimeout(
|
|
1682
|
+
() => __privateMethod(this, _MFUReassembler_instances, onPendingTimeout_fn).call(this, address),
|
|
1683
|
+
Math.max(0, pending.startedAt + __privateGet(this, _timeoutMs) - Date.now())
|
|
1684
|
+
);
|
|
1685
|
+
};
|
|
1686
|
+
deletePendingRun_fn = function(address) {
|
|
1687
|
+
__privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this, address);
|
|
1688
|
+
__privateGet(this, _pendingContinuations).delete(address);
|
|
1519
1689
|
};
|
|
1520
|
-
onPendingTimeout_fn = function(
|
|
1521
|
-
const pending = __privateGet(this, _pendingContinuations).get(
|
|
1690
|
+
onPendingTimeout_fn = function(address) {
|
|
1691
|
+
const pending = __privateGet(this, _pendingContinuations).get(address);
|
|
1522
1692
|
if (pending === void 0) return;
|
|
1693
|
+
if (!parkedRunHoldsASlot(__privateMethod(this, _MFUReassembler_instances, openRunAt_fn).call(this, address) !== void 0, pending.fragments.size)) {
|
|
1694
|
+
__privateMethod(this, _MFUReassembler_instances, clearPendingTimeout_fn).call(this, address);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1523
1697
|
this.stats.timedOutMFUs++;
|
|
1524
1698
|
console.warn(
|
|
1525
|
-
`[MfuReassembler] timeout: run=${
|
|
1699
|
+
`[MfuReassembler] timeout: run=${address} fragments=${pending.fragments.size} (continuations awaiting first fragment, discarded)`
|
|
1526
1700
|
);
|
|
1527
|
-
__privateGet(this, _pendingContinuations).delete(
|
|
1701
|
+
__privateGet(this, _pendingContinuations).delete(address);
|
|
1528
1702
|
};
|
|
1529
1703
|
clearTimeout_fn = function(bufferKey) {
|
|
1530
1704
|
const state = __privateGet(this, _timeouts).get(bufferKey);
|
|
@@ -1542,17 +1716,37 @@ markEmitted_fn = function(bufferKey) {
|
|
|
1542
1716
|
if (oldest !== void 0) __privateGet(this, _emitted).delete(oldest);
|
|
1543
1717
|
}
|
|
1544
1718
|
};
|
|
1719
|
+
markCompleted_fn = function(address) {
|
|
1720
|
+
if (__privateGet(this, _completed).has(address)) return;
|
|
1721
|
+
__privateGet(this, _completed).add(address);
|
|
1722
|
+
__privateGet(this, _completedOrder).push(address);
|
|
1723
|
+
const cap = __privateGet(this, _maxBuffered) * COMPLETED_ADDRESS_WINDOW_MULTIPLE;
|
|
1724
|
+
while (__privateGet(this, _completedOrder).length > cap) {
|
|
1725
|
+
const oldest = __privateGet(this, _completedOrder).shift();
|
|
1726
|
+
if (oldest !== void 0) __privateGet(this, _completed).delete(oldest);
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1545
1729
|
orderValue_fn = function(fragment) {
|
|
1546
1730
|
return fragmentCounterOrder(fragment);
|
|
1547
1731
|
};
|
|
1732
|
+
/** Close the run behind an expired buffer. Early arrivals parked under its
|
|
1733
|
+
* address stay, now headless, and expire from their own arrival. */
|
|
1548
1734
|
removeActiveDataUnitForBuffer_fn = function(bufferKey) {
|
|
1549
|
-
for (const [
|
|
1735
|
+
for (const [objectKey2, active] of __privateGet(this, _activeDataUnits)) {
|
|
1550
1736
|
if (active.bufferKey === bufferKey) {
|
|
1551
|
-
__privateGet(this, _activeDataUnits).delete(
|
|
1552
|
-
__privateMethod(this, _MFUReassembler_instances,
|
|
1737
|
+
__privateGet(this, _activeDataUnits).delete(objectKey2);
|
|
1738
|
+
__privateMethod(this, _MFUReassembler_instances, armHeadlessPendingTimeout_fn).call(this, active.address);
|
|
1553
1739
|
}
|
|
1554
1740
|
}
|
|
1555
1741
|
};
|
|
1742
|
+
/** The one open run holding this address, with its object key. Admission
|
|
1743
|
+
* rejects a second head on an open address, so at most one run can match. */
|
|
1744
|
+
openRunAt_fn = function(address) {
|
|
1745
|
+
for (const entry of __privateGet(this, _activeDataUnits)) {
|
|
1746
|
+
if (entry[1].address === address) return entry;
|
|
1747
|
+
}
|
|
1748
|
+
return void 0;
|
|
1749
|
+
};
|
|
1556
1750
|
onTimeout_fn = function(bufferKey) {
|
|
1557
1751
|
const fragments = __privateGet(this, _buffers).get(bufferKey);
|
|
1558
1752
|
if (!fragments || fragments.length === 0) {
|
|
@@ -1564,9 +1758,7 @@ onTimeout_fn = function(bufferKey) {
|
|
|
1564
1758
|
return;
|
|
1565
1759
|
}
|
|
1566
1760
|
this.stats.timedOutMFUs++;
|
|
1567
|
-
console.warn(
|
|
1568
|
-
`[MfuReassembler] timeout: key=${bufferKey} fragments=${fragments.length} (partial, discarded)`
|
|
1569
|
-
);
|
|
1761
|
+
console.warn(`[MfuReassembler] timeout: key=${bufferKey} fragments=${fragments.length} (partial, discarded)`);
|
|
1570
1762
|
__privateGet(this, _buffers).delete(bufferKey);
|
|
1571
1763
|
__privateGet(this, _timeouts).delete(bufferKey);
|
|
1572
1764
|
__privateMethod(this, _MFUReassembler_instances, removeActiveDataUnitForBuffer_fn).call(this, bufferKey);
|
|
@@ -1691,15 +1883,14 @@ if (void 0) {
|
|
|
1691
1883
|
rapFlag: true,
|
|
1692
1884
|
...identity
|
|
1693
1885
|
});
|
|
1886
|
+
expectRejection(() => reassembler.addFragment(complete({})), "missing-data-unit-identity");
|
|
1694
1887
|
expectRejection(
|
|
1695
|
-
() => reassembler.addFragment(
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
sampleNumber: 1
|
|
1702
|
-
})),
|
|
1888
|
+
() => reassembler.addFragment(
|
|
1889
|
+
complete({
|
|
1890
|
+
movieFragmentSequenceNumber: 2,
|
|
1891
|
+
sampleNumber: 1
|
|
1892
|
+
})
|
|
1893
|
+
),
|
|
1703
1894
|
"missing-data-unit-identity"
|
|
1704
1895
|
);
|
|
1705
1896
|
});
|
|
@@ -1721,18 +1912,20 @@ if (void 0) {
|
|
|
1721
1912
|
});
|
|
1722
1913
|
test("accepts a fragmented subsample DU whose first fragment has a nonzero authored offset", () => {
|
|
1723
1914
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
1724
|
-
expect(
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1915
|
+
expect(
|
|
1916
|
+
reassembler.addFragment({
|
|
1917
|
+
fragmentationIndicator: 1,
|
|
1918
|
+
mpuSequenceNumber: 2,
|
|
1919
|
+
fragmentCounter: 1,
|
|
1920
|
+
movieFragmentSequenceNumber: 2,
|
|
1921
|
+
sampleNumber: 1,
|
|
1922
|
+
sampleOffset: 4,
|
|
1923
|
+
packetSequenceNumber: testPacketSequence(2, 1),
|
|
1924
|
+
data: new Uint8Array([161]),
|
|
1925
|
+
timestamp: 1000n,
|
|
1926
|
+
rapFlag: true
|
|
1927
|
+
})
|
|
1928
|
+
).toBeNull();
|
|
1736
1929
|
expect(reassembler.pendingCount).toBe(1);
|
|
1737
1930
|
expect(reassembler.getStatus().pendingFragments).toBe(1);
|
|
1738
1931
|
const out = reassembler.addFragment({
|
|
@@ -1749,30 +1942,34 @@ if (void 0) {
|
|
|
1749
1942
|
});
|
|
1750
1943
|
test("sibling subsample DUs of one sample reassemble independently by offset", () => {
|
|
1751
1944
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
1752
|
-
expect(
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1945
|
+
expect(
|
|
1946
|
+
reassembler.addFragment({
|
|
1947
|
+
mpuSequenceNumber: 3,
|
|
1948
|
+
movieFragmentSequenceNumber: 5,
|
|
1949
|
+
sampleNumber: 9,
|
|
1950
|
+
fragmentationIndicator: 1,
|
|
1951
|
+
fragmentCounter: 1,
|
|
1952
|
+
sampleOffset: 0,
|
|
1953
|
+
data: new Uint8Array([161]),
|
|
1954
|
+
timestamp: 2000n,
|
|
1955
|
+
rapFlag: true,
|
|
1956
|
+
packetSequenceNumber: testPacketSequence(3, 1, 0)
|
|
1957
|
+
})
|
|
1958
|
+
).toBeNull();
|
|
1959
|
+
expect(
|
|
1960
|
+
reassembler.addFragment({
|
|
1961
|
+
mpuSequenceNumber: 3,
|
|
1962
|
+
movieFragmentSequenceNumber: 5,
|
|
1963
|
+
sampleNumber: 9,
|
|
1964
|
+
fragmentationIndicator: 1,
|
|
1965
|
+
fragmentCounter: 2,
|
|
1966
|
+
sampleOffset: 3,
|
|
1967
|
+
data: new Uint8Array([177]),
|
|
1968
|
+
timestamp: 2001n,
|
|
1969
|
+
rapFlag: false,
|
|
1970
|
+
packetSequenceNumber: testPacketSequence(3, 1, 1)
|
|
1971
|
+
})
|
|
1972
|
+
).toBeNull();
|
|
1776
1973
|
expect(reassembler.pendingCount).toBe(2);
|
|
1777
1974
|
const first = reassembler.addFragment({
|
|
1778
1975
|
mpuSequenceNumber: 3,
|
|
@@ -1785,16 +1982,27 @@ if (void 0) {
|
|
|
1785
1982
|
});
|
|
1786
1983
|
expect(first?.data).toEqual(new Uint8Array([161, 162]));
|
|
1787
1984
|
expect(first?.sampleOffset).toBe(0);
|
|
1985
|
+
expect(
|
|
1986
|
+
reassembler.addFragment({
|
|
1987
|
+
mpuSequenceNumber: 3,
|
|
1988
|
+
fragmentationIndicator: 2,
|
|
1989
|
+
fragmentCounter: 1,
|
|
1990
|
+
data: new Uint8Array([178]),
|
|
1991
|
+
timestamp: 2001n,
|
|
1992
|
+
rapFlag: false,
|
|
1993
|
+
packetSequenceNumber: testPacketSequence(3, 0, 1)
|
|
1994
|
+
})
|
|
1995
|
+
).toBeNull();
|
|
1788
1996
|
const second = reassembler.addFragment({
|
|
1789
1997
|
mpuSequenceNumber: 3,
|
|
1790
1998
|
fragmentationIndicator: 3,
|
|
1791
1999
|
fragmentCounter: 0,
|
|
1792
|
-
data: new Uint8Array([
|
|
2000
|
+
data: new Uint8Array([179]),
|
|
1793
2001
|
timestamp: 2001n,
|
|
1794
2002
|
rapFlag: false,
|
|
1795
|
-
packetSequenceNumber: testPacketSequence(3, 0,
|
|
2003
|
+
packetSequenceNumber: testPacketSequence(3, 0, 2)
|
|
1796
2004
|
});
|
|
1797
|
-
expect(second?.data).toEqual(new Uint8Array([177, 178]));
|
|
2005
|
+
expect(second?.data).toEqual(new Uint8Array([177, 178, 179]));
|
|
1798
2006
|
expect(second?.sampleOffset).toBe(3);
|
|
1799
2007
|
expect(reassembler.pendingCount).toBe(0);
|
|
1800
2008
|
});
|
|
@@ -1833,10 +2041,7 @@ if (void 0) {
|
|
|
1833
2041
|
packetSequenceNumber: testPacketSequence(1, fragmentCounter)
|
|
1834
2042
|
});
|
|
1835
2043
|
expect(reassembler.addFragment(fragment(10, 1, 1, 16))).toBeNull();
|
|
1836
|
-
expectRejection(
|
|
1837
|
-
() => reassembler.addFragment(fragment(20, 1, 1, 32)),
|
|
1838
|
-
"overlapping-data-unit"
|
|
1839
|
-
);
|
|
2044
|
+
expectRejection(() => reassembler.addFragment(fragment(20, 1, 1, 32)), "overlapping-data-unit");
|
|
1840
2045
|
expect(reassembler.pendingCount).toBe(1);
|
|
1841
2046
|
expect(reassembler.addFragment(fragment(10, 3, 0, 17))?.data).toEqual(new Uint8Array([16, 17]));
|
|
1842
2047
|
expect(reassembler.pendingCount).toBe(0);
|
|
@@ -1888,15 +2093,17 @@ if (void 0) {
|
|
|
1888
2093
|
});
|
|
1889
2094
|
test("buffers a terminal continuation until its authored first fragment arrives", () => {
|
|
1890
2095
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
1891
|
-
expect(
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
2096
|
+
expect(
|
|
2097
|
+
reassembler.addFragment({
|
|
2098
|
+
fragmentationIndicator: 3,
|
|
2099
|
+
mpuSequenceNumber: 1,
|
|
2100
|
+
fragmentCounter: 0,
|
|
2101
|
+
data: new Uint8Array([3, 4]),
|
|
2102
|
+
timestamp: 1000n,
|
|
2103
|
+
rapFlag: false,
|
|
2104
|
+
packetSequenceNumber: testPacketSequence(1, 0)
|
|
2105
|
+
})
|
|
2106
|
+
).toBeNull();
|
|
1900
2107
|
expect(reassembler.pendingCount).toBe(1);
|
|
1901
2108
|
const completed = reassembler.addFragment({
|
|
1902
2109
|
fragmentationIndicator: 1,
|
|
@@ -1957,25 +2164,29 @@ if (void 0) {
|
|
|
1957
2164
|
});
|
|
1958
2165
|
test("should reassemble 3 fragments (FI=1, FI=2, FI=3)", () => {
|
|
1959
2166
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
1960
|
-
expect(
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2167
|
+
expect(
|
|
2168
|
+
reassembler.addFragment({
|
|
2169
|
+
fragmentationIndicator: 1,
|
|
2170
|
+
mpuSequenceNumber: 10,
|
|
2171
|
+
fragmentCounter: 2,
|
|
2172
|
+
itemId: 1,
|
|
2173
|
+
data: new Uint8Array([170, 187]),
|
|
2174
|
+
timestamp: 5000n,
|
|
2175
|
+
rapFlag: true,
|
|
2176
|
+
packetSequenceNumber: testPacketSequence(10, 2)
|
|
2177
|
+
})
|
|
2178
|
+
).toBeNull();
|
|
2179
|
+
expect(
|
|
2180
|
+
reassembler.addFragment({
|
|
2181
|
+
fragmentationIndicator: 2,
|
|
2182
|
+
mpuSequenceNumber: 10,
|
|
2183
|
+
fragmentCounter: 1,
|
|
2184
|
+
data: new Uint8Array([204, 221]),
|
|
2185
|
+
timestamp: 5000n,
|
|
2186
|
+
rapFlag: false,
|
|
2187
|
+
packetSequenceNumber: testPacketSequence(10, 1)
|
|
2188
|
+
})
|
|
2189
|
+
).toBeNull();
|
|
1979
2190
|
const result = reassembler.addFragment({
|
|
1980
2191
|
fragmentationIndicator: 3,
|
|
1981
2192
|
mpuSequenceNumber: 10,
|
|
@@ -2067,16 +2278,18 @@ if (void 0) {
|
|
|
2067
2278
|
expect(a).not.toBeNull();
|
|
2068
2279
|
expect(a.data).toEqual(new Uint8Array([161, 162]));
|
|
2069
2280
|
expect(a.rapFlag).toBe(true);
|
|
2070
|
-
expect(
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2281
|
+
expect(
|
|
2282
|
+
reassembler.addFragment({
|
|
2283
|
+
fragmentationIndicator: 1,
|
|
2284
|
+
mpuSequenceNumber: mpuSeq,
|
|
2285
|
+
fragmentCounter: 1,
|
|
2286
|
+
itemId: 20,
|
|
2287
|
+
data: new Uint8Array([177]),
|
|
2288
|
+
timestamp: 200n,
|
|
2289
|
+
rapFlag: false,
|
|
2290
|
+
packetSequenceNumber: testPacketSequence(mpuSeq, 1, 10)
|
|
2291
|
+
})
|
|
2292
|
+
).toBeNull();
|
|
2080
2293
|
const b = reassembler.addFragment({
|
|
2081
2294
|
fragmentationIndicator: 3,
|
|
2082
2295
|
mpuSequenceNumber: mpuSeq,
|
|
@@ -2096,26 +2309,30 @@ if (void 0) {
|
|
|
2096
2309
|
movieFragmentSequenceNumber: 9,
|
|
2097
2310
|
sampleNumber: 17
|
|
2098
2311
|
};
|
|
2099
|
-
expect(
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2312
|
+
expect(
|
|
2313
|
+
reassembler.addFragment({
|
|
2314
|
+
...common,
|
|
2315
|
+
fragmentationIndicator: 1,
|
|
2316
|
+
fragmentCounter: 2,
|
|
2317
|
+
sampleOffset: 0,
|
|
2318
|
+
data: new Uint8Array([161]),
|
|
2319
|
+
timestamp: 1000n,
|
|
2320
|
+
rapFlag: true,
|
|
2321
|
+
packetSequenceNumber: testPacketSequence(0, 2)
|
|
2322
|
+
})
|
|
2323
|
+
).toBeNull();
|
|
2324
|
+
expect(
|
|
2325
|
+
reassembler.addFragment({
|
|
2326
|
+
...common,
|
|
2327
|
+
fragmentationIndicator: 2,
|
|
2328
|
+
fragmentCounter: 1,
|
|
2329
|
+
sampleOffset: 1,
|
|
2330
|
+
data: new Uint8Array([178]),
|
|
2331
|
+
timestamp: 1001n,
|
|
2332
|
+
rapFlag: false,
|
|
2333
|
+
packetSequenceNumber: testPacketSequence(0, 1)
|
|
2334
|
+
})
|
|
2335
|
+
).toBeNull();
|
|
2119
2336
|
const out = reassembler.addFragment({
|
|
2120
2337
|
...common,
|
|
2121
2338
|
fragmentationIndicator: 3,
|
|
@@ -2141,23 +2358,27 @@ if (void 0) {
|
|
|
2141
2358
|
timestamp: 1000n,
|
|
2142
2359
|
rapFlag: true
|
|
2143
2360
|
};
|
|
2144
|
-
expect(
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2361
|
+
expect(
|
|
2362
|
+
reassembler.addFragment({
|
|
2363
|
+
...common,
|
|
2364
|
+
fragmentationIndicator: 1,
|
|
2365
|
+
fragmentCounter: 2,
|
|
2366
|
+
sampleOffset: 0,
|
|
2367
|
+
data: new Uint8Array([161, 162]),
|
|
2368
|
+
packetSequenceNumber: testPacketSequence(0, 2)
|
|
2369
|
+
})
|
|
2370
|
+
).toBeNull();
|
|
2371
|
+
expect(
|
|
2372
|
+
reassembler.addFragment({
|
|
2373
|
+
...common,
|
|
2374
|
+
fragmentationIndicator: 3,
|
|
2375
|
+
fragmentCounter: 0,
|
|
2376
|
+
sampleOffset: 4,
|
|
2377
|
+
data: new Uint8Array([193, 194]),
|
|
2378
|
+
packetSequenceNumber: testPacketSequence(0, 0)
|
|
2379
|
+
})
|
|
2380
|
+
).toBeNull();
|
|
2381
|
+
expect(reassembler.pendingCount).toBe(1);
|
|
2161
2382
|
const out = reassembler.addFragment({
|
|
2162
2383
|
...common,
|
|
2163
2384
|
fragmentationIndicator: 2,
|
|
@@ -2166,33 +2387,35 @@ if (void 0) {
|
|
|
2166
2387
|
data: new Uint8Array([177, 178]),
|
|
2167
2388
|
packetSequenceNumber: testPacketSequence(0, 1)
|
|
2168
2389
|
});
|
|
2169
|
-
expect(out?.data).toEqual(
|
|
2170
|
-
new Uint8Array([161, 162, 177, 178, 193, 194])
|
|
2171
|
-
);
|
|
2390
|
+
expect(out?.data).toEqual(new Uint8Array([161, 162, 177, 178, 193, 194]));
|
|
2172
2391
|
});
|
|
2173
2392
|
test("reassembles first-only DU-header fragments across changing packet timestamps", () => {
|
|
2174
2393
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2175
|
-
expect(
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2394
|
+
expect(
|
|
2395
|
+
reassembler.addFragment({
|
|
2396
|
+
fragmentationIndicator: 1,
|
|
2397
|
+
mpuSequenceNumber: 44,
|
|
2398
|
+
fragmentCounter: 2,
|
|
2399
|
+
movieFragmentSequenceNumber: 9,
|
|
2400
|
+
sampleNumber: 17,
|
|
2401
|
+
sampleOffset: 0,
|
|
2402
|
+
data: new Uint8Array([161]),
|
|
2403
|
+
timestamp: 12345n,
|
|
2404
|
+
rapFlag: true,
|
|
2405
|
+
packetSequenceNumber: testPacketSequence(44, 2)
|
|
2406
|
+
})
|
|
2407
|
+
).toBeNull();
|
|
2408
|
+
expect(
|
|
2409
|
+
reassembler.addFragment({
|
|
2410
|
+
fragmentationIndicator: 2,
|
|
2411
|
+
mpuSequenceNumber: 44,
|
|
2412
|
+
fragmentCounter: 1,
|
|
2413
|
+
data: new Uint8Array([178]),
|
|
2414
|
+
timestamp: 12346n,
|
|
2415
|
+
rapFlag: false,
|
|
2416
|
+
packetSequenceNumber: testPacketSequence(44, 1)
|
|
2417
|
+
})
|
|
2418
|
+
).toBeNull();
|
|
2196
2419
|
const out = reassembler.addFragment({
|
|
2197
2420
|
fragmentationIndicator: 3,
|
|
2198
2421
|
mpuSequenceNumber: 44,
|
|
@@ -2211,36 +2434,42 @@ if (void 0) {
|
|
|
2211
2434
|
test("reassembles first-only DU-header fragments with an ISO countdown", () => {
|
|
2212
2435
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2213
2436
|
const timestamp = 12345n;
|
|
2214
|
-
expect(
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2437
|
+
expect(
|
|
2438
|
+
reassembler.addFragment({
|
|
2439
|
+
fragmentationIndicator: 1,
|
|
2440
|
+
mpuSequenceNumber: 44,
|
|
2441
|
+
fragmentCounter: 3,
|
|
2442
|
+
movieFragmentSequenceNumber: 9,
|
|
2443
|
+
sampleNumber: 17,
|
|
2444
|
+
sampleOffset: 0,
|
|
2445
|
+
data: new Uint8Array([161]),
|
|
2446
|
+
timestamp,
|
|
2447
|
+
rapFlag: true,
|
|
2448
|
+
packetSequenceNumber: testPacketSequence(44, 3)
|
|
2449
|
+
})
|
|
2450
|
+
).toBeNull();
|
|
2451
|
+
expect(
|
|
2452
|
+
reassembler.addFragment({
|
|
2453
|
+
fragmentationIndicator: 2,
|
|
2454
|
+
mpuSequenceNumber: 44,
|
|
2455
|
+
fragmentCounter: 2,
|
|
2456
|
+
data: new Uint8Array([178]),
|
|
2457
|
+
timestamp,
|
|
2458
|
+
rapFlag: false,
|
|
2459
|
+
packetSequenceNumber: testPacketSequence(44, 2)
|
|
2460
|
+
})
|
|
2461
|
+
).toBeNull();
|
|
2462
|
+
expect(
|
|
2463
|
+
reassembler.addFragment({
|
|
2464
|
+
fragmentationIndicator: 2,
|
|
2465
|
+
mpuSequenceNumber: 44,
|
|
2466
|
+
fragmentCounter: 1,
|
|
2467
|
+
data: new Uint8Array([195]),
|
|
2468
|
+
timestamp,
|
|
2469
|
+
rapFlag: false,
|
|
2470
|
+
packetSequenceNumber: testPacketSequence(44, 1)
|
|
2471
|
+
})
|
|
2472
|
+
).toBeNull();
|
|
2244
2473
|
const out = reassembler.addFragment({
|
|
2245
2474
|
fragmentationIndicator: 3,
|
|
2246
2475
|
mpuSequenceNumber: 44,
|
|
@@ -2258,107 +2487,134 @@ if (void 0) {
|
|
|
2258
2487
|
test("rejects fragmented MFUs without an authored DU identity", () => {
|
|
2259
2488
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2260
2489
|
const timestamp = 22222n;
|
|
2261
|
-
expectRejection(
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2490
|
+
expectRejection(
|
|
2491
|
+
() => reassembler.addFragment({
|
|
2492
|
+
fragmentationIndicator: 1,
|
|
2493
|
+
mpuSequenceNumber: 55,
|
|
2494
|
+
fragmentCounter: 2,
|
|
2495
|
+
data: new Uint8Array([161]),
|
|
2496
|
+
timestamp,
|
|
2497
|
+
rapFlag: true,
|
|
2498
|
+
packetSequenceNumber: testPacketSequence(55, 2)
|
|
2499
|
+
}),
|
|
2500
|
+
"missing-data-unit-identity"
|
|
2501
|
+
);
|
|
2270
2502
|
expect(reassembler.pendingCount).toBe(0);
|
|
2271
2503
|
});
|
|
2272
2504
|
test("rejects fragmented MFUs without an authored packet sequence", () => {
|
|
2273
2505
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2274
|
-
expectRejection(
|
|
2275
|
-
|
|
2276
|
-
mpuSequenceNumber: 55,
|
|
2277
|
-
fragmentCounter: 1,
|
|
2278
|
-
itemId: 1,
|
|
2279
|
-
data: new Uint8Array([161]),
|
|
2280
|
-
timestamp: 22222n,
|
|
2281
|
-
rapFlag: true
|
|
2282
|
-
}), "missing-packet-sequence");
|
|
2283
|
-
expect(reassembler.pendingCount).toBe(0);
|
|
2284
|
-
});
|
|
2285
|
-
test("rejects fragmented MFUs whose authored packet sequence is not u32", () => {
|
|
2286
|
-
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2287
|
-
for (const packetSequenceNumber of [-1, 4294967296, 1.5, Number.NaN]) {
|
|
2288
|
-
expectRejection(() => reassembler.addFragment({
|
|
2506
|
+
expectRejection(
|
|
2507
|
+
() => reassembler.addFragment({
|
|
2289
2508
|
fragmentationIndicator: 1,
|
|
2290
2509
|
mpuSequenceNumber: 55,
|
|
2291
2510
|
fragmentCounter: 1,
|
|
2292
2511
|
itemId: 1,
|
|
2293
2512
|
data: new Uint8Array([161]),
|
|
2294
2513
|
timestamp: 22222n,
|
|
2295
|
-
rapFlag: true
|
|
2296
|
-
|
|
2297
|
-
|
|
2514
|
+
rapFlag: true
|
|
2515
|
+
}),
|
|
2516
|
+
"missing-packet-sequence"
|
|
2517
|
+
);
|
|
2518
|
+
expect(reassembler.pendingCount).toBe(0);
|
|
2519
|
+
});
|
|
2520
|
+
test("rejects fragmented MFUs whose authored packet sequence is not u32", () => {
|
|
2521
|
+
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2522
|
+
for (const packetSequenceNumber of [-1, 4294967296, 1.5, Number.NaN]) {
|
|
2523
|
+
expectRejection(
|
|
2524
|
+
() => reassembler.addFragment({
|
|
2525
|
+
fragmentationIndicator: 1,
|
|
2526
|
+
mpuSequenceNumber: 55,
|
|
2527
|
+
fragmentCounter: 1,
|
|
2528
|
+
itemId: 1,
|
|
2529
|
+
data: new Uint8Array([161]),
|
|
2530
|
+
timestamp: 22222n,
|
|
2531
|
+
rapFlag: true,
|
|
2532
|
+
packetSequenceNumber
|
|
2533
|
+
}),
|
|
2534
|
+
"missing-packet-sequence"
|
|
2535
|
+
);
|
|
2298
2536
|
}
|
|
2299
2537
|
expect(reassembler.pendingCount).toBe(0);
|
|
2300
2538
|
});
|
|
2301
|
-
test("
|
|
2539
|
+
test("a packet sequence gap inside a fragmented DU splits it and never completes", () => {
|
|
2302
2540
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2303
2541
|
const timestamp = 66666n;
|
|
2304
|
-
expect(
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
fragmentCounter: 17,
|
|
2308
|
-
movieFragmentSequenceNumber: 241,
|
|
2309
|
-
sampleNumber: 1,
|
|
2310
|
-
sampleOffset: 0,
|
|
2311
|
-
packetSequenceNumber: 1e3,
|
|
2312
|
-
data: new Uint8Array([160]),
|
|
2313
|
-
timestamp,
|
|
2314
|
-
rapFlag: true
|
|
2315
|
-
})).toBeNull();
|
|
2316
|
-
for (let i = 1; i < 17; i++) {
|
|
2317
|
-
expect(reassembler.addFragment({
|
|
2318
|
-
fragmentationIndicator: 2,
|
|
2542
|
+
expect(
|
|
2543
|
+
reassembler.addFragment({
|
|
2544
|
+
fragmentationIndicator: 1,
|
|
2319
2545
|
mpuSequenceNumber: 80,
|
|
2320
|
-
fragmentCounter:
|
|
2321
|
-
|
|
2322
|
-
|
|
2546
|
+
fragmentCounter: 3,
|
|
2547
|
+
movieFragmentSequenceNumber: 241,
|
|
2548
|
+
sampleNumber: 1,
|
|
2549
|
+
sampleOffset: 0,
|
|
2550
|
+
packetSequenceNumber: 1e3,
|
|
2551
|
+
data: new Uint8Array([160]),
|
|
2323
2552
|
timestamp,
|
|
2324
|
-
rapFlag:
|
|
2325
|
-
})
|
|
2553
|
+
rapFlag: true
|
|
2554
|
+
})
|
|
2555
|
+
).toBeNull();
|
|
2556
|
+
for (const [counter, psn] of [
|
|
2557
|
+
[2, 1002],
|
|
2558
|
+
[1, 1003]
|
|
2559
|
+
]) {
|
|
2560
|
+
expect(
|
|
2561
|
+
reassembler.addFragment({
|
|
2562
|
+
fragmentationIndicator: 2,
|
|
2563
|
+
mpuSequenceNumber: 80,
|
|
2564
|
+
fragmentCounter: counter,
|
|
2565
|
+
packetSequenceNumber: psn,
|
|
2566
|
+
data: new Uint8Array([160 + counter]),
|
|
2567
|
+
timestamp,
|
|
2568
|
+
rapFlag: false
|
|
2569
|
+
})
|
|
2570
|
+
).toBeNull();
|
|
2326
2571
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2572
|
+
expect(
|
|
2573
|
+
reassembler.addFragment({
|
|
2574
|
+
fragmentationIndicator: 3,
|
|
2575
|
+
mpuSequenceNumber: 80,
|
|
2576
|
+
fragmentCounter: 0,
|
|
2577
|
+
packetSequenceNumber: 1004,
|
|
2578
|
+
data: new Uint8Array([177]),
|
|
2579
|
+
timestamp,
|
|
2580
|
+
rapFlag: false
|
|
2581
|
+
})
|
|
2582
|
+
).toBeNull();
|
|
2583
|
+
expect(reassembler.pendingCount).toBe(2);
|
|
2584
|
+
expect(reassembler.getStatus().pendingFragments).toBe(4);
|
|
2338
2585
|
expect(reassembler.stats.reassembledMFUs).toBe(0);
|
|
2586
|
+
expect(reassembler.stats.rejectedFragments).toBe(0);
|
|
2339
2587
|
});
|
|
2340
|
-
test("
|
|
2588
|
+
test("keeps adjacent MFUs with equal countdowns in one MPU separate", () => {
|
|
2341
2589
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2342
2590
|
const common = { mpuSequenceNumber: 80, packetId: 7, timestamp: 66666n };
|
|
2343
|
-
expect(
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2591
|
+
expect(
|
|
2592
|
+
reassembler.addFragment({
|
|
2593
|
+
...common,
|
|
2594
|
+
fragmentationIndicator: 1,
|
|
2595
|
+
fragmentCounter: 1,
|
|
2596
|
+
packetSequenceNumber: 100,
|
|
2597
|
+
movieFragmentSequenceNumber: 241,
|
|
2598
|
+
sampleNumber: 1,
|
|
2599
|
+
sampleOffset: 0,
|
|
2600
|
+
data: new Uint8Array([161]),
|
|
2601
|
+
rapFlag: true
|
|
2602
|
+
})
|
|
2603
|
+
).toBeNull();
|
|
2604
|
+
expect(
|
|
2605
|
+
reassembler.addFragment({
|
|
2606
|
+
...common,
|
|
2607
|
+
fragmentationIndicator: 1,
|
|
2608
|
+
fragmentCounter: 1,
|
|
2609
|
+
packetSequenceNumber: 102,
|
|
2610
|
+
movieFragmentSequenceNumber: 241,
|
|
2611
|
+
sampleNumber: 2,
|
|
2612
|
+
sampleOffset: 0,
|
|
2613
|
+
data: new Uint8Array([177]),
|
|
2614
|
+
rapFlag: false
|
|
2615
|
+
})
|
|
2616
|
+
).toBeNull();
|
|
2617
|
+
expect(reassembler.pendingCount).toBe(2);
|
|
2362
2618
|
const earlier = reassembler.addFragment({
|
|
2363
2619
|
...common,
|
|
2364
2620
|
fragmentationIndicator: 3,
|
|
@@ -2368,105 +2624,122 @@ if (void 0) {
|
|
|
2368
2624
|
rapFlag: false
|
|
2369
2625
|
});
|
|
2370
2626
|
expect(earlier?.data).toEqual(new Uint8Array([161, 162]));
|
|
2627
|
+
expect(earlier?.packetSequenceNumbers).toEqual([100, 101]);
|
|
2371
2628
|
const later = reassembler.addFragment({
|
|
2372
2629
|
...common,
|
|
2373
|
-
fragmentationIndicator:
|
|
2374
|
-
fragmentCounter:
|
|
2375
|
-
packetSequenceNumber:
|
|
2376
|
-
|
|
2377
|
-
sampleNumber: 2,
|
|
2378
|
-
sampleOffset: 0,
|
|
2379
|
-
data: new Uint8Array([177]),
|
|
2630
|
+
fragmentationIndicator: 3,
|
|
2631
|
+
fragmentCounter: 0,
|
|
2632
|
+
packetSequenceNumber: 103,
|
|
2633
|
+
data: new Uint8Array([178]),
|
|
2380
2634
|
rapFlag: false
|
|
2381
2635
|
});
|
|
2382
2636
|
expect(later?.data).toEqual(new Uint8Array([177, 178]));
|
|
2637
|
+
expect(later?.packetSequenceNumbers).toEqual([102, 103]);
|
|
2638
|
+
expect(reassembler.pendingCount).toBe(0);
|
|
2383
2639
|
});
|
|
2384
2640
|
test("wraps the fragmented run coordinate at u32 packet sequence", () => {
|
|
2385
2641
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2386
|
-
expect(
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2642
|
+
expect(
|
|
2643
|
+
reassembler.addFragment({
|
|
2644
|
+
fragmentationIndicator: 1,
|
|
2645
|
+
mpuSequenceNumber: 81,
|
|
2646
|
+
fragmentCounter: 1,
|
|
2647
|
+
packetSequenceNumber: 4294967295,
|
|
2648
|
+
itemId: 1,
|
|
2649
|
+
data: new Uint8Array([161]),
|
|
2650
|
+
timestamp: 66666n,
|
|
2651
|
+
rapFlag: true
|
|
2652
|
+
})
|
|
2653
|
+
).toBeNull();
|
|
2654
|
+
expect(
|
|
2655
|
+
reassembler.addFragment({
|
|
2656
|
+
fragmentationIndicator: 3,
|
|
2657
|
+
mpuSequenceNumber: 81,
|
|
2658
|
+
fragmentCounter: 0,
|
|
2659
|
+
packetSequenceNumber: 0,
|
|
2660
|
+
data: new Uint8Array([162]),
|
|
2661
|
+
timestamp: 66667n,
|
|
2662
|
+
rapFlag: false
|
|
2663
|
+
})?.data
|
|
2664
|
+
).toEqual(new Uint8Array([161, 162]));
|
|
2405
2665
|
});
|
|
2406
2666
|
test("dedupes recovered copies by ISO fragment-counter position", () => {
|
|
2407
2667
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2408
2668
|
const timestamp = 44444n;
|
|
2409
|
-
expect(
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2669
|
+
expect(
|
|
2670
|
+
reassembler.addFragment({
|
|
2671
|
+
fragmentationIndicator: 1,
|
|
2672
|
+
mpuSequenceNumber: 77,
|
|
2673
|
+
fragmentCounter: 2,
|
|
2674
|
+
movieFragmentSequenceNumber: 12,
|
|
2675
|
+
sampleNumber: 1,
|
|
2676
|
+
sampleOffset: 0,
|
|
2677
|
+
packetSequenceNumber: 2e3,
|
|
2678
|
+
data: new Uint8Array([161]),
|
|
2679
|
+
timestamp,
|
|
2680
|
+
rapFlag: true
|
|
2681
|
+
})
|
|
2682
|
+
).toBeNull();
|
|
2683
|
+
expect(
|
|
2684
|
+
reassembler.addFragment({
|
|
2685
|
+
fragmentationIndicator: 2,
|
|
2686
|
+
mpuSequenceNumber: 77,
|
|
2687
|
+
fragmentCounter: 1,
|
|
2688
|
+
packetSequenceNumber: 2001,
|
|
2689
|
+
data: new Uint8Array([178]),
|
|
2690
|
+
timestamp,
|
|
2691
|
+
rapFlag: false
|
|
2692
|
+
})
|
|
2693
|
+
).toBeNull();
|
|
2694
|
+
expect(
|
|
2695
|
+
reassembler.addFragment({
|
|
2696
|
+
fragmentationIndicator: 3,
|
|
2697
|
+
mpuSequenceNumber: 77,
|
|
2698
|
+
fragmentCounter: 0,
|
|
2699
|
+
packetSequenceNumber: 2002,
|
|
2700
|
+
data: new Uint8Array([195]),
|
|
2701
|
+
timestamp,
|
|
2702
|
+
rapFlag: false
|
|
2703
|
+
})
|
|
2704
|
+
).not.toBeNull();
|
|
2439
2705
|
reassembler.flush();
|
|
2440
|
-
expect(
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2706
|
+
expect(
|
|
2707
|
+
reassembler.addFragment({
|
|
2708
|
+
fragmentationIndicator: 1,
|
|
2709
|
+
mpuSequenceNumber: 78,
|
|
2710
|
+
fragmentCounter: 2,
|
|
2711
|
+
movieFragmentSequenceNumber: 13,
|
|
2712
|
+
sampleNumber: 1,
|
|
2713
|
+
sampleOffset: 0,
|
|
2714
|
+
packetSequenceNumber: 3e3,
|
|
2715
|
+
data: new Uint8Array([212]),
|
|
2716
|
+
timestamp,
|
|
2717
|
+
rapFlag: true
|
|
2718
|
+
})
|
|
2719
|
+
).toBeNull();
|
|
2720
|
+
expect(
|
|
2721
|
+
reassembler.addFragment({
|
|
2722
|
+
fragmentationIndicator: 2,
|
|
2723
|
+
mpuSequenceNumber: 78,
|
|
2724
|
+
fragmentCounter: 1,
|
|
2725
|
+
packetSequenceNumber: 3001,
|
|
2726
|
+
data: new Uint8Array([229]),
|
|
2727
|
+
timestamp,
|
|
2728
|
+
rapFlag: false
|
|
2729
|
+
})
|
|
2730
|
+
).toBeNull();
|
|
2731
|
+
expectRejection(
|
|
2732
|
+
() => reassembler.addFragment({
|
|
2733
|
+
fragmentationIndicator: 2,
|
|
2734
|
+
mpuSequenceNumber: 78,
|
|
2735
|
+
fragmentCounter: 1,
|
|
2736
|
+
packetSequenceNumber: 3001,
|
|
2737
|
+
data: new Uint8Array([229]),
|
|
2738
|
+
timestamp,
|
|
2739
|
+
rapFlag: false
|
|
2740
|
+
}),
|
|
2741
|
+
"fragment-counter-mismatch"
|
|
2742
|
+
);
|
|
2470
2743
|
const out = reassembler.addFragment({
|
|
2471
2744
|
fragmentationIndicator: 3,
|
|
2472
2745
|
mpuSequenceNumber: 78,
|
|
@@ -2482,24 +2755,28 @@ if (void 0) {
|
|
|
2482
2755
|
test("reassembles middle and terminal continuations that arrive before first", () => {
|
|
2483
2756
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2484
2757
|
const timestamp = 54321n;
|
|
2485
|
-
expect(
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2758
|
+
expect(
|
|
2759
|
+
reassembler.addFragment({
|
|
2760
|
+
fragmentationIndicator: 2,
|
|
2761
|
+
mpuSequenceNumber: 45,
|
|
2762
|
+
fragmentCounter: 1,
|
|
2763
|
+
data: new Uint8Array([178]),
|
|
2764
|
+
timestamp,
|
|
2765
|
+
rapFlag: false,
|
|
2766
|
+
packetSequenceNumber: testPacketSequence(45, 1)
|
|
2767
|
+
})
|
|
2768
|
+
).toBeNull();
|
|
2769
|
+
expect(
|
|
2770
|
+
reassembler.addFragment({
|
|
2771
|
+
fragmentationIndicator: 3,
|
|
2772
|
+
mpuSequenceNumber: 45,
|
|
2773
|
+
fragmentCounter: 0,
|
|
2774
|
+
data: new Uint8Array([195]),
|
|
2775
|
+
timestamp: timestamp + 1n,
|
|
2776
|
+
rapFlag: false,
|
|
2777
|
+
packetSequenceNumber: testPacketSequence(45, 0)
|
|
2778
|
+
})
|
|
2779
|
+
).toBeNull();
|
|
2503
2780
|
expect(reassembler.pendingCount).toBe(1);
|
|
2504
2781
|
const out = reassembler.addFragment({
|
|
2505
2782
|
fragmentationIndicator: 1,
|
|
@@ -2522,33 +2799,40 @@ if (void 0) {
|
|
|
2522
2799
|
test("rejects a duplicate continuation before first without replacing retained media", () => {
|
|
2523
2800
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2524
2801
|
const timestamp = 54322n;
|
|
2525
|
-
expect(
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2802
|
+
expect(
|
|
2803
|
+
reassembler.addFragment({
|
|
2804
|
+
fragmentationIndicator: 3,
|
|
2805
|
+
mpuSequenceNumber: 46,
|
|
2806
|
+
fragmentCounter: 0,
|
|
2807
|
+
data: new Uint8Array([195]),
|
|
2808
|
+
timestamp,
|
|
2809
|
+
rapFlag: false,
|
|
2810
|
+
packetSequenceNumber: testPacketSequence(46, 0)
|
|
2811
|
+
})
|
|
2812
|
+
).toBeNull();
|
|
2813
|
+
expectRejection(
|
|
2814
|
+
() => reassembler.addFragment({
|
|
2815
|
+
fragmentationIndicator: 3,
|
|
2816
|
+
mpuSequenceNumber: 46,
|
|
2817
|
+
fragmentCounter: 0,
|
|
2818
|
+
data: new Uint8Array([255]),
|
|
2819
|
+
timestamp: timestamp + 1n,
|
|
2820
|
+
rapFlag: false,
|
|
2821
|
+
packetSequenceNumber: testPacketSequence(46, 0)
|
|
2822
|
+
}),
|
|
2823
|
+
"fragment-counter-mismatch"
|
|
2824
|
+
);
|
|
2825
|
+
expect(
|
|
2826
|
+
reassembler.addFragment({
|
|
2827
|
+
fragmentationIndicator: 2,
|
|
2828
|
+
mpuSequenceNumber: 46,
|
|
2829
|
+
fragmentCounter: 1,
|
|
2830
|
+
data: new Uint8Array([178]),
|
|
2831
|
+
timestamp: timestamp + 2n,
|
|
2832
|
+
rapFlag: false,
|
|
2833
|
+
packetSequenceNumber: testPacketSequence(46, 1)
|
|
2834
|
+
})
|
|
2835
|
+
).toBeNull();
|
|
2552
2836
|
const out = reassembler.addFragment({
|
|
2553
2837
|
fragmentationIndicator: 1,
|
|
2554
2838
|
mpuSequenceNumber: 46,
|
|
@@ -2581,13 +2865,16 @@ if (void 0) {
|
|
|
2581
2865
|
{ fragmentationIndicator: 1, fragmentCounter: Number.NaN }
|
|
2582
2866
|
];
|
|
2583
2867
|
for (const header of invalidHeaders) {
|
|
2584
|
-
expectRejection(
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2868
|
+
expectRejection(
|
|
2869
|
+
() => reassembler.addFragment({
|
|
2870
|
+
...header,
|
|
2871
|
+
mpuSequenceNumber: 48,
|
|
2872
|
+
data: new Uint8Array([161]),
|
|
2873
|
+
timestamp: 54324n,
|
|
2874
|
+
rapFlag: false
|
|
2875
|
+
}),
|
|
2876
|
+
"invalid-fragment-state"
|
|
2877
|
+
);
|
|
2591
2878
|
}
|
|
2592
2879
|
expect(reassembler.pendingCount).toBe(0);
|
|
2593
2880
|
expect(reassembler.stats.reassembledMFUs).toBe(0);
|
|
@@ -2599,23 +2886,27 @@ if (void 0) {
|
|
|
2599
2886
|
timestamp: 54323n,
|
|
2600
2887
|
rapFlag: false
|
|
2601
2888
|
};
|
|
2602
|
-
expect(
|
|
2603
|
-
|
|
2604
|
-
fragmentationIndicator: 1,
|
|
2605
|
-
fragmentCounter: 255,
|
|
2606
|
-
itemId: 1,
|
|
2607
|
-
data: new Uint8Array([0]),
|
|
2608
|
-
rapFlag: true,
|
|
2609
|
-
packetSequenceNumber: testPacketSequence(47, 255)
|
|
2610
|
-
})).toBeNull();
|
|
2611
|
-
for (let counter = 254; counter > 0; counter--) {
|
|
2612
|
-
expect(reassembler.addFragment({
|
|
2889
|
+
expect(
|
|
2890
|
+
reassembler.addFragment({
|
|
2613
2891
|
...common,
|
|
2614
|
-
fragmentationIndicator:
|
|
2615
|
-
fragmentCounter:
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2892
|
+
fragmentationIndicator: 1,
|
|
2893
|
+
fragmentCounter: 255,
|
|
2894
|
+
itemId: 1,
|
|
2895
|
+
data: new Uint8Array([0]),
|
|
2896
|
+
rapFlag: true,
|
|
2897
|
+
packetSequenceNumber: testPacketSequence(47, 255)
|
|
2898
|
+
})
|
|
2899
|
+
).toBeNull();
|
|
2900
|
+
for (let counter = 254; counter > 0; counter--) {
|
|
2901
|
+
expect(
|
|
2902
|
+
reassembler.addFragment({
|
|
2903
|
+
...common,
|
|
2904
|
+
fragmentationIndicator: 2,
|
|
2905
|
+
fragmentCounter: counter,
|
|
2906
|
+
data: new Uint8Array([255 - counter]),
|
|
2907
|
+
packetSequenceNumber: testPacketSequence(47, counter)
|
|
2908
|
+
})
|
|
2909
|
+
).toBeNull();
|
|
2619
2910
|
}
|
|
2620
2911
|
const out = reassembler.addFragment({
|
|
2621
2912
|
...common,
|
|
@@ -2652,37 +2943,49 @@ if (void 0) {
|
|
|
2652
2943
|
expect(reassembler.pendingCount).toBe(1);
|
|
2653
2944
|
});
|
|
2654
2945
|
test("bounds continuations that arrive before their first fragments", () => {
|
|
2655
|
-
const reassembler = new MFUReassembler({
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2946
|
+
const reassembler = new MFUReassembler({
|
|
2947
|
+
timeoutMs: null,
|
|
2948
|
+
maxBuffered: 1
|
|
2949
|
+
});
|
|
2950
|
+
expect(
|
|
2951
|
+
reassembler.addFragment({
|
|
2952
|
+
fragmentationIndicator: 3,
|
|
2953
|
+
mpuSequenceNumber: 1,
|
|
2954
|
+
fragmentCounter: 0,
|
|
2955
|
+
data: new Uint8Array([1]),
|
|
2956
|
+
timestamp: 1000n,
|
|
2957
|
+
rapFlag: false,
|
|
2958
|
+
packetSequenceNumber: testPacketSequence(1, 0)
|
|
2959
|
+
})
|
|
2960
|
+
).toBeNull();
|
|
2961
|
+
expect(reassembler.pendingCount).toBe(1);
|
|
2962
|
+
expect(
|
|
2963
|
+
reassembler.addFragment({
|
|
2964
|
+
fragmentationIndicator: 3,
|
|
2965
|
+
mpuSequenceNumber: 2,
|
|
2966
|
+
fragmentCounter: 0,
|
|
2967
|
+
data: new Uint8Array([2]),
|
|
2968
|
+
timestamp: 1000n,
|
|
2969
|
+
rapFlag: false,
|
|
2970
|
+
packetSequenceNumber: testPacketSequence(2, 0)
|
|
2971
|
+
})
|
|
2972
|
+
).toBeNull();
|
|
2973
|
+
expect(reassembler.pendingCount).toBe(1);
|
|
2974
|
+
expect(reassembler.getStatus().pendingFragments).toBe(1);
|
|
2975
|
+
expect(reassembler.stats.evictedMemoryLimit).toBe(1);
|
|
2976
|
+
const completed = reassembler.addFragment({
|
|
2675
2977
|
fragmentationIndicator: 1,
|
|
2676
2978
|
mpuSequenceNumber: 2,
|
|
2677
2979
|
fragmentCounter: 1,
|
|
2678
2980
|
itemId: 2,
|
|
2679
|
-
data: new Uint8Array([
|
|
2981
|
+
data: new Uint8Array([4]),
|
|
2680
2982
|
timestamp: 1000n,
|
|
2681
2983
|
rapFlag: true,
|
|
2682
2984
|
packetSequenceNumber: testPacketSequence(2, 1)
|
|
2683
|
-
})
|
|
2684
|
-
expect(
|
|
2685
|
-
expect(reassembler.
|
|
2985
|
+
});
|
|
2986
|
+
expect(completed?.data).toEqual(new Uint8Array([4, 2]));
|
|
2987
|
+
expect(reassembler.stats.evictedMemoryLimit).toBe(1);
|
|
2988
|
+
expect(reassembler.pendingCount).toBe(0);
|
|
2686
2989
|
const result = reassembler.addFragment({
|
|
2687
2990
|
fragmentationIndicator: 1,
|
|
2688
2991
|
mpuSequenceNumber: 1,
|
|
@@ -2693,21 +2996,27 @@ if (void 0) {
|
|
|
2693
2996
|
rapFlag: true,
|
|
2694
2997
|
packetSequenceNumber: testPacketSequence(1, 1)
|
|
2695
2998
|
});
|
|
2696
|
-
expect(result
|
|
2999
|
+
expect(result).toBeNull();
|
|
3000
|
+
expect(reassembler.pendingCount).toBe(1);
|
|
2697
3001
|
});
|
|
2698
3002
|
test("expires a continuation that never receives its first fragment", () => {
|
|
2699
3003
|
vi.useFakeTimers();
|
|
2700
3004
|
try {
|
|
2701
|
-
const reassembler = new MFUReassembler({
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
3005
|
+
const reassembler = new MFUReassembler({
|
|
3006
|
+
timeoutMs: 50,
|
|
3007
|
+
maxBuffered: 1
|
|
3008
|
+
});
|
|
3009
|
+
expect(
|
|
3010
|
+
reassembler.addFragment({
|
|
3011
|
+
fragmentationIndicator: 3,
|
|
3012
|
+
mpuSequenceNumber: 1,
|
|
3013
|
+
fragmentCounter: 0,
|
|
3014
|
+
data: new Uint8Array([1]),
|
|
3015
|
+
timestamp: 1000n,
|
|
3016
|
+
rapFlag: false,
|
|
3017
|
+
packetSequenceNumber: testPacketSequence(1, 0)
|
|
3018
|
+
})
|
|
3019
|
+
).toBeNull();
|
|
2711
3020
|
vi.advanceTimersByTime(50);
|
|
2712
3021
|
expect(reassembler.pendingCount).toBe(0);
|
|
2713
3022
|
expect(reassembler.stats.timedOutMFUs).toBe(1);
|
|
@@ -2719,28 +3028,32 @@ if (void 0) {
|
|
|
2719
3028
|
vi.useFakeTimers();
|
|
2720
3029
|
try {
|
|
2721
3030
|
const enabled = new MFUReassembler({ timeoutMs: null, maxBuffered: 1 });
|
|
2722
|
-
expect(
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
3031
|
+
expect(
|
|
3032
|
+
enabled.addFragment({
|
|
3033
|
+
fragmentationIndicator: 3,
|
|
3034
|
+
mpuSequenceNumber: 1,
|
|
3035
|
+
fragmentCounter: 0,
|
|
3036
|
+
data: new Uint8Array([1]),
|
|
3037
|
+
timestamp: 1000n,
|
|
3038
|
+
rapFlag: false,
|
|
3039
|
+
packetSequenceNumber: testPacketSequence(1, 0)
|
|
3040
|
+
})
|
|
3041
|
+
).toBeNull();
|
|
2731
3042
|
enabled.timeoutMs = 10;
|
|
2732
3043
|
vi.advanceTimersByTime(10);
|
|
2733
3044
|
expect(enabled.pendingCount).toBe(0);
|
|
2734
3045
|
const disabled = new MFUReassembler({ timeoutMs: 10, maxBuffered: 1 });
|
|
2735
|
-
expect(
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
3046
|
+
expect(
|
|
3047
|
+
disabled.addFragment({
|
|
3048
|
+
fragmentationIndicator: 3,
|
|
3049
|
+
mpuSequenceNumber: 2,
|
|
3050
|
+
fragmentCounter: 0,
|
|
3051
|
+
data: new Uint8Array([2]),
|
|
3052
|
+
timestamp: 1000n,
|
|
3053
|
+
rapFlag: false,
|
|
3054
|
+
packetSequenceNumber: testPacketSequence(2, 0)
|
|
3055
|
+
})
|
|
3056
|
+
).toBeNull();
|
|
2744
3057
|
disabled.timeoutMs = null;
|
|
2745
3058
|
vi.advanceTimersByTime(10);
|
|
2746
3059
|
expect(disabled.pendingCount).toBe(1);
|
|
@@ -2749,8 +3062,11 @@ if (void 0) {
|
|
|
2749
3062
|
vi.useRealTimers();
|
|
2750
3063
|
}
|
|
2751
3064
|
});
|
|
2752
|
-
test("bounds active data units
|
|
2753
|
-
const reassembler = new MFUReassembler({
|
|
3065
|
+
test("bounds active data units by evicting the oldest at maxBuffered", () => {
|
|
3066
|
+
const reassembler = new MFUReassembler({
|
|
3067
|
+
timeoutMs: 1e3,
|
|
3068
|
+
maxBuffered: 2
|
|
3069
|
+
});
|
|
2754
3070
|
for (let i = 0; i < 2; i++) {
|
|
2755
3071
|
reassembler.addFragment({
|
|
2756
3072
|
fragmentationIndicator: 1,
|
|
@@ -2763,19 +3079,35 @@ if (void 0) {
|
|
|
2763
3079
|
packetSequenceNumber: testPacketSequence(i, 1)
|
|
2764
3080
|
});
|
|
2765
3081
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
3082
|
+
expect(
|
|
3083
|
+
reassembler.addFragment({
|
|
3084
|
+
fragmentationIndicator: 1,
|
|
3085
|
+
mpuSequenceNumber: 2,
|
|
3086
|
+
fragmentCounter: 1,
|
|
3087
|
+
itemId: 2,
|
|
3088
|
+
data: new Uint8Array([2]),
|
|
3089
|
+
timestamp: 200n,
|
|
3090
|
+
rapFlag: false,
|
|
3091
|
+
packetSequenceNumber: testPacketSequence(2, 1)
|
|
3092
|
+
})
|
|
3093
|
+
).toBeNull();
|
|
3094
|
+
expect(reassembler.pendingCount).toBe(2);
|
|
3095
|
+
expect(reassembler.stats.timedOutMFUs).toBe(0);
|
|
3096
|
+
expect(reassembler.stats.evictedMemoryLimit).toBe(1);
|
|
3097
|
+
expect(
|
|
3098
|
+
reassembler.addFragment({
|
|
3099
|
+
fragmentationIndicator: 3,
|
|
3100
|
+
mpuSequenceNumber: 0,
|
|
3101
|
+
fragmentCounter: 0,
|
|
3102
|
+
data: new Uint8Array([9]),
|
|
3103
|
+
timestamp: 300n,
|
|
3104
|
+
rapFlag: false,
|
|
3105
|
+
packetSequenceNumber: testPacketSequence(0, 0)
|
|
3106
|
+
})
|
|
3107
|
+
).toBeNull();
|
|
2776
3108
|
expect(reassembler.pendingCount).toBe(2);
|
|
3109
|
+
expect(reassembler.stats.evictedMemoryLimit).toBe(2);
|
|
2777
3110
|
expect(reassembler.stats.timedOutMFUs).toBe(0);
|
|
2778
|
-
expect(reassembler.stats.evictedMemoryLimit).toBe(0);
|
|
2779
3111
|
});
|
|
2780
3112
|
test("should report stats correctly", () => {
|
|
2781
3113
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
@@ -2806,17 +3138,20 @@ if (void 0) {
|
|
|
2806
3138
|
source: "multicast",
|
|
2807
3139
|
packetSequenceNumber: testPacketSequence(1, 1)
|
|
2808
3140
|
});
|
|
2809
|
-
expectRejection(
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
3141
|
+
expectRejection(
|
|
3142
|
+
() => reassembler.addFragment({
|
|
3143
|
+
fragmentationIndicator: 1,
|
|
3144
|
+
mpuSequenceNumber: 1,
|
|
3145
|
+
fragmentCounter: 1,
|
|
3146
|
+
itemId: 2,
|
|
3147
|
+
data: new Uint8Array([187]),
|
|
3148
|
+
timestamp: 1000n,
|
|
3149
|
+
rapFlag: true,
|
|
3150
|
+
source: "moq",
|
|
3151
|
+
packetSequenceNumber: testPacketSequence(1, 1)
|
|
3152
|
+
}),
|
|
3153
|
+
"overlapping-data-unit"
|
|
3154
|
+
);
|
|
2820
3155
|
const result = reassembler.addFragment({
|
|
2821
3156
|
fragmentationIndicator: 3,
|
|
2822
3157
|
mpuSequenceNumber: 1,
|
|
@@ -2844,24 +3179,28 @@ if (void 0) {
|
|
|
2844
3179
|
rapFlag: true,
|
|
2845
3180
|
packetSequenceNumber: testPacketSequence(1, 3)
|
|
2846
3181
|
});
|
|
2847
|
-
expect(
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
3182
|
+
expect(
|
|
3183
|
+
reassembler.addFragment({
|
|
3184
|
+
fragmentationIndicator: 2,
|
|
3185
|
+
mpuSequenceNumber: 1,
|
|
3186
|
+
fragmentCounter: 1,
|
|
3187
|
+
data: new Uint8Array([204]),
|
|
3188
|
+
timestamp: 1000n,
|
|
3189
|
+
rapFlag: false,
|
|
3190
|
+
packetSequenceNumber: testPacketSequence(1, 1)
|
|
3191
|
+
})
|
|
3192
|
+
).toBeNull();
|
|
3193
|
+
expect(
|
|
3194
|
+
reassembler.addFragment({
|
|
3195
|
+
fragmentationIndicator: 3,
|
|
3196
|
+
mpuSequenceNumber: 1,
|
|
3197
|
+
fragmentCounter: 0,
|
|
3198
|
+
data: new Uint8Array([221]),
|
|
3199
|
+
timestamp: 1000n,
|
|
3200
|
+
rapFlag: false,
|
|
3201
|
+
packetSequenceNumber: testPacketSequence(1, 0)
|
|
3202
|
+
})
|
|
3203
|
+
).toBeNull();
|
|
2865
3204
|
expect(reassembler.pendingCount).toBe(1);
|
|
2866
3205
|
expect(reassembler.getStatus().pendingFragments).toBe(3);
|
|
2867
3206
|
});
|
|
@@ -2877,33 +3216,40 @@ if (void 0) {
|
|
|
2877
3216
|
rapFlag: true,
|
|
2878
3217
|
packetSequenceNumber: testPacketSequence(1, 3)
|
|
2879
3218
|
});
|
|
2880
|
-
expect(
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3219
|
+
expect(
|
|
3220
|
+
reassembler.addFragment({
|
|
3221
|
+
fragmentationIndicator: 2,
|
|
3222
|
+
mpuSequenceNumber: 1,
|
|
3223
|
+
fragmentCounter: 1,
|
|
3224
|
+
data: new Uint8Array([204]),
|
|
3225
|
+
timestamp: 1000n,
|
|
3226
|
+
rapFlag: false,
|
|
3227
|
+
packetSequenceNumber: testPacketSequence(1, 1)
|
|
3228
|
+
})
|
|
3229
|
+
).toBeNull();
|
|
3230
|
+
expect(
|
|
3231
|
+
reassembler.addFragment({
|
|
3232
|
+
fragmentationIndicator: 2,
|
|
3233
|
+
mpuSequenceNumber: 1,
|
|
3234
|
+
fragmentCounter: 2,
|
|
3235
|
+
data: new Uint8Array([187]),
|
|
3236
|
+
timestamp: 1000n,
|
|
3237
|
+
rapFlag: false,
|
|
3238
|
+
packetSequenceNumber: testPacketSequence(1, 2)
|
|
3239
|
+
})
|
|
3240
|
+
).toBeNull();
|
|
3241
|
+
expectRejection(
|
|
3242
|
+
() => reassembler.addFragment({
|
|
3243
|
+
fragmentationIndicator: 2,
|
|
3244
|
+
mpuSequenceNumber: 1,
|
|
3245
|
+
fragmentCounter: 1,
|
|
3246
|
+
data: new Uint8Array([204]),
|
|
3247
|
+
timestamp: 1001n,
|
|
3248
|
+
rapFlag: false,
|
|
3249
|
+
packetSequenceNumber: testPacketSequence(1, 1)
|
|
3250
|
+
}),
|
|
3251
|
+
"fragment-counter-mismatch"
|
|
3252
|
+
);
|
|
2907
3253
|
const result = reassembler.addFragment({
|
|
2908
3254
|
fragmentationIndicator: 3,
|
|
2909
3255
|
mpuSequenceNumber: 1,
|
|
@@ -2931,57 +3277,60 @@ if (void 0) {
|
|
|
2931
3277
|
});
|
|
2932
3278
|
expect(reassembler.addFragment(fragment(1, 3, 170))).toBeNull();
|
|
2933
3279
|
expect(reassembler.addFragment(fragment(2, 1, 204))).toBeNull();
|
|
2934
|
-
expectRejection(
|
|
2935
|
-
() => reassembler.addFragment(fragment(1, 1, 238)),
|
|
2936
|
-
"overlapping-data-unit"
|
|
2937
|
-
);
|
|
3280
|
+
expectRejection(() => reassembler.addFragment(fragment(1, 1, 238)), "overlapping-data-unit");
|
|
2938
3281
|
expect(reassembler.addFragment(fragment(2, 2, 187))).toBeNull();
|
|
2939
|
-
expect(reassembler.addFragment(fragment(3, 0, 221))?.data).toEqual(
|
|
2940
|
-
new Uint8Array([170, 187, 204, 221])
|
|
2941
|
-
);
|
|
3282
|
+
expect(reassembler.addFragment(fragment(3, 0, 221))?.data).toEqual(new Uint8Array([170, 187, 204, 221]));
|
|
2942
3283
|
});
|
|
2943
3284
|
test("rejects a duplicate emitted intrinsic identity", () => {
|
|
2944
3285
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
2945
|
-
expect(
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
3286
|
+
expect(
|
|
3287
|
+
reassembler.addFragment({
|
|
3288
|
+
fragmentationIndicator: 1,
|
|
3289
|
+
mpuSequenceNumber: 42,
|
|
3290
|
+
fragmentCounter: 1,
|
|
3291
|
+
itemId: 1,
|
|
3292
|
+
data: new Uint8Array([170]),
|
|
3293
|
+
timestamp: 1000n,
|
|
3294
|
+
rapFlag: true,
|
|
3295
|
+
packetSequenceNumber: testPacketSequence(42, 1)
|
|
3296
|
+
})
|
|
3297
|
+
).toBeNull();
|
|
3298
|
+
expect(
|
|
3299
|
+
reassembler.addFragment({
|
|
3300
|
+
fragmentationIndicator: 3,
|
|
3301
|
+
mpuSequenceNumber: 42,
|
|
3302
|
+
fragmentCounter: 0,
|
|
3303
|
+
data: new Uint8Array([171]),
|
|
3304
|
+
timestamp: 1001n,
|
|
3305
|
+
rapFlag: false,
|
|
3306
|
+
packetSequenceNumber: testPacketSequence(42, 0)
|
|
3307
|
+
})
|
|
3308
|
+
).not.toBeNull();
|
|
3309
|
+
expect(
|
|
3310
|
+
reassembler.addFragment({
|
|
3311
|
+
fragmentationIndicator: 3,
|
|
3312
|
+
mpuSequenceNumber: 42,
|
|
3313
|
+
fragmentCounter: 0,
|
|
3314
|
+
data: new Uint8Array([188]),
|
|
3315
|
+
timestamp: 1002n,
|
|
3316
|
+
rapFlag: false,
|
|
3317
|
+
packetSequenceNumber: testPacketSequence(42, 0, 10)
|
|
3318
|
+
})
|
|
3319
|
+
).toBeNull();
|
|
3320
|
+
expect(reassembler.pendingCount).toBe(1);
|
|
3321
|
+
expect(
|
|
3322
|
+
reassembler.addFragment({
|
|
3323
|
+
fragmentationIndicator: 1,
|
|
3324
|
+
mpuSequenceNumber: 42,
|
|
3325
|
+
fragmentCounter: 1,
|
|
3326
|
+
itemId: 1,
|
|
3327
|
+
data: new Uint8Array([187]),
|
|
3328
|
+
timestamp: 2000n,
|
|
3329
|
+
rapFlag: true,
|
|
3330
|
+
packetSequenceNumber: testPacketSequence(42, 1, 10)
|
|
3331
|
+
})
|
|
3332
|
+
).toBeNull();
|
|
2973
3333
|
expect(reassembler.pendingCount).toBe(1);
|
|
2974
|
-
expect(reassembler.addFragment({
|
|
2975
|
-
fragmentationIndicator: 1,
|
|
2976
|
-
mpuSequenceNumber: 42,
|
|
2977
|
-
fragmentCounter: 1,
|
|
2978
|
-
itemId: 1,
|
|
2979
|
-
data: new Uint8Array([187]),
|
|
2980
|
-
timestamp: 2000n,
|
|
2981
|
-
rapFlag: true,
|
|
2982
|
-
packetSequenceNumber: testPacketSequence(42, 1, 10)
|
|
2983
|
-
})).toBeNull();
|
|
2984
|
-
expect(reassembler.pendingCount).toBe(0);
|
|
2985
3334
|
const result = reassembler.addFragment({
|
|
2986
3335
|
fragmentationIndicator: 1,
|
|
2987
3336
|
mpuSequenceNumber: 43,
|
|
@@ -2993,52 +3342,60 @@ if (void 0) {
|
|
|
2993
3342
|
packetSequenceNumber: testPacketSequence(43, 1)
|
|
2994
3343
|
});
|
|
2995
3344
|
expect(result).toBeNull();
|
|
2996
|
-
expect(
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3345
|
+
expect(
|
|
3346
|
+
reassembler.addFragment({
|
|
3347
|
+
fragmentationIndicator: 3,
|
|
3348
|
+
mpuSequenceNumber: 43,
|
|
3349
|
+
fragmentCounter: 0,
|
|
3350
|
+
data: new Uint8Array([221]),
|
|
3351
|
+
timestamp: 2001n,
|
|
3352
|
+
rapFlag: false,
|
|
3353
|
+
packetSequenceNumber: testPacketSequence(43, 0)
|
|
3354
|
+
})?.data
|
|
3355
|
+
).toEqual(new Uint8Array([204, 221]));
|
|
3005
3356
|
});
|
|
3006
3357
|
test("does not create active state for an emitted identity duplicate", () => {
|
|
3007
3358
|
const reassembler = new MFUReassembler({ timeoutMs: 1e3 });
|
|
3008
|
-
expect(
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3359
|
+
expect(
|
|
3360
|
+
reassembler.addFragment({
|
|
3361
|
+
fragmentationIndicator: 1,
|
|
3362
|
+
mpuSequenceNumber: 50,
|
|
3363
|
+
fragmentCounter: 1,
|
|
3364
|
+
movieFragmentSequenceNumber: 10,
|
|
3365
|
+
sampleNumber: 18,
|
|
3366
|
+
sampleOffset: 0,
|
|
3367
|
+
data: new Uint8Array([161]),
|
|
3368
|
+
timestamp: 1000n,
|
|
3369
|
+
rapFlag: true,
|
|
3370
|
+
packetSequenceNumber: testPacketSequence(50, 1)
|
|
3371
|
+
})
|
|
3372
|
+
).toBeNull();
|
|
3373
|
+
expect(
|
|
3374
|
+
reassembler.addFragment({
|
|
3375
|
+
fragmentationIndicator: 3,
|
|
3376
|
+
mpuSequenceNumber: 50,
|
|
3377
|
+
fragmentCounter: 0,
|
|
3378
|
+
data: new Uint8Array([178]),
|
|
3379
|
+
timestamp: 1000n,
|
|
3380
|
+
rapFlag: false,
|
|
3381
|
+
packetSequenceNumber: testPacketSequence(50, 0)
|
|
3382
|
+
})?.data
|
|
3383
|
+
).toEqual(new Uint8Array([161, 178]));
|
|
3029
3384
|
expect(reassembler.pendingCount).toBe(0);
|
|
3030
|
-
expect(
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3385
|
+
expect(
|
|
3386
|
+
reassembler.addFragment({
|
|
3387
|
+
fragmentationIndicator: 1,
|
|
3388
|
+
mpuSequenceNumber: 50,
|
|
3389
|
+
fragmentCounter: 1,
|
|
3390
|
+
movieFragmentSequenceNumber: 10,
|
|
3391
|
+
sampleNumber: 18,
|
|
3392
|
+
sampleOffset: 0,
|
|
3393
|
+
data: new Uint8Array([161]),
|
|
3394
|
+
timestamp: 2000n,
|
|
3395
|
+
rapFlag: true,
|
|
3396
|
+
packetSequenceNumber: testPacketSequence(50, 1, 10)
|
|
3397
|
+
})
|
|
3398
|
+
).toBeNull();
|
|
3042
3399
|
expect(reassembler.pendingCount).toBe(0);
|
|
3043
3400
|
});
|
|
3044
3401
|
test("flush should clear emitted set and allow re-assembly", () => {
|
|
@@ -3064,16 +3421,18 @@ if (void 0) {
|
|
|
3064
3421
|
packetSequenceNumber: testPacketSequence(42, 0)
|
|
3065
3422
|
});
|
|
3066
3423
|
expect(assembled).not.toBeNull();
|
|
3067
|
-
expect(
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3424
|
+
expect(
|
|
3425
|
+
reassembler.addFragment({
|
|
3426
|
+
fragmentationIndicator: 1,
|
|
3427
|
+
mpuSequenceNumber: 42,
|
|
3428
|
+
fragmentCounter: 1,
|
|
3429
|
+
itemId: 1,
|
|
3430
|
+
data: new Uint8Array([204]),
|
|
3431
|
+
timestamp: 1000n,
|
|
3432
|
+
rapFlag: true,
|
|
3433
|
+
packetSequenceNumber: testPacketSequence(42, 1, 10)
|
|
3434
|
+
})
|
|
3435
|
+
).toBeNull();
|
|
3077
3436
|
reassembler.flush();
|
|
3078
3437
|
const r2 = reassembler.addFragment({
|
|
3079
3438
|
fragmentationIndicator: 1,
|
|
@@ -3278,6 +3637,15 @@ function trackNameOfKey(key) {
|
|
|
3278
3637
|
const name = JSON.parse(key)[0];
|
|
3279
3638
|
return name ?? void 0;
|
|
3280
3639
|
}
|
|
3640
|
+
function identityOfKey(key) {
|
|
3641
|
+
const [name, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber] = JSON.parse(key);
|
|
3642
|
+
return {
|
|
3643
|
+
catalogTrackName: name ?? void 0,
|
|
3644
|
+
mpuSequenceNumber,
|
|
3645
|
+
movieFragmentSequenceNumber,
|
|
3646
|
+
sampleNumber
|
|
3647
|
+
};
|
|
3648
|
+
}
|
|
3281
3649
|
var SampleCombineError = class extends Error {
|
|
3282
3650
|
constructor(code, message) {
|
|
3283
3651
|
super(message);
|
|
@@ -3285,13 +3653,14 @@ var SampleCombineError = class extends Error {
|
|
|
3285
3653
|
this.name = "SampleCombineError";
|
|
3286
3654
|
}
|
|
3287
3655
|
};
|
|
3288
|
-
var _maxBuffered2, _requireAuthoredSize, _maxSegmentsPerSample, _authoredSizes, _pending, _retired, _stats, _SampleCombiner_instances, tryComplete_fn, retire_fn, evictOldestPending_fn;
|
|
3656
|
+
var _maxBuffered2, _requireAuthoredSize, _maxSegmentsPerSample, _onAbandon, _authoredSizes, _pending, _retired, _stats, _SampleCombiner_instances, abandon_fn, tryComplete_fn, retire_fn, evictOldestPending_fn;
|
|
3289
3657
|
var SampleCombiner = class {
|
|
3290
3658
|
constructor(options) {
|
|
3291
3659
|
__privateAdd(this, _SampleCombiner_instances);
|
|
3292
3660
|
__privateAdd(this, _maxBuffered2);
|
|
3293
3661
|
__privateAdd(this, _requireAuthoredSize);
|
|
3294
3662
|
__privateAdd(this, _maxSegmentsPerSample);
|
|
3663
|
+
__privateAdd(this, _onAbandon);
|
|
3295
3664
|
__privateAdd(this, _authoredSizes, /* @__PURE__ */ new Map());
|
|
3296
3665
|
/** Map iteration is insertion-ordered, so the first key is the oldest. */
|
|
3297
3666
|
__privateAdd(this, _pending, /* @__PURE__ */ new Map());
|
|
@@ -3307,7 +3676,7 @@ var SampleCombiner = class {
|
|
|
3307
3676
|
timedOutSamples: 0,
|
|
3308
3677
|
evictedSamples: 0
|
|
3309
3678
|
});
|
|
3310
|
-
const { maxBuffered, requireAuthoredSize, maxSegmentsPerSample } = options;
|
|
3679
|
+
const { maxBuffered, requireAuthoredSize, maxSegmentsPerSample, onAbandon } = options;
|
|
3311
3680
|
if (!Number.isInteger(maxBuffered) || maxBuffered <= 0) {
|
|
3312
3681
|
throw new Error("[SampleCombiner] maxBuffered must be a positive integer");
|
|
3313
3682
|
}
|
|
@@ -3317,6 +3686,7 @@ var SampleCombiner = class {
|
|
|
3317
3686
|
__privateSet(this, _maxBuffered2, maxBuffered);
|
|
3318
3687
|
__privateSet(this, _requireAuthoredSize, requireAuthoredSize);
|
|
3319
3688
|
__privateSet(this, _maxSegmentsPerSample, maxSegmentsPerSample);
|
|
3689
|
+
__privateSet(this, _onAbandon, onAbandon);
|
|
3320
3690
|
}
|
|
3321
3691
|
/**
|
|
3322
3692
|
* Register the authored sample size from the host's own FT=1 parse. Per
|
|
@@ -3431,6 +3801,7 @@ var SampleCombiner = class {
|
|
|
3431
3801
|
__privateGet(this, _pending).delete(key);
|
|
3432
3802
|
__privateGet(this, _stats).timedOutSamples++;
|
|
3433
3803
|
__privateMethod(this, _SampleCombiner_instances, retire_fn).call(this, key);
|
|
3804
|
+
__privateMethod(this, _SampleCombiner_instances, abandon_fn).call(this, key);
|
|
3434
3805
|
}
|
|
3435
3806
|
}
|
|
3436
3807
|
}
|
|
@@ -3477,11 +3848,17 @@ var SampleCombiner = class {
|
|
|
3477
3848
|
_maxBuffered2 = new WeakMap();
|
|
3478
3849
|
_requireAuthoredSize = new WeakMap();
|
|
3479
3850
|
_maxSegmentsPerSample = new WeakMap();
|
|
3851
|
+
_onAbandon = new WeakMap();
|
|
3480
3852
|
_authoredSizes = new WeakMap();
|
|
3481
3853
|
_pending = new WeakMap();
|
|
3482
3854
|
_retired = new WeakMap();
|
|
3483
3855
|
_stats = new WeakMap();
|
|
3484
3856
|
_SampleCombiner_instances = new WeakSet();
|
|
3857
|
+
/** Report one abandoned sample to the owner of any state keyed on it. */
|
|
3858
|
+
abandon_fn = function(key) {
|
|
3859
|
+
var _a;
|
|
3860
|
+
(_a = __privateGet(this, _onAbandon)) == null ? void 0 : _a.call(this, identityOfKey(key));
|
|
3861
|
+
};
|
|
3485
3862
|
/**
|
|
3486
3863
|
* Emit the sample when its held data units are byte-adjacent from zero and
|
|
3487
3864
|
* total `size`. Completion is a protocol event, never a timer.
|
|
@@ -3536,6 +3913,7 @@ evictOldestPending_fn = function() {
|
|
|
3536
3913
|
__privateGet(this, _pending).delete(oldest);
|
|
3537
3914
|
__privateGet(this, _stats).evictedSamples++;
|
|
3538
3915
|
__privateMethod(this, _SampleCombiner_instances, retire_fn).call(this, oldest);
|
|
3916
|
+
__privateMethod(this, _SampleCombiner_instances, abandon_fn).call(this, oldest);
|
|
3539
3917
|
};
|
|
3540
3918
|
function fromPassthrough(mfu) {
|
|
3541
3919
|
return {
|
|
@@ -3543,6 +3921,7 @@ function fromPassthrough(mfu) {
|
|
|
3543
3921
|
movieFragmentSequenceNumber: mfu.movieFragmentSequenceNumber ?? 0,
|
|
3544
3922
|
sampleNumber: mfu.sampleNumber ?? 0,
|
|
3545
3923
|
data: mfu.data,
|
|
3924
|
+
mmtpTimestamp: mfu.timestamp,
|
|
3546
3925
|
timestamp: mfu.timestamp,
|
|
3547
3926
|
rapFlag: mfu.rapFlag,
|
|
3548
3927
|
priority: mfu.priority,
|
|
@@ -3584,6 +3963,7 @@ function fromSegments(segments, sortedOffsets) {
|
|
|
3584
3963
|
movieFragmentSequenceNumber: first.movieFragmentSequenceNumber,
|
|
3585
3964
|
sampleNumber: first.sampleNumber,
|
|
3586
3965
|
data,
|
|
3966
|
+
mmtpTimestamp: first.timestamp,
|
|
3587
3967
|
timestamp: first.timestamp,
|
|
3588
3968
|
rapFlag: first.rapFlag,
|
|
3589
3969
|
priority: first.priority,
|
|
@@ -3690,6 +4070,30 @@ if (void 0) {
|
|
|
3690
4070
|
expect(c.addMfu(mfu(1, 9, 17, 2, [3, 4], 1001n, false))).toBeNull();
|
|
3691
4071
|
expect(() => c.addMfu(mfu(1, 9, 17, 4, [5, 6], 1002n, false))).toThrow(SampleCombineError);
|
|
3692
4072
|
});
|
|
4073
|
+
test("reports every abandoned sample so its per-sample state can be freed", () => {
|
|
4074
|
+
const abandoned = [];
|
|
4075
|
+
const c = combiner({ maxBuffered: 2, onAbandon: (s) => abandoned.push(s) });
|
|
4076
|
+
c.registerAuthoredSize(void 0, 1, 9, 17, 10);
|
|
4077
|
+
c.registerAuthoredSize(void 0, 1, 9, 18, 10);
|
|
4078
|
+
c.registerAuthoredSize(void 0, 1, 9, 19, 10);
|
|
4079
|
+
expect(c.addMfu(mfu(1, 9, 17, 0, [1, 1, 1, 1, 1], 1000n, true))).toBeNull();
|
|
4080
|
+
expect(c.addMfu(mfu(1, 9, 18, 0, [2, 2, 2, 2, 2], 1001n, true))).toBeNull();
|
|
4081
|
+
expect(abandoned).toEqual([]);
|
|
4082
|
+
expect(c.addMfu(mfu(1, 9, 19, 0, [3, 3, 3, 3, 3], 1002n, true))).toBeNull();
|
|
4083
|
+
expect(abandoned).toEqual([{
|
|
4084
|
+
catalogTrackName: void 0,
|
|
4085
|
+
mpuSequenceNumber: 1,
|
|
4086
|
+
movieFragmentSequenceNumber: 9,
|
|
4087
|
+
sampleNumber: 17
|
|
4088
|
+
}]);
|
|
4089
|
+
c.cleanupTimeouts(-1);
|
|
4090
|
+
expect(abandoned.map((s) => s.sampleNumber)).toEqual([17, 18, 19]);
|
|
4091
|
+
const d = combiner({ maxBuffered: 2, onAbandon: (s) => abandoned.push(s) });
|
|
4092
|
+
d.registerAuthoredSize(void 0, 1, 9, 20, 10);
|
|
4093
|
+
expect(d.addMfu(mfu(1, 9, 20, 0, [4, 4, 4, 4, 4], 1003n, true))).toBeNull();
|
|
4094
|
+
expect(d.addMfu(mfu(1, 9, 20, 5, [5, 5, 5, 5, 5], 1004n, false))).not.toBeNull();
|
|
4095
|
+
expect(abandoned.map((s) => s.sampleNumber)).toEqual([17, 18, 19]);
|
|
4096
|
+
});
|
|
3693
4097
|
test("a full combiner evicts the oldest pending sample instead of throwing", () => {
|
|
3694
4098
|
const c = combiner({ maxBuffered: 2 });
|
|
3695
4099
|
c.registerAuthoredSize(void 0, 1, 9, 17, 10);
|
|
@@ -4270,7 +4674,10 @@ function detectH264Keyframe(data, options = {}) {
|
|
|
4270
4674
|
return classifyH264Keyframe(data, options) === true;
|
|
4271
4675
|
}
|
|
4272
4676
|
function mmtpTimestampToMicros(rawTs) {
|
|
4273
|
-
const ts = Number(rawTs)
|
|
4677
|
+
const ts = typeof rawTs === "bigint" ? rawTs >= 0n && rawTs <= 0xffffffffn ? Number(rawTs) : void 0 : Number.isSafeInteger(rawTs) && rawTs >= 0 && rawTs <= 4294967295 ? rawTs : void 0;
|
|
4678
|
+
if (ts === void 0) {
|
|
4679
|
+
throw new RangeError(`MMTP timestamp must be an unsigned 32-bit integer, got ${String(rawTs)}`);
|
|
4680
|
+
}
|
|
4274
4681
|
if (ts === 0) return 0;
|
|
4275
4682
|
const seconds = ts >>> 16;
|
|
4276
4683
|
const frac = ts & 65535;
|
|
@@ -4295,6 +4702,7 @@ function isFmp4InitSegment(data) {
|
|
|
4295
4702
|
size = data.byteLength - offset;
|
|
4296
4703
|
}
|
|
4297
4704
|
if (size < headerSize || offset + size > data.byteLength) return false;
|
|
4705
|
+
if (type === "mmpu") return false;
|
|
4298
4706
|
if (type === "ftyp") sawFtyp = true;
|
|
4299
4707
|
if (type === "moov") {
|
|
4300
4708
|
if (!sawFtyp) return false;
|
|
@@ -4304,6 +4712,44 @@ function isFmp4InitSegment(data) {
|
|
|
4304
4712
|
}
|
|
4305
4713
|
return offset === data.byteLength && sawFtyp && sawMoov;
|
|
4306
4714
|
}
|
|
4715
|
+
function normalizeMmtpInitSegment(data) {
|
|
4716
|
+
if (isFmp4InitSegment(data)) return data;
|
|
4717
|
+
const parts = [];
|
|
4718
|
+
let offset = 0;
|
|
4719
|
+
let removedMpu = false;
|
|
4720
|
+
while (offset + 8 <= data.byteLength) {
|
|
4721
|
+
const view2 = new DataView(data.buffer, data.byteOffset + offset, data.byteLength - offset);
|
|
4722
|
+
let size = view2.getUint32(0, false);
|
|
4723
|
+
let headerSize = 8;
|
|
4724
|
+
if (size === 1) {
|
|
4725
|
+
if (offset + 16 > data.byteLength) return void 0;
|
|
4726
|
+
const largeSize = view2.getBigUint64(8, false);
|
|
4727
|
+
if (largeSize > BigInt(Number.MAX_SAFE_INTEGER)) return void 0;
|
|
4728
|
+
size = Number(largeSize);
|
|
4729
|
+
headerSize = 16;
|
|
4730
|
+
} else if (size === 0) {
|
|
4731
|
+
size = data.byteLength - offset;
|
|
4732
|
+
}
|
|
4733
|
+
if (size < headerSize || offset + size > data.byteLength) return void 0;
|
|
4734
|
+
const type = String.fromCharCode(
|
|
4735
|
+
data[offset + 4],
|
|
4736
|
+
data[offset + 5],
|
|
4737
|
+
data[offset + 6],
|
|
4738
|
+
data[offset + 7]
|
|
4739
|
+
);
|
|
4740
|
+
if (type === "mmpu") removedMpu = true;
|
|
4741
|
+
else parts.push(data.subarray(offset, offset + size));
|
|
4742
|
+
offset += size;
|
|
4743
|
+
}
|
|
4744
|
+
if (!removedMpu || offset !== data.byteLength) return void 0;
|
|
4745
|
+
const normalized = new Uint8Array(parts.reduce((total, part) => total + part.byteLength, 0));
|
|
4746
|
+
let writeOffset = 0;
|
|
4747
|
+
for (const part of parts) {
|
|
4748
|
+
normalized.set(part, writeOffset);
|
|
4749
|
+
writeOffset += part.byteLength;
|
|
4750
|
+
}
|
|
4751
|
+
return isFmp4InitSegment(normalized) ? normalized : void 0;
|
|
4752
|
+
}
|
|
4307
4753
|
function hasIsoBmffMagic(data) {
|
|
4308
4754
|
for (let i = 0; i + 8 <= data.length && i < 32; i += 4) {
|
|
4309
4755
|
const tag = String.fromCharCode(data[i + 4], data[i + 5], data[i + 6], data[i + 7]);
|
|
@@ -7283,13 +7729,13 @@ var CmafBlockAssembler = class {
|
|
|
7283
7729
|
* is guarded by the per-frame `lastEmittedTs` check (also enforced in
|
|
7284
7730
|
* `#emitPerSample`). The `sbn`/`esi` parameters are kept for the call-site
|
|
7285
7731
|
* signature but no longer affect routing. */
|
|
7286
|
-
addFrame(sbn, esi, data, timestamp, keyframe, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance) {
|
|
7732
|
+
addFrame(sbn, esi, data, timestamp, keyframe, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance, mmtpTimestamp, movieFragmentSequenceNumber, sampleNumber) {
|
|
7287
7733
|
void sbn;
|
|
7288
7734
|
void esi;
|
|
7289
7735
|
__privateWrapper(this, _addFrameCount)._++;
|
|
7290
7736
|
__privateMethod(this, _CmafBlockAssembler_instances, startAggregateTimer_fn).call(this);
|
|
7291
7737
|
if (!__privateMethod(this, _CmafBlockAssembler_instances, ordersSamples_fn).call(this)) __privateMethod(this, _CmafBlockAssembler_instances, drainRecoveredUpTo_fn).call(this, timestamp);
|
|
7292
|
-
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, data, timestamp, __privateGet(this, _frameDuration2), keyframe, true, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance);
|
|
7738
|
+
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, data, timestamp, __privateGet(this, _frameDuration2), keyframe, true, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance, mmtpTimestamp, movieFragmentSequenceNumber, sampleNumber);
|
|
7293
7739
|
__privateMethod(this, _CmafBlockAssembler_instances, dumpEmit_fn).call(this, "addFrame-direct", [{ data, timestamp, keyframe }]);
|
|
7294
7740
|
}
|
|
7295
7741
|
/** Mark a block as complete (all K source symbols received or FEC recovery succeeded).
|
|
@@ -7334,9 +7780,9 @@ var CmafBlockAssembler = class {
|
|
|
7334
7780
|
}
|
|
7335
7781
|
}
|
|
7336
7782
|
/** Emit a single frame immediately as a one-sample segment (for oversized packets bypassing FEC). */
|
|
7337
|
-
addBypassFrame(data, timestamp, keyframe, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance) {
|
|
7783
|
+
addBypassFrame(data, timestamp, keyframe, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance, mmtpTimestamp, movieFragmentSequenceNumber, sampleNumber) {
|
|
7338
7784
|
if (!__privateMethod(this, _CmafBlockAssembler_instances, ordersSamples_fn).call(this)) __privateMethod(this, _CmafBlockAssembler_instances, drainRecoveredUpTo_fn).call(this, timestamp);
|
|
7339
|
-
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, data, timestamp, __privateGet(this, _frameDuration2), keyframe, true, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance);
|
|
7785
|
+
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, data, timestamp, __privateGet(this, _frameDuration2), keyframe, true, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance, mmtpTimestamp, movieFragmentSequenceNumber, sampleNumber);
|
|
7340
7786
|
}
|
|
7341
7787
|
/** Build and emit init segment from catalog config. */
|
|
7342
7788
|
emitInit() {
|
|
@@ -7486,7 +7932,7 @@ unwrapTimestamp_fn = function(timestamp) {
|
|
|
7486
7932
|
if (reference < 0) return timestamp;
|
|
7487
7933
|
return timestamp + Math.round((reference - timestamp) / wrapPeriod) * wrapPeriod;
|
|
7488
7934
|
};
|
|
7489
|
-
emitSample_fn = function(data, timestamp, duration, keyframe, advancesHorizon, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance) {
|
|
7935
|
+
emitSample_fn = function(data, timestamp, duration, keyframe, advancesHorizon, catalogTrackName, mpuSequenceNumber, packetSequenceNumber, packetSequenceNumbers, repairedPacketSequenceNumbers, authoredTiming, deliveryProvenance, mmtpTimestamp, movieFragmentSequenceNumber, sampleNumber) {
|
|
7490
7936
|
const scaledTiming = authoredTiming === void 0 ? null : scaleAuthoredTiming(authoredTiming, __privateGet(this, _config).timescale);
|
|
7491
7937
|
if (authoredTiming !== void 0 && scaledTiming === null) return;
|
|
7492
7938
|
const authoredTimestamp = scaledTiming?.timestamp ?? timestamp;
|
|
@@ -7513,6 +7959,7 @@ emitSample_fn = function(data, timestamp, duration, keyframe, advancesHorizon, c
|
|
|
7513
7959
|
timestamp: unwrappedTimestamp,
|
|
7514
7960
|
duration: authoredDuration,
|
|
7515
7961
|
keyframe,
|
|
7962
|
+
...mmtpTimestamp === void 0 ? {} : { mmtpTimestamp },
|
|
7516
7963
|
...compositionTimeOffset === void 0 ? {} : { compositionTimeOffset },
|
|
7517
7964
|
advancesHorizon,
|
|
7518
7965
|
...authoredTiming === void 0 ? {} : { authoredTiming },
|
|
@@ -7520,6 +7967,8 @@ emitSample_fn = function(data, timestamp, duration, keyframe, advancesHorizon, c
|
|
|
7520
7967
|
...packetSequenceNumbers === void 0 ? {} : { packetSequenceNumbers },
|
|
7521
7968
|
...repairedPacketSequenceNumbers === void 0 ? {} : { repairedPacketSequenceNumbers },
|
|
7522
7969
|
...mpuSequenceNumber === void 0 ? {} : { mpuSequenceNumber },
|
|
7970
|
+
...movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber },
|
|
7971
|
+
...sampleNumber === void 0 ? {} : { sampleNumber },
|
|
7523
7972
|
...catalogTrackName === void 0 ? {} : { catalogTrackName },
|
|
7524
7973
|
...deliveryProvenance === void 0 ? {} : { deliveryProvenance }
|
|
7525
7974
|
});
|
|
@@ -7677,7 +8126,7 @@ emitPerSample_fn = function(frames, audioDurationOverride, advancesHorizon = fal
|
|
|
7677
8126
|
duration = audioDurationOverride ?? __privateGet(this, _frameDuration2);
|
|
7678
8127
|
}
|
|
7679
8128
|
if (duration <= 0) duration = audioDurationOverride ?? __privateGet(this, _frameDuration2);
|
|
7680
|
-
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, f.data, f.timestamp, duration, f.keyframe, advancesHorizon, f.catalogTrackName, f.mpuSequenceNumber, f.packetSequenceNumber, f.packetSequenceNumbers, f.repairedPacketSequenceNumbers, f.authoredTiming, f.deliveryProvenance);
|
|
8129
|
+
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, f.data, f.timestamp, duration, f.keyframe, advancesHorizon, f.catalogTrackName, f.mpuSequenceNumber, f.packetSequenceNumber, f.packetSequenceNumbers, f.repairedPacketSequenceNumbers, f.authoredTiming, f.deliveryProvenance, f.mmtpTimestamp, f.movieFragmentSequenceNumber, f.sampleNumber);
|
|
7681
8130
|
}
|
|
7682
8131
|
};
|
|
7683
8132
|
_dumpCount = new WeakMap();
|
|
@@ -7692,8 +8141,9 @@ _aggregateTimer = new WeakMap();
|
|
|
7692
8141
|
startAggregateTimer_fn = function() {
|
|
7693
8142
|
if (__privateGet(this, _aggregateTimer)) return;
|
|
7694
8143
|
__privateSet(this, _aggregateTimer, setInterval(() => {
|
|
8144
|
+
const order = __privateGet(this, _sampleOrderStats);
|
|
7695
8145
|
console.log(
|
|
7696
|
-
`[L4-AGG-${__privateGet(this, _config).trackType}] emits=${__privateGet(this, _emitCount)} samples=${__privateGet(this, _sampleCount)} kf=${__privateGet(this, _keyframeCount)} addFrame=${__privateGet(this, _addFrameCount)} recovered=${__privateGet(this, _recoveredBlockCount)} complete=${__privateGet(this, _completeBlockCount)} fail=${__privateGet(this, _failBlockCount)} pendingBlocks=${__privateGet(this, _blocks).size} pendingFlushes=${__privateGet(this, _pendingFlush).size} armedVideo=${__privateGet(this, _armedVideo)}`
|
|
8146
|
+
`[L4-AGG-${__privateGet(this, _config).trackType}] emits=${__privateGet(this, _emitCount)} samples=${__privateGet(this, _sampleCount)} kf=${__privateGet(this, _keyframeCount)} addFrame=${__privateGet(this, _addFrameCount)} recovered=${__privateGet(this, _recoveredBlockCount)} complete=${__privateGet(this, _completeBlockCount)} fail=${__privateGet(this, _failBlockCount)} pendingBlocks=${__privateGet(this, _blocks).size} pendingFlushes=${__privateGet(this, _pendingFlush).size} armedVideo=${__privateGet(this, _armedVideo)} forced=${order.forcedReleases} stale=${order.staleSamples} dup=${order.duplicateSamples} preRap=${order.preRapDeadlineDrops} noRap=${order.noRapFlushDrops}`
|
|
7697
8147
|
);
|
|
7698
8148
|
__privateSet(this, _emitCount, 0);
|
|
7699
8149
|
__privateSet(this, _sampleCount, 0);
|
|
@@ -7751,7 +8201,7 @@ drainRecoveredUpTo_fn = function(horizonTs) {
|
|
|
7751
8201
|
const ready = [...__privateGet(this, _pendingRecovered).values()].filter((frame) => frame.timestamp <= horizonTs).sort((a, b) => a.timestamp - b.timestamp);
|
|
7752
8202
|
for (const frame of ready) {
|
|
7753
8203
|
__privateGet(this, _pendingRecovered).delete(frame.timestamp);
|
|
7754
|
-
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, frame.data, frame.timestamp, __privateGet(this, _frameDuration2), frame.keyframe, false, frame.catalogTrackName, frame.mpuSequenceNumber, frame.packetSequenceNumber, frame.packetSequenceNumbers, frame.repairedPacketSequenceNumbers, frame.authoredTiming, frame.deliveryProvenance);
|
|
8204
|
+
__privateMethod(this, _CmafBlockAssembler_instances, emitSample_fn).call(this, frame.data, frame.timestamp, __privateGet(this, _frameDuration2), frame.keyframe, false, frame.catalogTrackName, frame.mpuSequenceNumber, frame.packetSequenceNumber, frame.packetSequenceNumbers, frame.repairedPacketSequenceNumbers, frame.authoredTiming, frame.deliveryProvenance, frame.mmtpTimestamp, frame.movieFragmentSequenceNumber, frame.sampleNumber);
|
|
7755
8205
|
__privateMethod(this, _CmafBlockAssembler_instances, dumpEmit_fn).call(this, "recovered-drain", [frame]);
|
|
7756
8206
|
}
|
|
7757
8207
|
};
|
|
@@ -8123,6 +8573,15 @@ function deriveFecInterleaveDepth(interleaveDepthMs, mediaUnitDurationMs) {
|
|
|
8123
8573
|
}
|
|
8124
8574
|
return Math.max(1, Math.floor(interleaveDepthMs / mediaUnitDurationMs + 0.5));
|
|
8125
8575
|
}
|
|
8576
|
+
function fecBlockBudgetMs(interleaveDepthMs, reorderToleranceMs) {
|
|
8577
|
+
if (!Number.isFinite(interleaveDepthMs) || !(interleaveDepthMs > 0)) {
|
|
8578
|
+
throw new Error(`[FEC] interleaveDepthMs must be a finite value > 0 (got ${interleaveDepthMs}ms). Fix encoder config.`);
|
|
8579
|
+
}
|
|
8580
|
+
if (!(reorderToleranceMs >= 0) || !Number.isFinite(reorderToleranceMs)) {
|
|
8581
|
+
throw new Error(`[FEC] reorderToleranceMs must be a finite value >= 0 (got ${reorderToleranceMs}ms). It is a catalog value (fec.reorderToleranceMs), not a local default.`);
|
|
8582
|
+
}
|
|
8583
|
+
return Math.ceil(interleaveDepthMs + reorderToleranceMs);
|
|
8584
|
+
}
|
|
8126
8585
|
function parseRepairPacket(packet) {
|
|
8127
8586
|
const parsed = parseMoqRepairFrame(packet);
|
|
8128
8587
|
if (!parsed)
|
|
@@ -8224,32 +8683,18 @@ function computeFecLossWindowMs(fecInterleaveMs, fecK, jitterBufferMs) {
|
|
|
8224
8683
|
function computeFecDeliveryWindowMs(config) {
|
|
8225
8684
|
const fecInterleaveMs = config.interleaveDepthMs;
|
|
8226
8685
|
const fecK = config.sourceSymbols;
|
|
8227
|
-
if (fecInterleaveMs <= 0) {
|
|
8686
|
+
if (!Number.isFinite(fecInterleaveMs) || fecInterleaveMs <= 0) {
|
|
8228
8687
|
throw new Error(
|
|
8229
8688
|
`[TrackSession] fecInterleaveMs=${fecInterleaveMs}. Derive a positive FEC interleave window from the stream catalog.`
|
|
8230
8689
|
);
|
|
8231
8690
|
}
|
|
8232
|
-
if (fecK <= 0) {
|
|
8691
|
+
if (!Number.isFinite(fecK) || fecK <= 0) {
|
|
8233
8692
|
throw new Error(
|
|
8234
8693
|
`[TrackSession] fecK=${fecK}. Derive a positive FEC source-symbol count from the stream catalog.`
|
|
8235
8694
|
);
|
|
8236
8695
|
}
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
const mediaUnitMs = 1e3 / config.mediaUnitsPerSecond;
|
|
8240
|
-
const interleaveUnits = deriveFecInterleaveDepth(fecInterleaveMs, mediaUnitMs);
|
|
8241
|
-
const deliverySymbols = fecK + Math.max(0, config.repairSymbols ?? 0);
|
|
8242
|
-
spansMs.push(deliverySymbols * interleaveUnits * mediaUnitMs);
|
|
8243
|
-
}
|
|
8244
|
-
if (config.symbolSize && config.symbolSize > 0 && config.bandwidthBps && config.bandwidthBps > 0) {
|
|
8245
|
-
const repairSymbols = Math.max(0, config.repairSymbols ?? 0);
|
|
8246
|
-
spansMs.push(
|
|
8247
|
-
(fecK + repairSymbols) * config.symbolSize * 8 * 1e3 / config.bandwidthBps
|
|
8248
|
-
);
|
|
8249
|
-
}
|
|
8250
|
-
const coreMs = Math.max(...spansMs);
|
|
8251
|
-
const marginMs = config.jitterBufferMs !== void 0 && config.jitterBufferMs >= 0 ? config.jitterBufferMs : fecInterleaveMs;
|
|
8252
|
-
return Math.ceil(coreMs) + Math.ceil(marginMs);
|
|
8696
|
+
if (config.jitterBufferMs === void 0) return Math.ceil(fecInterleaveMs);
|
|
8697
|
+
return fecBlockBudgetMs(fecInterleaveMs, config.jitterBufferMs);
|
|
8253
8698
|
}
|
|
8254
8699
|
function computeFecDeliveryWindowMediaUnits(config, timestampTimescale) {
|
|
8255
8700
|
if (!Number.isFinite(timestampTimescale) || timestampTimescale <= 0) {
|
|
@@ -8265,7 +8710,7 @@ function computeFecDeliveryWindowMediaUnits(config, timestampTimescale) {
|
|
|
8265
8710
|
}
|
|
8266
8711
|
return deliveryWindow;
|
|
8267
8712
|
}
|
|
8268
|
-
var _state, _subscription, _hysteresisTimer, _joinDeadlineTimer, _previousPrimary, _manager, _suppressFired, _TrackSession_instances, transition_fn, clearHysteresis_fn, armJoinDeadline_fn, clearJoinDeadline_fn, suppress_fn, unsuppress_fn;
|
|
8713
|
+
var _state, _subscription, _hysteresisTimer, _joinDeadlineTimer, _previousPrimary, _manager, _suppressFired, _fecArmed, _TrackSession_instances, transition_fn, clearHysteresis_fn, armJoinDeadline_fn, clearJoinDeadline_fn, armFec_fn, disarmFec_fn, suppress_fn, unsuppress_fn;
|
|
8269
8714
|
var TrackSession = class {
|
|
8270
8715
|
constructor(config, manager) {
|
|
8271
8716
|
__privateAdd(this, _TrackSession_instances);
|
|
@@ -8282,6 +8727,12 @@ var TrackSession = class {
|
|
|
8282
8727
|
// from `failing` (exactly where a starving track sits) was re-subscribed to
|
|
8283
8728
|
// unicast with the host's media still muted: MoQ playing into a dead sink.
|
|
8284
8729
|
__privateAdd(this, _suppressFired, false);
|
|
8730
|
+
// Single FEC authority flag: set by the fec-setup edges (joinStarted,
|
|
8731
|
+
// enterUnicastFallback), cleared by the fec-teardown edge in #transition.
|
|
8732
|
+
// Setup and teardown are paired by construction, so a takeover timeout that
|
|
8733
|
+
// parks the session in `unicast` keeps the decoder the still-joined
|
|
8734
|
+
// transport is feeding, and the later unsubscribe still tears it down.
|
|
8735
|
+
__privateAdd(this, _fecArmed, false);
|
|
8285
8736
|
this.config = config;
|
|
8286
8737
|
__privateSet(this, _manager, manager);
|
|
8287
8738
|
}
|
|
@@ -8356,7 +8807,7 @@ var TrackSession = class {
|
|
|
8356
8807
|
joinStarted() {
|
|
8357
8808
|
if (__privateGet(this, _state) !== "unicast") return;
|
|
8358
8809
|
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "joining");
|
|
8359
|
-
|
|
8810
|
+
__privateMethod(this, _TrackSession_instances, armFec_fn).call(this);
|
|
8360
8811
|
}
|
|
8361
8812
|
/**
|
|
8362
8813
|
* any non-fallback state → unicast-fallback. Used when the SSM bridge is
|
|
@@ -8377,7 +8828,7 @@ var TrackSession = class {
|
|
|
8377
8828
|
__privateMethod(this, _TrackSession_instances, clearHysteresis_fn).call(this);
|
|
8378
8829
|
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "unicast-fallback");
|
|
8379
8830
|
__privateMethod(this, _TrackSession_instances, suppress_fn).call(this);
|
|
8380
|
-
|
|
8831
|
+
__privateMethod(this, _TrackSession_instances, armFec_fn).call(this);
|
|
8381
8832
|
}
|
|
8382
8833
|
/**
|
|
8383
8834
|
* unicast-fallback → unicast. Called when the SSM bridge appears mid-session
|
|
@@ -8408,7 +8859,11 @@ var TrackSession = class {
|
|
|
8408
8859
|
if (__privateGet(this, _state) !== "multicast" && __privateGet(this, _state) !== "amt") return;
|
|
8409
8860
|
__privateSet(this, _previousPrimary, __privateGet(this, _state));
|
|
8410
8861
|
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "failing");
|
|
8411
|
-
const hysteresisMs = computeFecLossWindowMs(
|
|
8862
|
+
const hysteresisMs = computeFecLossWindowMs(
|
|
8863
|
+
this.config.fecInterleaveMs,
|
|
8864
|
+
this.config.fecK,
|
|
8865
|
+
this.config.fecReorderToleranceMs
|
|
8866
|
+
);
|
|
8412
8867
|
__privateSet(this, _hysteresisTimer, setTimeout(() => {
|
|
8413
8868
|
this.hysteresisExpired();
|
|
8414
8869
|
}, hysteresisMs));
|
|
@@ -8432,23 +8887,18 @@ var TrackSession = class {
|
|
|
8432
8887
|
const prefs = this.config.preferredTransports;
|
|
8433
8888
|
const currentIdx = current ? prefs.indexOf(current) : -1;
|
|
8434
8889
|
const nextMode = prefs[currentIdx + 1];
|
|
8435
|
-
if (nextMode) {
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
__privateMethod(this, _TrackSession_instances, unsuppress_fn).call(this);
|
|
8439
|
-
if (this.config.trackType === "audio") {
|
|
8440
|
-
__privateGet(this, _manager).mmtp?.onTrackEvent?.(this.config.trackName, { type: "reset" }, this.config);
|
|
8441
|
-
}
|
|
8442
|
-
} else {
|
|
8443
|
-
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "joining");
|
|
8444
|
-
__privateMethod(this, _TrackSession_instances, unsuppress_fn).call(this);
|
|
8445
|
-
}
|
|
8446
|
-
} else {
|
|
8447
|
-
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "unicast");
|
|
8890
|
+
if (nextMode !== void 0 && nextMode !== "moq") {
|
|
8891
|
+
__privateSet(this, _previousPrimary, nextMode);
|
|
8892
|
+
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "joining");
|
|
8448
8893
|
__privateMethod(this, _TrackSession_instances, unsuppress_fn).call(this);
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8894
|
+
return;
|
|
8895
|
+
}
|
|
8896
|
+
__privateSet(this, _previousPrimary, null);
|
|
8897
|
+
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "unicast");
|
|
8898
|
+
__privateMethod(this, _TrackSession_instances, disarmFec_fn).call(this);
|
|
8899
|
+
__privateMethod(this, _TrackSession_instances, unsuppress_fn).call(this);
|
|
8900
|
+
if (this.config.trackType === "audio") {
|
|
8901
|
+
__privateGet(this, _manager).mmtp?.onTrackEvent?.(this.config.trackName, { type: "reset" }, this.config);
|
|
8452
8902
|
}
|
|
8453
8903
|
}
|
|
8454
8904
|
/** Hysteresis expired → failback to next transport (Claim 9g). */
|
|
@@ -8472,11 +8922,20 @@ var TrackSession = class {
|
|
|
8472
8922
|
get isUnicastFallback() {
|
|
8473
8923
|
return __privateGet(this, _state) === "unicast-fallback";
|
|
8474
8924
|
}
|
|
8925
|
+
/**
|
|
8926
|
+
* True while this track owns a FEC decoder (fec-setup fired, fec-teardown
|
|
8927
|
+
* not yet). Stays true across a takeover timeout that parked the session in
|
|
8928
|
+
* `unicast`; SessionManager.failbackAll uses it to release such sessions.
|
|
8929
|
+
*/
|
|
8930
|
+
get isFecArmed() {
|
|
8931
|
+
return __privateGet(this, _fecArmed);
|
|
8932
|
+
}
|
|
8475
8933
|
dispose() {
|
|
8476
8934
|
__privateMethod(this, _TrackSession_instances, clearHysteresis_fn).call(this);
|
|
8477
8935
|
__privateMethod(this, _TrackSession_instances, clearJoinDeadline_fn).call(this);
|
|
8478
8936
|
__privateSet(this, _state, "idle");
|
|
8479
8937
|
__privateSet(this, _subscription, null);
|
|
8938
|
+
__privateMethod(this, _TrackSession_instances, disarmFec_fn).call(this);
|
|
8480
8939
|
}
|
|
8481
8940
|
};
|
|
8482
8941
|
_state = new WeakMap();
|
|
@@ -8486,20 +8945,20 @@ _joinDeadlineTimer = new WeakMap();
|
|
|
8486
8945
|
_previousPrimary = new WeakMap();
|
|
8487
8946
|
_manager = new WeakMap();
|
|
8488
8947
|
_suppressFired = new WeakMap();
|
|
8948
|
+
_fecArmed = new WeakMap();
|
|
8489
8949
|
_TrackSession_instances = new WeakSet();
|
|
8490
8950
|
// -----------------------------------------------------------------------
|
|
8491
8951
|
// Internal
|
|
8492
8952
|
// -----------------------------------------------------------------------
|
|
8493
|
-
transition_fn = function(to) {
|
|
8953
|
+
transition_fn = function(to, opts) {
|
|
8494
8954
|
const from = __privateGet(this, _state);
|
|
8495
8955
|
if (from === to) return;
|
|
8956
|
+
const joinDeadlineMs = to === "joining" ? __privateGet(this, _manager).takeoverDeadlineMs : void 0;
|
|
8496
8957
|
__privateSet(this, _state, to);
|
|
8497
8958
|
if (from === "joining") __privateMethod(this, _TrackSession_instances, clearJoinDeadline_fn).call(this);
|
|
8498
|
-
if (
|
|
8959
|
+
if (joinDeadlineMs !== void 0) __privateMethod(this, _TrackSession_instances, armJoinDeadline_fn).call(this, joinDeadlineMs);
|
|
8499
8960
|
const fecActive = (s) => s === "joining" || s === "multicast" || s === "amt" || s === "failing" || s === "unicast-fallback";
|
|
8500
|
-
if (fecActive(
|
|
8501
|
-
__privateGet(this, _manager).mmtp?.onTrackEvent?.(this.config.trackName, { type: "fec-teardown" }, this.config);
|
|
8502
|
-
}
|
|
8961
|
+
if (!fecActive(to) && !opts?.retainFec) __privateMethod(this, _TrackSession_instances, disarmFec_fn).call(this);
|
|
8503
8962
|
__privateGet(this, _manager).callbacks?.onStateChange?.(this.config.trackName, from, to);
|
|
8504
8963
|
};
|
|
8505
8964
|
clearHysteresis_fn = function() {
|
|
@@ -8508,14 +8967,13 @@ clearHysteresis_fn = function() {
|
|
|
8508
8967
|
__privateSet(this, _hysteresisTimer, null);
|
|
8509
8968
|
}
|
|
8510
8969
|
};
|
|
8511
|
-
armJoinDeadline_fn = function() {
|
|
8970
|
+
armJoinDeadline_fn = function(deadlineMs) {
|
|
8512
8971
|
__privateMethod(this, _TrackSession_instances, clearJoinDeadline_fn).call(this);
|
|
8513
|
-
|
|
8514
|
-
if (deadlineMs <= 0) return;
|
|
8972
|
+
if (deadlineMs === 0) return;
|
|
8515
8973
|
__privateSet(this, _joinDeadlineTimer, setTimeout(() => {
|
|
8516
8974
|
__privateSet(this, _joinDeadlineTimer, null);
|
|
8517
8975
|
if (__privateGet(this, _state) !== "joining") return;
|
|
8518
|
-
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "unicast");
|
|
8976
|
+
__privateMethod(this, _TrackSession_instances, transition_fn).call(this, "unicast", { retainFec: true });
|
|
8519
8977
|
__privateGet(this, _manager).callbacks?.onTakeoverTimeout?.(this.config.trackName);
|
|
8520
8978
|
}, deadlineMs));
|
|
8521
8979
|
};
|
|
@@ -8525,6 +8983,18 @@ clearJoinDeadline_fn = function() {
|
|
|
8525
8983
|
__privateSet(this, _joinDeadlineTimer, null);
|
|
8526
8984
|
}
|
|
8527
8985
|
};
|
|
8986
|
+
/** Fire fec-setup exactly once per FEC epoch. */
|
|
8987
|
+
armFec_fn = function() {
|
|
8988
|
+
if (__privateGet(this, _fecArmed)) return;
|
|
8989
|
+
__privateSet(this, _fecArmed, true);
|
|
8990
|
+
__privateGet(this, _manager).mmtp?.onTrackEvent?.(this.config.trackName, { type: "fec-setup" }, this.config);
|
|
8991
|
+
};
|
|
8992
|
+
/** Fire fec-teardown for an armed decoder; no-op when none is armed. */
|
|
8993
|
+
disarmFec_fn = function() {
|
|
8994
|
+
if (!__privateGet(this, _fecArmed)) return;
|
|
8995
|
+
__privateSet(this, _fecArmed, false);
|
|
8996
|
+
__privateGet(this, _manager).mmtp?.onTrackEvent?.(this.config.trackName, { type: "fec-teardown" }, this.config);
|
|
8997
|
+
};
|
|
8528
8998
|
/** Fire onSuppress(true) exactly once per suppression epoch. */
|
|
8529
8999
|
suppress_fn = function() {
|
|
8530
9000
|
if (__privateGet(this, _suppressFired)) return;
|
|
@@ -8537,18 +9007,14 @@ unsuppress_fn = function() {
|
|
|
8537
9007
|
__privateSet(this, _suppressFired, false);
|
|
8538
9008
|
__privateGet(this, _manager).callbacks?.onSuppress?.(this.config.trackName, false);
|
|
8539
9009
|
};
|
|
9010
|
+
var MAX_TAKEOVER_DEADLINE_MS = 2147483647;
|
|
9011
|
+
var _takeoverDeadlineMs, _SessionManager_instances, requireTakeoverDeadlineMs_fn;
|
|
8540
9012
|
var SessionManager = class {
|
|
8541
9013
|
constructor(poolOptions) {
|
|
9014
|
+
__privateAdd(this, _SessionManager_instances);
|
|
8542
9015
|
this.sessions = /* @__PURE__ */ new Map();
|
|
8543
9016
|
this.callbacks = null;
|
|
8544
|
-
|
|
8545
|
-
* Takeover deadline (BLO-9413/D9): max time a session may sit in `joining`
|
|
8546
|
-
* without a RAP before returning to `unicast` and firing onTakeoverTimeout.
|
|
8547
|
-
* MoQ is never suppressed during `joining`, so expiry is a silent abandon —
|
|
8548
|
-
* playback never glitches. Hosts tune per stream (e.g. GOP length); 0
|
|
8549
|
-
* disables.
|
|
8550
|
-
*/
|
|
8551
|
-
this.takeoverDeadlineMs = 5e3;
|
|
9017
|
+
__privateAdd(this, _takeoverDeadlineMs);
|
|
8552
9018
|
this.pool = new TransportPool(poolOptions);
|
|
8553
9019
|
this.pool.onSubscriptionFailed((sub) => {
|
|
8554
9020
|
for (const trackName of sub.consumers) {
|
|
@@ -8557,18 +9023,56 @@ var SessionManager = class {
|
|
|
8557
9023
|
}
|
|
8558
9024
|
});
|
|
8559
9025
|
}
|
|
9026
|
+
/**
|
|
9027
|
+
* Takeover deadline (BLO-9413/D9): the time a session may sit in `joining`
|
|
9028
|
+
* without a RAP before returning to `unicast` and firing onTakeoverTimeout.
|
|
9029
|
+
* MoQ is never suppressed during `joining`, so expiry abandons the takeover
|
|
9030
|
+
* without a playback glitch. The host derives it from the catalog (RAP
|
|
9031
|
+
* cadence from groupDurationTicks/timescale plus the signaled FEC delivery
|
|
9032
|
+
* window or reorder tolerance) and sets it before addTrack(). There is no
|
|
9033
|
+
* default: an unset value fails addTrack() closed, and reading the getter
|
|
9034
|
+
* before assignment throws the same way, so no host ever holds a
|
|
9035
|
+
* `number | undefined` it could coalesce back into a default.
|
|
9036
|
+
* `hasTakeoverDeadline` answers whether one was admitted. A declared 0
|
|
9037
|
+
* disables the deadline. The value is an integer number of milliseconds:
|
|
9038
|
+
* setTimeout truncates fractions, so a host that divides ticks by a
|
|
9039
|
+
* timescale must round before assigning.
|
|
9040
|
+
*
|
|
9041
|
+
* Assignment validates, so the diagnostic lands at the host's own call site.
|
|
9042
|
+
* That is what bounds the blast radius: `joining` is entered from
|
|
9043
|
+
* joinStarted(), from the transportFailed() failover branch -- itself
|
|
9044
|
+
* reachable from a setTimeout via hysteresisExpired(), where no caller could
|
|
9045
|
+
* catch a throw -- and from the activateMulticast()/activateAmt()/pool-failure
|
|
9046
|
+
* loops, where a throw on one session would abandon its siblings mid-loop
|
|
9047
|
+
* (the split-brain those loops exist to prevent). Refusing a bad value at
|
|
9048
|
+
* assignment means no host mutation can make an already-admitted session
|
|
9049
|
+
* throw out of a transport edge.
|
|
9050
|
+
*/
|
|
9051
|
+
get takeoverDeadlineMs() {
|
|
9052
|
+
return __privateMethod(this, _SessionManager_instances, requireTakeoverDeadlineMs_fn).call(this);
|
|
9053
|
+
}
|
|
9054
|
+
set takeoverDeadlineMs(value) {
|
|
9055
|
+
if (!Number.isInteger(value) || value < 0 || value > MAX_TAKEOVER_DEADLINE_MS) {
|
|
9056
|
+
throw new Error(
|
|
9057
|
+
`[SessionManager] takeoverDeadlineMs must be an integer from 0 to ${MAX_TAKEOVER_DEADLINE_MS} (0 disables it, larger values overflow setTimeout); got ${String(value)}`
|
|
9058
|
+
);
|
|
9059
|
+
}
|
|
9060
|
+
__privateSet(this, _takeoverDeadlineMs, value);
|
|
9061
|
+
}
|
|
9062
|
+
/** True once the host has admitted a deadline; a declared 0 counts. */
|
|
9063
|
+
get hasTakeoverDeadline() {
|
|
9064
|
+
return __privateGet(this, _takeoverDeadlineMs) !== void 0;
|
|
9065
|
+
}
|
|
8560
9066
|
/** Narrow to MMTP callbacks (onTrackEvent). Safe — optional chaining handles PIM-only consumers. */
|
|
8561
9067
|
get mmtp() {
|
|
8562
9068
|
return this.callbacks;
|
|
8563
9069
|
}
|
|
8564
|
-
// -----------------------------------------------------------------------
|
|
8565
|
-
// Track management
|
|
8566
|
-
// -----------------------------------------------------------------------
|
|
8567
9070
|
/** Add a track session from catalog config. */
|
|
8568
9071
|
addTrack(config) {
|
|
8569
9072
|
if (this.sessions.has(config.trackName)) {
|
|
8570
9073
|
throw new Error(`[SessionManager] Track "${config.trackName}" already exists`);
|
|
8571
9074
|
}
|
|
9075
|
+
__privateMethod(this, _SessionManager_instances, requireTakeoverDeadlineMs_fn).call(this);
|
|
8572
9076
|
const session = new TrackSession(config, this);
|
|
8573
9077
|
this.sessions.set(config.trackName, session);
|
|
8574
9078
|
return session;
|
|
@@ -8592,8 +9096,9 @@ var SessionManager = class {
|
|
|
8592
9096
|
* Prevents split-brain between video and audio.
|
|
8593
9097
|
*
|
|
8594
9098
|
* A takeover deadline can return a track from `joining` to `unicast` while
|
|
8595
|
-
* the multicast socket keeps delivering packets
|
|
8596
|
-
* later,
|
|
9099
|
+
* the multicast socket keeps delivering packets and the track keeps its FEC
|
|
9100
|
+
* decoder armed. If a valid RAP arrives later, that late-RAP edge suppresses
|
|
9101
|
+
* unicast and promotes multicast without a second fec-setup.
|
|
8597
9102
|
*/
|
|
8598
9103
|
activateMulticast() {
|
|
8599
9104
|
for (const [, session] of this.sessions) {
|
|
@@ -8625,13 +9130,17 @@ var SessionManager = class {
|
|
|
8625
9130
|
* and they must drain uninterrupted across transport-state events.
|
|
8626
9131
|
*
|
|
8627
9132
|
* Coherence invariant (BLO-9413): after this returns, every non-fallback
|
|
8628
|
-
* session is `unicast` (or `idle`)
|
|
8629
|
-
* any outstanding suppression regardless of the state
|
|
8630
|
-
* (`failing` included; see TrackSession.#suppressFired)
|
|
9133
|
+
* session is `unicast` (or `idle`), unsuppressed, and without a FEC decoder.
|
|
9134
|
+
* unsubscribe() unwinds any outstanding suppression regardless of the state
|
|
9135
|
+
* it tears down from (`failing` included; see TrackSession.#suppressFired),
|
|
9136
|
+
* and a session that a takeover timeout parked in `unicast` with its decoder
|
|
9137
|
+
* still armed is cycled too, because this transport edge is what releases
|
|
9138
|
+
* that decoder (unsubscribe fires fec-teardown).
|
|
8631
9139
|
*/
|
|
8632
9140
|
failbackAll() {
|
|
8633
9141
|
for (const [, session] of this.sessions) {
|
|
8634
|
-
|
|
9142
|
+
const parkedWithFec = session.state === "unicast" && session.isFecArmed;
|
|
9143
|
+
if (parkedWithFec || session.state !== "idle" && session.state !== "unicast" && session.state !== "unicast-fallback") {
|
|
8635
9144
|
session.unsubscribe();
|
|
8636
9145
|
session.subscribe();
|
|
8637
9146
|
}
|
|
@@ -8715,6 +9224,27 @@ var SessionManager = class {
|
|
|
8715
9224
|
this.callbacks = null;
|
|
8716
9225
|
}
|
|
8717
9226
|
};
|
|
9227
|
+
_takeoverDeadlineMs = new WeakMap();
|
|
9228
|
+
_SessionManager_instances = new WeakSet();
|
|
9229
|
+
// -----------------------------------------------------------------------
|
|
9230
|
+
// Track management
|
|
9231
|
+
// -----------------------------------------------------------------------
|
|
9232
|
+
/**
|
|
9233
|
+
* The admitted takeover deadline. Throws only when the host never set one,
|
|
9234
|
+
* every other invalid value being refused by the setter, and returns a
|
|
9235
|
+
* declared 0 as 0. The getter and addTrack() both route through it, so a
|
|
9236
|
+
* session cannot exist without an admitted deadline, which is what makes
|
|
9237
|
+
* the #transition() read unable to throw on an already-admitted session.
|
|
9238
|
+
*/
|
|
9239
|
+
requireTakeoverDeadlineMs_fn = function() {
|
|
9240
|
+
const value = __privateGet(this, _takeoverDeadlineMs);
|
|
9241
|
+
if (value === void 0) {
|
|
9242
|
+
throw new Error(
|
|
9243
|
+
"[SessionManager] takeoverDeadlineMs is unset; set a catalog-derived takeover deadline (0 disables it) before adding tracks"
|
|
9244
|
+
);
|
|
9245
|
+
}
|
|
9246
|
+
return value;
|
|
9247
|
+
};
|
|
8718
9248
|
|
|
8719
9249
|
// src/fec-manager.ts
|
|
8720
9250
|
var MIN_INTERLEAVE_DEPTH_MS = 33;
|
|
@@ -8733,7 +9263,7 @@ function newFecCounters() {
|
|
|
8733
9263
|
greenFillFrames: 0
|
|
8734
9264
|
};
|
|
8735
9265
|
}
|
|
8736
|
-
var _fecDecoders, _workerClients, _syncDecoderBlockOwners, _decoderBlockOwners, _fecK, _fecSymbolSize, _fecTimeoutMs, _trackConfigs, _fecStats, _fecTrackStats, _fecWorkerStats, _fecStatsObservers, _activeBlocks, _activeBlockDeadlinesMs, _activeBlockTimeoutOwners, _lifecycleTrace, _pendingWorkerCleanups, _pendingWorkerCleanupClients, _blockSourceCounts, _blockSourceEsis, _blockRepairRsIds, _completedBlocks, _seenSymbols, _blockPathCounts, _blockCatalogTrackNames, _startupAlignedPackets, _startupPartialBlocks, _startupRequireRepairReady, _startupRepairReadyPackets, _startupRepairBoundaryCounts, _startupRepairFailOpenPackets, _cleanlyCompletedBlocks, _repairRequestedBlocks, _repairDeadlines, _workerRecoveredBlocks, _fpsDetected, _fecFps, _fecInterleaveDepthMs, _workerErrorCounts, _repairSymbolSizeMismatchCount, _FECManager_instances, streamKey_fn, belongsToPacket_fn, belongsToStream_fn, packetIdsForStreamKeys_fn, blockKey_fn, parseBlockKey_fn, decoderForPacket_fn, workerForPacket_fn, claimDecoderBlock_fn, decoderBlockCatalogTrackName_fn, resetDecoderBlockOwners_fn, discardFecDecoderClient_fn, assertExclusiveDecoderClient_fn, statsForPacket_fn, markPathBlockComplete_fn, clearRepairDeadline_fn, requestRepair_fn, traceLifecycle_fn, defaultTrackConfig_fn, resolveTrackConfig_fn, computeTimeout_fn, repairDeadlineMs_fn, repairGraceMs_fn, armBlockTimeout_fn, upsertTrackConfig_fn, normalizeSymbolData_fn, blockKeysFor_fn, observeBlockCatalogTrackName_fn, markStartupPartial_fn, routeSyncCleanup_fn, failBlock_fn;
|
|
9266
|
+
var _fecDecoders, _workerClients, _syncDecoderBlockOwners, _decoderBlockOwners, _fecK, _fecSymbolSize, _fecTimeoutMs, _trackConfigs, _fecStats, _fecTrackStats, _fecWorkerStats, _fecStatsObservers, _activeBlocks, _activeBlockDeadlinesMs, _activeBlockTimeoutOwners, _lifecycleTrace, _pendingWorkerCleanups, _pendingWorkerCleanupClients, _blockSourceCounts, _blockSourceEsis, _blockRepairRsIds, _completedBlocks, _seenSymbols, _blockPathCounts, _blockCatalogTrackNames, _startupAlignedPackets, _startupPartialBlocks, _startupRequireRepairReady, _startupRepairReadyPackets, _startupRepairBoundaryCounts, _startupRepairFailOpenPackets, _cleanlyCompletedBlocks, _repairRequestedBlocks, _repairDeadlines, _workerRecoveredBlocks, _fpsDetected, _fecFps, _fecInterleaveDepthMs, _workerErrorCounts, _decoderUnavailableEvents, _repairSymbolSizeMismatchCount, _FECManager_instances, streamKey_fn, belongsToPacket_fn, belongsToStream_fn, packetIdsForStreamKeys_fn, parseStreamKey_fn, blockKey_fn, parseBlockKey_fn, decoderForPacket_fn, workerForPacket_fn, routableWorkerForPacket_fn, claimDecoderBlock_fn, decoderBlockCatalogTrackName_fn, resetDecoderBlockOwners_fn, discardFecDecoderClient_fn, assertExclusiveDecoderClient_fn, statsForPacket_fn, markPathBlockComplete_fn, clearRepairDeadline_fn, requestRepair_fn, traceLifecycle_fn, defaultTrackConfig_fn, resolveTrackConfig_fn, computeTimeout_fn, repairDeadlineMs_fn, repairGraceMs_fn, armBlockTimeout_fn, upsertTrackConfig_fn, normalizeSymbolData_fn, blockKeysFor_fn, observeBlockCatalogTrackName_fn, markStartupPartial_fn, retireWorker_fn, releaseWorkerClient_fn, settleOwedCleanup_fn, evictDisposedWorker_fn, routeSyncCleanup_fn, failBlock_fn;
|
|
8737
9267
|
var FECManager = class {
|
|
8738
9268
|
constructor() {
|
|
8739
9269
|
__privateAdd(this, _FECManager_instances);
|
|
@@ -8837,6 +9367,7 @@ var FECManager = class {
|
|
|
8837
9367
|
// validation harness — which caps headless warnings at 500 — fails even
|
|
8838
9368
|
// though the errors are duplicates of a single underlying event.
|
|
8839
9369
|
__privateAdd(this, _workerErrorCounts, /* @__PURE__ */ new Map());
|
|
9370
|
+
__privateAdd(this, _decoderUnavailableEvents, 0);
|
|
8840
9371
|
__privateAdd(this, _repairSymbolSizeMismatchCount, 0);
|
|
8841
9372
|
// Event callbacks — block lifecycle notifications for MmtpRouter wiring.
|
|
8842
9373
|
// NOTE: For sync WASM path, recovery routing is handled by MmtpRouter from
|
|
@@ -8845,6 +9376,17 @@ var FECManager = class {
|
|
|
8845
9376
|
this.onBlockStarted = null;
|
|
8846
9377
|
this.onBlockComplete = null;
|
|
8847
9378
|
this.onBlockFailed = null;
|
|
9379
|
+
/**
|
|
9380
|
+
* Fired once per stream when its Worker decoder stops being routable.
|
|
9381
|
+
* `reason` is 'cleared' when clearFecWorker() removed it and 'disposed' when
|
|
9382
|
+
* a symbol or reset found the registered client already disposed and
|
|
9383
|
+
* evicted it. `fallbackDecoder` is 'sync' when a synchronous WASM decoder
|
|
9384
|
+
* registered for the same stream keeps decoding its symbols, and 'none'
|
|
9385
|
+
* when nothing does. Only 'none' leaves the stream unprotected: the host
|
|
9386
|
+
* must then register a decoder again or leave the stream, because counting
|
|
9387
|
+
* its symbols as handled would hide the loss.
|
|
9388
|
+
*/
|
|
9389
|
+
this.onDecoderUnavailable = null;
|
|
8848
9390
|
// D-28 on-demand repair callbacks. When onRepairNeeded is registered, each
|
|
8849
9391
|
// block arms a one-shot repair deadline after the derived K-symbol source
|
|
8850
9392
|
// horizon; a block still short of K when it fires requests a per-SBN MoQ
|
|
@@ -8878,6 +9420,7 @@ var FECManager = class {
|
|
|
8878
9420
|
startupRepairReadyPackets: __privateMethod(this, _FECManager_instances, packetIdsForStreamKeys_fn).call(this, __privateGet(this, _startupRepairReadyPackets)),
|
|
8879
9421
|
startupRepairFailOpenPackets: __privateMethod(this, _FECManager_instances, packetIdsForStreamKeys_fn).call(this, __privateGet(this, _startupRepairFailOpenPackets)),
|
|
8880
9422
|
pendingWorkerCleanups: __privateGet(this, _pendingWorkerCleanups).size,
|
|
9423
|
+
decoderUnavailableEvents: __privateGet(this, _decoderUnavailableEvents),
|
|
8881
9424
|
repairTrackedBlocks: __privateGet(this, _blockRepairRsIds).size,
|
|
8882
9425
|
lifecycleTrace: __privateGet(this, _lifecycleTrace).map((entry) => ({ ...entry }))
|
|
8883
9426
|
};
|
|
@@ -9012,8 +9555,12 @@ var FECManager = class {
|
|
|
9012
9555
|
__privateMethod(this, _FECManager_instances, discardFecDecoderClient_fn).call(this, client);
|
|
9013
9556
|
}
|
|
9014
9557
|
}
|
|
9015
|
-
for (const [key, client] of __privateGet(this, _workerClients)) {
|
|
9558
|
+
for (const [key, client] of [...__privateGet(this, _workerClients)]) {
|
|
9016
9559
|
if (!__privateMethod(this, _FECManager_instances, belongsToStream_fn).call(this, key, packetId, catalogTrackName)) continue;
|
|
9560
|
+
if (client.disposed === true) {
|
|
9561
|
+
__privateMethod(this, _FECManager_instances, evictDisposedWorker_fn).call(this, client);
|
|
9562
|
+
continue;
|
|
9563
|
+
}
|
|
9017
9564
|
client.reset();
|
|
9018
9565
|
clients.add(client);
|
|
9019
9566
|
}
|
|
@@ -9233,6 +9780,12 @@ var FECManager = class {
|
|
|
9233
9780
|
}
|
|
9234
9781
|
const streamKey = __privateMethod(this, _FECManager_instances, streamKey_fn).call(this, packetId, catalogTrackName);
|
|
9235
9782
|
__privateMethod(this, _FECManager_instances, assertExclusiveDecoderClient_fn).call(this, streamKey, client, __privateGet(this, _workerClients), "worker");
|
|
9783
|
+
const incumbent = __privateGet(this, _workerClients).get(streamKey);
|
|
9784
|
+
if (incumbent !== void 0 && incumbent !== client) {
|
|
9785
|
+
throw new Error(
|
|
9786
|
+
`[FECManager] stream ${streamKey} already routes to a Worker; call clearFecWorker before registering another`
|
|
9787
|
+
);
|
|
9788
|
+
}
|
|
9236
9789
|
if (config) __privateMethod(this, _FECManager_instances, upsertTrackConfig_fn).call(this, packetId, config);
|
|
9237
9790
|
const workerK = __privateMethod(this, _FECManager_instances, resolveTrackConfig_fn).call(this, packetId).k;
|
|
9238
9791
|
__privateGet(this, _workerClients).set(streamKey, client);
|
|
@@ -9319,14 +9872,7 @@ var FECManager = class {
|
|
|
9319
9872
|
const block = __privateMethod(this, _FECManager_instances, parseBlockKey_fn).call(this, key);
|
|
9320
9873
|
return block.packetId === packetId && block.sbn === sbn && __privateGet(this, _pendingWorkerCleanupClients).get(key) === client;
|
|
9321
9874
|
});
|
|
9322
|
-
for (const key of cleanupKeys)
|
|
9323
|
-
__privateGet(this, _pendingWorkerCleanups).delete(key);
|
|
9324
|
-
__privateGet(this, _pendingWorkerCleanupClients).delete(key);
|
|
9325
|
-
if (__privateGet(this, _completedBlocks).has(key)) continue;
|
|
9326
|
-
const block = __privateMethod(this, _FECManager_instances, parseBlockKey_fn).call(this, key);
|
|
9327
|
-
__privateMethod(this, _FECManager_instances, traceLifecycle_fn).call(this, "cleanup-ack", sbn, packetId, block.catalogTrackName);
|
|
9328
|
-
__privateMethod(this, _FECManager_instances, failBlock_fn).call(this, sbn, packetId, false, block.catalogTrackName);
|
|
9329
|
-
}
|
|
9875
|
+
for (const key of cleanupKeys) __privateMethod(this, _FECManager_instances, settleOwedCleanup_fn).call(this, key, "cleanup-ack");
|
|
9330
9876
|
});
|
|
9331
9877
|
client.onError((msg, code) => {
|
|
9332
9878
|
const requestedKind = code ?? msg;
|
|
@@ -9342,6 +9888,25 @@ var FECManager = class {
|
|
|
9342
9888
|
}
|
|
9343
9889
|
});
|
|
9344
9890
|
}
|
|
9891
|
+
/**
|
|
9892
|
+
* Remove a stream's Worker decoder from routing.
|
|
9893
|
+
*
|
|
9894
|
+
* Returns false when no Worker is registered under that exact stream key.
|
|
9895
|
+
* On success every cleanup acknowledgement still owed by the client is
|
|
9896
|
+
* resolved by failing its block now (a Worker that is going away cannot
|
|
9897
|
+
* post it), the client's block ownership is released, and
|
|
9898
|
+
* onDecoderUnavailable fires once with reason 'cleared' and the decoder
|
|
9899
|
+
* that remains for the stream ('sync' or 'none'). Call this before
|
|
9900
|
+
* disposing the client; a client disposed while still registered is
|
|
9901
|
+
* evicted on its next symbol or reset with reason 'disposed'.
|
|
9902
|
+
*/
|
|
9903
|
+
clearFecWorker(packetId, catalogTrackName) {
|
|
9904
|
+
const streamKey = __privateMethod(this, _FECManager_instances, streamKey_fn).call(this, packetId, catalogTrackName);
|
|
9905
|
+
const client = __privateGet(this, _workerClients).get(streamKey);
|
|
9906
|
+
if (!client) return false;
|
|
9907
|
+
__privateMethod(this, _FECManager_instances, retireWorker_fn).call(this, streamKey, client, "cleared");
|
|
9908
|
+
return true;
|
|
9909
|
+
}
|
|
9345
9910
|
/** Called by player when framerate is known (from decoder output). Refines FEC block timeout. */
|
|
9346
9911
|
setFramerate(fps) {
|
|
9347
9912
|
if (__privateGet(this, _fpsDetected) || fps <= 0) return;
|
|
@@ -9392,7 +9957,7 @@ var FECManager = class {
|
|
|
9392
9957
|
const sbn = Math.floor(ssId / this.kForPacket(packetId));
|
|
9393
9958
|
const blockKey = __privateMethod(this, _FECManager_instances, blockKey_fn).call(this, sbn, packetId, catalogTrackName);
|
|
9394
9959
|
const syncBlockOwner = __privateGet(this, _syncDecoderBlockOwners).get(blockKey);
|
|
9395
|
-
const workerClient = __privateMethod(this, _FECManager_instances,
|
|
9960
|
+
const workerClient = __privateMethod(this, _FECManager_instances, routableWorkerForPacket_fn).call(this, packetId, catalogTrackName);
|
|
9396
9961
|
if (workerClient && !syncBlockOwner) {
|
|
9397
9962
|
if (!__privateMethod(this, _FECManager_instances, claimDecoderBlock_fn).call(this, workerClient, sbn, catalogTrackName)) return null;
|
|
9398
9963
|
const buf = fecData.buffer.slice(
|
|
@@ -9469,7 +10034,7 @@ var FECManager = class {
|
|
|
9469
10034
|
if (__privateGet(this, _repairRequestedBlocks).has(blockKey)) {
|
|
9470
10035
|
__privateMethod(this, _FECManager_instances, traceLifecycle_fn).call(this, "repair-arrived", sbn, packetId, catalogTrackName, rsId);
|
|
9471
10036
|
}
|
|
9472
|
-
const workerClient = __privateMethod(this, _FECManager_instances,
|
|
10037
|
+
const workerClient = __privateMethod(this, _FECManager_instances, routableWorkerForPacket_fn).call(this, packetId, catalogTrackName);
|
|
9473
10038
|
if (workerClient && !syncBlockOwner) {
|
|
9474
10039
|
if (!__privateMethod(this, _FECManager_instances, claimDecoderBlock_fn).call(this, workerClient, sbn, catalogTrackName)) return null;
|
|
9475
10040
|
const buf = fecData.buffer.slice(
|
|
@@ -9754,6 +10319,7 @@ _fpsDetected = new WeakMap();
|
|
|
9754
10319
|
_fecFps = new WeakMap();
|
|
9755
10320
|
_fecInterleaveDepthMs = new WeakMap();
|
|
9756
10321
|
_workerErrorCounts = new WeakMap();
|
|
10322
|
+
_decoderUnavailableEvents = new WeakMap();
|
|
9757
10323
|
_repairSymbolSizeMismatchCount = new WeakMap();
|
|
9758
10324
|
_FECManager_instances = new WeakSet();
|
|
9759
10325
|
streamKey_fn = function(packetId, catalogTrackName) {
|
|
@@ -9773,6 +10339,14 @@ belongsToStream_fn = function(key, packetId, catalogTrackName) {
|
|
|
9773
10339
|
packetIdsForStreamKeys_fn = function(keys) {
|
|
9774
10340
|
return [...new Set([...keys].map((key) => Number(key.split(":", 1)[0])))];
|
|
9775
10341
|
};
|
|
10342
|
+
parseStreamKey_fn = function(key) {
|
|
10343
|
+
const separator = key.indexOf(":");
|
|
10344
|
+
if (separator < 0) return { packetId: Number(key) || 0 };
|
|
10345
|
+
return {
|
|
10346
|
+
packetId: Number(key.slice(0, separator)) || 0,
|
|
10347
|
+
catalogTrackName: decodeURIComponent(key.slice(separator + 1))
|
|
10348
|
+
};
|
|
10349
|
+
};
|
|
9776
10350
|
blockKey_fn = function(sbn, packetId, catalogTrackName) {
|
|
9777
10351
|
const base = `${packetId}:${sbn}`;
|
|
9778
10352
|
return catalogTrackName === void 0 ? base : `${base}:${encodeURIComponent(catalogTrackName)}`;
|
|
@@ -9792,6 +10366,18 @@ decoderForPacket_fn = function(packetId, catalogTrackName) {
|
|
|
9792
10366
|
workerForPacket_fn = function(packetId, catalogTrackName) {
|
|
9793
10367
|
return __privateGet(this, _workerClients).get(__privateMethod(this, _FECManager_instances, streamKey_fn).call(this, packetId, catalogTrackName)) ?? __privateGet(this, _workerClients).get(__privateMethod(this, _FECManager_instances, streamKey_fn).call(this, packetId));
|
|
9794
10368
|
};
|
|
10369
|
+
/**
|
|
10370
|
+
* The stream's Worker client for routing, or undefined after evicting a
|
|
10371
|
+
* registered client that was already disposed. A disposed client throws on
|
|
10372
|
+
* every command, so it leaves routing here before a symbol reaches it and
|
|
10373
|
+
* the stream reports its decoder unavailable instead.
|
|
10374
|
+
*/
|
|
10375
|
+
routableWorkerForPacket_fn = function(packetId, catalogTrackName) {
|
|
10376
|
+
const client = __privateMethod(this, _FECManager_instances, workerForPacket_fn).call(this, packetId, catalogTrackName);
|
|
10377
|
+
if (client === void 0 || client.disposed !== true) return client;
|
|
10378
|
+
__privateMethod(this, _FECManager_instances, evictDisposedWorker_fn).call(this, client);
|
|
10379
|
+
return void 0;
|
|
10380
|
+
};
|
|
9795
10381
|
claimDecoderBlock_fn = function(client, sbn, catalogTrackName) {
|
|
9796
10382
|
let blocks = __privateGet(this, _decoderBlockOwners).get(client);
|
|
9797
10383
|
if (!blocks) {
|
|
@@ -9959,7 +10545,7 @@ armBlockTimeout_fn = function(sbn, packetId, catalogTrackName, delayMs) {
|
|
|
9959
10545
|
__privateMethod(this, _FECManager_instances, failBlock_fn).call(this, sbn, packetId, false, catalogTrackName);
|
|
9960
10546
|
return;
|
|
9961
10547
|
}
|
|
9962
|
-
const workerClient = __privateMethod(this, _FECManager_instances,
|
|
10548
|
+
const workerClient = __privateMethod(this, _FECManager_instances, routableWorkerForPacket_fn).call(this, packetId, catalogTrackName);
|
|
9963
10549
|
if (workerClient) {
|
|
9964
10550
|
__privateGet(this, _pendingWorkerCleanups).add(key);
|
|
9965
10551
|
__privateGet(this, _pendingWorkerCleanupClients).set(key, workerClient);
|
|
@@ -10042,6 +10628,59 @@ markStartupPartial_fn = function(blockKey) {
|
|
|
10042
10628
|
));
|
|
10043
10629
|
}
|
|
10044
10630
|
};
|
|
10631
|
+
/**
|
|
10632
|
+
* Take a Worker client out of routing under one stream key, fail the blocks
|
|
10633
|
+
* whose completion it still owed, and report the stream's decoder
|
|
10634
|
+
* unavailable together with whatever still decodes the stream.
|
|
10635
|
+
*/
|
|
10636
|
+
retireWorker_fn = function(streamKey, client, reason) {
|
|
10637
|
+
__privateGet(this, _workerClients).delete(streamKey);
|
|
10638
|
+
__privateMethod(this, _FECManager_instances, releaseWorkerClient_fn).call(this, client);
|
|
10639
|
+
__privateWrapper(this, _decoderUnavailableEvents)._++;
|
|
10640
|
+
const stream = __privateMethod(this, _FECManager_instances, parseStreamKey_fn).call(this, streamKey);
|
|
10641
|
+
const fallbackDecoder = __privateMethod(this, _FECManager_instances, decoderForPacket_fn).call(this, stream.packetId, stream.catalogTrackName) ? "sync" : "none";
|
|
10642
|
+
this.onDecoderUnavailable?.({
|
|
10643
|
+
packetId: stream.packetId,
|
|
10644
|
+
...stream.catalogTrackName === void 0 ? {} : { catalogTrackName: stream.catalogTrackName },
|
|
10645
|
+
reason,
|
|
10646
|
+
fallbackDecoder
|
|
10647
|
+
});
|
|
10648
|
+
};
|
|
10649
|
+
/**
|
|
10650
|
+
* Fail every block whose Worker cleanup acknowledgement is owed by `client`
|
|
10651
|
+
* and drop its block ownership, so no block waits on a message the Worker
|
|
10652
|
+
* will never post.
|
|
10653
|
+
*/
|
|
10654
|
+
releaseWorkerClient_fn = function(client) {
|
|
10655
|
+
const owed = [...__privateGet(this, _pendingWorkerCleanupClients)].filter(([, owner]) => owner === client).map(([key]) => key);
|
|
10656
|
+
for (const key of owed) __privateMethod(this, _FECManager_instances, settleOwedCleanup_fn).call(this, key, "decoder-unavailable");
|
|
10657
|
+
__privateGet(this, _decoderBlockOwners).delete(client);
|
|
10658
|
+
};
|
|
10659
|
+
/**
|
|
10660
|
+
* Resolve one owed Worker cleanup: drop the pending entry and fail the block
|
|
10661
|
+
* unless it already completed. Shared by the cleanup acknowledgement and by
|
|
10662
|
+
* Worker retirement, so the two paths cannot drift in their bookkeeping.
|
|
10663
|
+
*/
|
|
10664
|
+
settleOwedCleanup_fn = function(key, event) {
|
|
10665
|
+
__privateGet(this, _pendingWorkerCleanups).delete(key);
|
|
10666
|
+
__privateGet(this, _pendingWorkerCleanupClients).delete(key);
|
|
10667
|
+
if (__privateGet(this, _completedBlocks).has(key)) return;
|
|
10668
|
+
const block = __privateMethod(this, _FECManager_instances, parseBlockKey_fn).call(this, key);
|
|
10669
|
+
__privateMethod(this, _FECManager_instances, traceLifecycle_fn).call(this, event, block.sbn, block.packetId, block.catalogTrackName);
|
|
10670
|
+
__privateMethod(this, _FECManager_instances, failBlock_fn).call(this, block.sbn, block.packetId, false, block.catalogTrackName);
|
|
10671
|
+
};
|
|
10672
|
+
/** Evict a disposed client under every stream key that still maps to it. */
|
|
10673
|
+
evictDisposedWorker_fn = function(client) {
|
|
10674
|
+
for (const [key, registered] of [...__privateGet(this, _workerClients)]) {
|
|
10675
|
+
if (registered !== client) continue;
|
|
10676
|
+
const stream = __privateMethod(this, _FECManager_instances, parseStreamKey_fn).call(this, key);
|
|
10677
|
+
const track = stream.catalogTrackName === void 0 ? "" : ` track=${stream.catalogTrackName}`;
|
|
10678
|
+
console.warn(
|
|
10679
|
+
`[FECManager] packetId=${stream.packetId}${track} Worker decoder was disposed while still registered; evicting it`
|
|
10680
|
+
);
|
|
10681
|
+
__privateMethod(this, _FECManager_instances, retireWorker_fn).call(this, key, client, "disposed");
|
|
10682
|
+
}
|
|
10683
|
+
};
|
|
10045
10684
|
routeSyncCleanup_fn = function(decoder, sbn, packetId) {
|
|
10046
10685
|
let framed;
|
|
10047
10686
|
try {
|
|
@@ -10613,13 +11252,15 @@ function sameTrack(a, b) {
|
|
|
10613
11252
|
function sameSample(a, b) {
|
|
10614
11253
|
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;
|
|
10615
11254
|
}
|
|
10616
|
-
var _track, _fragments, _pendingSamples;
|
|
11255
|
+
var _track, _fragments, _pendingSamples, _lastMovieFragmentRejection, _IsoBmffTimingResolver_instances, clearSlot_fn;
|
|
10617
11256
|
var IsoBmffTimingResolver = class {
|
|
10618
11257
|
constructor(maxPendingSamples) {
|
|
10619
11258
|
this.maxPendingSamples = maxPendingSamples;
|
|
11259
|
+
__privateAdd(this, _IsoBmffTimingResolver_instances);
|
|
10620
11260
|
__privateAdd(this, _track, null);
|
|
10621
11261
|
__privateAdd(this, _fragments, /* @__PURE__ */ new Map());
|
|
10622
11262
|
__privateAdd(this, _pendingSamples, 0);
|
|
11263
|
+
__privateAdd(this, _lastMovieFragmentRejection, null);
|
|
10623
11264
|
if (!Number.isInteger(maxPendingSamples) || maxPendingSamples <= 0) {
|
|
10624
11265
|
throw new Error("[IsoBmffTimingResolver] maxPendingSamples must be a positive integer");
|
|
10625
11266
|
}
|
|
@@ -10627,6 +11268,10 @@ var IsoBmffTimingResolver = class {
|
|
|
10627
11268
|
get pendingSampleCount() {
|
|
10628
11269
|
return __privateGet(this, _pendingSamples);
|
|
10629
11270
|
}
|
|
11271
|
+
/** Typed reason for the most recent rejected movie fragment. */
|
|
11272
|
+
get lastMovieFragmentRejection() {
|
|
11273
|
+
return __privateGet(this, _lastMovieFragmentRejection);
|
|
11274
|
+
}
|
|
10630
11275
|
addInitialization(data) {
|
|
10631
11276
|
const timing = parseIsoBmffInitializationTiming(data);
|
|
10632
11277
|
if (!timing) return false;
|
|
@@ -10635,14 +11280,24 @@ var IsoBmffTimingResolver = class {
|
|
|
10635
11280
|
return true;
|
|
10636
11281
|
}
|
|
10637
11282
|
addMovieFragment(mpuSequenceNumber, data, additionalSampleLimit = this.maxPendingSamples) {
|
|
10638
|
-
|
|
10639
|
-
if (!
|
|
11283
|
+
__privateSet(this, _lastMovieFragmentRejection, null);
|
|
11284
|
+
if (!Number.isInteger(additionalSampleLimit) || additionalSampleLimit < 0) {
|
|
11285
|
+
__privateSet(this, _lastMovieFragmentRejection, "invalid");
|
|
11286
|
+
return false;
|
|
11287
|
+
}
|
|
11288
|
+
if (!__privateGet(this, _track) || !isU322(mpuSequenceNumber)) {
|
|
11289
|
+
__privateSet(this, _lastMovieFragmentRejection, "invalid");
|
|
11290
|
+
return false;
|
|
11291
|
+
}
|
|
10640
11292
|
const parsed = parseIsoBmffMovieFragmentTiming(
|
|
10641
11293
|
data,
|
|
10642
11294
|
__privateGet(this, _track),
|
|
10643
11295
|
this.maxPendingSamples
|
|
10644
11296
|
);
|
|
10645
|
-
if (!parsed)
|
|
11297
|
+
if (!parsed) {
|
|
11298
|
+
__privateSet(this, _lastMovieFragmentRejection, "invalid");
|
|
11299
|
+
return false;
|
|
11300
|
+
}
|
|
10646
11301
|
const key = fragmentKey(mpuSequenceNumber, parsed.movieFragmentSequenceNumber);
|
|
10647
11302
|
const existing = __privateGet(this, _fragments).get(key);
|
|
10648
11303
|
if (existing) {
|
|
@@ -10650,8 +11305,14 @@ var IsoBmffTimingResolver = class {
|
|
|
10650
11305
|
(sample, index) => sample !== null && sameSample(sample, parsed.samples[index])
|
|
10651
11306
|
);
|
|
10652
11307
|
}
|
|
10653
|
-
if (parsed.samples.length > additionalSampleLimit)
|
|
10654
|
-
|
|
11308
|
+
if (parsed.samples.length > additionalSampleLimit) {
|
|
11309
|
+
__privateSet(this, _lastMovieFragmentRejection, "budget");
|
|
11310
|
+
return false;
|
|
11311
|
+
}
|
|
11312
|
+
if (__privateGet(this, _pendingSamples) + parsed.samples.length > this.maxPendingSamples) {
|
|
11313
|
+
__privateSet(this, _lastMovieFragmentRejection, "budget");
|
|
11314
|
+
return false;
|
|
11315
|
+
}
|
|
10655
11316
|
__privateGet(this, _fragments).set(key, [...parsed.samples]);
|
|
10656
11317
|
__privateSet(this, _pendingSamples, __privateGet(this, _pendingSamples) + parsed.samples.length);
|
|
10657
11318
|
return true;
|
|
@@ -10666,15 +11327,36 @@ var IsoBmffTimingResolver = class {
|
|
|
10666
11327
|
return samples?.[sampleNumber - 1] ?? null;
|
|
10667
11328
|
}
|
|
10668
11329
|
consumeSample(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, expected) {
|
|
10669
|
-
const
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
const sample = samples?.[
|
|
10673
|
-
if (!
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
11330
|
+
const samples = __privateGet(this, _fragments).get(
|
|
11331
|
+
fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber)
|
|
11332
|
+
);
|
|
11333
|
+
const sample = samples?.[sampleNumber - 1];
|
|
11334
|
+
if (!sample || !sameSample(sample, expected)) return false;
|
|
11335
|
+
return __privateMethod(this, _IsoBmffTimingResolver_instances, clearSlot_fn).call(this, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber);
|
|
11336
|
+
}
|
|
11337
|
+
/**
|
|
11338
|
+
* Free the budget slot of a sample that will never be delivered.
|
|
11339
|
+
*
|
|
11340
|
+
* `consumeSample` is the delivery path and is the only decrement on a
|
|
11341
|
+
* healthy stream. But a sample whose data units never complete is never
|
|
11342
|
+
* delivered, and without this path its slot is held until `reset()` --
|
|
11343
|
+
* making `maxPendingSamples` a one-way ratchet. Once it saturates,
|
|
11344
|
+
* `addMovieFragment` rejects every subsequent moof and the stream is
|
|
11345
|
+
* wedged permanently: the consumer sees media stop with no error, and only
|
|
11346
|
+
* a full track reset clears it.
|
|
11347
|
+
*
|
|
11348
|
+
* The caller is whoever abandoned the sample, so the release is driven by
|
|
11349
|
+
* the abandonment itself rather than by a timer, an LRU, or an age bound --
|
|
11350
|
+
* the slot is freed exactly when its sample provably cannot arrive, which
|
|
11351
|
+
* is a protocol event the combiner already detects and counts.
|
|
11352
|
+
*
|
|
11353
|
+
* Deliberately takes no `expected` timing: the caller is discarding, not
|
|
11354
|
+
* matching, and requiring it would make the release fail exactly when the
|
|
11355
|
+
* timing entry is the thing being reclaimed. Returns false when the slot
|
|
11356
|
+
* was already freed, so a double release cannot drive the count negative.
|
|
11357
|
+
*/
|
|
11358
|
+
releaseSample(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) {
|
|
11359
|
+
return __privateMethod(this, _IsoBmffTimingResolver_instances, clearSlot_fn).call(this, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber);
|
|
10678
11360
|
}
|
|
10679
11361
|
reset() {
|
|
10680
11362
|
__privateSet(this, _track, null);
|
|
@@ -10685,6 +11367,22 @@ var IsoBmffTimingResolver = class {
|
|
|
10685
11367
|
_track = new WeakMap();
|
|
10686
11368
|
_fragments = new WeakMap();
|
|
10687
11369
|
_pendingSamples = new WeakMap();
|
|
11370
|
+
_lastMovieFragmentRejection = new WeakMap();
|
|
11371
|
+
_IsoBmffTimingResolver_instances = new WeakSet();
|
|
11372
|
+
/** Null one slot, decrement the budget, and drop the fragment when empty. */
|
|
11373
|
+
clearSlot_fn = function(mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber) {
|
|
11374
|
+
if (!isU322(mpuSequenceNumber) || !isU322(movieFragmentSequenceNumber) || movieFragmentSequenceNumber === 0 || !isU322(sampleNumber) || sampleNumber === 0) {
|
|
11375
|
+
return false;
|
|
11376
|
+
}
|
|
11377
|
+
const key = fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber);
|
|
11378
|
+
const samples = __privateGet(this, _fragments).get(key);
|
|
11379
|
+
const index = sampleNumber - 1;
|
|
11380
|
+
if (!samples || !samples[index]) return false;
|
|
11381
|
+
samples[index] = null;
|
|
11382
|
+
__privateWrapper(this, _pendingSamples)._--;
|
|
11383
|
+
if (samples.every((value) => value === null)) __privateGet(this, _fragments).delete(key);
|
|
11384
|
+
return true;
|
|
11385
|
+
};
|
|
10688
11386
|
function fragmentKey(mpuSequenceNumber, movieFragmentSequenceNumber) {
|
|
10689
11387
|
return `${mpuSequenceNumber}:${movieFragmentSequenceNumber}`;
|
|
10690
11388
|
}
|
|
@@ -10849,7 +11547,7 @@ function isFmp4Box(data) {
|
|
|
10849
11547
|
const type = String.fromCharCode(data[4], data[5], data[6], data[7]);
|
|
10850
11548
|
return FMPU_BOX_TYPES.has(type);
|
|
10851
11549
|
}
|
|
10852
|
-
var _tracks, _defaultTrack, _reassembler, _sampleCombiners, _sampleCombinerOptions, _retiredSampleCombinerStats, _timingStreams, _maxPendingTimingSamples, _pendingTimingSamples, _sequencers, _reorderWindow, _reassemblyPolicy, _frameCb, _initCb, _sourceSymbolCb, _trackFrameCbs, _reorderStats, _reliableMoqStartupGate, _disposed2, _MmtpContainerParser_instances, streamKey_fn2, sampleCombinerFor_fn, dropSampleCombiner_fn, emitCombinedSample_fn, passReliableMoqStartupGate_fn, timingResolver_fn, dropTimingStream_fn, validateReassemblyPolicy_fn, buildFrameMeta_fn, sequencerFor_fn, flushSequencers_fn, emitFrame_fn2, _MmtpContainerParser_static, scanForKeyframe_fn, scanLengthPrefixedHevcForKeyframe_fn, scanAnnexBForKeyframe_fn;
|
|
11550
|
+
var _tracks, _defaultTrack, _reassembler, _sampleCombiners, _sampleCombinerOptions, _retiredSampleCombinerStats, _timingStreams, _maxPendingTimingSamples, _pendingTimingSamples, _reclaimedTimingSamples, _rejectedMovieFragments, _sequencers, _reorderWindow, _reassemblyPolicy, _frameCb, _initCb, _sourceSymbolCb, _trackFrameCbs, _reorderStats, _reliableMoqStartupGate, _disposed2, _MmtpContainerParser_instances, streamKey_fn2, sampleCombinerFor_fn, releaseTimingSlot_fn, dropSampleCombiner_fn, emitCombinedSample_fn, passReliableMoqStartupGate_fn, timingResolver_fn, dropTimingStream_fn, validateReassemblyPolicy_fn, buildFrameMeta_fn, sequencerFor_fn, flushSequencers_fn, emitFrame_fn2, _MmtpContainerParser_static, scanForKeyframe_fn, scanLengthPrefixedHevcForKeyframe_fn, scanAnnexBForKeyframe_fn;
|
|
10853
11551
|
var _MmtpContainerParser = class _MmtpContainerParser {
|
|
10854
11552
|
constructor(config) {
|
|
10855
11553
|
__privateAdd(this, _MmtpContainerParser_instances);
|
|
@@ -10882,6 +11580,8 @@ var _MmtpContainerParser = class _MmtpContainerParser {
|
|
|
10882
11580
|
__privateAdd(this, _timingStreams, /* @__PURE__ */ new Map());
|
|
10883
11581
|
__privateAdd(this, _maxPendingTimingSamples);
|
|
10884
11582
|
__privateAdd(this, _pendingTimingSamples, 0);
|
|
11583
|
+
__privateAdd(this, _reclaimedTimingSamples, 0);
|
|
11584
|
+
__privateAdd(this, _rejectedMovieFragments, 0);
|
|
10885
11585
|
__privateAdd(this, _sequencers, /* @__PURE__ */ new Map());
|
|
10886
11586
|
__privateAdd(this, _reorderWindow, 0);
|
|
10887
11587
|
__privateAdd(this, _reassemblyPolicy, null);
|
|
@@ -10995,6 +11695,23 @@ var _MmtpContainerParser = class _MmtpContainerParser {
|
|
|
10995
11695
|
}
|
|
10996
11696
|
return total;
|
|
10997
11697
|
}
|
|
11698
|
+
/**
|
|
11699
|
+
* Typed view of the ISO-BMFF timing-sample budget.
|
|
11700
|
+
*
|
|
11701
|
+
* This is the acceptance signal for the budget ratchet: `pending` walking
|
|
11702
|
+
* to `capacity` while `rejectedMovieFragments` climbs is the stream wedging,
|
|
11703
|
+
* and it is observable here rather than only as media silently stopping.
|
|
11704
|
+
* Per repo policy this is a producer-owned counter -- never infer budget
|
|
11705
|
+
* state by parsing logs.
|
|
11706
|
+
*/
|
|
11707
|
+
get timingBudgetStats() {
|
|
11708
|
+
return {
|
|
11709
|
+
pending: __privateGet(this, _pendingTimingSamples),
|
|
11710
|
+
capacity: __privateGet(this, _maxPendingTimingSamples),
|
|
11711
|
+
reclaimed: __privateGet(this, _reclaimedTimingSamples),
|
|
11712
|
+
rejectedMovieFragments: __privateGet(this, _rejectedMovieFragments)
|
|
11713
|
+
};
|
|
11714
|
+
}
|
|
10998
11715
|
get reorderStats() {
|
|
10999
11716
|
let bufferedFrames = 0;
|
|
11000
11717
|
for (const { sequencer } of __privateGet(this, _sequencers).values()) bufferedFrames += sequencer.buffered;
|
|
@@ -11007,6 +11724,20 @@ var _MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11007
11724
|
}
|
|
11008
11725
|
return streams;
|
|
11009
11726
|
}
|
|
11727
|
+
/**
|
|
11728
|
+
* Reclaim samples abandoned by the sample combiner's protocol deadline.
|
|
11729
|
+
*
|
|
11730
|
+
* This is deliberately driven by the parser's existing packet/lifecycle
|
|
11731
|
+
* cleanup path rather than by another timer. The reassembler timeout is the
|
|
11732
|
+
* same catalog/FEC-derived deadline that bounds each constituent MFU.
|
|
11733
|
+
*/
|
|
11734
|
+
cleanupTimeouts() {
|
|
11735
|
+
const timeoutMs = __privateGet(this, _reassembler).timeoutMs;
|
|
11736
|
+
if (timeoutMs === null) return;
|
|
11737
|
+
for (const combiner of __privateGet(this, _sampleCombiners).values()) {
|
|
11738
|
+
combiner.cleanupTimeouts(timeoutMs);
|
|
11739
|
+
}
|
|
11740
|
+
}
|
|
11010
11741
|
/**
|
|
11011
11742
|
* Phase 1 of two-phase parsing. Returns structured header info for FEC feeding.
|
|
11012
11743
|
* Call this before feed() when you need to extract FEC payload ID and feed
|
|
@@ -11037,6 +11768,7 @@ var _MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11037
11768
|
feed(packet, options) {
|
|
11038
11769
|
var _a;
|
|
11039
11770
|
if (__privateGet(this, _disposed2)) return null;
|
|
11771
|
+
this.cleanupTimeouts();
|
|
11040
11772
|
const info = this.parseHeader(packet, options);
|
|
11041
11773
|
if (!info) return null;
|
|
11042
11774
|
if (info.packetType !== PACKET_TYPE_MPU2) {
|
|
@@ -11064,13 +11796,15 @@ var _MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11064
11796
|
const streamKey = __privateMethod(this, _MmtpContainerParser_instances, streamKey_fn2).call(this, info.packetId, options?.catalogTrackName);
|
|
11065
11797
|
if (fragmentType === 0) {
|
|
11066
11798
|
if (mpu.fragmentationIndicator !== 0 || mpu.fragmentCounter !== 0) return null;
|
|
11799
|
+
const normalizedInit = normalizeMmtpInitSegment(data);
|
|
11800
|
+
if (!normalizedInit) return null;
|
|
11067
11801
|
const resolver2 = __privateMethod(this, _MmtpContainerParser_instances, timingResolver_fn).call(this, streamKey, info.packetId, true);
|
|
11068
|
-
if (!resolver2?.addInitialization(
|
|
11069
|
-
(_a = __privateGet(this, _initCb)) == null ? void 0 : _a.call(this,
|
|
11802
|
+
if (!resolver2?.addInitialization(normalizedInit)) return null;
|
|
11803
|
+
(_a = __privateGet(this, _initCb)) == null ? void 0 : _a.call(this, normalizedInit, info.packetId, options?.catalogTrackName);
|
|
11070
11804
|
return {
|
|
11071
11805
|
kind: "init",
|
|
11072
11806
|
packet: info,
|
|
11073
|
-
data,
|
|
11807
|
+
data: normalizedInit,
|
|
11074
11808
|
mpuSequenceNumber: mpu.mpuSequenceNumber,
|
|
11075
11809
|
fragmentationIndicator: mpu.fragmentationIndicator,
|
|
11076
11810
|
fragmentCounter: mpu.fragmentCounter
|
|
@@ -11082,7 +11816,12 @@ var _MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11082
11816
|
if (!resolver2) return null;
|
|
11083
11817
|
const pendingBefore = resolver2.pendingSampleCount;
|
|
11084
11818
|
const remaining = __privateGet(this, _maxPendingTimingSamples) - __privateGet(this, _pendingTimingSamples);
|
|
11085
|
-
if (!resolver2.addMovieFragment(mpu.mpuSequenceNumber, data, remaining))
|
|
11819
|
+
if (!resolver2.addMovieFragment(mpu.mpuSequenceNumber, data, remaining)) {
|
|
11820
|
+
if (resolver2.lastMovieFragmentRejection === "budget") {
|
|
11821
|
+
__privateWrapper(this, _rejectedMovieFragments)._++;
|
|
11822
|
+
}
|
|
11823
|
+
return null;
|
|
11824
|
+
}
|
|
11086
11825
|
__privateSet(this, _pendingTimingSamples, __privateGet(this, _pendingTimingSamples) + (resolver2.pendingSampleCount - pendingBefore));
|
|
11087
11826
|
return {
|
|
11088
11827
|
kind: "fragment",
|
|
@@ -11144,7 +11883,7 @@ var _MmtpContainerParser = class _MmtpContainerParser {
|
|
|
11144
11883
|
packetId: info.packetId
|
|
11145
11884
|
});
|
|
11146
11885
|
} catch (error) {
|
|
11147
|
-
if (!(error
|
|
11886
|
+
if (!isMfuReassemblyError(error)) throw error;
|
|
11148
11887
|
console.warn(
|
|
11149
11888
|
`[MmtpContainerParser] rejected MFU fragment: code=${error.code} mpu=${mpu.mpuSequenceNumber} fi=${mpu.fragmentationIndicator} counter=${mpu.fragmentCounter}`
|
|
11150
11889
|
);
|
|
@@ -11368,6 +12107,8 @@ _retiredSampleCombinerStats = new WeakMap();
|
|
|
11368
12107
|
_timingStreams = new WeakMap();
|
|
11369
12108
|
_maxPendingTimingSamples = new WeakMap();
|
|
11370
12109
|
_pendingTimingSamples = new WeakMap();
|
|
12110
|
+
_reclaimedTimingSamples = new WeakMap();
|
|
12111
|
+
_rejectedMovieFragments = new WeakMap();
|
|
11371
12112
|
_sequencers = new WeakMap();
|
|
11372
12113
|
_reorderWindow = new WeakMap();
|
|
11373
12114
|
_reassemblyPolicy = new WeakMap();
|
|
@@ -11385,11 +12126,42 @@ streamKey_fn2 = function(packetId, catalogTrackName) {
|
|
|
11385
12126
|
sampleCombinerFor_fn = function(streamKey) {
|
|
11386
12127
|
let combiner = __privateGet(this, _sampleCombiners).get(streamKey);
|
|
11387
12128
|
if (combiner === void 0) {
|
|
11388
|
-
combiner = new SampleCombiner(
|
|
12129
|
+
combiner = new SampleCombiner({
|
|
12130
|
+
...__privateGet(this, _sampleCombinerOptions),
|
|
12131
|
+
onAbandon: (sample) => __privateMethod(this, _MmtpContainerParser_instances, releaseTimingSlot_fn).call(this, streamKey, sample)
|
|
12132
|
+
});
|
|
11389
12133
|
__privateGet(this, _sampleCombiners).set(streamKey, combiner);
|
|
11390
12134
|
}
|
|
11391
12135
|
return combiner;
|
|
11392
12136
|
};
|
|
12137
|
+
/**
|
|
12138
|
+
* Return the timing budget slot of a sample the combiner abandoned.
|
|
12139
|
+
*
|
|
12140
|
+
* Without this, `#pendingTimingSamples` is a one-way ratchet: it is charged
|
|
12141
|
+
* for every sample a moof declares but credited only when that sample is
|
|
12142
|
+
* delivered, so each sample lost to eviction or timeout permanently burns a
|
|
12143
|
+
* slot. At saturation `addMovieFragment` rejects every subsequent moof and
|
|
12144
|
+
* every FT=2 data unit is dropped -- media stops with no error surfaced and
|
|
12145
|
+
* nothing short of a track reset recovers.
|
|
12146
|
+
*
|
|
12147
|
+
* The parser total is decremented only when the resolver reports it
|
|
12148
|
+
* actually freed a slot, so a resolver already dropped by a track reset, or
|
|
12149
|
+
* a sample released twice, cannot drive the accounting negative.
|
|
12150
|
+
*/
|
|
12151
|
+
releaseTimingSlot_fn = function(streamKey, sample) {
|
|
12152
|
+
const resolver = __privateGet(this, _timingStreams).get(streamKey)?.resolver;
|
|
12153
|
+
if (!resolver) return;
|
|
12154
|
+
if (!resolver.releaseSample(
|
|
12155
|
+
sample.mpuSequenceNumber,
|
|
12156
|
+
sample.movieFragmentSequenceNumber,
|
|
12157
|
+
sample.sampleNumber
|
|
12158
|
+
)) return;
|
|
12159
|
+
__privateWrapper(this, _pendingTimingSamples)._--;
|
|
12160
|
+
__privateWrapper(this, _reclaimedTimingSamples)._++;
|
|
12161
|
+
if (__privateGet(this, _pendingTimingSamples) < 0) {
|
|
12162
|
+
throw new Error("[MmtpContainerParser] pending timing sample accounting underflow");
|
|
12163
|
+
}
|
|
12164
|
+
};
|
|
11393
12165
|
/** Drop one stream's combiner, folding its counters into the retained
|
|
11394
12166
|
* totals so a reset cannot make the exposed stats run backwards. */
|
|
11395
12167
|
dropSampleCombiner_fn = function(streamKey) {
|
|
@@ -11473,7 +12245,7 @@ validateReassemblyPolicy_fn = function(policy = __privateGet(this, _reassemblyPo
|
|
|
11473
12245
|
mmtpFrameReorderWindowFrames(policy, reorderWindow);
|
|
11474
12246
|
};
|
|
11475
12247
|
buildFrameMeta_fn = function(assembled, packetId, seq, timing, recovered = false) {
|
|
11476
|
-
const { data,
|
|
12248
|
+
const { data, rapFlag, priority } = assembled;
|
|
11477
12249
|
if (isFmp4Box(data)) return null;
|
|
11478
12250
|
const presentationTimestamp = mediaTicksToMicroseconds(
|
|
11479
12251
|
timing.presentationTime,
|
|
@@ -11512,7 +12284,7 @@ buildFrameMeta_fn = function(assembled, packetId, seq, timing, recovered = false
|
|
|
11512
12284
|
...assembled.packetSequenceNumbers === void 0 ? {} : { packetSequenceNumbers: assembled.packetSequenceNumbers },
|
|
11513
12285
|
...assembled.repairedPacketSequenceNumbers === void 0 ? {} : { repairedPacketSequenceNumbers: assembled.repairedPacketSequenceNumbers },
|
|
11514
12286
|
...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName },
|
|
11515
|
-
mmtpTimestamp,
|
|
12287
|
+
...assembled.mmtpTimestamp === void 0 ? {} : { mmtpTimestamp: assembled.mmtpTimestamp },
|
|
11516
12288
|
mpuSequenceNumber: assembled.mpuSequenceNumber,
|
|
11517
12289
|
...assembled.movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber },
|
|
11518
12290
|
...assembled.sampleNumber === void 0 ? {} : { sampleNumber: assembled.sampleNumber },
|
|
@@ -11527,7 +12299,7 @@ buildFrameMeta_fn = function(assembled, packetId, seq, timing, recovered = false
|
|
|
11527
12299
|
}
|
|
11528
12300
|
return meta;
|
|
11529
12301
|
};
|
|
11530
|
-
sequencerFor_fn = function(packetId,
|
|
12302
|
+
sequencerFor_fn = function(packetId, _trackType2, catalogTrackName) {
|
|
11531
12303
|
const windowSize = __privateGet(this, _reassemblyPolicy) ? mmtpFrameReorderWindowFrames(__privateGet(this, _reassemblyPolicy), __privateGet(this, _reorderWindow)) : __privateGet(this, _reorderWindow);
|
|
11532
12304
|
if (windowSize <= 0) return null;
|
|
11533
12305
|
const streamKey = __privateMethod(this, _MmtpContainerParser_instances, streamKey_fn2).call(this, packetId, catalogTrackName);
|
|
@@ -11843,6 +12615,7 @@ var MmtpRouter = class {
|
|
|
11843
12615
|
const isVideo = __privateMethod(this, _MmtpRouter_instances, classifyPacketId_fn).call(this, packetId) === true;
|
|
11844
12616
|
const frameMeta = {
|
|
11845
12617
|
timestamp: meta.timestamp,
|
|
12618
|
+
...meta.mmtpTimestamp === void 0 ? {} : { mmtpTimestamp: meta.mmtpTimestamp },
|
|
11846
12619
|
keyframe: isVideo ? meta.keyframe : true,
|
|
11847
12620
|
packetId,
|
|
11848
12621
|
packetSequenceNumber: meta.packetSequenceNumber ?? __privateGet(this, _currentSeqNum),
|
|
@@ -11850,6 +12623,8 @@ var MmtpRouter = class {
|
|
|
11850
12623
|
...meta.packetSequenceNumbers === void 0 ? {} : { packetSequenceNumbers: meta.packetSequenceNumbers },
|
|
11851
12624
|
...meta.repairedPacketSequenceNumbers === void 0 ? {} : { repairedPacketSequenceNumbers: meta.repairedPacketSequenceNumbers },
|
|
11852
12625
|
...meta.mpuSequenceNumber === void 0 ? {} : { mpuSequenceNumber: meta.mpuSequenceNumber },
|
|
12626
|
+
...meta.movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber: meta.movieFragmentSequenceNumber },
|
|
12627
|
+
...meta.sampleNumber === void 0 ? {} : { sampleNumber: meta.sampleNumber },
|
|
11853
12628
|
...meta.catalogTrackName === void 0 ? {} : { catalogTrackName: meta.catalogTrackName }
|
|
11854
12629
|
};
|
|
11855
12630
|
if (meta.source) frameMeta.source = meta.source;
|
|
@@ -11873,7 +12648,10 @@ var MmtpRouter = class {
|
|
|
11873
12648
|
frameMeta.packetSequenceNumbers,
|
|
11874
12649
|
frameMeta.repairedPacketSequenceNumbers,
|
|
11875
12650
|
frameMeta.authoredTiming,
|
|
11876
|
-
frameMeta.deliveryProvenance
|
|
12651
|
+
frameMeta.deliveryProvenance,
|
|
12652
|
+
frameMeta.mmtpTimestamp,
|
|
12653
|
+
frameMeta.movieFragmentSequenceNumber,
|
|
12654
|
+
frameMeta.sampleNumber
|
|
11877
12655
|
);
|
|
11878
12656
|
} else {
|
|
11879
12657
|
assembler.addBypassFrame(
|
|
@@ -11886,7 +12664,10 @@ var MmtpRouter = class {
|
|
|
11886
12664
|
frameMeta.packetSequenceNumbers,
|
|
11887
12665
|
frameMeta.repairedPacketSequenceNumbers,
|
|
11888
12666
|
frameMeta.authoredTiming,
|
|
11889
|
-
frameMeta.deliveryProvenance
|
|
12667
|
+
frameMeta.deliveryProvenance,
|
|
12668
|
+
frameMeta.mmtpTimestamp,
|
|
12669
|
+
frameMeta.movieFragmentSequenceNumber,
|
|
12670
|
+
frameMeta.sampleNumber
|
|
11890
12671
|
);
|
|
11891
12672
|
}
|
|
11892
12673
|
}
|
|
@@ -12955,7 +13736,10 @@ isCatalogTrackUnambiguous_fn = function(packetId, catalogTrackName) {
|
|
|
12955
13736
|
};
|
|
12956
13737
|
resolveRecoveredTiming_fn = function(packetId, mpuSequenceNumber, movieFragmentSequenceNumber, sampleNumber, mmtpTimestamp, catalogTrackName) {
|
|
12957
13738
|
if (movieFragmentSequenceNumber === void 0 || sampleNumber === void 0) {
|
|
12958
|
-
return {
|
|
13739
|
+
return {
|
|
13740
|
+
timestamp: mmtpTimestampToMicros(mmtpTimestamp),
|
|
13741
|
+
mmtpTimestamp
|
|
13742
|
+
};
|
|
12959
13743
|
}
|
|
12960
13744
|
const authoredTiming = __privateGet(this, _parser).consumeAuthoredTimingFor(
|
|
12961
13745
|
packetId,
|
|
@@ -12967,7 +13751,7 @@ resolveRecoveredTiming_fn = function(packetId, mpuSequenceNumber, movieFragmentS
|
|
|
12967
13751
|
if (authoredTiming === null) return null;
|
|
12968
13752
|
const scaled = scaleAuthoredTiming(authoredTiming, 1e6);
|
|
12969
13753
|
if (scaled === null) return null;
|
|
12970
|
-
return { authoredTiming, timestamp: scaled.timestamp };
|
|
13754
|
+
return { authoredTiming, timestamp: scaled.timestamp, mmtpTimestamp };
|
|
12971
13755
|
};
|
|
12972
13756
|
packetSeqKey_fn = function(packetId, packetSequenceNumber, catalogTrackName) {
|
|
12973
13757
|
return JSON.stringify([packetId, catalogTrackName ?? null, packetSequenceNumber]);
|
|
@@ -13266,12 +14050,15 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13266
14050
|
frames.push({
|
|
13267
14051
|
data: sym.fragment.data,
|
|
13268
14052
|
timestamp: recoveredTiming.timestamp,
|
|
14053
|
+
mmtpTimestamp: recoveredTiming.mmtpTimestamp,
|
|
13269
14054
|
keyframe,
|
|
13270
14055
|
authoredTiming: recoveredTiming.authoredTiming,
|
|
13271
14056
|
packetSequenceNumber: sym.packetSequenceNumber,
|
|
13272
14057
|
packetSequenceNumbers: [sym.packetSequenceNumber],
|
|
13273
14058
|
repairedPacketSequenceNumbers: [sym.packetSequenceNumber],
|
|
13274
14059
|
mpuSequenceNumber: sym.fragment.mpuSequenceNumber,
|
|
14060
|
+
...sym.fragment.movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber },
|
|
14061
|
+
...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
|
|
13275
14062
|
...catalogTrackName === void 0 ? {} : { catalogTrackName },
|
|
13276
14063
|
// A single-packet MFU needs the block verdict just as much as a
|
|
13277
14064
|
// reassembled one. The multi-fragment branch below gets it via the
|
|
@@ -13311,7 +14098,7 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13311
14098
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
13312
14099
|
});
|
|
13313
14100
|
} catch (error) {
|
|
13314
|
-
if (!(error
|
|
14101
|
+
if (!isMfuReassemblyError(error)) throw error;
|
|
13315
14102
|
console.warn(
|
|
13316
14103
|
`[MmtpRouter] dropped rejected recovered MFU fragment: block=${blockId} packetId=${ownerPacketId} code=${error.code}`
|
|
13317
14104
|
);
|
|
@@ -13329,6 +14116,7 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13329
14116
|
frames.push({
|
|
13330
14117
|
data: assembled.data,
|
|
13331
14118
|
timestamp: recoveredTiming.timestamp,
|
|
14119
|
+
mmtpTimestamp: recoveredTiming.mmtpTimestamp,
|
|
13332
14120
|
keyframe,
|
|
13333
14121
|
authoredTiming: recoveredTiming.authoredTiming,
|
|
13334
14122
|
// The singular delivery identity is the fragment that completed
|
|
@@ -13339,6 +14127,8 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13339
14127
|
repairedPacketSequenceNumbers: assembled.repairedPacketSequenceNumbers
|
|
13340
14128
|
},
|
|
13341
14129
|
mpuSequenceNumber: assembled.mpuSequenceNumber,
|
|
14130
|
+
...assembled.movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber },
|
|
14131
|
+
...assembled.sampleNumber === void 0 ? {} : { sampleNumber: assembled.sampleNumber },
|
|
13342
14132
|
...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName },
|
|
13343
14133
|
deliveryProvenance: assembled.deliveryProvenance
|
|
13344
14134
|
});
|
|
@@ -13384,6 +14174,7 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13384
14174
|
if (!recoveredTiming) continue;
|
|
13385
14175
|
__privateGet(this, _parser).routeFrame(d, {
|
|
13386
14176
|
timestamp: recoveredTiming.timestamp,
|
|
14177
|
+
mmtpTimestamp: recoveredTiming.mmtpTimestamp,
|
|
13387
14178
|
authoredTiming: recoveredTiming.authoredTiming,
|
|
13388
14179
|
keyframe,
|
|
13389
14180
|
packetId: ownerPacketId,
|
|
@@ -13392,6 +14183,8 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13392
14183
|
packetSequenceNumbers: [sym.packetSequenceNumber],
|
|
13393
14184
|
repairedPacketSequenceNumbers: [sym.packetSequenceNumber],
|
|
13394
14185
|
mpuSequenceNumber: sym.fragment.mpuSequenceNumber,
|
|
14186
|
+
...sym.fragment.movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber },
|
|
14187
|
+
...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
|
|
13395
14188
|
recovered: true,
|
|
13396
14189
|
deliveryProvenance,
|
|
13397
14190
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
@@ -13426,7 +14219,7 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13426
14219
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
13427
14220
|
});
|
|
13428
14221
|
} catch (error) {
|
|
13429
|
-
if (!(error
|
|
14222
|
+
if (!isMfuReassemblyError(error)) throw error;
|
|
13430
14223
|
console.warn(
|
|
13431
14224
|
`[MmtpRouter] dropped rejected recovered MFU fragment: block=${blockId} packetId=${ownerPacketId} code=${error.code}`
|
|
13432
14225
|
);
|
|
@@ -13443,6 +14236,7 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13443
14236
|
if (!recoveredTiming) continue;
|
|
13444
14237
|
__privateGet(this, _parser).routeFrame(assembled.data, {
|
|
13445
14238
|
timestamp: recoveredTiming.timestamp,
|
|
14239
|
+
mmtpTimestamp: recoveredTiming.mmtpTimestamp,
|
|
13446
14240
|
authoredTiming: recoveredTiming.authoredTiming,
|
|
13447
14241
|
keyframe,
|
|
13448
14242
|
packetId: ownerPacketId,
|
|
@@ -13453,6 +14247,8 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13453
14247
|
repairedPacketSequenceNumbers: assembled.repairedPacketSequenceNumbers
|
|
13454
14248
|
},
|
|
13455
14249
|
mpuSequenceNumber: assembled.mpuSequenceNumber,
|
|
14250
|
+
...assembled.movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber: assembled.movieFragmentSequenceNumber },
|
|
14251
|
+
...assembled.sampleNumber === void 0 ? {} : { sampleNumber: assembled.sampleNumber },
|
|
13456
14252
|
recovered: true,
|
|
13457
14253
|
deliveryProvenance,
|
|
13458
14254
|
...assembled.catalogTrackName === void 0 ? {} : { catalogTrackName: assembled.catalogTrackName }
|
|
@@ -13470,6 +14266,7 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13470
14266
|
if (recoveredTiming === null) continue;
|
|
13471
14267
|
const meta = {
|
|
13472
14268
|
timestamp: recoveredTiming.timestamp,
|
|
14269
|
+
mmtpTimestamp: recoveredTiming.mmtpTimestamp,
|
|
13473
14270
|
authoredTiming: recoveredTiming.authoredTiming,
|
|
13474
14271
|
keyframe: true,
|
|
13475
14272
|
packetId: ownerPacketId,
|
|
@@ -13477,6 +14274,8 @@ routeProcessedFecSymbols_fn = function(blockId, result, ownerPacketId, catalogTr
|
|
|
13477
14274
|
packetSequenceNumbers: [sym.packetSequenceNumber],
|
|
13478
14275
|
repairedPacketSequenceNumbers: [sym.packetSequenceNumber],
|
|
13479
14276
|
mpuSequenceNumber: sym.fragment.mpuSequenceNumber,
|
|
14277
|
+
...sym.fragment.movieFragmentSequenceNumber === void 0 ? {} : { movieFragmentSequenceNumber: sym.fragment.movieFragmentSequenceNumber },
|
|
14278
|
+
...sym.fragment.sampleNumber === void 0 ? {} : { sampleNumber: sym.fragment.sampleNumber },
|
|
13480
14279
|
recovered: true,
|
|
13481
14280
|
...catalogTrackName === void 0 ? {} : { catalogTrackName }
|
|
13482
14281
|
};
|
|
@@ -13684,7 +14483,7 @@ refreshStats_fn = function() {
|
|
|
13684
14483
|
this.stats.reassembledMFUs = asNumber(stats.reassembled_mfus);
|
|
13685
14484
|
this.stats.rejectedFragments = __privateGet(this, _rejectedFragments);
|
|
13686
14485
|
this.stats.timedOutMFUs = asNumber(stats.timed_out_mfus);
|
|
13687
|
-
this.stats.evictedMemoryLimit = asNumber(stats.evicted_memory_limit);
|
|
14486
|
+
this.stats.evictedMemoryLimit = asNumber(stats.evicted_memory_limit) + asNumber(stats.evicted_data_units ?? 0);
|
|
13688
14487
|
this.stats.outOfOrderFragments = asNumber(stats.out_of_order_fragments);
|
|
13689
14488
|
stats.free?.();
|
|
13690
14489
|
};
|
|
@@ -14065,12 +14864,22 @@ function createDefaultRepairLifecycleEvent(detail, options) {
|
|
|
14065
14864
|
function isRecord(value) {
|
|
14066
14865
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
14067
14866
|
}
|
|
14068
|
-
function
|
|
14867
|
+
function isRepairStreamIdentity(value) {
|
|
14069
14868
|
if (!isRecord(value)) return false;
|
|
14070
14869
|
const packetId = value["packetId"];
|
|
14071
14870
|
const catalogTrackName = value["catalogTrackName"];
|
|
14871
|
+
return Number.isSafeInteger(packetId) && packetId >= 0 && (catalogTrackName === null || typeof catalogTrackName === "string" && catalogTrackName.length > 0);
|
|
14872
|
+
}
|
|
14873
|
+
function isRepairBlockIdentity(value) {
|
|
14874
|
+
if (!isRecord(value)) return false;
|
|
14072
14875
|
const sbn = value["sbn"];
|
|
14073
|
-
return
|
|
14876
|
+
return isRepairStreamIdentity(value) && Number.isSafeInteger(sbn) && sbn >= 0;
|
|
14877
|
+
}
|
|
14878
|
+
function isDecoderUnavailableReason(value) {
|
|
14879
|
+
return value === "cleared" || value === "disposed";
|
|
14880
|
+
}
|
|
14881
|
+
function isFecFallbackDecoder(value) {
|
|
14882
|
+
return value === "sync" || value === "none";
|
|
14074
14883
|
}
|
|
14075
14884
|
function isRepairLifecycleEvent(value) {
|
|
14076
14885
|
if (!isRecord(value)) return false;
|
|
@@ -14078,7 +14887,11 @@ function isRepairLifecycleEvent(value) {
|
|
|
14078
14887
|
const type = value["type"];
|
|
14079
14888
|
const block = value["block"];
|
|
14080
14889
|
const symbols = value["symbols"];
|
|
14081
|
-
if (version !== 1
|
|
14890
|
+
if (version !== 1) return false;
|
|
14891
|
+
if (type === "decoder-unavailable") {
|
|
14892
|
+
return isRepairStreamIdentity(value["stream"]) && isDecoderUnavailableReason(value["reason"]) && isFecFallbackDecoder(value["fallbackDecoder"]) && block === void 0 && symbols === void 0;
|
|
14893
|
+
}
|
|
14894
|
+
if (type !== "requested" && type !== "recovered" && type !== "failed" || !isRepairBlockIdentity(block) || value["stream"] !== void 0 || value["reason"] !== void 0 || value["fallbackDecoder"] !== void 0) {
|
|
14082
14895
|
return false;
|
|
14083
14896
|
}
|
|
14084
14897
|
if (type === "requested") return symbols === void 0;
|
|
@@ -14097,22 +14910,25 @@ function forwardRepairLifecycleEvents(source, target, options = {}) {
|
|
|
14097
14910
|
target.dispatchEvent(forwardedEvent);
|
|
14098
14911
|
});
|
|
14099
14912
|
}
|
|
14100
|
-
function
|
|
14913
|
+
function repairStreamIdentity(packetId, catalogTrackName) {
|
|
14101
14914
|
if (!Number.isSafeInteger(packetId) || packetId < 0) {
|
|
14102
14915
|
throw new TypeError("packetId must be a non-negative safe integer");
|
|
14103
14916
|
}
|
|
14104
|
-
if (!Number.isSafeInteger(sbn) || sbn < 0) {
|
|
14105
|
-
throw new TypeError("sbn must be a non-negative safe integer");
|
|
14106
|
-
}
|
|
14107
14917
|
if (catalogTrackName !== void 0 && (typeof catalogTrackName !== "string" || catalogTrackName.length === 0)) {
|
|
14108
14918
|
throw new TypeError("catalogTrackName must be a non-empty string when provided");
|
|
14109
14919
|
}
|
|
14110
14920
|
return {
|
|
14111
14921
|
packetId,
|
|
14112
|
-
catalogTrackName: catalogTrackName ?? null
|
|
14113
|
-
sbn
|
|
14922
|
+
catalogTrackName: catalogTrackName ?? null
|
|
14114
14923
|
};
|
|
14115
14924
|
}
|
|
14925
|
+
function repairBlockIdentity(packetId, sbn, catalogTrackName) {
|
|
14926
|
+
const stream = repairStreamIdentity(packetId, catalogTrackName);
|
|
14927
|
+
if (!Number.isSafeInteger(sbn) || sbn < 0) {
|
|
14928
|
+
throw new TypeError("sbn must be a non-negative safe integer");
|
|
14929
|
+
}
|
|
14930
|
+
return { ...stream, sbn };
|
|
14931
|
+
}
|
|
14116
14932
|
function assertPositiveGeometry(label, value) {
|
|
14117
14933
|
if (value === void 0) return;
|
|
14118
14934
|
if (!Number.isFinite(value) || value <= 0) {
|
|
@@ -14282,6 +15098,15 @@ var MulticastManager = class {
|
|
|
14282
15098
|
}
|
|
14283
15099
|
});
|
|
14284
15100
|
};
|
|
15101
|
+
__privateGet(this, _fec).onDecoderUnavailable = (event) => {
|
|
15102
|
+
__privateMethod(this, _MulticastManager_instances, emitRepairLifecycle_fn).call(this, {
|
|
15103
|
+
version: 1,
|
|
15104
|
+
type: "decoder-unavailable",
|
|
15105
|
+
stream: repairStreamIdentity(event.packetId, event.catalogTrackName),
|
|
15106
|
+
reason: event.reason,
|
|
15107
|
+
fallbackDecoder: event.fallbackDecoder
|
|
15108
|
+
});
|
|
15109
|
+
};
|
|
14285
15110
|
}
|
|
14286
15111
|
// ---------------------------------------------------------------------------
|
|
14287
15112
|
// Decomposed component accessors (for new code)
|
|
@@ -14336,6 +15161,17 @@ var MulticastManager = class {
|
|
|
14336
15161
|
__privateMethod(this, _MulticastManager_instances, configureTrackMapper_fn).call(this, packetId, config);
|
|
14337
15162
|
__privateGet(this, _router).setPacketIdType(packetId, trackType);
|
|
14338
15163
|
}
|
|
15164
|
+
/**
|
|
15165
|
+
* Stop routing a stream's symbols to its Worker decoder. Returns false when
|
|
15166
|
+
* none is registered; on success one `decoder-unavailable` repair lifecycle
|
|
15167
|
+
* event with reason 'cleared' reaches every observer, carrying whether a
|
|
15168
|
+
* synchronous WASM decoder still serves the stream. The packet mapping and
|
|
15169
|
+
* track type are untouched: the track still exists, it just has no Worker
|
|
15170
|
+
* decoder. Call this before disposing the client.
|
|
15171
|
+
*/
|
|
15172
|
+
clearFecWorker(packetId, catalogTrackName) {
|
|
15173
|
+
return __privateGet(this, _fec).clearFecWorker(packetId, catalogTrackName);
|
|
15174
|
+
}
|
|
14339
15175
|
/** Register callback for Worker-level FecTrackStats updates. */
|
|
14340
15176
|
setOnFecStatsUpdate(cb) {
|
|
14341
15177
|
__privateGet(this, _fec).onFecStatsUpdate = cb;
|
|
@@ -14381,8 +15217,9 @@ var MulticastManager = class {
|
|
|
14381
15217
|
repairSymbols: config.fecRepairSymbols,
|
|
14382
15218
|
symbolSize: config.fecSymbolSize,
|
|
14383
15219
|
mediaUnitsPerSecond: config.fps,
|
|
14384
|
-
bandwidthBps: config.bandwidth
|
|
14385
|
-
|
|
15220
|
+
bandwidthBps: config.bandwidth,
|
|
15221
|
+
jitterBufferMs: config.fecReorderToleranceMs
|
|
15222
|
+
}) : void 0;
|
|
14386
15223
|
const fecConfig = {
|
|
14387
15224
|
k: config.fecK,
|
|
14388
15225
|
symbolSize: config.fecSymbolSize,
|
|
@@ -14419,8 +15256,9 @@ var MulticastManager = class {
|
|
|
14419
15256
|
repairSymbols: config.fecRepairSymbols,
|
|
14420
15257
|
symbolSize: config.fecSymbolSize,
|
|
14421
15258
|
mediaUnitsPerSecond: config.fps,
|
|
14422
|
-
bandwidthBps: config.bandwidth
|
|
14423
|
-
|
|
15259
|
+
bandwidthBps: config.bandwidth,
|
|
15260
|
+
jitterBufferMs: config.fecReorderToleranceMs
|
|
15261
|
+
}) : void 0;
|
|
14424
15262
|
__privateGet(this, _fec).configure({
|
|
14425
15263
|
k: config.fecK,
|
|
14426
15264
|
symbolSize: config.fecSymbolSize,
|
|
@@ -14937,11 +15775,13 @@ applyStreamGlobalFecTiming_fn = function() {
|
|
|
14937
15775
|
};
|
|
14938
15776
|
_onTrackTypeDiscovered = new WeakMap();
|
|
14939
15777
|
emitRepairLifecycle_fn = function(event) {
|
|
14940
|
-
const immutableEvent = Object.freeze(
|
|
14941
|
-
...event,
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
15778
|
+
const immutableEvent = Object.freeze(
|
|
15779
|
+
event.type === "decoder-unavailable" ? { ...event, stream: Object.freeze({ ...event.stream }) } : {
|
|
15780
|
+
...event,
|
|
15781
|
+
block: Object.freeze({ ...event.block }),
|
|
15782
|
+
...event.type === "requested" ? {} : { symbols: Object.freeze({ ...event.symbols }) }
|
|
15783
|
+
}
|
|
15784
|
+
);
|
|
14945
15785
|
console.info(
|
|
14946
15786
|
`${REPAIR_LIFECYCLE_LOG_PREFIX}${JSON.stringify(immutableEvent)}`
|
|
14947
15787
|
);
|
|
@@ -14965,7 +15805,8 @@ async function setupFecWorkerForTrack(opts) {
|
|
|
14965
15805
|
sourceSymbols: opts.fecK,
|
|
14966
15806
|
repairSymbols: opts.fecRepairCount,
|
|
14967
15807
|
symbolSize: opts.fecSymbolSize,
|
|
14968
|
-
mediaUnitsPerSecond: 1e3 / opts.mediaUnitDurationMs
|
|
15808
|
+
mediaUnitsPerSecond: 1e3 / opts.mediaUnitDurationMs,
|
|
15809
|
+
jitterBufferMs: opts.fecReorderToleranceMs
|
|
14969
15810
|
});
|
|
14970
15811
|
const origin = typeof location !== "undefined" && location.origin ? location.origin : "http://localhost";
|
|
14971
15812
|
const workerUrl = new URL(opts.assetUrls.fecWorker, origin);
|
|
@@ -15011,15 +15852,23 @@ async function setupFecWorkerForTrack(opts) {
|
|
|
15011
15852
|
relayBlockSigEnabled: false
|
|
15012
15853
|
};
|
|
15013
15854
|
client.configure(fecTrackConfig, rqWasm, altaKeyBuffer, altaWasmModule);
|
|
15014
|
-
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15855
|
+
try {
|
|
15856
|
+
opts.multicastManager.setFecWorker(opts.packetId, client, opts.trackType, {
|
|
15857
|
+
k: opts.fecK,
|
|
15858
|
+
symbolSize: opts.fecSymbolSize,
|
|
15859
|
+
interleaveDepthMs: opts.fecInterleaveMs,
|
|
15860
|
+
// FEC timing is expressed in media units. For audio this is the access-unit
|
|
15861
|
+
// cadence (for example 1024/48kHz), not a video-only framerate field.
|
|
15862
|
+
fps: 1e3 / opts.mediaUnitDurationMs,
|
|
15863
|
+
timeoutMs: deliveryWindowMs
|
|
15864
|
+
}, opts.trackName);
|
|
15865
|
+
} catch (e) {
|
|
15866
|
+
try {
|
|
15867
|
+
client.dispose();
|
|
15868
|
+
} catch {
|
|
15869
|
+
}
|
|
15870
|
+
throw e;
|
|
15871
|
+
}
|
|
15023
15872
|
let disposed = false;
|
|
15024
15873
|
return {
|
|
15025
15874
|
client,
|
|
@@ -15027,6 +15876,11 @@ async function setupFecWorkerForTrack(opts) {
|
|
|
15027
15876
|
dispose: () => {
|
|
15028
15877
|
if (disposed) return;
|
|
15029
15878
|
disposed = true;
|
|
15879
|
+
if (!opts.multicastManager.clearFecWorker(opts.packetId, opts.trackName)) {
|
|
15880
|
+
console.warn(
|
|
15881
|
+
`[fec-worker-setup] packetId=${opts.packetId} track=${opts.trackName} Worker was not registered when disposed`
|
|
15882
|
+
);
|
|
15883
|
+
}
|
|
15030
15884
|
try {
|
|
15031
15885
|
client.dispose();
|
|
15032
15886
|
} catch {
|
|
@@ -15910,7 +16764,7 @@ _readIndex = new WeakMap();
|
|
|
15910
16764
|
_stalled = new WeakMap();
|
|
15911
16765
|
|
|
15912
16766
|
// src/mmtp-decode-pipeline.ts
|
|
15913
|
-
var _parser2, _fecClient, _frameCb2, _initCb2, _needsKeyframe, _codec, _gateDropped, _gateEmitted, _fedObjects, _warnedNoParser, _feedOutcomes, _MmtpDecodePipeline_instances, gatedEmit_fn;
|
|
16767
|
+
var _parser2, _fecClient, _frameCb2, _initCb2, _needsKeyframe, _codec, _trackType, _gateDropped, _gateEmitted, _fedObjects, _warnedNoParser, _feedOutcomes, _MmtpDecodePipeline_instances, tag_fn, gatedEmit_fn;
|
|
15914
16768
|
var MmtpDecodePipeline = class {
|
|
15915
16769
|
constructor() {
|
|
15916
16770
|
__privateAdd(this, _MmtpDecodePipeline_instances);
|
|
@@ -15919,7 +16773,11 @@ var MmtpDecodePipeline = class {
|
|
|
15919
16773
|
__privateAdd(this, _frameCb2, null);
|
|
15920
16774
|
__privateAdd(this, _initCb2, null);
|
|
15921
16775
|
__privateAdd(this, _needsKeyframe, false);
|
|
15922
|
-
__privateAdd(this, _codec,
|
|
16776
|
+
__privateAdd(this, _codec, null);
|
|
16777
|
+
/** Set together with #codec at configure(). Also the diagnostic label: a
|
|
16778
|
+
* player builds one pipeline per track, and codec alone does not say which
|
|
16779
|
+
* role a line belongs to at a glance. */
|
|
16780
|
+
__privateAdd(this, _trackType, null);
|
|
15923
16781
|
/** Diagnostic counters. The keyframe gate drops silently, so a parser that
|
|
15924
16782
|
* never marks a frame as a keyframe stalls the whole pipeline with no
|
|
15925
16783
|
* output anywhere: frames are fed, frames are parsed, and nothing is
|
|
@@ -15948,6 +16806,7 @@ var MmtpDecodePipeline = class {
|
|
|
15948
16806
|
__privateSet(this, _warnedNoParser, false);
|
|
15949
16807
|
__privateSet(this, _codec, config.codec);
|
|
15950
16808
|
const trackType = isVideoCodec(config.codec) ? "video" : "audio";
|
|
16809
|
+
__privateSet(this, _trackType, trackType);
|
|
15951
16810
|
const trackInfo = {
|
|
15952
16811
|
type: trackType,
|
|
15953
16812
|
codec: config.codec,
|
|
@@ -15991,6 +16850,24 @@ var MmtpDecodePipeline = class {
|
|
|
15991
16850
|
get reorderStats() {
|
|
15992
16851
|
return __privateGet(this, _parser2)?.reorderStats ?? null;
|
|
15993
16852
|
}
|
|
16853
|
+
/**
|
|
16854
|
+
* Feed tallies for this track. A fresh snapshot per call, not a live view.
|
|
16855
|
+
*
|
|
16856
|
+
* Unlike reorderStats this never returns null: the counters belong to the
|
|
16857
|
+
* pipeline rather than the parser, so they survive dispose(). That is
|
|
16858
|
+
* deliberate — a wedged pipeline is usually read after teardown, which is
|
|
16859
|
+
* exactly when the parser is already gone.
|
|
16860
|
+
*/
|
|
16861
|
+
get feedStats() {
|
|
16862
|
+
return {
|
|
16863
|
+
trackType: __privateGet(this, _trackType),
|
|
16864
|
+
codec: __privateGet(this, _codec),
|
|
16865
|
+
fedObjects: __privateGet(this, _fedObjects),
|
|
16866
|
+
emitted: __privateGet(this, _gateEmitted),
|
|
16867
|
+
gateDropped: __privateGet(this, _gateDropped),
|
|
16868
|
+
outcomes: Object.fromEntries(__privateGet(this, _feedOutcomes))
|
|
16869
|
+
};
|
|
16870
|
+
}
|
|
15994
16871
|
/**
|
|
15995
16872
|
* Feed a raw MoQ object payload (one MMTP packet per MoQ object).
|
|
15996
16873
|
* Optionally feeds source symbols to FEC client before parsing.
|
|
@@ -15999,33 +16876,37 @@ var MmtpDecodePipeline = class {
|
|
|
15999
16876
|
if (!__privateGet(this, _parser2)) {
|
|
16000
16877
|
if (!__privateGet(this, _warnedNoParser)) {
|
|
16001
16878
|
__privateSet(this, _warnedNoParser, true);
|
|
16002
|
-
console.warn(
|
|
16879
|
+
console.warn(`[mmtp-pipeline] feedMoqObject called with NO PARSER configured ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} \u2014 packet dropped`);
|
|
16003
16880
|
}
|
|
16004
16881
|
return;
|
|
16005
16882
|
}
|
|
16006
16883
|
__privateSet(this, _fedObjects, __privateGet(this, _fedObjects) + 1);
|
|
16007
16884
|
if (__privateGet(this, _fedObjects) === 1) {
|
|
16008
|
-
console.log(`[mmtp-pipeline] first object fed
|
|
16885
|
+
console.log(`[mmtp-pipeline] first object fed ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} bytes=${packet.length}`);
|
|
16009
16886
|
}
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
if (
|
|
16013
|
-
__privateGet(this,
|
|
16887
|
+
let outcomeKind = "threw";
|
|
16888
|
+
try {
|
|
16889
|
+
if (__privateGet(this, _fecClient)?.addSourceSymbol) {
|
|
16890
|
+
const info = __privateGet(this, _parser2).parseHeader(packet);
|
|
16891
|
+
if (info?.fecPayloadId) {
|
|
16892
|
+
__privateGet(this, _fecClient).addSourceSymbol(info.fecPayloadId.ssId, packet);
|
|
16893
|
+
}
|
|
16894
|
+
}
|
|
16895
|
+
const record = __privateGet(this, _parser2).feed(packet);
|
|
16896
|
+
outcomeKind = record?.kind ?? "null(parse-or-extract-failed)";
|
|
16897
|
+
} finally {
|
|
16898
|
+
const seen = (__privateGet(this, _feedOutcomes).get(outcomeKind) ?? 0) + 1;
|
|
16899
|
+
__privateGet(this, _feedOutcomes).set(outcomeKind, seen);
|
|
16900
|
+
if (seen === 1) {
|
|
16901
|
+
console.log(
|
|
16902
|
+
`[mmtp-pipeline] feed outcome FIRST '${outcomeKind}' ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} at object #${__privateGet(this, _fedObjects)} bytes=${packet.length}`
|
|
16903
|
+
);
|
|
16014
16904
|
}
|
|
16015
|
-
}
|
|
16016
|
-
const record = __privateGet(this, _parser2).feed(packet);
|
|
16017
|
-
const kind = record?.kind ?? "null(parse-or-extract-failed)";
|
|
16018
|
-
const seen = (__privateGet(this, _feedOutcomes).get(kind) ?? 0) + 1;
|
|
16019
|
-
__privateGet(this, _feedOutcomes).set(kind, seen);
|
|
16020
|
-
if (seen === 1) {
|
|
16021
|
-
console.log(
|
|
16022
|
-
`[mmtp-pipeline] feed outcome FIRST '${kind}' at object #${__privateGet(this, _fedObjects)} bytes=${packet.length}`
|
|
16023
|
-
);
|
|
16024
16905
|
}
|
|
16025
16906
|
if (__privateGet(this, _fedObjects) % 200 === 0) {
|
|
16026
16907
|
const hist = [...__privateGet(this, _feedOutcomes)].map(([k, v]) => `${k}=${v}`).join(" ");
|
|
16027
16908
|
console.log(
|
|
16028
|
-
`[mmtp-pipeline] feed histogram @${__privateGet(this, _fedObjects)}: ${hist} | emitted=${__privateGet(this, _gateEmitted)} gateDropped=${__privateGet(this, _gateDropped)}`
|
|
16909
|
+
`[mmtp-pipeline] feed histogram ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} @${__privateGet(this, _fedObjects)}: ${hist} | emitted=${__privateGet(this, _gateEmitted)} gateDropped=${__privateGet(this, _gateDropped)}`
|
|
16029
16910
|
);
|
|
16030
16911
|
}
|
|
16031
16912
|
}
|
|
@@ -16042,9 +16923,13 @@ var MmtpDecodePipeline = class {
|
|
|
16042
16923
|
feedAssembledFrame(data, meta) {
|
|
16043
16924
|
const frameMeta = {
|
|
16044
16925
|
timestamp: meta.timestamp,
|
|
16926
|
+
...meta.mmtpTimestamp === void 0 ? {} : { mmtpTimestamp: meta.mmtpTimestamp },
|
|
16045
16927
|
keyframe: meta.keyframe,
|
|
16046
16928
|
packetId: meta.packetId,
|
|
16047
|
-
|
|
16929
|
+
// Same derivation as configure(), read from the field it set rather than
|
|
16930
|
+
// recomputed. Pre-configure the parser is null and routeFrame below is a
|
|
16931
|
+
// no-op, so the fallback is unreachable in practice.
|
|
16932
|
+
trackType: __privateGet(this, _trackType) ?? "audio",
|
|
16048
16933
|
packetSequenceNumber: meta.packetSequenceNumber
|
|
16049
16934
|
};
|
|
16050
16935
|
if (meta.sourceDiscontinuity) frameMeta.sourceDiscontinuity = true;
|
|
@@ -16070,7 +16955,7 @@ var MmtpDecodePipeline = class {
|
|
|
16070
16955
|
const droppedFrames = (__privateGet(this, _parser2)?.reorderStats.droppedOnLiveDispose ?? droppedBefore) - droppedBefore;
|
|
16071
16956
|
if (droppedFrames > 0) {
|
|
16072
16957
|
console.warn(
|
|
16073
|
-
`[MmtpDecodePipeline] live dispose dropped ${droppedFrames} buffered frame(s); finite inputs must use dispose({ flush: true })`
|
|
16958
|
+
`[MmtpDecodePipeline] live dispose ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} dropped ${droppedFrames} buffered frame(s); finite inputs must use dispose({ flush: true })`
|
|
16074
16959
|
);
|
|
16075
16960
|
}
|
|
16076
16961
|
__privateSet(this, _parser2, null);
|
|
@@ -16085,12 +16970,19 @@ _frameCb2 = new WeakMap();
|
|
|
16085
16970
|
_initCb2 = new WeakMap();
|
|
16086
16971
|
_needsKeyframe = new WeakMap();
|
|
16087
16972
|
_codec = new WeakMap();
|
|
16973
|
+
_trackType = new WeakMap();
|
|
16088
16974
|
_gateDropped = new WeakMap();
|
|
16089
16975
|
_gateEmitted = new WeakMap();
|
|
16090
16976
|
_fedObjects = new WeakMap();
|
|
16091
16977
|
_warnedNoParser = new WeakMap();
|
|
16092
16978
|
_feedOutcomes = new WeakMap();
|
|
16093
16979
|
_MmtpDecodePipeline_instances = new WeakSet();
|
|
16980
|
+
/** Identity prefix for diagnostics: one pipeline per track means two
|
|
16981
|
+
* instances write to the same console, and unlabelled lines interleave into
|
|
16982
|
+
* a single unreadable stream. */
|
|
16983
|
+
tag_fn = function() {
|
|
16984
|
+
return `track=${__privateGet(this, _trackType) ?? "unconfigured"} codec=${__privateGet(this, _codec) ?? "none"}`;
|
|
16985
|
+
};
|
|
16094
16986
|
gatedEmit_fn = function(data, meta) {
|
|
16095
16987
|
var _a;
|
|
16096
16988
|
let sourceDiscontinuity = false;
|
|
@@ -16099,7 +16991,7 @@ gatedEmit_fn = function(data, meta) {
|
|
|
16099
16991
|
__privateSet(this, _gateDropped, __privateGet(this, _gateDropped) + 1);
|
|
16100
16992
|
if (__privateGet(this, _gateDropped) <= 3 || __privateGet(this, _gateDropped) % 200 === 0) {
|
|
16101
16993
|
console.warn(
|
|
16102
|
-
`[mmtp-pipeline] keyframe gate DROP #${__privateGet(this, _gateDropped)}
|
|
16994
|
+
`[mmtp-pipeline] keyframe gate DROP #${__privateGet(this, _gateDropped)} ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} bytes=${data.length} fed=${__privateGet(this, _fedObjects)} emitted=${__privateGet(this, _gateEmitted)}`
|
|
16103
16995
|
);
|
|
16104
16996
|
}
|
|
16105
16997
|
return;
|
|
@@ -16107,12 +16999,12 @@ gatedEmit_fn = function(data, meta) {
|
|
|
16107
16999
|
__privateSet(this, _needsKeyframe, false);
|
|
16108
17000
|
sourceDiscontinuity = true;
|
|
16109
17001
|
console.log(
|
|
16110
|
-
`[mmtp-pipeline] keyframe gate OPENED
|
|
17002
|
+
`[mmtp-pipeline] keyframe gate OPENED ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} after ${__privateGet(this, _gateDropped)} drops, fed=${__privateGet(this, _fedObjects)}`
|
|
16111
17003
|
);
|
|
16112
17004
|
}
|
|
16113
17005
|
__privateSet(this, _gateEmitted, __privateGet(this, _gateEmitted) + 1);
|
|
16114
17006
|
if (__privateGet(this, _gateEmitted) === 1) {
|
|
16115
|
-
console.log(`[mmtp-pipeline] first frame emitted
|
|
17007
|
+
console.log(`[mmtp-pipeline] first frame emitted ${__privateMethod(this, _MmtpDecodePipeline_instances, tag_fn).call(this)} bytes=${data.length}`);
|
|
16116
17008
|
}
|
|
16117
17009
|
const outMeta = sourceDiscontinuity ? { ...meta, sourceDiscontinuity: true } : meta;
|
|
16118
17010
|
(_a = __privateGet(this, _frameCb2)) == null ? void 0 : _a.call(this, data, outMeta);
|
|
@@ -16786,6 +17678,24 @@ emit_fn = function(params) {
|
|
|
16786
17678
|
};
|
|
16787
17679
|
|
|
16788
17680
|
// src/catalog-fec-params.ts
|
|
17681
|
+
var MAX_SAFE_TICKS_PER_HZ = Number.MAX_SAFE_INTEGER / 1e3;
|
|
17682
|
+
function timescaleHz(value) {
|
|
17683
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
17684
|
+
}
|
|
17685
|
+
function catalogGroupDurationMs(rendition) {
|
|
17686
|
+
if (!rendition) return void 0;
|
|
17687
|
+
const ticks = numericField(rendition.groupDurationTicks);
|
|
17688
|
+
const ms = numericField(rendition.groupDurationMs);
|
|
17689
|
+
const timescale = timescaleHz(rendition.container?.kind === "mmtp" ? rendition.container.timescale : void 0) ?? timescaleHz(rendition.timescale);
|
|
17690
|
+
if (ticks !== void 0 && timescale !== void 0 && Number.isSafeInteger(ticks) && ticks <= timescale * MAX_SAFE_TICKS_PER_HZ) {
|
|
17691
|
+
return resolveGroupDurationMs({ groupDurationTicks: ticks }, timescale);
|
|
17692
|
+
}
|
|
17693
|
+
return ms;
|
|
17694
|
+
}
|
|
17695
|
+
function resolveCatalogTrackGroupDurationMs(catalog, track) {
|
|
17696
|
+
const renditions = track.type === "audio" ? catalog?.audio?.renditions : catalog?.video?.renditions;
|
|
17697
|
+
return catalogGroupDurationMs(renditions?.[track.name]);
|
|
17698
|
+
}
|
|
16789
17699
|
var NO_FEC = {
|
|
16790
17700
|
fecK: 0,
|
|
16791
17701
|
fecRepairCount: 0,
|
|
@@ -16868,8 +17778,10 @@ function catalogAmtNetworkSource(networkSource) {
|
|
|
16868
17778
|
// src/repair-fetch.ts
|
|
16869
17779
|
function wireRepairFetch(mc, opts) {
|
|
16870
17780
|
const inFlight = /* @__PURE__ */ new Map();
|
|
17781
|
+
const warnedNoTarget = /* @__PURE__ */ new Set();
|
|
16871
17782
|
let disposed = false;
|
|
16872
17783
|
const requestKey = (sbn, packetId, catalogTrackName) => JSON.stringify([packetId, catalogTrackName ?? null, sbn]);
|
|
17784
|
+
const noTargetKey = (packetId, catalogTrackName) => JSON.stringify([packetId, catalogTrackName ?? null]);
|
|
16873
17785
|
const removeResetObserver = mc.addTrackResetObserver(
|
|
16874
17786
|
(packetId, catalogTrackName) => {
|
|
16875
17787
|
for (const [key, request] of inFlight) {
|
|
@@ -16878,6 +17790,7 @@ function wireRepairFetch(mc, opts) {
|
|
|
16878
17790
|
inFlight.delete(key);
|
|
16879
17791
|
}
|
|
16880
17792
|
}
|
|
17793
|
+
warnedNoTarget.delete(noTargetKey(packetId, catalogTrackName));
|
|
16881
17794
|
}
|
|
16882
17795
|
);
|
|
16883
17796
|
const timeoutFor = (sbn, packetId, catalogTrackName) => opts.timeoutMsForPacket?.(packetId) ?? mc.fecManager.repairFetchTimeoutMsForBlock(
|
|
@@ -16916,6 +17829,13 @@ function wireRepairFetch(mc, opts) {
|
|
|
16916
17829
|
}
|
|
16917
17830
|
const target = opts.resolveTarget(packetId, catalogTrackName);
|
|
16918
17831
|
if (!target) {
|
|
17832
|
+
const warnKey = noTargetKey(packetId, catalogTrackName);
|
|
17833
|
+
if (!warnedNoTarget.has(warnKey)) {
|
|
17834
|
+
warnedNoTarget.add(warnKey);
|
|
17835
|
+
console.warn(
|
|
17836
|
+
`[RepairFetch] no repair target for pid=${packetId} sbn=${sbn}${catalogTrackName === void 0 ? "" : ` track=${catalogTrackName}`}; releasing without a fetch`
|
|
17837
|
+
);
|
|
17838
|
+
}
|
|
16919
17839
|
clearRepair(sbn, packetId, catalogTrackName);
|
|
16920
17840
|
return;
|
|
16921
17841
|
}
|
|
@@ -16992,6 +17912,7 @@ export {
|
|
|
16992
17912
|
FecStarvationDetector,
|
|
16993
17913
|
KEYFRAME_LOST_TIMEOUT_SLACK,
|
|
16994
17914
|
LOCHeaderExtensionId,
|
|
17915
|
+
MAX_TAKEOVER_DEADLINE_MS,
|
|
16995
17916
|
MFUReassembler,
|
|
16996
17917
|
MediaRouter,
|
|
16997
17918
|
MfuReassemblyError,
|
|
@@ -17022,6 +17943,7 @@ export {
|
|
|
17022
17943
|
buildVideoInitSegment,
|
|
17023
17944
|
catalogAmtNetworkSource,
|
|
17024
17945
|
catalogFecInterleaveMs,
|
|
17946
|
+
catalogGroupDurationMs,
|
|
17025
17947
|
classifyH264Keyframe,
|
|
17026
17948
|
classifyPacket,
|
|
17027
17949
|
codecStringFromAvcC,
|
|
@@ -17068,6 +17990,7 @@ export {
|
|
|
17068
17990
|
isFecCatalogConfig,
|
|
17069
17991
|
isFmp4InitSegment,
|
|
17070
17992
|
isHevcCodec,
|
|
17993
|
+
isMfuReassemblyError,
|
|
17071
17994
|
isMulticastCatalogConfig,
|
|
17072
17995
|
isMulticastEndpoint,
|
|
17073
17996
|
isMulticastTrackObject,
|
|
@@ -17083,6 +18006,7 @@ export {
|
|
|
17083
18006
|
multicastFromConfig,
|
|
17084
18007
|
multicastFromEnv,
|
|
17085
18008
|
normalizeH264SampleToAvcc,
|
|
18009
|
+
normalizeMmtpInitSegment,
|
|
17086
18010
|
parseAacFrameSamples,
|
|
17087
18011
|
parseAacSampleRate,
|
|
17088
18012
|
parseOpusFrameSamples,
|
|
@@ -17090,6 +18014,7 @@ export {
|
|
|
17090
18014
|
reliableMoqMfuReassembly,
|
|
17091
18015
|
removeEmulationPrevention,
|
|
17092
18016
|
resolveCatalogTrackFec,
|
|
18017
|
+
resolveCatalogTrackGroupDurationMs,
|
|
17093
18018
|
resolveGlobalMmtWasmBindings,
|
|
17094
18019
|
resolveGroupDurationMs,
|
|
17095
18020
|
resolveGroupDurationTicks,
|