@mebius-io/web 0.3.0 → 0.4.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/README.md +25 -2
- package/dist/index.cjs +130 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -3
- package/dist/index.d.ts +32 -3
- package/dist/index.global.js +130 -9
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +130 -9
- package/dist/index.js.map +1 -1
- package/package.json +12 -11
- package/LICENSE +0 -21
package/dist/index.global.js
CHANGED
|
@@ -42999,17 +42999,103 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42999
42999
|
}
|
|
43000
43000
|
}
|
|
43001
43001
|
|
|
43002
|
+
// src/internal/telemetry.ts
|
|
43003
|
+
var SDK_VERSION = "web/0.4.0";
|
|
43004
|
+
var FLUSH_INTERVAL_MS = 15e3;
|
|
43005
|
+
var MAX_BATCH = 64;
|
|
43006
|
+
function describeDevice() {
|
|
43007
|
+
const nav = typeof navigator === "undefined" ? void 0 : navigator;
|
|
43008
|
+
const uaData = nav?.userAgentData;
|
|
43009
|
+
return { os: uaData?.platform || nav?.platform || void 0, sdk: SDK_VERSION };
|
|
43010
|
+
}
|
|
43011
|
+
function describeNetwork() {
|
|
43012
|
+
const conn = typeof navigator === "undefined" ? void 0 : navigator.connection;
|
|
43013
|
+
return conn?.effectiveType ? { type: conn.effectiveType } : void 0;
|
|
43014
|
+
}
|
|
43015
|
+
var QoeReporter = class {
|
|
43016
|
+
constructor(target, role, streamId, userId) {
|
|
43017
|
+
this.target = target;
|
|
43018
|
+
this.role = role;
|
|
43019
|
+
this.streamId = streamId;
|
|
43020
|
+
this.userId = userId;
|
|
43021
|
+
this.sessionId = randomId();
|
|
43022
|
+
this.buffer = [];
|
|
43023
|
+
this.timer = null;
|
|
43024
|
+
this.unloadHandler = null;
|
|
43025
|
+
}
|
|
43026
|
+
start() {
|
|
43027
|
+
if (this.timer) return;
|
|
43028
|
+
this.timer = setInterval(() => void this.flush(), FLUSH_INTERVAL_MS);
|
|
43029
|
+
if (typeof window !== "undefined") {
|
|
43030
|
+
this.unloadHandler = () => void this.flush(true);
|
|
43031
|
+
window.addEventListener("pagehide", this.unloadHandler);
|
|
43032
|
+
}
|
|
43033
|
+
}
|
|
43034
|
+
add(sample) {
|
|
43035
|
+
this.buffer.push(sample);
|
|
43036
|
+
if (this.buffer.length >= MAX_BATCH) void this.flush();
|
|
43037
|
+
}
|
|
43038
|
+
async stop() {
|
|
43039
|
+
if (this.timer) clearInterval(this.timer);
|
|
43040
|
+
this.timer = null;
|
|
43041
|
+
if (this.unloadHandler && typeof window !== "undefined") {
|
|
43042
|
+
window.removeEventListener("pagehide", this.unloadHandler);
|
|
43043
|
+
}
|
|
43044
|
+
this.unloadHandler = null;
|
|
43045
|
+
await this.flush();
|
|
43046
|
+
}
|
|
43047
|
+
/** Send and clear the buffer. `beacon` uses sendBeacon, for page-unload flushes. */
|
|
43048
|
+
async flush(beacon = false) {
|
|
43049
|
+
if (!this.buffer.length) return;
|
|
43050
|
+
const samples = this.buffer.splice(0, MAX_BATCH);
|
|
43051
|
+
const body = JSON.stringify({
|
|
43052
|
+
sessionId: this.sessionId,
|
|
43053
|
+
streamId: this.streamId,
|
|
43054
|
+
role: this.role,
|
|
43055
|
+
userId: this.userId,
|
|
43056
|
+
samples,
|
|
43057
|
+
device: describeDevice(),
|
|
43058
|
+
network: describeNetwork()
|
|
43059
|
+
});
|
|
43060
|
+
if (beacon && typeof navigator !== "undefined" && navigator.sendBeacon) {
|
|
43061
|
+
const url = `${this.target.url}${this.target.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.target.token)}`;
|
|
43062
|
+
try {
|
|
43063
|
+
navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
|
|
43064
|
+
} catch {
|
|
43065
|
+
}
|
|
43066
|
+
return;
|
|
43067
|
+
}
|
|
43068
|
+
try {
|
|
43069
|
+
await fetch(this.target.url, {
|
|
43070
|
+
method: "POST",
|
|
43071
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.target.token}` },
|
|
43072
|
+
body,
|
|
43073
|
+
keepalive: true
|
|
43074
|
+
});
|
|
43075
|
+
} catch {
|
|
43076
|
+
}
|
|
43077
|
+
}
|
|
43078
|
+
};
|
|
43079
|
+
function randomId() {
|
|
43080
|
+
const c = typeof crypto === "undefined" ? void 0 : crypto;
|
|
43081
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
43082
|
+
return `s-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
43083
|
+
}
|
|
43084
|
+
|
|
43002
43085
|
// src/broadcaster.ts
|
|
43003
43086
|
var STATS_INTERVAL_MS = 2e3;
|
|
43004
43087
|
var MebiusBroadcaster = class extends TypedEmitter {
|
|
43005
43088
|
/** @internal */
|
|
43006
|
-
constructor(signaling, options) {
|
|
43089
|
+
constructor(signaling, options, telemetry = null, userId) {
|
|
43007
43090
|
super();
|
|
43008
43091
|
this.options = options;
|
|
43092
|
+
this.telemetry = telemetry;
|
|
43093
|
+
this.userId = userId;
|
|
43009
43094
|
this.stream = null;
|
|
43010
43095
|
this.facingMode = "user";
|
|
43011
43096
|
this.statsTimer = null;
|
|
43012
43097
|
this.started = false;
|
|
43098
|
+
this.reporter = null;
|
|
43013
43099
|
this.transport = createPublishTransport(signaling);
|
|
43014
43100
|
}
|
|
43015
43101
|
/** Begin broadcasting under the given stream id. */
|
|
@@ -43018,12 +43104,18 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43018
43104
|
this.stream = await this.capture();
|
|
43019
43105
|
await this.transport.start(streamId, this.stream);
|
|
43020
43106
|
this.started = true;
|
|
43107
|
+
if (this.telemetry) {
|
|
43108
|
+
this.reporter = new QoeReporter(this.telemetry, "pub", streamId, this.userId);
|
|
43109
|
+
this.reporter.start();
|
|
43110
|
+
}
|
|
43021
43111
|
this.startStats();
|
|
43022
43112
|
this.emit("started", { streamId });
|
|
43023
43113
|
}
|
|
43024
43114
|
/** Stop broadcasting and release the camera/microphone. */
|
|
43025
43115
|
async stop() {
|
|
43026
43116
|
this.stopStats();
|
|
43117
|
+
await this.reporter?.stop();
|
|
43118
|
+
this.reporter = null;
|
|
43027
43119
|
await this.transport.stop();
|
|
43028
43120
|
this.stream?.getTracks().forEach((t) => t.stop());
|
|
43029
43121
|
this.stream = null;
|
|
@@ -43079,7 +43171,14 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43079
43171
|
startStats() {
|
|
43080
43172
|
this.statsTimer = setInterval(async () => {
|
|
43081
43173
|
const stats = await this.transport.getStats();
|
|
43082
|
-
if (stats)
|
|
43174
|
+
if (!stats) return;
|
|
43175
|
+
this.emit("stats", stats);
|
|
43176
|
+
this.reporter?.add({
|
|
43177
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
43178
|
+
bitrateKbps: stats.bitrateKbps,
|
|
43179
|
+
fps: stats.framesPerSecond,
|
|
43180
|
+
rttMs: stats.rttMs
|
|
43181
|
+
});
|
|
43083
43182
|
}, STATS_INTERVAL_MS);
|
|
43084
43183
|
}
|
|
43085
43184
|
stopStats() {
|
|
@@ -43097,12 +43196,15 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43097
43196
|
var FIRST_FRAME_TIMEOUT_MS = 8e3;
|
|
43098
43197
|
var MebiusPlayer = class extends TypedEmitter {
|
|
43099
43198
|
/** @internal */
|
|
43100
|
-
constructor(signaling, options = {}, deliveries = []) {
|
|
43199
|
+
constructor(signaling, options = {}, deliveries = [], telemetry = null, userId) {
|
|
43101
43200
|
super();
|
|
43201
|
+
this.telemetry = telemetry;
|
|
43202
|
+
this.userId = userId;
|
|
43102
43203
|
this.transport = null;
|
|
43103
43204
|
this.video = null;
|
|
43104
43205
|
this.statsTimer = null;
|
|
43105
43206
|
this.playing = false;
|
|
43207
|
+
this.reporter = null;
|
|
43106
43208
|
this.candidates = createViewCandidates(options.mode ?? "auto", signaling, deliveries);
|
|
43107
43209
|
}
|
|
43108
43210
|
/** Start playing `streamId` into the given video element or selector. */
|
|
@@ -43110,6 +43212,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43110
43212
|
if (this.playing) return;
|
|
43111
43213
|
const video = resolveVideoElement(viewTarget);
|
|
43112
43214
|
this.video = video;
|
|
43215
|
+
const startedAtMs = Date.now();
|
|
43113
43216
|
let lastError = null;
|
|
43114
43217
|
for (const candidate of this.candidates) {
|
|
43115
43218
|
try {
|
|
@@ -43118,6 +43221,11 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43118
43221
|
if (await hasFirstFrame(video)) {
|
|
43119
43222
|
this.transport = candidate;
|
|
43120
43223
|
this.playing = true;
|
|
43224
|
+
if (this.telemetry) {
|
|
43225
|
+
this.reporter = new QoeReporter(this.telemetry, "play", streamId, this.userId);
|
|
43226
|
+
this.reporter.start();
|
|
43227
|
+
this.reporter.add({ ts: Math.floor(Date.now() / 1e3), firstFrameMs: Date.now() - startedAtMs });
|
|
43228
|
+
}
|
|
43121
43229
|
this.startStats();
|
|
43122
43230
|
this.emit("playing", { streamId });
|
|
43123
43231
|
return;
|
|
@@ -43134,6 +43242,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43134
43242
|
/** Stop playback and detach from the video element. */
|
|
43135
43243
|
async stop() {
|
|
43136
43244
|
this.stopStats();
|
|
43245
|
+
await this.reporter?.stop();
|
|
43246
|
+
this.reporter = null;
|
|
43137
43247
|
await this.transport?.stop();
|
|
43138
43248
|
this.transport = null;
|
|
43139
43249
|
this.video = null;
|
|
@@ -43149,6 +43259,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43149
43259
|
if (this.transport !== transport) return;
|
|
43150
43260
|
this.playing = false;
|
|
43151
43261
|
this.stopStats();
|
|
43262
|
+
void this.reporter?.stop();
|
|
43263
|
+
this.reporter = null;
|
|
43152
43264
|
this.emit("ended", void 0);
|
|
43153
43265
|
});
|
|
43154
43266
|
transport.onBuffering(() => {
|
|
@@ -43159,7 +43271,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43159
43271
|
startStats() {
|
|
43160
43272
|
this.statsTimer = setInterval(async () => {
|
|
43161
43273
|
const stats = await this.transport?.getStats();
|
|
43162
|
-
if (stats)
|
|
43274
|
+
if (!stats) return;
|
|
43275
|
+
this.emit("stats", stats);
|
|
43276
|
+
this.reporter?.add({
|
|
43277
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
43278
|
+
bitrateKbps: stats.bitrateKbps,
|
|
43279
|
+
fps: stats.framesPerSecond
|
|
43280
|
+
});
|
|
43163
43281
|
}, STATS_INTERVAL_MS2);
|
|
43164
43282
|
}
|
|
43165
43283
|
stopStats() {
|
|
@@ -43294,10 +43412,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43294
43412
|
// src/client.ts
|
|
43295
43413
|
var MebiusClient = class extends TypedEmitter {
|
|
43296
43414
|
/** @internal */
|
|
43297
|
-
constructor(config2, token, deliveries = []) {
|
|
43415
|
+
constructor(config2, token, deliveries = [], telemetry = null, userId) {
|
|
43298
43416
|
super();
|
|
43299
43417
|
this.token = token;
|
|
43300
43418
|
this.deliveries = deliveries;
|
|
43419
|
+
this.telemetry = telemetry;
|
|
43420
|
+
this.userId = userId;
|
|
43301
43421
|
this.expiryTimer = null;
|
|
43302
43422
|
this.connected = false;
|
|
43303
43423
|
this.signaling = new SignalingClient(config2.gateway, token);
|
|
@@ -43322,12 +43442,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43322
43442
|
/** Create a broadcaster bound to this connection. */
|
|
43323
43443
|
createBroadcaster(options = {}) {
|
|
43324
43444
|
this.assertConnected();
|
|
43325
|
-
return new MebiusBroadcaster(this.signaling, options);
|
|
43445
|
+
return new MebiusBroadcaster(this.signaling, options, this.telemetry, this.userId);
|
|
43326
43446
|
}
|
|
43327
43447
|
/** Create a player bound to this connection. */
|
|
43328
43448
|
createPlayer(options = {}) {
|
|
43329
43449
|
this.assertConnected();
|
|
43330
|
-
return new MebiusPlayer(this.signaling, options, this.deliveries);
|
|
43450
|
+
return new MebiusPlayer(this.signaling, options, this.deliveries, this.telemetry, this.userId);
|
|
43331
43451
|
}
|
|
43332
43452
|
/**
|
|
43333
43453
|
* Create a monitor: a player tuned for watching a stream you are interacting
|
|
@@ -43342,7 +43462,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43342
43462
|
*/
|
|
43343
43463
|
createMonitor() {
|
|
43344
43464
|
this.assertConnected();
|
|
43345
|
-
return new MebiusPlayer(this.signaling, { mode: "low-latency" }, this.deliveries);
|
|
43465
|
+
return new MebiusPlayer(this.signaling, { mode: "low-latency" }, this.deliveries, this.telemetry, this.userId);
|
|
43346
43466
|
}
|
|
43347
43467
|
/** Close the connection and release resources. */
|
|
43348
43468
|
disconnect(reason) {
|
|
@@ -43375,7 +43495,8 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43375
43495
|
throw mebiusError("UNKNOWN", "Call Mebius.init() before Mebius.connect().");
|
|
43376
43496
|
}
|
|
43377
43497
|
if (!options.token) throw mebiusError("UNKNOWN", "Mebius.connect requires a token.");
|
|
43378
|
-
const
|
|
43498
|
+
const telemetry = options.beaconToken && options.beaconUrl ? { token: options.beaconToken, url: options.beaconUrl } : null;
|
|
43499
|
+
const client = new MebiusClient(config, options.token, options.deliveries ?? [], telemetry, options.userId);
|
|
43379
43500
|
client.open();
|
|
43380
43501
|
return client;
|
|
43381
43502
|
},
|