@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.js
CHANGED
|
@@ -88,6 +88,20 @@ var DEFAULT_RTC_CONFIG = {
|
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
// src/internal/publish-transport.ts
|
|
91
|
+
function preferH264(pc) {
|
|
92
|
+
const caps = RTCRtpSender.getCapabilities?.("video");
|
|
93
|
+
if (!caps?.codecs) return;
|
|
94
|
+
const h264 = caps.codecs.filter((c) => c.mimeType.toLowerCase() === "video/h264");
|
|
95
|
+
if (h264.length === 0) return;
|
|
96
|
+
const rest = caps.codecs.filter((c) => c.mimeType.toLowerCase() !== "video/h264");
|
|
97
|
+
for (const tr of pc.getTransceivers()) {
|
|
98
|
+
if (tr.sender.track?.kind !== "video") continue;
|
|
99
|
+
try {
|
|
100
|
+
tr.setCodecPreferences?.([...h264, ...rest]);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
91
105
|
var WhipPublishTransport = class {
|
|
92
106
|
constructor(signaling) {
|
|
93
107
|
this.signaling = signaling;
|
|
@@ -100,6 +114,7 @@ var WhipPublishTransport = class {
|
|
|
100
114
|
for (const track of stream.getTracks()) {
|
|
101
115
|
pc.addTrack(track, stream);
|
|
102
116
|
}
|
|
117
|
+
preferH264(pc);
|
|
103
118
|
const offer = await pc.createOffer();
|
|
104
119
|
await pc.setLocalDescription(offer);
|
|
105
120
|
await waitForIceGathering(pc);
|
|
@@ -266,6 +281,8 @@ var HlsViewTransport = class {
|
|
|
266
281
|
this.video = null;
|
|
267
282
|
this.endedCb = null;
|
|
268
283
|
this.bufferingCb = null;
|
|
284
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
285
|
+
this.listeners = null;
|
|
269
286
|
/** True when playback only started because the element had to be muted. */
|
|
270
287
|
this.mutedByPolicy = false;
|
|
271
288
|
}
|
|
@@ -278,8 +295,10 @@ var HlsViewTransport = class {
|
|
|
278
295
|
async start(streamId, video) {
|
|
279
296
|
this.video = video;
|
|
280
297
|
const url = this.deliveryPath ? this.signaling.deliveryUrl(this.deliveryPath) : this.signaling.scalePlaylistUrl(streamId);
|
|
281
|
-
|
|
282
|
-
|
|
298
|
+
this.listeners = new AbortController();
|
|
299
|
+
const { signal } = this.listeners;
|
|
300
|
+
video.addEventListener("ended", () => this.endedCb?.(), { signal });
|
|
301
|
+
video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
|
|
283
302
|
if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
|
284
303
|
video.src = url;
|
|
285
304
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
@@ -295,7 +314,7 @@ var HlsViewTransport = class {
|
|
|
295
314
|
if (!Hls.isSupported()) {
|
|
296
315
|
throw mebiusError("CONNECTION_FAILED", "Scale playback is not supported in this browser.");
|
|
297
316
|
}
|
|
298
|
-
const hls = new Hls();
|
|
317
|
+
const hls = new Hls({ maxLiveSyncPlaybackRate: 1.5 });
|
|
299
318
|
this.hls = hls;
|
|
300
319
|
hls.on(Hls.Events.ERROR, (_evt, data) => {
|
|
301
320
|
if (data.fatal) this.bufferingCb?.();
|
|
@@ -305,6 +324,8 @@ var HlsViewTransport = class {
|
|
|
305
324
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
306
325
|
}
|
|
307
326
|
async stop() {
|
|
327
|
+
this.listeners?.abort();
|
|
328
|
+
this.listeners = null;
|
|
308
329
|
this.hls?.destroy();
|
|
309
330
|
this.hls = null;
|
|
310
331
|
if (this.video) {
|
|
@@ -324,6 +345,40 @@ var HlsViewTransport = class {
|
|
|
324
345
|
};
|
|
325
346
|
|
|
326
347
|
// src/internal/balanced-view-transport.ts
|
|
348
|
+
var LIVE_FLV_CONFIG = {
|
|
349
|
+
enableStashBuffer: false,
|
|
350
|
+
stashInitialSize: 128,
|
|
351
|
+
lazyLoad: false,
|
|
352
|
+
autoCleanupSourceBuffer: true,
|
|
353
|
+
autoCleanupMaxBackwardDuration: 30,
|
|
354
|
+
autoCleanupMinBackwardDuration: 10,
|
|
355
|
+
reuseRedirectedURL: true
|
|
356
|
+
};
|
|
357
|
+
var MAX_DRIFT_S = 2;
|
|
358
|
+
var EDGE_MARGIN_S = 0.4;
|
|
359
|
+
var AUDIO_RETRY_MS = 2500;
|
|
360
|
+
function stalledAtZero(video, ms) {
|
|
361
|
+
if (video.currentTime > 0) return Promise.resolve(false);
|
|
362
|
+
return new Promise((resolve) => {
|
|
363
|
+
const done = (stalled) => {
|
|
364
|
+
clearTimeout(timer);
|
|
365
|
+
video.removeEventListener("timeupdate", onTime);
|
|
366
|
+
resolve(stalled);
|
|
367
|
+
};
|
|
368
|
+
const onTime = () => {
|
|
369
|
+
if (video.currentTime > 0) done(false);
|
|
370
|
+
};
|
|
371
|
+
const timer = setTimeout(() => done(true), ms);
|
|
372
|
+
video.addEventListener("timeupdate", onTime);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function chaseLiveEdge(video) {
|
|
376
|
+
const ranges = video.buffered;
|
|
377
|
+
if (ranges.length === 0) return;
|
|
378
|
+
const edge = ranges.end(ranges.length - 1);
|
|
379
|
+
if (edge - video.currentTime <= MAX_DRIFT_S) return;
|
|
380
|
+
video.currentTime = edge - EDGE_MARGIN_S;
|
|
381
|
+
}
|
|
327
382
|
var FlvViewTransport = class {
|
|
328
383
|
constructor(signaling, deliveryPath) {
|
|
329
384
|
this.signaling = signaling;
|
|
@@ -332,6 +387,8 @@ var FlvViewTransport = class {
|
|
|
332
387
|
this.video = null;
|
|
333
388
|
this.endedCb = null;
|
|
334
389
|
this.bufferingCb = null;
|
|
390
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
391
|
+
this.listeners = null;
|
|
335
392
|
/** True when playback only started because the element had to be muted. */
|
|
336
393
|
this.mutedByPolicy = false;
|
|
337
394
|
}
|
|
@@ -344,8 +401,11 @@ var FlvViewTransport = class {
|
|
|
344
401
|
async start(_streamId, video) {
|
|
345
402
|
this.video = video;
|
|
346
403
|
const url = this.signaling.deliveryUrl(this.deliveryPath);
|
|
347
|
-
|
|
348
|
-
|
|
404
|
+
this.listeners = new AbortController();
|
|
405
|
+
const { signal } = this.listeners;
|
|
406
|
+
video.addEventListener("ended", () => this.endedCb?.(), { signal });
|
|
407
|
+
video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
|
|
408
|
+
video.addEventListener("timeupdate", () => chaseLiveEdge(video), { signal });
|
|
349
409
|
let mod;
|
|
350
410
|
try {
|
|
351
411
|
mod = await import("flv.js");
|
|
@@ -356,20 +416,38 @@ var FlvViewTransport = class {
|
|
|
356
416
|
if (!flvjs.isSupported()) {
|
|
357
417
|
throw mebiusError("CONNECTION_FAILED", "Balanced playback is not supported in this browser.");
|
|
358
418
|
}
|
|
359
|
-
|
|
419
|
+
this.attachPlayer(flvjs, video, url, true);
|
|
420
|
+
const firstPlay = playWithAutoplayFallback(video);
|
|
421
|
+
firstPlay.catch(() => void 0);
|
|
422
|
+
if (await stalledAtZero(video, AUDIO_RETRY_MS)) {
|
|
423
|
+
this.teardownPlayer();
|
|
424
|
+
this.attachPlayer(flvjs, video, url, false);
|
|
425
|
+
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this.mutedByPolicy = (await firstPlay).mutedByPolicy;
|
|
429
|
+
}
|
|
430
|
+
attachPlayer(flvjs, video, url, withAudio) {
|
|
431
|
+
const player = flvjs.createPlayer(
|
|
432
|
+
{ type: "flv", url, isLive: true, ...withAudio ? {} : { hasAudio: false } },
|
|
433
|
+
LIVE_FLV_CONFIG
|
|
434
|
+
);
|
|
360
435
|
this.player = player;
|
|
361
436
|
player.on(flvjs.Events.ERROR ?? "error", () => this.bufferingCb?.());
|
|
362
437
|
player.attachMediaElement(video);
|
|
363
438
|
player.load();
|
|
364
|
-
|
|
439
|
+
}
|
|
440
|
+
teardownPlayer() {
|
|
441
|
+
if (!this.player) return;
|
|
442
|
+
this.player.unload();
|
|
443
|
+
this.player.detachMediaElement();
|
|
444
|
+
this.player.destroy();
|
|
445
|
+
this.player = null;
|
|
365
446
|
}
|
|
366
447
|
async stop() {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.player.destroy();
|
|
371
|
-
this.player = null;
|
|
372
|
-
}
|
|
448
|
+
this.listeners?.abort();
|
|
449
|
+
this.listeners = null;
|
|
450
|
+
this.teardownPlayer();
|
|
373
451
|
if (this.video) {
|
|
374
452
|
this.video.removeAttribute("src");
|
|
375
453
|
this.video.load();
|
|
@@ -418,7 +496,7 @@ function createViewCandidates(mode, signaling, deliveries = []) {
|
|
|
418
496
|
}
|
|
419
497
|
|
|
420
498
|
// src/internal/telemetry.ts
|
|
421
|
-
var SDK_VERSION = "web/0.4.
|
|
499
|
+
var SDK_VERSION = "web/0.4.3";
|
|
422
500
|
var FLUSH_INTERVAL_MS = 15e3;
|
|
423
501
|
var MAX_BATCH = 64;
|
|
424
502
|
function describeDevice() {
|
|
@@ -478,7 +556,7 @@ var QoeReporter = class {
|
|
|
478
556
|
if (beacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
479
557
|
const url = `${this.target.url}${this.target.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.target.token)}`;
|
|
480
558
|
try {
|
|
481
|
-
navigator.sendBeacon(url, new Blob([body], { type: "
|
|
559
|
+
navigator.sendBeacon(url, new Blob([body], { type: "text/plain" }));
|
|
482
560
|
} catch {
|
|
483
561
|
}
|
|
484
562
|
return;
|
|
@@ -612,6 +690,7 @@ function normalize(c, fallback) {
|
|
|
612
690
|
// src/player.ts
|
|
613
691
|
var STATS_INTERVAL_MS2 = 2e3;
|
|
614
692
|
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
693
|
+
var ELEMENT_OWNER = /* @__PURE__ */ new WeakMap();
|
|
615
694
|
var MebiusPlayer = class extends TypedEmitter {
|
|
616
695
|
/** @internal */
|
|
617
696
|
constructor(signaling, options = {}, deliveries = [], telemetry = null, userId) {
|
|
@@ -623,13 +702,30 @@ var MebiusPlayer = class extends TypedEmitter {
|
|
|
623
702
|
this.statsTimer = null;
|
|
624
703
|
this.playing = false;
|
|
625
704
|
this.reporter = null;
|
|
705
|
+
/** True between a `buffering` event and the element actually resuming. */
|
|
706
|
+
this.stalled = false;
|
|
707
|
+
/** Cancels element listeners bound for the lifetime of one play(). */
|
|
708
|
+
this.elementListeners = null;
|
|
626
709
|
this.candidates = createViewCandidates(options.mode ?? "auto", signaling, deliveries);
|
|
627
710
|
}
|
|
628
711
|
/** Start playing `streamId` into the given video element or selector. */
|
|
629
712
|
async play(streamId, viewTarget) {
|
|
630
713
|
if (this.playing) return;
|
|
631
714
|
const video = resolveVideoElement(viewTarget);
|
|
715
|
+
const previous = ELEMENT_OWNER.get(video);
|
|
716
|
+
if (previous && previous !== this) await previous.stop();
|
|
717
|
+
ELEMENT_OWNER.set(video, this);
|
|
632
718
|
this.video = video;
|
|
719
|
+
this.elementListeners = new AbortController();
|
|
720
|
+
video.addEventListener(
|
|
721
|
+
"playing",
|
|
722
|
+
() => {
|
|
723
|
+
if (!this.stalled || !this.playing) return;
|
|
724
|
+
this.stalled = false;
|
|
725
|
+
this.emit("playing", { streamId });
|
|
726
|
+
},
|
|
727
|
+
{ signal: this.elementListeners.signal }
|
|
728
|
+
);
|
|
633
729
|
const startedAtMs = Date.now();
|
|
634
730
|
let lastError = null;
|
|
635
731
|
for (const candidate of this.candidates) {
|
|
@@ -660,6 +756,12 @@ var MebiusPlayer = class extends TypedEmitter {
|
|
|
660
756
|
}
|
|
661
757
|
/** Stop playback and detach from the video element. */
|
|
662
758
|
async stop() {
|
|
759
|
+
this.elementListeners?.abort();
|
|
760
|
+
this.elementListeners = null;
|
|
761
|
+
if (this.video && ELEMENT_OWNER.get(this.video) === this) {
|
|
762
|
+
ELEMENT_OWNER.delete(this.video);
|
|
763
|
+
}
|
|
764
|
+
this.stalled = false;
|
|
663
765
|
this.stopStats();
|
|
664
766
|
await this.reporter?.stop();
|
|
665
767
|
this.reporter = null;
|
|
@@ -684,6 +786,7 @@ var MebiusPlayer = class extends TypedEmitter {
|
|
|
684
786
|
});
|
|
685
787
|
transport.onBuffering(() => {
|
|
686
788
|
if (this.transport !== transport) return;
|
|
789
|
+
this.stalled = true;
|
|
687
790
|
this.emit("buffering", void 0);
|
|
688
791
|
});
|
|
689
792
|
}
|