@mebius-io/web 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.cjs +118 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.global.js +118 -15
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +118 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.global.js
CHANGED
|
@@ -42705,6 +42705,20 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42705
42705
|
};
|
|
42706
42706
|
|
|
42707
42707
|
// src/internal/publish-transport.ts
|
|
42708
|
+
function preferH264(pc) {
|
|
42709
|
+
const caps = RTCRtpSender.getCapabilities?.("video");
|
|
42710
|
+
if (!caps?.codecs) return;
|
|
42711
|
+
const h264 = caps.codecs.filter((c) => c.mimeType.toLowerCase() === "video/h264");
|
|
42712
|
+
if (h264.length === 0) return;
|
|
42713
|
+
const rest = caps.codecs.filter((c) => c.mimeType.toLowerCase() !== "video/h264");
|
|
42714
|
+
for (const tr of pc.getTransceivers()) {
|
|
42715
|
+
if (tr.sender.track?.kind !== "video") continue;
|
|
42716
|
+
try {
|
|
42717
|
+
tr.setCodecPreferences?.([...h264, ...rest]);
|
|
42718
|
+
} catch {
|
|
42719
|
+
}
|
|
42720
|
+
}
|
|
42721
|
+
}
|
|
42708
42722
|
var WhipPublishTransport = class {
|
|
42709
42723
|
constructor(signaling) {
|
|
42710
42724
|
this.signaling = signaling;
|
|
@@ -42717,6 +42731,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42717
42731
|
for (const track of stream.getTracks()) {
|
|
42718
42732
|
pc.addTrack(track, stream);
|
|
42719
42733
|
}
|
|
42734
|
+
preferH264(pc);
|
|
42720
42735
|
const offer = await pc.createOffer();
|
|
42721
42736
|
await pc.setLocalDescription(offer);
|
|
42722
42737
|
await waitForIceGathering(pc);
|
|
@@ -42883,6 +42898,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42883
42898
|
this.video = null;
|
|
42884
42899
|
this.endedCb = null;
|
|
42885
42900
|
this.bufferingCb = null;
|
|
42901
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
42902
|
+
this.listeners = null;
|
|
42886
42903
|
/** True when playback only started because the element had to be muted. */
|
|
42887
42904
|
this.mutedByPolicy = false;
|
|
42888
42905
|
}
|
|
@@ -42895,8 +42912,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42895
42912
|
async start(streamId, video) {
|
|
42896
42913
|
this.video = video;
|
|
42897
42914
|
const url = this.deliveryPath ? this.signaling.deliveryUrl(this.deliveryPath) : this.signaling.scalePlaylistUrl(streamId);
|
|
42898
|
-
|
|
42899
|
-
|
|
42915
|
+
this.listeners = new AbortController();
|
|
42916
|
+
const { signal } = this.listeners;
|
|
42917
|
+
video.addEventListener("ended", () => this.endedCb?.(), { signal });
|
|
42918
|
+
video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
|
|
42900
42919
|
if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
|
42901
42920
|
video.src = url;
|
|
42902
42921
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
@@ -42912,7 +42931,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42912
42931
|
if (!Hls2.isSupported()) {
|
|
42913
42932
|
throw mebiusError("CONNECTION_FAILED", "Scale playback is not supported in this browser.");
|
|
42914
42933
|
}
|
|
42915
|
-
const hls = new Hls2();
|
|
42934
|
+
const hls = new Hls2({ maxLiveSyncPlaybackRate: 1.5 });
|
|
42916
42935
|
this.hls = hls;
|
|
42917
42936
|
hls.on(Hls2.Events.ERROR, (_evt, data) => {
|
|
42918
42937
|
if (data.fatal) this.bufferingCb?.();
|
|
@@ -42922,6 +42941,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42922
42941
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
42923
42942
|
}
|
|
42924
42943
|
async stop() {
|
|
42944
|
+
this.listeners?.abort();
|
|
42945
|
+
this.listeners = null;
|
|
42925
42946
|
this.hls?.destroy();
|
|
42926
42947
|
this.hls = null;
|
|
42927
42948
|
if (this.video) {
|
|
@@ -42941,6 +42962,40 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42941
42962
|
};
|
|
42942
42963
|
|
|
42943
42964
|
// src/internal/balanced-view-transport.ts
|
|
42965
|
+
var LIVE_FLV_CONFIG = {
|
|
42966
|
+
enableStashBuffer: false,
|
|
42967
|
+
stashInitialSize: 128,
|
|
42968
|
+
lazyLoad: false,
|
|
42969
|
+
autoCleanupSourceBuffer: true,
|
|
42970
|
+
autoCleanupMaxBackwardDuration: 30,
|
|
42971
|
+
autoCleanupMinBackwardDuration: 10,
|
|
42972
|
+
reuseRedirectedURL: true
|
|
42973
|
+
};
|
|
42974
|
+
var MAX_DRIFT_S = 2;
|
|
42975
|
+
var EDGE_MARGIN_S = 0.4;
|
|
42976
|
+
var AUDIO_RETRY_MS = 2500;
|
|
42977
|
+
function stalledAtZero(video, ms) {
|
|
42978
|
+
if (video.currentTime > 0) return Promise.resolve(false);
|
|
42979
|
+
return new Promise((resolve) => {
|
|
42980
|
+
const done = (stalled) => {
|
|
42981
|
+
clearTimeout(timer);
|
|
42982
|
+
video.removeEventListener("timeupdate", onTime);
|
|
42983
|
+
resolve(stalled);
|
|
42984
|
+
};
|
|
42985
|
+
const onTime = () => {
|
|
42986
|
+
if (video.currentTime > 0) done(false);
|
|
42987
|
+
};
|
|
42988
|
+
const timer = setTimeout(() => done(true), ms);
|
|
42989
|
+
video.addEventListener("timeupdate", onTime);
|
|
42990
|
+
});
|
|
42991
|
+
}
|
|
42992
|
+
function chaseLiveEdge(video) {
|
|
42993
|
+
const ranges = video.buffered;
|
|
42994
|
+
if (ranges.length === 0) return;
|
|
42995
|
+
const edge = ranges.end(ranges.length - 1);
|
|
42996
|
+
if (edge - video.currentTime <= MAX_DRIFT_S) return;
|
|
42997
|
+
video.currentTime = edge - EDGE_MARGIN_S;
|
|
42998
|
+
}
|
|
42944
42999
|
var FlvViewTransport = class {
|
|
42945
43000
|
constructor(signaling, deliveryPath) {
|
|
42946
43001
|
this.signaling = signaling;
|
|
@@ -42949,6 +43004,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42949
43004
|
this.video = null;
|
|
42950
43005
|
this.endedCb = null;
|
|
42951
43006
|
this.bufferingCb = null;
|
|
43007
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
43008
|
+
this.listeners = null;
|
|
42952
43009
|
/** True when playback only started because the element had to be muted. */
|
|
42953
43010
|
this.mutedByPolicy = false;
|
|
42954
43011
|
}
|
|
@@ -42961,8 +43018,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42961
43018
|
async start(_streamId, video) {
|
|
42962
43019
|
this.video = video;
|
|
42963
43020
|
const url = this.signaling.deliveryUrl(this.deliveryPath);
|
|
42964
|
-
|
|
42965
|
-
|
|
43021
|
+
this.listeners = new AbortController();
|
|
43022
|
+
const { signal } = this.listeners;
|
|
43023
|
+
video.addEventListener("ended", () => this.endedCb?.(), { signal });
|
|
43024
|
+
video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
|
|
43025
|
+
video.addEventListener("timeupdate", () => chaseLiveEdge(video), { signal });
|
|
42966
43026
|
let mod;
|
|
42967
43027
|
try {
|
|
42968
43028
|
mod = await Promise.resolve().then(() => __toESM(require_flv(), 1));
|
|
@@ -42973,20 +43033,38 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42973
43033
|
if (!flvjs.isSupported()) {
|
|
42974
43034
|
throw mebiusError("CONNECTION_FAILED", "Balanced playback is not supported in this browser.");
|
|
42975
43035
|
}
|
|
42976
|
-
|
|
43036
|
+
this.attachPlayer(flvjs, video, url, true);
|
|
43037
|
+
const firstPlay = playWithAutoplayFallback(video);
|
|
43038
|
+
firstPlay.catch(() => void 0);
|
|
43039
|
+
if (await stalledAtZero(video, AUDIO_RETRY_MS)) {
|
|
43040
|
+
this.teardownPlayer();
|
|
43041
|
+
this.attachPlayer(flvjs, video, url, false);
|
|
43042
|
+
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
43043
|
+
return;
|
|
43044
|
+
}
|
|
43045
|
+
this.mutedByPolicy = (await firstPlay).mutedByPolicy;
|
|
43046
|
+
}
|
|
43047
|
+
attachPlayer(flvjs, video, url, withAudio) {
|
|
43048
|
+
const player = flvjs.createPlayer(
|
|
43049
|
+
{ type: "flv", url, isLive: true, ...withAudio ? {} : { hasAudio: false } },
|
|
43050
|
+
LIVE_FLV_CONFIG
|
|
43051
|
+
);
|
|
42977
43052
|
this.player = player;
|
|
42978
43053
|
player.on(flvjs.Events.ERROR ?? "error", () => this.bufferingCb?.());
|
|
42979
43054
|
player.attachMediaElement(video);
|
|
42980
43055
|
player.load();
|
|
42981
|
-
|
|
43056
|
+
}
|
|
43057
|
+
teardownPlayer() {
|
|
43058
|
+
if (!this.player) return;
|
|
43059
|
+
this.player.unload();
|
|
43060
|
+
this.player.detachMediaElement();
|
|
43061
|
+
this.player.destroy();
|
|
43062
|
+
this.player = null;
|
|
42982
43063
|
}
|
|
42983
43064
|
async stop() {
|
|
42984
|
-
|
|
42985
|
-
|
|
42986
|
-
|
|
42987
|
-
this.player.destroy();
|
|
42988
|
-
this.player = null;
|
|
42989
|
-
}
|
|
43065
|
+
this.listeners?.abort();
|
|
43066
|
+
this.listeners = null;
|
|
43067
|
+
this.teardownPlayer();
|
|
42990
43068
|
if (this.video) {
|
|
42991
43069
|
this.video.removeAttribute("src");
|
|
42992
43070
|
this.video.load();
|
|
@@ -43035,7 +43113,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43035
43113
|
}
|
|
43036
43114
|
|
|
43037
43115
|
// src/internal/telemetry.ts
|
|
43038
|
-
var SDK_VERSION = "web/0.4.
|
|
43116
|
+
var SDK_VERSION = "web/0.4.3";
|
|
43039
43117
|
var FLUSH_INTERVAL_MS = 15e3;
|
|
43040
43118
|
var MAX_BATCH = 64;
|
|
43041
43119
|
function describeDevice() {
|
|
@@ -43095,7 +43173,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43095
43173
|
if (beacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
43096
43174
|
const url = `${this.target.url}${this.target.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.target.token)}`;
|
|
43097
43175
|
try {
|
|
43098
|
-
navigator.sendBeacon(url, new Blob([body], { type: "
|
|
43176
|
+
navigator.sendBeacon(url, new Blob([body], { type: "text/plain" }));
|
|
43099
43177
|
} catch {
|
|
43100
43178
|
}
|
|
43101
43179
|
return;
|
|
@@ -43229,6 +43307,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43229
43307
|
// src/player.ts
|
|
43230
43308
|
var STATS_INTERVAL_MS2 = 2e3;
|
|
43231
43309
|
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
43310
|
+
var ELEMENT_OWNER = /* @__PURE__ */ new WeakMap();
|
|
43232
43311
|
var MebiusPlayer = class extends TypedEmitter {
|
|
43233
43312
|
/** @internal */
|
|
43234
43313
|
constructor(signaling, options = {}, deliveries = [], telemetry = null, userId) {
|
|
@@ -43240,13 +43319,30 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43240
43319
|
this.statsTimer = null;
|
|
43241
43320
|
this.playing = false;
|
|
43242
43321
|
this.reporter = null;
|
|
43322
|
+
/** True between a `buffering` event and the element actually resuming. */
|
|
43323
|
+
this.stalled = false;
|
|
43324
|
+
/** Cancels element listeners bound for the lifetime of one play(). */
|
|
43325
|
+
this.elementListeners = null;
|
|
43243
43326
|
this.candidates = createViewCandidates(options.mode ?? "auto", signaling, deliveries);
|
|
43244
43327
|
}
|
|
43245
43328
|
/** Start playing `streamId` into the given video element or selector. */
|
|
43246
43329
|
async play(streamId, viewTarget) {
|
|
43247
43330
|
if (this.playing) return;
|
|
43248
43331
|
const video = resolveVideoElement(viewTarget);
|
|
43332
|
+
const previous = ELEMENT_OWNER.get(video);
|
|
43333
|
+
if (previous && previous !== this) await previous.stop();
|
|
43334
|
+
ELEMENT_OWNER.set(video, this);
|
|
43249
43335
|
this.video = video;
|
|
43336
|
+
this.elementListeners = new AbortController();
|
|
43337
|
+
video.addEventListener(
|
|
43338
|
+
"playing",
|
|
43339
|
+
() => {
|
|
43340
|
+
if (!this.stalled || !this.playing) return;
|
|
43341
|
+
this.stalled = false;
|
|
43342
|
+
this.emit("playing", { streamId });
|
|
43343
|
+
},
|
|
43344
|
+
{ signal: this.elementListeners.signal }
|
|
43345
|
+
);
|
|
43250
43346
|
const startedAtMs = Date.now();
|
|
43251
43347
|
let lastError = null;
|
|
43252
43348
|
for (const candidate of this.candidates) {
|
|
@@ -43277,6 +43373,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43277
43373
|
}
|
|
43278
43374
|
/** Stop playback and detach from the video element. */
|
|
43279
43375
|
async stop() {
|
|
43376
|
+
this.elementListeners?.abort();
|
|
43377
|
+
this.elementListeners = null;
|
|
43378
|
+
if (this.video && ELEMENT_OWNER.get(this.video) === this) {
|
|
43379
|
+
ELEMENT_OWNER.delete(this.video);
|
|
43380
|
+
}
|
|
43381
|
+
this.stalled = false;
|
|
43280
43382
|
this.stopStats();
|
|
43281
43383
|
await this.reporter?.stop();
|
|
43282
43384
|
this.reporter = null;
|
|
@@ -43301,6 +43403,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43301
43403
|
});
|
|
43302
43404
|
transport.onBuffering(() => {
|
|
43303
43405
|
if (this.transport !== transport) return;
|
|
43406
|
+
this.stalled = true;
|
|
43304
43407
|
this.emit("buffering", void 0);
|
|
43305
43408
|
});
|
|
43306
43409
|
}
|