@mebius-io/web 0.4.8 → 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 +152 -1
- 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 +164 -14
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +151 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -123,6 +123,37 @@ interface PlaybackStats {
|
|
|
123
123
|
/** Estimated end-to-end latency in milliseconds, if known. */
|
|
124
124
|
latencyMs?: number;
|
|
125
125
|
}
|
|
126
|
+
/** Options for {@link MebiusClient.createCaptions}. */
|
|
127
|
+
interface CaptionsOptions {
|
|
128
|
+
/**
|
|
129
|
+
* Which translation to read from each segment, e.g. `"id"`. Must match a
|
|
130
|
+
* `targetLangs` entry your backend passed to `captions/start` — the engine
|
|
131
|
+
* only ever sends the translations that session produced.
|
|
132
|
+
*/
|
|
133
|
+
lang: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* One caption segment, timed against a wall clock so it can be compared to
|
|
137
|
+
* {@link MebiusPlayer.currentEpochMs}.
|
|
138
|
+
*/
|
|
139
|
+
interface CaptionSegment {
|
|
140
|
+
/** Stable id for this segment. A later revision replaces it in place. */
|
|
141
|
+
segmentId: string;
|
|
142
|
+
/** Revision counter. Only the highest-`rev` copy of a `segmentId` is kept. */
|
|
143
|
+
rev: number;
|
|
144
|
+
/** `"interim"` or `"final"`. Interim only arrives if the session enabled it. */
|
|
145
|
+
state: "interim" | "final";
|
|
146
|
+
/** Unix ms the audio was actually spoken. Compare against the playhead. */
|
|
147
|
+
epochMs: number;
|
|
148
|
+
/** How long the segment should stay on screen once due, in ms. */
|
|
149
|
+
durationMs: number;
|
|
150
|
+
/** Original transcript, in the source language. */
|
|
151
|
+
text: string;
|
|
152
|
+
/** The requested {@link CaptionsOptions.lang} translation, if produced yet. */
|
|
153
|
+
translation?: string;
|
|
154
|
+
/** Always `true`. Render a machine-generated indicator — never as a direct quote. */
|
|
155
|
+
machineGenerated: true;
|
|
156
|
+
}
|
|
126
157
|
/** Canonical Mebius error codes surfaced to your app. */
|
|
127
158
|
type MebiusErrorCode = "TOKEN_EXPIRED" | "PERMISSION_DENIED" | "CONNECTION_FAILED" | "NOT_CONNECTED" | "STREAM_NOT_FOUND" | "UNKNOWN";
|
|
128
159
|
|
|
@@ -167,6 +198,17 @@ type PlayerEventMap = {
|
|
|
167
198
|
ended: void;
|
|
168
199
|
stats: PlaybackStats;
|
|
169
200
|
};
|
|
201
|
+
/** Event payloads emitted by {@link MebiusCaptions}. */
|
|
202
|
+
type CaptionsEventMap = {
|
|
203
|
+
/** A segment became due (its `epochMs` reached the playhead). Render it. */
|
|
204
|
+
segment: CaptionSegment;
|
|
205
|
+
/** A previously-shown segment aged out (playhead passed its window). Clear it. */
|
|
206
|
+
cleared: {
|
|
207
|
+
segmentId: string;
|
|
208
|
+
};
|
|
209
|
+
/** The SSE connection dropped. `EventSource` reconnects on its own. */
|
|
210
|
+
error: void;
|
|
211
|
+
};
|
|
170
212
|
type Listener<T> = (payload: T) => void;
|
|
171
213
|
/**
|
|
172
214
|
* A tiny strongly-typed event emitter. `EventMap` maps each event name to its
|
|
@@ -216,6 +258,12 @@ declare class SignalingClient {
|
|
|
216
258
|
deliveryUrl(path: string): string;
|
|
217
259
|
/** Playlist URL for scale-mode playback. */
|
|
218
260
|
scalePlaylistUrl(streamId: string): string;
|
|
261
|
+
/**
|
|
262
|
+
* Realtime captions SSE URL. Same play token as media — the engine's
|
|
263
|
+
* `PlayVerifier` gates both, so a viewer who can watch the stream can already
|
|
264
|
+
* read its captions with zero extra credential.
|
|
265
|
+
*/
|
|
266
|
+
captionsUrl(streamId: string, lang: string): string;
|
|
219
267
|
private pathFor;
|
|
220
268
|
/**
|
|
221
269
|
* Run the session offer/answer exchange. Throws a {@link MebiusError} with a
|
|
@@ -315,11 +363,64 @@ declare class MebiusPlayer extends TypedEmitter<PlayerEventMap> {
|
|
|
315
363
|
* drags to zero also survives a later unmute at the element level.
|
|
316
364
|
*/
|
|
317
365
|
setVolume(volume: number): void;
|
|
366
|
+
/**
|
|
367
|
+
* Wall-clock time (Unix ms) currently on screen, or `null` when the active
|
|
368
|
+
* route cannot produce one (HTTP-FLV, WHEP — see {@link ViewTransport}).
|
|
369
|
+
*
|
|
370
|
+
* This is what {@link MebiusClient.createCaptions} compares against a
|
|
371
|
+
* segment's `epochMs` to know when it is due. Delegating to the transport
|
|
372
|
+
* rather than reading the element directly is what keeps this correct across
|
|
373
|
+
* a route failover: the player may switch from HLS to FLV mid-session, and
|
|
374
|
+
* the clock source has to follow.
|
|
375
|
+
*/
|
|
376
|
+
currentEpochMs(): number | null;
|
|
318
377
|
private attach;
|
|
319
378
|
private startStats;
|
|
320
379
|
private stopStats;
|
|
321
380
|
}
|
|
322
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Realtime captions — subscribes to the engine's caption SSE feed and emits
|
|
384
|
+
* segments on the video's own timeline, not on arrival order.
|
|
385
|
+
*
|
|
386
|
+
* The engine (mebius-stream-engine, `internal/caption`) does the ASR/translate
|
|
387
|
+
* work and starts sending segments the moment a sentence finishes — which is
|
|
388
|
+
* BEFORE the viewer's player, sitting behind the live edge by however much its
|
|
389
|
+
* transport buffers, has shown the matching frame. Rendering on arrival would
|
|
390
|
+
* show the caption before the streamer says it. So every segment is buffered by
|
|
391
|
+
* `epochMs` (when the audio was actually spoken, a wall clock) and released only
|
|
392
|
+
* once {@link MebiusPlayer.currentEpochMs} reaches it. See
|
|
393
|
+
* mebius-stream-engine/docs/INTEGRATION.md §6.
|
|
394
|
+
*/
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Subscribes to one stream's caption feed. Create with
|
|
398
|
+
* {@link MebiusClient.createCaptions}, `start()` it, and listen for `"segment"`
|
|
399
|
+
* / `"cleared"`.
|
|
400
|
+
*
|
|
401
|
+
* Starting the caption SESSION (`captions/start`, which spends money) is a
|
|
402
|
+
* separate, server-side call — this class only ever reads the feed a session
|
|
403
|
+
* already produces. That split is deliberate: the control endpoint needs an API
|
|
404
|
+
* key, which must never reach a browser.
|
|
405
|
+
*/
|
|
406
|
+
declare class MebiusCaptions extends TypedEmitter<CaptionsEventMap> {
|
|
407
|
+
private readonly signaling;
|
|
408
|
+
private readonly player;
|
|
409
|
+
private readonly opts;
|
|
410
|
+
private es;
|
|
411
|
+
private timer;
|
|
412
|
+
private readonly pending;
|
|
413
|
+
private readonly shown;
|
|
414
|
+
/** @internal */
|
|
415
|
+
constructor(signaling: SignalingClient, player: MebiusPlayer, opts: CaptionsOptions);
|
|
416
|
+
/** Open the SSE connection and begin emitting segments for `streamId`. */
|
|
417
|
+
start(streamId: string): void;
|
|
418
|
+
/** Close the connection and drop all buffered segments. */
|
|
419
|
+
stop(): void;
|
|
420
|
+
private onFrame;
|
|
421
|
+
private tick;
|
|
422
|
+
}
|
|
423
|
+
|
|
323
424
|
/**
|
|
324
425
|
* A live connection to Mebius. Obtain one from {@link Mebius.connect}, then
|
|
325
426
|
* create broadcasters and players from it.
|
|
@@ -352,6 +453,19 @@ declare class MebiusClient extends TypedEmitter<ClientEventMap> {
|
|
|
352
453
|
* black frame to a live audience, so it belongs here rather than in every app.
|
|
353
454
|
*/
|
|
354
455
|
createMonitor(): MebiusPlayer;
|
|
456
|
+
/**
|
|
457
|
+
* Subscribe to a stream's realtime captions.
|
|
458
|
+
*
|
|
459
|
+
* Reads the same feed a session already produces — it does NOT start the
|
|
460
|
+
* caption session itself. `captions/start` spends money and requires an API
|
|
461
|
+
* key, so it belongs to your own backend (see
|
|
462
|
+
* mebius-stream-engine/docs/API.md §5.1), called once when you want captions
|
|
463
|
+
* on for a stream. This only ever consumes what that call turned on.
|
|
464
|
+
*
|
|
465
|
+
* `player` must be the one showing `streamId`: captions are timed against its
|
|
466
|
+
* playhead, and a mismatched player would compare against the wrong clock.
|
|
467
|
+
*/
|
|
468
|
+
createCaptions(player: MebiusPlayer, options: CaptionsOptions): MebiusCaptions;
|
|
355
469
|
/** Close the connection and release resources. */
|
|
356
470
|
disconnect(reason?: string): void;
|
|
357
471
|
private assertConnected;
|
|
@@ -377,4 +491,4 @@ declare const Mebius: {
|
|
|
377
491
|
_reset(): void;
|
|
378
492
|
};
|
|
379
493
|
|
|
380
|
-
export { type BroadcastStats, type BroadcasterEventMap, type BroadcasterOptions, type ClientEventMap, Mebius, MebiusBroadcaster, MebiusClient, type MebiusConnectOptions, type MebiusDelivery, MebiusError, type MebiusErrorCode, type MebiusInitOptions, MebiusPlayer, type MediaConstraint, type PlaybackMode, type PlaybackStats, type PlayerEventMap, type PlayerOptions, type ViewTarget, mebiusError };
|
|
494
|
+
export { type BroadcastStats, type BroadcasterEventMap, type BroadcasterOptions, type CaptionSegment, type CaptionsEventMap, type CaptionsOptions, type ClientEventMap, Mebius, MebiusBroadcaster, MebiusCaptions, MebiusClient, type MebiusConnectOptions, type MebiusDelivery, MebiusError, type MebiusErrorCode, type MebiusInitOptions, MebiusPlayer, type MediaConstraint, type PlaybackMode, type PlaybackStats, type PlayerEventMap, type PlayerOptions, type ViewTarget, mebiusError };
|
package/dist/index.d.ts
CHANGED
|
@@ -123,6 +123,37 @@ interface PlaybackStats {
|
|
|
123
123
|
/** Estimated end-to-end latency in milliseconds, if known. */
|
|
124
124
|
latencyMs?: number;
|
|
125
125
|
}
|
|
126
|
+
/** Options for {@link MebiusClient.createCaptions}. */
|
|
127
|
+
interface CaptionsOptions {
|
|
128
|
+
/**
|
|
129
|
+
* Which translation to read from each segment, e.g. `"id"`. Must match a
|
|
130
|
+
* `targetLangs` entry your backend passed to `captions/start` — the engine
|
|
131
|
+
* only ever sends the translations that session produced.
|
|
132
|
+
*/
|
|
133
|
+
lang: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* One caption segment, timed against a wall clock so it can be compared to
|
|
137
|
+
* {@link MebiusPlayer.currentEpochMs}.
|
|
138
|
+
*/
|
|
139
|
+
interface CaptionSegment {
|
|
140
|
+
/** Stable id for this segment. A later revision replaces it in place. */
|
|
141
|
+
segmentId: string;
|
|
142
|
+
/** Revision counter. Only the highest-`rev` copy of a `segmentId` is kept. */
|
|
143
|
+
rev: number;
|
|
144
|
+
/** `"interim"` or `"final"`. Interim only arrives if the session enabled it. */
|
|
145
|
+
state: "interim" | "final";
|
|
146
|
+
/** Unix ms the audio was actually spoken. Compare against the playhead. */
|
|
147
|
+
epochMs: number;
|
|
148
|
+
/** How long the segment should stay on screen once due, in ms. */
|
|
149
|
+
durationMs: number;
|
|
150
|
+
/** Original transcript, in the source language. */
|
|
151
|
+
text: string;
|
|
152
|
+
/** The requested {@link CaptionsOptions.lang} translation, if produced yet. */
|
|
153
|
+
translation?: string;
|
|
154
|
+
/** Always `true`. Render a machine-generated indicator — never as a direct quote. */
|
|
155
|
+
machineGenerated: true;
|
|
156
|
+
}
|
|
126
157
|
/** Canonical Mebius error codes surfaced to your app. */
|
|
127
158
|
type MebiusErrorCode = "TOKEN_EXPIRED" | "PERMISSION_DENIED" | "CONNECTION_FAILED" | "NOT_CONNECTED" | "STREAM_NOT_FOUND" | "UNKNOWN";
|
|
128
159
|
|
|
@@ -167,6 +198,17 @@ type PlayerEventMap = {
|
|
|
167
198
|
ended: void;
|
|
168
199
|
stats: PlaybackStats;
|
|
169
200
|
};
|
|
201
|
+
/** Event payloads emitted by {@link MebiusCaptions}. */
|
|
202
|
+
type CaptionsEventMap = {
|
|
203
|
+
/** A segment became due (its `epochMs` reached the playhead). Render it. */
|
|
204
|
+
segment: CaptionSegment;
|
|
205
|
+
/** A previously-shown segment aged out (playhead passed its window). Clear it. */
|
|
206
|
+
cleared: {
|
|
207
|
+
segmentId: string;
|
|
208
|
+
};
|
|
209
|
+
/** The SSE connection dropped. `EventSource` reconnects on its own. */
|
|
210
|
+
error: void;
|
|
211
|
+
};
|
|
170
212
|
type Listener<T> = (payload: T) => void;
|
|
171
213
|
/**
|
|
172
214
|
* A tiny strongly-typed event emitter. `EventMap` maps each event name to its
|
|
@@ -216,6 +258,12 @@ declare class SignalingClient {
|
|
|
216
258
|
deliveryUrl(path: string): string;
|
|
217
259
|
/** Playlist URL for scale-mode playback. */
|
|
218
260
|
scalePlaylistUrl(streamId: string): string;
|
|
261
|
+
/**
|
|
262
|
+
* Realtime captions SSE URL. Same play token as media — the engine's
|
|
263
|
+
* `PlayVerifier` gates both, so a viewer who can watch the stream can already
|
|
264
|
+
* read its captions with zero extra credential.
|
|
265
|
+
*/
|
|
266
|
+
captionsUrl(streamId: string, lang: string): string;
|
|
219
267
|
private pathFor;
|
|
220
268
|
/**
|
|
221
269
|
* Run the session offer/answer exchange. Throws a {@link MebiusError} with a
|
|
@@ -315,11 +363,64 @@ declare class MebiusPlayer extends TypedEmitter<PlayerEventMap> {
|
|
|
315
363
|
* drags to zero also survives a later unmute at the element level.
|
|
316
364
|
*/
|
|
317
365
|
setVolume(volume: number): void;
|
|
366
|
+
/**
|
|
367
|
+
* Wall-clock time (Unix ms) currently on screen, or `null` when the active
|
|
368
|
+
* route cannot produce one (HTTP-FLV, WHEP — see {@link ViewTransport}).
|
|
369
|
+
*
|
|
370
|
+
* This is what {@link MebiusClient.createCaptions} compares against a
|
|
371
|
+
* segment's `epochMs` to know when it is due. Delegating to the transport
|
|
372
|
+
* rather than reading the element directly is what keeps this correct across
|
|
373
|
+
* a route failover: the player may switch from HLS to FLV mid-session, and
|
|
374
|
+
* the clock source has to follow.
|
|
375
|
+
*/
|
|
376
|
+
currentEpochMs(): number | null;
|
|
318
377
|
private attach;
|
|
319
378
|
private startStats;
|
|
320
379
|
private stopStats;
|
|
321
380
|
}
|
|
322
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Realtime captions — subscribes to the engine's caption SSE feed and emits
|
|
384
|
+
* segments on the video's own timeline, not on arrival order.
|
|
385
|
+
*
|
|
386
|
+
* The engine (mebius-stream-engine, `internal/caption`) does the ASR/translate
|
|
387
|
+
* work and starts sending segments the moment a sentence finishes — which is
|
|
388
|
+
* BEFORE the viewer's player, sitting behind the live edge by however much its
|
|
389
|
+
* transport buffers, has shown the matching frame. Rendering on arrival would
|
|
390
|
+
* show the caption before the streamer says it. So every segment is buffered by
|
|
391
|
+
* `epochMs` (when the audio was actually spoken, a wall clock) and released only
|
|
392
|
+
* once {@link MebiusPlayer.currentEpochMs} reaches it. See
|
|
393
|
+
* mebius-stream-engine/docs/INTEGRATION.md §6.
|
|
394
|
+
*/
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Subscribes to one stream's caption feed. Create with
|
|
398
|
+
* {@link MebiusClient.createCaptions}, `start()` it, and listen for `"segment"`
|
|
399
|
+
* / `"cleared"`.
|
|
400
|
+
*
|
|
401
|
+
* Starting the caption SESSION (`captions/start`, which spends money) is a
|
|
402
|
+
* separate, server-side call — this class only ever reads the feed a session
|
|
403
|
+
* already produces. That split is deliberate: the control endpoint needs an API
|
|
404
|
+
* key, which must never reach a browser.
|
|
405
|
+
*/
|
|
406
|
+
declare class MebiusCaptions extends TypedEmitter<CaptionsEventMap> {
|
|
407
|
+
private readonly signaling;
|
|
408
|
+
private readonly player;
|
|
409
|
+
private readonly opts;
|
|
410
|
+
private es;
|
|
411
|
+
private timer;
|
|
412
|
+
private readonly pending;
|
|
413
|
+
private readonly shown;
|
|
414
|
+
/** @internal */
|
|
415
|
+
constructor(signaling: SignalingClient, player: MebiusPlayer, opts: CaptionsOptions);
|
|
416
|
+
/** Open the SSE connection and begin emitting segments for `streamId`. */
|
|
417
|
+
start(streamId: string): void;
|
|
418
|
+
/** Close the connection and drop all buffered segments. */
|
|
419
|
+
stop(): void;
|
|
420
|
+
private onFrame;
|
|
421
|
+
private tick;
|
|
422
|
+
}
|
|
423
|
+
|
|
323
424
|
/**
|
|
324
425
|
* A live connection to Mebius. Obtain one from {@link Mebius.connect}, then
|
|
325
426
|
* create broadcasters and players from it.
|
|
@@ -352,6 +453,19 @@ declare class MebiusClient extends TypedEmitter<ClientEventMap> {
|
|
|
352
453
|
* black frame to a live audience, so it belongs here rather than in every app.
|
|
353
454
|
*/
|
|
354
455
|
createMonitor(): MebiusPlayer;
|
|
456
|
+
/**
|
|
457
|
+
* Subscribe to a stream's realtime captions.
|
|
458
|
+
*
|
|
459
|
+
* Reads the same feed a session already produces — it does NOT start the
|
|
460
|
+
* caption session itself. `captions/start` spends money and requires an API
|
|
461
|
+
* key, so it belongs to your own backend (see
|
|
462
|
+
* mebius-stream-engine/docs/API.md §5.1), called once when you want captions
|
|
463
|
+
* on for a stream. This only ever consumes what that call turned on.
|
|
464
|
+
*
|
|
465
|
+
* `player` must be the one showing `streamId`: captions are timed against its
|
|
466
|
+
* playhead, and a mismatched player would compare against the wrong clock.
|
|
467
|
+
*/
|
|
468
|
+
createCaptions(player: MebiusPlayer, options: CaptionsOptions): MebiusCaptions;
|
|
355
469
|
/** Close the connection and release resources. */
|
|
356
470
|
disconnect(reason?: string): void;
|
|
357
471
|
private assertConnected;
|
|
@@ -377,4 +491,4 @@ declare const Mebius: {
|
|
|
377
491
|
_reset(): void;
|
|
378
492
|
};
|
|
379
493
|
|
|
380
|
-
export { type BroadcastStats, type BroadcasterEventMap, type BroadcasterOptions, type ClientEventMap, Mebius, MebiusBroadcaster, MebiusClient, type MebiusConnectOptions, type MebiusDelivery, MebiusError, type MebiusErrorCode, type MebiusInitOptions, MebiusPlayer, type MediaConstraint, type PlaybackMode, type PlaybackStats, type PlayerEventMap, type PlayerOptions, type ViewTarget, mebiusError };
|
|
494
|
+
export { type BroadcastStats, type BroadcasterEventMap, type BroadcasterOptions, type CaptionSegment, type CaptionsEventMap, type CaptionsOptions, type ClientEventMap, Mebius, MebiusBroadcaster, MebiusCaptions, MebiusClient, type MebiusConnectOptions, type MebiusDelivery, MebiusError, type MebiusErrorCode, type MebiusInitOptions, MebiusPlayer, type MediaConstraint, type PlaybackMode, type PlaybackStats, type PlayerEventMap, type PlayerOptions, type ViewTarget, mebiusError };
|
package/dist/index.global.js
CHANGED
|
@@ -23901,19 +23901,19 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
23901
23901
|
isInterstitial(item) {
|
|
23902
23902
|
return !!(item != null && item.event);
|
|
23903
23903
|
}
|
|
23904
|
-
retreiveMediaSource(assetId,
|
|
23904
|
+
retreiveMediaSource(assetId, toSegment2) {
|
|
23905
23905
|
const player = this.getAssetPlayer(assetId);
|
|
23906
23906
|
if (player) {
|
|
23907
|
-
this.transferMediaFromPlayer(player,
|
|
23907
|
+
this.transferMediaFromPlayer(player, toSegment2);
|
|
23908
23908
|
}
|
|
23909
23909
|
}
|
|
23910
|
-
transferMediaFromPlayer(player,
|
|
23910
|
+
transferMediaFromPlayer(player, toSegment2) {
|
|
23911
23911
|
const appendInPlace = player.interstitial.appendInPlace;
|
|
23912
23912
|
const playerMedia = player.media;
|
|
23913
23913
|
if (appendInPlace && playerMedia === this.primaryMedia) {
|
|
23914
23914
|
this.bufferingAsset = null;
|
|
23915
|
-
if (!
|
|
23916
|
-
if (
|
|
23915
|
+
if (!toSegment2 || this.isInterstitial(toSegment2) && !toSegment2.event.appendInPlace) {
|
|
23916
|
+
if (toSegment2 && playerMedia) {
|
|
23917
23917
|
this.detachedData = {
|
|
23918
23918
|
media: playerMedia
|
|
23919
23919
|
};
|
|
@@ -23923,7 +23923,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
23923
23923
|
const attachMediaSourceData = player.transferMedia();
|
|
23924
23924
|
this.log(`transfer MediaSource from ${player} ${stringify(attachMediaSourceData)}`);
|
|
23925
23925
|
this.detachedData = attachMediaSourceData;
|
|
23926
|
-
} else if (
|
|
23926
|
+
} else if (toSegment2 && playerMedia) {
|
|
23927
23927
|
this.shouldPlay || (this.shouldPlay = !playerMedia.paused);
|
|
23928
23928
|
}
|
|
23929
23929
|
}
|
|
@@ -24942,13 +24942,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
24942
24942
|
});
|
|
24943
24943
|
return player;
|
|
24944
24944
|
}
|
|
24945
|
-
clearInterstitial(interstitial,
|
|
24946
|
-
this.clearAssetPlayers(interstitial,
|
|
24945
|
+
clearInterstitial(interstitial, toSegment2) {
|
|
24946
|
+
this.clearAssetPlayers(interstitial, toSegment2);
|
|
24947
24947
|
interstitial.reset();
|
|
24948
24948
|
}
|
|
24949
|
-
clearAssetPlayers(interstitial,
|
|
24949
|
+
clearAssetPlayers(interstitial, toSegment2) {
|
|
24950
24950
|
interstitial.assetList.forEach((asset) => {
|
|
24951
|
-
this.clearAssetPlayer(asset.identifier,
|
|
24951
|
+
this.clearAssetPlayer(asset.identifier, toSegment2);
|
|
24952
24952
|
});
|
|
24953
24953
|
}
|
|
24954
24954
|
resetAssetPlayer(assetId) {
|
|
@@ -24960,12 +24960,12 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
24960
24960
|
player.resetDetails();
|
|
24961
24961
|
}
|
|
24962
24962
|
}
|
|
24963
|
-
clearAssetPlayer(assetId,
|
|
24963
|
+
clearAssetPlayer(assetId, toSegment2) {
|
|
24964
24964
|
const playerIndex = this.getAssetPlayerQueueIndex(assetId);
|
|
24965
24965
|
if (playerIndex !== -1) {
|
|
24966
24966
|
const player = this.playerQueue[playerIndex];
|
|
24967
|
-
this.log(`clear ${player} toSegment: ${
|
|
24968
|
-
this.transferMediaFromPlayer(player,
|
|
24967
|
+
this.log(`clear ${player} toSegment: ${toSegment2 ? segmentToString(toSegment2) : toSegment2}`);
|
|
24968
|
+
this.transferMediaFromPlayer(player, toSegment2);
|
|
24969
24969
|
this.playerQueue.splice(playerIndex, 1);
|
|
24970
24970
|
player.destroy();
|
|
24971
24971
|
}
|
|
@@ -42609,6 +42609,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42609
42609
|
__export(index_exports, {
|
|
42610
42610
|
Mebius: () => Mebius,
|
|
42611
42611
|
MebiusBroadcaster: () => MebiusBroadcaster,
|
|
42612
|
+
MebiusCaptions: () => MebiusCaptions,
|
|
42612
42613
|
MebiusClient: () => MebiusClient,
|
|
42613
42614
|
MebiusError: () => MebiusError,
|
|
42614
42615
|
MebiusPlayer: () => MebiusPlayer,
|
|
@@ -42922,6 +42923,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42922
42923
|
};
|
|
42923
42924
|
|
|
42924
42925
|
// src/internal/scale-view-transport.ts
|
|
42926
|
+
function retryWarmupNotFound(cfg, retryCount, res, retry) {
|
|
42927
|
+
return retry || retryCount < (cfg?.maxNumRetry ?? 0) && res?.code === 404;
|
|
42928
|
+
}
|
|
42925
42929
|
var HlsViewTransport = class {
|
|
42926
42930
|
/**
|
|
42927
42931
|
* deliveryPath, when given, is a gateway-relative path from the gateway's own
|
|
@@ -42969,7 +42973,22 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42969
42973
|
this.mutedByPolicy = (await playWithAutoplayFallback(video)).mutedByPolicy;
|
|
42970
42974
|
return;
|
|
42971
42975
|
}
|
|
42972
|
-
const hls = new Hls2({
|
|
42976
|
+
const hls = new Hls2({
|
|
42977
|
+
maxLiveSyncPlaybackRate: 1.1,
|
|
42978
|
+
manifestLoadPolicy: {
|
|
42979
|
+
default: {
|
|
42980
|
+
maxTimeToFirstByteMs: 1e4,
|
|
42981
|
+
maxLoadTimeMs: 2e4,
|
|
42982
|
+
timeoutRetry: { maxNumRetry: 2, retryDelayMs: 0, maxRetryDelayMs: 0 },
|
|
42983
|
+
errorRetry: {
|
|
42984
|
+
maxNumRetry: 5,
|
|
42985
|
+
retryDelayMs: 500,
|
|
42986
|
+
maxRetryDelayMs: 2e3,
|
|
42987
|
+
shouldRetry: (cfg, retryCount, _isTimeout, res, retry) => retryWarmupNotFound(cfg, retryCount, res, retry)
|
|
42988
|
+
}
|
|
42989
|
+
}
|
|
42990
|
+
}
|
|
42991
|
+
});
|
|
42973
42992
|
this.hls = hls;
|
|
42974
42993
|
hls.on(Hls2.Events.ERROR, (_evt, data) => {
|
|
42975
42994
|
if (data.fatal) this.bufferingCb?.();
|
|
@@ -42997,6 +43016,22 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
42997
43016
|
framesPerSecond: 0
|
|
42998
43017
|
};
|
|
42999
43018
|
}
|
|
43019
|
+
/**
|
|
43020
|
+
* hls.js exposes `playingDate` straight from the segment the element is
|
|
43021
|
+
* currently rendering, derived from the playlist's `EXT-X-PROGRAM-DATE-TIME`
|
|
43022
|
+
* (MediaMTX writes it). Safari's native player has no such property, but
|
|
43023
|
+
* `getStartDate()` (the wall-clock time of the playlist's first segment) plus
|
|
43024
|
+
* elapsed `currentTime` is the same clock by construction.
|
|
43025
|
+
*/
|
|
43026
|
+
playheadEpochMs() {
|
|
43027
|
+
if (this.hls) return this.hls.playingDate?.getTime() ?? null;
|
|
43028
|
+
const video = this.video;
|
|
43029
|
+
if (video?.getStartDate) {
|
|
43030
|
+
const start = video.getStartDate().getTime();
|
|
43031
|
+
if (Number.isFinite(start)) return start + video.currentTime * 1e3;
|
|
43032
|
+
}
|
|
43033
|
+
return null;
|
|
43034
|
+
}
|
|
43000
43035
|
};
|
|
43001
43036
|
|
|
43002
43037
|
// src/internal/balanced-view-transport.ts
|
|
@@ -43368,6 +43403,82 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43368
43403
|
return c;
|
|
43369
43404
|
}
|
|
43370
43405
|
|
|
43406
|
+
// src/captions.ts
|
|
43407
|
+
var TICK_MS = 100;
|
|
43408
|
+
var STALE_MS = 5e3;
|
|
43409
|
+
var MebiusCaptions = class extends TypedEmitter {
|
|
43410
|
+
/** @internal */
|
|
43411
|
+
constructor(signaling, player, opts) {
|
|
43412
|
+
super();
|
|
43413
|
+
this.signaling = signaling;
|
|
43414
|
+
this.player = player;
|
|
43415
|
+
this.opts = opts;
|
|
43416
|
+
this.es = null;
|
|
43417
|
+
this.timer = null;
|
|
43418
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
43419
|
+
this.shown = /* @__PURE__ */ new Set();
|
|
43420
|
+
}
|
|
43421
|
+
/** Open the SSE connection and begin emitting segments for `streamId`. */
|
|
43422
|
+
start(streamId) {
|
|
43423
|
+
if (this.es) return;
|
|
43424
|
+
const url = this.signaling.captionsUrl(streamId, this.opts.lang);
|
|
43425
|
+
const es = new EventSource(url);
|
|
43426
|
+
es.onmessage = (ev) => this.onFrame(ev);
|
|
43427
|
+
es.onerror = () => this.emit("error", void 0);
|
|
43428
|
+
this.es = es;
|
|
43429
|
+
this.timer = setInterval(() => this.tick(), TICK_MS);
|
|
43430
|
+
}
|
|
43431
|
+
/** Close the connection and drop all buffered segments. */
|
|
43432
|
+
stop() {
|
|
43433
|
+
this.es?.close();
|
|
43434
|
+
this.es = null;
|
|
43435
|
+
if (this.timer) clearInterval(this.timer);
|
|
43436
|
+
this.timer = null;
|
|
43437
|
+
this.pending.clear();
|
|
43438
|
+
this.shown.clear();
|
|
43439
|
+
}
|
|
43440
|
+
onFrame(ev) {
|
|
43441
|
+
let frame;
|
|
43442
|
+
try {
|
|
43443
|
+
frame = JSON.parse(ev.data);
|
|
43444
|
+
} catch {
|
|
43445
|
+
return;
|
|
43446
|
+
}
|
|
43447
|
+
if (frame.type !== "caption" || !frame.segmentId) return;
|
|
43448
|
+
const prev = this.pending.get(frame.segmentId);
|
|
43449
|
+
if (prev && (frame.rev ?? 0) < (prev.rev ?? 0)) return;
|
|
43450
|
+
this.pending.set(frame.segmentId, frame);
|
|
43451
|
+
}
|
|
43452
|
+
tick() {
|
|
43453
|
+
const now2 = this.player.currentEpochMs();
|
|
43454
|
+
if (now2 == null) return;
|
|
43455
|
+
for (const [id, frame] of this.pending) {
|
|
43456
|
+
const due = frame.epochMs ?? 0;
|
|
43457
|
+
if (due > now2) continue;
|
|
43458
|
+
if (due < now2 - STALE_MS) {
|
|
43459
|
+
this.pending.delete(id);
|
|
43460
|
+
if (this.shown.delete(id)) this.emit("cleared", { segmentId: id });
|
|
43461
|
+
continue;
|
|
43462
|
+
}
|
|
43463
|
+
this.shown.add(id);
|
|
43464
|
+
this.emit("segment", toSegment(id, frame, this.opts.lang));
|
|
43465
|
+
if (frame.state === "final") this.pending.delete(id);
|
|
43466
|
+
}
|
|
43467
|
+
}
|
|
43468
|
+
};
|
|
43469
|
+
function toSegment(segmentId, frame, lang) {
|
|
43470
|
+
return {
|
|
43471
|
+
segmentId,
|
|
43472
|
+
rev: frame.rev ?? 0,
|
|
43473
|
+
state: frame.state === "final" ? "final" : "interim",
|
|
43474
|
+
epochMs: frame.epochMs ?? 0,
|
|
43475
|
+
durationMs: frame.durationMs ?? 0,
|
|
43476
|
+
text: frame.text ?? "",
|
|
43477
|
+
translation: frame.translations?.[lang],
|
|
43478
|
+
machineGenerated: true
|
|
43479
|
+
};
|
|
43480
|
+
}
|
|
43481
|
+
|
|
43371
43482
|
// src/internal/freeze-clock.ts
|
|
43372
43483
|
var FreezeClock = class {
|
|
43373
43484
|
constructor(now2 = Date.now) {
|
|
@@ -43537,6 +43648,19 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43537
43648
|
this.video.volume = v;
|
|
43538
43649
|
this.video.muted = v === 0;
|
|
43539
43650
|
}
|
|
43651
|
+
/**
|
|
43652
|
+
* Wall-clock time (Unix ms) currently on screen, or `null` when the active
|
|
43653
|
+
* route cannot produce one (HTTP-FLV, WHEP — see {@link ViewTransport}).
|
|
43654
|
+
*
|
|
43655
|
+
* This is what {@link MebiusClient.createCaptions} compares against a
|
|
43656
|
+
* segment's `epochMs` to know when it is due. Delegating to the transport
|
|
43657
|
+
* rather than reading the element directly is what keeps this correct across
|
|
43658
|
+
* a route failover: the player may switch from HLS to FLV mid-session, and
|
|
43659
|
+
* the clock source has to follow.
|
|
43660
|
+
*/
|
|
43661
|
+
currentEpochMs() {
|
|
43662
|
+
return this.transport?.playheadEpochMs?.() ?? null;
|
|
43663
|
+
}
|
|
43540
43664
|
attach(transport) {
|
|
43541
43665
|
transport.onEnded(() => {
|
|
43542
43666
|
if (this.transport !== transport) return;
|
|
@@ -43631,6 +43755,16 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43631
43755
|
scalePlaylistUrl(streamId) {
|
|
43632
43756
|
return this.withToken(`${this.base()}/live/${encodeURIComponent(streamId)}/index.m3u8`);
|
|
43633
43757
|
}
|
|
43758
|
+
/**
|
|
43759
|
+
* Realtime captions SSE URL. Same play token as media — the engine's
|
|
43760
|
+
* `PlayVerifier` gates both, so a viewer who can watch the stream can already
|
|
43761
|
+
* read its captions with zero extra credential.
|
|
43762
|
+
*/
|
|
43763
|
+
captionsUrl(streamId, lang) {
|
|
43764
|
+
return this.withToken(
|
|
43765
|
+
`${this.base()}/live/${encodeURIComponent(streamId)}/captions?lang=${encodeURIComponent(lang)}`
|
|
43766
|
+
);
|
|
43767
|
+
}
|
|
43634
43768
|
// Maps a neutral session kind to the concrete signaling path segment. This
|
|
43635
43769
|
// mapping (publish -> WHIP, view -> WHEP) lives ONLY in this method body, so
|
|
43636
43770
|
// the protocol names never appear in any exported type signature.
|
|
@@ -43754,6 +43888,22 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43754
43888
|
this.assertConnected();
|
|
43755
43889
|
return new MebiusPlayer(this.signaling, { mode: "low-latency" }, this.deliveries, this.telemetry, this.userId);
|
|
43756
43890
|
}
|
|
43891
|
+
/**
|
|
43892
|
+
* Subscribe to a stream's realtime captions.
|
|
43893
|
+
*
|
|
43894
|
+
* Reads the same feed a session already produces — it does NOT start the
|
|
43895
|
+
* caption session itself. `captions/start` spends money and requires an API
|
|
43896
|
+
* key, so it belongs to your own backend (see
|
|
43897
|
+
* mebius-stream-engine/docs/API.md §5.1), called once when you want captions
|
|
43898
|
+
* on for a stream. This only ever consumes what that call turned on.
|
|
43899
|
+
*
|
|
43900
|
+
* `player` must be the one showing `streamId`: captions are timed against its
|
|
43901
|
+
* playhead, and a mismatched player would compare against the wrong clock.
|
|
43902
|
+
*/
|
|
43903
|
+
createCaptions(player, options) {
|
|
43904
|
+
this.assertConnected();
|
|
43905
|
+
return new MebiusCaptions(this.signaling, player, options);
|
|
43906
|
+
}
|
|
43757
43907
|
/** Close the connection and release resources. */
|
|
43758
43908
|
disconnect(reason) {
|
|
43759
43909
|
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|