@oddin-gg/havik-player 2.6.2 → 2.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -397,14 +397,15 @@ function he(e) {
397
397
  }
398
398
  //#endregion
399
399
  //#region src/core/events.ts
400
+ var ge = 45e3;
400
401
  function E(e) {
401
- let t = Math.max(250, e.minBackoffMs ?? 1e3), n = Math.max(t, e.maxBackoffMs ?? 15e3), r = new AbortController(), i = !1, a = (e) => new Promise((t) => {
402
- if (r.signal.aborted) return t();
402
+ let t = Math.max(250, e.minBackoffMs ?? 1e3), n = Math.max(t, e.maxBackoffMs ?? 15e3), r = e.idleTimeoutMs ?? ge, i = Number.isFinite(r) && r > 0 ? r : 0, a = new AbortController(), o = !1, l = (e) => new Promise((t) => {
403
+ if (a.signal.aborted) return t();
403
404
  let n = setTimeout(t, e);
404
- r.signal.addEventListener("abort", () => {
405
+ a.signal.addEventListener("abort", () => {
405
406
  clearTimeout(n), t();
406
407
  }, { once: !0 });
407
- }), o = (t) => {
408
+ }), u = (t) => {
408
409
  e.onAlive?.();
409
410
  let n = "message", r = [];
410
411
  for (let e of t.split("\n")) {
@@ -417,51 +418,66 @@ function E(e) {
417
418
  let t = JSON.parse(r.join("\n"));
418
419
  t && typeof t.state == "string" && e.onState(t);
419
420
  } catch {}
420
- }, l = /\r\n\r\n|\n\n|\r\r/, u = async (e, t) => {
421
- let n = e.getReader(), r = new TextDecoder(), a = "", s = !1;
421
+ }, d = /\r\n\r\n|\n\n|\r\r/, f = async (e, t, n) => {
422
+ let r = e.getReader(), a = new TextDecoder(), s = "", c = !1, l, f = () => {
423
+ i && (clearTimeout(l), l = setTimeout(() => {
424
+ n(), r.cancel().catch(() => {});
425
+ }, i));
426
+ };
422
427
  try {
423
- for (;;) {
424
- let { value: e, done: c } = await n.read();
425
- if (c) return;
426
- !s && e && e.length > 0 && (s = !0, t()), a += r.decode(e, { stream: !0 });
427
- let u;
428
- for (; (u = l.exec(a)) !== null;) o(a.slice(0, u.index)), a = a.slice(u.index + u[0].length);
429
- if (i) return;
428
+ for (f();;) {
429
+ let { value: e, done: n } = await r.read();
430
+ if (n) return;
431
+ e && e.length > 0 && (f(), c || (c = !0, t())), s += a.decode(e, { stream: !0 });
432
+ let i;
433
+ for (; (i = d.exec(s)) !== null;) u(s.slice(0, i.index)), s = s.slice(i.index + i[0].length);
434
+ if (o) return;
430
435
  }
431
436
  } finally {
432
- await n.cancel().catch(() => {});
437
+ clearTimeout(l), await r.cancel().catch(() => {});
433
438
  }
434
439
  };
435
440
  return (async () => {
436
- let o = t;
437
- for (; !i;) {
441
+ let r = t;
442
+ for (; !o;) {
443
+ let u = new AbortController(), d = () => u.abort();
444
+ a.signal.addEventListener("abort", d, { once: !0 });
445
+ let p = null, m, h = () => {
446
+ p = `no data for ${i}ms`, u.abort();
447
+ };
438
448
  try {
439
449
  let n = await s(e.credential);
440
- if (i) return;
441
- let a = c(e.eventsBaseUrl, `/v1/events/${encodeURIComponent(e.matchUrn)}`), l = await fetch(a, {
450
+ if (o) return;
451
+ let a = c(e.eventsBaseUrl, `/v1/events/${encodeURIComponent(e.matchUrn)}`);
452
+ i && (m = setTimeout(() => {
453
+ p = `no response headers within ${i}ms`, u.abort();
454
+ }, i));
455
+ let l = await fetch(a, {
442
456
  method: "GET",
443
457
  headers: {
444
458
  "x-api-key": n.apiKey,
445
459
  accept: "text/event-stream"
446
460
  },
447
- signal: r.signal,
461
+ signal: u.signal,
448
462
  cache: "no-store",
449
463
  credentials: "omit",
450
464
  mode: "cors"
451
465
  });
452
- if (i) return;
453
- !l.ok || !l.body ? e.onError?.(/* @__PURE__ */ Error(`live-state subscribe failed: HTTP ${l.status}`)) : await u(l.body, () => {
454
- o = t;
455
- });
466
+ if (clearTimeout(m), o) return;
467
+ !l.ok || !l.body ? e.onError?.(/* @__PURE__ */ Error(`live-state subscribe failed: HTTP ${l.status}`)) : await f(l.body, () => {
468
+ r = t;
469
+ }, h);
456
470
  } catch (t) {
457
- if (i || r.signal.aborted) return;
458
- e.onError?.(t);
471
+ if (o || a.signal.aborted) return;
472
+ p || e.onError?.(t);
473
+ } finally {
474
+ clearTimeout(m), a.signal.removeEventListener("abort", d), u.abort();
459
475
  }
460
- if (i) return;
461
- await a(o), o = Math.min(n, o * 2);
476
+ if (o) return;
477
+ p && e.onError?.(/* @__PURE__ */ Error(`live-state subscribe idle: ${p} — reconnecting`)), await l(r), r = Math.min(n, r * 2);
462
478
  }
463
479
  })(), { close() {
464
- i || (i = !0, r.abort());
480
+ o || (o = !0, a.abort());
465
481
  } };
466
482
  }
467
483
  function D(e) {
@@ -481,20 +497,20 @@ function A() {
481
497
  let e = globalThis.localStorage?.getItem(O);
482
498
  if (e) return k = e, e;
483
499
  } catch {}
484
- let e = _e();
500
+ let e = ve();
485
501
  k = e;
486
502
  try {
487
503
  globalThis.localStorage?.setItem(O, e);
488
504
  } catch {}
489
505
  return e;
490
506
  }
491
- function ge() {
507
+ function _e() {
492
508
  k = void 0;
493
509
  try {
494
510
  globalThis.localStorage?.removeItem(O);
495
511
  } catch {}
496
512
  }
497
- function _e() {
513
+ function ve() {
498
514
  let e = globalThis.crypto;
499
515
  if (e && typeof e.randomUUID == "function") return e.randomUUID();
500
516
  let t = /* @__PURE__ */ new Uint8Array(16);
@@ -506,7 +522,7 @@ function _e() {
506
522
  }
507
523
  //#endregion
508
524
  //#region src/core/drm/headers.ts
509
- function ve(e, t, n = {}) {
525
+ function ye(e, t, n = {}) {
510
526
  let r = {
511
527
  "x-api-key": t.apiKey,
512
528
  "X-Match-Urn": e.matchUrn,
@@ -518,7 +534,7 @@ function ve(e, t, n = {}) {
518
534
  //#endregion
519
535
  //#region src/core/drm/cdm.ts
520
536
  var j = /* @__PURE__ */ new WeakMap();
521
- function ye(e, t) {
537
+ function be(e, t) {
522
538
  let n = j.get(e);
523
539
  n || (n = /* @__PURE__ */ new Map(), j.set(e, n));
524
540
  let r = n.get(t.keySystem);
@@ -532,11 +548,11 @@ function M() {
532
548
  return typeof navigator < "u" && typeof navigator.requestMediaKeySystemAccess == "function";
533
549
  }
534
550
  var N = ["SW_SECURE_DECODE", "SW_SECURE_CRYPTO"];
535
- function be(e) {
551
+ function xe(e) {
536
552
  return e.startsWith("com.widevine");
537
553
  }
538
- function xe(e, t) {
539
- if (!be(e)) return t;
554
+ function Se(e, t) {
555
+ if (!xe(e)) return t;
540
556
  let n = (e, t) => {
541
557
  let n = e.videoCapabilities;
542
558
  return !n?.length || !n.some((e) => e.robustness) ? !1 : n.every((e) => {
@@ -552,20 +568,20 @@ function xe(e, t) {
552
568
  })));
553
569
  return [...t, ...r];
554
570
  }
555
- function Se(e) {
556
- return (t, n) => M() ? navigator.requestMediaKeySystemAccess(t, xe(t, n)).then((t) => ({
571
+ function Ce(e) {
572
+ return (t, n) => M() ? navigator.requestMediaKeySystemAccess(t, Se(t, n)).then((t) => ({
557
573
  keySystem: t.keySystem,
558
574
  getConfiguration: () => t.getConfiguration(),
559
- createMediaKeys: () => ye(e, t)
575
+ createMediaKeys: () => be(e, t)
560
576
  })) : Promise.reject(/* @__PURE__ */ Error("navigator.requestMediaKeySystemAccess is unavailable — EME needs a secure context (https, or localhost)"));
561
577
  }
562
578
  //#endregion
563
579
  //#region src/core/drm/support.ts
564
- var Ce = 5e3, we = "video/mp4; codecs=\"avc1.42E01E\"", P = "audio/mp4; codecs=\"mp4a.40.2\"", Te = {
580
+ var we = 5e3, Te = "video/mp4; codecs=\"avc1.42E01E\"", P = "audio/mp4; codecs=\"mp4a.40.2\"", Ee = {
565
581
  widevine: ["com.widevine.alpha"],
566
582
  fairplay: ["com.apple.fps"]
567
583
  };
568
- function Ee(e) {
584
+ function De(e) {
569
585
  return e === "fairplay" ? [{
570
586
  initDataTypes: [
571
587
  "sinf",
@@ -573,7 +589,7 @@ function Ee(e) {
573
589
  "cenc"
574
590
  ],
575
591
  videoCapabilities: [{
576
- contentType: we,
592
+ contentType: Te,
577
593
  robustness: "",
578
594
  encryptionScheme: "cbcs"
579
595
  }],
@@ -582,10 +598,10 @@ function Ee(e) {
582
598
  robustness: "",
583
599
  encryptionScheme: "cbcs"
584
600
  }]
585
- }] : xe("com.widevine.alpha", [{
601
+ }] : Se("com.widevine.alpha", [{
586
602
  initDataTypes: ["cenc"],
587
603
  videoCapabilities: [{
588
- contentType: we,
604
+ contentType: Te,
589
605
  robustness: "SW_SECURE_DECODE",
590
606
  encryptionScheme: "cbcs"
591
607
  }],
@@ -606,8 +622,8 @@ function F(e, t) {
606
622
  });
607
623
  });
608
624
  }
609
- async function De(e = {}) {
610
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? Ce, r = {};
625
+ async function Oe(e = {}) {
626
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? we, r = {};
611
627
  if (typeof window < "u" && window.isSecureContext === !1) return {
612
628
  verdict: "insecure-context",
613
629
  detail: r
@@ -619,7 +635,7 @@ async function De(e = {}) {
619
635
  };
620
636
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
621
637
  for (let e of t) {
622
- let t = Te[e];
638
+ let t = Ee[e];
623
639
  if (!t) {
624
640
  r[e] = "unknown system";
625
641
  continue;
@@ -630,7 +646,7 @@ async function De(e = {}) {
630
646
  break;
631
647
  }
632
648
  try {
633
- return await F((await F(i(n, Ee(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
649
+ return await F((await F(i(n, De(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
634
650
  verdict: "supported",
635
651
  system: e,
636
652
  detail: r
@@ -654,9 +670,9 @@ async function De(e = {}) {
654
670
  }
655
671
  //#endregion
656
672
  //#region src/core/manifest.ts
657
- var Oe = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
658
- function ke(e) {
659
- return Oe.test(e);
673
+ var ke = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
674
+ function Ae(e) {
675
+ return ke.test(e);
660
676
  }
661
677
  //#endregion
662
678
  //#region src/core/stats.ts
@@ -668,11 +684,11 @@ function L(e) {
668
684
  return "";
669
685
  }
670
686
  }
671
- var Ae = /\.(m3u8|ts|m4s|mp4|cmfv|cmfa)(\?|$)/i;
672
- function je(e) {
687
+ var je = /\.(m3u8|ts|m4s|mp4|cmfv|cmfa)(\?|$)/i;
688
+ function Me(e) {
673
689
  if (typeof performance > "u" || typeof performance.getEntriesByType != "function") return "";
674
690
  let t = "", n = -1, r = performance.getEntriesByType("resource");
675
- for (let i of r) if (!(i.startTime < e) && (i.initiatorType === "video" || i.initiatorType === "audio" || Ae.test(i.name)) && i.startTime >= n) {
691
+ for (let i of r) if (!(i.startTime < e) && (i.initiatorType === "video" || i.initiatorType === "audio" || je.test(i.name)) && i.startTime >= n) {
676
692
  let e = L(i.name);
677
693
  e && (t = e, n = i.startTime);
678
694
  }
@@ -685,7 +701,7 @@ function R(e) {
685
701
  totalFrames: t?.totalVideoFrames ?? 0
686
702
  };
687
703
  }
688
- function Me(e, t) {
704
+ function Ne(e, t) {
689
705
  let n = [
690
706
  ["playing", () => t("playing")],
691
707
  ["waiting", () => t("buffering")],
@@ -702,7 +718,7 @@ function Me(e, t) {
702
718
  }
703
719
  //#endregion
704
720
  //#region src/core/engine/native.ts
705
- function Ne(e) {
721
+ function Pe(e) {
706
722
  try {
707
723
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
708
724
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -754,7 +770,7 @@ var V = class {
754
770
  type: "error",
755
771
  error: new n("INTERNAL", 0, "native HLS playback error"),
756
772
  fatal: !0
757
- }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = Pe(t.manifestUrl, t.analytics?.sid), e.load();
773
+ }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = Fe(t.manifestUrl, t.analytics?.sid), e.load();
758
774
  }
759
775
  startLoad() {
760
776
  this.loadStarted || (this.loadStarted = !0, this.restorePreload());
@@ -776,7 +792,7 @@ var V = class {
776
792
  });
777
793
  this.onEncrypted = (n) => {
778
794
  let i = n;
779
- i.initData && !this.fairplayArmed && (this.fairplayArmed = !0, this.fairplayKeyIV = Ne(i.initData), (async () => {
795
+ i.initData && !this.fairplayArmed && (this.fairplayArmed = !0, this.fairplayKeyIV = Pe(i.initData), (async () => {
780
796
  try {
781
797
  let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
782
798
  initDataTypes: [i.initDataType],
@@ -875,7 +891,7 @@ var V = class {
875
891
  droppedFrames: t.dropped,
876
892
  decodedFrames: t.totalFrames
877
893
  };
878
- return this.cdnHost ||= je(this.builtAt), this.cdnHost && (n.cdnHost = this.cdnHost), 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;
894
+ return this.cdnHost ||= Me(this.builtAt), this.cdnHost && (n.cdnHost = this.cdnHost), 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;
879
895
  }
880
896
  stop() {
881
897
  try {
@@ -894,25 +910,25 @@ var V = class {
894
910
  } catch {}
895
911
  }
896
912
  };
897
- function Pe(e, t) {
913
+ function Fe(e, t) {
898
914
  return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
899
915
  }
900
916
  //#endregion
901
917
  //#region src/core/engine/hls.ts
902
- function Fe() {
918
+ function Ie() {
903
919
  return e.isSupported();
904
920
  }
905
- var Ie = 3, Le = 5, Re = 3, ze = 3e4, Be = 12e3, Ve = 3e3, H = 6e3, He = 4e3, Ue = 2, We = 5, Ge = 2, Ke = 2, qe = 2e3, Je = 3e4, Ye = 2, Xe = 9, Ze = .5, Qe = 64;
906
- function $e(e) {
921
+ var Le = 3, Re = 5, ze = 3, Be = 3e4, Ve = 12e3, He = 3e3, H = 6e3, Ue = 5e3, We = 4e3, Ge = 2, Ke = 5, qe = 2, Je = 2, Ye = 2e3, Xe = 3e4, Ze = 2, Qe = 9, $e = .5, et = 64;
922
+ function tt(e) {
907
923
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
908
924
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
909
925
  return;
910
926
  }
911
- e.liveSyncDuration ??= Ye;
912
- let t = Math.max(12, e.liveSyncDuration + Xe);
913
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), et(e, e.liveSyncDuration);
927
+ e.liveSyncDuration ??= Ze;
928
+ let t = Math.max(12, e.liveSyncDuration + Qe);
929
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), nt(e, e.liveSyncDuration);
914
930
  }
915
- function et(e, t) {
931
+ function nt(e, t) {
916
932
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
917
933
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
918
934
  }
@@ -944,6 +960,11 @@ var U = class {
944
960
  licenseRetryTimer = null;
945
961
  onVisibility = null;
946
962
  onSeeking = null;
963
+ onPause = null;
964
+ onPlay = null;
965
+ pauseSuspendTimer = null;
966
+ loadSuspended = !1;
967
+ stopped = !1;
947
968
  sawLive = !1;
948
969
  lastEndSN = -1;
949
970
  lastAdvanceMs = 0;
@@ -986,10 +1007,34 @@ var U = class {
986
1007
  if (typeof t != "number") return;
987
1008
  let n = typeof e.targetLatency == "number" && e.targetLatency > 0 ? e.targetLatency : 3;
988
1009
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
989
- }, 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());
1010
+ }, document.addEventListener("visibilitychange", this.onVisibility)), this.onSeeking = () => {
1011
+ this.clampOverSeek(), this.loadSuspended && (this.resumeLoading(), this.schedulePauseSuspend());
1012
+ }, r.addEventListener("seeking", this.onSeeking), this.onPause = () => this.schedulePauseSuspend(), this.onPlay = () => this.resumeLoading(), r.addEventListener("pause", this.onPause), r.addEventListener("play", this.onPlay), this.sizeCapManaged && typeof ResizeObserver < "u" && (this.capObserver = new ResizeObserver(() => this.onMediaResize()), this.capObserver.observe(r), this.watchDprChanges());
1013
+ }
1014
+ schedulePauseSuspend() {
1015
+ this.pauseSuspendTimer || this.loadSuspended || this.stopped || !this.loadStarted || (this.pauseSuspendTimer = setTimeout(() => {
1016
+ this.pauseSuspendTimer = null;
1017
+ let { video: e } = this.deps;
1018
+ if (e.paused && !e.ended && !this.stopped && this.hls) {
1019
+ try {
1020
+ this.hls.stopLoad();
1021
+ } catch {
1022
+ return;
1023
+ }
1024
+ this.loadSuspended = !0;
1025
+ }
1026
+ }, Ue));
1027
+ }
1028
+ resumeLoading() {
1029
+ if (this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null), this.loadSuspended && (this.loadSuspended = !1, !this.stopped && this.hls)) {
1030
+ this.lastAdvanceMs = Date.now(), this.lastFragLoadingMs = 0, this.starvedTicks = 0, this.kickPending = !1, this.kickTicks = 0;
1031
+ try {
1032
+ this.hls.startLoad(-1);
1033
+ } catch {}
1034
+ }
990
1035
  }
991
1036
  startLoad() {
992
- this.loadStarted || (this.loadStarted = !0, this.hls?.startLoad(-1));
1037
+ this.loadStarted || (this.loadStarted = !0, this.deps.video.paused && this.schedulePauseSuspend(), this.hls?.startLoad(-1));
993
1038
  }
994
1039
  watchDprChanges() {
995
1040
  if (typeof matchMedia != "function") return;
@@ -1010,7 +1055,7 @@ var U = class {
1010
1055
  let t = e.liveSyncPosition;
1011
1056
  if (typeof t != "number" || !Number.isFinite(t)) return;
1012
1057
  let { video: n } = this.deps;
1013
- n.currentTime <= t + Ze || (n.currentTime = t);
1058
+ n.currentTime <= t + $e || (n.currentTime = t);
1014
1059
  }
1015
1060
  startHls() {
1016
1061
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
@@ -1034,7 +1079,7 @@ var U = class {
1034
1079
  videoEncryptionScheme: "cbcs",
1035
1080
  audioEncryptionScheme: "cbcs"
1036
1081
  },
1037
- ...M() ? { requestMediaKeySystemAccessFunc: Se(t) } : {},
1082
+ ...M() ? { requestMediaKeySystemAccessFunc: Ce(t) } : {},
1038
1083
  licenseXhrSetup: (e, t) => {
1039
1084
  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;
1040
1085
  let o = performance.now();
@@ -1050,23 +1095,23 @@ var U = class {
1050
1095
  } } : {},
1051
1096
  ...c
1052
1097
  };
1053
- $e(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
1098
+ tt(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
1054
1099
  let f = new e(d);
1055
1100
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
1056
1101
  this.mediaRecoveries = 0, this.networkRecoveries = 0;
1057
1102
  let e = f.levels.map((e) => e.height).filter((e) => !!e);
1058
- 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" });
1103
+ 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(), this.loadSuspended = !1, t.paused && this.schedulePauseSuspend(), l({ type: "ready" });
1059
1104
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
1060
1105
  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(() => {
1061
1106
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.starvationRebuilds = 0, this.recoveryResetTimer = null;
1062
- }, ze)));
1107
+ }, Be)));
1063
1108
  }), f.on(e.Events.FRAG_LOADING, (e, t) => {
1064
1109
  t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
1065
1110
  }), f.on(e.Events.FRAG_LOADED, (e, t) => {
1066
1111
  let n = t?.frag?.stats, r = n?.loaded;
1067
1112
  typeof r == "number" && (this.bytesLoaded += r);
1068
1113
  let i = n?.loading;
1069
- 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 > Qe && this.requestTimesMs.shift());
1114
+ 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 > et && this.requestTimesMs.shift());
1070
1115
  let a = t?.frag?.url;
1071
1116
  if (a) {
1072
1117
  let e = L(a);
@@ -1082,7 +1127,7 @@ var U = class {
1082
1127
  }));
1083
1128
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog && clearInterval(this.endWatchdog), this.endWatchdog = setInterval(() => {
1084
1129
  this.checkEndStale(), this.checkStarvation();
1085
- }, Ve), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1130
+ }, He), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1086
1131
  this.renditionSwitches += 1;
1087
1132
  let n = this.lastLevelIndex >= 0 ? f.levels[this.lastLevelIndex] : void 0, r = f.levels[t.level];
1088
1133
  n && r && r.bitrate !== n.bitrate && (r.bitrate > n.bitrate ? this.upshifts += 1 : this.downshifts += 1), this.lastLevelIndex = t.level, l({
@@ -1146,7 +1191,7 @@ var U = class {
1146
1191
  return;
1147
1192
  }
1148
1193
  this.capTimer && clearTimeout(this.capTimer);
1149
- let t = e > this.sizeCapLevel ? qe : Je;
1194
+ let t = e > this.sizeCapLevel ? Ye : Xe;
1150
1195
  this.capTimer = setTimeout(() => {
1151
1196
  this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1152
1197
  }, t);
@@ -1234,27 +1279,31 @@ var U = class {
1234
1279
  this.lastAdvanceMs = Date.now();
1235
1280
  return;
1236
1281
  }
1282
+ if (this.loadSuspended) {
1283
+ this.lastAdvanceMs = Date.now();
1284
+ return;
1285
+ }
1237
1286
  let e = Date.now() - this.lastAdvanceMs;
1238
- !this.chaseSuspended && e > H && this.suspendChasing(), e > Be && this.emitEnded("stalled");
1287
+ !this.chaseSuspended && e > H && this.suspendChasing(), e > Ve && this.emitEnded("stalled");
1239
1288
  }
1240
1289
  checkStarvation() {
1241
1290
  let e = this.hls, { video: t } = this.deps;
1242
1291
  if (!e || this.endedEmitted || !this.sawLive || this.chaseSuspended) return;
1243
- if (typeof document < "u" && document.hidden) {
1292
+ if (typeof document < "u" && document.hidden || this.loadSuspended) {
1244
1293
  this.starvedTicks = 0;
1245
1294
  return;
1246
1295
  }
1247
- let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < H, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > He;
1296
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < H, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > We;
1248
1297
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1249
1298
  this.starvedTicks = 0;
1250
1299
  return;
1251
1300
  }
1252
- if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Ue)) {
1253
- if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Ge && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < Ke)) {
1301
+ if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Ge)) {
1302
+ if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= qe && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < Je)) {
1254
1303
  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");
1255
1304
  return;
1256
1305
  }
1257
- if (!(this.starvationKicks >= We)) {
1306
+ if (!(this.starvationKicks >= Ke)) {
1258
1307
  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");
1259
1308
  try {
1260
1309
  e.stopLoad(), e.startLoad(-1);
@@ -1280,7 +1329,7 @@ var U = class {
1280
1329
  }));
1281
1330
  }
1282
1331
  stop() {
1283
- this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.kickPending = !1, this.kickTicks = 0;
1332
+ this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.kickPending = !1, this.kickTicks = 0, this.stopped = !0, this.loadSuspended = !1, this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null);
1284
1333
  try {
1285
1334
  this.hls?.stopLoad();
1286
1335
  } catch {}
@@ -1289,7 +1338,7 @@ var U = class {
1289
1338
  } catch {}
1290
1339
  }
1291
1340
  destroy() {
1292
- if (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.networkRetryTimer &&= (clearTimeout(this.networkRetryTimer), null), this.licenseRetryTimer &&= (clearTimeout(this.licenseRetryTimer), null), this.hls) {
1341
+ if (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.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) {
1293
1342
  try {
1294
1343
  this.hls.destroy();
1295
1344
  } catch (e) {
@@ -1304,7 +1353,7 @@ var U = class {
1304
1353
  scheduleLicenseRetry(t) {
1305
1354
  if (t.details !== e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED && t.details !== e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED) return !1;
1306
1355
  let n = t.response?.code ?? 0;
1307
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= Re) return !1;
1356
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= ze) return !1;
1308
1357
  this.licenseRecoveries += 1;
1309
1358
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1310
1359
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1336,7 +1385,7 @@ var U = class {
1336
1385
  return;
1337
1386
  }
1338
1387
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1339
- if (this.networkRecoveries < Le) {
1388
+ if (this.networkRecoveries < Re) {
1340
1389
  this.networkRecoveries += 1;
1341
1390
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1342
1391
  this.networkRetryTimer = setTimeout(() => {
@@ -1353,7 +1402,7 @@ var U = class {
1353
1402
  return;
1354
1403
  }
1355
1404
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1356
- if (this.mediaRecoveries < Ie) {
1405
+ if (this.mediaRecoveries < Le) {
1357
1406
  this.mediaRecoveries += 1, n.recoverMediaError();
1358
1407
  return;
1359
1408
  }
@@ -1377,11 +1426,11 @@ var U = class {
1377
1426
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1378
1427
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1379
1428
  }
1380
- }, tt = 15e3, nt = 100, W = 1, rt = 1e3, G = 3e4, it = 1e3, at = {
1429
+ }, rt = 15e3, it = 100, W = 1, at = 1e3, G = 3e4, ot = 1e3, st = {
1381
1430
  inline: "PRESENTATION_INLINE",
1382
1431
  fullscreen: "PRESENTATION_FULLSCREEN",
1383
1432
  pip: "PRESENTATION_PIP"
1384
- }, ot = {
1433
+ }, ct = {
1385
1434
  widevine: "KEY_SYSTEM_WIDEVINE",
1386
1435
  fairplay: "KEY_SYSTEM_FAIRPLAY",
1387
1436
  none: "KEY_SYSTEM_NONE"
@@ -1389,7 +1438,7 @@ var U = class {
1389
1438
  function K(e, t) {
1390
1439
  return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
1391
1440
  }
1392
- var st = /* @__PURE__ */ new Set([
1441
+ var lt = /* @__PURE__ */ new Set([
1393
1442
  "INVALID_URN",
1394
1443
  "NOT_FOUND",
1395
1444
  "GONE",
@@ -1409,7 +1458,7 @@ var st = /* @__PURE__ */ new Set([
1409
1458
  ended: "PLAYER_STATE_ENDED",
1410
1459
  error: "PLAYER_STATE_ERROR"
1411
1460
  };
1412
- function ct(e) {
1461
+ function ut(e) {
1413
1462
  try {
1414
1463
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1415
1464
  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);
@@ -1417,7 +1466,7 @@ function ct(e) {
1417
1466
  return;
1418
1467
  }
1419
1468
  }
1420
- var lt = class {
1469
+ var dt = class {
1421
1470
  opts;
1422
1471
  url;
1423
1472
  now;
@@ -1489,7 +1538,7 @@ var lt = class {
1489
1538
  constructor(e) {
1490
1539
  this.opts = {
1491
1540
  ...e,
1492
- heartbeatMs: e.heartbeatMs ?? tt
1541
+ heartbeatMs: e.heartbeatMs ?? rt
1493
1542
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now(), this.presentationSinceMs = this.bornMs, this.startupAnchorMs = this.bornMs;
1494
1543
  }
1495
1544
  start() {
@@ -1499,7 +1548,7 @@ var lt = class {
1499
1548
  sdkVersion: this.opts.sdkVersion,
1500
1549
  ...this.opts.environment
1501
1550
  };
1502
- this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), rt), 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());
1551
+ this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), at), 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());
1503
1552
  }
1504
1553
  noteStateChange(e, t, n, r) {
1505
1554
  if (this.endedFlag || e === t) return;
@@ -1513,12 +1562,12 @@ var lt = class {
1513
1562
  let a = {
1514
1563
  fromState: q[e] ?? "PLAYER_STATE_UNSPECIFIED",
1515
1564
  toState: q[t] ?? "PLAYER_STATE_UNSPECIFIED",
1516
- presentation: at[this.presentation]
1565
+ presentation: st[this.presentation]
1517
1566
  }, o = this.causeOf(e, t, i);
1518
1567
  o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1519
1568
  }
1520
1569
  seekInduced(e) {
1521
- return this.lastSeekMs >= 0 && e - this.lastSeekMs < it;
1570
+ return this.lastSeekMs >= 0 && e - this.lastSeekMs < ot;
1522
1571
  }
1523
1572
  causeOf(e, t, n) {
1524
1573
  if (t === "error") return "TRANSITION_CAUSE_ERROR";
@@ -1586,7 +1635,7 @@ var lt = class {
1586
1635
  let r = n ?? "";
1587
1636
  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";
1588
1637
  if (e === "DRM_CLIENT") return "ERROR_STAGE_DRM";
1589
- if (st.has(e)) return "ERROR_STAGE_RESOLVE";
1638
+ if (lt.has(e)) return "ERROR_STAGE_RESOLVE";
1590
1639
  let i = t ?? "";
1591
1640
  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";
1592
1641
  }
@@ -1638,7 +1687,7 @@ var lt = class {
1638
1687
  maybeEmitMediaInfo(e) {
1639
1688
  if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
1640
1689
  let t = {};
1641
- e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = ot[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);
1690
+ e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = ct[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);
1642
1691
  let n = JSON.stringify(t);
1643
1692
  n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
1644
1693
  }
@@ -1678,7 +1727,7 @@ var lt = class {
1678
1727
  };
1679
1728
  }
1680
1729
  enqueue(e) {
1681
- for (this.pending.push(this.wrap(e)); this.pending.length > nt;) {
1730
+ for (this.pending.push(this.wrap(e)); this.pending.length > it;) {
1682
1731
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1683
1732
  this.pending.splice(e >= 0 ? e : 0, 1);
1684
1733
  }
@@ -1701,7 +1750,7 @@ var lt = class {
1701
1750
  };
1702
1751
  //#endregion
1703
1752
  //#region src/core/environment.ts
1704
- function ut() {
1753
+ function ft() {
1705
1754
  if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
1706
1755
  let e;
1707
1756
  try {
@@ -1726,14 +1775,14 @@ function ut() {
1726
1775
  integration: "INTEGRATION_KIND_IFRAME"
1727
1776
  };
1728
1777
  }
1729
- function dt() {
1778
+ function pt() {
1730
1779
  if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
1731
1780
  let e = navigator.userAgentData, t = e?.platform ?? "";
1732
1781
  if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
1733
1782
  let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
1734
1783
  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";
1735
1784
  }
1736
- function ft() {
1785
+ function mt() {
1737
1786
  let e = typeof navigator < "u" ? navigator.connection : void 0;
1738
1787
  if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
1739
1788
  let t = {
@@ -1752,17 +1801,17 @@ function ft() {
1752
1801
  }
1753
1802
  return typeof e.downlink == "number" && e.downlink > 0 && (i.downlinkKbps = Math.round(e.downlink * 1e3)), typeof e.rtt == "number" && e.rtt > 0 && (i.rttMs = Math.round(e.rtt)), e.saveData && (i.saveData = !0), i;
1754
1803
  }
1755
- function pt(e) {
1756
- let t = ut(), n = {
1757
- deviceClass: dt(),
1804
+ function ht(e) {
1805
+ let t = ft(), n = {
1806
+ deviceClass: pt(),
1758
1807
  integration: t.integration,
1759
- ...ft()
1808
+ ...mt()
1760
1809
  };
1761
1810
  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);
1762
1811
  let r = e.getBoundingClientRect?.();
1763
1812
  return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
1764
1813
  }
1765
- async function mt() {
1814
+ async function gt() {
1766
1815
  let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
1767
1816
  if (!e?.getHighEntropyValues) return {};
1768
1817
  try {
@@ -1774,7 +1823,7 @@ async function mt() {
1774
1823
  }
1775
1824
  //#endregion
1776
1825
  //#region src/core/version.ts
1777
- var ht = "2.6.2", J = {
1826
+ var _t = "2.6.4", J = {
1778
1827
  accent: "#E1B600",
1779
1828
  accentText: "#1B1C23",
1780
1829
  background: "#0e0f13",
@@ -1807,15 +1856,15 @@ function X(e, t) {
1807
1856
  }
1808
1857
  //#endregion
1809
1858
  //#region src/ui/styles.ts
1810
- var Z = "havik-player-styles", gt = "\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";
1811
- function _t(e = document) {
1859
+ var Z = "havik-player-styles", vt = "\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";
1860
+ function yt(e = document) {
1812
1861
  if (e.getElementById(Z)) return;
1813
1862
  let t = e.createElement("style");
1814
- t.id = Z, t.textContent = gt, e.head.appendChild(t);
1863
+ t.id = Z, t.textContent = vt, e.head.appendChild(t);
1815
1864
  }
1816
1865
  //#endregion
1817
1866
  //#region src/ui/icons.ts
1818
- var vt = {
1867
+ var bt = {
1819
1868
  play: "<path d=\"M8 5v14l11-7z\"/>",
1820
1869
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1821
1870
  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\"/>",
@@ -1829,20 +1878,20 @@ var vt = {
1829
1878
  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\"/>"
1830
1879
  };
1831
1880
  function Q(e) {
1832
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${vt[e]}</svg>`;
1881
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${bt[e]}</svg>`;
1833
1882
  }
1834
1883
  //#endregion
1835
1884
  //#region src/ui/controls.ts
1836
- var yt = 3e3, bt = /* @__PURE__ */ new Set([
1885
+ var xt = 3e3, St = /* @__PURE__ */ new Set([
1837
1886
  "GONE",
1838
1887
  "NOT_FOUND",
1839
1888
  "INVALID_URN",
1840
1889
  "UNAUTHORIZED"
1841
1890
  ]);
1842
- function xt(e) {
1843
- return new St(e);
1891
+ function Ct(e) {
1892
+ return new wt(e);
1844
1893
  }
1845
- var St = class {
1894
+ var wt = class {
1846
1895
  root;
1847
1896
  video;
1848
1897
  player;
@@ -1884,7 +1933,7 @@ var St = class {
1884
1933
  scrubbing = !1;
1885
1934
  offFns = [];
1886
1935
  constructor(e) {
1887
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? yt, 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, _t(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");
1936
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? xt, 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, yt(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");
1888
1937
  let t = (e, t, n) => {
1889
1938
  let r = this.doc.createElement(e);
1890
1939
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1975,7 +2024,7 @@ var St = class {
1975
2024
  e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Q("play"), e.addEventListener("click", () => void this.player.play()), n.append(e);
1976
2025
  } else {
1977
2026
  let r = this.doc.createElement("div");
1978
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !bt.has(this.lastError.code)) {
2027
+ if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !St.has(this.lastError.code)) {
1979
2028
  let e = this.doc.createElement("button");
1980
2029
  e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Q("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
1981
2030
  }
@@ -2016,7 +2065,7 @@ var St = class {
2016
2065
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
2017
2066
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
2018
2067
  }
2019
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Ct(this.video.currentTime)} / ${Ct(this.video.duration)}`);
2068
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Tt(this.video.currentTime)} / ${Tt(this.video.duration)}`);
2020
2069
  }
2021
2070
  syncLive() {
2022
2071
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -2066,17 +2115,17 @@ var St = class {
2066
2115
  renderQualityMenu() {
2067
2116
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
2068
2117
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
2069
- for (let n of e) this.menu.append(this.menuItem(wt(n), t === n.index, () => this.player.setQuality(n.index)));
2118
+ for (let n of e) this.menu.append(this.menuItem(Et(n), t === n.index, () => this.player.setQuality(n.index)));
2070
2119
  }
2071
2120
  renderAudioMenu() {
2072
2121
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
2073
2122
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
2074
- for (let t of e) this.menu.append(this.menuItem(Tt(t), t.default, () => this.player.setAudioTrack(t.id)));
2123
+ for (let t of e) this.menu.append(this.menuItem(Dt(t), t.default, () => this.player.setAudioTrack(t.id)));
2075
2124
  }
2076
2125
  renderCcMenu() {
2077
2126
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
2078
2127
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
2079
- for (let t of e) this.menu.append(this.menuItem(Et(t), !1, () => this.player.setTextTrack(t.id)));
2128
+ for (let t of e) this.menu.append(this.menuItem(Ot(t), !1, () => this.player.setTextTrack(t.id)));
2080
2129
  }
2081
2130
  onTracksChanged() {
2082
2131
  this.openMenu && this.renderMenu(this.openMenu);
@@ -2176,32 +2225,32 @@ var St = class {
2176
2225
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
2177
2226
  }
2178
2227
  };
2179
- function Ct(e) {
2228
+ function Tt(e) {
2180
2229
  (!Number.isFinite(e) || e < 0) && (e = 0);
2181
2230
  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");
2182
2231
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
2183
2232
  }
2184
- function wt(e) {
2233
+ function Et(e) {
2185
2234
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
2186
2235
  }
2187
- function Tt(e) {
2236
+ function Dt(e) {
2188
2237
  return e.name || e.lang || `Track ${e.id}`;
2189
2238
  }
2190
- function Et(e) {
2239
+ function Ot(e) {
2191
2240
  return e.name || e.lang || `Track ${e.id}`;
2192
2241
  }
2193
2242
  //#endregion
2194
2243
  //#region src/managed/index.ts
2195
- var Dt = 6e3, Ot = 45e3, kt = 2e3, At = 3, jt = 2e3, Mt = 48e4;
2196
- async function Nt(e) {
2244
+ var kt = 6e3, At = 45e3, jt = 2e3, Mt = 3, Nt = 2e3, Pt = 48e4;
2245
+ async function Ft(e) {
2197
2246
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
2198
2247
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
2199
2248
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
2200
2249
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
2201
- let t = new Pt(e);
2250
+ let t = new It(e);
2202
2251
  return await t.start(), t;
2203
2252
  }
2204
- var Pt = class {
2253
+ var It = class {
2205
2254
  opts;
2206
2255
  abort = new AbortController();
2207
2256
  listeners = {};
@@ -2288,7 +2337,7 @@ var Pt = class {
2288
2337
  setTimeout(() => {
2289
2338
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
2290
2339
  phase: i ? "unavailable" : "tooEarly",
2291
- retryInMs: Ot,
2340
+ retryInMs: At,
2292
2341
  attempt: 1,
2293
2342
  elapsedMs: 0,
2294
2343
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -2327,13 +2376,13 @@ var Pt = class {
2327
2376
  !a && !p() && h();
2328
2377
  }, e);
2329
2378
  };
2330
- _(Dt), this.armingSub = E({
2379
+ _(kt), this.armingSub = E({
2331
2380
  eventsBaseUrl: t,
2332
2381
  matchUrn: this.opts.matchUrn,
2333
2382
  credential: this.opts.credential,
2334
- onAlive: () => _(Ot),
2383
+ onAlive: () => _(At),
2335
2384
  onState: (e) => {
2336
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * kt);
2385
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * jt);
2337
2386
  },
2338
2387
  onError: () => h()
2339
2388
  });
@@ -2356,7 +2405,7 @@ var Pt = class {
2356
2405
  let n = await s(this.opts.credential);
2357
2406
  if (this.destroyed || t !== this.loadGen) return;
2358
2407
  this.setState("loading");
2359
- let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = Me(this.opts.video, (e) => {
2408
+ let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = Ne(this.opts.video, (e) => {
2360
2409
  this._state !== "ended" && this._state !== "error" && this.setState(e);
2361
2410
  });
2362
2411
  if (await i.load(), this.destroyed || t !== this.loadGen) {
@@ -2375,14 +2424,14 @@ var Pt = class {
2375
2424
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
2376
2425
  let n = e.analytics?.sid;
2377
2426
  if (!n) return;
2378
- let r = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? ct(this.opts.baseUrl);
2427
+ let r = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? ut(this.opts.baseUrl);
2379
2428
  if (!r) return;
2380
- let i = this.opts.video, a = new lt({
2429
+ let i = this.opts.video, a = new dt({
2381
2430
  endpoint: r,
2382
2431
  sid: n,
2383
2432
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
2384
- sdkVersion: ht,
2385
- environment: pt(i),
2433
+ sdkVersion: _t,
2434
+ environment: ht(i),
2386
2435
  sample: () => {
2387
2436
  let e = this.engine?.getStats() ?? I, t = i.buffered.length ? Math.max(0, i.buffered.end(i.buffered.length - 1) - i.currentTime) : 0;
2388
2437
  return {
@@ -2398,7 +2447,7 @@ var Pt = class {
2398
2447
  let o = () => a.noteSeek(), s = () => a.noteVolume(i.muted, i.volume);
2399
2448
  i.addEventListener("seeking", o), i.addEventListener("volumechange", s), this.detachMediaListeners = () => {
2400
2449
  i.removeEventListener("seeking", o), i.removeEventListener("volumechange", s);
2401
- }, a.start(), a.seedVolume(i.muted, i.volume), a.notePresentation(this.currentPresentation()), mt().then((e) => a.noteEnvironment(e)), this.opts.autoplay === !1 && a.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
2450
+ }, a.start(), a.seedVolume(i.muted, i.volume), a.notePresentation(this.currentPresentation()), gt().then((e) => a.noteEnvironment(e)), this.opts.autoplay === !1 && a.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
2402
2451
  }
2403
2452
  currentPresentation() {
2404
2453
  let e = this.opts.video;
@@ -2425,7 +2474,7 @@ var Pt = class {
2425
2474
  if (o === "native") return new V(a);
2426
2475
  if (o === "hls") return new U(a);
2427
2476
  if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && z(this.opts.video) && B()) return new V(a);
2428
- if (Fe()) return new U(a);
2477
+ if (Ie()) return new U(a);
2429
2478
  if (z(this.opts.video)) return new V(a);
2430
2479
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
2431
2480
  }
@@ -2469,7 +2518,7 @@ var Pt = class {
2469
2518
  r = 0, i = !1;
2470
2519
  },
2471
2520
  onError: () => {
2472
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= At && (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.`))));
2521
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= Mt && (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.`))));
2473
2522
  }
2474
2523
  });
2475
2524
  }
@@ -2491,7 +2540,7 @@ var Pt = class {
2491
2540
  scheduleRejoin(e) {
2492
2541
  this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
2493
2542
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
2494
- }, Math.random() * kt));
2543
+ }, Math.random() * jt));
2495
2544
  }
2496
2545
  cancelRejoin() {
2497
2546
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -2515,7 +2564,7 @@ var Pt = class {
2515
2564
  }
2516
2565
  t.controls = !1;
2517
2566
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2518
- r && r.insertBefore(n, t), n.appendChild(t), X(n, this.opts.theme), this.container = n, this.controlBar = xt({
2567
+ r && r.insertBefore(n, t), n.appendChild(t), X(n, this.opts.theme), this.container = n, this.controlBar = Ct({
2519
2568
  root: n,
2520
2569
  video: t,
2521
2570
  player: this,
@@ -2545,7 +2594,7 @@ var Pt = class {
2545
2594
  }
2546
2595
  startStats() {
2547
2596
  this.stopStats();
2548
- let e = Math.max(500, this.opts.statsIntervalMs ?? jt);
2597
+ let e = Math.max(500, this.opts.statsIntervalMs ?? Nt);
2549
2598
  this.statsTimer = setInterval(() => {
2550
2599
  if (!this.engine) return;
2551
2600
  let e = this.engine.getStats();
@@ -2557,7 +2606,7 @@ var Pt = class {
2557
2606
  }
2558
2607
  scheduleLicenseRefresh() {
2559
2608
  this.stopRefresh();
2560
- let e = this.opts.licenseRefreshMs ?? Mt;
2609
+ let e = this.opts.licenseRefreshMs ?? Pt;
2561
2610
  e && this._descriptor?.drmEnabled && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2562
2611
  }
2563
2612
  stopRefresh() {
@@ -2709,7 +2758,7 @@ function $(e) {
2709
2758
  }
2710
2759
  //#endregion
2711
2760
  //#region src/embed/host.ts
2712
- function Ft(e) {
2761
+ function Lt(e) {
2713
2762
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2714
2763
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2715
2764
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2768,8 +2817,8 @@ function Ft(e) {
2768
2817
  }
2769
2818
  //#endregion
2770
2819
  //#region src/embed/page.ts
2771
- var It = /^[A-Za-z0-9:_.-]{1,128}$/;
2772
- function Lt() {
2820
+ var Rt = /^[A-Za-z0-9:_.-]{1,128}$/;
2821
+ function zt() {
2773
2822
  let e = window.HAVIK_EMBED_CONFIG;
2774
2823
  if (e) return {
2775
2824
  baseUrl: e.baseUrl ?? "",
@@ -2791,9 +2840,9 @@ function Lt() {
2791
2840
  waitForLive: !0
2792
2841
  };
2793
2842
  }
2794
- async function Rt() {
2795
- let e = Lt();
2796
- Bt();
2843
+ async function Bt() {
2844
+ let e = zt();
2845
+ Ht();
2797
2846
  let t = document.createElement("video");
2798
2847
  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);
2799
2848
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2848,12 +2897,12 @@ async function Rt() {
2848
2897
  code: e.code,
2849
2898
  message: e.message
2850
2899
  })), e.on("autoplayblocked", () => {
2851
- i({ type: "oddin:autoplayblocked" }), zt(t, e);
2900
+ i({ type: "oddin:autoplayblocked" }), Vt(t, e);
2852
2901
  });
2853
2902
  }, s = async (r) => {
2854
2903
  a?.destroy(), a = null;
2855
2904
  try {
2856
- let n = await Nt({
2905
+ let n = await Ft({
2857
2906
  video: t,
2858
2907
  baseUrl: e.baseUrl,
2859
2908
  matchUrn: r,
@@ -2914,7 +2963,7 @@ async function Rt() {
2914
2963
  a?.retry();
2915
2964
  break;
2916
2965
  case "oddin:load":
2917
- typeof e.matchUrn == "string" && It.test(e.matchUrn) && s(e.matchUrn);
2966
+ typeof e.matchUrn == "string" && Rt.test(e.matchUrn) && s(e.matchUrn);
2918
2967
  break;
2919
2968
  case "oddin:destroy": c();
2920
2969
  }
@@ -2926,18 +2975,18 @@ async function Rt() {
2926
2975
  protocol: 1
2927
2976
  });
2928
2977
  }
2929
- function zt(e, t) {
2978
+ function Vt(e, t) {
2930
2979
  if (document.getElementById("havik-tap")) return;
2931
2980
  let n = document.createElement("button");
2932
2981
  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", () => {
2933
2982
  n.remove(), t.play();
2934
2983
  }), document.body.appendChild(n);
2935
2984
  }
2936
- function Bt() {
2985
+ function Ht() {
2937
2986
  let e = document.createElement("style");
2938
2987
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2939
2988
  }
2940
2989
  //#endregion
2941
- export { J as ODDIN_THEME, n as PlaybackError, X as applyTheme, Rt as bootEmbed, ge as clearDeviceId, Nt as createPlayer, D as deriveEventsBaseUrl, De as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, ue as fetchCatalog, fe as fetchTournament, me as flattenMatches, A as getDeviceId, ke as isLowLatencyManifest, $ as isOddinMessage, ve as licenseRequestHeaders, Ft as mountEmbed, oe as pollToLive, y as resolveStream, E as subscribeLiveState, C as videoFullscreenActive, he as watchStatus };
2990
+ export { J as ODDIN_THEME, n as PlaybackError, X as applyTheme, Bt as bootEmbed, _e as clearDeviceId, Ft as createPlayer, D as deriveEventsBaseUrl, Oe as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, ue as fetchCatalog, fe as fetchTournament, me as flattenMatches, A as getDeviceId, Ae as isLowLatencyManifest, $ as isOddinMessage, ye as licenseRequestHeaders, Lt as mountEmbed, oe as pollToLive, y as resolveStream, E as subscribeLiveState, C as videoFullscreenActive, he as watchStatus };
2942
2991
 
2943
2992
  //# sourceMappingURL=index.js.map