@mebius-io/web 0.4.9 → 0.5.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/dist/index.cjs +133 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +115 -1
- package/dist/index.d.ts +115 -1
- package/dist/index.global.js +145 -13
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +132 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -398,6 +398,22 @@ var HlsViewTransport = class {
|
|
|
398
398
|
framesPerSecond: 0
|
|
399
399
|
};
|
|
400
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* hls.js exposes `playingDate` straight from the segment the element is
|
|
403
|
+
* currently rendering, derived from the playlist's `EXT-X-PROGRAM-DATE-TIME`
|
|
404
|
+
* (MediaMTX writes it). Safari's native player has no such property, but
|
|
405
|
+
* `getStartDate()` (the wall-clock time of the playlist's first segment) plus
|
|
406
|
+
* elapsed `currentTime` is the same clock by construction.
|
|
407
|
+
*/
|
|
408
|
+
playheadEpochMs() {
|
|
409
|
+
if (this.hls) return this.hls.playingDate?.getTime() ?? null;
|
|
410
|
+
const video = this.video;
|
|
411
|
+
if (video?.getStartDate) {
|
|
412
|
+
const start = video.getStartDate().getTime();
|
|
413
|
+
if (Number.isFinite(start)) return start + video.currentTime * 1e3;
|
|
414
|
+
}
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
401
417
|
};
|
|
402
418
|
|
|
403
419
|
// src/internal/balanced-view-transport.ts
|
|
@@ -769,6 +785,82 @@ function normalize(c, fallback) {
|
|
|
769
785
|
return c;
|
|
770
786
|
}
|
|
771
787
|
|
|
788
|
+
// src/captions.ts
|
|
789
|
+
var TICK_MS = 100;
|
|
790
|
+
var STALE_MS = 5e3;
|
|
791
|
+
var MebiusCaptions = class extends TypedEmitter {
|
|
792
|
+
/** @internal */
|
|
793
|
+
constructor(signaling, player, opts) {
|
|
794
|
+
super();
|
|
795
|
+
this.signaling = signaling;
|
|
796
|
+
this.player = player;
|
|
797
|
+
this.opts = opts;
|
|
798
|
+
this.es = null;
|
|
799
|
+
this.timer = null;
|
|
800
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
801
|
+
this.shown = /* @__PURE__ */ new Set();
|
|
802
|
+
}
|
|
803
|
+
/** Open the SSE connection and begin emitting segments for `streamId`. */
|
|
804
|
+
start(streamId) {
|
|
805
|
+
if (this.es) return;
|
|
806
|
+
const url = this.signaling.captionsUrl(streamId, this.opts.lang);
|
|
807
|
+
const es = new EventSource(url);
|
|
808
|
+
es.onmessage = (ev) => this.onFrame(ev);
|
|
809
|
+
es.onerror = () => this.emit("error", void 0);
|
|
810
|
+
this.es = es;
|
|
811
|
+
this.timer = setInterval(() => this.tick(), TICK_MS);
|
|
812
|
+
}
|
|
813
|
+
/** Close the connection and drop all buffered segments. */
|
|
814
|
+
stop() {
|
|
815
|
+
this.es?.close();
|
|
816
|
+
this.es = null;
|
|
817
|
+
if (this.timer) clearInterval(this.timer);
|
|
818
|
+
this.timer = null;
|
|
819
|
+
this.pending.clear();
|
|
820
|
+
this.shown.clear();
|
|
821
|
+
}
|
|
822
|
+
onFrame(ev) {
|
|
823
|
+
let frame;
|
|
824
|
+
try {
|
|
825
|
+
frame = JSON.parse(ev.data);
|
|
826
|
+
} catch {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (frame.type !== "caption" || !frame.segmentId) return;
|
|
830
|
+
const prev = this.pending.get(frame.segmentId);
|
|
831
|
+
if (prev && (frame.rev ?? 0) < (prev.rev ?? 0)) return;
|
|
832
|
+
this.pending.set(frame.segmentId, frame);
|
|
833
|
+
}
|
|
834
|
+
tick() {
|
|
835
|
+
const now = this.player.currentEpochMs();
|
|
836
|
+
if (now == null) return;
|
|
837
|
+
for (const [id, frame] of this.pending) {
|
|
838
|
+
const due = frame.epochMs ?? 0;
|
|
839
|
+
if (due > now) continue;
|
|
840
|
+
if (due < now - STALE_MS) {
|
|
841
|
+
this.pending.delete(id);
|
|
842
|
+
if (this.shown.delete(id)) this.emit("cleared", { segmentId: id });
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
this.shown.add(id);
|
|
846
|
+
this.emit("segment", toSegment(id, frame, this.opts.lang));
|
|
847
|
+
if (frame.state === "final") this.pending.delete(id);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
function toSegment(segmentId, frame, lang) {
|
|
852
|
+
return {
|
|
853
|
+
segmentId,
|
|
854
|
+
rev: frame.rev ?? 0,
|
|
855
|
+
state: frame.state === "final" ? "final" : "interim",
|
|
856
|
+
epochMs: frame.epochMs ?? 0,
|
|
857
|
+
durationMs: frame.durationMs ?? 0,
|
|
858
|
+
text: frame.text ?? "",
|
|
859
|
+
translation: frame.translations?.[lang],
|
|
860
|
+
machineGenerated: true
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
|
|
772
864
|
// src/internal/freeze-clock.ts
|
|
773
865
|
var FreezeClock = class {
|
|
774
866
|
constructor(now = Date.now) {
|
|
@@ -938,6 +1030,19 @@ var MebiusPlayer = class extends TypedEmitter {
|
|
|
938
1030
|
this.video.volume = v;
|
|
939
1031
|
this.video.muted = v === 0;
|
|
940
1032
|
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Wall-clock time (Unix ms) currently on screen, or `null` when the active
|
|
1035
|
+
* route cannot produce one (HTTP-FLV, WHEP — see {@link ViewTransport}).
|
|
1036
|
+
*
|
|
1037
|
+
* This is what {@link MebiusClient.createCaptions} compares against a
|
|
1038
|
+
* segment's `epochMs` to know when it is due. Delegating to the transport
|
|
1039
|
+
* rather than reading the element directly is what keeps this correct across
|
|
1040
|
+
* a route failover: the player may switch from HLS to FLV mid-session, and
|
|
1041
|
+
* the clock source has to follow.
|
|
1042
|
+
*/
|
|
1043
|
+
currentEpochMs() {
|
|
1044
|
+
return this.transport?.playheadEpochMs?.() ?? null;
|
|
1045
|
+
}
|
|
941
1046
|
attach(transport) {
|
|
942
1047
|
transport.onEnded(() => {
|
|
943
1048
|
if (this.transport !== transport) return;
|
|
@@ -1032,6 +1137,16 @@ var SignalingClient = class {
|
|
|
1032
1137
|
scalePlaylistUrl(streamId) {
|
|
1033
1138
|
return this.withToken(`${this.base()}/live/${encodeURIComponent(streamId)}/index.m3u8`);
|
|
1034
1139
|
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Realtime captions SSE URL. Same play token as media — the engine's
|
|
1142
|
+
* `PlayVerifier` gates both, so a viewer who can watch the stream can already
|
|
1143
|
+
* read its captions with zero extra credential.
|
|
1144
|
+
*/
|
|
1145
|
+
captionsUrl(streamId, lang) {
|
|
1146
|
+
return this.withToken(
|
|
1147
|
+
`${this.base()}/live/${encodeURIComponent(streamId)}/captions?lang=${encodeURIComponent(lang)}`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1035
1150
|
// Maps a neutral session kind to the concrete signaling path segment. This
|
|
1036
1151
|
// mapping (publish -> WHIP, view -> WHEP) lives ONLY in this method body, so
|
|
1037
1152
|
// the protocol names never appear in any exported type signature.
|
|
@@ -1155,6 +1270,22 @@ var MebiusClient = class extends TypedEmitter {
|
|
|
1155
1270
|
this.assertConnected();
|
|
1156
1271
|
return new MebiusPlayer(this.signaling, { mode: "low-latency" }, this.deliveries, this.telemetry, this.userId);
|
|
1157
1272
|
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Subscribe to a stream's realtime captions.
|
|
1275
|
+
*
|
|
1276
|
+
* Reads the same feed a session already produces — it does NOT start the
|
|
1277
|
+
* caption session itself. `captions/start` spends money and requires an API
|
|
1278
|
+
* key, so it belongs to your own backend (see
|
|
1279
|
+
* mebius-stream-engine/docs/API.md §5.1), called once when you want captions
|
|
1280
|
+
* on for a stream. This only ever consumes what that call turned on.
|
|
1281
|
+
*
|
|
1282
|
+
* `player` must be the one showing `streamId`: captions are timed against its
|
|
1283
|
+
* playhead, and a mismatched player would compare against the wrong clock.
|
|
1284
|
+
*/
|
|
1285
|
+
createCaptions(player, options) {
|
|
1286
|
+
this.assertConnected();
|
|
1287
|
+
return new MebiusCaptions(this.signaling, player, options);
|
|
1288
|
+
}
|
|
1158
1289
|
/** Close the connection and release resources. */
|
|
1159
1290
|
disconnect(reason) {
|
|
1160
1291
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
@@ -1199,6 +1330,7 @@ var Mebius = {
|
|
|
1199
1330
|
export {
|
|
1200
1331
|
Mebius,
|
|
1201
1332
|
MebiusBroadcaster,
|
|
1333
|
+
MebiusCaptions,
|
|
1202
1334
|
MebiusClient,
|
|
1203
1335
|
MebiusError,
|
|
1204
1336
|
MebiusPlayer,
|