@oddin-gg/havik-player 2.0.3 → 2.1.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.js CHANGED
@@ -616,11 +616,15 @@ function Ce(e) {
616
616
  }
617
617
  //#endregion
618
618
  //#region src/core/stats.ts
619
- var F = { droppedFrames: 0 };
620
- function I(e) {
621
- return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
619
+ var we = { droppedFrames: 0 };
620
+ function F(e) {
621
+ let t = e.getVideoPlaybackQuality?.();
622
+ return {
623
+ dropped: t?.droppedVideoFrames ?? 0,
624
+ decoded: t?.totalVideoFrames ?? 0
625
+ };
622
626
  }
623
- function we(e, t) {
627
+ function Te(e, t) {
624
628
  let n = [
625
629
  ["playing", () => t("playing")],
626
630
  ["waiting", () => t("buffering")],
@@ -637,7 +641,7 @@ function we(e, t) {
637
641
  }
638
642
  //#endregion
639
643
  //#region src/core/engine/native.ts
640
- function Te(e) {
644
+ function Ee(e) {
641
645
  try {
642
646
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
643
647
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -645,15 +649,15 @@ function Te(e) {
645
649
  return;
646
650
  }
647
651
  }
648
- function L(e) {
652
+ function I(e) {
649
653
  return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
650
654
  }
651
- function R() {
655
+ function L() {
652
656
  if (typeof window > "u") return !1;
653
657
  let e = "WebKitMediaKeys" in window || typeof HTMLMediaElement < "u" && "webkitSetMediaKeys" in HTMLMediaElement.prototype, t = typeof navigator < "u" && /Apple/i.test(navigator.vendor ?? "");
654
658
  return e || t;
655
659
  }
656
- var z = class {
660
+ var R = class {
657
661
  name = "native";
658
662
  deps;
659
663
  onLoaded;
@@ -663,12 +667,15 @@ var z = class {
663
667
  keySession = null;
664
668
  fairplayArmed = !1;
665
669
  fairplayKeyIV;
670
+ loadStarted;
671
+ preloadTouched = !1;
672
+ savedPreload = null;
666
673
  constructor(e) {
667
- this.deps = e;
674
+ this.deps = e, this.loadStarted = e.deferLoad !== !0;
668
675
  }
669
676
  async load() {
670
677
  let { video: e, descriptor: t, emit: r } = this.deps;
671
- if (t.drmEnabled) {
678
+ if (this.loadStarted || (this.preloadTouched = !0, this.savedPreload = e.getAttribute("preload"), e.preload = "none"), t.drmEnabled) {
672
679
  if (!t.drm?.fairplay) {
673
680
  r({
674
681
  type: "error",
@@ -679,11 +686,21 @@ var z = class {
679
686
  }
680
687
  this.armFairPlay();
681
688
  }
682
- this.onLoaded = () => r({ type: "ready" }), this.onError = () => r({
689
+ let i = !this.loadStarted;
690
+ this.onLoaded = i ? void 0 : () => r({ type: "ready" }), this.onError = () => r({
683
691
  type: "error",
684
692
  error: new n("INTERNAL", 0, "native HLS playback error"),
685
693
  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();
694
+ }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = De(t.manifestUrl, t.analytics?.sid), e.load();
695
+ }
696
+ startLoad() {
697
+ this.loadStarted || (this.loadStarted = !0, this.restorePreload());
698
+ }
699
+ restorePreload() {
700
+ if (!this.preloadTouched) return;
701
+ this.preloadTouched = !1;
702
+ let { video: e } = this.deps;
703
+ this.savedPreload === null ? e.removeAttribute("preload") : e.setAttribute("preload", this.savedPreload);
687
704
  }
688
705
  armFairPlay() {
689
706
  let { video: e, descriptor: t, credential: r, deviceId: a, userId: o, emit: s } = this.deps, c = t.drm?.fairplay;
@@ -696,7 +713,7 @@ var z = class {
696
713
  });
697
714
  this.onEncrypted = (n) => {
698
715
  let i = n;
699
- !i.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = Te(i.initData), (async () => {
716
+ !i.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = Ee(i.initData), (async () => {
700
717
  try {
701
718
  let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
702
719
  initDataTypes: [i.initDataType],
@@ -791,8 +808,11 @@ var z = class {
791
808
  e.seekable.length > 0 && (e.currentTime = e.seekable.end(e.seekable.length - 1));
792
809
  }
793
810
  getStats() {
794
- let e = this.deps.video, t = { droppedFrames: I(e) };
795
- return e.duration === Infinity && (t.isLive = !0, e.seekable.length > 0 && (t.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), t;
811
+ let e = this.deps.video, t = F(e), n = {
812
+ droppedFrames: t.dropped,
813
+ decodedFrames: t.decoded
814
+ };
815
+ return this.deps.descriptor.drmEnabled && (n.keySystem = "fairplay"), e.duration === Infinity && (n.isLive = !0, e.seekable.length > 0 && (n.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), n;
796
816
  }
797
817
  stop() {
798
818
  try {
@@ -805,35 +825,42 @@ var z = class {
805
825
  try {
806
826
  e.pause();
807
827
  } catch {}
808
- e.removeAttribute("src");
828
+ e.removeAttribute("src"), this.restorePreload();
809
829
  try {
810
830
  e.load();
811
831
  } catch {}
812
832
  }
813
833
  };
814
- function Ee(e, t) {
834
+ function De(e, t) {
815
835
  return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
816
836
  }
817
837
  //#endregion
818
838
  //#region src/core/engine/hls.ts
819
- function De() {
839
+ function Oe() {
820
840
  return e.isSupported();
821
841
  }
822
- var Oe = 3, ke = 5, Ae = 3, je = 3e4, Me = 12e3, Ne = 3e3, B = 6e3, Pe = 4e3, Fe = 2, Ie = 5, Le = 2, Re = 2, ze = 2e3, Be = 3e4, Ve = 2, He = 9, Ue = .5;
823
- function We(e) {
842
+ var ke = 3, Ae = 5, je = 3, Me = 3e4, Ne = 12e3, Pe = 3e3, z = 6e3, Fe = 4e3, Ie = 2, Le = 5, Re = 2, ze = 2, Be = 2e3, Ve = 3e4, He = 2, Ue = 9, We = .5, Ge = 64;
843
+ function Ke(e) {
844
+ try {
845
+ return new URL(e).host;
846
+ } catch {
847
+ return "";
848
+ }
849
+ }
850
+ function qe(e) {
824
851
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
825
852
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
826
853
  return;
827
854
  }
828
- e.liveSyncDuration ??= Ve;
829
- let t = Math.max(12, e.liveSyncDuration + He);
830
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Ge(e, e.liveSyncDuration);
855
+ e.liveSyncDuration ??= He;
856
+ let t = Math.max(12, e.liveSyncDuration + Ue);
857
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Je(e, e.liveSyncDuration);
831
858
  }
832
- function Ge(e, t) {
859
+ function Je(e, t) {
833
860
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
834
861
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
835
862
  }
836
- var V = class {
863
+ var B = class {
837
864
  name = "hls";
838
865
  hls = null;
839
866
  deps;
@@ -843,6 +870,16 @@ var V = class {
843
870
  playlistBytesLoaded = 0;
844
871
  renditionSwitches = 0;
845
872
  licenseTimeMs = 0;
873
+ licenseRequested = !1;
874
+ upshifts = 0;
875
+ downshifts = 0;
876
+ lastLevelIndex = -1;
877
+ requestCount = 0;
878
+ requestErrors = 0;
879
+ requestTimesMs = [];
880
+ cdnHost = "";
881
+ ladderHeights;
882
+ ladderTopBitrateKbps = 0;
846
883
  mediaRecoveries = 0;
847
884
  networkRecoveries = 0;
848
885
  licenseRecoveries = 0;
@@ -872,11 +909,12 @@ var V = class {
872
909
  sizeCapManaged = !0;
873
910
  dprMedia = null;
874
911
  onDprChange = null;
912
+ loadStarted;
875
913
  constructor(e) {
876
- this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
914
+ this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl, this.loadStarted = e.deferLoad !== !0;
877
915
  }
878
916
  async load() {
879
- let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
917
+ let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = I(r) && L(), o = !!e.drm?.widevine?.licenseUrl && !a;
880
918
  if (e.drmEnabled && !o) {
881
919
  i({
882
920
  type: "error",
@@ -894,6 +932,9 @@ var V = class {
894
932
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
895
933
  }, 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
934
  }
935
+ startLoad() {
936
+ this.loadStarted || (this.loadStarted = !0, this.hls?.startLoad(-1));
937
+ }
897
938
  watchDprChanges() {
898
939
  if (typeof matchMedia != "function") return;
899
940
  this.unwatchDprChanges();
@@ -913,7 +954,7 @@ var V = class {
913
954
  let t = e.liveSyncPosition;
914
955
  if (typeof t != "number" || !Number.isFinite(t)) return;
915
956
  let { video: n } = this.deps;
916
- n.currentTime <= t + Ue || (n.currentTime = t);
957
+ n.currentTime <= t + We || (n.currentTime = t);
917
958
  }
918
959
  startHls() {
919
960
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
@@ -927,6 +968,7 @@ var V = class {
927
968
  maxLiveSyncPlaybackRate: 1.5,
928
969
  abrEwmaDefaultEstimate: 1e6,
929
970
  capLevelToPlayerSize: !1,
971
+ ...this.loadStarted ? {} : { autoStartLoad: !1 },
930
972
  ...u ? {
931
973
  emeEnabled: !0,
932
974
  drmSystems: { ...this.licenseUrl ? { "com.widevine.alpha": { licenseUrl: this.licenseUrl } } : {} },
@@ -937,7 +979,7 @@ var V = class {
937
979
  audioEncryptionScheme: "cbcs"
938
980
  },
939
981
  licenseXhrSetup: (e, t) => {
940
- e.open("POST", this.licenseUrl ?? t, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a);
982
+ e.open("POST", this.licenseUrl ?? t, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a), this.licenseRequested = !0;
941
983
  let o = performance.now();
942
984
  e.addEventListener("loadend", () => {
943
985
  this.licenseTimeMs = Math.max(1, Math.round(performance.now() - o));
@@ -951,19 +993,28 @@ var V = class {
951
993
  } } : {},
952
994
  ...c
953
995
  };
954
- We(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
996
+ qe(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
955
997
  let f = new e(d);
956
998
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
957
- this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
999
+ this.mediaRecoveries = 0, this.networkRecoveries = 0;
1000
+ let e = f.levels.map((e) => e.height).filter((e) => !!e);
1001
+ this.ladderHeights = e.length ? [...new Set(e)].sort((e, t) => e - t) : void 0, this.ladderTopBitrateKbps = f.levels.length ? Math.round(Math.max(...f.levels.map((e) => e.bitrate)) / 1e3) : 0, this.applySizeCap(), l({ type: "ready" });
958
1002
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
959
1003
  t?.frag?.type === "main" && (this.kickPending = !1, this.kickTicks = 0, (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0 || this.starvationRebuilds > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
960
1004
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.starvationRebuilds = 0, this.recoveryResetTimer = null;
961
- }, je)));
1005
+ }, Me)));
962
1006
  }), f.on(e.Events.FRAG_LOADING, (e, t) => {
963
1007
  t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
964
1008
  }), f.on(e.Events.FRAG_LOADED, (e, t) => {
965
- let n = t?.frag?.stats?.loaded;
966
- typeof n == "number" && (this.bytesLoaded += n);
1009
+ let n = t?.frag?.stats, r = n?.loaded;
1010
+ typeof r == "number" && (this.bytesLoaded += r);
1011
+ let i = n?.loading;
1012
+ i && i.end > 0 && i.start > 0 && (this.requestCount += 1, this.requestTimesMs.push(Math.max(0, Math.round(i.end - i.start))), this.requestTimesMs.length > Ge && this.requestTimesMs.shift());
1013
+ let a = t?.frag?.url;
1014
+ if (a) {
1015
+ let e = Ke(a);
1016
+ e && (this.cdnHost = e);
1017
+ }
967
1018
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => {
968
1019
  let n = t?.stats?.loaded;
969
1020
  if (typeof n == "number" && (this.playlistBytesLoaded += n), this.detectedLowLatency) return;
@@ -974,8 +1025,10 @@ var V = class {
974
1025
  }));
975
1026
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog && clearInterval(this.endWatchdog), this.endWatchdog = setInterval(() => {
976
1027
  this.checkEndStale(), this.checkStarvation();
977
- }, Ne), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
978
- this.renditionSwitches += 1, l({
1028
+ }, Pe), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1029
+ this.renditionSwitches += 1;
1030
+ let n = this.lastLevelIndex >= 0 ? f.levels[this.lastLevelIndex] : void 0, r = f.levels[t.level];
1031
+ n && r && r.bitrate !== n.bitrate && (r.bitrate > n.bitrate ? this.upshifts += 1 : this.downshifts += 1), this.lastLevelIndex = t.level, l({
979
1032
  type: "qualitychange",
980
1033
  index: t.level
981
1034
  });
@@ -1036,7 +1089,7 @@ var V = class {
1036
1089
  return;
1037
1090
  }
1038
1091
  this.capTimer && clearTimeout(this.capTimer);
1039
- let t = e > this.sizeCapLevel ? ze : Be;
1092
+ let t = e > this.sizeCapLevel ? Be : Ve;
1040
1093
  this.capTimer = setTimeout(() => {
1041
1094
  this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1042
1095
  }, t);
@@ -1083,20 +1136,28 @@ var V = class {
1083
1136
  e.widevine && (this.licenseUrl = e.widevine);
1084
1137
  }
1085
1138
  getStats() {
1086
- let e = this.hls, t = {
1087
- droppedFrames: I(this.deps.video),
1139
+ let e = this.hls, t = F(this.deps.video), n = {
1140
+ droppedFrames: t.dropped,
1141
+ decodedFrames: t.decoded,
1088
1142
  lowLatency: this.detectedLowLatency,
1089
1143
  bytesLoaded: this.bytesLoaded,
1090
1144
  playlistBytesLoaded: this.playlistBytesLoaded,
1091
1145
  renditionSwitches: this.renditionSwitches,
1092
- licenseTimeMs: this.licenseTimeMs
1146
+ licenseTimeMs: this.licenseTimeMs,
1147
+ upshifts: this.upshifts,
1148
+ downshifts: this.downshifts,
1149
+ requestCount: this.requestCount,
1150
+ requestErrors: this.requestErrors,
1151
+ requestTimesMs: [...this.requestTimesMs],
1152
+ ladderHeights: this.ladderHeights,
1153
+ ladderTopBitrateKbps: this.ladderTopBitrateKbps || void 0
1093
1154
  };
1094
- if (e) {
1095
- typeof e.latency == "number" && (t.latencySeconds = e.latency), typeof e.targetLatency == "number" && e.targetLatency > 0 && (t.targetLatencySeconds = e.targetLatency), typeof e.bandwidthEstimate == "number" && (t.bandwidthKbps = Math.round(e.bandwidthEstimate / 1e3));
1096
- let n = e.currentLevel >= 0 ? e.levels[e.currentLevel] : void 0;
1097
- n && (t.levelHeight = n.height), n && typeof n.bitrate == "number" && (t.renditionBitrateKbps = Math.round(n.bitrate / 1e3)), typeof n?.details?.live == "boolean" && (t.isLive = n.details.live, typeof e.liveSyncPosition == "number" && (t.atLiveEdge = this.deps.video.currentTime >= e.liveSyncPosition - 1.5));
1155
+ if (this.cdnHost && (n.cdnHost = this.cdnHost), this.licenseRequested && (n.keySystem = "widevine", n.drmSecurityLevel = "L3"), e) {
1156
+ n.qualityPinned = !e.autoLevelEnabled, typeof e.latency == "number" && (n.latencySeconds = e.latency), typeof e.targetLatency == "number" && e.targetLatency > 0 && (n.targetLatencySeconds = e.targetLatency), typeof e.bandwidthEstimate == "number" && (n.bandwidthKbps = Math.round(e.bandwidthEstimate / 1e3));
1157
+ let t = e.currentLevel >= 0 ? e.levels[e.currentLevel] : void 0;
1158
+ t && (n.levelHeight = t.height), t?.videoCodec && (n.videoCodec = t.videoCodec), t?.audioCodec && (n.audioCodec = t.audioCodec), t && typeof t.bitrate == "number" && (n.renditionBitrateKbps = Math.round(t.bitrate / 1e3)), typeof t?.details?.live == "boolean" && (n.isLive = t.details.live, typeof e.liveSyncPosition == "number" && (n.atLiveEdge = this.deps.video.currentTime >= e.liveSyncPosition - 1.5));
1098
1159
  }
1099
- return t;
1160
+ return n;
1100
1161
  }
1101
1162
  trackEnd(e) {
1102
1163
  let t = e.details;
@@ -1117,7 +1178,7 @@ var V = class {
1117
1178
  return;
1118
1179
  }
1119
1180
  let e = Date.now() - this.lastAdvanceMs;
1120
- !this.chaseSuspended && e > B && this.suspendChasing(), e > Me && this.emitEnded("stalled");
1181
+ !this.chaseSuspended && e > z && this.suspendChasing(), e > Ne && this.emitEnded("stalled");
1121
1182
  }
1122
1183
  checkStarvation() {
1123
1184
  let e = this.hls, { video: t } = this.deps;
@@ -1126,17 +1187,17 @@ var V = class {
1126
1187
  this.starvedTicks = 0;
1127
1188
  return;
1128
1189
  }
1129
- let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < B, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Pe;
1190
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < z, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Fe;
1130
1191
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1131
1192
  this.starvedTicks = 0;
1132
1193
  return;
1133
1194
  }
1134
- if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Fe)) {
1135
- if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Le && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < Re)) {
1195
+ if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Ie)) {
1196
+ if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Re && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < ze)) {
1136
1197
  this.starvationRebuilds += 1, console.warn("havik-player: restarting the load did not resume video (the wedge is in hls.js state that only a rebuild clears) — rebuilding the player"), this.rebuildInstance("starvation rebuild");
1137
1198
  return;
1138
1199
  }
1139
- if (!(this.starvationKicks >= Ie)) {
1200
+ if (!(this.starvationKicks >= Le)) {
1140
1201
  this.starvationKicks += 1, this.kickPending = !0, this.kickTicks = 0, console.warn("havik-player: video starved while the live playlist advances (wedged hls.js fragment pipeline) — restarting the load");
1141
1202
  try {
1142
1203
  e.stopLoad(), e.startLoad(-1);
@@ -1186,7 +1247,7 @@ var V = class {
1186
1247
  scheduleLicenseRetry(t) {
1187
1248
  if (t.details !== e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED && t.details !== e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED) return !1;
1188
1249
  let n = t.response?.code ?? 0;
1189
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= Ae) return !1;
1250
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= je) return !1;
1190
1251
  this.licenseRecoveries += 1;
1191
1252
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1192
1253
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1204,7 +1265,7 @@ var V = class {
1204
1265
  }
1205
1266
  }
1206
1267
  onError(t) {
1207
- if (!t.fatal) return;
1268
+ if (t.type === e.ErrorTypes.NETWORK_ERROR && String(t.details ?? "").startsWith("frag") && (this.requestErrors += 1), !t.fatal) return;
1208
1269
  let n = this.hls;
1209
1270
  if (n) {
1210
1271
  if (this.clearRecoveryReset(), t.type === e.ErrorTypes.KEY_SYSTEM_ERROR) {
@@ -1212,12 +1273,13 @@ var V = class {
1212
1273
  this.deps.emit({
1213
1274
  type: "error",
1214
1275
  error: this.toError(t, "DRM"),
1215
- fatal: !0
1276
+ fatal: !0,
1277
+ detail: t.details
1216
1278
  });
1217
1279
  return;
1218
1280
  }
1219
1281
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1220
- if (this.networkRecoveries < ke) {
1282
+ if (this.networkRecoveries < Ae) {
1221
1283
  this.networkRecoveries += 1;
1222
1284
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1223
1285
  this.networkRetryTimer = setTimeout(() => {
@@ -1228,26 +1290,29 @@ var V = class {
1228
1290
  this.deps.emit({
1229
1291
  type: "error",
1230
1292
  error: this.toError(t, "NETWORK"),
1231
- fatal: !0
1293
+ fatal: !0,
1294
+ detail: t.details
1232
1295
  });
1233
1296
  return;
1234
1297
  }
1235
1298
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1236
- if (this.mediaRecoveries < Oe) {
1299
+ if (this.mediaRecoveries < ke) {
1237
1300
  this.mediaRecoveries += 1, n.recoverMediaError();
1238
1301
  return;
1239
1302
  }
1240
1303
  this.deps.emit({
1241
1304
  type: "error",
1242
1305
  error: this.toError(t),
1243
- fatal: !0
1306
+ fatal: !0,
1307
+ detail: t.details
1244
1308
  });
1245
1309
  return;
1246
1310
  }
1247
1311
  this.deps.emit({
1248
1312
  type: "error",
1249
1313
  error: this.toError(t),
1250
- fatal: !0
1314
+ fatal: !0,
1315
+ detail: t.details
1251
1316
  });
1252
1317
  }
1253
1318
  }
@@ -1255,7 +1320,29 @@ var V = class {
1255
1320
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1256
1321
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1257
1322
  }
1258
- }, Ke = 15e3, qe = 100, H = 1, U = {
1323
+ }, Ye = 15e3, Xe = 100, V = 1, Ze = 1e3, H = 3e4, Qe = {
1324
+ inline: "PRESENTATION_INLINE",
1325
+ fullscreen: "PRESENTATION_FULLSCREEN",
1326
+ pip: "PRESENTATION_PIP"
1327
+ }, $e = {
1328
+ widevine: "KEY_SYSTEM_WIDEVINE",
1329
+ fairplay: "KEY_SYSTEM_FAIRPLAY",
1330
+ none: "KEY_SYSTEM_NONE"
1331
+ };
1332
+ function U(e, t) {
1333
+ return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
1334
+ }
1335
+ var et = /* @__PURE__ */ new Set([
1336
+ "INVALID_URN",
1337
+ "NOT_FOUND",
1338
+ "GONE",
1339
+ "TOO_EARLY",
1340
+ "UNAVAILABLE",
1341
+ "UNAUTHORIZED",
1342
+ "FORBIDDEN",
1343
+ "RATE_LIMITED",
1344
+ "TIMEOUT"
1345
+ ]), W = {
1259
1346
  idle: "PLAYER_STATE_IDLE",
1260
1347
  waiting: "PLAYER_STATE_WAITING",
1261
1348
  loading: "PLAYER_STATE_LOADING",
@@ -1265,7 +1352,7 @@ var V = class {
1265
1352
  ended: "PLAYER_STATE_ENDED",
1266
1353
  error: "PLAYER_STATE_ERROR"
1267
1354
  };
1268
- function Je(e) {
1355
+ function tt(e) {
1269
1356
  try {
1270
1357
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1271
1358
  return !n || r.length === 0 || n !== "feed" && !n.startsWith("feed-") ? void 0 : (t.hostname = [n.replace(/^feed/, "beacons"), ...r].join("."), t.pathname = "", t.search = "", t.origin);
@@ -1273,7 +1360,7 @@ function Je(e) {
1273
1360
  return;
1274
1361
  }
1275
1362
  }
1276
- var Ye = class {
1363
+ var nt = class {
1277
1364
  opts;
1278
1365
  url;
1279
1366
  now;
@@ -1297,50 +1384,133 @@ var Ye = class {
1297
1384
  errorCount = 0;
1298
1385
  seekCount = 0;
1299
1386
  pauseCount = 0;
1387
+ isPlaying = !1;
1388
+ isHidden = !1;
1389
+ isMuted = !1;
1390
+ activeSinceMs = null;
1391
+ activeTotalMs = 0;
1392
+ mutedSinceMs = null;
1393
+ mutedTotalMs = 0;
1394
+ lastHbMutedMs = 0;
1395
+ volumeChanges = 0;
1396
+ muteChanges = 0;
1397
+ presentation = "inline";
1398
+ presentationSinceMs;
1399
+ fullscreenTotalMs = 0;
1400
+ pipTotalMs = 0;
1401
+ waitingSinceMs = null;
1402
+ waitingTotalMs = 0;
1403
+ firstAudioMs = 0;
1404
+ timeToFirstStallMs = 0;
1405
+ lastStallMs = 0;
1406
+ lastErrorMs = 0;
1407
+ lastSeekMs = 0;
1408
+ lastErrorStage = null;
1409
+ sawFirstBytes = !1;
1410
+ mediaInfoSignature = "";
1411
+ lastUpshifts = 0;
1412
+ lastDownshifts = 0;
1413
+ lastDecoded = 0;
1414
+ lastRequestCount = 0;
1415
+ lastRequestErrors = 0;
1416
+ pendingStart = null;
1417
+ firstFlushTimer = null;
1300
1418
  bornMs;
1301
1419
  startupTimeMs = 0;
1302
1420
  manifestLoadMs = 0;
1303
1421
  firstFragLoadMs = 0;
1304
1422
  onPagehide = () => this.end("END_REASON_PAGEHIDE");
1305
1423
  onVisibility = () => {
1306
- typeof document > "u" || (document.visibilityState === "hidden" ? this.hiddenSinceMs === null && (this.hiddenSinceMs = this.now()) : this.hiddenSinceMs !== null && (this.hiddenTotalMs += this.now() - this.hiddenSinceMs, this.hiddenSinceMs = null));
1424
+ typeof document > "u" || (document.visibilityState === "hidden" ? (this.hiddenSinceMs === null && (this.hiddenSinceMs = this.now()), this.isHidden = !0) : (this.hiddenSinceMs !== null && (this.hiddenTotalMs += this.now() - this.hiddenSinceMs, this.hiddenSinceMs = null), this.isHidden = !1), this.syncActive());
1307
1425
  };
1308
1426
  constructor(e) {
1309
1427
  this.opts = {
1310
1428
  ...e,
1311
- heartbeatMs: e.heartbeatMs ?? Ke
1312
- }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
1429
+ heartbeatMs: e.heartbeatMs ?? Ye
1430
+ }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now(), this.presentationSinceMs = this.bornMs;
1313
1431
  }
1314
1432
  start() {
1315
- this.endedFlag || (this.enqueue({ sessionStart: {
1433
+ if (this.endedFlag) return;
1434
+ let e = {
1316
1435
  player: this.opts.player,
1317
- sdkVersion: this.opts.sdkVersion
1318
- } }), this.flush(), this.timer = setInterval(() => this.heartbeat(), this.opts.heartbeatMs), typeof window < "u" && window.addEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.addEventListener("visibilitychange", this.onVisibility), this.onVisibility()));
1436
+ sdkVersion: this.opts.sdkVersion,
1437
+ ...this.opts.environment
1438
+ };
1439
+ this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), Ze), this.timer = setInterval(() => this.heartbeat(), this.opts.heartbeatMs), typeof window < "u" && window.addEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.addEventListener("visibilitychange", this.onVisibility), this.onVisibility());
1319
1440
  }
1320
1441
  noteStateChange(e, t, n, r) {
1321
1442
  if (this.endedFlag || e === t) return;
1322
1443
  let i = this.now();
1323
- t === "playing" ? (this.playingSinceMs === null && (this.playingSinceMs = i), this.startupTimeMs === 0 && (this.startupTimeMs = Math.max(H, i - this.bornMs))) : this.playingSinceMs !== null && (this.playingTotalMs += i - this.playingSinceMs, this.playingSinceMs = null), t === "buffering" && e === "playing" ? (this.rebufferCount += 1, this.bufferingSinceMs = i) : e === "buffering" && this.bufferingSinceMs !== null && (this.rebufferTotalMs += i - this.bufferingSinceMs, this.bufferingSinceMs = null), t === "paused" && (this.pauseCount += 1);
1444
+ t === "playing" ? (this.playingSinceMs === null && (this.playingSinceMs = i), this.startupTimeMs === 0 && (this.startupTimeMs = Math.max(V, i - this.bornMs)), this.firstAudioMs === 0 && !this.isMuted && (this.firstAudioMs = Math.max(V, i - this.bornMs)), this.isMuted && this.mutedSinceMs === null && (this.mutedSinceMs = i)) : this.playingSinceMs !== null && (this.playingTotalMs += i - this.playingSinceMs, this.playingSinceMs = null, this.mutedSinceMs !== null && (this.mutedTotalMs += i - this.mutedSinceMs, this.mutedSinceMs = null)), this.isPlaying = t === "playing", this.syncActive(), t === "waiting" ? this.waitingSinceMs = i : this.waitingSinceMs !== null && (this.waitingTotalMs += i - this.waitingSinceMs, this.waitingSinceMs = null), t === "buffering" && e === "playing" ? (this.rebufferCount += 1, this.bufferingSinceMs = i, this.lastStallMs = i, this.timeToFirstStallMs === 0 && (this.timeToFirstStallMs = Math.max(V, Math.round(this.playingTotalMs)))) : e === "buffering" && this.bufferingSinceMs !== null && (this.rebufferTotalMs += i - this.bufferingSinceMs, this.bufferingSinceMs = null), t === "paused" && (this.pauseCount += 1);
1324
1445
  let a = {
1325
- fromState: U[e] ?? "PLAYER_STATE_UNSPECIFIED",
1326
- toState: U[t] ?? "PLAYER_STATE_UNSPECIFIED"
1327
- };
1328
- n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1446
+ fromState: W[e] ?? "PLAYER_STATE_UNSPECIFIED",
1447
+ toState: W[t] ?? "PLAYER_STATE_UNSPECIFIED",
1448
+ presentation: Qe[this.presentation]
1449
+ }, o = this.causeOf(e, t, i);
1450
+ o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1451
+ }
1452
+ causeOf(e, t, n) {
1453
+ if (t === "error") return "TRANSITION_CAUSE_ERROR";
1454
+ if (t === "buffering") return n - this.lastSeekMs < 1e3 ? "TRANSITION_CAUSE_SEEK" : "TRANSITION_CAUSE_NETWORK_STALL";
1455
+ if (t === "paused" || t === "playing" && e === "paused") return "TRANSITION_CAUSE_USER";
1456
+ }
1457
+ syncActive() {
1458
+ let e = this.isPlaying && !this.isHidden && !this.isMuted;
1459
+ e && this.activeSinceMs === null ? this.activeSinceMs = this.now() : !e && this.activeSinceMs !== null && (this.activeTotalMs += this.now() - this.activeSinceMs, this.activeSinceMs = null);
1460
+ }
1461
+ closePresentation() {
1462
+ let e = this.now(), t = e - this.presentationSinceMs;
1463
+ this.presentation === "fullscreen" ? this.fullscreenTotalMs += t : this.presentation === "pip" && (this.pipTotalMs += t), this.presentationSinceMs = e;
1464
+ }
1465
+ noteAutoplay(e) {
1466
+ this.pendingStart && (this.pendingStart.autoplay = e);
1467
+ }
1468
+ noteEnvironment(e) {
1469
+ this.pendingStart && Object.assign(this.pendingStart, e);
1470
+ }
1471
+ seedVolume(e, t) {
1472
+ this.endedFlag || (this.isMuted = e || t === 0, this.isMuted && this.isPlaying && this.mutedSinceMs === null && (this.mutedSinceMs = this.now()), this.syncActive());
1473
+ }
1474
+ noteVolume(e, t) {
1475
+ if (this.endedFlag) return;
1476
+ let n = e || t === 0;
1477
+ if (n === this.isMuted) {
1478
+ this.volumeChanges += 1;
1479
+ return;
1480
+ }
1481
+ this.muteChanges += 1;
1482
+ let r = this.now();
1483
+ n ? this.isPlaying && this.mutedSinceMs === null && (this.mutedSinceMs = r) : (this.mutedSinceMs !== null && (this.mutedTotalMs += r - this.mutedSinceMs, this.mutedSinceMs = null), this.firstAudioMs === 0 && this.isPlaying && (this.firstAudioMs = Math.max(V, r - this.bornMs))), this.isMuted = n, this.syncActive();
1484
+ }
1485
+ notePresentation(e) {
1486
+ this.endedFlag || e === this.presentation || (this.closePresentation(), this.presentation = e);
1329
1487
  }
1330
1488
  noteReady() {
1331
- this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(H, this.now() - this.bornMs));
1489
+ this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(V, this.now() - this.bornMs));
1332
1490
  }
1333
1491
  noteFirstBytes() {
1334
- this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(H, this.now() - this.bornMs));
1492
+ this.sawFirstBytes = !0, this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(V, this.now() - this.bornMs));
1335
1493
  }
1336
1494
  noteSeek() {
1337
- this.endedFlag || (this.seekCount += 1);
1495
+ this.endedFlag || (this.seekCount += 1, this.lastSeekMs = this.now());
1338
1496
  }
1339
- noteError(e, t, n, r, i) {
1497
+ noteError(e, t, n, r, i, a) {
1340
1498
  if (this.endedFlag) return;
1341
- this.errorCount += 1;
1342
- let a = { code: e };
1343
- t && (a.detail = t), n && (a.fatal = !0), r > 0 && (a.httpStatus = r), i > 0 && (a.positionMs = Math.round(i)), this.enqueue({ error: a }), this.flush();
1499
+ this.errorCount += 1, this.lastErrorMs = this.now();
1500
+ let o = { code: e };
1501
+ t && (o.detail = t), n && (o.fatal = !0), r > 0 && (o.httpStatus = r), i > 0 && (o.positionMs = Math.round(i));
1502
+ let s = this.stageOf(e, t, a);
1503
+ o.stage = s, this.lastErrorStage = s;
1504
+ let c = this.safeSample()?.cdnHost;
1505
+ c && (o.cdnHost = c), this.enqueue({ error: o }), this.flush();
1506
+ }
1507
+ stageOf(e, t, n) {
1508
+ let r = n ?? "";
1509
+ if (r !== "") return /^fragLoad/i.test(r) ? this.sawFirstBytes ? "ERROR_STAGE_SEGMENT" : "ERROR_STAGE_FIRST_SEGMENT" : /^(manifest|level|audioTrack|subtitleTrack)/i.test(r) ? "ERROR_STAGE_MANIFEST" : /key|licen[cs]e/i.test(r) ? "ERROR_STAGE_DRM" : /parsing|append|buffer|remux|decrypt|media/i.test(r) ? "ERROR_STAGE_DECODE" : "ERROR_STAGE_OTHER";
1510
+ if (e === "DRM_CLIENT") return "ERROR_STAGE_DRM";
1511
+ if (et.has(e)) return "ERROR_STAGE_RESOLVE";
1512
+ let i = t ?? "";
1513
+ return /frag|segment/i.test(i) ? this.sawFirstBytes ? "ERROR_STAGE_SEGMENT" : "ERROR_STAGE_FIRST_SEGMENT" : /manifest|level|playlist/i.test(i) ? "ERROR_STAGE_MANIFEST" : /key|licen[cs]e/i.test(i) ? "ERROR_STAGE_DRM" : /buffer|decode|media|append/i.test(i) ? "ERROR_STAGE_DECODE" : "ERROR_STAGE_OTHER";
1344
1514
  }
1345
1515
  noteEnded() {
1346
1516
  this.end("END_REASON_ENDED");
@@ -1364,25 +1534,51 @@ var Ye = class {
1364
1534
  let s = e.droppedFrames ?? 0;
1365
1535
  s > this.lastDropped && (i.droppedFramesDelta = s - this.lastDropped), this.lastDropped = s;
1366
1536
  let c = e.renditionSwitches ?? 0;
1367
- c > this.lastSwitches && (i.renditionSwitchesDelta = c - this.lastSwitches), this.lastSwitches = c, n > this.lastHbPlayingMs && (i.playingMsDelta = Math.round(n - this.lastHbPlayingMs)), this.lastHbPlayingMs = n, r > this.lastHbHiddenMs && (i.hiddenMsDelta = Math.round(r - this.lastHbHiddenMs)), this.lastHbHiddenMs = r, typeof e.latencySeconds == "number" && (i.latencyMs = Math.round(e.latencySeconds * 1e3)), typeof e.targetLatencySeconds == "number" && (i.targetLatencyMs = Math.round(e.targetLatencySeconds * 1e3)), typeof e.bandwidthKbps == "number" && (i.bandwidthEstKbps = e.bandwidthKbps), typeof e.levelHeight == "number" && (i.renditionHeight = e.levelHeight), typeof e.renditionBitrateKbps == "number" && (i.renditionBitrateKbps = e.renditionBitrateKbps), e.atLiveEdge === !0 && (i.atLiveEdge = !0);
1368
- let l = this.bufferedMsFrom(e);
1369
- l > 0 && (i.bufferedMs = l), i.playerState = U[e.state] ?? "PLAYER_STATE_UNSPECIFIED", this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1537
+ c > this.lastSwitches && (i.renditionSwitchesDelta = c - this.lastSwitches), this.lastSwitches = c, n > this.lastHbPlayingMs && (i.playingMsDelta = Math.round(n - this.lastHbPlayingMs)), this.lastHbPlayingMs = n, r > this.lastHbHiddenMs && (i.hiddenMsDelta = Math.round(r - this.lastHbHiddenMs)), this.lastHbHiddenMs = r, typeof e.latencySeconds == "number" && (i.latencyMs = Math.round(e.latencySeconds * 1e3)), typeof e.targetLatencySeconds == "number" && (i.targetLatencyMs = Math.round(e.targetLatencySeconds * 1e3)), typeof e.bandwidthKbps == "number" && (i.bandwidthEstKbps = e.bandwidthKbps), typeof e.levelHeight == "number" && (i.renditionHeight = e.levelHeight), typeof e.renditionBitrateKbps == "number" && (i.renditionBitrateKbps = e.renditionBitrateKbps), e.atLiveEdge === !0 && (i.atLiveEdge = !0), typeof e.bufferedMs == "number" && e.bufferedMs > 0 && (i.bufferedMs = Math.round(e.bufferedMs)), i.playerState = W[e.state] ?? "PLAYER_STATE_UNSPECIFIED";
1538
+ let l = this.mutedTotalMs;
1539
+ this.mutedSinceMs !== null && (l += t - this.mutedSinceMs), l > this.lastHbMutedMs && (i.mutedMsDelta = Math.round(l - this.lastHbMutedMs)), this.lastHbMutedMs = l;
1540
+ let u = e.upshifts ?? 0;
1541
+ u > this.lastUpshifts && (i.upshiftsDelta = u - this.lastUpshifts), this.lastUpshifts = u;
1542
+ let d = e.downshifts ?? 0;
1543
+ d > this.lastDownshifts && (i.downshiftsDelta = d - this.lastDownshifts), this.lastDownshifts = d;
1544
+ let f = e.decodedFrames ?? 0;
1545
+ f > this.lastDecoded && (i.decodedFramesDelta = f - this.lastDecoded), this.lastDecoded = f;
1546
+ let p = e.requestErrors ?? 0;
1547
+ p > this.lastRequestErrors && (i.requestErrorsDelta = p - this.lastRequestErrors), this.lastRequestErrors = p;
1548
+ let m = e.requestCount ?? 0, h = m - this.lastRequestCount;
1549
+ if (this.lastRequestCount = m, h > 0) {
1550
+ i.requestCountDelta = h;
1551
+ let t = e.requestTimesMs ?? [], n = t.slice(Math.max(0, t.length - h)).sort((e, t) => e - t);
1552
+ n.length > 0 && (i.requestTimeP50Ms = U(n, 50), i.requestTimeP95Ms = U(n, 95));
1553
+ }
1554
+ typeof e.latencySeconds == "number" && typeof e.targetLatencySeconds == "number" && (i.driftMs = Math.round((e.latencySeconds - e.targetLatencySeconds) * 1e3)), typeof e.playbackRate == "number" && e.playbackRate > 0 && (i.playbackRate = e.playbackRate), e.qualityPinned === !0 && (i.qualityPinned = !0), e.cdnHost && (i.cdnHost = e.cdnHost), this.maybeEmitMediaInfo(e), this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1370
1555
  }
1371
- bufferedMsFrom(e) {
1372
- let t = e;
1373
- return typeof t.bufferedMs == "number" ? Math.round(t.bufferedMs) : 0;
1556
+ maybeEmitMediaInfo(e) {
1557
+ if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
1558
+ let t = {};
1559
+ e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = $e[e.keySystem]), e.drmSecurityLevel && (t.drmSecurityLevel = e.drmSecurityLevel), e.ladderHeights?.length && (t.ladderHeights = e.ladderHeights), e.ladderTopBitrateKbps && (t.ladderTopBitrateKbps = e.ladderTopBitrateKbps), e.lowLatency && (t.lowLatency = !0);
1560
+ let n = JSON.stringify(t);
1561
+ n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
1374
1562
  }
1375
1563
  end(e) {
1376
1564
  if (this.endedFlag) return;
1377
1565
  this.endedFlag = !0;
1378
1566
  let t = this.now();
1379
- this.playingSinceMs !== null && (this.playingTotalMs += t - this.playingSinceMs, this.playingSinceMs = null), this.hiddenSinceMs !== null && (this.hiddenTotalMs += t - this.hiddenSinceMs, this.hiddenSinceMs = null), this.bufferingSinceMs !== null && (this.rebufferTotalMs += t - this.bufferingSinceMs, this.bufferingSinceMs = null), this.timer &&= (clearInterval(this.timer), null), typeof window < "u" && window.removeEventListener("pagehide", this.onPagehide), typeof document < "u" && document.removeEventListener("visibilitychange", this.onVisibility);
1380
- let n = this.safeSample(), r = { reason: e }, i = n?.bytesLoaded ?? this.lastBytes;
1567
+ this.playingSinceMs !== null && (this.playingTotalMs += t - this.playingSinceMs, this.playingSinceMs = null), this.hiddenSinceMs !== null && (this.hiddenTotalMs += t - this.hiddenSinceMs, this.hiddenSinceMs = null), this.bufferingSinceMs !== null && (this.rebufferTotalMs += t - this.bufferingSinceMs, this.bufferingSinceMs = null), this.mutedSinceMs !== null && (this.mutedTotalMs += t - this.mutedSinceMs, this.mutedSinceMs = null), this.waitingSinceMs !== null && (this.waitingTotalMs += t - this.waitingSinceMs, this.waitingSinceMs = null), this.isPlaying = !1, this.syncActive(), this.closePresentation(), this.firstFlushTimer &&= (clearTimeout(this.firstFlushTimer), null), this.timer &&= (clearInterval(this.timer), null), typeof window < "u" && window.removeEventListener("pagehide", this.onPagehide), typeof document < "u" && document.removeEventListener("visibilitychange", this.onVisibility);
1568
+ let n = this.safeSample();
1569
+ n && this.maybeEmitMediaInfo(n);
1570
+ let r = { reason: e }, i = n?.bytesLoaded ?? this.lastBytes;
1381
1571
  i > 0 && (r.bytesDownloadedTotal = i), this.playingTotalMs > 0 && (r.watchTimeMs = Math.round(this.playingTotalMs)), this.rebufferCount > 0 && (r.rebufferCount = this.rebufferCount), this.rebufferTotalMs > 0 && (r.rebufferTimeMs = Math.round(this.rebufferTotalMs)), this.startupTimeMs > 0 && (r.startupTimeMs = Math.round(this.startupTimeMs)), this.manifestLoadMs > 0 && (r.manifestLoadMs = Math.round(this.manifestLoadMs)), this.firstFragLoadMs > 0 && (r.firstFragLoadMs = Math.round(this.firstFragLoadMs));
1382
1572
  let a = n?.licenseTimeMs ?? 0;
1383
1573
  a > 0 && (r.licenseTimeMs = Math.round(a));
1384
1574
  let o = n?.droppedFrames ?? this.lastDropped;
1385
- o > 0 && (r.droppedFramesTotal = o), this.errorCount > 0 && (r.errorCount = this.errorCount), this.hiddenTotalMs > 0 && (r.hiddenTimeMs = Math.round(this.hiddenTotalMs)), this.seekCount > 0 && (r.seekCount = this.seekCount), this.pauseCount > 0 && (r.pauseCount = this.pauseCount), this.pending.push(this.wrap({ sessionEnd: r })), this.flush(e === "END_REASON_PAGEHIDE");
1575
+ o > 0 && (r.droppedFramesTotal = o), this.errorCount > 0 && (r.errorCount = this.errorCount), this.hiddenTotalMs > 0 && (r.hiddenTimeMs = Math.round(this.hiddenTotalMs)), this.seekCount > 0 && (r.seekCount = this.seekCount), this.pauseCount > 0 && (r.pauseCount = this.pauseCount), this.firstAudioMs > 0 && (r.firstAudioMs = Math.round(this.firstAudioMs)), this.waitingTotalMs > 0 && (r.waitingForLiveMs = Math.round(this.waitingTotalMs)), this.timeToFirstStallMs > 0 && (r.timeToFirstStallMs = Math.round(this.timeToFirstStallMs));
1576
+ let s = n?.positionMs ?? 0;
1577
+ s > 0 && (r.exitPositionMs = Math.round(s)), this.activeTotalMs > 0 && (r.activeWatchTimeMs = Math.round(this.activeTotalMs)), this.mutedTotalMs > 0 && (r.mutedTimeMs = Math.round(this.mutedTotalMs));
1578
+ let c = e !== "END_REASON_ENDED";
1579
+ c && this.lastStallMs > 0 && t - this.lastStallMs <= H && (r.abandonAfterStallMs = Math.max(V, Math.round(t - this.lastStallMs))), c && this.lastErrorMs > 0 && t - this.lastErrorMs <= H && (r.abandonAfterErrorMs = Math.max(V, Math.round(t - this.lastErrorMs))), this.fullscreenTotalMs > 0 && (r.fullscreenTimeMs = Math.round(this.fullscreenTotalMs)), this.pipTotalMs > 0 && (r.pipTimeMs = Math.round(this.pipTotalMs)), this.volumeChanges > 0 && (r.volumeChanges = this.volumeChanges), this.muteChanges > 0 && (r.muteChanges = this.muteChanges);
1580
+ let l = n?.decodedFrames ?? this.lastDecoded;
1581
+ l > 0 && (r.decodedFramesTotal = l), this.startupTimeMs === 0 && (r.failedStage = this.lastErrorStage ?? (this.manifestLoadMs === 0 ? "ERROR_STAGE_MANIFEST" : this.firstFragLoadMs === 0 ? "ERROR_STAGE_FIRST_SEGMENT" : "ERROR_STAGE_OTHER")), this.pending.push(this.wrap({ sessionEnd: r })), this.flush(e === "END_REASON_PAGEHIDE");
1386
1582
  }
1387
1583
  safeSample() {
1388
1584
  try {
@@ -1400,13 +1596,13 @@ var Ye = class {
1400
1596
  };
1401
1597
  }
1402
1598
  enqueue(e) {
1403
- for (this.pending.push(this.wrap(e)); this.pending.length > qe;) {
1599
+ for (this.pending.push(this.wrap(e)); this.pending.length > Xe;) {
1404
1600
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1405
1601
  this.pending.splice(e >= 0 ? e : 0, 1);
1406
1602
  }
1407
1603
  }
1408
1604
  flush(e = !1) {
1409
- if (this.pending.length === 0) return;
1605
+ if (this.firstFlushTimer &&= (clearTimeout(this.firstFlushTimer), null), this.pendingStart = null, this.pending.length === 0) return;
1410
1606
  let t = JSON.stringify({ beacons: this.pending });
1411
1607
  if (this.pending = [], e) {
1412
1608
  let e = this.opts.sendBeacon ?? (typeof navigator < "u" && navigator.sendBeacon ? navigator.sendBeacon.bind(navigator) : void 0);
@@ -1422,7 +1618,74 @@ var Ye = class {
1422
1618
  keepalive: !0
1423
1619
  }).catch(() => {});
1424
1620
  }
1425
- }, Xe = "2.0.3", W = {
1621
+ };
1622
+ //#endregion
1623
+ //#region src/core/environment.ts
1624
+ function rt() {
1625
+ if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
1626
+ let e;
1627
+ try {
1628
+ e = window.top !== window.self;
1629
+ } catch {
1630
+ e = !0;
1631
+ }
1632
+ let t = window.location?.origin;
1633
+ if (!e) return {
1634
+ host: t && t !== "null" ? t : void 0,
1635
+ integration: "INTEGRATION_KIND_SDK"
1636
+ };
1637
+ let n;
1638
+ try {
1639
+ let e = window.location.ancestorOrigins;
1640
+ e && e.length > 0 && (n = e[e.length - 1] ?? void 0), !n && typeof document < "u" && document.referrer && (n = new URL(document.referrer).origin);
1641
+ } catch {
1642
+ n = void 0;
1643
+ }
1644
+ return n === "null" && (n = void 0), {
1645
+ host: n,
1646
+ integration: "INTEGRATION_KIND_IFRAME"
1647
+ };
1648
+ }
1649
+ function it() {
1650
+ if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
1651
+ let e = navigator.userAgentData, t = e?.platform ?? "";
1652
+ if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
1653
+ let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
1654
+ return e?.mobile ?? (typeof navigator.maxTouchPoints == "number" && navigator.maxTouchPoints > 1 && n > 0 && n < 900) ? n >= 600 ? "DEVICE_CLASS_TABLET" : "DEVICE_CLASS_PHONE" : e || n >= 900 || /mac|win|linux|chrome os/i.test(t) ? "DEVICE_CLASS_DESKTOP" : "DEVICE_CLASS_OTHER";
1655
+ }
1656
+ function at() {
1657
+ let e = typeof navigator < "u" ? navigator.connection : void 0;
1658
+ if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
1659
+ let t = { connectionType: e.type ? {
1660
+ wifi: "CONNECTION_TYPE_WIFI",
1661
+ cellular: "CONNECTION_TYPE_CELLULAR",
1662
+ ethernet: "CONNECTION_TYPE_ETHERNET"
1663
+ }[e.type] ?? "CONNECTION_TYPE_OTHER" : "CONNECTION_TYPE_UNKNOWN" };
1664
+ return typeof e.downlink == "number" && e.downlink > 0 && (t.downlinkKbps = Math.round(e.downlink * 1e3)), e.saveData && (t.saveData = !0), t;
1665
+ }
1666
+ function ot(e) {
1667
+ let t = rt(), n = {
1668
+ deviceClass: it(),
1669
+ integration: t.integration,
1670
+ ...at()
1671
+ };
1672
+ t.host && (n.host = t.host), typeof screen < "u" && screen.width > 0 && (n.screenWidth = screen.width, n.screenHeight = screen.height), typeof window < "u" && window.devicePixelRatio > 0 && (n.devicePixelRatio = Math.round(window.devicePixelRatio * 100) / 100);
1673
+ let r = e.getBoundingClientRect?.();
1674
+ return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
1675
+ }
1676
+ async function st() {
1677
+ let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
1678
+ if (!e?.getHighEntropyValues) return {};
1679
+ try {
1680
+ let t = await e.getHighEntropyValues(["model", "platformVersion"]), n = {};
1681
+ return t.model && (n.deviceModel = t.model), t.platformVersion && (n.osVersion = t.platformVersion), n;
1682
+ } catch {
1683
+ return {};
1684
+ }
1685
+ }
1686
+ //#endregion
1687
+ //#region src/core/version.ts
1688
+ var ct = "2.1.0", G = {
1426
1689
  accent: "#E1B600",
1427
1690
  accentText: "#1B1C23",
1428
1691
  background: "#0e0f13",
@@ -1433,7 +1696,7 @@ var Ye = class {
1433
1696
  border: "rgba(255, 255, 255, 0.1)",
1434
1697
  radius: "8px",
1435
1698
  fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
1436
- }, G = {
1699
+ }, K = {
1437
1700
  accent: "--havik-accent",
1438
1701
  accentText: "--havik-accent-text",
1439
1702
  background: "--havik-bg",
@@ -1445,25 +1708,25 @@ var Ye = class {
1445
1708
  radius: "--havik-radius",
1446
1709
  fontFamily: "--havik-font"
1447
1710
  };
1448
- function K(e, t) {
1711
+ function q(e, t) {
1449
1712
  let n = {
1450
- ...W,
1713
+ ...G,
1451
1714
  ...t
1452
1715
  };
1453
- for (let t of Object.keys(G)) e.style.setProperty(G[t], n[t]);
1716
+ for (let t of Object.keys(K)) e.style.setProperty(K[t], n[t]);
1454
1717
  n.logoUrl && e.style.setProperty("--havik-logo", `url("${n.logoUrl}")`);
1455
1718
  }
1456
1719
  //#endregion
1457
1720
  //#region src/ui/styles.ts
1458
- var q = "havik-player-styles", Ze = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n.havik-spinner{width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n";
1459
- function Qe(e = document) {
1460
- if (e.getElementById(q)) return;
1721
+ var J = "havik-player-styles", lt = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n.havik-spinner{width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n";
1722
+ function ut(e = document) {
1723
+ if (e.getElementById(J)) return;
1461
1724
  let t = e.createElement("style");
1462
- t.id = q, t.textContent = Ze, e.head.appendChild(t);
1725
+ t.id = J, t.textContent = lt, e.head.appendChild(t);
1463
1726
  }
1464
1727
  //#endregion
1465
1728
  //#region src/ui/icons.ts
1466
- var $e = {
1729
+ var dt = {
1467
1730
  play: "<path d=\"M8 5v14l11-7z\"/>",
1468
1731
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1469
1732
  volumeHigh: "<path d=\"M3 10v4h4l5 5V5L7 10H3zm13.5 2a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4zm-2.5-9v2.06A7 7 0 0 1 14 19v2a9 9 0 0 0 0-18z\"/>",
@@ -1476,21 +1739,21 @@ var $e = {
1476
1739
  liveDot: "<circle cx=\"12\" cy=\"12\" r=\"6\"/>",
1477
1740
  retry: "<path d=\"M17.65 6.35A8 8 0 1 0 19.73 13h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>"
1478
1741
  };
1479
- function J(e) {
1480
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${$e[e]}</svg>`;
1742
+ function Y(e) {
1743
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${dt[e]}</svg>`;
1481
1744
  }
1482
1745
  //#endregion
1483
1746
  //#region src/ui/controls.ts
1484
- var et = 3e3, tt = /* @__PURE__ */ new Set([
1747
+ var ft = 3e3, pt = /* @__PURE__ */ new Set([
1485
1748
  "GONE",
1486
1749
  "NOT_FOUND",
1487
1750
  "INVALID_URN",
1488
1751
  "UNAUTHORIZED"
1489
1752
  ]);
1490
- function nt(e) {
1491
- return new rt(e);
1753
+ function mt(e) {
1754
+ return new ht(e);
1492
1755
  }
1493
- var rt = class {
1756
+ var ht = class {
1494
1757
  root;
1495
1758
  video;
1496
1759
  player;
@@ -1532,7 +1795,7 @@ var rt = class {
1532
1795
  scrubbing = !1;
1533
1796
  offFns = [];
1534
1797
  constructor(e) {
1535
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? et, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, Qe(this.doc), this.root.classList.add("havik-player"), this.root.hasAttribute("tabindex") || (this.root.tabIndex = 0), this.root.setAttribute("role", "region"), this.root.setAttribute("aria-label", "Video player");
1798
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? ft, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, ut(this.doc), this.root.classList.add("havik-player"), this.root.hasAttribute("tabindex") || (this.root.tabIndex = 0), this.root.setAttribute("role", "region"), this.root.setAttribute("aria-label", "Video player");
1536
1799
  let t = (e, t, n) => {
1537
1800
  let r = this.doc.createElement(e);
1538
1801
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1543,12 +1806,12 @@ var rt = class {
1543
1806
  "aria-label": r,
1544
1807
  title: r
1545
1808
  });
1546
- return i.innerHTML = J(n), i;
1809
+ return i.innerHTML = Y(n), i;
1547
1810
  };
1548
1811
  this.overlay = t("div", "havik-overlay"), this.center = t("button", "havik-center", {
1549
1812
  type: "button",
1550
1813
  "aria-label": "Play"
1551
- }), this.center.innerHTML = J("play"), this.spinner = t("div", "havik-spinner"), this.spinner.style.display = "none", this.overlay.append(this.center, this.spinner), this.gradient = t("div", "havik-gradient"), this.bar = t("div", "havik-bar"), this.seek = t("div", "havik-seek", {
1814
+ }), this.center.innerHTML = Y("play"), this.spinner = t("div", "havik-spinner"), this.spinner.style.display = "none", this.overlay.append(this.center, this.spinner), this.gradient = t("div", "havik-gradient"), this.bar = t("div", "havik-bar"), this.seek = t("div", "havik-seek", {
1552
1815
  role: "slider",
1553
1816
  "aria-label": "Seek",
1554
1817
  "aria-valuemin": "0",
@@ -1566,7 +1829,7 @@ var rt = class {
1566
1829
  "aria-label": "Volume"
1567
1830
  });
1568
1831
  let a = t("div", "havik-vol");
1569
- a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = J("liveDot");
1832
+ a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = Y("liveDot");
1570
1833
  let o = this.doc.createElement("span");
1571
1834
  o.textContent = "LIVE", this.liveGo = t("button", "havik-live__go", { type: "button" }), this.liveGo.textContent = "Go live", this.live.append(o, this.liveGo), this.time = t("div", "havik-time"), this.time.textContent = "0:00", this.time.style.display = "none";
1572
1835
  let s = t("div", "havik-spacer");
@@ -1620,12 +1883,12 @@ var rt = class {
1620
1883
  }
1621
1884
  if (e === "preplay") {
1622
1885
  let e = this.doc.createElement("button");
1623
- e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = J("play"), e.addEventListener("click", () => void this.player.play()), n.append(e);
1886
+ e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Y("play"), e.addEventListener("click", () => void this.player.play()), n.append(e);
1624
1887
  } else {
1625
1888
  let r = this.doc.createElement("div");
1626
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !tt.has(this.lastError.code)) {
1889
+ if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !pt.has(this.lastError.code)) {
1627
1890
  let e = this.doc.createElement("button");
1628
- e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${J("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
1891
+ e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Y("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
1629
1892
  }
1630
1893
  }
1631
1894
  this.root.append(n), this.statusBox = n, this.show();
@@ -1664,7 +1927,7 @@ var rt = class {
1664
1927
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
1665
1928
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
1666
1929
  }
1667
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Y(this.video.currentTime)} / ${Y(this.video.duration)}`);
1930
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${X(this.video.currentTime)} / ${X(this.video.duration)}`);
1668
1931
  }
1669
1932
  syncLive() {
1670
1933
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -1714,17 +1977,17 @@ var rt = class {
1714
1977
  renderQualityMenu() {
1715
1978
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1716
1979
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1717
- for (let n of e) this.menu.append(this.menuItem(it(n), t === n.index, () => this.player.setQuality(n.index)));
1980
+ for (let n of e) this.menu.append(this.menuItem(gt(n), t === n.index, () => this.player.setQuality(n.index)));
1718
1981
  }
1719
1982
  renderAudioMenu() {
1720
1983
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1721
1984
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1722
- for (let t of e) this.menu.append(this.menuItem(at(t), t.default, () => this.player.setAudioTrack(t.id)));
1985
+ for (let t of e) this.menu.append(this.menuItem(_t(t), t.default, () => this.player.setAudioTrack(t.id)));
1723
1986
  }
1724
1987
  renderCcMenu() {
1725
1988
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1726
1989
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1727
- for (let t of e) this.menu.append(this.menuItem(ot(t), !1, () => this.player.setTextTrack(t.id)));
1990
+ for (let t of e) this.menu.append(this.menuItem(vt(t), !1, () => this.player.setTextTrack(t.id)));
1728
1991
  }
1729
1992
  onTracksChanged() {
1730
1993
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1807,7 +2070,7 @@ var rt = class {
1807
2070
  }
1808
2071
  }
1809
2072
  setIcon(e, t) {
1810
- e.innerHTML = J(t);
2073
+ e.innerHTML = Y(t);
1811
2074
  }
1812
2075
  destroy() {
1813
2076
  this.hideTimer && clearTimeout(this.hideTimer);
@@ -1824,32 +2087,32 @@ var rt = class {
1824
2087
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
1825
2088
  }
1826
2089
  };
1827
- function Y(e) {
2090
+ function X(e) {
1828
2091
  (!Number.isFinite(e) || e < 0) && (e = 0);
1829
2092
  let t = Math.floor(e % 60), n = Math.floor(e / 60 % 60), r = Math.floor(e / 3600), i = r > 0 ? String(n).padStart(2, "0") : String(n), a = String(t).padStart(2, "0");
1830
2093
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1831
2094
  }
1832
- function it(e) {
2095
+ function gt(e) {
1833
2096
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1834
2097
  }
1835
- function at(e) {
2098
+ function _t(e) {
1836
2099
  return e.name || e.lang || `Track ${e.id}`;
1837
2100
  }
1838
- function ot(e) {
2101
+ function vt(e) {
1839
2102
  return e.name || e.lang || `Track ${e.id}`;
1840
2103
  }
1841
2104
  //#endregion
1842
2105
  //#region src/managed/index.ts
1843
- var st = 6e3, X = 45e3, Z = 2e3, ct = 3, lt = 2e3, ut = 48e4;
1844
- async function Q(e) {
2106
+ var yt = 6e3, Z = 45e3, Q = 2e3, bt = 3, xt = 2e3, St = 48e4;
2107
+ async function Ct(e) {
1845
2108
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1846
2109
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
1847
2110
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
1848
2111
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
1849
- let t = new dt(e);
2112
+ let t = new wt(e);
1850
2113
  return await t.start(), t;
1851
2114
  }
1852
- var dt = class {
2115
+ var wt = class {
1853
2116
  opts;
1854
2117
  abort = new AbortController();
1855
2118
  listeners = {};
@@ -1873,7 +2136,9 @@ var dt = class {
1873
2136
  armFallbackCtrl = null;
1874
2137
  _lastError = null;
1875
2138
  analyticsSession = null;
1876
- detachSeekListener;
2139
+ detachMediaListeners;
2140
+ playRequested = !1;
2141
+ pendingStartLoad = !1;
1877
2142
  constructor(e) {
1878
2143
  this.opts = e;
1879
2144
  }
@@ -1934,7 +2199,7 @@ var dt = class {
1934
2199
  setTimeout(() => {
1935
2200
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1936
2201
  phase: i ? "unavailable" : "tooEarly",
1937
- retryInMs: X,
2202
+ retryInMs: Z,
1938
2203
  attempt: 1,
1939
2204
  elapsedMs: 0,
1940
2205
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -1973,13 +2238,13 @@ var dt = class {
1973
2238
  !a && !p() && h();
1974
2239
  }, e);
1975
2240
  };
1976
- _(st), this.armingSub = D({
2241
+ _(yt), this.armingSub = D({
1977
2242
  eventsBaseUrl: t,
1978
2243
  matchUrn: this.opts.matchUrn,
1979
2244
  credential: this.opts.credential,
1980
- onAlive: () => _(X),
2245
+ onAlive: () => _(Z),
1981
2246
  onState: (e) => {
1982
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Z);
2247
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Q);
1983
2248
  },
1984
2249
  onError: () => h()
1985
2250
  });
@@ -2002,49 +2267,61 @@ var dt = class {
2002
2267
  let n = await s(this.opts.credential);
2003
2268
  if (this.destroyed || t !== this.loadGen) return;
2004
2269
  this.setState("loading");
2005
- let r = this.createEngine(e, n), i = we(this.opts.video, (e) => {
2270
+ let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = Te(this.opts.video, (e) => {
2006
2271
  this._state !== "ended" && this._state !== "error" && this.setState(e);
2007
2272
  });
2008
- if (await r.load(), this.destroyed || t !== this.loadGen) {
2009
- i(), r.destroy();
2273
+ if (await i.load(), this.destroyed || t !== this.loadGen) {
2274
+ a(), i.destroy();
2010
2275
  return;
2011
2276
  }
2012
2277
  if (this._state === "error") {
2013
- i(), r.destroy();
2278
+ a(), i.destroy();
2014
2279
  return;
2015
2280
  }
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);
2281
+ 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(() => {
2282
+ this.destroyed || t !== this.loadGen || this.engine !== i || (this.analyticsSession?.noteReady(), this.emit("ready"));
2283
+ }, 0));
2017
2284
  }
2018
2285
  startAnalytics(e, t, n) {
2019
- if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
2286
+ if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
2020
2287
  let r = e.analytics?.sid;
2021
2288
  if (!r) return;
2022
- let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Je(this.opts.baseUrl);
2289
+ let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? tt(this.opts.baseUrl);
2023
2290
  if (!i) return;
2024
- let a = this.opts.video, o = new Ye({
2291
+ let a = this.opts.video, o = new nt({
2025
2292
  endpoint: i,
2026
2293
  sid: r,
2027
2294
  apiKey: n,
2028
2295
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
2029
- sdkVersion: Xe,
2296
+ sdkVersion: ct,
2297
+ environment: ot(a),
2030
2298
  sample: () => {
2031
- let e = this.engine?.getStats() ?? F, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
2299
+ let e = this.engine?.getStats() ?? we, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
2032
2300
  return {
2033
2301
  ...e,
2034
2302
  bufferedMs: Math.round(t * 1e3),
2035
2303
  state: this._state,
2036
- positionMs: Math.round(a.currentTime * 1e3)
2304
+ positionMs: Math.round(a.currentTime * 1e3),
2305
+ playbackRate: a.playbackRate
2037
2306
  };
2038
2307
  }
2039
2308
  });
2040
2309
  this.analyticsSession = o;
2041
- let s = () => o.noteSeek();
2042
- a.addEventListener("seeking", s), this.detachSeekListener = () => a.removeEventListener("seeking", s), o.start();
2310
+ let s = () => o.noteSeek(), c = () => o.noteVolume(a.muted, a.volume);
2311
+ a.addEventListener("seeking", s), a.addEventListener("volumechange", c), this.detachMediaListeners = () => {
2312
+ a.removeEventListener("seeking", s), a.removeEventListener("volumechange", c);
2313
+ }, o.start(), o.seedVolume(a.muted, a.volume), o.notePresentation(this.currentPresentation()), st().then((e) => o.noteEnvironment(e)), this.opts.autoplay === !1 && o.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
2043
2314
  }
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 = {
2315
+ currentPresentation() {
2316
+ let e = this.opts.video;
2317
+ if (typeof document < "u" && document.pictureInPictureElement === e) return "pip";
2318
+ let t = typeof document < "u" ? document.fullscreenElement : null;
2319
+ return e.webkitDisplayingFullscreen === !0 || t && (t === e || t.contains(e)) ? "fullscreen" : "inline";
2320
+ }
2321
+ createEngine(e, t, r = !1) {
2322
+ let i = { ...this.opts.hlsConfig };
2323
+ this.opts.startLevel != null && (i.startLevel = this.opts.startLevel), this.opts.liveLatencyTarget != null && (i.liveSyncDuration = this.opts.liveLatencyTarget);
2324
+ let a = {
2048
2325
  video: this.opts.video,
2049
2326
  descriptor: e,
2050
2327
  credential: t,
@@ -2053,20 +2330,21 @@ var dt = class {
2053
2330
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
2054
2331
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
2055
2332
  debug: this.opts.debug,
2056
- hlsConfig: r,
2333
+ deferLoad: r,
2334
+ hlsConfig: i,
2057
2335
  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);
2336
+ }, o = this.opts.engine ?? "auto";
2337
+ if (o === "native") return new R(a);
2338
+ if (o === "hls") return new B(a);
2339
+ if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && I(this.opts.video) && L()) return new R(a);
2340
+ if (Oe()) return new B(a);
2341
+ if (I(this.opts.video)) return new R(a);
2064
2342
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
2065
2343
  }
2066
2344
  onEngineEvent(e) {
2067
2345
  if (!this.destroyed) switch (e.type) {
2068
2346
  case "ready":
2069
- this.analyticsSession?.noteReady(), this.emit("ready"), this.opts.autoplay !== !1 && this.play();
2347
+ this.analyticsSession?.noteReady(), this.emit("ready"), (this.opts.autoplay !== !1 || this.playRequested) && this.play();
2070
2348
  break;
2071
2349
  case "lowLatencyDetected":
2072
2350
  this._descriptor && (this._descriptor.isLowLatency = e.value);
@@ -2083,7 +2361,7 @@ var dt = class {
2083
2361
  case "ended":
2084
2362
  this.onStreamEnded();
2085
2363
  break;
2086
- case "error": this.analyticsSession?.noteError(e.error.code, e.error.message, e.fatal, e.error.httpStatus ?? 0, Math.round(this.opts.video.currentTime * 1e3)), e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
2364
+ case "error": this.analyticsSession?.noteError(e.error.code, e.error.message, e.fatal, e.error.httpStatus ?? 0, Math.round(this.opts.video.currentTime * 1e3), e.detail), e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
2087
2365
  }
2088
2366
  }
2089
2367
  startLiveState(e) {
@@ -2103,7 +2381,7 @@ var dt = class {
2103
2381
  r = 0, i = !1;
2104
2382
  },
2105
2383
  onError: () => {
2106
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= ct && (i = !0, this.emit("warning", new n("NETWORK", 0, `live-state events unreachable at ${t} after ${r} attempts — end-of-stream detection degraded to the HLS staleness watchdog. Check the events host, the api-key's allowed origins, and CSP connect-src.`))));
2384
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= bt && (i = !0, this.emit("warning", new n("NETWORK", 0, `live-state events unreachable at ${t} after ${r} attempts — end-of-stream detection degraded to the HLS staleness watchdog. Check the events host, the api-key's allowed origins, and CSP connect-src.`))));
2107
2385
  }
2108
2386
  });
2109
2387
  }
@@ -2116,7 +2394,7 @@ var dt = class {
2116
2394
  t === "match_ended" && this._endedReason !== "match_ended" && (this._endedReason = t, this.emit("ended", t));
2117
2395
  return;
2118
2396
  }
2119
- this.endedHandled = !0, this._endedReason = t ?? null, this.stopRefresh(), this.engine?.stop(), this.setState("ended");
2397
+ this.endedHandled = !0, this._endedReason = t ?? null, this.stopRefresh(), this.pendingStartLoad = !1, this.engine?.stop(), this.setState("ended");
2120
2398
  }
2121
2399
  }
2122
2400
  rejoinEnabled() {
@@ -2125,7 +2403,7 @@ var dt = class {
2125
2403
  scheduleRejoin(e) {
2126
2404
  !this.rejoinEnabled() || this.rejoinTimer || (this.rejoinTimer = setTimeout(() => {
2127
2405
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
2128
- }, Math.random() * Z));
2406
+ }, Math.random() * Q));
2129
2407
  }
2130
2408
  cancelRejoin() {
2131
2409
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -2135,7 +2413,7 @@ var dt = class {
2135
2413
  this.teardownPlayback(), this.setState("waiting"), this.beginArming(e, void 0, !0);
2136
2414
  }
2137
2415
  teardownPlayback() {
2138
- this.stopStats(), this.stopRefresh(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null;
2416
+ this.stopStats(), this.stopRefresh(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null, this.pendingStartLoad = !1;
2139
2417
  }
2140
2418
  mountControls() {
2141
2419
  let e = this.opts.controls ?? "custom", t = this.opts.video;
@@ -2149,7 +2427,7 @@ var dt = class {
2149
2427
  }
2150
2428
  t.controls = !1;
2151
2429
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2152
- r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = nt({
2430
+ r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = mt({
2153
2431
  root: n,
2154
2432
  video: t,
2155
2433
  player: this,
@@ -2166,14 +2444,20 @@ var dt = class {
2166
2444
  e && (e.parentNode ? (e.parentNode.insertBefore(this.opts.video, e), e.remove()) : this.opts.video.parentNode === e && e.removeChild(this.opts.video)), this.container = null;
2167
2445
  }
2168
2446
  attachPipListeners() {
2169
- let e = this.opts.video, t = () => this.emit("pipchange", !0), n = () => this.emit("pipchange", !1);
2170
- e.addEventListener("enterpictureinpicture", t), e.addEventListener("leavepictureinpicture", n), this.detachPip = () => {
2171
- e.removeEventListener("enterpictureinpicture", t), e.removeEventListener("leavepictureinpicture", n);
2447
+ let e = this.opts.video, t = () => this.analyticsSession?.notePresentation(this.currentPresentation()), n = () => {
2448
+ t(), this.emit("pipchange", !0);
2449
+ }, r = () => {
2450
+ t(), this.emit("pipchange", !1);
2451
+ };
2452
+ e.addEventListener("enterpictureinpicture", n), e.addEventListener("leavepictureinpicture", r);
2453
+ let i = typeof document < "u" ? document : void 0;
2454
+ i?.addEventListener("fullscreenchange", t), i?.addEventListener("webkitfullscreenchange", t), this.detachPip = () => {
2455
+ e.removeEventListener("enterpictureinpicture", n), e.removeEventListener("leavepictureinpicture", r), i?.removeEventListener("fullscreenchange", t), i?.removeEventListener("webkitfullscreenchange", t);
2172
2456
  };
2173
2457
  }
2174
2458
  startStats() {
2175
2459
  this.stopStats();
2176
- let e = Math.max(500, this.opts.statsIntervalMs ?? lt);
2460
+ let e = Math.max(500, this.opts.statsIntervalMs ?? xt);
2177
2461
  this.statsTimer = setInterval(() => {
2178
2462
  if (!this.engine) return;
2179
2463
  let e = this.engine.getStats();
@@ -2185,7 +2469,7 @@ var dt = class {
2185
2469
  }
2186
2470
  scheduleLicenseRefresh() {
2187
2471
  this.stopRefresh();
2188
- let e = this.opts.licenseRefreshMs ?? ut;
2472
+ let e = this.opts.licenseRefreshMs ?? St;
2189
2473
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2190
2474
  }
2191
2475
  stopRefresh() {
@@ -2206,10 +2490,15 @@ var dt = class {
2206
2490
  }
2207
2491
  }
2208
2492
  async play() {
2209
- if (!this.destroyed) try {
2210
- await this.opts.video.play();
2211
- } catch (e) {
2212
- e instanceof DOMException && e.name === "NotAllowedError" && this.emit("autoplayblocked");
2493
+ if (this.destroyed) return;
2494
+ let e = !this.playRequested;
2495
+ this.playRequested = !0;
2496
+ let t = this.pendingStartLoad && this._state !== "ended" && this._state !== "error";
2497
+ t && (this.pendingStartLoad = !1, this.setState("loading"), this.engine?.startLoad());
2498
+ try {
2499
+ await this.opts.video.play(), e && this.analyticsSession?.noteAutoplay(this.opts.video.muted ? "AUTOPLAY_RESULT_ALLOWED_MUTED" : "AUTOPLAY_RESULT_ALLOWED");
2500
+ } catch (n) {
2501
+ n instanceof DOMException && n.name === "NotAllowedError" && (this.analyticsSession?.noteAutoplay("AUTOPLAY_RESULT_BLOCKED"), e && (this.playRequested = !1), t && !this.destroyed && this._state === "loading" && this.setState("paused"), this.emit("autoplayblocked"));
2213
2502
  }
2214
2503
  }
2215
2504
  pause() {
@@ -2222,7 +2511,7 @@ var dt = class {
2222
2511
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
2223
2512
  }
2224
2513
  getStats() {
2225
- return this.engine?.getStats() ?? F;
2514
+ return this.engine?.getStats() ?? we;
2226
2515
  }
2227
2516
  getQualityLevels() {
2228
2517
  return this.engine?.getQualityLevels() ?? [];
@@ -2289,7 +2578,7 @@ var dt = class {
2289
2578
  return n.add(t), () => n.delete(t);
2290
2579
  }
2291
2580
  destroy() {
2292
- this.destroyed || (this.destroyed = !0, this.loadGen += 1, this.abort.abort(), this.stopStats(), this.stopRefresh(), this.unmountControls(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.detachPip?.(), this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.engine?.destroy(), this.engine = null, this.setState("idle"), this.clearListeners());
2581
+ this.destroyed || (this.destroyed = !0, this.loadGen += 1, this.abort.abort(), this.stopStats(), this.stopRefresh(), this.unmountControls(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.detachPip?.(), this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.engine?.destroy(), this.engine = null, this.setState("idle"), this.clearListeners());
2293
2582
  }
2294
2583
  clearListeners() {
2295
2584
  for (let e of Object.keys(this.listeners)) this.listeners[e]?.clear();
@@ -2332,7 +2621,7 @@ function $(e) {
2332
2621
  }
2333
2622
  //#endregion
2334
2623
  //#region src/embed/host.ts
2335
- function ft(e) {
2624
+ function Tt(e) {
2336
2625
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2337
2626
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2338
2627
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2391,8 +2680,8 @@ function ft(e) {
2391
2680
  }
2392
2681
  //#endregion
2393
2682
  //#region src/embed/page.ts
2394
- var pt = /^[A-Za-z0-9:_.-]{1,128}$/;
2395
- function mt() {
2683
+ var Et = /^[A-Za-z0-9:_.-]{1,128}$/;
2684
+ function Dt() {
2396
2685
  let e = window.HAVIK_EMBED_CONFIG;
2397
2686
  if (e) return {
2398
2687
  baseUrl: e.baseUrl ?? "",
@@ -2414,9 +2703,9 @@ function mt() {
2414
2703
  waitForLive: !0
2415
2704
  };
2416
2705
  }
2417
- async function ht() {
2418
- let e = mt();
2419
- _t();
2706
+ async function Ot() {
2707
+ let e = Dt();
2708
+ At();
2420
2709
  let t = document.createElement("video");
2421
2710
  t.setAttribute("playsinline", ""), t.muted = e.muted ?? !0, t.style.cssText = "width:100%;height:100%;object-fit:contain;background:#000;aspect-ratio:auto", document.body.appendChild(t);
2422
2711
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2471,12 +2760,12 @@ async function ht() {
2471
2760
  code: e.code,
2472
2761
  message: e.message
2473
2762
  })), e.on("autoplayblocked", () => {
2474
- i({ type: "oddin:autoplayblocked" }), gt(t, e);
2763
+ i({ type: "oddin:autoplayblocked" }), kt(t, e);
2475
2764
  });
2476
2765
  }, s = async (r) => {
2477
2766
  a?.destroy(), a = null;
2478
2767
  try {
2479
- let n = await Q({
2768
+ let n = await Ct({
2480
2769
  video: t,
2481
2770
  baseUrl: e.baseUrl,
2482
2771
  matchUrn: r,
@@ -2537,7 +2826,7 @@ async function ht() {
2537
2826
  a?.retry();
2538
2827
  break;
2539
2828
  case "oddin:load":
2540
- typeof e.matchUrn == "string" && pt.test(e.matchUrn) && s(e.matchUrn);
2829
+ typeof e.matchUrn == "string" && Et.test(e.matchUrn) && s(e.matchUrn);
2541
2830
  break;
2542
2831
  case "oddin:destroy": c();
2543
2832
  }
@@ -2549,18 +2838,18 @@ async function ht() {
2549
2838
  protocol: 1
2550
2839
  });
2551
2840
  }
2552
- function gt(e, t) {
2841
+ function kt(e, t) {
2553
2842
  if (document.getElementById("havik-tap")) return;
2554
2843
  let n = document.createElement("button");
2555
2844
  n.id = "havik-tap", n.textContent = "▶ Tap to play", n.style.cssText = "position:fixed;inset:0;width:100%;height:100%;border:0;background:rgba(0,0,0,.55);color:#fff;font:600 18px system-ui,sans-serif;cursor:pointer;z-index:9", n.addEventListener("click", () => {
2556
2845
  n.remove(), t.play();
2557
2846
  }), document.body.appendChild(n);
2558
2847
  }
2559
- function _t() {
2848
+ function At() {
2560
2849
  let e = document.createElement("style");
2561
2850
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2562
2851
  }
2563
2852
  //#endregion
2564
- export { W as ODDIN_THEME, n as PlaybackError, K as applyTheme, ht as bootEmbed, he as clearDeviceId, Q as createPlayer, O as deriveEventsBaseUrl, xe as detectDrmSupport, T as enterVideoFullscreen, E as exitVideoFullscreen, le as fetchCatalog, de as fetchTournament, pe as flattenMatches, j as getDeviceId, Ce as isLowLatencyManifest, $ as isOddinMessage, _e as licenseRequestHeaders, ft as mountEmbed, v as pollToLive, b as resolveStream, D as subscribeLiveState, w as videoFullscreenActive, me as watchStatus };
2853
+ export { G as ODDIN_THEME, n as PlaybackError, q as applyTheme, Ot as bootEmbed, he as clearDeviceId, Ct as createPlayer, O as deriveEventsBaseUrl, xe as detectDrmSupport, T as enterVideoFullscreen, E as exitVideoFullscreen, le as fetchCatalog, de as fetchTournament, pe as flattenMatches, j as getDeviceId, Ce as isLowLatencyManifest, $ as isOddinMessage, _e as licenseRequestHeaders, Tt as mountEmbed, v as pollToLive, b as resolveStream, D as subscribeLiveState, w as videoFullscreenActive, me as watchStatus };
2565
2854
 
2566
2855
  //# sourceMappingURL=index.js.map