@oddin-gg/havik-player 2.6.3 → 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
@@ -918,17 +918,17 @@ function Fe(e, t) {
918
918
  function Ie() {
919
919
  return e.isSupported();
920
920
  }
921
- var Le = 3, Re = 5, ze = 3, Be = 3e4, Ve = 12e3, He = 3e3, H = 6e3, Ue = 4e3, We = 2, Ge = 5, Ke = 2, qe = 2, Je = 2e3, Ye = 3e4, Xe = 2, Ze = 9, Qe = .5, $e = 64;
922
- function et(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) {
923
923
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
924
924
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
925
925
  return;
926
926
  }
927
- e.liveSyncDuration ??= Xe;
928
- let t = Math.max(12, e.liveSyncDuration + Ze);
929
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), tt(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);
930
930
  }
931
- function tt(e, t) {
931
+ function nt(e, t) {
932
932
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
933
933
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
934
934
  }
@@ -960,6 +960,11 @@ var U = class {
960
960
  licenseRetryTimer = null;
961
961
  onVisibility = null;
962
962
  onSeeking = null;
963
+ onPause = null;
964
+ onPlay = null;
965
+ pauseSuspendTimer = null;
966
+ loadSuspended = !1;
967
+ stopped = !1;
963
968
  sawLive = !1;
964
969
  lastEndSN = -1;
965
970
  lastAdvanceMs = 0;
@@ -1002,10 +1007,34 @@ var U = class {
1002
1007
  if (typeof t != "number") return;
1003
1008
  let n = typeof e.targetLatency == "number" && e.targetLatency > 0 ? e.targetLatency : 3;
1004
1009
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
1005
- }, 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
+ }
1006
1035
  }
1007
1036
  startLoad() {
1008
- 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));
1009
1038
  }
1010
1039
  watchDprChanges() {
1011
1040
  if (typeof matchMedia != "function") return;
@@ -1026,7 +1055,7 @@ var U = class {
1026
1055
  let t = e.liveSyncPosition;
1027
1056
  if (typeof t != "number" || !Number.isFinite(t)) return;
1028
1057
  let { video: n } = this.deps;
1029
- n.currentTime <= t + Qe || (n.currentTime = t);
1058
+ n.currentTime <= t + $e || (n.currentTime = t);
1030
1059
  }
1031
1060
  startHls() {
1032
1061
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
@@ -1066,12 +1095,12 @@ var U = class {
1066
1095
  } } : {},
1067
1096
  ...c
1068
1097
  };
1069
- et(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
1098
+ tt(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
1070
1099
  let f = new e(d);
1071
1100
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
1072
1101
  this.mediaRecoveries = 0, this.networkRecoveries = 0;
1073
1102
  let e = f.levels.map((e) => e.height).filter((e) => !!e);
1074
- 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" });
1075
1104
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
1076
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(() => {
1077
1106
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.starvationRebuilds = 0, this.recoveryResetTimer = null;
@@ -1082,7 +1111,7 @@ var U = class {
1082
1111
  let n = t?.frag?.stats, r = n?.loaded;
1083
1112
  typeof r == "number" && (this.bytesLoaded += r);
1084
1113
  let i = n?.loading;
1085
- 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 > $e && 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());
1086
1115
  let a = t?.frag?.url;
1087
1116
  if (a) {
1088
1117
  let e = L(a);
@@ -1162,7 +1191,7 @@ var U = class {
1162
1191
  return;
1163
1192
  }
1164
1193
  this.capTimer && clearTimeout(this.capTimer);
1165
- let t = e > this.sizeCapLevel ? Je : Ye;
1194
+ let t = e > this.sizeCapLevel ? Ye : Xe;
1166
1195
  this.capTimer = setTimeout(() => {
1167
1196
  this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1168
1197
  }, t);
@@ -1250,27 +1279,31 @@ var U = class {
1250
1279
  this.lastAdvanceMs = Date.now();
1251
1280
  return;
1252
1281
  }
1282
+ if (this.loadSuspended) {
1283
+ this.lastAdvanceMs = Date.now();
1284
+ return;
1285
+ }
1253
1286
  let e = Date.now() - this.lastAdvanceMs;
1254
1287
  !this.chaseSuspended && e > H && this.suspendChasing(), e > Ve && this.emitEnded("stalled");
1255
1288
  }
1256
1289
  checkStarvation() {
1257
1290
  let e = this.hls, { video: t } = this.deps;
1258
1291
  if (!e || this.endedEmitted || !this.sawLive || this.chaseSuspended) return;
1259
- if (typeof document < "u" && document.hidden) {
1292
+ if (typeof document < "u" && document.hidden || this.loadSuspended) {
1260
1293
  this.starvedTicks = 0;
1261
1294
  return;
1262
1295
  }
1263
- let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < H, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Ue;
1296
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < H, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > We;
1264
1297
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1265
1298
  this.starvedTicks = 0;
1266
1299
  return;
1267
1300
  }
1268
- if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < We)) {
1269
- if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Ke && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < qe)) {
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)) {
1270
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");
1271
1304
  return;
1272
1305
  }
1273
- if (!(this.starvationKicks >= Ge)) {
1306
+ if (!(this.starvationKicks >= Ke)) {
1274
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");
1275
1308
  try {
1276
1309
  e.stopLoad(), e.startLoad(-1);
@@ -1296,7 +1329,7 @@ var U = class {
1296
1329
  }));
1297
1330
  }
1298
1331
  stop() {
1299
- 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);
1300
1333
  try {
1301
1334
  this.hls?.stopLoad();
1302
1335
  } catch {}
@@ -1305,7 +1338,7 @@ var U = class {
1305
1338
  } catch {}
1306
1339
  }
