@mebius-io/web 0.5.4 → 0.6.1
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 +67 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.global.js +67 -9
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +67 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -149,6 +149,13 @@ interface CaptionSegment {
|
|
|
149
149
|
durationMs: number;
|
|
150
150
|
/** Original transcript, in the source language. */
|
|
151
151
|
text: string;
|
|
152
|
+
/**
|
|
153
|
+
* Language `text` is actually in, as the recogniser identified it. With an
|
|
154
|
+
* "auto" source this varies per sentence, so a client showing `text` as a
|
|
155
|
+
* subtitle must check this rather than assume it matches what the viewer
|
|
156
|
+
* asked for.
|
|
157
|
+
*/
|
|
158
|
+
srcLang?: string;
|
|
152
159
|
/** The requested {@link CaptionsOptions.lang} translation, if produced yet. */
|
|
153
160
|
translation?: string;
|
|
154
161
|
/** Always `true`. Render a machine-generated indicator — never as a direct quote. */
|
|
@@ -417,13 +424,37 @@ declare class MebiusCaptions extends TypedEmitter<CaptionsEventMap> {
|
|
|
417
424
|
private es;
|
|
418
425
|
private timer;
|
|
419
426
|
private readonly pending;
|
|
427
|
+
/**
|
|
428
|
+
* segmentId -> the rev already handed to listeners. Not a Set: with interim
|
|
429
|
+
* revisions on, a segment stays pending while it is being corrected, and a
|
|
430
|
+
* plain "have I shown this id" check re-emits the same unchanged text on
|
|
431
|
+
* every 100ms tick. Comparing revs emits exactly once per actual revision.
|
|
432
|
+
*/
|
|
420
433
|
private readonly shown;
|
|
434
|
+
/** Kept so the feed can be reopened after a hidden-tab suspend. */
|
|
435
|
+
private streamId;
|
|
436
|
+
private hiddenTimer;
|
|
437
|
+
private onVisibility;
|
|
421
438
|
/** @internal */
|
|
422
439
|
constructor(signaling: SignalingClient, player: MebiusPlayer, opts: CaptionsOptions);
|
|
423
440
|
/** Open the SSE connection and begin emitting segments for `streamId`. */
|
|
424
441
|
start(streamId: string): void;
|
|
425
442
|
/** Close the connection and drop all buffered segments. */
|
|
426
443
|
stop(): void;
|
|
444
|
+
private openFeed;
|
|
445
|
+
private closeFeed;
|
|
446
|
+
/**
|
|
447
|
+
* Drop the feed while the page is hidden, restore it when it comes back.
|
|
448
|
+
*
|
|
449
|
+
* Nothing here is about rendering — a hidden tab shows no captions either
|
|
450
|
+
* way. It is about not holding a subscriber open, since that subscriber is
|
|
451
|
+
* what keeps a billed session alive on the engine.
|
|
452
|
+
*
|
|
453
|
+
* Buffered segments are deliberately kept across a suspend: they age out on
|
|
454
|
+
* their own (STALE_MS) and clearing them would blank the screen on return
|
|
455
|
+
* for no benefit.
|
|
456
|
+
*/
|
|
457
|
+
private watchVisibility;
|
|
427
458
|
private onFrame;
|
|
428
459
|
private tick;
|
|
429
460
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -149,6 +149,13 @@ interface CaptionSegment {
|
|
|
149
149
|
durationMs: number;
|
|
150
150
|
/** Original transcript, in the source language. */
|
|
151
151
|
text: string;
|
|
152
|
+
/**
|
|
153
|
+
* Language `text` is actually in, as the recogniser identified it. With an
|
|
154
|
+
* "auto" source this varies per sentence, so a client showing `text` as a
|
|
155
|
+
* subtitle must check this rather than assume it matches what the viewer
|
|
156
|
+
* asked for.
|
|
157
|
+
*/
|
|
158
|
+
srcLang?: string;
|
|
152
159
|
/** The requested {@link CaptionsOptions.lang} translation, if produced yet. */
|
|
153
160
|
translation?: string;
|
|
154
161
|
/** Always `true`. Render a machine-generated indicator — never as a direct quote. */
|
|
@@ -417,13 +424,37 @@ declare class MebiusCaptions extends TypedEmitter<CaptionsEventMap> {
|
|
|
417
424
|
private es;
|
|
418
425
|
private timer;
|
|
419
426
|
private readonly pending;
|
|
427
|
+
/**
|
|
428
|
+
* segmentId -> the rev already handed to listeners. Not a Set: with interim
|
|
429
|
+
* revisions on, a segment stays pending while it is being corrected, and a
|
|
430
|
+
* plain "have I shown this id" check re-emits the same unchanged text on
|
|
431
|
+
* every 100ms tick. Comparing revs emits exactly once per actual revision.
|
|
432
|
+
*/
|
|
420
433
|
private readonly shown;
|
|
434
|
+
/** Kept so the feed can be reopened after a hidden-tab suspend. */
|
|
435
|
+
private streamId;
|
|
436
|
+
private hiddenTimer;
|
|
437
|
+
private onVisibility;
|
|
421
438
|
/** @internal */
|
|
422
439
|
constructor(signaling: SignalingClient, player: MebiusPlayer, opts: CaptionsOptions);
|
|
423
440
|
/** Open the SSE connection and begin emitting segments for `streamId`. */
|
|
424
441
|
start(streamId: string): void;
|
|
425
442
|
/** Close the connection and drop all buffered segments. */
|
|
426
443
|
stop(): void;
|
|
444
|
+
private openFeed;
|
|
445
|
+
private closeFeed;
|
|
446
|
+
/**
|
|
447
|
+
* Drop the feed while the page is hidden, restore it when it comes back.
|
|
448
|
+
*
|
|
449
|
+
* Nothing here is about rendering — a hidden tab shows no captions either
|
|
450
|
+
* way. It is about not holding a subscriber open, since that subscriber is
|
|
451
|
+
* what keeps a billed session alive on the engine.
|
|
452
|
+
*
|
|
453
|
+
* Buffered segments are deliberately kept across a suspend: they age out on
|
|
454
|
+
* their own (STALE_MS) and clearing them would blank the screen on return
|
|
455
|
+
* for no benefit.
|
|
456
|
+
*/
|
|
457
|
+
private watchVisibility;
|
|
427
458
|
private onFrame;
|
|
428
459
|
private tick;
|
|
429
460
|
}
|
package/dist/index.global.js
CHANGED
|
@@ -43428,6 +43428,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43428
43428
|
var TICK_MS = 100;
|
|
43429
43429
|
var STALE_MS = 5e3;
|
|
43430
43430
|
var MAX_QUEUE_MS = 1500;
|
|
43431
|
+
var HIDDEN_GRACE_MS = 6e4;
|
|
43431
43432
|
var MebiusCaptions = class extends TypedEmitter {
|
|
43432
43433
|
/** @internal */
|
|
43433
43434
|
constructor(signaling, player, opts) {
|
|
@@ -43438,26 +43439,81 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43438
43439
|
this.es = null;
|
|
43439
43440
|
this.timer = null;
|
|
43440
43441
|
this.pending = /* @__PURE__ */ new Map();
|
|
43441
|
-
|
|
43442
|
+
/**
|
|
43443
|
+
* segmentId -> the rev already handed to listeners. Not a Set: with interim
|
|
43444
|
+
* revisions on, a segment stays pending while it is being corrected, and a
|
|
43445
|
+
* plain "have I shown this id" check re-emits the same unchanged text on
|
|
43446
|
+
* every 100ms tick. Comparing revs emits exactly once per actual revision.
|
|
43447
|
+
*/
|
|
43448
|
+
this.shown = /* @__PURE__ */ new Map();
|
|
43449
|
+
/** Kept so the feed can be reopened after a hidden-tab suspend. */
|
|
43450
|
+
this.streamId = null;
|
|
43451
|
+
this.hiddenTimer = null;
|
|
43452
|
+
this.onVisibility = null;
|
|
43442
43453
|
}
|
|
43443
43454
|
/** Open the SSE connection and begin emitting segments for `streamId`. */
|
|
43444
43455
|
start(streamId) {
|
|
43445
43456
|
if (this.es) return;
|
|
43446
|
-
|
|
43447
|
-
|
|
43457
|
+
this.streamId = streamId;
|
|
43458
|
+
this.openFeed();
|
|
43459
|
+
this.watchVisibility();
|
|
43460
|
+
}
|
|
43461
|
+
/** Close the connection and drop all buffered segments. */
|
|
43462
|
+
stop() {
|
|
43463
|
+
this.streamId = null;
|
|
43464
|
+
if (this.onVisibility && typeof document !== "undefined") {
|
|
43465
|
+
document.removeEventListener("visibilitychange", this.onVisibility);
|
|
43466
|
+
}
|
|
43467
|
+
this.onVisibility = null;
|
|
43468
|
+
if (this.hiddenTimer) clearTimeout(this.hiddenTimer);
|
|
43469
|
+
this.hiddenTimer = null;
|
|
43470
|
+
this.closeFeed();
|
|
43471
|
+
this.pending.clear();
|
|
43472
|
+
this.shown.clear();
|
|
43473
|
+
}
|
|
43474
|
+
openFeed() {
|
|
43475
|
+
if (this.es || !this.streamId) return;
|
|
43476
|
+
const es = new EventSource(this.signaling.captionsUrl(this.streamId, this.opts.lang));
|
|
43448
43477
|
es.onmessage = (ev) => this.onFrame(ev);
|
|
43449
43478
|
es.onerror = () => this.emit("error", void 0);
|
|
43450
43479
|
this.es = es;
|
|
43451
43480
|
this.timer = setInterval(() => this.tick(), TICK_MS);
|
|
43452
43481
|
}
|
|
43453
|
-
|
|
43454
|
-
stop() {
|
|
43482
|
+
closeFeed() {
|
|
43455
43483
|
this.es?.close();
|
|
43456
43484
|
this.es = null;
|
|
43457
43485
|
if (this.timer) clearInterval(this.timer);
|
|
43458
43486
|
this.timer = null;
|
|
43459
|
-
|
|
43460
|
-
|
|
43487
|
+
}
|
|
43488
|
+
/**
|
|
43489
|
+
* Drop the feed while the page is hidden, restore it when it comes back.
|
|
43490
|
+
*
|
|
43491
|
+
* Nothing here is about rendering — a hidden tab shows no captions either
|
|
43492
|
+
* way. It is about not holding a subscriber open, since that subscriber is
|
|
43493
|
+
* what keeps a billed session alive on the engine.
|
|
43494
|
+
*
|
|
43495
|
+
* Buffered segments are deliberately kept across a suspend: they age out on
|
|
43496
|
+
* their own (STALE_MS) and clearing them would blank the screen on return
|
|
43497
|
+
* for no benefit.
|
|
43498
|
+
*/
|
|
43499
|
+
watchVisibility() {
|
|
43500
|
+
if (typeof document === "undefined") return;
|
|
43501
|
+
this.onVisibility = () => {
|
|
43502
|
+
if (document.visibilityState === "hidden") {
|
|
43503
|
+
if (this.hiddenTimer) return;
|
|
43504
|
+
this.hiddenTimer = setTimeout(() => {
|
|
43505
|
+
this.hiddenTimer = null;
|
|
43506
|
+
this.closeFeed();
|
|
43507
|
+
}, HIDDEN_GRACE_MS);
|
|
43508
|
+
return;
|
|
43509
|
+
}
|
|
43510
|
+
if (this.hiddenTimer) {
|
|
43511
|
+
clearTimeout(this.hiddenTimer);
|
|
43512
|
+
this.hiddenTimer = null;
|
|
43513
|
+
}
|
|
43514
|
+
this.openFeed();
|
|
43515
|
+
};
|
|
43516
|
+
document.addEventListener("visibilitychange", this.onVisibility);
|
|
43461
43517
|
}
|
|
43462
43518
|
onFrame(ev) {
|
|
43463
43519
|
let frame;
|
|
@@ -43487,8 +43543,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43487
43543
|
this.emit("cleared", { segmentId: id });
|
|
43488
43544
|
continue;
|
|
43489
43545
|
}
|
|
43490
|
-
|
|
43491
|
-
this.shown.
|
|
43546
|
+
const rev = frame.rev ?? 0;
|
|
43547
|
+
if (this.shown.get(id) === rev) continue;
|
|
43548
|
+
this.shown.set(id, rev);
|
|
43492
43549
|
this.emit("segment", toSegment(id, frame, this.opts.lang));
|
|
43493
43550
|
}
|
|
43494
43551
|
}
|
|
@@ -43501,6 +43558,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
|
|
|
43501
43558
|
epochMs: frame.epochMs ?? 0,
|
|
43502
43559
|
durationMs: frame.durationMs ?? 0,
|
|
43503
43560
|
text: frame.text ?? "",
|
|
43561
|
+
srcLang: frame.srcLang,
|
|
43504
43562
|
translation: frame.translations?.[lang],
|
|
43505
43563
|
machineGenerated: true
|
|
43506
43564
|
};
|