@oddin-gg/havik-player 2.19.0 → 2.20.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
@@ -7,8 +7,9 @@ var n = class extends Error {
7
7
  requestId;
8
8
  serverCode;
9
9
  liveStartsAtMs;
10
+ country;
10
11
  constructor(e, t, n, r) {
11
- super(n), this.name = "PlaybackError", this.code = e, this.httpStatus = t, this.retryAfterMs = r?.retryAfterMs, this.requestId = r?.requestId, this.serverCode = r?.serverCode, this.liveStartsAtMs = r?.liveStartsAtMs, r?.cause !== void 0 && (this.cause = r.cause);
12
+ super(n), this.name = "PlaybackError", this.code = e, this.httpStatus = t, this.retryAfterMs = r?.retryAfterMs, this.requestId = r?.requestId, this.serverCode = r?.serverCode, this.liveStartsAtMs = r?.liveStartsAtMs, this.country = r?.country, r?.cause !== void 0 && (this.cause = r.cause);
12
13
  }
13
14
  };
14
15
  function r(e) {
@@ -20,6 +21,7 @@ function r(e) {
20
21
  case 410: return "GONE";
21
22
  case 425: return "TOO_EARLY";
22
23
  case 429: return "RATE_LIMITED";
24
+ case 451: return "GEO_DENIED";
23
25
  case 503: return "UNAVAILABLE";
24
26
  default: return "INTERNAL";
25
27
  }
@@ -215,19 +217,21 @@ function y(e) {
215
217
  return e.waitForLive ? de(t, e.waitForLive, e.signal) : t();
216
218
  }
217
219
  async function fe(e) {
218
- let t = f(e.headers.get("retry-after")), i = e.headers.get("x-request-id") ?? e.headers.get("x-amzn-requestid") ?? void 0, a = e.statusText || `HTTP ${e.status}`, o, s;
220
+ let t = f(e.headers.get("retry-after")), i = e.headers.get("x-request-id") ?? e.headers.get("x-amzn-requestid") ?? void 0, a = e.statusText || `HTTP ${e.status}`, o, s, c;
219
221
  try {
220
222
  let t = await e.json();
221
223
  if (t?.error?.message && (a = t.error.message), t?.error?.code && (o = t.error.code), typeof t?.liveStartsAt == "string") {
222
224
  let e = Date.parse(t.liveStartsAt);
223
225
  Number.isFinite(e) && (s = e);
224
226
  }
227
+ e.status === 451 && typeof t?.country == "string" && /^[A-Z]{2}$/.test(t.country) && (c = t.country);
225
228
  } catch {}
226
229
  return new n(r(e.status), e.status, a, {
227
230
  retryAfterMs: t,
228
231
  requestId: i,
229
232
  serverCode: o,
230
- liveStartsAtMs: s
233
+ liveStartsAtMs: s,
234
+ country: c
231
235
  });
232
236
  }
233
237
  var pe = /* @__PURE__ */ new Set([
@@ -420,6 +424,7 @@ function xe(e) {
420
424
  //#region src/core/poll.ts
421
425
  var Se = /* @__PURE__ */ new Set([
422
426
  "NOT_FOUND",
427
+ "GEO_DENIED",
423
428
  "GONE",
424
429
  "INVALID_URN"
425
430
  ]);
@@ -606,18 +611,18 @@ function De(e, t, n = {}) {
606
611
  //#endregion
607
612
  //#region src/core/bandwidthMemory.ts
608
613
  var j = "havik.player.bandwidth", Oe = .8, ke = 2e7, Ae = 3e5;
609
- function M() {
614
+ function je() {
610
615
  return globalThis.navigator?.connection;
611
616
  }
612
- function je() {
613
- let e = M()?.type;
617
+ function Me() {
618
+ let e = je()?.type;
614
619
  return typeof e == "string" && e !== "" && e !== "unknown" ? e : "any";
615
620
  }
616
- function Me() {
617
- let e = M()?.downlink;
621
+ function Ne() {
622
+ let e = je()?.downlink;
618
623
  return typeof e == "number" && Number.isFinite(e) && e > 0 ? e * 1e6 : void 0;
619
624
  }
620
- function Ne() {
625
+ function Pe() {
621
626
  try {
622
627
  let e = globalThis.localStorage?.getItem(j);
623
628
  if (!e) return {};
@@ -627,18 +632,18 @@ function Ne() {
627
632
  return {};
628
633
  }
629
634
  }
630
- function Pe(e = Date.now()) {
631
- let t = Ne()[je()];
635
+ function Fe(e = Date.now()) {
636
+ let t = Pe()[Me()];
632
637
  if (!t || typeof t.bps != "number" || typeof t.at != "number" || !Number.isFinite(t.bps) || t.bps <= 0) return;
633
638
  let n = e - t.at;
634
639
  if (n < 0 || n > 2592e5) return;
635
- let r = Me(), i = r === void 0 ? t.bps : Math.min(t.bps, r);
640
+ let r = Ne(), i = r === void 0 ? t.bps : Math.min(t.bps, r);
636
641
  return Math.round(Math.min(ke, Math.max(Ae, i * Oe)));
637
642
  }
638
- function Fe(e, t = Date.now()) {
643
+ function Ie(e, t = Date.now()) {
639
644
  if (!Number.isFinite(e) || e <= 0) return;
640
- let n = Ne();
641
- n[je()] = {
645
+ let n = Pe();
646
+ n[Me()] = {
642
647
  bps: Math.round(e),
643
648
  at: t
644
649
  };
@@ -646,17 +651,17 @@ function Fe(e, t = Date.now()) {
646
651
  globalThis.localStorage?.setItem(j, JSON.stringify(n));
647
652
  } catch {}
648
653
  }
649
- function Ie() {
654
+ function Le() {
650
655
  try {
651
656
  globalThis.localStorage?.removeItem(j);
652
657
  } catch {}
653
658
  }
654
659
  //#endregion
655
660
  //#region src/core/drm/cdm.ts
656
- var Le = /* @__PURE__ */ new WeakMap();
657
- function Re(e, t) {
658
- let n = Le.get(e);
659
- n || (n = /* @__PURE__ */ new Map(), Le.set(e, n));
661
+ var Re = /* @__PURE__ */ new WeakMap();
662
+ function ze(e, t) {
663
+ let n = Re.get(e);
664
+ n || (n = /* @__PURE__ */ new Map(), Re.set(e, n));
660
665
  let r = n.get(t.keySystem);
661
666
  if (r) return r;
662
667
  let i = t.createMediaKeys().catch((e) => {
@@ -664,22 +669,22 @@ function Re(e, t) {
664
669
  });
665
670
  return n.set(t.keySystem, i), i;
666
671
  }
667
- function N() {
672
+ function M() {
668
673
  return typeof navigator < "u" && typeof navigator.requestMediaKeySystemAccess == "function";
669
674
  }
670
- var P = ["SW_SECURE_DECODE", "SW_SECURE_CRYPTO"];
671
- function ze(e) {
675
+ var N = ["SW_SECURE_DECODE", "SW_SECURE_CRYPTO"];
676
+ function Be(e) {
672
677
  return e.startsWith("com.widevine");
673
678
  }
674
- function F(e, t) {
675
- if (!ze(e)) return t;
679
+ function P(e, t) {
680
+ if (!Be(e)) return t;
676
681
  let n = (e, t) => {
677
682
  let n = e.videoCapabilities;
678
683
  return !n?.length || !n.some((e) => e.robustness) ? !1 : n.every((e) => {
679
- let n = P.indexOf(e.robustness);
680
- return n === -1 || P.indexOf(t) > n;
684
+ let n = N.indexOf(e.robustness);
685
+ return n === -1 || N.indexOf(t) > n;
681
686
  });
682
- }, r = P.flatMap((e) => t.filter((t) => n(t, e)).map((t) => ({
687
+ }, r = N.flatMap((e) => t.filter((t) => n(t, e)).map((t) => ({
683
688
  ...t,
684
689
  videoCapabilities: t.videoCapabilities?.map((t) => ({
685
690
  ...t,
@@ -688,53 +693,53 @@ function F(e, t) {
688
693
  })));
689
694
  return [...t, ...r];
690
695
  }
691
- function Be(e) {
692
- return (t, n) => N() ? navigator.requestMediaKeySystemAccess(t, F(t, n)).then((t) => ({
696
+ function Ve(e) {
697
+ return (t, n) => M() ? navigator.requestMediaKeySystemAccess(t, P(t, n)).then((t) => ({
693
698
  keySystem: t.keySystem,
694
699
  getConfiguration: () => t.getConfiguration(),
695
- createMediaKeys: () => Re(e, t)
700
+ createMediaKeys: () => ze(e, t)
696
701
  })) : Promise.reject(/* @__PURE__ */ Error("navigator.requestMediaKeySystemAccess is unavailable — EME needs a secure context (https, or localhost)"));
697
702
  }
698
- var I = {
703
+ var F = {
699
704
  video: "SW_SECURE_DECODE",
700
705
  audio: "SW_SECURE_CRYPTO",
701
706
  scheme: "cbcs"
702
707
  };
703
- function Ve(e) {
704
- if (!N()) return;
708
+ function He(e) {
709
+ if (!M()) return;
705
710
  let t = (e, t) => ({
706
711
  contentType: e,
707
712
  robustness: t,
708
- encryptionScheme: I.scheme
713
+ encryptionScheme: F.scheme
709
714
  });
710
- Be(e)("com.widevine.alpha", [{
715
+ Ve(e)("com.widevine.alpha", [{
711
716
  initDataTypes: ["cenc"],
712
717
  distinctiveIdentifier: "optional",
713
718
  persistentState: "optional",
714
719
  sessionTypes: ["temporary"],
715
- audioCapabilities: [t("audio/mp4; codecs=\"mp4a.40.2\"", I.audio)],
716
- videoCapabilities: [t("video/mp4; codecs=\"avc1.42e01e\"", I.video)]
720
+ audioCapabilities: [t("audio/mp4; codecs=\"mp4a.40.2\"", F.audio)],
721
+ videoCapabilities: [t("video/mp4; codecs=\"avc1.42e01e\"", F.video)]
717
722
  }]).then((e) => e.createMediaKeys()).catch(() => {});
718
723
  }
719
- function L(e) {
720
- let t = R(e);
724
+ function I(e) {
725
+ let t = L(e);
721
726
  if (/^avc[13]/.test(t)) return "avc";
722
727
  if (/^(hvc1|hev1)/.test(t)) return "hevc";
723
728
  if (/^(dvh1|dvhe)/.test(t)) return "dv";
724
729
  if (t.startsWith("av01")) return "av1";
725
730
  if (/^vp0?9/.test(t)) return "vp9";
726
731
  }
727
- function R(e) {
732
+ function L(e) {
728
733
  return (e ?? "").trim().toLowerCase();
729
734
  }
730
- var He = {
735
+ var Ue = {
731
736
  hevc: "hvc1.1.6.L93.90",
732
737
  dv: "dvh1.05.06",
733
738
  av1: "av01.0.05M.08",
734
739
  vp9: "vp09.00.10.08"
735
- }, Ue = /* @__PURE__ */ new Map(), z = /* @__PURE__ */ new Map();
736
- function We(e) {
737
- let t = R(e), n = Ue.get(t);
740
+ }, We = /* @__PURE__ */ new Map(), R = /* @__PURE__ */ new Map();
741
+ function Ge(e) {
742
+ let t = L(e), n = We.get(t);
738
743
  if (n) return n;
739
744
  let r = {
740
745
  initDataTypes: ["cenc"],
@@ -743,32 +748,32 @@ function We(e) {
743
748
  sessionTypes: ["temporary"],
744
749
  videoCapabilities: [{
745
750
  contentType: `video/mp4; codecs="${e.trim()}"`,
746
- robustness: I.video,
747
- encryptionScheme: I.scheme
751
+ robustness: F.video,
752
+ encryptionScheme: F.scheme
748
753
  }]
749
754
  }, i = (async () => {
750
755
  try {
751
- return await navigator.requestMediaKeySystemAccess("com.widevine.alpha", F("com.widevine.alpha", [r])), !0;
756
+ return await navigator.requestMediaKeySystemAccess("com.widevine.alpha", P("com.widevine.alpha", [r])), !0;
752
757
  } catch {
753
758
  return !1;
754
759
  }
755
- })().then((e) => (z.set(t, e), e));
756
- return Ue.set(t, i), i;
760
+ })().then((e) => (R.set(t, e), e));
761
+ return We.set(t, i), i;
757
762
  }
758
- function Ge() {
759
- if (N()) for (let e of Object.values(He)) We(e);
763
+ function Ke() {
764
+ if (M()) for (let e of Object.values(Ue)) Ge(e);
760
765
  }
761
- function Ke(e) {
762
- let t = L(e);
763
- if (t) return t === "avc" ? !0 : z.get(R(e)) ?? z.get(R(He[t]));
766
+ function qe(e) {
767
+ let t = I(e);
768
+ if (t) return t === "avc" ? !0 : R.get(L(e)) ?? R.get(L(Ue[t]));
764
769
  }
765
770
  //#endregion
766
771
  //#region src/core/drm/support.ts
767
- var qe = 5e3, Je = "video/mp4; codecs=\"avc1.42E01E\"", Ye = "audio/mp4; codecs=\"mp4a.40.2\"", Xe = {
772
+ var Je = 5e3, Ye = "video/mp4; codecs=\"avc1.42E01E\"", Xe = "audio/mp4; codecs=\"mp4a.40.2\"", Ze = {
768
773
  widevine: ["com.widevine.alpha"],
769
774
  fairplay: ["com.apple.fps"]
770
775
  };
771
- function Ze(e) {
776
+ function Qe(e) {
772
777
  return e === "fairplay" ? [{
773
778
  initDataTypes: [
774
779
  "sinf",
@@ -776,30 +781,30 @@ function Ze(e) {
776
781
  "cenc"
777
782
  ],
778
783
  videoCapabilities: [{
779
- contentType: Je,
784
+ contentType: Ye,
780
785
  robustness: "",
781
786
  encryptionScheme: "cbcs"
782
787
  }],
783
788
  audioCapabilities: [{
784
- contentType: Ye,
789
+ contentType: Xe,
785
790
  robustness: "",
786
791
  encryptionScheme: "cbcs"
787
792
  }]
788
- }] : F("com.widevine.alpha", [{
793
+ }] : P("com.widevine.alpha", [{
789
794
  initDataTypes: ["cenc"],
790
795
  videoCapabilities: [{
791
- contentType: Je,
792
- robustness: I.video,
793
- encryptionScheme: I.scheme
796
+ contentType: Ye,
797
+ robustness: F.video,
798
+ encryptionScheme: F.scheme
794
799
  }],
795
800
  audioCapabilities: [{
796
- contentType: Ye,
797
- robustness: I.audio,
798
- encryptionScheme: I.scheme
801
+ contentType: Xe,
802
+ robustness: F.audio,
803
+ encryptionScheme: F.scheme
799
804
  }]
800
805
  }]);
801
806
  }
802
- function Qe(e, t) {
807
+ function $e(e, t) {
803
808
  return new Promise((n, r) => {
804
809
  let i = setTimeout(() => r(/* @__PURE__ */ Error("probe-timeout")), t);
805
810
  e.then((e) => {
@@ -809,8 +814,8 @@ function Qe(e, t) {
809
814
  });
810
815
  });
811
816
  }
812
- async function $e(e = {}) {
813
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? qe, r = {};
817
+ async function et(e = {}) {
818
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? Je, r = {};
814
819
  if (typeof window < "u" && window.isSecureContext === !1) return {
815
820
  verdict: "insecure-context",
816
821
  detail: r
@@ -822,7 +827,7 @@ async function $e(e = {}) {
822
827
  };
823
828
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
824
829
  for (let e of t) {
825
- let t = Xe[e];
830
+ let t = Ze[e];
826
831
  if (!t) {
827
832
  r[e] = "unknown system";
828
833
  continue;
@@ -833,7 +838,7 @@ async function $e(e = {}) {
833
838
  break;
834
839
  }
835
840
  try {
836
- return await Qe((await Qe(i(n, Ze(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
841
+ return await $e((await $e(i(n, Qe(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
837
842
  verdict: "supported",
838
843
  system: e,
839
844
  detail: r
@@ -857,24 +862,24 @@ async function $e(e = {}) {
857
862
  }
858
863
  //#endregion
859
864
  //#region src/core/manifest.ts
860
- var et = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
861
- function tt(e) {
862
- return et.test(e);
865
+ var tt = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
866
+ function nt(e) {
867
+ return tt.test(e);
863
868
  }
864
869
  //#endregion
865
870
  //#region src/core/env.ts
866
- var nt = {
871
+ var rt = {
867
872
  integration: "https://feed-dev.oddin-video.gg",
868
873
  production: "https://feed.oddin-video.gg"
869
874
  };
870
- function rt(e) {
871
- let t = Object.prototype.hasOwnProperty.call(nt, e) ? nt[e] : void 0;
875
+ function it(e) {
876
+ let t = Object.prototype.hasOwnProperty.call(rt, e) ? rt[e] : void 0;
872
877
  if (!t) throw new n("INTERNAL", 0, `unknown env ${JSON.stringify(e)} — expected 'integration' or 'production'`);
873
878
  return t;
874
879
  }
875
880
  //#endregion
876
881
  //#region src/core/stats.ts
877
- var it = { droppedFrames: 0 };
882
+ var z = { droppedFrames: 0 };
878
883
  function at(e) {
879
884
  try {
880
885
  return new URL(e).host;
@@ -1551,7 +1556,7 @@ var _n = class {
1551
1556
  });
1552
1557
  return;
1553
1558
  }
1554
- e.drmEnabled && o && !this.deps.hlsConfig?.requestMediaKeySystemAccessFunc && (Ve(r), Ge()), this.startHls(), t !== !1 && typeof document < "u" && (this.onVisibility = () => {
1559
+ e.drmEnabled && o && !this.deps.hlsConfig?.requestMediaKeySystemAccessFunc && (He(r), Ke()), this.startHls(), t !== !1 && typeof document < "u" && (this.onVisibility = () => {
1555
1560
  let e = this.hls;
1556
1561
  if (document.visibilityState !== "visible" || !e) return;
1557
1562
  let t = e.liveSyncPosition;
@@ -1641,12 +1646,12 @@ var _n = class {
1641
1646
  emeEnabled: !0,
1642
1647
  drmSystems: { ...this.licenseUrl ? { "com.widevine.alpha": { licenseUrl: this.licenseUrl } } : {} },
1643
1648
  drmSystemOptions: {
1644
- videoRobustness: I.video,
1645
- audioRobustness: I.audio,
1646
- videoEncryptionScheme: I.scheme,
1647
- audioEncryptionScheme: I.scheme
1649
+ videoRobustness: F.video,
1650
+ audioRobustness: F.audio,
1651
+ videoEncryptionScheme: F.scheme,
1652
+ audioEncryptionScheme: F.scheme
1648
1653
  },
1649
- ...N() ? { requestMediaKeySystemAccessFunc: Be(t) } : {},
1654
+ ...M() ? { requestMediaKeySystemAccessFunc: Ve(t) } : {},
1650
1655
  licenseXhrSetup: (e, t) => {
1651
1656
  let o = performance.now(), s = () => {
1652
1657
  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, e.addEventListener("loadend", () => {
@@ -1794,9 +1799,9 @@ var _n = class {
1794
1799
  this.ladderHeights = t.length ? [...new Set(t)].sort((e, t) => e - t) : void 0, this.ladderTopBitrateKbps = e.levels.length ? Math.round(Math.max(...e.levels.map((e) => e.bitrate)) / 1e3) : 0;
1795
1800
  }
1796
1801
  dropLevelsWidevineCannotPlay(e) {
1797
- this.removeLevelsWhere(e, (e) => Ke(e) !== !0);
1798
- let t = new Set(e.levels.map((e) => e.videoCodec).filter((e) => !!e && L(e) !== "avc" && !!L(e)));
1799
- for (let n of t) We(n).then((t) => {
1802
+ this.removeLevelsWhere(e, (e) => qe(e) !== !0);
1803
+ let t = new Set(e.levels.map((e) => e.videoCodec).filter((e) => !!e && I(e) !== "avc" && !!I(e)));
1804
+ for (let n of t) Ge(n).then((t) => {
1800
1805
  t || this.hls !== e || this.removeLevelsWhere(e, (e) => e === n) && (this.measureLadder(e), this.applySizeCap());
1801
1806
  });
1802
1807
  }
@@ -1804,10 +1809,10 @@ var _n = class {
1804
1809
  let n = e.levels ?? [], r = [], i = /* @__PURE__ */ new Set();
1805
1810
  if (n.forEach((e, n) => {
1806
1811
  let a = e.videoCodec;
1807
- a && L(a) && t(a) && (r.push(n), i.add(a));
1812
+ a && I(a) && t(a) && (r.push(n), i.add(a));
1808
1813
  }), !r.length) return !1;
1809
1814
  let a = [...i].join(", ");
1810
- if (!n.some((e, t) => r.includes(t) ? !1 : !L(e.videoCodec) || Ke(e.videoCodec) === !0)) return console.warn(`havik-player: every rendition is ${a}, which Widevine may not play on this browser; keeping them`), !1;
1815
+ if (!n.some((e, t) => r.includes(t) ? !1 : !I(e.videoCodec) || qe(e.videoCodec) === !0)) return console.warn(`havik-player: every rendition is ${a}, which Widevine may not play on this browser; keeping them`), !1;
1811
1816
  for (let t = r.length - 1; t >= 0; --t) e.removeLevel(r[t]);
1812
1817
  return console.info(`havik-player: skipped ${r.length} rendition(s) Widevine cannot play on this browser (${a})`), !0;
1813
1818
  }
@@ -2047,14 +2052,14 @@ var _n = class {
2047
2052
  } catch {}
2048
2053
  }
2049
2054
  startEstimate() {
2050
- return this.deps.rememberBandwidth === !1 ? Pt : Pe() ?? Pt;
2055
+ return this.deps.rememberBandwidth === !1 ? Pt : Fe() ?? Pt;
2051
2056
  }
2052
2057
  maybeRememberBandwidth(e) {
2053
2058
  if (this.deps.rememberBandwidth === !1 || this.bwFragsLoaded < Ft) return;
2054
2059
  let t = Date.now();
2055
2060
  if (!e && t - this.bwSavedAt < It) return;
2056
2061
  let n = this.hls?.bandwidthEstimate;
2057
- typeof n != "number" || !Number.isFinite(n) || n <= 0 || (this.bwSavedAt = t, Fe(n));
2062
+ typeof n != "number" || !Number.isFinite(n) || n <= 0 || (this.bwSavedAt = t, Ie(n));
2058
2063
  }
2059
2064
  destroy() {
2060
2065
  if (this.maybeRememberBandwidth(!0), this.detachFrames?.(), this.detachFrames = null, this.prefetch?.destroy(), this.prefetch = null, this.clearRecoveryReset(), this.capTimer &&= (clearTimeout(this.capTimer), null), this.capObserver &&= (this.capObserver.disconnect(), null), this.unwatchDprChanges(), this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.onVisibility && typeof document < "u" && (document.removeEventListener("visibilitychange", this.onVisibility), this.onVisibility = null), this.onSeeking &&= (this.deps.video.removeEventListener("seeking", this.onSeeking), null), this.onTimeUpdate &&= (this.deps.video.removeEventListener("timeupdate", this.onTimeUpdate), null), this.onPause &&= (this.deps.video.removeEventListener("pause", this.onPause), null), this.onPlay &&= (this.deps.video.removeEventListener("play", this.onPlay), null), this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null), this.networkRetryTimer &&= (clearTimeout(this.networkRetryTimer), null), this.licenseRetryTimer &&= (clearTimeout(this.licenseRetryTimer), null), this.hls) {
@@ -2160,6 +2165,7 @@ function Dn(e, t) {
2160
2165
  var On = /* @__PURE__ */ new Set([
2161
2166
  "INVALID_URN",
2162
2167
  "NOT_FOUND",
2168
+ "GEO_DENIED",
2163
2169
  "GONE",
2164
2170
  "TOO_EARLY",
2165
2171
  "UNAVAILABLE",
@@ -2623,7 +2629,7 @@ async function Vn() {
2623
2629
  }
2624
2630
  //#endregion
2625
2631
  //#region src/core/version.ts
2626
- var Hn = "2.19.0", Un = {
2632
+ var Hn = "2.20.0", Un = {
2627
2633
  accent: "#E1B600",
2628
2634
  accentText: "#1B1C23",
2629
2635
  background: "#0e0f13",
@@ -2646,7 +2652,7 @@ var Hn = "2.19.0", Un = {
2646
2652
  radius: "--havik-radius",
2647
2653
  fontFamily: "--havik-font"
2648
2654
  };
2649
- function Z(e, t) {
2655
+ function Gn(e, t) {
2650
2656
  let n = {
2651
2657
  ...Un,
2652
2658
  ...t
@@ -2656,12 +2662,12 @@ function Z(e, t) {
2656
2662
  }
2657
2663
  //#endregion
2658
2664
  //#region src/ui/styles.ts
2659
- var Gn = "havik-player-styles", Kn = "\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 touch-action:manipulation}\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/* Absolutely centred, NOT a flex sibling of .havik-center: the hidden center\n button (.havik-gone) still occupies its 74px in the row, which pushed an\n in-flow spinner 37px right of centre. */\n.havik-spinner{position:absolute;inset:0;margin:auto;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", qn = /* @__PURE__ */ new WeakMap();
2665
+ var Z = "havik-player-styles", Kn = "\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 touch-action:manipulation}\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/* Absolutely centred, NOT a flex sibling of .havik-center: the hidden center\n button (.havik-gone) still occupies its 74px in the row, which pushed an\n in-flow spinner 37px right of centre. */\n.havik-spinner{position:absolute;inset:0;margin:auto;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", qn = /* @__PURE__ */ new WeakMap();
2660
2666
  function Jn(e = document, t) {
2661
- let n = e.querySelector(`style#${Gn}`);
2667
+ let n = e.querySelector(`style#${Z}`);
2662
2668
  if (n && (!t || qn.get(n) === t)) return;
2663
2669
  let r = e.createElement("style");
2664
- r.id = Gn, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = Kn, qn.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2670
+ r.id = Z, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = Kn, qn.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2665
2671
  }
2666
2672
  //#endregion
2667
2673
  //#region src/ui/icons.ts
@@ -2686,13 +2692,18 @@ function Q(e) {
2686
2692
  var Xn = 3e3, Zn = 250, Qn = /* @__PURE__ */ new Set([
2687
2693
  "GONE",
2688
2694
  "NOT_FOUND",
2695
+ "GEO_DENIED",
2689
2696
  "INVALID_URN",
2690
2697
  "UNAUTHORIZED"
2691
2698
  ]);
2692
2699
  function $n(e) {
2693
- return new er(e);
2700
+ let t = "This match isn't available in your country";
2701
+ return e ? `${t} (${e})` : t;
2702
+ }
2703
+ function er(e) {
2704
+ return new tr(e);
2694
2705
  }
2695
- var er = class {
2706
+ var tr = class {
2696
2707
  root;
2697
2708
  video;
2698
2709
  player;
@@ -2846,7 +2857,7 @@ var er = class {
2846
2857
  this.statusOverlays && (e === "ended" ? t = "ended" : e === "error" ? t = "error" : e === "waiting" ? t = "waiting" : !this.hasPlayed && (e === "idle" || e === "paused") && (t = "preplay")), this.renderStatus(t);
2847
2858
  }
2848
2859
  statusMessageFor(e) {
2849
- return e === "ended" ? this.player.endedReason === "interrupted" ? this.interruptedMessage : this.endedMessage : e === "waiting" ? this.hasPlayed ? "Reconnecting — please stand by" : "Starting soon…" : e === "error" ? this.errorMessage ?? this.lastError?.message ?? "Playback unavailable" : null;
2860
+ return e === "ended" ? this.player.endedReason === "interrupted" ? this.interruptedMessage : this.endedMessage : e === "waiting" ? this.hasPlayed ? "Reconnecting — please stand by" : "Starting soon…" : e === "error" ? this.errorMessage === void 0 ? this.lastError?.code === "GEO_DENIED" ? $n(this.lastError.country) : this.lastError?.message ?? "Playback unavailable" : this.errorMessage : null;
2850
2861
  }
2851
2862
  renderStatus(e) {
2852
2863
  let t = this.statusMessageFor(e);
@@ -2902,7 +2913,7 @@ var er = class {
2902
2913
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
2903
2914
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
2904
2915
  }
2905
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${tr(this.video.currentTime)} / ${tr(this.video.duration)}`);
2916
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${nr(this.video.currentTime)} / ${nr(this.video.duration)}`);
2906
2917
  }
2907
2918
  syncLive() {
2908
2919
  let e = this.player.getStats(), t = e.isLive !== !1, n = this.player.state === "ended", r = !t && !this.streamEnded();
@@ -2952,17 +2963,17 @@ var er = class {
2952
2963
  renderQualityMenu() {
2953
2964
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
2954
2965
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
2955
- for (let n of e) this.menu.append(this.menuItem(nr(n), t === n.index, () => this.player.setQuality(n.index)));
2966
+ for (let n of e) this.menu.append(this.menuItem(rr(n), t === n.index, () => this.player.setQuality(n.index)));
2956
2967
  }
2957
2968
  renderAudioMenu() {
2958
2969
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
2959
2970
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
2960
- for (let t of e) this.menu.append(this.menuItem(rr(t), t.default, () => this.player.setAudioTrack(t.id)));
2971
+ for (let t of e) this.menu.append(this.menuItem(ir(t), t.default, () => this.player.setAudioTrack(t.id)));
2961
2972
  }
2962
2973
  renderCcMenu() {
2963
2974
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
2964
2975
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
2965
- for (let t of e) this.menu.append(this.menuItem(ir(t), !1, () => this.player.setTextTrack(t.id)));
2976
+ for (let t of e) this.menu.append(this.menuItem(ar(t), !1, () => this.player.setTextTrack(t.id)));
2966
2977
  }
2967
2978
  onTracksChanged() {
2968
2979
  this.openMenu && this.renderMenu(this.openMenu);
@@ -3067,41 +3078,41 @@ var er = class {
3067
3078
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
3068
3079
  }
3069
3080
  };
3070
- function tr(e) {
3081
+ function nr(e) {
3071
3082
  (!Number.isFinite(e) || e < 0) && (e = 0);
3072
3083
  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");
3073
3084
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
3074
3085
  }
3075
- function nr(e) {
3086
+ function rr(e) {
3076
3087
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
3077
3088
  }
3078
- function rr(e) {
3089
+ function ir(e) {
3079
3090
  return e.name || e.lang || `Track ${e.id}`;
3080
3091
  }
3081
- function ir(e) {
3092
+ function ar(e) {
3082
3093
  return e.name || e.lang || `Track ${e.id}`;
3083
3094
  }
3084
3095
  //#endregion
3085
3096
  //#region src/managed/index.ts
3086
- var ar = 6e3, or = 45e3, sr = 2e3, cr = 3, lr = 5e3, ur = 2e3, dr = 48e4, fr = "keySystemLicenseRequestFailed";
3087
- function pr(e) {
3097
+ var or = 6e3, sr = 45e3, cr = 2e3, lr = 3, ur = 5e3, dr = 2e3, fr = 48e4, pr = "keySystemLicenseRequestFailed";
3098
+ function mr(e) {
3088
3099
  if (e.env !== void 0 && e.baseUrl !== void 0) throw new n("INTERNAL", 0, `createPlayer: pass env OR baseUrl, not both (got env=${JSON.stringify(e.env)} and baseUrl=${JSON.stringify(e.baseUrl)})`);
3089
- if (e.env !== void 0) return rt(e.env);
3100
+ if (e.env !== void 0) return it(e.env);
3090
3101
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: env or baseUrl is required (env: 'integration' | 'production')");
3091
3102
  return e.baseUrl;
3092
3103
  }
3093
- async function mr(e) {
3104
+ async function hr(e) {
3094
3105
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
3095
- let t = pr(e);
3106
+ let t = mr(e);
3096
3107
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
3097
3108
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
3098
- let r = new hr({
3109
+ let r = new gr({
3099
3110
  ...e,
3100
3111
  baseUrl: t
3101
3112
  });
3102
3113
  return await r.start(), r;
3103
3114
  }
3104
- var hr = class {
3115
+ var gr = class {
3105
3116
  opts;
3106
3117
  abort = new AbortController();
3107
3118
  listeners = {};
@@ -3196,7 +3207,7 @@ var hr = class {
3196
3207
  setTimeout(() => {
3197
3208
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
3198
3209
  phase: i ? "unavailable" : "tooEarly",
3199
- retryInMs: or,
3210
+ retryInMs: sr,
3200
3211
  attempt: 1,
3201
3212
  elapsedMs: 0,
3202
3213
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -3235,13 +3246,13 @@ var hr = class {
3235
3246
  !a && !p() && h();
3236
3247
  }, e);
3237
3248
  };
3238
- _(ar), this.armingSub = E({
3249
+ _(or), this.armingSub = E({
3239
3250
  eventsBaseUrl: t,
3240
3251
  matchUrn: this.opts.matchUrn,
3241
3252
  credential: this.opts.credential,
3242
- onAlive: () => _(or),
3253
+ onAlive: () => _(sr),
3243
3254
  onState: (e) => {
3244
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * sr);
3255
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * cr);
3245
3256
  },
3246
3257
  onError: () => h()
3247
3258
  });
@@ -3305,7 +3316,7 @@ var hr = class {
3305
3316
  this.destroyed || r !== this.loadGen || this.emit("warning", new n("INTERNAL", 0, e));
3306
3317
  }, 0),
3307
3318
  sample: () => {
3308
- let e = this.engine?.getStats() ?? it, t = c.buffered.length ? Math.max(0, c.buffered.end(c.buffered.length - 1) - c.currentTime) : 0;
3319
+ let e = this.engine?.getStats() ?? z, t = c.buffered.length ? Math.max(0, c.buffered.end(c.buffered.length - 1) - c.currentTime) : 0;
3309
3320
  return {
3310
3321
  ...e,
3311
3322
  bufferedMs: Math.round(t * 1e3),
@@ -3412,7 +3423,7 @@ var hr = class {
3412
3423
  r = 0, i = !1;
3413
3424
  },
3414
3425
  onError: () => {
3415
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= cr && (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.`))));
3426
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= lr && (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.`))));
3416
3427
  }
3417
3428
  });
3418
3429
  }
@@ -3426,7 +3437,7 @@ var hr = class {
3426
3437
  return;
3427
3438
  }
3428
3439
  let e = this.hiddenAtMs;
3429
- this.hiddenAtMs = null, e != null && Date.now() - e >= lr && this.resyncLiveState();
3440
+ this.hiddenAtMs = null, e != null && Date.now() - e >= ur && this.resyncLiveState();
3430
3441
  }, this.onReturnPageShow = (e) => {
3431
3442
  e.persisted && this.resyncLiveState();
3432
3443
  }, document.addEventListener("visibilitychange", this.onReturnVisible), typeof window < "u" && window.addEventListener("pageshow", this.onReturnPageShow));
@@ -3458,7 +3469,7 @@ var hr = class {
3458
3469
  scheduleRejoin(e) {
3459
3470
  this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
3460
3471
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
3461
- }, Math.random() * sr));
3472
+ }, Math.random() * cr));
3462
3473
  }
3463
3474
  cancelRejoin() {
3464
3475
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -3482,7 +3493,7 @@ var hr = class {
3482
3493
  }
3483
3494
  t.controls = !1;
3484
3495
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
3485
- r && r.insertBefore(n, t), n.appendChild(t), Z(n, this.opts.theme), this.container = n, this.controlBar = $n({
3496
+ r && r.insertBefore(n, t), n.appendChild(t), Gn(n, this.opts.theme), this.container = n, this.controlBar = er({
3486
3497
  root: n,
3487
3498
  video: t,
3488
3499
  player: this,
@@ -3520,7 +3531,7 @@ var hr = class {
3520
3531
  }
3521
3532
  startStats() {
3522
3533
  this.stopStats();
3523
- let e = Math.max(500, this.opts.statsIntervalMs ?? ur);
3534
+ let e = Math.max(500, this.opts.statsIntervalMs ?? dr);
3524
3535
  this.statsTimer = setInterval(() => {
3525
3536
  if (!this.engine) return;
3526
3537
  let e = this.engine.getStats();
@@ -3531,14 +3542,14 @@ var hr = class {
3531
3542
  this.statsTimer && clearInterval(this.statsTimer), this.statsTimer = void 0;
3532
3543
  }
3533
3544
  isStaleLicenseDenial(e) {
3534
- return this.staleLicenseReloaded || this.opts.hlsConfig?.licenseXhrSetup || this._state === "ended" || this._state === "error" || e.detail !== fr || e.error.httpStatus !== 403 ? !1 : K(this._descriptor?.drm?.widevine?.licenseUrl);
3545
+ return this.staleLicenseReloaded || this.opts.hlsConfig?.licenseXhrSetup || this._state === "ended" || this._state === "error" || e.detail !== pr || e.error.httpStatus !== 403 ? !1 : K(this._descriptor?.drm?.widevine?.licenseUrl);
3535
3546
  }
3536
3547
  scheduleLicenseRefresh() {
3537
3548
  if (this.stopRefresh(), !this._descriptor?.drmEnabled) return;
3538
3549
  typeof document < "u" && this.engine?.name === "hls" && (this.onRefocus = () => {
3539
3550
  document.visibilityState !== "visible" || this.destroyed || K(this._descriptor?.drm?.widevine?.licenseUrl) && this.refreshLicense();
3540
3551
  }, document.addEventListener("visibilitychange", this.onRefocus));
3541
- let e = this.opts.licenseRefreshMs ?? dr;
3552
+ let e = this.opts.licenseRefreshMs ?? fr;
3542
3553
  e && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
3543
3554
  }
3544
3555
  stopRefresh() {
@@ -3595,7 +3606,7 @@ var hr = class {
3595
3606
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
3596
3607
  }
3597
3608
  getStats() {
3598
- return this.engine?.getStats() ?? it;
3609
+ return this.engine?.getStats() ?? z;
3599
3610
  }
3600
3611
  getQualityLevels() {
3601
3612
  return this.engine?.getQualityLevels() ?? [];
@@ -3708,7 +3719,7 @@ function $(e) {
3708
3719
  }
3709
3720
  //#endregion
3710
3721
  //#region src/embed/host.ts
3711
- function gr(e) {
3722
+ function _r(e) {
3712
3723
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
3713
3724
  if (!e.src) throw Error("mountEmbed: opts.src is required");
3714
3725
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -3767,15 +3778,15 @@ function gr(e) {
3767
3778
  }
3768
3779
  //#endregion
3769
3780
  //#region src/embed/page.ts
3770
- var _r = /^[A-Za-z0-9:_.-]{1,128}$/;
3771
- function vr(e) {
3781
+ var vr = /^[A-Za-z0-9:_.-]{1,128}$/;
3782
+ function yr(e) {
3772
3783
  if (typeof e == "boolean") return e;
3773
3784
  if (typeof e == "object" && e) {
3774
3785
  let t = e.endpoint;
3775
3786
  return typeof t == "string" ? { endpoint: t } : {};
3776
3787
  }
3777
3788
  }
3778
- function yr() {
3789
+ function br() {
3779
3790
  let e = window.HAVIK_EMBED_CONFIG;
3780
3791
  if (e) return {
3781
3792
  baseUrl: e.baseUrl ?? "",
@@ -3785,7 +3796,7 @@ function yr() {
3785
3796
  autoplay: e.autoplay ?? !0,
3786
3797
  muted: e.muted ?? !0,
3787
3798
  waitForLive: e.waitForLive ?? !0,
3788
- analytics: vr(e.analytics)
3799
+ analytics: yr(e.analytics)
3789
3800
  };
3790
3801
  let t = new URLSearchParams(location.search);
3791
3802
  return {
@@ -3798,9 +3809,9 @@ function yr() {
3798
3809
  waitForLive: !0
3799
3810
  };
3800
3811
  }
3801
- async function br() {
3802
- let e = yr();
3803
- Sr();
3812
+ async function xr() {
3813
+ let e = br();
3814
+ Cr();
3804
3815
  let t = document.createElement("video");
3805
3816
  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);
3806
3817
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -3853,14 +3864,15 @@ async function br() {
3853
3864
  })), e.on("error", (e) => i({
3854
3865
  type: "oddin:error",
3855
3866
  code: e.code,
3856
- message: e.message
3867
+ message: e.message,
3868
+ ...e.country === void 0 ? {} : { country: e.country }
3857
3869
  })), e.on("autoplayblocked", () => {
3858
- i({ type: "oddin:autoplayblocked" }), xr(t, e);
3870
+ i({ type: "oddin:autoplayblocked" }), Sr(t, e);
3859
3871
  });
3860
3872
  }, s = async (r) => {
3861
3873
  a?.destroy(), a = null;
3862
3874
  try {
3863
- let n = await mr({
3875
+ let n = await hr({
3864
3876
  video: t,
3865
3877
  baseUrl: e.baseUrl,
3866
3878
  matchUrn: r,
@@ -3874,11 +3886,12 @@ async function br() {
3874
3886
  let i = t.parentElement;
3875
3887
  i?.classList.contains("havik-player") && (i.style.cssText = "position:absolute;inset:0;width:100%;height:100%;aspect-ratio:auto"), o(n);
3876
3888
  } catch (e) {
3877
- let t = e instanceof n ? e.code : "INTERNAL", r = e instanceof Error ? e.message : String(e);
3889
+ let t = e instanceof n ? e.code : "INTERNAL", r = e instanceof Error ? e.message : String(e), a = e instanceof n ? e.country : void 0;
3878
3890
  i({
3879
3891
  type: "oddin:error",
3880
3892
  code: t,
3881
- message: r
3893
+ message: r,
3894
+ ...a === void 0 ? {} : { country: a }
3882
3895
  });
3883
3896
  }
3884
3897
  }, c = () => {
@@ -3922,7 +3935,7 @@ async function br() {
3922
3935
  a?.retry();
3923
3936
  break;
3924
3937
  case "oddin:load":
3925
- typeof e.matchUrn == "string" && _r.test(e.matchUrn) && s(e.matchUrn);
3938
+ typeof e.matchUrn == "string" && vr.test(e.matchUrn) && s(e.matchUrn);
3926
3939
  break;
3927
3940
  case "oddin:destroy": c();
3928
3941
  }
@@ -3934,18 +3947,18 @@ async function br() {
3934
3947
  protocol: 1
3935
3948
  });
3936
3949
  }
3937
- function xr(e, t) {
3950
+ function Sr(e, t) {
3938
3951
  if (document.getElementById("havik-tap")) return;
3939
3952
  let n = document.createElement("button");
3940
3953
  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", () => {
3941
3954
  n.remove(), t.play();
3942
3955
  }), document.body.appendChild(n);
3943
3956
  }
3944
- function Sr() {
3957
+ function Cr() {
3945
3958
  let e = document.createElement("style");
3946
3959
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
3947
3960
  }
3948
3961
  //#endregion
3949
- export { Un as ODDIN_THEME, n as PlaybackError, Z as applyTheme, rt as baseUrlForEnv, br as bootEmbed, Te as clearDeviceId, Ie as clearRememberedBandwidth, mr as createPlayer, D as deriveEventsBaseUrl, $e as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, _e as fetchCatalog, ye as fetchTournament, xe as flattenMatches, C as fullscreenSupported, A as getDeviceId, tt as isLowLatencyManifest, $ as isOddinMessage, De as licenseRequestHeaders, gr as mountEmbed, de as pollToLive, y as resolveStream, E as subscribeLiveState, S as videoFullscreenActive, Ce as watchStatus };
3962
+ export { Un as ODDIN_THEME, n as PlaybackError, Gn as applyTheme, it as baseUrlForEnv, xr as bootEmbed, Te as clearDeviceId, Le as clearRememberedBandwidth, hr as createPlayer, D as deriveEventsBaseUrl, et as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, _e as fetchCatalog, ye as fetchTournament, xe as flattenMatches, C as fullscreenSupported, A as getDeviceId, nt as isLowLatencyManifest, $ as isOddinMessage, De as licenseRequestHeaders, _r as mountEmbed, de as pollToLive, y as resolveStream, E as subscribeLiveState, S as videoFullscreenActive, Ce as watchStatus };
3950
3963
 
3951
3964
  //# sourceMappingURL=index.js.map