@mebius-io/web 0.4.1 → 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/README.md +2 -2
- package/dist/index.cjs +93 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.global.js +93 -15
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +93 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,12 +55,12 @@ external deps. `Mebius` becomes a global.
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
File + full PHP example: [`standalone/`](./standalone/). Raw download:
|
|
58
|
-
`https://raw.githubusercontent.com/russimobiledroidx/mebius-web-sdk/v0.4.
|
|
58
|
+
`https://raw.githubusercontent.com/russimobiledroidx/mebius-web-sdk/v0.4.2/packages/web/standalone/mebius.min.js`
|
|
59
59
|
|
|
60
60
|
The drop-in file is not part of the npm package — `files` ships only `dist` — so it
|
|
61
61
|
is fetched from the tag, and the tag must match the version you installed. For a
|
|
62
62
|
page with a build step, or one that can use an import map, prefer the ESM path:
|
|
63
|
-
`https://esm.sh/@mebius-io/web@0.4.
|
|
63
|
+
`https://esm.sh/@mebius-io/web@0.4.2`.
|
|
64
64
|
|
|
65
65
|
## Quick Start
|
|
66
66
|
|
package/dist/index.cjs
CHANGED
|
@@ -129,6 +129,20 @@ var DEFAULT_RTC_CONFIG = {
|
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
// src/internal/publish-transport.ts
|
|
132
|
+
function preferH264(pc) {
|
|
133
|
+
const caps = RTCRtpSender.getCapabilities?.("video");
|
|
134
|
+
if (!caps?.codecs) return;
|
|
135
|
+
const h264 = caps.codecs.filter((c) => c.mimeType.toLowerCase() === "video/h264");
|
|
136
|
+
if (h264.length === 0) return;
|
|
137
|
+
const rest = caps.codecs.filter((c) => c.mimeType.toLowerCase() !== "video/h264");
|
|
138
|
+
for (const tr of pc.getTransceivers()) {
|
|
139
|
+
if (tr.sender.track?.kind !== "video") continue;
|
|
140
|
+
try {
|
|
141
|
+
tr.setCodecPreferences?.([...h264, ...rest]);
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
132
146
|
var WhipPublishTransport = class {
|
|
133
147
|
constructor(signaling) {
|
|
134
148
|
this.signaling = signaling;
|
|
@@ -141,6 +155,7 @@ var WhipPublishTransport = class {
|
|
|
141
155
|
for (const track of stream.getTracks()) {
|
|
142
156
|
pc.addTrack(track, stream);
|
|
143
157
|
}
|
|
158
|
+
preferH264(pc);
|
|
144
159
|
const offer = await pc.createOffer();
|
|
145
160
|
await pc.setLocalDescription(offer);
|
|
146
161
|
await waitForIceGathering(pc);
|
|
@@ -307,6 +322,8 @@ var HlsViewTransport = class {
|
|
|
307
322
|
this.video = null;
|
|
308
323
|
this.endedCb = null;
|
|
309
324
|
this.bufferingCb = null;
|
|
325
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
326
|
+
this.listeners = null;
|
|
310
327
|
/** True when playback only started because the element had to be muted. */
|
|
311
328
|
this.mutedByPolicy = false;
|
|
312
329
|
}
|
|
@@ -319,8 +336,10 @@ var HlsViewTransport = class {
|
|
|
319
336
|
async start(streamId, video) {
|
|
320
337
|
this.video = video;
|
|
321
338
|
const url = this.deliveryPath ? this.signaling.deliveryUrl(this.deliveryPath) : this.signaling.scalePlaylistUrl(streamId);
|
|
322
|
-
|
|
323
|
-
|
|
339
|
+
this.listeners = new AbortController();
|
|
340
|
+
const { signal } = this.listeners;
|
|
341
|
+
video.addEventListener("ended", () => this.endedCb?.(), { signal });
|
|
342
|
+
video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
|
|
324
343
|
if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
|
325
344
|
video.src = url;
|
|
326
345
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
@@ -336,7 +355,7 @@ var HlsViewTransport = class {
|
|
|
336
355
|
if (!Hls.isSupported()) {
|
|
337
356
|
throw mebiusError("CONNECTION_FAILED", "Scale playback is not supported in this browser.");
|
|
338
357
|
}
|
|
339
|
-
const hls = new Hls();
|
|
358
|
+
const hls = new Hls({ maxLiveSyncPlaybackRate: 1.5 });
|
|
340
359
|
this.hls = hls;
|
|
341
360
|
hls.on(Hls.Events.ERROR, (_evt, data) => {
|
|
342
361
|
if (data.fatal) this.bufferingCb?.();
|
|
@@ -346,6 +365,8 @@ var HlsViewTransport = class {
|
|
|
346
365
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
347
366
|
}
|
|
348
367
|
async stop() {
|
|
368
|
+
this.listeners?.abort();
|
|
369
|
+
this.listeners = null;
|
|
349
370
|
this.hls?.destroy();
|
|
350
371
|
this.hls = null;
|
|
351
372
|
if (this.video) {
|
|
@@ -365,6 +386,40 @@ var HlsViewTransport = class {
|
|
|
365
386
|
};
|
|
366
387
|
|
|
367
388
|
// src/internal/balanced-view-transport.ts
|
|
389
|
+
var LIVE_FLV_CONFIG = {
|
|
390
|
+
enableStashBuffer: false,
|
|
391
|
+
stashInitialSize: 128,
|
|
392
|
+
lazyLoad: false,
|
|
393
|
+
autoCleanupSourceBuffer: true,
|
|
394
|
+
autoCleanupMaxBackwardDuration: 30,
|
|
395
|
+
autoCleanupMinBackwardDuration: 10,
|
|
396
|
+
reuseRedirectedURL: true
|
|
397
|
+
};
|
|
398
|
+
var MAX_DRIFT_S = 2;
|
|
399
|
+
var EDGE_MARGIN_S = 0.4;
|
|
400
|
+
var AUDIO_RETRY_MS = 2500;
|
|
401
|
+
function stalledAtZero(video, ms) {
|
|
402
|
+
if (video.currentTime > 0) return Promise.resolve(false);
|
|
403
|
+
return new Promise((resolve) => {
|
|
404
|
+
const done = (stalled) => {
|
|
405
|
+
clearTimeout(timer);
|
|
406
|
+
video.removeEventListener("timeupdate", onTime);
|
|
407
|
+
resolve(stalled);
|
|
408
|
+
};
|
|
409
|
+
const onTime = () => {
|
|
410
|
+
if (video.currentTime > 0) done(false);
|
|
411
|
+
};
|
|
412
|
+
const timer = setTimeout(() => done(true), ms);
|
|
413
|
+
video.addEventListener("timeupdate", onTime);
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function chaseLiveEdge(video) {
|
|
417
|
+
const ranges = video.buffered;
|
|
418
|
+
if (ranges.length === 0) return;
|
|
419
|
+
const edge = ranges.end(ranges.length - 1);
|
|
420
|
+
if (edge - video.currentTime <= MAX_DRIFT_S) return;
|
|
421
|
+
video.currentTime = edge - EDGE_MARGIN_S;
|
|
422
|
+
}
|
|
368
423
|
var FlvViewTransport = class {
|
|
369
424
|
constructor(signaling, deliveryPath) {
|
|
370
425
|
this.signaling = signaling;
|
|
@@ -373,6 +428,8 @@ var FlvViewTransport = class {
|
|
|
373
428
|
this.video = null;
|
|
374
429
|
this.endedCb = null;
|
|
375
430
|
this.bufferingCb = null;
|
|
431
|
+
/** Aborts this attempt's element listeners; see start(). */
|
|
432
|
+
this.listeners = null;
|
|
376
433
|
/** True when playback only started because the element had to be muted. */
|
|
377
434
|
this.mutedByPolicy = false;
|
|
378
435
|
}
|
|
@@ -385,8 +442,11 @@ var FlvViewTransport = class {
|
|
|
385
442
|
async start(_streamId, video) {
|
|
386
443
|
this.video = video;
|
|
387
444
|
const url = this.signaling.deliveryUrl(this.deliveryPath);
|
|
388
|
-
|
|
389
|
-
|
|
445
|
+
this.listeners = new AbortController();
|
|
446
|
+
const { signal } = this.listeners;
|
|
447
|
+
video.addEventListener("ended", () => this.endedCb?.(), { signal });
|
|
448
|
+
video.addEventListener("waiting", () => this.bufferingCb?.(), { signal });
|
|
449
|
+
video.addEventListener("timeupdate", () => chaseLiveEdge(video), { signal });
|
|
390
450
|
let mod;
|
|
391
451
|
try {
|
|
392
452
|
mod = await import("flv.js");
|
|
@@ -397,20 +457,38 @@ var FlvViewTransport = class {
|
|
|
397
457
|
if (!flvjs.isSupported()) {
|
|
398
458
|
throw mebiusError("CONNECTION_FAILED", "Balanced playback is not supported in this browser.");
|
|
399
459
|
}
|
|
400
|
-
|
|
460
|
+
this.attachPlayer(flvjs, video, url, true);
|
|
461
|
+
const firstPlay = playWithAutoplayFallback(video);
|
|
462
|
+
firstPlay.catch(() => void 0);
|
|
463
|
+
if (await stalledAtZero(video, AUDIO_RETRY_MS)) {
|
|
464
|
+
this.teardownPlayer();
|
|
465
|
+
this.attachPlayer(flvjs, video, url, false);
|
|
466
|
+
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
this.mutedByPolicy = (await firstPlay).mutedByPolicy;
|
|
470
|
+
}
|
|
471
|
+
attachPlayer(flvjs, video, url, withAudio) {
|
|
472
|
+
const player = flvjs.createPlayer(
|
|
473
|
+
{ type: "flv", url, isLive: true, ...withAudio ? {} : { hasAudio: false } },
|
|
474
|
+
LIVE_FLV_CONFIG
|
|
475
|
+
);
|
|
401
476
|
this.player = player;
|
|
402
477
|
player.on(flvjs.Events.ERROR ?? "error", () => this.bufferingCb?.());
|
|
403
478
|
player.attachMediaElement(video);
|
|
404
479
|
player.load();
|
|
405
|
-
|
|
480
|
+
}
|
|
481
|
+
teardownPlayer() {
|
|
482
|
+
if (!this.player) return;
|
|
483
|
+
this.player.unload();
|
|
484
|
+
this.player.detachMediaElement();
|
|
485
|
+
this.player.destroy();
|
|
486
|
+
this.player = null;
|
|
406
487
|
}
|
|
407
488
|
async stop() {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
this.player.destroy();
|
|
412
|
-
this.player = null;
|
|
413
|
-
}
|
|
489
|
+
this.listeners?.abort();
|
|
490
|
+
this.listeners = null;
|
|
491
|
+
this.teardownPlayer();
|
|
414
492
|
if (this.video) {
|
|
415
493
|
this.video.removeAttribute("src");
|
|
416
494
|
this.video.load();
|
|
@@ -459,7 +537,7 @@ function createViewCandidates(mode, signaling, deliveries = []) {
|
|
|
459
537
|
}
|
|
460
538
|
|
|
461
539
|
// src/internal/telemetry.ts
|
|
462
|
-
var SDK_VERSION = "web/0.4.
|
|
540
|
+
var SDK_VERSION = "web/0.4.2";
|
|
463
541
|
var FLUSH_INTERVAL_MS = 15e3;
|
|
464
542
|
var MAX_BATCH = 64;
|
|
465
543
|
function describeDevice() {
|
|
@@ -519,7 +597,7 @@ var QoeReporter = class {
|
|
|
519
597
|
if (beacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
520
598
|
const url = `${this.target.url}${this.target.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.target.token)}`;
|
|
521
599
|
try {
|
|
522
|
-
navigator.sendBeacon(url, new Blob([body], { type: "
|
|
600
|
+
navigator.sendBeacon(url, new Blob([body], { type: "text/plain" }));
|
|
523
601
|
} catch {
|
|
524
602
|
}
|
|
525
603
|
return;
|