@oddin-gg/havik-player 2.0.2 → 2.0.4

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
@@ -67,7 +67,16 @@ export declare interface CreatePlayerOptions {
67
67
  baseUrl: string;
68
68
  matchUrn: string;
69
69
  credential: CredentialSource;
70
- /** Start playback once ready. Default: true. */
70
+ /**
71
+ * Start playback once ready. Default: true. With false the player resolves
72
+ * playback, then holds in a pre-play 'paused' state (poster + play card,
73
+ * no spinner) and defers media loading — segments and DRM license traffic —
74
+ * until the first play(). The hls.js engine still parses the manifest
75
+ * pre-play ('ready', track lists); the native engine defers all fetching
76
+ * and emits 'ready' at attach. A policy-blocked programmatic play()
77
+ * ('autoplayblocked') leaves the loader warming the buffer — media and DRM
78
+ * traffic flows from that kick, not from a viewer gesture.
79
+ */
71
80
  autoplay?: boolean;
72
81
  /** Mute the element (required for reliable autoplay with sound policies). Default: false. */
73
82
  muted?: boolean;
package/dist/index.d.ts CHANGED
@@ -67,7 +67,16 @@ export declare interface CreatePlayerOptions {
67
67
  baseUrl: string;
68
68
  matchUrn: string;
69
69
  credential: CredentialSource;
70
- /** Start playback once ready. Default: true. */
70
+ /**
71
+ * Start playback once ready. Default: true. With false the player resolves
72
+ * playback, then holds in a pre-play 'paused' state (poster + play card,
73
+ * no spinner) and defers media loading — segments and DRM license traffic —
74
+ * until the first play(). The hls.js engine still parses the manifest
75
+ * pre-play ('ready', track lists); the native engine defers all fetching
76
+ * and emits 'ready' at attach. A policy-blocked programmatic play()
77
+ * ('autoplayblocked') leaves the loader warming the buffer — media and DRM
78
+ * traffic flows from that kick, not from a viewer gesture.
79
+ */
71
80
  autoplay?: boolean;
72
81
  /** Mute the element (required for reliable autoplay with sound policies). Default: false. */
73
82
  muted?: boolean;
package/dist/index.js CHANGED
@@ -663,12 +663,15 @@ var z = class {
663
663
  keySession = null;
664
664
  fairplayArmed = !1;
665
665
  fairplayKeyIV;
666
+ loadStarted;
667
+ preloadTouched = !1;
668
+ savedPreload = null;
666
669
  constructor(e) {
667
- this.deps = e;
670
+ this.deps = e, this.loadStarted = e.deferLoad !== !0;
668
671
  }
669
672
  async load() {
670
673
  let { video: e, descriptor: t, emit: r } = this.deps;
671
- if (t.drmEnabled) {
674
+ if (this.loadStarted || (this.preloadTouched = !0, this.savedPreload = e.getAttribute("preload"), e.preload = "none"), t.drmEnabled) {
672
675
  if (!t.drm?.fairplay) {
673
676
  r({
674
677
  type: "error",
@@ -679,11 +682,21 @@ var z = class {
679
682
  }
680
683
  this.armFairPlay();
681
684
  }
682
- this.onLoaded = () => r({ type: "ready" }), this.onError = () => r({
685
+ let i = !this.loadStarted;
686
+ this.onLoaded = i ? void 0 : () => r({ type: "ready" }), this.onError = () => r({
683
687
  type: "error",
684
688
  error: new n("INTERNAL", 0, "native HLS playback error"),
685
689
  fatal: !0
686
- }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = Ee(t.manifestUrl, t.analytics?.sid), e.load();
690
+ }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = Ee(t.manifestUrl, t.analytics?.sid), e.load();
691
+ }
692
+ startLoad() {
693
+ this.loadStarted || (this.loadStarted = !0, this.restorePreload());
694
+ }
695
+ restorePreload() {
696
+ if (!this.preloadTouched) return;
697
+ this.preloadTouched = !1;
698
+ let { video: e } = this.deps;
699
+ this.savedPreload === null ? e.removeAttribute("preload") : e.setAttribute("preload", this.savedPreload);
687
700
  }
688
701
  armFairPlay() {
689
702
  let { video: e, descriptor: t, credential: r, deviceId: a, userId: o, emit: s } = this.deps, c = t.drm?.fairplay;
@@ -805,7 +818,7 @@ var z = class {
805
818
  try {
806
819
  e.pause();
807
820
  } catch {}
808
- e.removeAttribute("src");
821
+ e.removeAttribute("src"), this.restorePreload();
809
822
  try {
810
823
  e.load();
811
824
  } catch {}
@@ -872,8 +885,9 @@ var V = class {
872
885
  sizeCapManaged = !0;
873
886
  dprMedia = null;
874
887
  onDprChange = null;
888
+ loadStarted;
875
889
  constructor(e) {
876
- this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
890
+ this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl, this.loadStarted = e.deferLoad !== !0;
877
891
  }
878
892
  async load() {
879
893
  let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
@@ -894,6 +908,9 @@ var V = class {
894
908
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
895
909
  }, document.addEventListener("visibilitychange", this.onVisibility)), this.onSeeking = () => this.clampOverSeek(), r.addEventListener("seeking", this.onSeeking), this.sizeCapManaged && typeof ResizeObserver < "u" && (this.capObserver = new ResizeObserver(() => this.onMediaResize()), this.capObserver.observe(r), this.watchDprChanges());
896
910
  }
911
+ startLoad() {
912
+ this.loadStarted || (this.loadStarted = !0, this.hls?.startLoad(-1));
913
+ }
897
914
  watchDprChanges() {
898
915
  if (typeof matchMedia != "function") return;
899
916
  this.unwatchDprChanges();
@@ -927,6 +944,7 @@ var V = class {
927
944
  maxLiveSyncPlaybackRate: 1.5,
928
945
  abrEwmaDefaultEstimate: 1e6,
929
946
  capLevelToPlayerSize: !1,
947
+ ...this.loadStarted ? {} : { autoStartLoad: !1 },
930
948
  ...u ? {
931
949
  emeEnabled: !0,
932
950
  drmSystems: { ...this.licenseUrl ? { "com.widevine.alpha": { licenseUrl: this.licenseUrl } } : {} },
@@ -951,7 +969,7 @@ var V = class {
951
969
  } } : {},
952
970
  ...c
953
971
  };
954
- We(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
972
+ We(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
955
973
  let f = new e(d);
956
974
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
957
975
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
@@ -1422,7 +1440,7 @@ var Ye = class {
1422
1440
  keepalive: !0
1423
1441
  }).catch(() => {});
1424
1442
  }
1425
- }, Xe = "2.0.2", W = {
1443
+ }, Xe = "2.0.4", W = {
1426
1444
  accent: "#E1B600",
1427
1445
  accentText: "#1B1C23",
1428
1446
  background: "#0e0f13",
@@ -1874,6 +1892,8 @@ var dt = class {
1874
1892
  _lastError = null;
1875
1893
  analyticsSession = null;
1876
1894
  detachSeekListener;
1895
+ playRequested = !1;
1896
+ pendingStartLoad = !1;
1877
1897
  constructor(e) {
1878
1898
  this.opts = e;
1879
1899
  }
@@ -2002,18 +2022,20 @@ var dt = class {
2002
2022
  let n = await s(this.opts.credential);
2003
2023
  if (this.destroyed || t !== this.loadGen) return;
2004
2024
  this.setState("loading");
2005
- let r = this.createEngine(e, n), i = we(this.opts.video, (e) => {
2025
+ let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = we(this.opts.video, (e) => {
2006
2026
  this._state !== "ended" && this._state !== "error" && this.setState(e);
2007
2027
  });
2008
- if (await r.load(), this.destroyed || t !== this.loadGen) {
2009
- i(), r.destroy();
2028
+ if (await i.load(), this.destroyed || t !== this.loadGen) {
2029
+ a(), i.destroy();
2010
2030
  return;
2011
2031
  }
2012
2032
  if (this._state === "error") {
2013
- i(), r.destroy();
2033
+ a(), i.destroy();
2014
2034
  return;
2015
2035
  }
2016
- this.engine = r, this.detachMedia = i, this.opts.maxBitrate != null && r.setMaxBitrate(this.opts.maxBitrate), this.startStats(), this.scheduleLicenseRefresh(), this.startLiveState(t), this.startAnalytics(e, r, n.apiKey);
2036
+ this.engine = i, this.detachMedia = a, this.opts.maxBitrate != null && i.setMaxBitrate(this.opts.maxBitrate), this.startStats(), this.scheduleLicenseRefresh(), this.startLiveState(t), this.startAnalytics(e, i, n.apiKey), r && (this.pendingStartLoad = !0, this.setState("paused"), i.name === "native" && setTimeout(() => {
2037
+ this.destroyed || t !== this.loadGen || this.engine !== i || (this.analyticsSession?.noteReady(), this.emit("ready"));
2038
+ }, 0));
2017
2039
  }
2018
2040
  startAnalytics(e, t, n) {
2019
2041
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
@@ -2041,10 +2063,10 @@ var dt = class {
2041
2063
  let s = () => o.noteSeek();
2042
2064
  a.addEventListener("seeking", s), this.detachSeekListener = () => a.removeEventListener("seeking", s), o.start();
2043
2065
  }
2044
- createEngine(e, t) {
2045
- let r = { ...this.opts.hlsConfig };
2046
- this.opts.startLevel != null && (r.startLevel = this.opts.startLevel), this.opts.liveLatencyTarget != null && (r.liveSyncDuration = this.opts.liveLatencyTarget);
2047
- let i = {
2066
+ createEngine(e, t, r = !1) {
2067
+ let i = { ...this.opts.hlsConfig };
2068
+ this.opts.startLevel != null && (i.startLevel = this.opts.startLevel), this.opts.liveLatencyTarget != null && (i.liveSyncDuration = this.opts.liveLatencyTarget);
2069
+ let a = {
2048
2070
  video: this.opts.video,
2049
2071
  descriptor: e,
2050
2072
  credential: t,
@@ -2053,20 +2075,21 @@ var dt = class {
2053
2075
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
2054
2076
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
2055
2077
  debug: this.opts.debug,
2056
- hlsConfig: r,
2078
+ deferLoad: r,
2079
+ hlsConfig: i,
2057
2080
  emit: (e) => this.onEngineEvent(e)
2058
- }, a = this.opts.engine ?? "auto";
2059
- if (a === "native") return new z(i);
2060
- if (a === "hls") return new V(i);
2061
- if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && L(this.opts.video) && R()) return new z(i);
2062
- if (De()) return new V(i);
2063
- if (L(this.opts.video)) return new z(i);
2081
+ }, o = this.opts.engine ?? "auto";
2082
+ if (o === "native") return new z(a);
2083
+ if (o === "hls") return new V(a);
2084
+ if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && L(this.opts.video) && R()) return new z(a);
2085
+ if (De()) return new V(a);
2086
+ if (L(this.opts.video)) return new z(a);
2064
2087
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
2065
2088
  }
2066
2089
  onEngineEvent(e) {
2067
2090
  if (!this.destroyed) switch (e.type) {
2068
2091
  case "ready":
2069
- this.analyticsSession?.noteReady(), this.emit("ready"), this.opts.autoplay !== !1 && this.play();
2092
+ this.analyticsSession?.noteReady(), this.emit("ready"), (this.opts.autoplay !== !1 || this.playRequested) && this.play();
2070
2093
  break;
2071
2094
  case "lowLatencyDetected":
2072
2095
  this._descriptor && (this._descriptor.isLowLatency = e.value);
@@ -2116,7 +2139,7 @@ var dt = class {
2116
2139
  t === "match_ended" && this._endedReason !== "match_ended" && (this._endedReason = t, this.emit("ended", t));
2117
2140
  return;
2118
2141
  }
2119
- this.endedHandled = !0, this._endedReason = t ?? null, this.stopRefresh(), this.engine?.stop(), this.setState("ended");
2142
+ this.endedHandled = !0, this._endedReason = t ?? null, this.stopRefresh(), this.pendingStartLoad = !1, this.engine?.stop(), this.setState("ended");
2120
2143
  }
2121
2144
  }
2122
2145
  rejoinEnabled() {
@@ -2135,7 +2158,7 @@ var dt = class {
2135
2158
  this.teardownPlayback(), this.setState("waiting"), this.beginArming(e, void 0, !0);
2136
2159
  }
2137
2160
  teardownPlayback() {
2138
- this.stopStats(), this.stopRefresh(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null;
2161
+ this.stopStats(), this.stopRefresh(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null, this.pendingStartLoad = !1;
2139
2162
  }
2140
2163
  mountControls() {
2141
2164
  let e = this.opts.controls ?? "custom", t = this.opts.video;
@@ -2206,10 +2229,15 @@ var dt = class {
2206
2229
  }
2207
2230
  }
2208
2231
  async play() {
2209
- if (!this.destroyed) try {
2232
+ if (this.destroyed) return;
2233
+ let e = !this.playRequested;
2234
+ this.playRequested = !0;
2235
+ let t = this.pendingStartLoad && this._state !== "ended" && this._state !== "error";
2236
+ t && (this.pendingStartLoad = !1, this.setState("loading"), this.engine?.startLoad());
2237
+ try {
2210
2238
  await this.opts.video.play();
2211
- } catch (e) {
2212
- e instanceof DOMException && e.name === "NotAllowedError" && this.emit("autoplayblocked");
2239
+ } catch (n) {
2240
+ n instanceof DOMException && n.name === "NotAllowedError" && (e && (this.playRequested = !1), t && !this.destroyed && this._state === "loading" && this.setState("paused"), this.emit("autoplayblocked"));
2213
2241
  }
2214
2242
  }
2215
2243
  pause() {