@mebius-io/web 0.6.0 → 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) {
@@ -869,25 +870,74 @@ var MebiusCaptions = class extends TypedEmitter {
869
870
  * every 100ms tick. Comparing revs emits exactly once per actual revision.
870
871
  */
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;
872
877
  }
873
878
  /** Open the SSE connection and begin emitting segments for `streamId`. */
874
879
  start(streamId) {
875
880
  if (this.es) return;
876
- const url = this.signaling.captionsUrl(streamId, this.opts.lang);
877
- 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));
878
901
  es.onmessage = (ev) => this.onFrame(ev);
879
902
  es.onerror = () => this.emit("error", void 0);
880
903
  this.es = es;
881
904
  this.timer = setInterval(() => this.tick(), TICK_MS);
882
905
  }
883
- /** Close the connection and drop all buffered segments. */
884
- stop() {
906
+ closeFeed() {
885
907
  this.es?.close();
886
908
  this.es = null;
887
909
  if (this.timer) clearInterval(this.timer);
888
910
  this.timer = null;
889
- this.pending.clear();
890
- 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);
891
941
  }
892
942
  onFrame(ev) {
893
943
  let frame;