@mebius-io/web 0.4.0 → 0.4.2
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/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/index.cjs +132 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +132 -18
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +132 -18
- package/dist/index.js.map +1 -1
- package/package.json +11 -12
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);
|
|
@@ -42764,6 +42779,28 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42764
42779
|
}
|
|
42765
42780
|
};
|
|
42766
42781
|
|
|
42782
|
+
// src/internal/autoplay.ts
|
|
42783
|
+
function isAutoplayBlocked(e) {
|
|
42784
|
+
return typeof e === "object" && e !== null && e.name === "NotAllowedError";
|
|
42785
|
+
}
|
|
42786
|
+
async function playWithAutoplayFallback(video) {
|
|
42787
|
+
video.playsInline = true;
|
|
42788
|
+
try {
|
|
42789
|
+
await video.play();
|
|
42790
|
+
return { mutedByPolicy: false };
|
|
42791
|
+
} catch (e) {
|
|
42792
|
+
if (!isAutoplayBlocked(e) || video.muted) throw e;
|
|
42793
|
+
video.muted = true;
|
|
42794
|
+
await video.play();
|
|
42795
|
+
return { mutedByPolicy: true };
|
|
42796
|
+
}
|
|
42797
|
+
}
|
|
42798
|
+
function resetVideoElement(video) {
|
|
42799
|
+
video.srcObject = null;
|
|
42800
|
+
video.removeAttribute("src");
|
|
42801
|
+
video.load();
|
|
42802
|
+
}
|
|
42803
|
+
|
|
42767
42804
|
// src/internal/ll-view-transport.ts
|
|
42768
42805
|
var WhepViewTransport = class {
|
|
42769
42806
|
constructor(signaling) {
|
|
@@ -42772,6 +42809,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42772
42809
|
this.resourceUrl = null;
|
|
42773
42810
|
this.endedCb = null;
|
|
42774
42811
|
this.bufferingCb = null;
|
|
42812
|
+
/** Element this route attached a MediaStream to, so stop() can release it. */
|
|
42813
|
+
this.video = null;
|
|
42814
|
+
/** True when playback only started because the element had to be muted. */
|
|
42815
|
+
this.mutedByPolicy = false;
|
|
42775
42816
|
}
|
|
42776
42817
|
onEnded(cb) {
|
|
42777
42818
|
this.endedCb = cb;
|
|
@@ -42780,6 +42821,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42780
42821
|
this.bufferingCb = cb;
|
|
42781
42822
|
}
|
|
42782
42823
|
async start(streamId, video) {
|
|
42824
|
+
this.video = video;
|
|
42783
42825
|
const pc = new RTCPeerConnection(DEFAULT_RTC_CONFIG);
|
|
42784
42826
|
this.pc = pc;
|
|
42785
42827
|
const remote = new MediaStream();
|
|
@@ -42788,7 +42830,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42788
42830
|
pc.ontrack = (ev) => {
|
|
42789
42831
|
remote.addTrack(ev.track);
|
|
42790
42832
|
video.srcObject = remote;
|
|
42791
|
-
void video
|
|
42833
|
+
void playWithAutoplayFallback(video).then((o) => {
|
|
42834
|
+
this.mutedByPolicy = o.mutedByPolicy;
|
|
42835
|
+
}).catch(() => {
|
|
42792
42836
|
});
|
|
42793
42837
|
};
|
|
42794
42838
|
pc.onconnectionstatechange = () => {
|
|
@@ -42815,6 +42859,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42815
42859
|
this.resourceUrl = null;
|
|
42816
42860
|
this.pc?.close();
|
|
42817
42861
|
this.pc = null;
|
|
42862
|
+
if (this.video) resetVideoElement(this.video);
|
|
42863
|
+
this.video = null;
|
|
42818
42864
|
}
|
|
42819
42865
|
async getStats() {
|
|
42820
42866
|
if (!this.pc) return null;
|
|
@@ -42852,6 +42898,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42852
42898
|
this.video = null;
|
|
42853
42899
|
this.endedCb = null;
|
|
42854
42900
|
this.bufferingCb = null;
|
|
42901
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
42902
|
+
this.listeners = null;
|
|
42903
|
+
/** True when playback only started because the element had to be muted. */
|
|
42904
|
+
this.mutedByPolicy = false;
|
|
42855
42905
|
}
|
|
42856
42906
|
onEnded(cb) {
|
|
42857
42907
|
this.endedCb = cb;
|
|
@@ -42862,11 +42912,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42862
42912
|
async start(streamId, video) {
|
|
42863
42913
|
this.video = video;
|
|
42864
42914
|
const url = this.deliveryPath ? this.signaling.deliveryUrl(this.deliveryPath) : this.signaling.scalePlaylistUrl(streamId);
|
|
42865
|
-
|
|
42866
|
-
|
|
42915
|
+
this.listeners = new AbortController();
|
|
42916
|
+
const { signal } = this.listeners;
|
|
42917
|
+
video.addEventListener("ended", () => this.endedCb?.(), { signal });
|
|
42918
|
+
video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
|
|
42867
42919
|
if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
|
42868
42920
|
video.src = url;
|
|
42869
|
-
await video
|
|
42921
|
+
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
42870
42922
|
return;
|
|
42871
42923
|
}
|
|
42872
42924
|
let mod;
|
|
@@ -42879,16 +42931,18 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42879
42931
|
if (!Hls2.isSupported()) {
|
|
42880
42932
|
throw mebiusError("CONNECTION_FAILED", "Scale playback is not supported in this browser.");
|
|
42881
42933
|
}
|
|
42882
|
-
const hls = new Hls2({
|
|
42934
|
+
const hls = new Hls2({ maxLiveSyncPlaybackRate: 1.5 });
|
|
42883
42935
|
this.hls = hls;
|
|
42884
42936
|
hls.on(Hls2.Events.ERROR, (_evt, data) => {
|
|
42885
42937
|
if (data.fatal) this.bufferingCb?.();
|
|
42886
42938
|
});
|
|
42887
42939
|
hls.loadSource(url);
|
|
42888
42940
|
hls.attachMedia(video);
|
|
42889
|
-
await video
|
|
42941
|
+
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
42890
42942
|
}
|
|
42891
42943
|
async stop() {
|
|
42944
|
+
this.listeners?.abort();
|
|
42945
|
+
this.listeners = null;
|
|
42892
42946
|
this.hls?.destroy();
|
|
42893
42947
|
this.hls = null;
|
|
42894
42948
|
if (this.video) {
|
|
@@ -42908,6 +42962,40 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42908
42962
|
};
|
|
42909
42963
|
|
|
42910
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
|
+
}
|
|
42911
42999
|
var FlvViewTransport = class {
|
|
42912
43000
|
constructor(signaling, deliveryPath) {
|
|
42913
43001
|
this.signaling = signaling;
|
|
@@ -42916,6 +43004,10 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42916
43004
|
this.video = null;
|
|
42917
43005
|
this.endedCb = null;
|
|
42918
43006
|
this.bufferingCb = null;
|
|
43007
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
43008
|
+
this.listeners = null;
|
|
43009
|
+
/** True when playback only started because the element had to be muted. */
|
|
43010
|
+
this.mutedByPolicy = false;
|
|
42919
43011
|
}
|
|
42920
43012
|
onEnded(cb) {
|
|
42921
43013
|
this.endedCb = cb;
|
|
@@ -42926,8 +43018,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42926
43018
|
async start(_streamId, video) {
|
|
42927
43019
|
this.video = video;
|
|
42928
43020
|
const url = this.signaling.deliveryUrl(this.deliveryPath);
|
|
42929
|
-
|
|
42930
|
-
|
|
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 });
|
|
42931
43026
|
let mod;
|
|
42932
43027
|
try {
|
|
42933
43028
|
mod = await Promise.resolve().then(() => __toESM(require_flv(), 1));
|
|
@@ -42938,20 +43033,38 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42938
43033
|
if (!flvjs.isSupported()) {
|
|
42939
43034
|
throw mebiusError("CONNECTION_FAILED", "Balanced playback is not supported in this browser.");
|
|
42940
43035
|
}
|
|
42941
|
-
|
|
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
|
+
);
|
|
42942
43052
|
this.player = player;
|
|
42943
43053
|
player.on(flvjs.Events.ERROR ?? "error", () => this.bufferingCb?.());
|
|
42944
43054
|
player.attachMediaElement(video);
|
|
42945
43055
|
player.load();
|
|
42946
|
-
|
|
43056
|
+
}
|
|
43057
|
+
teardownPlayer() {
|
|
43058
|
+
if (!this.player) return;
|
|
43059
|
+
this.player.unload();
|
|
43060
|
+
this.player.detachMediaElement();
|
|
43061
|
+
this.player.destroy();
|
|
43062
|
+
this.player = null;
|
|
42947
43063
|
}
|
|
42948
43064
|
async stop() {
|
|
42949
|
-
|
|
42950
|
-
|
|
42951
|
-
|
|
42952
|
-
this.player.destroy();
|
|
42953
|
-
this.player = null;
|
|
42954
|
-
}
|
|
43065
|
+
this.listeners?.abort();
|
|
43066
|
+
this.listeners = null;
|
|
43067
|
+
this.teardownPlayer();
|
|
42955
43068
|
if (this.video) {
|
|
42956
43069
|
this.video.removeAttribute("src");
|
|
42957
43070
|
this.video.load();
|
|
@@ -43000,7 +43113,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43000
43113
|
}
|
|
43001
43114
|
|
|
43002
43115
|
// src/internal/telemetry.ts
|
|
43003
|
-
var SDK_VERSION = "web/0.4.
|
|
43116
|
+
var SDK_VERSION = "web/0.4.2";
|
|
43004
43117
|
var FLUSH_INTERVAL_MS = 15e3;
|
|
43005
43118
|
var MAX_BATCH = 64;
|
|
43006
43119
|
function describeDevice() {
|
|
@@ -43060,7 +43173,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43060
43173
|
if (beacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
43061
43174
|
const url = `${this.target.url}${this.target.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.target.token)}`;
|
|
43062
43175
|
try {
|
|
43063
|
-
navigator.sendBeacon(url, new Blob([body], { type: "
|
|
43176
|
+
navigator.sendBeacon(url, new Blob([body], { type: "text/plain" }));
|
|
43064
43177
|
} catch {
|
|
43065
43178
|
}
|
|
43066
43179
|
return;
|
|
@@ -43216,6 +43329,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43216
43329
|
let lastError = null;
|
|
43217
43330
|
for (const candidate of this.candidates) {
|
|
43218
43331
|
try {
|
|
43332
|
+
resetVideoElement(video);
|
|
43219
43333
|
this.attach(candidate);
|
|
43220
43334
|
await candidate.start(streamId, video);
|
|
43221
43335
|
if (await hasFirstFrame(video)) {
|