@oddin-gg/havik-player 1.0.4 → 1.0.6

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
@@ -510,17 +510,21 @@ function I(e, t) {
510
510
  function L() {
511
511
  return e.isSupported();
512
512
  }
513
- var R = 3, z = 5, B = 3, V = 3e4, H = 12e3, U = 3e3, de = 6e3, fe = 3, pe = 9;
514
- function me(e) {
513
+ var R = 3, z = 5, B = 3, V = 3e4, H = 12e3, U = 3e3, de = 6e3, fe = 3, pe = 9, me = .5;
514
+ function he(e) {
515
515
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
516
516
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
517
517
  return;
518
518
  }
519
519
  e.liveSyncDuration ??= fe;
520
520
  let t = Math.max(12, e.liveSyncDuration + pe);
521
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t);
521
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), ge(e, e.liveSyncDuration);
522
522
  }
523
- var he = class {
523
+ function ge(e, t) {
524
+ let n = (e, t, n) => Math.min(n, Math.max(t, e));
525
+ e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
526
+ }
527
+ var _e = class {
524
528
  name = "hls";
525
529
  hls = null;
526
530
  deps;
@@ -533,6 +537,7 @@ var he = class {
533
537
  networkRetryTimer = null;
534
538
  licenseRetryTimer = null;
535
539
  onVisibility = null;
540
+ onSeeking = null;
536
541
  sawLive = !1;
537
542
  lastEndSN = -1;
538
543
  lastAdvanceMs = 0;
@@ -561,7 +566,15 @@ var he = class {
561
566
  if (typeof t != "number") return;
562
567
  let n = typeof e.targetLatency == "number" && e.targetLatency > 0 ? e.targetLatency : 3;
563
568
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
564
- }, document.addEventListener("visibilitychange", this.onVisibility));
569
+ }, document.addEventListener("visibilitychange", this.onVisibility)), this.onSeeking = () => this.clampOverSeek(), r.addEventListener("seeking", this.onSeeking);
570
+ }
571
+ clampOverSeek() {
572
+ let e = this.hls;
573
+ if (!e || this.chaseSuspended || (e.levels[e.currentLevel] ?? e.levels[e.loadLevel])?.details?.live !== !0) return;
574
+ let t = e.liveSyncPosition;
575
+ if (typeof t != "number" || !Number.isFinite(t)) return;
576
+ let { video: n } = this.deps;
577
+ n.currentTime <= t + me || (n.currentTime = t);
565
578
  }
566
579
  startHls() {
567
580
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, hlsConfig: s, emit: c } = this.deps, l = n.drmEnabled && !!n.drm?.widevine?.licenseUrl, u = {
@@ -587,7 +600,7 @@ var he = class {
587
600
  } : {},
588
601
  ...s
589
602
  };
590
- me(u);
603
+ he(u);
591
604
  let d = new e(u);