1307
1340
  destroy() {
1308
- 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) {
1309
1342
  try {
1310
1343
  this.hls.destroy();
1311
1344
  } catch (e) {
@@ -1393,11 +1426,11 @@ var U = class {
1393
1426
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1394
1427
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1395
1428
  }
1396
- }, nt = 15e3, rt = 100, W = 1, it = 1e3, G = 3e4, at = 1e3, ot = {
1429
+ }, rt = 15e3, it = 100, W = 1, at = 1e3, G = 3e4, ot = 1e3, st = {
1397
1430
  inline: "PRESENTATION_INLINE",
1398
1431
  fullscreen: "PRESENTATION_FULLSCREEN",
1399
1432
  pip: "PRESENTATION_PIP"
1400
- }, st = {
1433
+ }, ct = {
1401
1434
  widevine: "KEY_SYSTEM_WIDEVINE",
1402
1435
  fairplay: "KEY_SYSTEM_FAIRPLAY",
1403
1436
  none: "KEY_SYSTEM_NONE"
@@ -1405,7 +1438,7 @@ var U = class {
1405
1438
  function K(e, t) {
1406
1439
  return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
1407
1440
  }
1408
- var ct = /* @__PURE__ */ new Set([
1441
+ var lt = /* @__PURE__ */ new Set([
1409
1442
  "INVALID_URN",
1410
1443
  "NOT_FOUND",
1411
1444
  "GONE",
@@ -1425,7 +1458,7 @@ var ct = /* @__PURE__ */ new Set([
1425
1458
  ended: "PLAYER_STATE_ENDED",
1426
1459
  error: "PLAYER_STATE_ERROR"
1427
1460
  };
1428
- function lt(e) {
1461
+ function ut(e) {
1429
1462
  try {
1430
1463
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1431
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);
@@ -1433,7 +1466,7 @@ function lt(e) {
1433
1466
  return;
1434
1467
  }
1435
1468
  }
1436
- var ut = class {
1469
+ var dt = class {
1437
1470
  opts;
1438
1471
  url;
1439
1472
  now;
@@ -1505,7 +1538,7 @@ var ut = class {
1505
1538
  constructor(e) {
1506
1539
  this.opts = {
1507
1540
  ...e,
1508
- heartbeatMs: e.heartbeatMs ?? nt
1541
+ heartbeatMs: e.heartbeatMs ?? rt
1509
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;
1510
1543
  }
1511
1544
  start() {
@@ -1515,7 +1548,7 @@ var ut = class {
1515
1548
  sdkVersion: this.opts.sdkVersion,
1516
1549
  ...this.opts.environment
1517
1550
  };
1518
- this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), it), 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());
1519
1552
  }
1520
1553
  noteStateChange(e, t, n, r) {
1521
1554
  if (this.endedFlag || e === t) return;
@@ -1529,12 +1562,12 @@ var ut = class {
1529
1562
  let a = {
1530
1563
  fromState: q[e] ?? "PLAYER_STATE_UNSPECIFIED",
1531
1564
  toState: q[t] ?? "PLAYER_STATE_UNSPECIFIED",
1532
- presentation: ot[this.presentation]
1565
+ presentation: st[this.presentation]
1533
1566
  }, o = this.causeOf(e, t, i);
1534
1567
  o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1535
1568
  }
1536
1569
  seekInduced(e) {
1537
- return this.lastSeekMs >= 0 && e - this.lastSeekMs < at;
1570
+ return this.lastSeekMs >= 0 && e - this.lastSeekMs < ot;
1538
1571
  }
1539
1572
  causeOf(e, t, n) {
1540
1573
  if (t === "error") return "TRANSITION_CAUSE_ERROR";
@@ -1602,7 +1635,7 @@ var ut = class {
1602
1635
  let r = n ?? "";
1603
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";
1604
1637
  if (e === "DRM_CLIENT") return "ERROR_STAGE_DRM";
1605
- if (ct.has(e)) return "ERROR_STAGE_RESOLVE";
1638
+ if (lt.has(e)) return "ERROR_STAGE_RESOLVE";
1606
1639
  let i = t ?? "";
1607
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";
1608
1641
  }
@@ -1654,7 +1687,7 @@ var ut = class {
1654
1687
  maybeEmitMediaInfo(e) {
1655
1688
  if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
1656
1689
  let t = {};
1657
- e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = st[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);
1658
1691
  let n = JSON.stringify(t);
1659
1692
  n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
1660
1693
  }
@@ -1694,7 +1727,7 @@ var ut = class {
1694
1727
  };
1695
1728
  }
1696
1729
  enqueue(e) {
1697
- for (this.pending.push(this.wrap(e)); this.pending.length > rt;) {
1730
+ for (this.pending.push(this.wrap(e)); this.pending.length > it;) {
1698
1731
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1699
1732
  this.pending.splice(e >= 0 ? e : 0, 1);
1700
1733
  }
@@ -1717,7 +1750,7 @@ var ut = class {
1717
1750
  };
1718
1751
  //#endregion
1719
1752
  //#region src/core/environment.ts
1720
- function dt() {
1753
+ function ft() {
1721
1754
  if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
1722
1755
  let e;
1723
1756
  try {
@@ -1742,14 +1775,14 @@ function dt() {
1742
1775
  integration: "INTEGRATION_KIND_IFRAME"
1743
1776
  };
1744
1777
  }
1745
- function ft() {
1778
+ function pt() {
1746
1779
  if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
1747
1780
  let e = navigator.userAgentData, t = e?.platform ?? "";
1748
1781
  if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
1749
1782
  let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
1750
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";
1751
1784
  }
1752
- function pt() {
1785
+ function mt() {
1753
1786
  let e = typeof navigator < "u" ? navigator.connection : void 0;
1754
1787
  if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
1755
1788
  let t = {
@@ -1768,17 +1801,17 @@ function pt() {
1768
1801
  }
1769
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;
1770
1803
  }
1771
- function mt(e) {
1772
- let t = dt(), n = {
1773
- deviceClass: ft(),
1804
+ function ht(e) {
1805
+ let t = ft(), n = {
1806
+ deviceClass: pt(),
1774
1807
  integration: t.integration,
1775
- ...pt()
1808
+ ...mt()
1776
1809
  };
1777
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);
1778
1811
  let r = e.getBoundingClientRect?.();
1779
1812
  return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
1780
1813
  }
1781
- async function ht() {
1814
+ async function gt() {
1782
1815
  let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
1783
1816
  if (!e?.getHighEntropyValues) return {};
1784
1817
  try {
@@ -1790,7 +1823,7 @@ async function ht() {
1790
1823
  }
1791
1824
  //#endregion
1792
1825
  //#region src/core/version.ts
1793
- var gt = "2.6.3", J = {
1826
+ var _t = "2.6.4", J = {
1794
1827
  accent: "#E1B600",
1795
1828
  accentText: "#1B1C23",
1796
1829
  background: "#0e0f13",
@@ -1823,15 +1856,15 @@ function X(e, t) {
1823
1856
  }
1824
1857
  //#endregion
1825
1858
  //#region src/ui/styles.ts
1826
- var Z = "havik-player-styles", _t = "\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";
1827
- function vt(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) {
1828
1861
  if (e.getElementById(Z)) return;
1829
1862
  let t = e.createElement("style");
1830
- t.id = Z, t.textContent = _t, e.head.appendChild(t);
1863
+ t.id = Z, t.textContent = vt, e.head.appendChild(t);
1831
1864
  }
1832
1865
  //#endregion
1833
1866
  //#region src/ui/icons.ts
1834
- var yt = {
1867
+ var bt = {
1835
1868
  play: "<path d=\"M8 5v14l11-7z\"/>",
1836
1869
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1837
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\"/>",
@@ -1845,20 +1878,20 @@ var yt = {
1845
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\"/>"
1846
1879
  };
1847
1880
  function Q(e) {
1848
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${yt[e]}</svg>`;
1881
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${bt[e]}</svg>`;
1849
1882
  }
1850
1883
  //#endregion
1851
1884
  //#region src/ui/controls.ts
1852
- var bt = 3e3, xt = /* @__PURE__ */ new Set([
1885
+ var xt = 3e3, St = /* @__PURE__ */ new Set([
1853
1886
  "GONE",
1854
1887
  "NOT_FOUND",
1855
1888
  "INVALID_URN",
1856
1889
  "UNAUTHORIZED"
1857
1890
  ]);
1858
- function St(e) {
1859
- return new Ct(e);
1891
+ function Ct(e) {
1892
+ return new wt(e);
1860
1893
  }
1861
- var Ct = class {
1894
+ var wt = class {
1862
1895
  root;
1863
1896
  video;
1864
1897
  player;
@@ -1900,7 +1933,7 @@ var Ct = class {
1900
1933
  scrubbing = !1;
1901
1934
  offFns = [];
1902
1935
  constructor(e) {
1903
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? bt, 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, vt(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");
1904
1937
  let t = (e, t, n) => {
1905
1938
  let r = this.doc.createElement(e);
1906
1939
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1991,7 +2024,7 @@ var Ct = class {
1991
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);
1992
2025
  } else {
1993
2026
  let r = this.doc.createElement("div");
1994
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !xt.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)) {
1995
2028
  let e = this.doc.createElement("button");
1996
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);
1997
2030
  }
@@ -2032,7 +2065,7 @@ var Ct = class {
2032
2065
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
2033
2066
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
2034
2067
  }
2035
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${wt(this.video.currentTime)} / ${wt(this.video.duration)}`);
2068
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Tt(this.video.currentTime)} / ${Tt(this.video.duration)}`);
2036
2069
  }
2037
2070
  syncLive() {
2038
2071
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -2082,17 +2115,17 @@ var Ct = class {
2082
2115
  renderQualityMenu() {
2083
2116
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
2084
2117
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
2085
- for (let n of e) this.menu.append(this.menuItem(Tt(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)));
2086
2119
  }
2087
2120
  renderAudioMenu() {
2088
2121
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
2089
2122
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
2090
- for (let t of e) this.menu.append(this.menuItem(Et(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)));
2091
2124
  }
2092
2125
  renderCcMenu() {
2093
2126
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
2094
2127
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
2095
- for (let t of e) this.menu.append(this.menuItem(Dt(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)));
2096
2129
  }
2097
2130
  onTracksChanged() {
2098
2131
  this.openMenu && this.renderMenu(this.openMenu);
@@ -2192,32 +2225,32 @@ var Ct = class {
2192
2225
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
2193
2226
  }
2194
2227
  };
2195
- function wt(e) {
2228
+ function Tt(e) {
2196
2229
  (!Number.isFinite(e) || e < 0) && (e = 0);
2197
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");
2198
2231
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
2199
2232
  }
2200
- function Tt(e) {
2233
+ function Et(e) {
2201
2234
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
2202
2235
  }
2203
- function Et(e) {
2236
+ function Dt(e) {
2204
2237
  return e.name || e.lang || `Track ${e.id}`;
2205
2238
  }
2206
- function Dt(e) {
2239
+ function Ot(e) {
2207
2240
  return e.name || e.lang || `Track ${e.id}`;
2208
2241
  }
2209
2242
  //#endregion
2210
2243
  //#region src/managed/index.ts
2211
- var Ot = 6e3, kt = 45e3, At = 2e3, jt = 3, Mt = 2e3, Nt = 48e4;
2212
- async function Pt(e) {
2244
+ var kt = 6e3, At = 45e3, jt = 2e3, Mt = 3, Nt = 2e3, Pt = 48e4;
2245
+ async function Ft(e) {
2213
2246
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
2214
2247
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
2215
2248
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
2216
2249
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
2217
- let t = new Ft(e);
2250
+ let t = new It(e);
2218
2251
  return await t.start(), t;
2219
2252
  }
2220
- var Ft = class {
2253
+ var It = class {
2221
2254
  opts;
2222
2255
  abort = new AbortController();
2223
2256
  listeners = {};
@@ -2304,7 +2337,7 @@ var Ft = class {
2304
2337
  setTimeout(() => {
2305
2338
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
2306
2339
  phase: i ? "unavailable" : "tooEarly",
2307
- retryInMs: kt,
2340
+ retryInMs: At,
2308
2341
  attempt: 1,
2309
2342
  elapsedMs: 0,
2310
2343
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -2343,13 +2376,13 @@ var Ft = class {
2343
2376
  !a && !p() && h();
2344
2377
  }, e);
2345
2378
  };
2346
- _(Ot), this.armingSub = E({
2379
+ _(kt), this.armingSub = E({
2347
2380
  eventsBaseUrl: t,
2348
2381
  matchUrn: this.opts.matchUrn,
2349
2382
  credential: this.opts.credential,
2350
- onAlive: () => _(kt),
2383
+ onAlive: () => _(At),
2351
2384
  onState: (e) => {
2352
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * At);
2385
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * jt);
2353
2386
  },
2354
2387
  onError: () => h()
2355
2388
  });
@@ -2391,14 +2424,14 @@ var Ft = class {
2391
2424
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
2392
2425
  let n = e.analytics?.sid;
2393
2426
  if (!n) return;
2394
- let r = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? lt(this.opts.baseUrl);
2427
+ let r = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? ut(this.opts.baseUrl);
2395
2428
  if (!r) return;
2396
- let i = this.opts.video, a = new ut({
2429
+ let i = this.opts.video, a = new dt({
2397
2430
  endpoint: r,
2398
2431
  sid: n,
2399
2432
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
2400
- sdkVersion: gt,
2401
- environment: mt(i),
2433
+ sdkVersion: _t,
2434
+ environment: ht(i),
2402
2435
  sample: () => {
2403
2436
  let e = this.engine?.getStats() ?? I, t = i.buffered.length ? Math.max(0, i.buffered.end(i.buffered.length - 1) - i.currentTime) : 0;
2404
2437
  return {
@@ -2414,7 +2447,7 @@ var Ft = class {
2414
2447
  let o = () => a.noteSeek(), s = () => a.noteVolume(i.muted, i.volume);
2415
2448
  i.addEventListener("seeking", o), i.addEventListener("volumechange", s), this.detachMediaListeners = () => {
2416
2449
  i.removeEventListener("seeking", o), i.removeEventListener("volumechange", s);
2417
- }, a.start(), a.seedVolume(i.muted, i.volume), a.notePresentation(this.currentPresentation()), ht().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");
2418
2451
  }
2419
2452
  currentPresentation() {
2420
2453
  let e = this.opts.video;
@@ -2485,7 +2518,7 @@ var Ft = class {
2485
2518
  r = 0, i = !1;
2486
2519
  },
2487
2520
  onError: () => {
2488
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= jt && (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.`))));
2489
2522
  }
2490
2523
  });
2491
2524
  }
@@ -2507,7 +2540,7 @@ var Ft = class {
2507
2540
  scheduleRejoin(e) {
2508
2541
  this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
2509
2542
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
2510
- }, Math.random() * At));
2543
+ }, Math.random() * jt));
2511
2544
  }
2512
2545
  cancelRejoin() {
2513
2546
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -2531,7 +2564,7 @@ var Ft = class {
2531
2564
  }
2532
2565
  t.controls = !1;
2533
2566
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2534
- r && r.insertBefore(n, t), n.appendChild(t), X(n, this.opts.theme), this.container = n, this.controlBar = St({
2567
+ r && r.insertBefore(n, t), n.appendChild(t), X(n, this.opts.theme), this.container = n, this.controlBar = Ct({
2535
2568
  root: n,
2536
2569
  video: t,
2537
2570
  player: this,
@@ -2561,7 +2594,7 @@ var Ft = class {
2561
2594
  }
2562
2595
  startStats() {
2563
2596
  this.stopStats();
2564
- let e = Math.max(500, this.opts.statsIntervalMs ?? Mt);
2597
+ let e = Math.max(500, this.opts.statsIntervalMs ?? Nt);
2565
2598
  this.statsTimer = setInterval(() => {
2566
2599
  if (!this.engine) return;
2567
2600
  let e = this.engine.getStats();
@@ -2573,7 +2606,7 @@ var Ft = class {
2573
2606
  }
2574
2607
  scheduleLicenseRefresh() {
2575
2608
  this.stopRefresh();
2576
- let e = this.opts.licenseRefreshMs ?? Nt;
2609
+ let e = this.opts.licenseRefreshMs ?? Pt;
2577
2610
  e && this._descriptor?.drmEnabled && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2578
2611
  }
2579
2612
  stopRefresh() {
@@ -2725,7 +2758,7 @@ function $(e) {
2725
2758
  }
2726
2759
  //#endregion
2727
2760
  //#region src/embed/host.ts
2728
- function It(e) {
2761
+ function Lt(e) {
2729
2762
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2730
2763
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2731
2764
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2784,8 +2817,8 @@ function It(e) {
2784
2817
  }
2785
2818
  //#endregion
2786
2819
  //#region src/embed/page.ts
2787
- var Lt = /^[A-Za-z0-9:_.-]{1,128}$/;
2788
- function Rt() {
2820
+ var Rt = /^[A-Za-z0-9:_.-]{1,128}$/;
2821
+ function zt() {
2789
2822
  let e = window.HAVIK_EMBED_CONFIG;
2790
2823
  if (e) return {
2791
2824
  baseUrl: e.baseUrl ?? "",
@@ -2807,9 +2840,9 @@ function Rt() {
2807
2840
  waitForLive: !0
2808
2841
  };
2809
2842
  }
2810
- async function zt() {
2811
- let e = Rt();
2812
- Vt();
2843
+ async function Bt() {
2844
+ let e = zt();
2845
+ Ht();
2813
2846
  let t = document.createElement("video");
2814
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);
2815
2848
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2864,12 +2897,12 @@ async function zt() {
2864
2897
  code: e.code,
2865
2898
  message: e.message
2866
2899
  })), e.on("autoplayblocked", () => {
2867
- i({ type: "oddin:autoplayblocked" }), Bt(t, e);
2900
+ i({ type: "oddin:autoplayblocked" }), Vt(t, e);
2868
2901
  });
2869
2902
  }, s = async (r) => {
2870
2903
  a?.destroy(), a = null;
2871
2904
  try {
2872
- let n = await Pt({
2905
+ let n = await Ft({
2873
2906
  video: t,
2874
2907
  baseUrl: e.baseUrl,
2875
2908
  matchUrn: r,
@@ -2930,7 +2963,7 @@ async function zt() {
2930
2963
  a?.retry();
2931
2964
  break;
2932
2965
  case "oddin:load":
2933
- typeof e.matchUrn == "string" && Lt.test(e.matchUrn) && s(e.matchUrn);
2966
+ typeof e.matchUrn == "string" && Rt.test(e.matchUrn) && s(e.matchUrn);
2934
2967
  break;
2935
2968
  case "oddin:destroy": c();
2936
2969
  }
@@ -2942,18 +2975,18 @@ async function zt() {
2942
2975
  protocol: 1
2943
2976
  });
2944
2977
  }
2945
- function Bt(e, t) {
2978
+ function Vt(e, t) {
2946
2979
  if (document.getElementById("havik-tap")) return;
2947
2980
  let n = document.createElement("button");
2948
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", () => {
2949
2982
  n.remove(), t.play();
2950
2983
  }), document.body.appendChild(n);
2951
2984
  }
2952
- function Vt() {
2985
+ function Ht() {
2953
2986
  let e = document.createElement("style");
2954
2987
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2955
2988
  }
2956
2989
  //#endregion
2957
- export { J as ODDIN_THEME, n as PlaybackError, X as applyTheme, zt as bootEmbed, _e as clearDeviceId, Pt 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, It 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 };
2958
2991
 
2959
2992
  //# sourceMappingURL=index.js.map