@rivmux/runtime-worker 0.4.0 → 0.5.0
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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -3
- package/dist/index.js.map +1 -1
- package/dist/rivmux-runtime-worker.d.ts +14 -23
- package/dist/rivmux-runtime-worker.d.ts.map +1 -1
- package/dist/rivmux-runtime-worker.js +679 -324
- package/dist/rivmux-runtime-worker.js.map +1 -1
- package/dist/rivmux-transmux-core.wasm +0 -0
- package/package.json +3 -3
|
@@ -141,6 +141,111 @@ const PLAYBACK_RATE_RESTORE_THRESHOLD_SECONDS = .1;
|
|
|
141
141
|
const SEEK_COOLDOWN_MS = 1e3;
|
|
142
142
|
const SEEK_MIN_DELTA_SECONDS = .1;
|
|
143
143
|
//#endregion
|
|
144
|
+
//#region src/runtime/lifecycle.ts
|
|
145
|
+
function raceLifecycleOperation(operation, signal, onLateValue) {
|
|
146
|
+
if (signal.aborted) {
|
|
147
|
+
operation.then(onLateValue, () => void 0);
|
|
148
|
+
return Promise.resolve({ cancelled: true });
|
|
149
|
+
}
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
let settled = false;
|
|
152
|
+
const onAbort = () => {
|
|
153
|
+
if (settled) return;
|
|
154
|
+
settled = true;
|
|
155
|
+
signal.removeEventListener("abort", onAbort);
|
|
156
|
+
operation.then(onLateValue, () => void 0);
|
|
157
|
+
resolve({ cancelled: true });
|
|
158
|
+
};
|
|
159
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
160
|
+
operation.then((value) => {
|
|
161
|
+
if (settled) return;
|
|
162
|
+
settled = true;
|
|
163
|
+
signal.removeEventListener("abort", onAbort);
|
|
164
|
+
resolve({
|
|
165
|
+
cancelled: false,
|
|
166
|
+
value
|
|
167
|
+
});
|
|
168
|
+
}, (error) => {
|
|
169
|
+
if (settled) return;
|
|
170
|
+
settled = true;
|
|
171
|
+
signal.removeEventListener("abort", onAbort);
|
|
172
|
+
reject(error);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/runtime/options.ts
|
|
178
|
+
function mergeOptions(current, updates) {
|
|
179
|
+
return {
|
|
180
|
+
playback: {
|
|
181
|
+
...current.playback,
|
|
182
|
+
...updates.playback
|
|
183
|
+
},
|
|
184
|
+
latency: {
|
|
185
|
+
...current.latency,
|
|
186
|
+
...updates.latency
|
|
187
|
+
},
|
|
188
|
+
network: {
|
|
189
|
+
...current.network,
|
|
190
|
+
...updates.network,
|
|
191
|
+
headers: {
|
|
192
|
+
...current.network.headers,
|
|
193
|
+
...updates.network?.headers
|
|
194
|
+
},
|
|
195
|
+
retry: {
|
|
196
|
+
...current.network.retry,
|
|
197
|
+
...updates.network?.retry
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
runtime: {
|
|
201
|
+
...current.runtime,
|
|
202
|
+
...updates.runtime
|
|
203
|
+
},
|
|
204
|
+
diagnostics: {
|
|
205
|
+
...current.diagnostics,
|
|
206
|
+
...updates.diagnostics
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/runtime/stats.ts
|
|
212
|
+
function updateAppendQueueHighWaterMark(current, mseStats) {
|
|
213
|
+
return {
|
|
214
|
+
length: Math.max(current.length, mseStats.appendQueueLength),
|
|
215
|
+
bytes: Math.max(current.bytes, mseStats.appendQueueBytes)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function getNetworkIdleMs(stats, nowMs) {
|
|
219
|
+
const markerMs = stats?.lastChunkAtMs ?? stats?.startedAtMs;
|
|
220
|
+
if (markerMs === void 0) return;
|
|
221
|
+
return Math.max(nowMs - markerMs, 0);
|
|
222
|
+
}
|
|
223
|
+
function createPlayerStats(snapshot) {
|
|
224
|
+
const { loaderStats, mseStats, latencyMetrics } = snapshot;
|
|
225
|
+
return {
|
|
226
|
+
bytesReceived: loaderStats?.bytesReceived ?? 0,
|
|
227
|
+
currentNetworkSpeed: loaderStats?.currentNetworkSpeed ?? 0,
|
|
228
|
+
networkIdleMs: getNetworkIdleMs(loaderStats, snapshot.nowMs),
|
|
229
|
+
outputBytes: snapshot.outputBytes,
|
|
230
|
+
appendQueueLength: mseStats.appendQueueLength,
|
|
231
|
+
appendQueueBytes: mseStats.appendQueueBytes,
|
|
232
|
+
appendQueueMaxLength: snapshot.appendQueueMaxLength,
|
|
233
|
+
appendQueueMaxBytes: snapshot.appendQueueMaxBytes,
|
|
234
|
+
loaderPaused: snapshot.loaderPaused,
|
|
235
|
+
sourceBufferUpdating: mseStats.sourceBufferUpdating,
|
|
236
|
+
sourceBufferCount: mseStats.sourceBufferCount,
|
|
237
|
+
bufferedStart: latencyMetrics.bufferedStart ?? mseStats.bufferedStart,
|
|
238
|
+
bufferedEnd: latencyMetrics.bufferedEnd ?? mseStats.bufferedEnd,
|
|
239
|
+
bufferedDuration: latencyMetrics.bufferedDuration ?? mseStats.bufferedDuration,
|
|
240
|
+
bufferedRangeCount: mseStats.bufferedRangeCount,
|
|
241
|
+
currentTime: latencyMetrics.currentTime,
|
|
242
|
+
liveLatency: latencyMetrics.liveLatency,
|
|
243
|
+
playbackRate: latencyMetrics.playbackRate,
|
|
244
|
+
readyState: latencyMetrics.readyState,
|
|
245
|
+
droppedFrames: latencyMetrics.droppedFrames
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
144
249
|
//#region src/loader/retry-policy.ts
|
|
145
250
|
function createRetryPolicy(input) {
|
|
146
251
|
return {
|
|
@@ -349,25 +454,25 @@ function createMp4VideoMime(codec) {
|
|
|
349
454
|
function createMp4AudioMime(codec) {
|
|
350
455
|
return `audio/mp4; codecs="${codec}"`;
|
|
351
456
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
mimeType
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}];
|
|
457
|
+
var MseUnsupportedMimeError = class extends Error {
|
|
458
|
+
mimeType;
|
|
459
|
+
constructor(mimeType) {
|
|
460
|
+
super(`MSE does not support ${mimeType}.`);
|
|
461
|
+
this.name = "MseUnsupportedMimeError";
|
|
462
|
+
this.mimeType = mimeType;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
361
465
|
function isMseSupported(mimeType) {
|
|
362
466
|
return typeof MediaSource !== "undefined" && typeof MediaSource.isTypeSupported === "function" && MediaSource.isTypeSupported(mimeType);
|
|
363
467
|
}
|
|
364
|
-
function
|
|
468
|
+
function assertMseRuntimeSupport() {
|
|
365
469
|
if (typeof MediaSource === "undefined") throw new Error("MediaSource is not available in this worker.");
|
|
366
470
|
if (MediaSource.canConstructInDedicatedWorker !== true) throw new Error("MediaSource cannot be constructed in this dedicated worker.");
|
|
367
|
-
if (
|
|
471
|
+
if (typeof MediaSource.isTypeSupported !== "function") throw new Error("MediaSource.isTypeSupported is not available in this worker.");
|
|
368
472
|
}
|
|
369
|
-
function
|
|
370
|
-
|
|
473
|
+
function assertMseSupport(mimeType) {
|
|
474
|
+
assertMseRuntimeSupport();
|
|
475
|
+
if (!isMseSupported(mimeType)) throw new MseUnsupportedMimeError(mimeType);
|
|
371
476
|
}
|
|
372
477
|
//#endregion
|
|
373
478
|
//#region src/mse/source-buffer-queue.ts
|
|
@@ -518,7 +623,7 @@ var MseController = class {
|
|
|
518
623
|
return Array.from(this.queues.values()).reduce((total, queue) => total + queue.bufferedRanges.length, 0);
|
|
519
624
|
}
|
|
520
625
|
async createMediaSourceHandle() {
|
|
521
|
-
|
|
626
|
+
assertMseRuntimeSupport();
|
|
522
627
|
const mediaSource = new MediaSource();
|
|
523
628
|
this.mediaSource = mediaSource;
|
|
524
629
|
const handle = mediaSource.handle;
|
|
@@ -533,7 +638,7 @@ var MseController = class {
|
|
|
533
638
|
await this.ensureQueue(segment.track, mimeType).append(toAppendBuffer(segment.bytes));
|
|
534
639
|
}
|
|
535
640
|
async appendMediaSegment(segment) {
|
|
536
|
-
const queue = this.queues.get(segment.track);
|
|
641
|
+
const queue = this.queues.get(segment.track) ?? this.queues.get("muxed");
|
|
537
642
|
if (queue === void 0) throw new Error(`Cannot append ${segment.track} media segment before init segment.`);
|
|
538
643
|
await queue.append(toAppendBuffer(segment.bytes));
|
|
539
644
|
const mediaSource = this.requireMediaSource();
|
|
@@ -677,10 +782,8 @@ function normalizeCoreEvent(value) {
|
|
|
677
782
|
type: "mediaSegment",
|
|
678
783
|
data: normalizeMediaSegment(data)
|
|
679
784
|
};
|
|
680
|
-
case "
|
|
681
|
-
case "
|
|
682
|
-
case "videoSample":
|
|
683
|
-
case "audioSample":
|
|
785
|
+
case "trackConfig":
|
|
786
|
+
case "sample":
|
|
684
787
|
case "metadata":
|
|
685
788
|
case "discontinuity": return {
|
|
686
789
|
type: value.type,
|
|
@@ -840,16 +943,9 @@ var TransmuxCore = class {
|
|
|
840
943
|
* @returns {any}
|
|
841
944
|
*/
|
|
842
945
|
flush() {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
|
|
847
|
-
var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
|
|
848
|
-
if (getDataViewMemory0().getInt32(retptr + 8, true)) throw takeObject(r1);
|
|
849
|
-
return takeObject(r0);
|
|
850
|
-
} finally {
|
|
851
|
-
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
852
|
-
}
|
|
946
|
+
const ret = wasm.transmuxcore_flush(this.__wbg_ptr);
|
|
947
|
+
if (ret[2]) throw takeFromExternrefTable0(ret[1]);
|
|
948
|
+
return takeFromExternrefTable0(ret[0]);
|
|
853
949
|
}
|
|
854
950
|
constructor() {
|
|
855
951
|
const ret = wasm.transmuxcore_new();
|
|
@@ -862,18 +958,11 @@ var TransmuxCore = class {
|
|
|
862
958
|
* @returns {any}
|
|
863
959
|
*/
|
|
864
960
|
pushChunk(data) {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
|
|
871
|
-
var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
|
|
872
|
-
if (getDataViewMemory0().getInt32(retptr + 8, true)) throw takeObject(r1);
|
|
873
|
-
return takeObject(r0);
|
|
874
|
-
} finally {
|
|
875
|
-
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
876
|
-
}
|
|
961
|
+
const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
|
962
|
+
const len0 = WASM_VECTOR_LEN;
|
|
963
|
+
const ret = wasm.transmuxcore_pushChunk(this.__wbg_ptr, ptr0, len0);
|
|
964
|
+
if (ret[2]) throw takeFromExternrefTable0(ret[1]);
|
|
965
|
+
return takeFromExternrefTable0(ret[0]);
|
|
877
966
|
}
|
|
878
967
|
reset() {
|
|
879
968
|
wasm.transmuxcore_reset(this.__wbg_ptr);
|
|
@@ -886,10 +975,13 @@ function __wbg_get_imports() {
|
|
|
886
975
|
"./rivmux_transmux_core_bg.js": {
|
|
887
976
|
__proto__: null,
|
|
888
977
|
__wbg_Error_fdd633d4bb5dd76a: function(arg0, arg1) {
|
|
889
|
-
return
|
|
978
|
+
return Error(getStringFromWasm0(arg0, arg1));
|
|
979
|
+
},
|
|
980
|
+
__wbg_Number_c4bdf66bb78f7977: function(arg0) {
|
|
981
|
+
return Number(arg0);
|
|
890
982
|
},
|
|
891
983
|
__wbg_String_8564e559799eccda: function(arg0, arg1) {
|
|
892
|
-
const ptr1 = passStringToWasm0(String(
|
|
984
|
+
const ptr1 = passStringToWasm0(String(arg1), wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
893
985
|
const len1 = WASM_VECTOR_LEN;
|
|
894
986
|
getDataViewMemory0().setInt32(arg0 + 4, len1, true);
|
|
895
987
|
getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
|
|
@@ -898,31 +990,34 @@ function __wbg_get_imports() {
|
|
|
898
990
|
throw new Error(getStringFromWasm0(arg0, arg1));
|
|
899
991
|
},
|
|
900
992
|
__wbg_new_2e117a478906f062: function() {
|
|
901
|
-
return
|
|
993
|
+
return /* @__PURE__ */ new Object();
|
|
902
994
|
},
|
|
903
995
|
__wbg_new_36e147a8ced3c6e0: function() {
|
|
904
|
-
return
|
|
996
|
+
return new Array();
|
|
905
997
|
},
|
|
906
998
|
__wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
|
|
907
|
-
|
|
999
|
+
arg0[arg1] = arg2;
|
|
908
1000
|
},
|
|
909
1001
|
__wbg_set_dc601f4a69da0bc2: function(arg0, arg1, arg2) {
|
|
910
|
-
|
|
1002
|
+
arg0[arg1 >>> 0] = arg2;
|
|
911
1003
|
},
|
|
912
1004
|
__wbindgen_cast_0000000000000001: function(arg0) {
|
|
913
|
-
return
|
|
1005
|
+
return arg0;
|
|
914
1006
|
},
|
|
915
1007
|
__wbindgen_cast_0000000000000002: function(arg0) {
|
|
916
|
-
return
|
|
1008
|
+
return arg0;
|
|
917
1009
|
},
|
|
918
1010
|
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
|
919
|
-
return
|
|
1011
|
+
return getStringFromWasm0(arg0, arg1);
|
|
920
1012
|
},
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
1013
|
+
__wbindgen_init_externref_table: function() {
|
|
1014
|
+
const table = wasm.__wbindgen_externrefs;
|
|
1015
|
+
const offset = table.grow(4);
|
|
1016
|
+
table.set(0, void 0);
|
|
1017
|
+
table.set(offset + 0, void 0);
|
|
1018
|
+
table.set(offset + 1, null);
|
|
1019
|
+
table.set(offset + 2, true);
|
|
1020
|
+
table.set(offset + 3, false);
|
|
926
1021
|
}
|
|
927
1022
|
}
|
|
928
1023
|
};
|
|
@@ -931,18 +1026,6 @@ const TransmuxCoreFinalization = typeof FinalizationRegistry === "undefined" ? {
|
|
|
931
1026
|
register: () => {},
|
|
932
1027
|
unregister: () => {}
|
|
933
1028
|
} : new FinalizationRegistry((ptr) => wasm.__wbg_transmuxcore_free(ptr, 1));
|
|
934
|
-
function addHeapObject(obj) {
|
|
935
|
-
if (heap_next === heap.length) heap.push(heap.length + 1);
|
|
936
|
-
const idx = heap_next;
|
|
937
|
-
heap_next = heap[idx];
|
|
938
|
-
heap[idx] = obj;
|
|
939
|
-
return idx;
|
|
940
|
-
}
|
|
941
|
-
function dropObject(idx) {
|
|
942
|
-
if (idx < 1028) return;
|
|
943
|
-
heap[idx] = heap_next;
|
|
944
|
-
heap_next = idx;
|
|
945
|
-
}
|
|
946
1029
|
let cachedDataViewMemory0 = null;
|
|
947
1030
|
function getDataViewMemory0() {
|
|
948
1031
|
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || cachedDataViewMemory0.buffer.detached === void 0 && cachedDataViewMemory0.buffer !== wasm.memory.buffer) cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
|
@@ -956,12 +1039,6 @@ function getUint8ArrayMemory0() {
|
|
|
956
1039
|
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
|
957
1040
|
return cachedUint8ArrayMemory0;
|
|
958
1041
|
}
|
|
959
|
-
function getObject(idx) {
|
|
960
|
-
return heap[idx];
|
|
961
|
-
}
|
|
962
|
-
let heap = new Array(1024).fill(void 0);
|
|
963
|
-
heap.push(void 0, null, true, false);
|
|
964
|
-
let heap_next = heap.length;
|
|
965
1042
|
function passArray8ToWasm0(arg, malloc) {
|
|
966
1043
|
const ptr = malloc(arg.length * 1, 1) >>> 0;
|
|
967
1044
|
getUint8ArrayMemory0().set(arg, ptr / 1);
|
|
@@ -996,10 +1073,10 @@ function passStringToWasm0(arg, malloc, realloc) {
|
|
|
996
1073
|
WASM_VECTOR_LEN = offset;
|
|
997
1074
|
return ptr;
|
|
998
1075
|
}
|
|
999
|
-
function
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
return
|
|
1076
|
+
function takeFromExternrefTable0(idx) {
|
|
1077
|
+
const value = wasm.__wbindgen_externrefs.get(idx);
|
|
1078
|
+
wasm.__externref_table_dealloc(idx);
|
|
1079
|
+
return value;
|
|
1003
1080
|
}
|
|
1004
1081
|
let cachedTextDecoder = new TextDecoder("utf-8", {
|
|
1005
1082
|
ignoreBOM: true,
|
|
@@ -1035,6 +1112,7 @@ function __wbg_finalize_init(instance, module) {
|
|
|
1035
1112
|
wasm = instance.exports;
|
|
1036
1113
|
cachedDataViewMemory0 = null;
|
|
1037
1114
|
cachedUint8ArrayMemory0 = null;
|
|
1115
|
+
wasm.__wbindgen_start();
|
|
1038
1116
|
return wasm;
|
|
1039
1117
|
}
|
|
1040
1118
|
async function __wbg_load(module, imports) {
|
|
@@ -1084,41 +1162,480 @@ async function loadWasmTransmuxCoreHost(wasmUrl) {
|
|
|
1084
1162
|
await __wbg_init(wasmUrl ?? new URL("./rivmux-transmux-core.wasm", import.meta.url));
|
|
1085
1163
|
return createWasmTransmuxCoreHost(TransmuxCore);
|
|
1086
1164
|
}
|
|
1165
|
+
var Fmp4AppendBatcher = class {
|
|
1166
|
+
pending = /* @__PURE__ */ new Map();
|
|
1167
|
+
timers = /* @__PURE__ */ new Map();
|
|
1168
|
+
maxDurationMs;
|
|
1169
|
+
maxBytes;
|
|
1170
|
+
onFlushDue;
|
|
1171
|
+
constructor(onFlushDue, options = {}) {
|
|
1172
|
+
this.onFlushDue = onFlushDue;
|
|
1173
|
+
this.maxDurationMs = options.maxDurationMs ?? 125;
|
|
1174
|
+
this.maxBytes = options.maxBytes ?? 524288;
|
|
1175
|
+
}
|
|
1176
|
+
push(segment) {
|
|
1177
|
+
let batch = this.pending.get(segment.track);
|
|
1178
|
+
if (batch !== void 0 && this.wouldExceedLimit(batch, segment)) {
|
|
1179
|
+
const flushed = this.flush(segment.track);
|
|
1180
|
+
batch = this.createBatch(segment);
|
|
1181
|
+
this.pending.set(segment.track, batch);
|
|
1182
|
+
this.scheduleFlush(segment.track);
|
|
1183
|
+
return flushed;
|
|
1184
|
+
}
|
|
1185
|
+
if (batch === void 0) {
|
|
1186
|
+
batch = this.createBatch(segment);
|
|
1187
|
+
this.pending.set(segment.track, batch);
|
|
1188
|
+
this.scheduleFlush(segment.track);
|
|
1189
|
+
} else {
|
|
1190
|
+
batch.dtsEndMs = Math.max(batch.dtsEndMs, segment.dtsEndMs);
|
|
1191
|
+
batch.parts.push(segment.bytes);
|
|
1192
|
+
batch.byteLength += segment.bytes.byteLength;
|
|
1193
|
+
}
|
|
1194
|
+
return batch.byteLength >= this.maxBytes ? this.flush(segment.track) : void 0;
|
|
1195
|
+
}
|
|
1196
|
+
flush(track) {
|
|
1197
|
+
const batch = this.pending.get(track);
|
|
1198
|
+
if (batch === void 0) return;
|
|
1199
|
+
this.pending.delete(track);
|
|
1200
|
+
this.cancelFlush(track);
|
|
1201
|
+
return {
|
|
1202
|
+
track,
|
|
1203
|
+
dtsStartMs: batch.dtsStartMs,
|
|
1204
|
+
dtsEndMs: batch.dtsEndMs,
|
|
1205
|
+
keyframe: batch.keyframe,
|
|
1206
|
+
bytes: mergeBytes(batch.parts, batch.byteLength)
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
flushAll() {
|
|
1210
|
+
const batches = [];
|
|
1211
|
+
for (const track of [...this.pending.keys()]) {
|
|
1212
|
+
const batch = this.flush(track);
|
|
1213
|
+
if (batch !== void 0) batches.push(batch);
|
|
1214
|
+
}
|
|
1215
|
+
return batches;
|
|
1216
|
+
}
|
|
1217
|
+
discard() {
|
|
1218
|
+
this.pending.clear();
|
|
1219
|
+
for (const timer of this.timers.values()) clearTimeout(timer);
|
|
1220
|
+
this.timers.clear();
|
|
1221
|
+
}
|
|
1222
|
+
createBatch(segment) {
|
|
1223
|
+
return {
|
|
1224
|
+
track: segment.track,
|
|
1225
|
+
dtsStartMs: segment.dtsStartMs,
|
|
1226
|
+
dtsEndMs: segment.dtsEndMs,
|
|
1227
|
+
keyframe: segment.keyframe,
|
|
1228
|
+
parts: [segment.bytes],
|
|
1229
|
+
byteLength: segment.bytes.byteLength
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
wouldExceedLimit(batch, segment) {
|
|
1233
|
+
return Math.max(batch.dtsEndMs, segment.dtsEndMs) - batch.dtsStartMs > this.maxDurationMs || batch.byteLength + segment.bytes.byteLength > this.maxBytes;
|
|
1234
|
+
}
|
|
1235
|
+
scheduleFlush(track) {
|
|
1236
|
+
this.timers.set(track, setTimeout(() => {
|
|
1237
|
+
this.timers.delete(track);
|
|
1238
|
+
this.onFlushDue(track);
|
|
1239
|
+
}, this.maxDurationMs));
|
|
1240
|
+
}
|
|
1241
|
+
cancelFlush(track) {
|
|
1242
|
+
const timer = this.timers.get(track);
|
|
1243
|
+
if (timer !== void 0) {
|
|
1244
|
+
clearTimeout(timer);
|
|
1245
|
+
this.timers.delete(track);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
function mergeBytes(parts, byteLength) {
|
|
1250
|
+
const bytes = new Uint8Array(byteLength);
|
|
1251
|
+
let offset = 0;
|
|
1252
|
+
for (const part of parts) {
|
|
1253
|
+
bytes.set(part, offset);
|
|
1254
|
+
offset += part.byteLength;
|
|
1255
|
+
}
|
|
1256
|
+
return bytes;
|
|
1257
|
+
}
|
|
1087
1258
|
//#endregion
|
|
1088
|
-
//#region src/runtime.ts
|
|
1089
|
-
|
|
1090
|
-
|
|
1259
|
+
//#region src/runtime/append.ts
|
|
1260
|
+
/** Owns fMP4 batching and serialized MSE media-segment appends. */
|
|
1261
|
+
var RuntimeAppendController = class {
|
|
1262
|
+
dependencies;
|
|
1263
|
+
batcher;
|
|
1264
|
+
generation = 0;
|
|
1265
|
+
tail = Promise.resolve(true);
|
|
1266
|
+
constructor(dependencies) {
|
|
1267
|
+
this.dependencies = dependencies;
|
|
1268
|
+
}
|
|
1269
|
+
start(context) {
|
|
1270
|
+
this.discard();
|
|
1271
|
+
this.batcher = new Fmp4AppendBatcher((track) => {
|
|
1272
|
+
const batch = this.batcher?.flush(track);
|
|
1273
|
+
if (batch !== void 0) this.enqueue(batch, context);
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
push(segment, context) {
|
|
1277
|
+
const batch = this.batcher?.push(segment);
|
|
1278
|
+
return batch === void 0 ? void 0 : this.enqueue(batch, context);
|
|
1279
|
+
}
|
|
1280
|
+
async flush(context, track) {
|
|
1281
|
+
const batcher = this.batcher;
|
|
1282
|
+
if (batcher === void 0) return true;
|
|
1283
|
+
const batches = track === void 0 ? batcher.flushAll() : [batcher.flush(track)].filter((batch) => batch !== void 0);
|
|
1284
|
+
for (const batch of batches) if (!await this.enqueue(batch, context)) return false;
|
|
1285
|
+
return true;
|
|
1286
|
+
}
|
|
1287
|
+
async waitForTail() {
|
|
1288
|
+
return this.tail;
|
|
1289
|
+
}
|
|
1290
|
+
discard() {
|
|
1291
|
+
this.generation += 1;
|
|
1292
|
+
this.batcher?.discard();
|
|
1293
|
+
this.batcher = void 0;
|
|
1294
|
+
this.tail = Promise.resolve(true);
|
|
1295
|
+
}
|
|
1296
|
+
enqueue(segment, context) {
|
|
1297
|
+
const generation = this.generation;
|
|
1298
|
+
const append = this.tail.then(async (previousAppendSucceeded) => {
|
|
1299
|
+
if (!previousAppendSucceeded || generation !== this.generation || !this.dependencies.isStarted() || !this.dependencies.isLifecycleContextCurrent(context)) return false;
|
|
1300
|
+
if (!await this.dependencies.appendToMse(segment, context)) return false;
|
|
1301
|
+
if (!this.dependencies.isLifecycleContextCurrent(context)) return false;
|
|
1302
|
+
await this.dependencies.onAppended(segment, context);
|
|
1303
|
+
return true;
|
|
1304
|
+
});
|
|
1305
|
+
this.tail = append.catch((cause) => {
|
|
1306
|
+
if (this.dependencies.isLifecycleContextCurrent(context)) this.dependencies.onError(cause, context);
|
|
1307
|
+
return false;
|
|
1308
|
+
});
|
|
1309
|
+
return this.tail;
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
//#endregion
|
|
1313
|
+
//#region src/runtime/session.ts
|
|
1314
|
+
/** Owns all resources that exist only for an attached playback session. */
|
|
1315
|
+
var RuntimeSession = class {
|
|
1316
|
+
dependencies;
|
|
1091
1317
|
createMseController;
|
|
1092
1318
|
createLoader;
|
|
1093
1319
|
createTransmuxCore;
|
|
1320
|
+
isStarted;
|
|
1321
|
+
isLifecycleContextCurrent;
|
|
1322
|
+
mse;
|
|
1323
|
+
loader;
|
|
1324
|
+
transmuxCore;
|
|
1325
|
+
loaderRunId = 0;
|
|
1326
|
+
loaderClosePromise;
|
|
1327
|
+
outputBytes = 0;
|
|
1328
|
+
appendController;
|
|
1329
|
+
constructor(dependencies) {
|
|
1330
|
+
this.dependencies = dependencies;
|
|
1331
|
+
this.createMseController = dependencies.createMseController ?? (() => new MseController());
|
|
1332
|
+
this.createLoader = dependencies.createLoader ?? ((config) => new HttpFlvLoader(config));
|
|
1333
|
+
this.createTransmuxCore = dependencies.createTransmuxCore ?? ((options) => loadWasmTransmuxCoreHost(options.runtime.wasmUrl));
|
|
1334
|
+
this.isStarted = dependencies.isStarted;
|
|
1335
|
+
this.isLifecycleContextCurrent = dependencies.isLifecycleContextCurrent;
|
|
1336
|
+
this.appendController = new RuntimeAppendController({
|
|
1337
|
+
appendToMse: (segment, context) => this.appendToMse(context, () => this.mse?.appendMediaSegment(segment)),
|
|
1338
|
+
isStarted: dependencies.isStarted,
|
|
1339
|
+
isLifecycleContextCurrent: dependencies.isLifecycleContextCurrent,
|
|
1340
|
+
onAppended: async (segment, context) => {
|
|
1341
|
+
if (!this.isLifecycleContextCurrent(context)) return;
|
|
1342
|
+
this.outputBytes += segment.bytes.byteLength;
|
|
1343
|
+
await dependencies.onMediaAppended(context);
|
|
1344
|
+
},
|
|
1345
|
+
onError: dependencies.onAppendError
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
get hasMse() {
|
|
1349
|
+
return this.mse !== void 0;
|
|
1350
|
+
}
|
|
1351
|
+
get loaderStats() {
|
|
1352
|
+
return this.loader?.stats;
|
|
1353
|
+
}
|
|
1354
|
+
get loaderPaused() {
|
|
1355
|
+
return this.loader?.paused ?? false;
|
|
1356
|
+
}
|
|
1357
|
+
get bufferedRanges() {
|
|
1358
|
+
return this.mse?.bufferedRanges ?? [];
|
|
1359
|
+
}
|
|
1360
|
+
get emittedBytes() {
|
|
1361
|
+
return this.outputBytes;
|
|
1362
|
+
}
|
|
1363
|
+
async attach(context) {
|
|
1364
|
+
this.mse ??= this.createMseController();
|
|
1365
|
+
const attachment = await raceLifecycleOperation(this.mse.createMediaSourceHandle(), context.signal);
|
|
1366
|
+
if (attachment.cancelled || !this.isLifecycleContextCurrent(context)) return;
|
|
1367
|
+
return attachment.value;
|
|
1368
|
+
}
|
|
1369
|
+
async createCore(options, context) {
|
|
1370
|
+
const creation = await raceLifecycleOperation(Promise.resolve(this.createTransmuxCore(options)), context.signal, (lateCore) => lateCore?.destroy());
|
|
1371
|
+
if (creation.cancelled || !this.isLifecycleContextCurrent(context)) return;
|
|
1372
|
+
return creation.value;
|
|
1373
|
+
}
|
|
1374
|
+
start(core, context) {
|
|
1375
|
+
this.transmuxCore?.destroy();
|
|
1376
|
+
this.transmuxCore = core;
|
|
1377
|
+
this.outputBytes = 0;
|
|
1378
|
+
this.appendController.start(context);
|
|
1379
|
+
}
|
|
1380
|
+
runLoader(config, context) {
|
|
1381
|
+
const { loader, runId } = this.startLoader(config);
|
|
1382
|
+
this.consumeLoader(loader, runId, context);
|
|
1383
|
+
}
|
|
1384
|
+
async close() {
|
|
1385
|
+
this.dependencies.onLoaderClosing();
|
|
1386
|
+
this.appendController.discard();
|
|
1387
|
+
this.transmuxCore?.destroy();
|
|
1388
|
+
this.transmuxCore = void 0;
|
|
1389
|
+
const loader = this.loader;
|
|
1390
|
+
if (loader === void 0) {
|
|
1391
|
+
await this.loaderClosePromise;
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
this.loader = void 0;
|
|
1395
|
+
this.loaderRunId += 1;
|
|
1396
|
+
await this.closeLoaderInstance(loader);
|
|
1397
|
+
}
|
|
1398
|
+
destroyMse() {
|
|
1399
|
+
this.mse?.destroy();
|
|
1400
|
+
this.mse = void 0;
|
|
1401
|
+
}
|
|
1402
|
+
discardAppend() {
|
|
1403
|
+
this.appendController.discard();
|
|
1404
|
+
}
|
|
1405
|
+
collectMseStats() {
|
|
1406
|
+
return {
|
|
1407
|
+
appendQueueLength: this.mse?.appendQueueLength ?? 0,
|
|
1408
|
+
appendQueueBytes: this.mse?.appendQueueBytes ?? 0,
|
|
1409
|
+
sourceBufferUpdating: this.mse?.sourceBufferUpdating ?? false,
|
|
1410
|
+
sourceBufferCount: this.mse?.sourceBufferCount ?? 0,
|
|
1411
|
+
bufferedRangeCount: this.mse?.bufferedRangeCount ?? 0,
|
|
1412
|
+
bufferedStart: this.mse?.bufferedStart,
|
|
1413
|
+
bufferedEnd: this.mse?.bufferedEnd,
|
|
1414
|
+
bufferedDuration: this.mse?.bufferedDuration
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
cleanupBefore(cutoff, force = false) {
|
|
1418
|
+
return this.mse?.cleanupBefore(cutoff, force ? { force: true } : void 0);
|
|
1419
|
+
}
|
|
1420
|
+
pauseLoader() {
|
|
1421
|
+
this.loader?.pause();
|
|
1422
|
+
}
|
|
1423
|
+
resumeLoader() {
|
|
1424
|
+
this.loader?.resume();
|
|
1425
|
+
}
|
|
1426
|
+
startLoader(config) {
|
|
1427
|
+
const loader = this.createLoader(config);
|
|
1428
|
+
const runId = this.loaderRunId + 1;
|
|
1429
|
+
this.loaderRunId = runId;
|
|
1430
|
+
this.loader = loader;
|
|
1431
|
+
return {
|
|
1432
|
+
loader,
|
|
1433
|
+
runId
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
pushChunk(bytes) {
|
|
1437
|
+
return this.transmuxCore?.pushChunk(bytes) ?? [];
|
|
1438
|
+
}
|
|
1439
|
+
isCurrentLoader(loader, runId) {
|
|
1440
|
+
return this.loader === loader && this.loaderRunId === runId && this.isStarted();
|
|
1441
|
+
}
|
|
1442
|
+
async closeCurrentLoader(loader, runId) {
|
|
1443
|
+
if (this.loader !== loader || this.loaderRunId !== runId) return;
|
|
1444
|
+
this.dependencies.onLoaderClosing();
|
|
1445
|
+
this.appendController.discard();
|
|
1446
|
+
this.loader = void 0;
|
|
1447
|
+
this.loaderRunId += 1;
|
|
1448
|
+
this.transmuxCore?.destroy();
|
|
1449
|
+
this.transmuxCore = void 0;
|
|
1450
|
+
await this.closeLoaderInstance(loader);
|
|
1451
|
+
}
|
|
1452
|
+
async closeLoaderInstance(loader) {
|
|
1453
|
+
let closing;
|
|
1454
|
+
try {
|
|
1455
|
+
closing = loader.close();
|
|
1456
|
+
} catch (cause) {
|
|
1457
|
+
closing = Promise.reject(cause);
|
|
1458
|
+
}
|
|
1459
|
+
this.loaderClosePromise = closing;
|
|
1460
|
+
try {
|
|
1461
|
+
await closing;
|
|
1462
|
+
} finally {
|
|
1463
|
+
if (this.loaderClosePromise === closing) this.loaderClosePromise = void 0;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
async consumeLoader(loader, runId, context) {
|
|
1467
|
+
try {
|
|
1468
|
+
await loader.open();
|
|
1469
|
+
while (this.isCurrentLoader(loader, runId) && this.isLifecycleContextCurrent(context)) {
|
|
1470
|
+
await this.dependencies.applyLatencyPolicy(context);
|
|
1471
|
+
if (!this.isCurrentLoader(loader, runId) || !this.isLifecycleContextCurrent(context)) return;
|
|
1472
|
+
const chunk = await loader.read();
|
|
1473
|
+
if (chunk === null || !this.isCurrentLoader(loader, runId) || !this.isLifecycleContextCurrent(context)) break;
|
|
1474
|
+
this.dependencies.onStats(loader.stats);
|
|
1475
|
+
if (!await this.processEvents(this.pushChunk(chunk.bytes), context)) {
|
|
1476
|
+
await this.closeCurrentLoader(loader, runId);
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
await this.dependencies.applyLatencyPolicy(context);
|
|
1480
|
+
if (!this.isCurrentLoader(loader, runId) || !this.isLifecycleContextCurrent(context)) return;
|
|
1481
|
+
this.dependencies.onStats(loader.stats);
|
|
1482
|
+
}
|
|
1483
|
+
if (this.isCurrentLoader(loader, runId) && this.isLifecycleContextCurrent(context)) {
|
|
1484
|
+
if (!await this.appendController.flush(context)) return;
|
|
1485
|
+
this.dependencies.onStats(loader.stats);
|
|
1486
|
+
}
|
|
1487
|
+
} catch (cause) {
|
|
1488
|
+
if (!this.isCurrentLoader(loader, runId) || isAbortLikeError(cause)) return;
|
|
1489
|
+
try {
|
|
1490
|
+
await this.closeCurrentLoader(loader, runId);
|
|
1491
|
+
} catch {}
|
|
1492
|
+
if (!this.isLifecycleContextCurrent(context)) return;
|
|
1493
|
+
const code = cause instanceof HttpFlvLoaderError ? cause.code : "RIVMUX_HTTP_LOADER_FAILED";
|
|
1494
|
+
this.dependencies.onFailure("network", code, "HTTP Fetch loader failed.", cause);
|
|
1495
|
+
} finally {
|
|
1496
|
+
if (this.isCurrentLoader(loader, runId)) try {
|
|
1497
|
+
await this.closeCurrentLoader(loader, runId);
|
|
1498
|
+
} catch (cause) {
|
|
1499
|
+
if (this.isLifecycleContextCurrent(context)) this.dependencies.onFailure("network", "RIVMUX_HTTP_LOADER_CLOSE_FAILED", "HTTP Fetch loader failed to close.", cause);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
async processEvents(events, context) {
|
|
1504
|
+
for (const event of events) {
|
|
1505
|
+
if (!this.isLifecycleContextCurrent(context)) return false;
|
|
1506
|
+
switch (event.type) {
|
|
1507
|
+
case "mediaInfo":
|
|
1508
|
+
this.dependencies.onMessage({
|
|
1509
|
+
type: "media-info",
|
|
1510
|
+
mediaInfo: coreMediaInfoToPlayerMediaInfo(event.data)
|
|
1511
|
+
});
|
|
1512
|
+
break;
|
|
1513
|
+
case "warning":
|
|
1514
|
+
this.dependencies.onMessage({
|
|
1515
|
+
type: "warning",
|
|
1516
|
+
warning: coreWarningToPlayerWarning(event.data)
|
|
1517
|
+
});
|
|
1518
|
+
break;
|
|
1519
|
+
case "fatalError":
|
|
1520
|
+
this.dependencies.onPlayerError(coreErrorToPlayerError(event.data));
|
|
1521
|
+
return false;
|
|
1522
|
+
case "initSegment":
|
|
1523
|
+
if (!await this.appendController.flush(context)) return false;
|
|
1524
|
+
if (!await this.appendToMse(context, () => this.mse?.appendInitSegment(event.data))) return false;
|
|
1525
|
+
if (!this.isLifecycleContextCurrent(context)) return false;
|
|
1526
|
+
this.outputBytes += event.data.bytes.byteLength;
|
|
1527
|
+
await this.dependencies.applyLatencyPolicy(context);
|
|
1528
|
+
break;
|
|
1529
|
+
case "mediaSegment": {
|
|
1530
|
+
const append = this.appendController.push(event.data, context);
|
|
1531
|
+
if (append !== void 0 && !await append) return false;
|
|
1532
|
+
break;
|
|
1533
|
+
}
|
|
1534
|
+
case "probeResult":
|
|
1535
|
+
case "trackConfig":
|
|
1536
|
+
case "sample":
|
|
1537
|
+
case "metadata":
|
|
1538
|
+
case "discontinuity": break;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return this.appendController.waitForTail();
|
|
1542
|
+
}
|
|
1543
|
+
async appendToMse(context, append) {
|
|
1544
|
+
try {
|
|
1545
|
+
if ((await raceLifecycleOperation(Promise.resolve(append()), context.signal)).cancelled || !this.isLifecycleContextCurrent(context)) return false;
|
|
1546
|
+
return true;
|
|
1547
|
+
} catch (cause) {
|
|
1548
|
+
if (!this.isLifecycleContextCurrent(context)) return false;
|
|
1549
|
+
if (isQuotaExceededError(cause) && await this.retryAppendAfterQuotaCleanup(context, append)) return true;
|
|
1550
|
+
if (cause instanceof MseUnsupportedMimeError) {
|
|
1551
|
+
this.dependencies.onFailure("unsupported", "RIVMUX_UNSUPPORTED_MSE_CODEC", cause.message, cause);
|
|
1552
|
+
return false;
|
|
1553
|
+
}
|
|
1554
|
+
this.dependencies.onFailure("mse", "RIVMUX_MSE_APPEND_FAILED", "MSE append failed.", cause);
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async retryAppendAfterQuotaCleanup(context, append) {
|
|
1559
|
+
const cutoff = this.dependencies.quotaCleanupCutoff();
|
|
1560
|
+
if (cutoff === void 0 || cutoff <= 0) return false;
|
|
1561
|
+
try {
|
|
1562
|
+
if ((await raceLifecycleOperation(Promise.resolve(this.cleanupBefore(cutoff, true)), context.signal)).cancelled || !this.isLifecycleContextCurrent(context)) return false;
|
|
1563
|
+
if ((await raceLifecycleOperation(Promise.resolve(append()), context.signal)).cancelled || !this.isLifecycleContextCurrent(context)) return false;
|
|
1564
|
+
this.dependencies.onMessage({
|
|
1565
|
+
type: "warning",
|
|
1566
|
+
warning: {
|
|
1567
|
+
code: "RIVMUX_MSE_QUOTA_RETRY",
|
|
1568
|
+
message: "MSE quota was exceeded; old buffered ranges were cleaned before retrying append."
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
return true;
|
|
1572
|
+
} catch {
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
function isQuotaExceededError(cause) {
|
|
1578
|
+
return typeof cause === "object" && cause !== null && "name" in cause && cause.name === "QuotaExceededError";
|
|
1579
|
+
}
|
|
1580
|
+
//#endregion
|
|
1581
|
+
//#region src/runtime/index.ts
|
|
1582
|
+
var RuntimeWorker = class {
|
|
1583
|
+
port;
|
|
1094
1584
|
detectRuntime;
|
|
1095
1585
|
now;
|
|
1096
1586
|
state = "idle";
|
|
1097
1587
|
url;
|
|
1098
1588
|
options;
|
|
1099
|
-
|
|
1100
|
-
loader;
|
|
1101
|
-
transmuxCore;
|
|
1589
|
+
session;
|
|
1102
1590
|
latencyController;
|
|
1103
1591
|
videoState;
|
|
1104
1592
|
lastLatencyMetrics = {};
|
|
1105
1593
|
statsTimer;
|
|
1106
1594
|
statsTickInFlight = false;
|
|
1107
|
-
loaderRunId = 0;
|
|
1108
|
-
outputBytes = 0;
|
|
1109
1595
|
appendQueueMaxLength = 0;
|
|
1110
1596
|
appendQueueMaxBytes = 0;
|
|
1597
|
+
commandTail = Promise.resolve();
|
|
1598
|
+
lifecycleGeneration = 0;
|
|
1599
|
+
lifecycleAbortController = new AbortController();
|
|
1600
|
+
fatalCleanupPromise = Promise.resolve();
|
|
1111
1601
|
constructor(port, dependencies = {}) {
|
|
1112
1602
|
this.port = port;
|
|
1113
|
-
this.createMseController = dependencies.createMseController ?? (() => new MseController());
|
|
1114
|
-
this.createLoader = dependencies.createLoader ?? ((config) => new HttpFlvLoader(config));
|
|
1115
|
-
this.createTransmuxCore = dependencies.createTransmuxCore ?? ((options) => loadWasmTransmuxCoreHost(options.runtime.wasmUrl));
|
|
1116
1603
|
this.detectRuntime = dependencies.detectRuntime ?? detectWorkerRuntime;
|
|
1117
1604
|
this.now = dependencies.now ?? (() => performance.now());
|
|
1605
|
+
this.session = new RuntimeSession({
|
|
1606
|
+
...dependencies,
|
|
1607
|
+
isStarted: () => this.state === "started",
|
|
1608
|
+
isLifecycleContextCurrent: (context) => this.isLifecycleContextCurrent(context),
|
|
1609
|
+
onMediaAppended: (context) => this.applyLatencyPolicy(context),
|
|
1610
|
+
onAppendError: (cause, context) => {
|
|
1611
|
+
if (this.isLifecycleContextCurrent(context)) this.fail("mse", "RIVMUX_MSE_APPEND_FAILED", "MSE append failed.", true, cause);
|
|
1612
|
+
},
|
|
1613
|
+
onLoaderClosing: () => this.stopStatsTimer(),
|
|
1614
|
+
onStats: (stats) => this.postStats(stats),
|
|
1615
|
+
onMessage: (message) => this.post(message),
|
|
1616
|
+
onFailure: (kind, code, message, cause) => this.fail(kind, code, message, true, cause),
|
|
1617
|
+
onPlayerError: (error) => this.failWithError(error),
|
|
1618
|
+
applyLatencyPolicy: (context) => this.applyLatencyPolicy(context),
|
|
1619
|
+
quotaCleanupCutoff: () => this.quotaCleanupCutoff()
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
handleCommand(command) {
|
|
1623
|
+
if (command.type === "stop" || command.type === "destroy") this.invalidateLifecycle();
|
|
1624
|
+
const context = {
|
|
1625
|
+
generation: this.lifecycleGeneration,
|
|
1626
|
+
signal: this.lifecycleAbortController.signal
|
|
1627
|
+
};
|
|
1628
|
+
const handling = this.commandTail.then(() => this.executeCommand(command, context));
|
|
1629
|
+
this.commandTail = handling.catch(() => void 0);
|
|
1630
|
+
return handling;
|
|
1118
1631
|
}
|
|
1119
|
-
async
|
|
1632
|
+
async executeCommand(command, context) {
|
|
1120
1633
|
if (this.state === "destroyed") return;
|
|
1121
|
-
if (
|
|
1634
|
+
if ((command.type === "attach-media-source" || command.type === "start") && !this.isLifecycleContextCurrent(context)) return;
|
|
1635
|
+
if (this.state === "fatal-error") {
|
|
1636
|
+
if (command.type === "stop") this.post({ type: "stopped" });
|
|
1637
|
+
if (command.type !== "destroy") return;
|
|
1638
|
+
}
|
|
1122
1639
|
try {
|
|
1123
1640
|
switch (command.type) {
|
|
1124
1641
|
case "init":
|
|
@@ -1136,10 +1653,10 @@ var RuntimeWorker = class {
|
|
|
1136
1653
|
this.post({ type: "ready" });
|
|
1137
1654
|
return;
|
|
1138
1655
|
case "attach-media-source":
|
|
1139
|
-
await this.attachMediaSource();
|
|
1656
|
+
await this.attachMediaSource(context);
|
|
1140
1657
|
return;
|
|
1141
1658
|
case "start":
|
|
1142
|
-
await this.start();
|
|
1659
|
+
await this.start(context);
|
|
1143
1660
|
return;
|
|
1144
1661
|
case "stop":
|
|
1145
1662
|
await this.stop();
|
|
@@ -1150,8 +1667,7 @@ var RuntimeWorker = class {
|
|
|
1150
1667
|
return;
|
|
1151
1668
|
case "video-state":
|
|
1152
1669
|
this.videoState = command.state;
|
|
1153
|
-
await this.applyLatencyPolicy();
|
|
1154
|
-
this.postStats();
|
|
1670
|
+
if (!(await raceLifecycleOperation(this.applyLatencyPolicy(context), context.signal)).cancelled && this.isLifecycleContextCurrent(context) && this.state === "started") this.postStats();
|
|
1155
1671
|
return;
|
|
1156
1672
|
case "playback-control-result":
|
|
1157
1673
|
this.latencyController?.recordPlaybackControlResult(command.result);
|
|
@@ -1164,57 +1680,55 @@ var RuntimeWorker = class {
|
|
|
1164
1680
|
this.fail("runtime", "RIVMUX_WORKER_COMMAND_FAILED", "Worker command failed.", true, cause);
|
|
1165
1681
|
}
|
|
1166
1682
|
}
|
|
1167
|
-
async attachMediaSource() {
|
|
1683
|
+
async attachMediaSource(context) {
|
|
1168
1684
|
if (this.state === "idle") {
|
|
1169
1685
|
this.fail("runtime", "RIVMUX_WORKER_NOT_INITIALIZED", "Worker must be initialized before attach.", true);
|
|
1170
1686
|
return;
|
|
1171
1687
|
}
|
|
1172
|
-
if (this.mse === void 0) this.mse = this.createMseController();
|
|
1173
1688
|
try {
|
|
1174
|
-
const handle = await this.
|
|
1689
|
+
const handle = await this.session.attach(context);
|
|
1690
|
+
if (handle === void 0 || context.generation !== this.lifecycleGeneration) return;
|
|
1175
1691
|
this.post({
|
|
1176
1692
|
type: "media-source-handle",
|
|
1177
1693
|
handle
|
|
1178
1694
|
}, [handle]);
|
|
1179
1695
|
this.state = "attached";
|
|
1180
1696
|
} catch (cause) {
|
|
1697
|
+
if (context.generation !== this.lifecycleGeneration) return;
|
|
1181
1698
|
this.fail("mse", "RIVMUX_MSE_ATTACH_FAILED", "MSE media source attachment failed.", true, cause);
|
|
1182
1699
|
}
|
|
1183
1700
|
}
|
|
1184
|
-
async start() {
|
|
1701
|
+
async start(context) {
|
|
1185
1702
|
const options = this.options;
|
|
1186
|
-
if (this.
|
|
1703
|
+
if (!this.session.hasMse || options === void 0 || this.state === "idle" || this.state === "ready") {
|
|
1187
1704
|
this.fail("runtime", "RIVMUX_WORKER_START_REQUIRES_ATTACH", "Worker start requires an attached MediaSource.", true);
|
|
1188
1705
|
return;
|
|
1189
1706
|
}
|
|
1190
1707
|
if (this.state === "started") return;
|
|
1191
|
-
let transmuxCore;
|
|
1192
1708
|
try {
|
|
1193
|
-
const
|
|
1194
|
-
if (
|
|
1709
|
+
const transmuxCore = await this.session.createCore(options, context);
|
|
1710
|
+
if (context.generation !== this.lifecycleGeneration) return;
|
|
1711
|
+
if (transmuxCore === void 0) {
|
|
1195
1712
|
this.fail("runtime", "RIVMUX_TRANSMUX_CORE_UNAVAILABLE", "Transmux core is not available.", true);
|
|
1196
1713
|
return;
|
|
1197
1714
|
}
|
|
1198
|
-
transmuxCore
|
|
1715
|
+
this.session.start(transmuxCore, context);
|
|
1199
1716
|
} catch (cause) {
|
|
1717
|
+
if (context.generation !== this.lifecycleGeneration) return;
|
|
1200
1718
|
this.fail("runtime", "RIVMUX_TRANSMUX_CORE_UNAVAILABLE", "Transmux core is not available.", true, cause);
|
|
1201
1719
|
return;
|
|
1202
1720
|
}
|
|
1203
|
-
this.transmuxCore?.destroy();
|
|
1204
|
-
this.transmuxCore = transmuxCore;
|
|
1205
1721
|
this.state = "started";
|
|
1206
|
-
this.outputBytes = 0;
|
|
1207
1722
|
this.appendQueueMaxLength = 0;
|
|
1208
1723
|
this.appendQueueMaxBytes = 0;
|
|
1724
|
+
if ((await raceLifecycleOperation(this.applyLatencyPolicy(context), context.signal)).cancelled || !this.isLifecycleContextCurrent(context)) return;
|
|
1209
1725
|
this.startStatsTimer();
|
|
1210
|
-
await this.applyLatencyPolicy();
|
|
1211
1726
|
this.postStats();
|
|
1212
|
-
this.startLoader();
|
|
1727
|
+
this.startLoader(context);
|
|
1213
1728
|
}
|
|
1214
1729
|
async stop() {
|
|
1215
1730
|
await this.closeLoader();
|
|
1216
|
-
this.
|
|
1217
|
-
this.mse = void 0;
|
|
1731
|
+
this.session.destroyMse();
|
|
1218
1732
|
this.latencyController?.reset();
|
|
1219
1733
|
this.videoState = void 0;
|
|
1220
1734
|
this.lastLatencyMetrics = {};
|
|
@@ -1222,9 +1736,9 @@ var RuntimeWorker = class {
|
|
|
1222
1736
|
this.post({ type: "stopped" });
|
|
1223
1737
|
}
|
|
1224
1738
|
async destroy() {
|
|
1739
|
+
await this.fatalCleanupPromise;
|
|
1225
1740
|
await this.closeLoader();
|
|
1226
|
-
this.
|
|
1227
|
-
this.mse = void 0;
|
|
1741
|
+
this.session.destroyMse();
|
|
1228
1742
|
this.latencyController?.reset();
|
|
1229
1743
|
this.videoState = void 0;
|
|
1230
1744
|
this.lastLatencyMetrics = {};
|
|
@@ -1232,199 +1746,70 @@ var RuntimeWorker = class {
|
|
|
1232
1746
|
this.post({ type: "destroyed" });
|
|
1233
1747
|
this.port.close();
|
|
1234
1748
|
}
|
|
1235
|
-
startLoader() {
|
|
1749
|
+
startLoader(context) {
|
|
1236
1750
|
const options = this.options;
|
|
1237
1751
|
const url = this.url;
|
|
1238
1752
|
if (options === void 0 || url === void 0) {
|
|
1239
1753
|
this.fail("runtime", "RIVMUX_WORKER_NOT_INITIALIZED", "Worker must be initialized before loader start.", true);
|
|
1240
1754
|
return;
|
|
1241
1755
|
}
|
|
1242
|
-
|
|
1756
|
+
this.session.runLoader({
|
|
1243
1757
|
url,
|
|
1244
1758
|
network: options.network
|
|
1245
|
-
});
|
|
1246
|
-
const runId = this.loaderRunId + 1;
|
|
1247
|
-
this.loaderRunId = runId;
|
|
1248
|
-
this.loader = loader;
|
|
1249
|
-
this.runLoader(loader, runId);
|
|
1250
|
-
}
|
|
1251
|
-
async runLoader(loader, runId) {
|
|
1252
|
-
try {
|
|
1253
|
-
await loader.open();
|
|
1254
|
-
while (this.isCurrentLoader(loader, runId)) {
|
|
1255
|
-
await this.applyLatencyPolicy();
|
|
1256
|
-
const chunk = await loader.read();
|
|
1257
|
-
if (chunk === null) break;
|
|
1258
|
-
this.postStats(loader.stats);
|
|
1259
|
-
if (!await this.processTransmuxEvents(this.transmuxCore?.pushChunk(chunk.bytes) ?? [])) {
|
|
1260
|
-
await this.closeCurrentLoader(loader, runId);
|
|
1261
|
-
return;
|
|
1262
|
-
}
|
|
1263
|
-
await this.applyLatencyPolicy();
|
|
1264
|
-
this.postStats(loader.stats);
|
|
1265
|
-
}
|
|
1266
|
-
} catch (cause) {
|
|
1267
|
-
if (!this.isCurrentLoader(loader, runId) || isAbortLikeError(cause)) return;
|
|
1268
|
-
await this.closeCurrentLoader(loader, runId);
|
|
1269
|
-
this.fail("network", getNetworkErrorCode(cause), "HTTP Fetch loader failed.", true, cause);
|
|
1270
|
-
return;
|
|
1271
|
-
} finally {
|
|
1272
|
-
if (this.isCurrentLoader(loader, runId)) await this.closeCurrentLoader(loader, runId);
|
|
1273
|
-
}
|
|
1759
|
+
}, context);
|
|
1274
1760
|
}
|
|
1275
1761
|
async closeLoader() {
|
|
1276
1762
|
this.stopStatsTimer();
|
|
1277
|
-
|
|
1278
|
-
if (loader === void 0) return;
|
|
1279
|
-
this.loader = void 0;
|
|
1280
|
-
this.loaderRunId += 1;
|
|
1281
|
-
this.transmuxCore?.destroy();
|
|
1282
|
-
this.transmuxCore = void 0;
|
|
1283
|
-
await loader.close();
|
|
1284
|
-
}
|
|
1285
|
-
async closeCurrentLoader(loader, runId) {
|
|
1286
|
-
if (this.loader !== loader || this.loaderRunId !== runId) return;
|
|
1287
|
-
this.stopStatsTimer();
|
|
1288
|
-
this.loader = void 0;
|
|
1289
|
-
this.loaderRunId += 1;
|
|
1290
|
-
this.transmuxCore?.destroy();
|
|
1291
|
-
this.transmuxCore = void 0;
|
|
1292
|
-
await loader.close();
|
|
1293
|
-
}
|
|
1294
|
-
isCurrentLoader(loader, runId) {
|
|
1295
|
-
return this.loader === loader && this.loaderRunId === runId && this.state === "started";
|
|
1763
|
+
await this.session.close();
|
|
1296
1764
|
}
|
|
1297
1765
|
postStats(loaderStats) {
|
|
1298
|
-
const metrics = this.lastLatencyMetrics;
|
|
1299
1766
|
const mseStats = this.collectMseStats();
|
|
1300
|
-
const loaderSnapshot = loaderStats ?? this.
|
|
1767
|
+
const loaderSnapshot = loaderStats ?? this.session.loaderStats;
|
|
1301
1768
|
this.post({
|
|
1302
1769
|
type: "stats",
|
|
1303
|
-
stats: {
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
outputBytes: this.
|
|
1308
|
-
appendQueueLength: mseStats.appendQueueLength,
|
|
1309
|
-
appendQueueBytes: mseStats.appendQueueBytes,
|
|
1770
|
+
stats: createPlayerStats({
|
|
1771
|
+
loaderStats: loaderSnapshot,
|
|
1772
|
+
mseStats,
|
|
1773
|
+
latencyMetrics: this.lastLatencyMetrics,
|
|
1774
|
+
outputBytes: this.session.emittedBytes,
|
|
1310
1775
|
appendQueueMaxLength: this.appendQueueMaxLength,
|
|
1311
1776
|
appendQueueMaxBytes: this.appendQueueMaxBytes,
|
|
1312
|
-
loaderPaused: this.
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
bufferedStart: metrics.bufferedStart ?? this.mse?.bufferedStart,
|
|
1316
|
-
bufferedEnd: metrics.bufferedEnd ?? this.mse?.bufferedEnd,
|
|
1317
|
-
bufferedDuration: metrics.bufferedDuration ?? this.mse?.bufferedDuration,
|
|
1318
|
-
bufferedRangeCount: mseStats.bufferedRangeCount,
|
|
1319
|
-
currentTime: metrics.currentTime,
|
|
1320
|
-
liveLatency: metrics.liveLatency,
|
|
1321
|
-
playbackRate: metrics.playbackRate,
|
|
1322
|
-
readyState: metrics.readyState,
|
|
1323
|
-
droppedFrames: metrics.droppedFrames
|
|
1324
|
-
}
|
|
1777
|
+
loaderPaused: this.session.loaderPaused,
|
|
1778
|
+
nowMs: this.now()
|
|
1779
|
+
})
|
|
1325
1780
|
});
|
|
1326
1781
|
}
|
|
1327
|
-
async processTransmuxEvents(events) {
|
|
1328
|
-
for (const event of events) switch (event.type) {
|
|
1329
|
-
case "mediaInfo":
|
|
1330
|
-
this.post({
|
|
1331
|
-
type: "media-info",
|
|
1332
|
-
mediaInfo: coreMediaInfoToPlayerMediaInfo(event.data)
|
|
1333
|
-
});
|
|
1334
|
-
break;
|
|
1335
|
-
case "warning":
|
|
1336
|
-
this.post({
|
|
1337
|
-
type: "warning",
|
|
1338
|
-
warning: coreWarningToPlayerWarning(event.data)
|
|
1339
|
-
});
|
|
1340
|
-
break;
|
|
1341
|
-
case "fatalError":
|
|
1342
|
-
this.failWithError(coreErrorToPlayerError(event.data));
|
|
1343
|
-
return false;
|
|
1344
|
-
case "initSegment":
|
|
1345
|
-
if (!await this.appendToMse(() => this.mse?.appendInitSegment(event.data))) return false;
|
|
1346
|
-
this.outputBytes += event.data.bytes.byteLength;
|
|
1347
|
-
await this.applyLatencyPolicy();
|
|
1348
|
-
break;
|
|
1349
|
-
case "mediaSegment":
|
|
1350
|
-
if (!await this.appendToMse(() => this.mse?.appendMediaSegment(event.data))) return false;
|
|
1351
|
-
this.outputBytes += event.data.bytes.byteLength;
|
|
1352
|
-
await this.applyLatencyPolicy();
|
|
1353
|
-
break;
|
|
1354
|
-
case "probeResult":
|
|
1355
|
-
case "videoConfig":
|
|
1356
|
-
case "audioConfig":
|
|
1357
|
-
case "videoSample":
|
|
1358
|
-
case "audioSample":
|
|
1359
|
-
case "metadata":
|
|
1360
|
-
case "discontinuity": break;
|
|
1361
|
-
}
|
|
1362
|
-
return true;
|
|
1363
|
-
}
|
|
1364
1782
|
collectMseStats() {
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
sourceBufferCount: this.mse?.sourceBufferCount ?? 0,
|
|
1374
|
-
bufferedRangeCount: this.mse?.bufferedRangeCount ?? 0
|
|
1375
|
-
};
|
|
1376
|
-
}
|
|
1377
|
-
async appendToMse(append) {
|
|
1378
|
-
try {
|
|
1379
|
-
await append();
|
|
1380
|
-
return true;
|
|
1381
|
-
} catch (cause) {
|
|
1382
|
-
if (isQuotaExceededError(cause) && await this.retryAppendAfterQuotaCleanup(append)) return true;
|
|
1383
|
-
this.fail("mse", "RIVMUX_MSE_APPEND_FAILED", "MSE append failed.", true, cause);
|
|
1384
|
-
return false;
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
async retryAppendAfterQuotaCleanup(append) {
|
|
1388
|
-
const mse = this.mse;
|
|
1389
|
-
const cutoff = this.quotaCleanupCutoff();
|
|
1390
|
-
if (mse === void 0 || cutoff === void 0 || cutoff <= 0) return false;
|
|
1391
|
-
try {
|
|
1392
|
-
await mse.cleanupBefore(cutoff, { force: true });
|
|
1393
|
-
await append();
|
|
1394
|
-
this.post({
|
|
1395
|
-
type: "warning",
|
|
1396
|
-
warning: {
|
|
1397
|
-
code: "RIVMUX_MSE_QUOTA_RETRY",
|
|
1398
|
-
message: "MSE quota was exceeded; old buffered ranges were cleaned before retrying append."
|
|
1399
|
-
}
|
|
1400
|
-
});
|
|
1401
|
-
return true;
|
|
1402
|
-
} catch {
|
|
1403
|
-
return false;
|
|
1404
|
-
}
|
|
1783
|
+
const stats = this.session.collectMseStats();
|
|
1784
|
+
const highWaterMark = updateAppendQueueHighWaterMark({
|
|
1785
|
+
length: this.appendQueueMaxLength,
|
|
1786
|
+
bytes: this.appendQueueMaxBytes
|
|
1787
|
+
}, stats);
|
|
1788
|
+
this.appendQueueMaxLength = highWaterMark.length;
|
|
1789
|
+
this.appendQueueMaxBytes = highWaterMark.bytes;
|
|
1790
|
+
return { ...stats };
|
|
1405
1791
|
}
|
|
1406
1792
|
quotaCleanupCutoff() {
|
|
1407
1793
|
const backwardBuffer = this.options?.latency.backwardBuffer ?? 0;
|
|
1408
1794
|
const currentTime = this.videoState?.currentTime;
|
|
1409
1795
|
if (currentTime !== void 0 && Number.isFinite(currentTime)) return Math.max(0, currentTime - backwardBuffer);
|
|
1410
|
-
const bufferedEnd = this.
|
|
1796
|
+
const bufferedEnd = this.session.collectMseStats().bufferedEnd;
|
|
1411
1797
|
return bufferedEnd === void 0 ? void 0 : Math.max(0, bufferedEnd - backwardBuffer);
|
|
1412
1798
|
}
|
|
1413
|
-
async applyLatencyPolicy() {
|
|
1799
|
+
async applyLatencyPolicy(context) {
|
|
1414
1800
|
const latencyController = this.latencyController;
|
|
1415
|
-
|
|
1416
|
-
if (latencyController === void 0 || mse === void 0) return;
|
|
1417
|
-
const loader = this.loader;
|
|
1801
|
+
if (latencyController === void 0 || !this.session.hasMse) return;
|
|
1418
1802
|
const evaluation = latencyController.evaluate({
|
|
1419
|
-
ranges:
|
|
1803
|
+
ranges: this.session.bufferedRanges,
|
|
1420
1804
|
videoState: this.videoState,
|
|
1421
|
-
loaderPaused:
|
|
1805
|
+
loaderPaused: this.session.loaderPaused,
|
|
1422
1806
|
nowMs: this.now()
|
|
1423
1807
|
});
|
|
1424
1808
|
this.lastLatencyMetrics = evaluation.metrics;
|
|
1425
|
-
if (evaluation.cleanupBefore !== void 0) await
|
|
1426
|
-
if (
|
|
1427
|
-
|
|
1809
|
+
if (evaluation.cleanupBefore !== void 0) await this.session.cleanupBefore(evaluation.cleanupBefore);
|
|
1810
|
+
if (context !== void 0 && !this.isLifecycleContextCurrent(context)) return;
|
|
1811
|
+
if (evaluation.loaderCommand === "pause") this.session.pauseLoader();
|
|
1812
|
+
else if (evaluation.loaderCommand === "resume") this.session.resumeLoader();
|
|
1428
1813
|
if (evaluation.playbackControl !== void 0) this.post({
|
|
1429
1814
|
type: "playback-control",
|
|
1430
1815
|
action: evaluation.playbackControl
|
|
@@ -1447,15 +1832,25 @@ var RuntimeWorker = class {
|
|
|
1447
1832
|
async emitStatsTick() {
|
|
1448
1833
|
if (this.statsTickInFlight || this.state !== "started") return;
|
|
1449
1834
|
this.statsTickInFlight = true;
|
|
1835
|
+
const context = this.currentLifecycleContext();
|
|
1450
1836
|
try {
|
|
1451
|
-
await this.applyLatencyPolicy();
|
|
1452
|
-
this.postStats();
|
|
1837
|
+
if (!(await raceLifecycleOperation(this.applyLatencyPolicy(context), context.signal)).cancelled && this.isLifecycleContextCurrent(context) && this.state === "started") this.postStats();
|
|
1453
1838
|
} catch (cause) {
|
|
1839
|
+
if (!this.isLifecycleContextCurrent(context)) return;
|
|
1454
1840
|
this.fail("mse", "RIVMUX_MSE_LATENCY_POLICY_FAILED", "MSE latency policy failed.", true, cause);
|
|
1455
1841
|
} finally {
|
|
1456
1842
|
this.statsTickInFlight = false;
|
|
1457
1843
|
}
|
|
1458
1844
|
}
|
|
1845
|
+
currentLifecycleContext() {
|
|
1846
|
+
return {
|
|
1847
|
+
generation: this.lifecycleGeneration,
|
|
1848
|
+
signal: this.lifecycleAbortController.signal
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
isLifecycleContextCurrent(context) {
|
|
1852
|
+
return !context.signal.aborted && context.generation === this.lifecycleGeneration;
|
|
1853
|
+
}
|
|
1459
1854
|
fail(kind, code, message, terminal, cause) {
|
|
1460
1855
|
const error = cause === void 0 ? {
|
|
1461
1856
|
kind,
|
|
@@ -1472,6 +1867,7 @@ var RuntimeWorker = class {
|
|
|
1472
1867
|
this.failWithError(error);
|
|
1473
1868
|
}
|
|
1474
1869
|
failWithError(error) {
|
|
1870
|
+
if (this.state === "destroyed") return;
|
|
1475
1871
|
if (error.terminal) this.enterFatalErrorState();
|
|
1476
1872
|
this.post({
|
|
1477
1873
|
type: "error",
|
|
@@ -1479,11 +1875,12 @@ var RuntimeWorker = class {
|
|
|
1479
1875
|
});
|
|
1480
1876
|
}
|
|
1481
1877
|
enterFatalErrorState() {
|
|
1482
|
-
if (this.state === "fatal-error") return;
|
|
1878
|
+
if (this.state === "fatal-error" || this.state === "destroyed") return;
|
|
1483
1879
|
this.state = "fatal-error";
|
|
1484
|
-
this.
|
|
1485
|
-
this.
|
|
1486
|
-
this.
|
|
1880
|
+
this.invalidateLifecycle();
|
|
1881
|
+
this.session.discardAppend();
|
|
1882
|
+
this.fatalCleanupPromise = this.closeLoader().catch(() => void 0);
|
|
1883
|
+
this.session.destroyMse();
|
|
1487
1884
|
this.latencyController?.reset();
|
|
1488
1885
|
this.videoState = void 0;
|
|
1489
1886
|
this.lastLatencyMetrics = {};
|
|
@@ -1491,15 +1888,12 @@ var RuntimeWorker = class {
|
|
|
1491
1888
|
post(message, transfer) {
|
|
1492
1889
|
this.port.postMessage(message, transfer);
|
|
1493
1890
|
}
|
|
1891
|
+
invalidateLifecycle() {
|
|
1892
|
+
this.lifecycleGeneration += 1;
|
|
1893
|
+
this.lifecycleAbortController.abort();
|
|
1894
|
+
this.lifecycleAbortController = new AbortController();
|
|
1895
|
+
}
|
|
1494
1896
|
};
|
|
1495
|
-
function getNetworkErrorCode(cause) {
|
|
1496
|
-
return cause instanceof HttpFlvLoaderError ? cause.code : "RIVMUX_HTTP_LOADER_FAILED";
|
|
1497
|
-
}
|
|
1498
|
-
function getNetworkIdleMs(stats, nowMs) {
|
|
1499
|
-
const markerMs = stats?.lastChunkAtMs ?? stats?.startedAtMs;
|
|
1500
|
-
if (markerMs === void 0) return;
|
|
1501
|
-
return Math.max(nowMs - markerMs, 0);
|
|
1502
|
-
}
|
|
1503
1897
|
function serializeCause(cause) {
|
|
1504
1898
|
if (cause instanceof Error) return {
|
|
1505
1899
|
name: cause.name,
|
|
@@ -1507,12 +1901,6 @@ function serializeCause(cause) {
|
|
|
1507
1901
|
};
|
|
1508
1902
|
return cause;
|
|
1509
1903
|
}
|
|
1510
|
-
function isQuotaExceededError(cause) {
|
|
1511
|
-
return isNamedError(cause, "QuotaExceededError");
|
|
1512
|
-
}
|
|
1513
|
-
function isNamedError(value, name) {
|
|
1514
|
-
return typeof value === "object" && value !== null && "name" in value && value.name === name;
|
|
1515
|
-
}
|
|
1516
1904
|
function detectWorkerRuntime() {
|
|
1517
1905
|
if (typeof fetch !== "function") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_FETCH", "Fetch is not available in this worker runtime.");
|
|
1518
1906
|
if (typeof ReadableStream === "undefined") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_READABLE_STREAM", "ReadableStream is not available in this worker runtime.");
|
|
@@ -1520,7 +1908,6 @@ function detectWorkerRuntime() {
|
|
|
1520
1908
|
if (typeof MediaSource === "undefined") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_MSE", "MediaSource is not available in this worker runtime.");
|
|
1521
1909
|
if (MediaSource.canConstructInDedicatedWorker !== true) return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_WORKER_MSE", "MediaSource cannot be constructed in this worker runtime.");
|
|
1522
1910
|
if (typeof MediaSource.isTypeSupported !== "function") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK", "MediaSource.isTypeSupported is not available in this worker runtime.");
|
|
1523
|
-
for (const requirement of REQUIRED_MSE_MIME_TYPES) if (!MediaSource.isTypeSupported(requirement.mimeType)) return createUnsupportedRuntimeError(requirement.unsupportedCode, `MSE does not support ${requirement.mimeType}.`);
|
|
1524
1911
|
}
|
|
1525
1912
|
function createUnsupportedRuntimeError(code, message) {
|
|
1526
1913
|
return {
|
|
@@ -1536,38 +1923,6 @@ function createLatencyController(options) {
|
|
|
1536
1923
|
playback: options.playback
|
|
1537
1924
|
});
|
|
1538
1925
|
}
|
|
1539
|
-
function mergeOptions(current, updates) {
|
|
1540
|
-
return {
|
|
1541
|
-
playback: {
|
|
1542
|
-
...current.playback,
|
|
1543
|
-
...updates.playback
|
|
1544
|
-
},
|
|
1545
|
-
latency: {
|
|
1546
|
-
...current.latency,
|
|
1547
|
-
...updates.latency
|
|
1548
|
-
},
|
|
1549
|
-
network: {
|
|
1550
|
-
...current.network,
|
|
1551
|
-
...updates.network,
|
|
1552
|
-
headers: {
|
|
1553
|
-
...current.network.headers,
|
|
1554
|
-
...updates.network?.headers
|
|
1555
|
-
},
|
|
1556
|
-
retry: {
|
|
1557
|
-
...current.network.retry,
|
|
1558
|
-
...updates.network?.retry
|
|
1559
|
-
}
|
|
1560
|
-
},
|
|
1561
|
-
runtime: {
|
|
1562
|
-
...current.runtime,
|
|
1563
|
-
...updates.runtime
|
|
1564
|
-
},
|
|
1565
|
-
diagnostics: {
|
|
1566
|
-
...current.diagnostics,
|
|
1567
|
-
...updates.diagnostics
|
|
1568
|
-
}
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
1571
1926
|
//#endregion
|
|
1572
1927
|
//#region src/worker-entry.ts
|
|
1573
1928
|
function startRuntimeWorker(scope) {
|