592
605
  this.hls = d, d.on(e.Events.MANIFEST_PARSED, () => {
593
606
  this.mediaRecoveries = 0, this.networkRecoveries = 0, c({ type: "ready" });
@@ -727,7 +740,7 @@ var he = class {
727
740
  } catch {}
728
741
  }
729
742
  destroy() {
730
- if (this.clearRecoveryReset(), this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.onVisibility && typeof document < "u" && (document.removeEventListener("visibilitychange", this.onVisibility), this.onVisibility = null), this.networkRetryTimer &&= (clearTimeout(this.networkRetryTimer), null), this.licenseRetryTimer &&= (clearTimeout(this.licenseRetryTimer), null), this.hls) {
743
+ if (this.clearRecoveryReset(), 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) {
731
744
  try {
732
745
  this.hls.destroy();
733
746
  } catch (e) {
@@ -811,7 +824,7 @@ var he = class {
811
824
  };
812
825
  //#endregion
813
826
  //#region src/core/engine/native.ts
814
- function ge(e) {
827
+ function ve(e) {
815
828
  return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
816
829
  }
817
830
  var W = class {
@@ -942,15 +955,15 @@ function q(e, t) {
942
955
  }
943
956
  //#endregion
944
957
  //#region src/ui/styles.ts
945
- var J = "havik-player-styles", _e = "\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\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.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:rgba(255,255,255,.3);border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:70px;opacity:1}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-moz-range-thumb{width:12px;height:12px;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";
946
- function ve(e = document) {
958
+ var J = "havik-player-styles", ye = "\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\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.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:rgba(255,255,255,.3);border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:70px;opacity:1}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-moz-range-thumb{width:12px;height:12px;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";
959
+ function be(e = document) {
947
960
  if (e.getElementById(J)) return;
948
961
  let t = e.createElement("style");
949
- t.id = J, t.textContent = _e, e.head.appendChild(t);
962
+ t.id = J, t.textContent = ye, e.head.appendChild(t);
950
963
  }
951
964
  //#endregion
952
965
  //#region src/ui/icons.ts
953
- var ye = {
966
+ var xe = {
954
967
  play: "<path d=\"M8 5v14l11-7z\"/>",
955
968
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
956
969
  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\"/>",
@@ -964,20 +977,20 @@ var ye = {
964
977
  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\"/>"
965
978
  };
966
979
  function Y(e) {
967
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${ye[e]}</svg>`;
980
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${xe[e]}</svg>`;
968
981
  }
969
982
  //#endregion
970
983
  //#region src/ui/controls.ts
971
- var be = 3e3, xe = /* @__PURE__ */ new Set([
984
+ var Se = 3e3, Ce = /* @__PURE__ */ new Set([
972
985
  "GONE",
973
986
  "NOT_FOUND",
974
987
  "INVALID_URN",
975
988
  "UNAUTHORIZED"
976
989
  ]);
977
- function Se(e) {
978
- return new Ce(e);
990
+ function we(e) {
991
+ return new Te(e);
979
992
  }
980
- var Ce = class {
993
+ var Te = class {
981
994
  root;
982
995
  video;
983
996
  player;
@@ -1017,7 +1030,7 @@ var Ce = class {
1017
1030
  scrubbing = !1;
1018
1031
  offFns = [];
1019
1032
  constructor(e) {
1020
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? be, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, ve(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");
1033
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Se, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, be(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");
1021
1034
  let t = (e, t, n) => {
1022
1035
  let r = this.doc.createElement(e);
1023
1036
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1104,7 +1117,7 @@ var Ce = class {
1104
1117
  e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Y("play"), e.addEventListener("click", () => void this.player.play()), t.append(e);
1105
1118
  } else {
1106
1119
  let n = this.doc.createElement("div");
1107
- if (n.className = "havik-status__msg", n.textContent = e === "ended" ? this.endedMessage : e === "waiting" ? "Starting soon…" : this.errorMessage ?? this.lastError?.message ?? "Playback unavailable", t.append(n), e === "error" && this.lastError && !xe.has(this.lastError.code)) {
1120
+ if (n.className = "havik-status__msg", n.textContent = e === "ended" ? this.endedMessage : e === "waiting" ? "Starting soon…" : this.errorMessage ?? this.lastError?.message ?? "Playback unavailable", t.append(n), e === "error" && this.lastError && !Ce.has(this.lastError.code)) {
1108
1121
  let e = this.doc.createElement("button");
1109
1122
  e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Y("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), t.append(e);
1110
1123
  }
@@ -1116,17 +1129,14 @@ var Ce = class {
1116
1129
  this.setIcon(this.muteBtn, t ? "volumeMute" : "volumeHigh"), this.muteBtn.setAttribute("aria-label", t ? "Unmute" : "Mute"), this.volSlider.value = String(t ? 0 : e.volume);
1117
1130
  }
1118
1131
  seekableRange() {
1119
- let e = this.video;
1120
- if (e.seekable && e.seekable.length > 0) {
1121
- let t = e.seekable.start(0), n = e.seekable.end(e.seekable.length - 1);
1122
- if (n > t) return {
1123
- start: t,
1124
- end: n
1125
- };
1126
- }
1127
- return Number.isFinite(e.duration) && e.duration > 0 ? {
1128
- start: 0,
1129
- end: e.duration
1132
+ let e = this.video, t, n;
1133
+ if (e.seekable && e.seekable.length > 0) t = e.seekable.start(0), n = e.seekable.end(e.seekable.length - 1);
1134
+ else if (Number.isFinite(e.duration) && e.duration > 0) t = 0, n = e.duration;
1135
+ else return null;
1136
+ let r = this.player.getStats();
1137
+ return r.isLive !== !1 && r.targetLatencySeconds && r.targetLatencySeconds > 0 && (n = Math.max(t, n - r.targetLatencySeconds)), n > t ? {
1138
+ start: t,
1139
+ end: n
1130
1140
  } : null;
1131
1141
  }
1132
1142
  syncProgress() {
@@ -1193,17 +1203,17 @@ var Ce = class {
1193
1203
  renderQualityMenu() {
1194
1204
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1195
1205
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1196
- for (let n of e) this.menu.append(this.menuItem(we(n), t === n.index, () => this.player.setQuality(n.index)));
1206
+ for (let n of e) this.menu.append(this.menuItem(Ee(n), t === n.index, () => this.player.setQuality(n.index)));
1197
1207
  }
1198
1208
  renderAudioMenu() {
1199
1209
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1200
1210
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1201
- for (let t of e) this.menu.append(this.menuItem(Te(t), t.default, () => this.player.setAudioTrack(t.id)));
1211
+ for (let t of e) this.menu.append(this.menuItem(De(t), t.default, () => this.player.setAudioTrack(t.id)));
1202
1212
  }
1203
1213
  renderCcMenu() {
1204
1214
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1205
1215
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1206
- for (let t of e) this.menu.append(this.menuItem(Ee(t), !1, () => this.player.setTextTrack(t.id)));
1216
+ for (let t of e) this.menu.append(this.menuItem(Oe(t), !1, () => this.player.setTextTrack(t.id)));
1207
1217
  }
1208
1218
  onTracksChanged() {
1209
1219
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1309,27 +1319,27 @@ function X(e) {
1309
1319
  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");
1310
1320
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1311
1321
  }
1312
- function we(e) {
1322
+ function Ee(e) {
1313
1323
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1314
1324
  }
1315
- function Te(e) {
1325
+ function De(e) {
1316
1326
  return e.name || e.lang || `Track ${e.id}`;
1317
1327
  }
1318
- function Ee(e) {
1328
+ function Oe(e) {
1319
1329
  return e.name || e.lang || `Track ${e.id}`;
1320
1330
  }
1321
1331
  //#endregion
1322
1332
  //#region src/managed/index.ts
1323
- var De = 6e3, Oe = 45e3, ke = 2e3, Z = 3, Ae = 2e3, je = 480 * 1e3;
1333
+ var ke = 6e3, Z = 45e3, Ae = 2e3, je = 3, Me = 2e3, Ne = 480 * 1e3;
1324
1334
  async function Q(e) {
1325
1335
  if (!e.video || typeof e.video.play != "function") throw new t("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1326
1336
  if (!e.baseUrl) throw new t("INTERNAL", 0, "createPlayer: baseUrl is required");
1327
1337
  if (!e.matchUrn) throw new t("INVALID_URN", 0, "createPlayer: matchUrn is required");
1328
1338
  if (!e.credential) throw new t("INTERNAL", 0, "createPlayer: credential is required");
1329
- let n = new Me(e);
1339
+ let n = new Pe(e);
1330
1340
  return await n.start(), n;
1331
1341
  }
1332
- var Me = class {
1342
+ var Pe = class {
1333
1343
  opts;
1334
1344
  abort = new AbortController();
1335
1345
  listeners = {};
@@ -1408,13 +1418,13 @@ var Me = class {
1408
1418
  !r && !s() && l();
1409
1419
  }, e);
1410
1420
  };
1411
- d(De), this.armingSub = w({
1421
+ d(ke), this.armingSub = w({
1412
1422
  eventsBaseUrl: n,
1413
1423
  matchUrn: this.opts.matchUrn,
1414
1424
  credential: this.opts.credential,
1415
- onAlive: () => d(Oe),
1425
+ onAlive: () => d(Z),
1416
1426
  onState: (e) => {
1417
- r || s() || (e.state === "live" || e.state === "gone") && setTimeout(u, Math.random() * ke);
1427
+ r || s() || (e.state === "live" || e.state === "gone") && setTimeout(u, Math.random() * Ae);
1418
1428
  },
1419
1429
  onError: () => l()
1420
1430
  });
@@ -1459,8 +1469,8 @@ var Me = class {
1459
1469
  emit: (e) => this.onEngineEvent(e)
1460
1470
  }, a = this.opts.engine ?? "auto";
1461
1471
  if (a === "native") return new W(i);
1462
- if (a === "hls" || L()) return new he(i);
1463
- if (ge(this.opts.video)) return new W(i);
1472
+ if (a === "hls" || L()) return new _e(i);
1473
+ if (ve(this.opts.video)) return new W(i);
1464
1474
  throw new t("INTERNAL", 0, "no supported HLS playback engine in this browser");
1465
1475
  }
1466
1476
  onEngineEvent(e) {
@@ -1505,7 +1515,7 @@ var Me = class {
1505
1515
  r = 0, i = !1;
1506
1516
  },
1507
1517
  onError: () => {
1508
- this.destroyed || e !== this.loadGen || i || (r += 1, r >= Z && (i = !0, this.emit("warning", new t("NETWORK", 0, `live-state events unreachable at ${n} 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.`))));
1518
+ this.destroyed || e !== this.loadGen || i || (r += 1, r >= je && (i = !0, this.emit("warning", new t("NETWORK", 0, `live-state events unreachable at ${n} 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.`))));
1509
1519
  }
1510
1520
  });
1511
1521
  }
@@ -1527,7 +1537,7 @@ var Me = class {
1527
1537
  }
1528
1538
  t.controls = !1;
1529
1539
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
1530
- r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = Se({
1540
+ r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = we({
1531
1541
  root: n,
1532
1542
  video: t,
1533
1543
  player: this,
@@ -1550,7 +1560,7 @@ var Me = class {
1550
1560
  }
1551
1561
  startStats() {
1552
1562
  this.stopStats();
1553
- let e = Math.max(500, this.opts.statsIntervalMs ?? Ae);
1563
+ let e = Math.max(500, this.opts.statsIntervalMs ?? Me);
1554
1564
  this.statsTimer = setInterval(() => {
1555
1565
  this.engine && this.emit("stats", this.engine.getStats());
1556
1566
  }, e);
@@ -1560,7 +1570,7 @@ var Me = class {
1560
1570
  }
1561
1571
  scheduleLicenseRefresh() {
1562
1572
  this.stopRefresh();
1563
- let e = this.opts.licenseRefreshMs ?? je;
1573
+ let e = this.opts.licenseRefreshMs ?? Ne;
1564
1574
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
1565
1575
  }
1566
1576
  stopRefresh() {
@@ -1695,7 +1705,7 @@ function $(e) {
1695
1705
  }
1696
1706
  //#endregion
1697
1707
  //#region src/embed/host.ts
1698
- function Ne(e) {
1708
+ function Fe(e) {
1699
1709
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
1700
1710
  if (!e.src) throw Error("mountEmbed: opts.src is required");
1701
1711
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -1754,8 +1764,8 @@ function Ne(e) {
1754
1764
  }
1755
1765
  //#endregion
1756
1766
  //#region src/embed/page.ts
1757
- var Pe = /^[A-Za-z0-9:_.-]{1,128}$/;
1758
- function Fe() {
1767
+ var Ie = /^[A-Za-z0-9:_.-]{1,128}$/;
1768
+ function Le() {
1759
1769
  let e = window.HAVIK_EMBED_CONFIG;
1760
1770
  if (e) return {
1761
1771
  baseUrl: e.baseUrl ?? "",
@@ -1777,9 +1787,9 @@ function Fe() {
1777
1787
  waitForLive: !0
1778
1788
  };
1779
1789
  }
1780
- async function Ie() {
1781
- let e = Fe();
1782
- Re();
1790
+ async function Re() {
1791
+ let e = Le();
1792
+ Be();
1783
1793
  let n = document.createElement("video");
1784
1794
  n.setAttribute("playsinline", ""), n.muted = e.muted ?? !0, n.style.cssText = "width:100%;height:100%;object-fit:contain;background:#000;aspect-ratio:auto", document.body.appendChild(n);
1785
1795
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -1834,7 +1844,7 @@ async function Ie() {
1834
1844
  code: e.code,
1835
1845
  message: e.message
1836
1846
  })), e.on("autoplayblocked", () => {
1837
- i({ type: "oddin:autoplayblocked" }), Le(n, e);
1847
+ i({ type: "oddin:autoplayblocked" }), ze(n, e);
1838
1848
  });
1839
1849
  }, s = async (r) => {
1840
1850
  a?.destroy(), a = null;
@@ -1900,7 +1910,7 @@ async function Ie() {
1900
1910
  a?.retry();
1901
1911
  break;
1902
1912
  case "oddin:load":
1903
- typeof e.matchUrn == "string" && Pe.test(e.matchUrn) && s(e.matchUrn);
1913
+ typeof e.matchUrn == "string" && Ie.test(e.matchUrn) && s(e.matchUrn);
1904
1914
  break;
1905
1915
  case "oddin:destroy":
1906
1916
  c();
@@ -1914,18 +1924,18 @@ async function Ie() {
1914
1924
  protocol: 1
1915
1925
  });
1916
1926
  }
1917
- function Le(e, t) {
1927
+ function ze(e, t) {
1918
1928
  if (document.getElementById("havik-tap")) return;
1919
1929
  let n = document.createElement("button");
1920
1930
  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", () => {
1921
1931
  n.remove(), t.play();
1922
1932
  }), document.body.appendChild(n);
1923
1933
  }
1924
- function Re() {
1934
+ function Be() {
1925
1935
  let e = document.createElement("style");
1926
1936
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
1927
1937
  }
1928
1938
  //#endregion
1929
- export { G as ODDIN_THEME, t as PlaybackError, q as applyTheme, Ie as bootEmbed, k as clearDeviceId, Q as createPlayer, T as deriveEventsBaseUrl, oe as fetchCatalog, ce as fetchTournament, ue as flattenMatches, O as getDeviceId, N as isLowLatencyManifest, $ as isOddinMessage, j as licenseRequestHeaders, Ne as mountEmbed, y as pollToLive, x as resolveStream, w as subscribeLiveState, C as watchStatus };
1939
+ export { G as ODDIN_THEME, t as PlaybackError, q as applyTheme, Re as bootEmbed, k as clearDeviceId, Q as createPlayer, T as deriveEventsBaseUrl, oe as fetchCatalog, ce as fetchTournament, ue as flattenMatches, O as getDeviceId, N as isLowLatencyManifest, $ as isOddinMessage, j as licenseRequestHeaders, Fe as mountEmbed, y as pollToLive, x as resolveStream, w as subscribeLiveState, C as watchStatus };
1930
1940
 
1931
1941
  //# sourceMappingURL=index.js.map