@mebius-io/web 0.5.3 → 0.6.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.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,6 +424,12 @@ 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;
421
434
  /** @internal */
422
435
  constructor(signaling: SignalingClient, player: MebiusPlayer, opts: CaptionsOptions);
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,6 +424,12 @@ 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;
421
434
  /** @internal */
422
435
  constructor(signaling: SignalingClient, player: MebiusPlayer, opts: CaptionsOptions);
@@ -43047,7 +43047,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
43047
43047
  var MAX_DRIFT_S = 2;
43048
43048
  var EDGE_MARGIN_S = 0.4;
43049
43049
  var AUDIO_RETRY_MS = 2500;
43050
- var ESTIMATED_LATENCY_MS = 3e3;
43050
+ var UPSTREAM_LATENCY_MS = 800;
43051
43051
  function stalledWithData(video, ms) {
43052
43052
  if (video.currentTime > 0) return Promise.resolve(false);
43053
43053
  return new Promise((resolve) => {
@@ -43177,13 +43177,24 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
43177
43177
  return { bitrateKbps, framesPerSecond, latencyMs: void 0 };
43178
43178
  }
43179
43179
  /**
43180
- * Estimate only see {@link ESTIMATED_LATENCY_MS}. Without this, captions
43181
- * never render on the FLV route at all: {@link MebiusCaptions} withholds
43182
- * every segment until the playhead reaches its `epochMs`, and a transport
43183
- * returning `null` here means the playhead comparison never runs.
43180
+ * Estimate — FLV carries no wall clock, so this is measured where possible
43181
+ * and assumed where not (see {@link UPSTREAM_LATENCY_MS}).
43182
+ *
43183
+ * The measurable half is the decoded media queued ahead of the playhead:
43184
+ * whatever is buffered but not yet shown is, by definition, how far behind
43185
+ * the newest received frame this viewer is watching. A flat guess for the
43186
+ * whole delay was previously used, and being too generous costs real time —
43187
+ * every millisecond of over-estimate is a caption withheld for no reason,
43188
+ * on top of the STT latency the viewer already pays.
43184
43189
  */
43185
43190
  playheadEpochMs() {
43186
- return Date.now() - ESTIMATED_LATENCY_MS;
43191
+ let bufferedAheadMs = 0;
43192
+ const ranges = this.video?.buffered;
43193
+ if (ranges && ranges.length > 0 && this.video) {
43194
+ const ahead = ranges.end(ranges.length - 1) - this.video.currentTime;
43195
+ if (Number.isFinite(ahead) && ahead > 0) bufferedAheadMs = ahead * 1e3;
43196
+ }
43197
+ return Date.now() - UPSTREAM_LATENCY_MS - bufferedAheadMs;
43187
43198
  }
43188
43199
  };
43189
43200
 
@@ -43416,7 +43427,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
43416
43427
  // src/captions.ts
43417
43428
  var TICK_MS = 100;
43418
43429
  var STALE_MS = 5e3;
43419
- var MAX_QUEUE_MS = 4e3;
43430
+ var MAX_QUEUE_MS = 1500;
43420
43431
  var MebiusCaptions = class extends TypedEmitter {
43421
43432
  /** @internal */
43422
43433
  constructor(signaling, player, opts) {
@@ -43427,7 +43438,13 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
43427
43438
  this.es = null;
43428
43439
  this.timer = null;
43429
43440
  this.pending = /* @__PURE__ */ new Map();
43430
- this.shown = /* @__PURE__ */ new Set();
43441
+ /**
43442
+ * segmentId -> the rev already handed to listeners. Not a Set: with interim
43443
+ * revisions on, a segment stays pending while it is being corrected, and a
43444
+ * plain "have I shown this id" check re-emits the same unchanged text on
43445
+ * every 100ms tick. Comparing revs emits exactly once per actual revision.
43446
+ */
43447
+ this.shown = /* @__PURE__ */ new Map();
43431
43448
  }
43432
43449
  /** Open the SSE connection and begin emitting segments for `streamId`. */
43433
43450
  start(streamId) {
@@ -43476,8 +43493,9 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
43476
43493
  this.emit("cleared", { segmentId: id });
43477
43494
  continue;
43478
43495
  }
43479
- if (this.shown.has(id) && frame.state === "final") continue;
43480
- this.shown.add(id);
43496
+ const rev = frame.rev ?? 0;
43497
+ if (this.shown.get(id) === rev) continue;
43498
+ this.shown.set(id, rev);
43481
43499
  this.emit("segment", toSegment(id, frame, this.opts.lang));
43482
43500
  }
43483
43501
  }
@@ -43490,6 +43508,7 @@ Schedule: ${scheduleItems.map((seg) => segmentToString(seg))} pos: ${this.timeli
43490
43508
  epochMs: frame.epochMs ?? 0,
43491
43509
  durationMs: frame.durationMs ?? 0,
43492
43510
  text: frame.text ?? "",
43511
+ srcLang: frame.srcLang,
43493
43512
  translation: frame.translations?.[lang],
43494
43513
  machineGenerated: true
43495
43514
  };