@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.
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -4
- package/dist/index.js.map +1 -1
- package/dist/rivmux-runtime-worker.d.ts +14 -24
- package/dist/rivmux-runtime-worker.d.ts.map +1 -1
- package/dist/rivmux-runtime-worker.js +762 -458
- package/dist/rivmux-runtime-worker.js.map +1 -1
- package/dist/rivmux-transmux-core.wasm +0 -0
- package/package.json +6 -6
|
@@ -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
|
-
|
|
365
|
-
|
|
366
|
-
mimeType
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
|
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 (
|
|
471
|
+
if (typeof MediaSource.isTypeSupported !== "function") throw new Error("MediaSource.isTypeSupported is not available in this worker.");
|
|
380
472
|
}
|
|
381
|
-
function
|
|
382
|
-
|
|
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
|
-
|
|
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
|
|
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 "
|
|
693
|
-
case "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
923
|
+
function isRecord(value) {
|
|
833
924
|
return typeof value === "object" && value !== null;
|
|
834
925
|
}
|
|
835
926
|
//#endregion
|
|
836
|
-
//#region
|
|
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
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
|
914
|
-
return
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
return
|
|
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
|
|
1057
|
-
wasm =
|
|
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
|
-
|
|
1118
|
-
|
|
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
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
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
|
-
|
|
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
|
|
1632
|
+
async executeCommand(command, context) {
|
|
1171
1633
|
if (this.state === "destroyed") return;
|
|
1172
|
-
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
|
+
}
|
|
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.
|
|
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.
|
|
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
|
|
1245
|
-
if (
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1767
|
+
const loaderSnapshot = loaderStats ?? this.session.loaderStats;
|
|
1352
1768
|
this.post({
|
|
1353
1769
|
type: "stats",
|
|
1354
|
-
stats: {
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
outputBytes: this.
|
|
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.
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
1803
|
+
ranges: this.session.bufferedRanges,
|
|
1471
1804
|
videoState: this.videoState,
|
|
1472
|
-
loaderPaused:
|
|
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
|
|
1477
|
-
if (
|
|
1478
|
-
|
|
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.
|
|
1536
|
-
this.
|
|
1537
|
-
this.
|
|
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) {
|