@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.js CHANGED
@@ -810,6 +810,7 @@ function normalize(c, fallback) {
810
810
  var TICK_MS = 100;
811
811
  var STALE_MS = 5e3;
812
812
  var MAX_QUEUE_MS = 1500;
813
+ var HIDDEN_GRACE_MS = 6e4;
813
814
  var MebiusCaptions = class extends TypedEmitter {
814
815
  /** @internal */
815
816
  constructor(signaling, player, opts) {
@@ -820,26 +821,81 @@ var MebiusCaptions = class extends TypedEmitter {
820
821
  this.es = null;
821
822
  this.timer = null;
822
823
  this.pending = /* @__PURE__ */ new Map();
823
- this.shown = /* @__PURE__ */ new Set();
824
+ /**
825
+ * segmentId -> the rev already handed to listeners. Not a Set: with interim
826
+ * revisions on, a segment stays pending while it is being corrected, and a
827
+ * plain "have I shown this id" check re-emits the same unchanged text on
828
+ * every 100ms tick. Comparing revs emits exactly once per actual revision.
829
+ */
830
+ this.shown = /* @__PURE__ */ new Map();
831
+ /** Kept so the feed can be reopened after a hidden-tab suspend. */
832
+ this.streamId = null;
833
+ this.hiddenTimer = null;
834
+ this.onVisibility = null;
824
835
  }
825
836
  /** Open the SSE connection and begin emitting segments for `streamId`. */
826
837
  start(streamId) {
827
838
  if (this.es) return;
828
- const url = this.signaling.captionsUrl(streamId, this.opts.lang);
829
- const es = new EventSource(url);
839
+ this.streamId = streamId;
840
+ this.openFeed();
841
+ this.watchVisibility();
842
+ }
843
+ /** Close the connection and drop all buffered segments. */
844
+ stop() {
845
+ this.streamId = null;
846
+ if (this.onVisibility && typeof document !== "undefined") {
847
+ document.removeEventListener("visibilitychange", this.onVisibility);
848
+ }
849
+ this.onVisibility = null;
850
+ if (this.hiddenTimer) clearTimeout(this.hiddenTimer);
851
+ this.hiddenTimer = null;
852
+ this.closeFeed();
853
+ this.pending.clear();
854
+ this.shown.clear();
855
+ }
856
+ openFeed() {
857
+ if (this.es || !this.streamId) return;
858
+ const es = new EventSource(this.signaling.captionsUrl(this.streamId, this.opts.lang));
830
859
  es.onmessage = (ev) => this.onFrame(ev);
831
860
  es.onerror = () => this.emit("error", void 0);
832
861
  this.es = es;
833
862
  this.timer = setInterval(() => this.tick(), TICK_MS);
834
863
  }
835
- /** Close the connection and drop all buffered segments. */
836
- stop() {
864
+ closeFeed() {
837
865
  this.es?.close();
838
866
  this.es = null;
839
867
  if (this.timer) clearInterval(this.timer);
840
868
  this.timer = null;
841
- this.pending.clear();
842
- this.shown.clear();
869
+ }
870
+ /**
871
+ * Drop the feed while the page is hidden, restore it when it comes back.
872
+ *
873
+ * Nothing here is about rendering — a hidden tab shows no captions either
874
+ * way. It is about not holding a subscriber open, since that subscriber is
875
+ * what keeps a billed session alive on the engine.
876
+ *
877
+ * Buffered segments are deliberately kept across a suspend: they age out on
878
+ * their own (STALE_MS) and clearing them would blank the screen on return
879
+ * for no benefit.
880
+ */
881
+ watchVisibility() {
882
+ if (typeof document === "undefined") return;
883
+ this.onVisibility = () => {
884
+ if (document.visibilityState === "hidden") {
885
+ if (this.hiddenTimer) return;
886
+ this.hiddenTimer = setTimeout(() => {
887
+ this.hiddenTimer = null;
888
+ this.closeFeed();
889
+ }, HIDDEN_GRACE_MS);
890
+ return;
891
+ }
892
+ if (this.hiddenTimer) {
893
+ clearTimeout(this.hiddenTimer);
894
+ this.hiddenTimer = null;
895
+ }
896
+ this.openFeed();
897
+ };
898
+ document.addEventListener("visibilitychange", this.onVisibility);
843
899
  }
844
900
  onFrame(ev) {
845
901
  let frame;
@@ -869,8 +925,9 @@ var MebiusCaptions = class extends TypedEmitter {
869
925
  this.emit("cleared", { segmentId: id });
870
926
  continue;
871
927
  }
872
- if (this.shown.has(id) && frame.state === "final") continue;
873
- this.shown.add(id);
928
+ const rev = frame.rev ?? 0;
929
+ if (this.shown.get(id) === rev) continue;
930
+ this.shown.set(id, rev);
874
931
  this.emit("segment", toSegment(id, frame, this.opts.lang));
875
932
  }
876
933
  }
@@ -883,6 +940,7 @@ function toSegment(segmentId, frame, lang) {
883
940
  epochMs: frame.epochMs ?? 0,
884
941
  durationMs: frame.durationMs ?? 0,
885
942
  text: frame.text ?? "",
943
+ srcLang: frame.srcLang,
886
944
  translation: frame.translations?.[lang],
887
945
  machineGenerated: true
888
946
  };