@rivmux/runtime-worker 0.3.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.
@@ -1,15 +1,3 @@
1
- //#region \0rolldown/runtime.js
2
- var __defProp = Object.defineProperty;
3
- var __exportAll = (all, no_symbols) => {
4
- let target = {};
5
- for (var name in all) __defProp(target, name, {
6
- get: all[name],
7
- enumerable: true
8
- });
9
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
- return target;
11
- };
12
- //#endregion
13
1
  //#region src/latency/buffer-ranges.ts
14
2
  function normalizeBufferedRanges(input) {
15
3
  if (isBufferedRangeArray(input)) return input.filter(isValidRange).map((range) => ({
@@ -153,6 +141,111 @@ const PLAYBACK_RATE_RESTORE_THRESHOLD_SECONDS = .1;
153
141
  const SEEK_COOLDOWN_MS = 1e3;
154
142
  const SEEK_MIN_DELTA_SECONDS = .1;
155
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
156
249
  //#region src/loader/retry-policy.ts
157
250
  function createRetryPolicy(input) {
158
251
  return {
@@ -361,25 +454,25 @@ function createMp4VideoMime(codec) {
361
454
  function createMp4AudioMime(codec) {
362
455
  return `audio/mp4; codecs="${codec}"`;
363
456
  }
364
- const REQUIRED_MSE_MIME_TYPES = [{
365
- mediaType: "video",
366
- mimeType: createMp4VideoMime("avc1.42C01E"),
367
- unsupportedCode: "RIVMUX_UNSUPPORTED_MSE_VIDEO_MIME"
368
- }, {
369
- mediaType: "audio",
370
- mimeType: createMp4AudioMime("mp4a.40.2"),
371
- unsupportedCode: "RIVMUX_UNSUPPORTED_MSE_AUDIO_MIME"
372
- }];
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
+ };
373
465
  function isMseSupported(mimeType) {
374
466
  return typeof MediaSource !== "undefined" && typeof MediaSource.isTypeSupported === "function" && MediaSource.isTypeSupported(mimeType);
375
467
  }
376
- function assertMseSupport(mimeType) {
468
+ function assertMseRuntimeSupport() {
377
469
  if (typeof MediaSource === "undefined") throw new Error("MediaSource is not available in this worker.");
378
470
  if (MediaSource.canConstructInDedicatedWorker !== true) throw new Error("MediaSource cannot be constructed in this dedicated worker.");
379
- if (!isMseSupported(mimeType)) throw new Error(`MSE does not support ${mimeType}.`);
471
+ if (typeof MediaSource.isTypeSupported !== "function") throw new Error("MediaSource.isTypeSupported is not available in this worker.");
380
472
  }
381
- function assertRequiredMseSupport() {
382
- for (const requirement of REQUIRED_MSE_MIME_TYPES) assertMseSupport(requirement.mimeType);
473
+ function assertMseSupport(mimeType) {
474
+ assertMseRuntimeSupport();
475
+ if (!isMseSupported(mimeType)) throw new MseUnsupportedMimeError(mimeType);
383
476
  }
384
477
  //#endregion
385
478
  //#region src/mse/source-buffer-queue.ts
@@ -530,7 +623,7 @@ var MseController = class {
530
623
  return Array.from(this.queues.values()).reduce((total, queue) => total + queue.bufferedRanges.length, 0);
531
624
  }
532
625
  async createMediaSourceHandle() {
533
- assertRequiredMseSupport();
626
+ assertMseRuntimeSupport();
534
627
  const mediaSource = new MediaSource();
535
628
  this.mediaSource = mediaSource;
536
629
  const handle = mediaSource.handle;
@@ -670,7 +763,7 @@ function coreWarningToPlayerWarning(warning) {
670
763
  };
671
764
  }
672
765
  function normalizeCoreEvent(value) {
673
- if (!isRecord$1(value) || typeof value.type !== "string") throw new TypeError("Transmux core event is missing a string type.");
766
+ if (!isRecord(value) || typeof value.type !== "string") throw new TypeError("Transmux core event is missing a string type.");
674
767
  const data = value.data;
675
768
  switch (value.type) {
676
769
  case "probeResult": return {
@@ -689,10 +782,8 @@ function normalizeCoreEvent(value) {
689
782
  type: "mediaSegment",
690
783
  data: normalizeMediaSegment(data)
691
784
  };
692
- case "videoConfig":
693
- case "audioConfig":
694
- case "videoSample":
695
- case "audioSample":
785
+ case "trackConfig":
786
+ case "sample":
696
787
  case "metadata":
697
788
  case "discontinuity": return {
698
789
  type: value.type,
@@ -710,7 +801,7 @@ function normalizeCoreEvent(value) {
710
801
  }
711
802
  }
712
803
  function normalizeInitSegment(value) {
713
- if (!isRecord$1(value)) throw new TypeError("Transmux core initSegment event payload must be an object.");
804
+ if (!isRecord(value)) throw new TypeError("Transmux core initSegment event payload must be an object.");
714
805
  return {
715
806
  track: normalizeTrackKind(value.track),
716
807
  codec: normalizeRequiredPrimitive(value.codec, "string", "initSegment codec"),
@@ -719,7 +810,7 @@ function normalizeInitSegment(value) {
719
810
  };
720
811
  }
721
812
  function normalizeMediaSegment(value) {
722
- if (!isRecord$1(value)) throw new TypeError("Transmux core mediaSegment event payload must be an object.");
813
+ if (!isRecord(value)) throw new TypeError("Transmux core mediaSegment event payload must be an object.");
723
814
  return {
724
815
  track: normalizeTrackKind(value.track),
725
816
  dtsStartMs: normalizeRequiredPrimitive(value.dtsStartMs, "number", "mediaSegment dtsStartMs"),
@@ -729,7 +820,7 @@ function normalizeMediaSegment(value) {
729
820
  };
730
821
  }
731
822
  function normalizeProbeResult(value) {
732
- if (!isRecord$1(value) || value.container !== "flv" && value.container !== "mpegts") throw new TypeError("Transmux core probe result has an unsupported container.");
823
+ if (!isRecord(value) || value.container !== "flv" && value.container !== "mpegts") throw new TypeError("Transmux core probe result has an unsupported container.");
733
824
  const result = { container: value.container };
734
825
  const video = normalizeOptionalString(value.video, [
735
826
  "avc",
@@ -748,7 +839,7 @@ function normalizeProbeResult(value) {
748
839
  return result;
749
840
  }
750
841
  function normalizeMediaInfo(value) {
751
- if (!isRecord$1(value)) throw new TypeError("Transmux core mediaInfo event payload must be an object.");
842
+ if (!isRecord(value)) throw new TypeError("Transmux core mediaInfo event payload must be an object.");
752
843
  const result = { ...normalizeProbeResult(value) };
753
844
  const videoCodec = normalizeOptionalPrimitive(value.videoCodec, "string");
754
845
  const audioCodec = normalizeOptionalPrimitive(value.audioCodec, "string");
@@ -765,14 +856,14 @@ function normalizeMediaInfo(value) {
765
856
  return result;
766
857
  }
767
858
  function normalizeWarning(value) {
768
- if (!isRecord$1(value) || typeof value.code !== "string" || typeof value.message !== "string") throw new TypeError("Transmux core warning payload must include code and message.");
859
+ if (!isRecord(value) || typeof value.code !== "string" || typeof value.message !== "string") throw new TypeError("Transmux core warning payload must include code and message.");
769
860
  return {
770
861
  code: value.code,
771
862
  message: value.message
772
863
  };
773
864
  }
774
865
  function normalizeError(value) {
775
- if (!isRecord$1(value) || typeof value.code !== "string" || typeof value.message !== "string") throw new TypeError("Transmux core error payload must include code and message.");
866
+ if (!isRecord(value) || typeof value.code !== "string" || typeof value.message !== "string") throw new TypeError("Transmux core error payload must include code and message.");
776
867
  return {
777
868
  code: normalizeCoreErrorCode(value.code),
778
869
  message: value.message
@@ -829,29 +920,11 @@ function normalizeBytes(value, field) {
829
920
  if (Array.isArray(value) && value.every((entry) => Number.isInteger(entry) && entry >= 0 && entry <= 255)) return new Uint8Array(value);
830
921
  throw new TypeError(`Expected ${field} to be Uint8Array-compatible bytes.`);
831
922
  }
832
- function isRecord$1(value) {
923
+ function isRecord(value) {
833
924
  return typeof value === "object" && value !== null;
834
925
  }
835
926
  //#endregion
836
- //#region \0wasm-helpers.js
837
- function instantiate(source, imports, stream) {
838
- const instantiate = WebAssembly[stream ? "instantiateStreaming" : "instantiate"];
839
- return instantiate(source, imports).then(({ instance }) => instance);
840
- }
841
- function loadWasmModule(sync, fileUrl, src, imports) {
842
- let buf = null;
843
- if (fileUrl) return instantiate(fetch(fileUrl), imports, true);
844
- const raw = globalThis.atob(src);
845
- const len = raw.length;
846
- buf = new Uint8Array(new ArrayBuffer(len));
847
- for (let i = 0; i < len; i++) buf[i] = raw.charCodeAt(i);
848
- if (sync) {
849
- const mod = new WebAssembly.Module(buf);
850
- return new WebAssembly.Instance(mod, imports);
851
- } else return instantiate(buf, imports);
852
- }
853
- //#endregion
854
- //#region ../../crates/transmux-core/dist/rivmux_transmux_core_bg.js
927
+ //#region ../../crates/transmux-core/dist/rivmux_transmux_core.js
855
928
  var TransmuxCore = class {
856
929
  __destroy_into_raw() {
857
930
  const ptr = this.__wbg_ptr;
@@ -870,16 +943,9 @@ var TransmuxCore = class {
870
943
  * @returns {any}
871
944
  */
872
945
  flush() {
873
- try {
874
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
875
- wasm.transmuxcore_flush(retptr, this.__wbg_ptr);
876
- var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
877
- var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
878
- if (getDataViewMemory0().getInt32(retptr + 8, true)) throw takeObject(r1);
879
- return takeObject(r0);
880
- } finally {
881
- wasm.__wbindgen_add_to_stack_pointer(16);
882
- }
946
+ const ret = wasm.transmuxcore_flush(this.__wbg_ptr);
947
+ if (ret[2]) throw takeFromExternrefTable0(ret[1]);
948
+ return takeFromExternrefTable0(ret[0]);
883
949
  }
884
950
  constructor() {
885
951
  const ret = wasm.transmuxcore_new();
@@ -892,79 +958,74 @@ var TransmuxCore = class {
892
958
  * @returns {any}
893
959
  */
894
960
  pushChunk(data) {
895
- try {
896
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
897
- const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_export);
898
- const len0 = WASM_VECTOR_LEN;
899
- wasm.transmuxcore_pushChunk(retptr, this.__wbg_ptr, ptr0, len0);
900
- var r0 = getDataViewMemory0().getInt32(retptr + 0, true);
901
- var r1 = getDataViewMemory0().getInt32(retptr + 4, true);
902
- if (getDataViewMemory0().getInt32(retptr + 8, true)) throw takeObject(r1);
903
- return takeObject(r0);
904
- } finally {
905
- wasm.__wbindgen_add_to_stack_pointer(16);
906
- }
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]);
907
966
  }
908
967
  reset() {
909
968
  wasm.transmuxcore_reset(this.__wbg_ptr);
910
969
  }
911
970
  };
912
971
  if (Symbol.dispose) TransmuxCore.prototype[Symbol.dispose] = TransmuxCore.prototype.free;
913
- function __wbg_Error_fdd633d4bb5dd76a(arg0, arg1) {
914
- return addHeapObject(Error(getStringFromWasm0(arg0, arg1)));
915
- }
916
- function __wbg_String_8564e559799eccda(arg0, arg1) {
917
- const ptr1 = passStringToWasm0(String(getObject(arg1)), wasm.__wbindgen_export, wasm.__wbindgen_export2);
918
- const len1 = WASM_VECTOR_LEN;
919
- getDataViewMemory0().setInt32(arg0 + 4, len1, true);
920
- getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
921
- }
922
- function __wbg___wbindgen_throw_ea4887a5f8f9a9db(arg0, arg1) {
923
- throw new Error(getStringFromWasm0(arg0, arg1));
924
- }
925
- function __wbg_new_2e117a478906f062() {
926
- return addHeapObject(/* @__PURE__ */ new Object());
927
- }
928
- function __wbg_new_36e147a8ced3c6e0() {
929
- return addHeapObject(new Array());
930
- }
931
- function __wbg_set_6be42768c690e380(arg0, arg1, arg2) {
932
- getObject(arg0)[takeObject(arg1)] = takeObject(arg2);
933
- }
934
- function __wbg_set_dc601f4a69da0bc2(arg0, arg1, arg2) {
935
- getObject(arg0)[arg1 >>> 0] = takeObject(arg2);
936
- }
937
- function __wbindgen_cast_0000000000000001(arg0) {
938
- return addHeapObject(arg0);
939
- }
940
- function __wbindgen_cast_0000000000000002(arg0) {
941
- return addHeapObject(arg0);
942
- }
943
- function __wbindgen_cast_0000000000000003(arg0, arg1) {
944
- return addHeapObject(getStringFromWasm0(arg0, arg1));
945
- }
946
- function __wbindgen_object_clone_ref(arg0) {
947
- return addHeapObject(getObject(arg0));
948
- }
949
- function __wbindgen_object_drop_ref(arg0) {
950
- takeObject(arg0);
972
+ function __wbg_get_imports() {
973
+ return {
974
+ __proto__: null,
975
+ "./rivmux_transmux_core_bg.js": {
976
+ __proto__: null,
977
+ __wbg_Error_fdd633d4bb5dd76a: function(arg0, arg1) {
978
+ return Error(getStringFromWasm0(arg0, arg1));
979
+ },
980
+ __wbg_Number_c4bdf66bb78f7977: function(arg0) {
981
+ return Number(arg0);
982
+ },
983
+ __wbg_String_8564e559799eccda: function(arg0, arg1) {
984
+ const ptr1 = passStringToWasm0(String(arg1), wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
985
+ const len1 = WASM_VECTOR_LEN;
986
+ getDataViewMemory0().setInt32(arg0 + 4, len1, true);
987
+ getDataViewMemory0().setInt32(arg0 + 0, ptr1, true);
988
+ },
989
+ __wbg___wbindgen_throw_ea4887a5f8f9a9db: function(arg0, arg1) {
990
+ throw new Error(getStringFromWasm0(arg0, arg1));
991
+ },
992
+ __wbg_new_2e117a478906f062: function() {
993
+ return /* @__PURE__ */ new Object();
994
+ },
995
+ __wbg_new_36e147a8ced3c6e0: function() {
996
+ return new Array();
997
+ },
998
+ __wbg_set_6be42768c690e380: function(arg0, arg1, arg2) {
999
+ arg0[arg1] = arg2;
1000
+ },
1001
+ __wbg_set_dc601f4a69da0bc2: function(arg0, arg1, arg2) {
1002
+ arg0[arg1 >>> 0] = arg2;
1003
+ },
1004
+ __wbindgen_cast_0000000000000001: function(arg0) {
1005
+ return arg0;
1006
+ },
1007
+ __wbindgen_cast_0000000000000002: function(arg0) {
1008
+ return arg0;
1009
+ },
1010
+ __wbindgen_cast_0000000000000003: function(arg0, arg1) {
1011
+ return getStringFromWasm0(arg0, arg1);
1012
+ },
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);
1021
+ }
1022
+ }
1023
+ };
951
1024
  }
952
1025
  const TransmuxCoreFinalization = typeof FinalizationRegistry === "undefined" ? {
953
1026
  register: () => {},
954
1027
  unregister: () => {}
955
1028
  } : new FinalizationRegistry((ptr) => wasm.__wbg_transmuxcore_free(ptr, 1));
956
- function addHeapObject(obj) {
957
- if (heap_next === heap.length) heap.push(heap.length + 1);
958
- const idx = heap_next;
959
- heap_next = heap[idx];
960
- heap[idx] = obj;
961
- return idx;
962
- }
963
- function dropObject(idx) {
964
- if (idx < 1028) return;
965
- heap[idx] = heap_next;
966
- heap_next = idx;
967
- }
968
1029
  let cachedDataViewMemory0 = null;
969
1030
  function getDataViewMemory0() {
970
1031
  if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || cachedDataViewMemory0.buffer.detached === void 0 && cachedDataViewMemory0.buffer !== wasm.memory.buffer) cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
@@ -978,12 +1039,6 @@ function getUint8ArrayMemory0() {
978
1039
  if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
979
1040
  return cachedUint8ArrayMemory0;
980
1041
  }
981
- function getObject(idx) {
982
- return heap[idx];
983
- }
984
- let heap = new Array(1024).fill(void 0);
985
- heap.push(void 0, null, true, false);
986
- let heap_next = heap.length;
987
1042
  function passArray8ToWasm0(arg, malloc) {
988
1043
  const ptr = malloc(arg.length * 1, 1) >>> 0;
989
1044
  getUint8ArrayMemory0().set(arg, ptr / 1);
@@ -1018,10 +1073,10 @@ function passStringToWasm0(arg, malloc, realloc) {
1018
1073
  WASM_VECTOR_LEN = offset;
1019
1074
  return ptr;
1020
1075
  }
1021
- function takeObject(idx) {
1022
- const ret = getObject(idx);
1023
- dropObject(idx);
1024
- return ret;
1076
+ function takeFromExternrefTable0(idx) {
1077
+ const value = wasm.__wbindgen_externrefs.get(idx);
1078
+ wasm.__externref_table_dealloc(idx);
1079
+ return value;
1025
1080
  }
1026
1081
  let cachedTextDecoder = new TextDecoder("utf-8", {
1027
1082
  ignoreBOM: true,
@@ -1053,123 +1108,534 @@ if (!("encodeInto" in cachedTextEncoder)) cachedTextEncoder.encodeInto = functio
1053
1108
  };
1054
1109
  let WASM_VECTOR_LEN = 0;
1055
1110
  let wasm;
1056
- function __wbg_set_wasm(val) {
1057
- wasm = val;
1111
+ function __wbg_finalize_init(instance, module) {
1112
+ wasm = instance.exports;
1113
+ cachedDataViewMemory0 = null;
1114
+ cachedUint8ArrayMemory0 = null;
1115
+ wasm.__wbindgen_start();
1116
+ return wasm;
1117
+ }
1118
+ async function __wbg_load(module, imports) {
1119
+ if (typeof Response === "function" && module instanceof Response) {
1120
+ if (typeof WebAssembly.instantiateStreaming === "function") try {
1121
+ return await WebAssembly.instantiateStreaming(module, imports);
1122
+ } catch (e) {
1123
+ if (module.ok && expectedResponseType(module.type) && module.headers.get("Content-Type") !== "application/wasm") console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
1124
+ else throw e;
1125
+ }
1126
+ const bytes = await module.arrayBuffer();
1127
+ return await WebAssembly.instantiate(bytes, imports);
1128
+ } else {
1129
+ const instance = await WebAssembly.instantiate(module, imports);
1130
+ if (instance instanceof WebAssembly.Instance) return {
1131
+ instance,
1132
+ module
1133
+ };
1134
+ else return instance;
1135
+ }
1136
+ function expectedResponseType(type) {
1137
+ switch (type) {
1138
+ case "basic":
1139
+ case "cors":
1140
+ case "default": return true;
1141
+ }
1142
+ return false;
1143
+ }
1144
+ }
1145
+ async function __wbg_init(module_or_path) {
1146
+ if (wasm !== void 0) return wasm;
1147
+ if (module_or_path !== void 0) if (Object.getPrototypeOf(module_or_path) === Object.prototype) ({module_or_path} = module_or_path);
1148
+ else console.warn("using deprecated parameters for the initialization function; pass a single object instead");
1149
+ if (module_or_path === void 0) module_or_path = new URL("rivmux_transmux_core_bg.wasm", import.meta.url);
1150
+ const imports = __wbg_get_imports();
1151
+ if (typeof module_or_path === "string" || typeof Request === "function" && module_or_path instanceof Request || typeof URL === "function" && module_or_path instanceof URL) module_or_path = fetch(module_or_path);
1152
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
1153
+ return __wbg_finalize_init(instance, module);
1058
1154
  }
1059
- //#endregion
1060
- //#region ../../crates/transmux-core/dist/rivmux_transmux_core_bg.wasm
1061
- var rivmux_transmux_core_bg_exports = /* @__PURE__ */ __exportAll({
1062
- __abort_handler: () => __abort_handler,
1063
- __instance_terminated: () => __instance_terminated,
1064
- __wbg_transmuxcore_free: () => __wbg_transmuxcore_free,
1065
- __wbindgen_add_to_stack_pointer: () => __wbindgen_add_to_stack_pointer,
1066
- __wbindgen_export: () => __wbindgen_export,
1067
- __wbindgen_export2: () => __wbindgen_export2,
1068
- memory: () => memory,
1069
- transmuxcore_destroy: () => transmuxcore_destroy,
1070
- transmuxcore_flush: () => transmuxcore_flush,
1071
- transmuxcore_new: () => transmuxcore_new,
1072
- transmuxcore_pushChunk: () => transmuxcore_pushChunk,
1073
- transmuxcore_reset: () => transmuxcore_reset
1074
- });
1075
- function __wasm_init(imports) {
1076
- return loadWasmModule(false, new URL("rivmux-transmux-core.wasm", import.meta.url), null, imports);
1077
- }
1078
- const instance = await __wasm_init({ "./rivmux_transmux_core_bg.js": {
1079
- "__wbindgen_object_drop_ref": __wbindgen_object_drop_ref,
1080
- "__wbg_set_dc601f4a69da0bc2": __wbg_set_dc601f4a69da0bc2,
1081
- "__wbg_set_6be42768c690e380": __wbg_set_6be42768c690e380,
1082
- "__wbindgen_object_clone_ref": __wbindgen_object_clone_ref,
1083
- "__wbg_String_8564e559799eccda": __wbg_String_8564e559799eccda,
1084
- "__wbg_new_36e147a8ced3c6e0": __wbg_new_36e147a8ced3c6e0,
1085
- "__wbg_new_2e117a478906f062": __wbg_new_2e117a478906f062,
1086
- "__wbg___wbindgen_throw_ea4887a5f8f9a9db": __wbg___wbindgen_throw_ea4887a5f8f9a9db,
1087
- "__wbg_Error_fdd633d4bb5dd76a": __wbg_Error_fdd633d4bb5dd76a,
1088
- "__wbindgen_cast_0000000000000001": __wbindgen_cast_0000000000000001,
1089
- "__wbindgen_cast_0000000000000002": __wbindgen_cast_0000000000000002,
1090
- "__wbindgen_cast_0000000000000003": __wbindgen_cast_0000000000000003
1091
- } });
1092
- const memory = instance.exports.memory;
1093
- const __wbg_transmuxcore_free = instance.exports.__wbg_transmuxcore_free;
1094
- const transmuxcore_destroy = instance.exports.transmuxcore_destroy;
1095
- const transmuxcore_flush = instance.exports.transmuxcore_flush;
1096
- const transmuxcore_new = instance.exports.transmuxcore_new;
1097
- const transmuxcore_pushChunk = instance.exports.transmuxcore_pushChunk;
1098
- const transmuxcore_reset = instance.exports.transmuxcore_reset;
1099
- const __abort_handler = instance.exports.__abort_handler;
1100
- const __instance_terminated = instance.exports.__instance_terminated;
1101
- const __wbindgen_export = instance.exports.__wbindgen_export;
1102
- const __wbindgen_export2 = instance.exports.__wbindgen_export2;
1103
- const __wbindgen_add_to_stack_pointer = instance.exports.__wbindgen_add_to_stack_pointer;
1104
- //#endregion
1105
- //#region ../../crates/transmux-core/dist/rivmux_transmux_core.js
1106
- __wbg_set_wasm(rivmux_transmux_core_bg_exports);
1107
1155
  //#endregion
1108
1156
  //#region src/wasm/wasm-loader.ts
1109
1157
  function createWasmTransmuxCoreHost(Core) {
1110
1158
  if (Core === void 0) throw new TypeError("WASM transmux core constructor is not available.");
1111
1159
  return new WasmTransmuxCoreHost(Core);
1112
1160
  }
1113
- function createBundledWasmTransmuxCoreHost() {
1114
- return createWasmTransmuxCoreHost(TransmuxCore);
1115
- }
1116
1161
  async function loadWasmTransmuxCoreHost(wasmUrl) {
1117
- if (wasmUrl === void 0) return createBundledWasmTransmuxCoreHost();
1118
- const wasmModule = normalizeWasmBindgenModule(await nativeDynamicImport(toWasmBindgenGlueUrl(wasmUrl)));
1119
- await wasmModule.default(wasmUrl);
1120
- return createWasmTransmuxCoreHost(wasmModule.TransmuxCore);
1121
- }
1122
- function toWasmBindgenGlueUrl(wasmUrl) {
1123
- const url = new URL(wasmUrl, globalThis.location?.href ?? "http://localhost/");
1124
- const path = url.pathname;
1125
- url.pathname = path.endsWith("_bg.wasm") ? `${path.slice(0, -8)}.js` : path.replace(/\.wasm$/u, ".js");
1126
- return url.href;
1127
- }
1128
- function normalizeWasmBindgenModule(value) {
1129
- if (!isRecord(value) || typeof value.default !== "function" || typeof value.TransmuxCore !== "function") throw new TypeError("WASM transmux module must export default init and TransmuxCore.");
1130
- return value;
1131
- }
1132
- function nativeDynamicImport(url) {
1133
- return new Function("url", "return import(url)")(url);
1162
+ await __wbg_init(wasmUrl ?? new URL("./rivmux-transmux-core.wasm", import.meta.url));
1163
+ return createWasmTransmuxCoreHost(TransmuxCore);
1134
1164
  }
1135
- function isRecord(value) {
1136
- return typeof value === "object" && value !== null;
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;
1137
1257
  }
1138
1258
  //#endregion
1139
- //#region src/runtime.ts
1140
- var RuntimeWorker = class {
1141
- port;
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;
1142
1317
  createMseController;
1143
1318
  createLoader;
1144
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;
1145
1584
  detectRuntime;
1146
1585
  now;
1147
1586
  state = "idle";
1148
1587
  url;
1149
1588
  options;
1150
- mse;
1151
- loader;
1152
- transmuxCore;
1589
+ session;
1153
1590
  latencyController;
1154
1591
  videoState;
1155
1592
  lastLatencyMetrics = {};
1156
1593
  statsTimer;
1157
1594
  statsTickInFlight = false;
1158
- loaderRunId = 0;
1159
- outputBytes = 0;
1160
1595
  appendQueueMaxLength = 0;
1161
1596
  appendQueueMaxBytes = 0;
1597
+ commandTail = Promise.resolve();
1598
+ lifecycleGeneration = 0;
1599
+ lifecycleAbortController = new AbortController();
1600
+ fatalCleanupPromise = Promise.resolve();
1162
1601
  constructor(port, dependencies = {}) {
1163
1602
  this.port = port;
1164
- this.createMseController = dependencies.createMseController ?? (() => new MseController());
1165
- this.createLoader = dependencies.createLoader ?? ((config) => new HttpFlvLoader(config));
1166
- this.createTransmuxCore = dependencies.createTransmuxCore ?? ((options) => loadWasmTransmuxCoreHost(options.runtime.wasmUrl));
1167
1603
  this.detectRuntime = dependencies.detectRuntime ?? detectWorkerRuntime;
1168
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;
1169
1631
  }
1170
- async handleCommand(command) {
1632
+ async executeCommand(command, context) {
1171
1633
  if (this.state === "destroyed") return;
1172
- if (this.state === "fatal-error" && command.type !== "destroy") return;
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
+ }
1173
1639
  try {
1174
1640
  switch (command.type) {
1175
1641
  case "init":
@@ -1187,10 +1653,10 @@ var RuntimeWorker = class {
1187
1653
  this.post({ type: "ready" });
1188
1654
  return;
1189
1655
  case "attach-media-source":
1190
- await this.attachMediaSource();
1656
+ await this.attachMediaSource(context);
1191
1657
  return;
1192
1658
  case "start":
1193
- await this.start();
1659
+ await this.start(context);
1194
1660
  return;
1195
1661
  case "stop":
1196
1662
  await this.stop();
@@ -1201,8 +1667,7 @@ var RuntimeWorker = class {
1201
1667
  return;
1202
1668
  case "video-state":
1203
1669
  this.videoState = command.state;
1204
- await this.applyLatencyPolicy();
1205
- this.postStats();
1670
+ if (!(await raceLifecycleOperation(this.applyLatencyPolicy(context), context.signal)).cancelled && this.isLifecycleContextCurrent(context) && this.state === "started") this.postStats();
1206
1671
  return;
1207
1672
  case "playback-control-result":
1208
1673
  this.latencyController?.recordPlaybackControlResult(command.result);
@@ -1215,57 +1680,55 @@ var RuntimeWorker = class {
1215
1680
  this.fail("runtime", "RIVMUX_WORKER_COMMAND_FAILED", "Worker command failed.", true, cause);
1216
1681
  }
1217
1682
  }
1218
- async attachMediaSource() {
1683
+ async attachMediaSource(context) {
1219
1684
  if (this.state === "idle") {
1220
1685
  this.fail("runtime", "RIVMUX_WORKER_NOT_INITIALIZED", "Worker must be initialized before attach.", true);
1221
1686
  return;
1222
1687
  }
1223
- if (this.mse === void 0) this.mse = this.createMseController();
1224
1688
  try {
1225
- const handle = await this.mse.createMediaSourceHandle();
1689
+ const handle = await this.session.attach(context);
1690
+ if (handle === void 0 || context.generation !== this.lifecycleGeneration) return;
1226
1691
  this.post({
1227
1692
  type: "media-source-handle",
1228
1693
  handle
1229
1694
  }, [handle]);
1230
1695
  this.state = "attached";
1231
1696
  } catch (cause) {
1697
+ if (context.generation !== this.lifecycleGeneration) return;
1232
1698
  this.fail("mse", "RIVMUX_MSE_ATTACH_FAILED", "MSE media source attachment failed.", true, cause);
1233
1699
  }
1234
1700
  }
1235
- async start() {
1701
+ async start(context) {
1236
1702
  const options = this.options;
1237
- if (this.mse === void 0 || options === void 0 || this.state === "idle" || this.state === "ready") {
1703
+ if (!this.session.hasMse || options === void 0 || this.state === "idle" || this.state === "ready") {
1238
1704
  this.fail("runtime", "RIVMUX_WORKER_START_REQUIRES_ATTACH", "Worker start requires an attached MediaSource.", true);
1239
1705
  return;
1240
1706
  }
1241
1707
  if (this.state === "started") return;
1242
- let transmuxCore;
1243
1708
  try {
1244
- const createdCore = await this.createTransmuxCore(options);
1245
- if (createdCore === void 0) {
1709
+ const transmuxCore = await this.session.createCore(options, context);
1710
+ if (context.generation !== this.lifecycleGeneration) return;
1711
+ if (transmuxCore === void 0) {
1246
1712
  this.fail("runtime", "RIVMUX_TRANSMUX_CORE_UNAVAILABLE", "Transmux core is not available.", true);
1247
1713
  return;
1248
1714
  }
1249
- transmuxCore = createdCore;
1715
+ this.session.start(transmuxCore, context);
1250
1716
  } catch (cause) {
1717
+ if (context.generation !== this.lifecycleGeneration) return;
1251
1718
  this.fail("runtime", "RIVMUX_TRANSMUX_CORE_UNAVAILABLE", "Transmux core is not available.", true, cause);
1252
1719
  return;
1253
1720
  }
1254
- this.transmuxCore?.destroy();
1255
- this.transmuxCore = transmuxCore;
1256
1721
  this.state = "started";
1257
- this.outputBytes = 0;
1258
1722
  this.appendQueueMaxLength = 0;
1259
1723
  this.appendQueueMaxBytes = 0;
1724
+ if ((await raceLifecycleOperation(this.applyLatencyPolicy(context), context.signal)).cancelled || !this.isLifecycleContextCurrent(context)) return;
1260
1725
  this.startStatsTimer();
1261
- await this.applyLatencyPolicy();
1262
1726
  this.postStats();
1263
- this.startLoader();
1727
+ this.startLoader(context);
1264
1728
  }
1265
1729
  async stop() {
1266
1730
  await this.closeLoader();
1267
- this.mse?.destroy();
1268
- this.mse = void 0;
1731
+ this.session.destroyMse();
1269
1732
  this.latencyController?.reset();
1270
1733
  this.videoState = void 0;
1271
1734
  this.lastLatencyMetrics = {};
@@ -1273,9 +1736,9 @@ var RuntimeWorker = class {
1273
1736
  this.post({ type: "stopped" });
1274
1737
  }
1275
1738
  async destroy() {
1739
+ await this.fatalCleanupPromise;
1276
1740
  await this.closeLoader();
1277
- this.mse?.destroy();
1278
- this.mse = void 0;
1741
+ this.session.destroyMse();
1279
1742
  this.latencyController?.reset();
1280
1743
  this.videoState = void 0;
1281
1744
  this.lastLatencyMetrics = {};
@@ -1283,199 +1746,70 @@ var RuntimeWorker = class {
1283
1746
  this.post({ type: "destroyed" });
1284
1747
  this.port.close();
1285
1748
  }
1286
- startLoader() {
1749
+ startLoader(context) {
1287
1750
  const options = this.options;
1288
1751
  const url = this.url;
1289
1752
  if (options === void 0 || url === void 0) {
1290
1753
  this.fail("runtime", "RIVMUX_WORKER_NOT_INITIALIZED", "Worker must be initialized before loader start.", true);
1291
1754
  return;
1292
1755
  }
1293
- const loader = this.createLoader({
1756
+ this.session.runLoader({
1294
1757
  url,
1295
1758
  network: options.network
1296
- });
1297
- const runId = this.loaderRunId + 1;
1298
- this.loaderRunId = runId;
1299
- this.loader = loader;
1300
- this.runLoader(loader, runId);
1301
- }
1302
- async runLoader(loader, runId) {
1303
- try {
1304
- await loader.open();
1305
- while (this.isCurrentLoader(loader, runId)) {
1306
- await this.applyLatencyPolicy();
1307
- const chunk = await loader.read();
1308
- if (chunk === null) break;
1309
- this.postStats(loader.stats);
1310
- if (!await this.processTransmuxEvents(this.transmuxCore?.pushChunk(chunk.bytes) ?? [])) {
1311
- await this.closeCurrentLoader(loader, runId);
1312
- return;
1313
- }
1314
- await this.applyLatencyPolicy();
1315
- this.postStats(loader.stats);
1316
- }
1317
- } catch (cause) {
1318
- if (!this.isCurrentLoader(loader, runId) || isAbortLikeError(cause)) return;
1319
- await this.closeCurrentLoader(loader, runId);
1320
- this.fail("network", getNetworkErrorCode(cause), "HTTP Fetch loader failed.", true, cause);
1321
- return;
1322
- } finally {
1323
- if (this.isCurrentLoader(loader, runId)) await this.closeCurrentLoader(loader, runId);
1324
- }
1759
+ }, context);
1325
1760
  }
1326
1761
  async closeLoader() {
1327
1762
  this.stopStatsTimer();
1328
- const loader = this.loader;
1329
- if (loader === void 0) return;
1330
- this.loader = void 0;
1331
- this.loaderRunId += 1;
1332
- this.transmuxCore?.destroy();
1333
- this.transmuxCore = void 0;
1334
- await loader.close();
1335
- }
1336
- async closeCurrentLoader(loader, runId) {
1337
- if (this.loader !== loader || this.loaderRunId !== runId) return;
1338
- this.stopStatsTimer();
1339
- this.loader = void 0;
1340
- this.loaderRunId += 1;
1341
- this.transmuxCore?.destroy();
1342
- this.transmuxCore = void 0;
1343
- await loader.close();
1344
- }
1345
- isCurrentLoader(loader, runId) {
1346
- return this.loader === loader && this.loaderRunId === runId && this.state === "started";
1763
+ await this.session.close();
1347
1764
  }
1348
1765
  postStats(loaderStats) {
1349
- const metrics = this.lastLatencyMetrics;
1350
1766
  const mseStats = this.collectMseStats();
1351
- const loaderSnapshot = loaderStats ?? this.loader?.stats;
1767
+ const loaderSnapshot = loaderStats ?? this.session.loaderStats;
1352
1768
  this.post({
1353
1769
  type: "stats",
1354
- stats: {
1355
- bytesReceived: loaderSnapshot?.bytesReceived ?? 0,
1356
- currentNetworkSpeed: loaderSnapshot?.currentNetworkSpeed ?? 0,
1357
- networkIdleMs: getNetworkIdleMs(loaderSnapshot, this.now()),
1358
- outputBytes: this.outputBytes,
1359
- appendQueueLength: mseStats.appendQueueLength,
1360
- appendQueueBytes: mseStats.appendQueueBytes,
1770
+ stats: createPlayerStats({
1771
+ loaderStats: loaderSnapshot,
1772
+ mseStats,
1773
+ latencyMetrics: this.lastLatencyMetrics,
1774
+ outputBytes: this.session.emittedBytes,
1361
1775
  appendQueueMaxLength: this.appendQueueMaxLength,
1362
1776
  appendQueueMaxBytes: this.appendQueueMaxBytes,
1363
- loaderPaused: this.loader?.paused ?? false,
1364
- sourceBufferUpdating: mseStats.sourceBufferUpdating,
1365
- sourceBufferCount: mseStats.sourceBufferCount,
1366
- bufferedStart: metrics.bufferedStart ?? this.mse?.bufferedStart,
1367
- bufferedEnd: metrics.bufferedEnd ?? this.mse?.bufferedEnd,
1368
- bufferedDuration: metrics.bufferedDuration ?? this.mse?.bufferedDuration,
1369
- bufferedRangeCount: mseStats.bufferedRangeCount,
1370
- currentTime: metrics.currentTime,
1371
- liveLatency: metrics.liveLatency,
1372
- playbackRate: metrics.playbackRate,
1373
- readyState: metrics.readyState,
1374
- droppedFrames: metrics.droppedFrames
1375
- }
1777
+ loaderPaused: this.session.loaderPaused,
1778
+ nowMs: this.now()
1779
+ })
1376
1780
  });
1377
1781
  }
1378
- async processTransmuxEvents(events) {
1379
- for (const event of events) switch (event.type) {
1380
- case "mediaInfo":
1381
- this.post({
1382
- type: "media-info",
1383
- mediaInfo: coreMediaInfoToPlayerMediaInfo(event.data)
1384
- });
1385
- break;
1386
- case "warning":
1387
- this.post({
1388
- type: "warning",
1389
- warning: coreWarningToPlayerWarning(event.data)
1390
- });
1391
- break;
1392
- case "fatalError":
1393
- this.failWithError(coreErrorToPlayerError(event.data));
1394
- return false;
1395
- case "initSegment":
1396
- if (!await this.appendToMse(() => this.mse?.appendInitSegment(event.data))) return false;
1397
- this.outputBytes += event.data.bytes.byteLength;
1398
- await this.applyLatencyPolicy();
1399
- break;
1400
- case "mediaSegment":
1401
- if (!await this.appendToMse(() => this.mse?.appendMediaSegment(event.data))) return false;
1402
- this.outputBytes += event.data.bytes.byteLength;
1403
- await this.applyLatencyPolicy();
1404
- break;
1405
- case "probeResult":
1406
- case "videoConfig":
1407
- case "audioConfig":
1408
- case "videoSample":
1409
- case "audioSample":
1410
- case "metadata":
1411
- case "discontinuity": break;
1412
- }
1413
- return true;
1414
- }
1415
1782
  collectMseStats() {
1416
- const appendQueueLength = this.mse?.appendQueueLength ?? 0;
1417
- const appendQueueBytes = this.mse?.appendQueueBytes ?? 0;
1418
- this.appendQueueMaxLength = Math.max(this.appendQueueMaxLength, appendQueueLength);
1419
- this.appendQueueMaxBytes = Math.max(this.appendQueueMaxBytes, appendQueueBytes);
1420
- return {
1421
- appendQueueLength,
1422
- appendQueueBytes,
1423
- sourceBufferUpdating: this.mse?.sourceBufferUpdating ?? false,
1424
- sourceBufferCount: this.mse?.sourceBufferCount ?? 0,
1425
- bufferedRangeCount: this.mse?.bufferedRangeCount ?? 0
1426
- };
1427
- }
1428
- async appendToMse(append) {
1429
- try {
1430
- await append();
1431
- return true;
1432
- } catch (cause) {
1433
- if (isQuotaExceededError(cause) && await this.retryAppendAfterQuotaCleanup(append)) return true;
1434
- this.fail("mse", "RIVMUX_MSE_APPEND_FAILED", "MSE append failed.", true, cause);
1435
- return false;
1436
- }
1437
- }
1438
- async retryAppendAfterQuotaCleanup(append) {
1439
- const mse = this.mse;
1440
- const cutoff = this.quotaCleanupCutoff();
1441
- if (mse === void 0 || cutoff === void 0 || cutoff <= 0) return false;
1442
- try {
1443
- await mse.cleanupBefore(cutoff, { force: true });
1444
- await append();
1445
- this.post({
1446
- type: "warning",
1447
- warning: {
1448
- code: "RIVMUX_MSE_QUOTA_RETRY",
1449
- message: "MSE quota was exceeded; old buffered ranges were cleaned before retrying append."
1450
- }
1451
- });
1452
- return true;
1453
- } catch {
1454
- return false;
1455
- }
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 };
1456
1791
  }
1457
1792
  quotaCleanupCutoff() {
1458
1793
  const backwardBuffer = this.options?.latency.backwardBuffer ?? 0;
1459
1794
  const currentTime = this.videoState?.currentTime;
1460
1795
  if (currentTime !== void 0 && Number.isFinite(currentTime)) return Math.max(0, currentTime - backwardBuffer);
1461
- const bufferedEnd = this.mse?.bufferedEnd;
1796
+ const bufferedEnd = this.session.collectMseStats().bufferedEnd;
1462
1797
  return bufferedEnd === void 0 ? void 0 : Math.max(0, bufferedEnd - backwardBuffer);
1463
1798
  }
1464
- async applyLatencyPolicy() {
1799
+ async applyLatencyPolicy(context) {
1465
1800
  const latencyController = this.latencyController;
1466
- const mse = this.mse;
1467
- if (latencyController === void 0 || mse === void 0) return;
1468
- const loader = this.loader;
1801
+ if (latencyController === void 0 || !this.session.hasMse) return;
1469
1802
  const evaluation = latencyController.evaluate({
1470
- ranges: mse.bufferedRanges,
1803
+ ranges: this.session.bufferedRanges,
1471
1804
  videoState: this.videoState,
1472
- loaderPaused: loader?.paused ?? false,
1805
+ loaderPaused: this.session.loaderPaused,
1473
1806
  nowMs: this.now()
1474
1807
  });
1475
1808
  this.lastLatencyMetrics = evaluation.metrics;
1476
- if (evaluation.cleanupBefore !== void 0) await mse.cleanupBefore(evaluation.cleanupBefore);
1477
- if (loader !== void 0 && evaluation.loaderCommand === "pause") loader.pause();
1478
- else if (loader !== void 0 && evaluation.loaderCommand === "resume") loader.resume();
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();
1479
1813
  if (evaluation.playbackControl !== void 0) this.post({
1480
1814
  type: "playback-control",
1481
1815
  action: evaluation.playbackControl
@@ -1498,15 +1832,25 @@ var RuntimeWorker = class {
1498
1832
  async emitStatsTick() {
1499
1833
  if (this.statsTickInFlight || this.state !== "started") return;
1500
1834
  this.statsTickInFlight = true;
1835
+ const context = this.currentLifecycleContext();
1501
1836
  try {
1502
- await this.applyLatencyPolicy();
1503
- this.postStats();
1837
+ if (!(await raceLifecycleOperation(this.applyLatencyPolicy(context), context.signal)).cancelled && this.isLifecycleContextCurrent(context) && this.state === "started") this.postStats();
1504
1838
  } catch (cause) {
1839
+ if (!this.isLifecycleContextCurrent(context)) return;
1505
1840
  this.fail("mse", "RIVMUX_MSE_LATENCY_POLICY_FAILED", "MSE latency policy failed.", true, cause);
1506
1841
  } finally {
1507
1842
  this.statsTickInFlight = false;
1508
1843
  }
1509
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
+ }
1510
1854
  fail(kind, code, message, terminal, cause) {
1511
1855
  const error = cause === void 0 ? {
1512
1856
  kind,
@@ -1523,6 +1867,7 @@ var RuntimeWorker = class {
1523
1867
  this.failWithError(error);
1524
1868
  }
1525
1869
  failWithError(error) {
1870
+ if (this.state === "destroyed") return;
1526
1871
  if (error.terminal) this.enterFatalErrorState();
1527
1872
  this.post({
1528
1873
  type: "error",
@@ -1530,11 +1875,12 @@ var RuntimeWorker = class {
1530
1875
  });
1531
1876
  }
1532
1877
  enterFatalErrorState() {
1533
- if (this.state === "fatal-error") return;
1878
+ if (this.state === "fatal-error" || this.state === "destroyed") return;
1534
1879
  this.state = "fatal-error";
1535
- this.closeLoader();
1536
- this.mse?.destroy();
1537
- this.mse = void 0;
1880
+ this.invalidateLifecycle();
1881
+ this.session.discardAppend();
1882
+ this.fatalCleanupPromise = this.closeLoader().catch(() => void 0);
1883
+ this.session.destroyMse();
1538
1884
  this.latencyController?.reset();
1539
1885
  this.videoState = void 0;
1540
1886
  this.lastLatencyMetrics = {};
@@ -1542,15 +1888,12 @@ var RuntimeWorker = class {
1542
1888
  post(message, transfer) {
1543
1889
  this.port.postMessage(message, transfer);
1544
1890
  }
1891
+ invalidateLifecycle() {
1892
+ this.lifecycleGeneration += 1;
1893
+ this.lifecycleAbortController.abort();
1894
+ this.lifecycleAbortController = new AbortController();
1895
+ }
1545
1896
  };
1546
- function getNetworkErrorCode(cause) {
1547
- return cause instanceof HttpFlvLoaderError ? cause.code : "RIVMUX_HTTP_LOADER_FAILED";
1548
- }
1549
- function getNetworkIdleMs(stats, nowMs) {
1550
- const markerMs = stats?.lastChunkAtMs ?? stats?.startedAtMs;
1551
- if (markerMs === void 0) return;
1552
- return Math.max(nowMs - markerMs, 0);
1553
- }
1554
1897
  function serializeCause(cause) {
1555
1898
  if (cause instanceof Error) return {
1556
1899
  name: cause.name,
@@ -1558,12 +1901,6 @@ function serializeCause(cause) {
1558
1901
  };
1559
1902
  return cause;
1560
1903
  }
1561
- function isQuotaExceededError(cause) {
1562
- return isNamedError(cause, "QuotaExceededError");
1563
- }
1564
- function isNamedError(value, name) {
1565
- return typeof value === "object" && value !== null && "name" in value && value.name === name;
1566
- }
1567
1904
  function detectWorkerRuntime() {
1568
1905
  if (typeof fetch !== "function") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_FETCH", "Fetch is not available in this worker runtime.");
1569
1906
  if (typeof ReadableStream === "undefined") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_READABLE_STREAM", "ReadableStream is not available in this worker runtime.");
@@ -1571,7 +1908,6 @@ function detectWorkerRuntime() {
1571
1908
  if (typeof MediaSource === "undefined") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_MSE", "MediaSource is not available in this worker runtime.");
1572
1909
  if (MediaSource.canConstructInDedicatedWorker !== true) return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_WORKER_MSE", "MediaSource cannot be constructed in this worker runtime.");
1573
1910
  if (typeof MediaSource.isTypeSupported !== "function") return createUnsupportedRuntimeError("RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK", "MediaSource.isTypeSupported is not available in this worker runtime.");
1574
- for (const requirement of REQUIRED_MSE_MIME_TYPES) if (!MediaSource.isTypeSupported(requirement.mimeType)) return createUnsupportedRuntimeError(requirement.unsupportedCode, `MSE does not support ${requirement.mimeType}.`);
1575
1911
  }
1576
1912
  function createUnsupportedRuntimeError(code, message) {
1577
1913
  return {
@@ -1587,38 +1923,6 @@ function createLatencyController(options) {
1587
1923
  playback: options.playback
1588
1924
  });
1589
1925
  }
1590
- function mergeOptions(current, updates) {
1591
- return {
1592
- playback: {
1593
- ...current.playback,
1594
- ...updates.playback
1595
- },
1596
- latency: {
1597
- ...current.latency,
1598
- ...updates.latency
1599
- },
1600
- network: {
1601
- ...current.network,
1602
- ...updates.network,
1603
- headers: {
1604
- ...current.network.headers,
1605
- ...updates.network?.headers
1606
- },
1607
- retry: {
1608
- ...current.network.retry,
1609
- ...updates.network?.retry
1610
- }
1611
- },
1612
- runtime: {
1613
- ...current.runtime,
1614
- ...updates.runtime
1615
- },
1616
- diagnostics: {
1617
- ...current.diagnostics,
1618
- ...updates.diagnostics
1619
- }
1620
- };
1621
- }
1622
1926
  //#endregion
1623
1927
  //#region src/worker-entry.ts
1624
1928
  function startRuntimeWorker(scope) {