@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 CHANGED
@@ -852,6 +852,7 @@ function normalize(c, fallback) {
852
852
  var TICK_MS = 100;
853
853
  var STALE_MS = 5e3;
854
854
  var MAX_QUEUE_MS = 1500;
855
+ var HIDDEN_GRACE_MS = 6e4;
855
856
  var MebiusCaptions = class extends TypedEmitter {
856
857
  /** @internal */
857
858
  constructor(signaling, player, opts) {
@@ -862,26 +863,81 @@ var MebiusCaptions = class extends TypedEmitter {
862
863
  this.es = null;
863
864
  this.timer = null;
864
865
  this.pending = /* @__PURE__ */ new Map();
865
- this.shown = /* @__PURE__ */ new Set();
866
+ /**
867
+ * segmentId -> the rev already handed to listeners. Not a Set: with interim
868
+ * revisions on, a segment stays pending while it is being corrected, and a
869
+ * plain "have I shown this id" check re-emits the same unchanged text on
870
+ * every 100ms tick. Comparing revs emits exactly once per actual revision.
871
+ */
872
+ this.shown = /* @__PURE__ */ new Map();
873
+ /** Kept so the feed can be reopened after a hidden-tab suspend. */
874
+ this.streamId = null;
875
+ this.hiddenTimer = null;
876
+ this.onVisibility = null;
866
877
  }
867
878
  /** Open the SSE connection and begin emitting segments for `streamId`. */
868
879
  start(streamId) {
869
880
  if (this.es) return;
870
- const url = this.signaling.captionsUrl(streamId, this.opts.lang);
871
- const es = new EventSource(url);
881
+ this.streamId = streamId;
882
+ this.openFeed();
883
+ this.watchVisibility();
884
+ }
885
+ /** Close the connection and drop all buffered segments. */
886
+ stop() {
887
+ this.streamId = null;
888
+ if (this.onVisibility && typeof document !== "undefined") {
889
+ document.removeEventListener("visibilitychange", this.onVisibility);
890
+ }
891
+ this.onVisibility = null;
892
+ if (this.hiddenTimer) clearTimeout(this.hiddenTimer);
893
+ this.hiddenTimer = null;
894
+ this.closeFeed();
895
+ this.pending.clear();
896
+ this.shown.clear();
897
+ }
898
+ openFeed() {
899
+ if (this.es || !this.streamId) return;
900
+ const es = new EventSource(this.signaling.captionsUrl(this.streamId, this.opts.lang));
872
901
  es.onmessage = (ev) => this.onFrame(ev);
873
902
  es.onerror = () => this.emit("error", void 0);
874
903
  this.es = es;
875
904
  this.timer = setInterval(() => this.tick(), TICK_MS);
876
905
  }
877
- /** Close the connection and drop all buffered segments. */
878
- stop() {
906
+ closeFeed() {
879
907
  this.es?.close();
880
908
  this.es = null;
881
909
  if (this.timer) clearInterval(this.timer);
882
910
  this.timer = null;
883
- this.pending.clear();
884
- this.shown.clear();
911
+ }
912
+ /**
913
+ * Drop the feed while the page is hidden, restore it when it comes back.
914
+ *
915
+ * Nothing here is about rendering — a hidden tab shows no captions either
916
+ * way. It is about not holding a subscriber open, since that subscriber is
917
+ * what keeps a billed session alive on the engine.
918
+ *
919
+ * Buffered segments are deliberately kept across a suspend: they age out on
920
+ * their own (STALE_MS) and clearing them would blank the screen on return
921
+ * for no benefit.
922
+ */
923
+ watchVisibility() {
924
+ if (typeof document === "undefined") return;
925
+ this.onVisibility = () => {
926
+ if (document.visibilityState === "hidden") {
927
+ if (this.hiddenTimer) return;
928
+ this.hiddenTimer = setTimeout(() => {
929
+ this.hiddenTimer = null;
930
+ this.closeFeed();
931
+ }, HIDDEN_GRACE_MS);
932
+ return;
933
+ }
934
+ if (this.hiddenTimer) {
935
+ clearTimeout(this.hiddenTimer);
936
+ this.hiddenTimer = null;
937
+ }
938
+ this.openFeed();
939
+ };
940
+ document.addEventListener("visibilitychange", this.onVisibility);
885
941
  }
886
942
  onFrame(ev) {
887
943
  let frame;
@@ -911,8 +967,9 @@ var MebiusCaptions = class extends TypedEmitter {
911
967
  this.emit("cleared", { segmentId: id });
912
968
  continue;
913
969
  }
914
- if (this.shown.has(id) && frame.state === "final") continue;
915
- this.shown.add(id);
970
+ const rev = frame.rev ?? 0;
971
+ if (this.shown.get(id) === rev) continue;
972
+ this.shown.set(id, rev);
916
973
  this.emit("segment", toSegment(id, frame, this.opts.lang));
917
974
  }
918
975
  }
@@ -925,6 +982,7 @@ function toSegment(segmentId, frame, lang) {
925
982
  epochMs: frame.epochMs ?? 0,
926
983
  durationMs: frame.durationMs ?? 0,
927
984
  text: frame.text ?? "",
985
+ srcLang: frame.srcLang,
928
986
  translation: frame.translations?.[lang],
929
987
  machineGenerated: true
930
988
  };