@oddin-gg/havik-player 2.21.0 → 2.22.1

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
@@ -1484,21 +1484,31 @@ function Ft(e, t) {
1484
1484
  function It() {
1485
1485
  return e.isSupported();
1486
1486
  }
1487
- var Lt = 1e6, Rt = 20, zt = 1e4, Bt = 3, Vt = 5, Ht = 3, Ut = 1e4, Wt = 3e4, Gt = 12e3, Kt = 3e3, qt = .02, Jt = 6e3, Yt = .5, Xt = 3, Zt = .5, Qt = 1.1, $t = .25, en = 5e3, tn = 4e3, nn = 2, rn = 5, an = 2, on = 2, sn = 4, cn = 4, ln = 2e3, un = 3e4, dn = .2, fn = 30, pn = 2, mn = 2, hn = 9, gn = .5, _n = 64;
1488
- function vn(e) {
1487
+ var Lt = 1e6, Rt = 20, zt = 1e4, Bt = 3, Vt = 5, Ht = 3, Ut = 1e4, Wt = 3e4, Gt = 12e3, Kt = 3e3, qt = .02, Jt = 6e3, Yt = .5, Xt = 3, Zt = .5, Qt = 1.1, $t = .25, en = 5e3, tn = 4e3, nn = 2, rn = 5, an = 2, on = 2, sn = 4, cn = 4, ln = 2e3, un = 3e4, dn = 1, fn = 2;
1488
+ function pn(e, t, n) {
1489
+ if (n <= 0 || t < 0 || t >= e.length) return t;
1490
+ let r = (t, n) => e[t].width === e[n].width && e[t].height === e[n].height, i = (t) => {
1491
+ for (; t + 1 < e.length && r(t + 1, t);) t++;
1492
+ return t;
1493
+ }, a = i(t);
1494
+ for (let t = 0; t < n && a + 1 < e.length; t++) a = i(a + 1);
1495
+ return a;
1496
+ }
1497
+ var mn = .2, hn = 30, gn = 2, _n = 2, vn = 2, yn = 9, bn = .5, xn = 64;
1498
+ function Sn(e) {
1489
1499
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
1490
1500
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
1491
1501
  return;
1492
1502
  }
1493
- e.liveSyncDuration ??= mn;
1494
- let t = Math.max(12, e.liveSyncDuration + hn);
1495
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), yn(e, e.liveSyncDuration);
1503
+ e.liveSyncDuration ??= vn;
1504
+ let t = Math.max(12, e.liveSyncDuration + yn);
1505
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Cn(e, e.liveSyncDuration);
1496
1506
  }
1497
- function yn(e, t) {
1507
+ function Cn(e, t) {
1498
1508
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
1499
1509
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
1500
1510
  }
1501
- var bn = class {
1511
+ var wn = class {
1502
1512
  name = "hls";
1503
1513
  hls = null;
1504
1514
  deps;
@@ -1574,6 +1584,7 @@ var bn = class {
1574
1584
  fpsLastFrames = -1;
1575
1585
  fpsLastDropped = -1;
1576
1586
  fpsBadTicks = 0;
1587
+ fpsGraceTicks = 0;
1577
1588
  fpsDownCaps = 0;
1578
1589
  sizeCapManaged = !0;
1579
1590
  dprMedia = null;
@@ -1650,7 +1661,7 @@ var bn = class {
1650
1661
  let t = e.liveSyncPosition;
1651
1662
  if (typeof t != "number" || !Number.isFinite(t)) return;
1652
1663
  let { video: n } = this.deps;
1653
- n.currentTime <= t + gn || (n.currentTime = t);
1664
+ n.currentTime <= t + bn || (n.currentTime = t);
1654
1665
  }
1655
1666
  reanchorPartTimeline(e, t) {
1656
1667
  let { frag: n, part: r } = t;
@@ -1724,7 +1735,7 @@ var bn = class {
1724
1735
  }), t = Mt(e);
1725
1736
  t && (this.prefetch = e, f.fLoader = t);
1726
1737
  }
1727
- vn(f), this.catchUpRate = typeof f.maxLiveSyncPlaybackRate == "number" ? f.maxLiveSyncPlaybackRate : 1, this.catchUpEngaged = !1, this.catchUpRate > 1 && (f.maxLiveSyncPlaybackRate = 1), this.sizeCapManaged = f.capLevelToPlayerSize !== !0, this.fpsCapManaged = !(c && "capLevelOnFPSDrop" in c), this.loadStarted = f.autoStartLoad !== !1;
1738
+ Sn(f), this.catchUpRate = typeof f.maxLiveSyncPlaybackRate == "number" ? f.maxLiveSyncPlaybackRate : 1, this.catchUpEngaged = !1, this.catchUpRate > 1 && (f.maxLiveSyncPlaybackRate = 1), this.sizeCapManaged = f.capLevelToPlayerSize !== !0, this.fpsCapManaged = !(c && "capLevelOnFPSDrop" in c), this.loadStarted = f.autoStartLoad !== !1;
1728
1739
  let p = new e(f);
1729
1740
  if (this.hls = p, p.on(e.Events.FRAG_PARSED, (e, t) => this.reanchorPartTimeline(p, t)), p.on(e.Events.MANIFEST_PARSED, () => {
1730
1741
  this.mediaRecoveries = 0, this.networkRecoveries = 0, u && !this.deps.hlsConfig?.requestMediaKeySystemAccessFunc && this.dropLevelsWidevineCannotPlay(p), this.measureLadder(p), this.applySizeCap(), this.loadSuspended = !1, t.paused && this.schedulePauseSuspend(), l({ type: "ready" });
@@ -1739,7 +1750,7 @@ var bn = class {
1739
1750
  let n = t?.frag?.stats, r = n?.loaded;
1740
1751
  typeof r == "number" && (this.bytesLoaded += r), !this.reportedFirstBytes && typeof r == "number" && r > 0 && (this.reportedFirstBytes = !0, l({ type: "firstBytes" }));
1741
1752
  let i = n?.loading;
1742
- 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 > _n && this.requestTimesMs.shift());
1753
+ 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 > xn && this.requestTimesMs.shift());
1743
1754
  let a = t?.frag?.url;
1744
1755
  if (a) {
1745
1756
  let e = ot(a);
@@ -1767,7 +1778,7 @@ var bn = class {
1767
1778
  }, Kt), p.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1768
1779
  this.renditionSwitches += 1;
1769
1780
  let n = this.lastLevelIndex >= 0 ? p.levels[this.lastLevelIndex] : void 0, r = p.levels[t.level];
1770
- n && r && r.bitrate !== n.bitrate && (r.bitrate > n.bitrate ? this.upshifts += 1 : this.downshifts += 1), this.lastLevelIndex = t.level, l({
1781
+ n && r && r.bitrate !== n.bitrate && (r.bitrate > n.bitrate ? this.upshifts += 1 : this.downshifts += 1), this.lastLevelIndex = t.level, this.armFrameDropGrace(), l({
1771
1782
  type: "qualitychange",
1772
1783
  index: t.level
1773
1784
  });
@@ -1812,8 +1823,12 @@ var bn = class {
1812
1823
  if (!e || e.levels.length === 0) return null;
1813
1824
  let { video: n } = this.deps, r = n.getBoundingClientRect(), i = r.width, a = r.height;
1814
1825
  if (!i && !a && (i = n.width || 0, a = n.height || 0), !(i > 0) || !(a > 0)) return null;
1815
- let o = e.config ?? {}, s = 1;
1816
- return !o.ignoreDevicePixelRatio && typeof self < "u" && self.devicePixelRatio > 0 && (s = self.devicePixelRatio), s = Math.min(s, o.maxDevicePixelRatio ?? Infinity), t.getMaxLevelByMediaSize(e.levels, i * s, a * s);
1826
+ let o = e.config ?? {}, s = typeof self < "u" && self.devicePixelRatio > 0 ? self.devicePixelRatio : 1, c = o.ignoreDevicePixelRatio ? 1 : s;
1827
+ c = Math.min(c, o.maxDevicePixelRatio ?? Infinity);
1828
+ let l = t.getMaxLevelByMediaSize(e.levels, i * c, a * c), u = this.deps.sizeCapHeadroom;
1829
+ if (u == null && s >= fn) return l;
1830
+ let d = u ?? dn, f = Number.isNaN(d) ? dn : d === Infinity ? e.levels.length : Math.max(0, Math.floor(d));
1831
+ return pn(e.levels, l, f);
1817
1832
  }
1818
1833
  onMediaResize() {
1819
1834
  if (!this.sizeCapManaged) return;
@@ -2013,6 +2028,9 @@ var bn = class {
2013
2028
  e.bitrate <= this.fpsCapBitrate && i && (n = r);
2014
2029
  }), n < 0 ? 0 : n;
2015
2030
  }
2031
+ armFrameDropGrace() {
2032
+ this.fpsGraceTicks > 0 || (this.fpsGraceTicks = _n, this.fpsBadTicks = 0);
2033
+ }
2016
2034
  resetFrameDropWindow() {
2017
2035
  this.fpsLastFrames = -1, this.fpsLastDropped = -1, this.fpsBadTicks = 0;
2018
2036
  }
@@ -2023,15 +2041,19 @@ var bn = class {
2023
2041
  return;
2024
2042
  }
2025
2043
  let { totalFrames: n, dropped: r } = B(t), i = this.fpsLastFrames < 0, a = n - this.fpsLastFrames, o = r - this.fpsLastDropped;
2026
- if (this.fpsLastFrames = n, this.fpsLastDropped = r, i || a < fn || o < 0) {
2044
+ if (this.fpsLastFrames = n, this.fpsLastDropped = r, i || a < hn || o < 0) {
2027
2045
  this.fpsBadTicks = 0;
2028
2046
  return;
2029
2047
  }
2030
- if (o <= dn * a) {
2048
+ if (this.fpsGraceTicks > 0) {
2049
+ --this.fpsGraceTicks, this.fpsBadTicks = 0;
2050
+ return;
2051
+ }
2052
+ if (o <= mn * a) {
2031
2053
  this.fpsBadTicks = 0;
2032
2054
  return;
2033
2055
  }
2034
- if (this.fpsBadTicks += 1, this.fpsBadTicks < pn) return;
2056
+ if (this.fpsBadTicks += 1, this.fpsBadTicks < gn) return;
2035
2057
  this.fpsBadTicks = 0;
2036
2058
  let s = e.levels, c = s[e.currentLevel];
2037
2059
  if (!c) return;
@@ -2130,7 +2152,7 @@ var bn = class {
2130
2152
  } catch (t) {
2131
2153
  console.warn(`havik-player: hls.js destroy() threw during ${e}`, t);
2132
2154
  }
2133
- this.hls = null, this.startHls();
2155
+ this.hls = null, this.startHls(), this.armFrameDropGrace();
2134
2156
  }
2135
2157
  }
2136
2158
  onError(t) {
@@ -2189,19 +2211,19 @@ var bn = class {
2189
2211
  let o = t.response?.code ?? 0, s = t.details ?? t.type, c = t.reason ?? t.error?.message ?? "", l = r === "DRM" && o === 403 && t.details === e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED ? i(Et(t.networkDetails)) : void 0;
2190
2212
  return new n(r === "DRM" ? a(o, l) : o === 401 ? "UNAUTHORIZED" : o === 403 ? "FORBIDDEN" : "INTERNAL", o, `${r} error: ${s}${c ? ` (${c})` : ""}`, l ? { serverCode: l } : void 0);
2191
2213
  }
2192
- }, xn = 15e3, Sn = 6e5, Cn = 3e5, wn = 100, q = 1, Tn = 1e3, En = 3e4, Dn = 1e3, On = {
2214
+ }, Tn = 15e3, En = 6e5, Dn = 3e5, On = 100, q = 1, kn = 1e3, An = 3e4, jn = 1e3, Mn = {
2193
2215
  inline: "PRESENTATION_INLINE",
2194
2216
  fullscreen: "PRESENTATION_FULLSCREEN",
2195
2217
  pip: "PRESENTATION_PIP"
2196
- }, kn = {
2218
+ }, Nn = {
2197
2219
  widevine: "KEY_SYSTEM_WIDEVINE",
2198
2220
  fairplay: "KEY_SYSTEM_FAIRPLAY",
2199
2221
  none: "KEY_SYSTEM_NONE"
2200
2222
  };
2201
- function An(e, t) {
2223
+ function Pn(e, t) {
2202
2224
  return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
2203
2225
  }
2204
- var jn = /* @__PURE__ */ new Set([
2226
+ var Fn = /* @__PURE__ */ new Set([
2205
2227
  "INVALID_URN",
2206
2228
  "NOT_FOUND",
2207
2229
  "GEO_DENIED",
@@ -2222,7 +2244,7 @@ var jn = /* @__PURE__ */ new Set([
2222
2244
  ended: "PLAYER_STATE_ENDED",
2223
2245
  error: "PLAYER_STATE_ERROR"
2224
2246
  };
2225
- function Mn(e) {
2247
+ function In(e) {
2226
2248
  try {
2227
2249
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
2228
2250
  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);
@@ -2230,8 +2252,8 @@ function Mn(e) {
2230
2252
  return;
2231
2253
  }
2232
2254
  }
2233
- var Nn = (e, t) => e.replace(/\/$/, "").toLowerCase() === t.replace(/\/$/, "").toLowerCase(), Pn = /* @__PURE__ */ new Set(), Y = /* @__PURE__ */ new Set(), X = /* @__PURE__ */ new Set(), Fn = (e) => `this page's Content-Security-Policy blocks QoE beacons. Add ${e} to connect-src; until then none are sent.`;
2234
- function In(e) {
2255
+ var Ln = (e, t) => e.replace(/\/$/, "").toLowerCase() === t.replace(/\/$/, "").toLowerCase(), Rn = /* @__PURE__ */ new Set(), Y = /* @__PURE__ */ new Set(), X = /* @__PURE__ */ new Set(), zn = (e) => `this page's Content-Security-Policy blocks QoE beacons. Add ${e} to connect-src; until then none are sent.`;
2256
+ function Bn(e) {
2235
2257
  if (e.disposition !== "enforce" || e.effectiveDirective !== "connect-src") return;
2236
2258
  let t;
2237
2259
  try {
@@ -2240,19 +2262,19 @@ function In(e) {
2240
2262
  } catch {
2241
2263
  return;
2242
2264
  }
2243
- if (Pn.has(t) && !Y.has(t)) {
2244
- Y.add(t), _(Fn(new URL(t).origin));
2265
+ if (Rn.has(t) && !Y.has(t)) {
2266
+ Y.add(t), _(zn(new URL(t).origin));
2245
2267
  for (let e of X) e.noteCspBlocked();
2246
2268
  }
2247
2269
  }
2248
- function Ln(e, t) {
2270
+ function Z(e, t) {
2249
2271
  return _(t, e), { disabledReason: t };
2250
2272
  }
2251
- function Rn(e, t, n) {
2252
- if (n) return t && !Nn(t, n) && _(`analytics.endpoint "${t}" ignored; the server named "${n}".`), { endpoint: n };
2273
+ function Vn(e, t, n) {
2274
+ if (n) return t && !Ln(t, n) && _(`analytics.endpoint "${t}" ignored; the server named "${n}".`), { endpoint: n };
2253
2275
  if (t) return { endpoint: t };
2254
- if (t !== void 0) return Ln("empty-endpoint", "analytics is disabled: no QoE beacons will be sent. analytics.endpoint was supplied but empty. Pass a real beacons origin, analytics: { endpoint: 'https://beacons[-dev].<domain>' }, or omit the option to derive it from baseUrl (or analytics: false to opt out).");
2255
- let r = Mn(e);
2276
+ if (t !== void 0) return Z("empty-endpoint", "analytics is disabled: no QoE beacons will be sent. analytics.endpoint was supplied but empty. Pass a real beacons origin, analytics: { endpoint: 'https://beacons[-dev].<domain>' }, or omit the option to derive it from baseUrl (or analytics: false to opt out).");
2277
+ let r = In(e);
2256
2278
  if (r) return { endpoint: r };
2257
2279
  let i;
2258
2280
  try {
@@ -2260,9 +2282,9 @@ function Rn(e, t, n) {
2260
2282
  } catch {
2261
2283
  i = e;
2262
2284
  }
2263
- return Ln(i, `analytics is disabled: no QoE beacons will be sent. The beacons endpoint cannot be derived from the baseUrl host "${i}" — the convention is feed[-dev].<domain> → beacons[-dev].<domain>. Pass an explicit endpoint, analytics: { endpoint: 'https://beacons[-dev].<domain>' }, to createPlayer (or analytics: false to opt out deliberately).`);
2285
+ return Z(i, `analytics is disabled: no QoE beacons will be sent. The beacons endpoint cannot be derived from the baseUrl host "${i}" — the convention is feed[-dev].<domain> → beacons[-dev].<domain>. Pass an explicit endpoint, analytics: { endpoint: 'https://beacons[-dev].<domain>' }, to createPlayer (or analytics: false to opt out deliberately).`);
2264
2286
  }
2265
- var zn = class {
2287
+ var Hn = class {
2266
2288
  opts;
2267
2289
  url;
2268
2290
  cspKey;
@@ -2344,8 +2366,8 @@ var zn = class {
2344
2366
  constructor(e) {
2345
2367
  this.opts = {
2346
2368
  ...e,
2347
- heartbeatMs: e.heartbeatMs ?? xn,
2348
- idleMs: e.idleMs ?? Sn
2369
+ heartbeatMs: e.heartbeatMs ?? Tn,
2370
+ idleMs: e.idleMs ?? En
2349
2371
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`;
2350
2372
  try {
2351
2373
  let e = new URL(this.url);
@@ -2362,11 +2384,11 @@ var zn = class {
2362
2384
  sdkVersion: this.opts.sdkVersion,
2363
2385
  ...this.opts.environment
2364
2386
  };
2365
- this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), Tn), this.lastTickMs = this.now(), this.timer = setInterval(() => this.heartbeat(), this.opts.heartbeatMs), typeof window < "u" && window.addEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.addEventListener("visibilitychange", this.onVisibility), document.addEventListener("freeze", this.onFreeze), document.addEventListener("resume", this.onResume), this.cspKey && Pn.add(this.cspKey), document.addEventListener("securitypolicyviolation", In), X.add(this), this.noteCspBlocked(), this.onVisibility());
2387
+ this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), kn), this.lastTickMs = this.now(), this.timer = setInterval(() => this.heartbeat(), this.opts.heartbeatMs), typeof window < "u" && window.addEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.addEventListener("visibilitychange", this.onVisibility), document.addEventListener("freeze", this.onFreeze), document.addEventListener("resume", this.onResume), this.cspKey && Rn.add(this.cspKey), document.addEventListener("securitypolicyviolation", Bn), X.add(this), this.noteCspBlocked(), this.onVisibility());
2366
2388
  }
2367
2389
  checkSuspension(e) {
2368
2390
  if (this.endedFlag || !this.opts.idleMs) return !1;
2369
- let t = Math.max(this.opts.idleMs, Cn);
2391
+ let t = Math.max(this.opts.idleMs, Dn);
2370
2392
  return this.lastTickMs > 0 && e - this.lastTickMs >= t ? (this.endIdle(this.lastTickMs), !0) : (this.lastTickMs = e, !1);
2371
2393
  }
2372
2394
  syncIdle(e) {
@@ -2389,12 +2411,12 @@ var zn = class {
2389
2411
  let a = {
2390
2412
  fromState: J[e] ?? "PLAYER_STATE_UNSPECIFIED",
2391
2413
  toState: J[t] ?? "PLAYER_STATE_UNSPECIFIED",
2392
- presentation: On[this.presentation]
2414
+ presentation: Mn[this.presentation]
2393
2415
  }, o = this.causeOf(e, t, i);
2394
2416
  o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
2395
2417
  }
2396
2418
  seekInduced(e) {
2397
- return this.lastSeekMs >= 0 && e - this.lastSeekMs < Dn;
2419
+ return this.lastSeekMs >= 0 && e - this.lastSeekMs < jn;
2398
2420
  }
2399
2421
  causeOf(e, t, n) {
2400
2422
  if (t === "error") return "TRANSITION_CAUSE_ERROR";
@@ -2468,7 +2490,7 @@ var zn = class {
2468
2490
  let r = n ?? "";
2469
2491
  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";
2470
2492
  if (e === "DRM_CLIENT" || e === "DRM_DEVICE_REVOKED") return "ERROR_STAGE_DRM";
2471
- if (jn.has(e)) return "ERROR_STAGE_RESOLVE";
2493
+ if (Fn.has(e)) return "ERROR_STAGE_RESOLVE";
2472
2494
  let i = t ?? "";
2473
2495
  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";
2474
2496
  }
@@ -2515,14 +2537,14 @@ var zn = class {
2515
2537
  if (this.lastRequestCount = g, _ > 0) {
2516
2538
  o.requestCountDelta = _;
2517
2539
  let e = t.requestTimesMs ?? [], n = e.slice(Math.max(0, e.length - _)).sort((e, t) => e - t);
2518
- n.length > 0 && (o.requestTimeP50Ms = An(n, 50), o.requestTimeP95Ms = An(n, 95));
2540
+ n.length > 0 && (o.requestTimeP50Ms = Pn(n, 50), o.requestTimeP95Ms = Pn(n, 95));
2519
2541
  }
2520
2542
  typeof t.latencySeconds == "number" && typeof t.targetLatencySeconds == "number" && (o.driftMs = Math.round((t.latencySeconds - t.targetLatencySeconds) * 1e3)), typeof t.playbackRate == "number" && t.playbackRate > 0 && (o.playbackRate = t.playbackRate), t.qualityPinned === !0 && (o.qualityPinned = !0), t.cdnHost && (o.cdnHost = t.cdnHost), this.maybeEmitMediaInfo(t), this.enqueue({ heartbeat: o }), this.flush(), s > 0 && this.noteFirstBytes();
2521
2543
  }
2522
2544
  maybeEmitMediaInfo(e) {
2523
2545
  if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
2524
2546
  let t = {};
2525
- e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = kn[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);
2547
+ e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = Nn[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);
2526
2548
  let n = JSON.stringify(t);
2527
2549
  n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
2528
2550
  }
@@ -2542,7 +2564,7 @@ var zn = class {
2542
2564
  let c = r?.positionMs ?? 0;
2543
2565
  c > 0 && (i.exitPositionMs = Math.round(c)), this.activeTotalMs > 0 && (i.activeWatchTimeMs = Math.round(this.activeTotalMs)), this.mutedTotalMs > 0 && (i.mutedTimeMs = Math.round(this.mutedTotalMs));
2544
2566
  let l = e !== "END_REASON_ENDED";
2545
- l && this.lastStallMs > 0 && n - this.lastStallMs <= En && (i.abandonAfterStallMs = Math.max(q, Math.round(n - this.lastStallMs))), l && this.lastErrorMs > 0 && n - this.lastErrorMs <= En && (i.abandonAfterErrorMs = Math.max(q, Math.round(n - this.lastErrorMs))), this.fullscreenTotalMs > 0 && (i.fullscreenTimeMs = Math.round(this.fullscreenTotalMs)), this.pipTotalMs > 0 && (i.pipTimeMs = Math.round(this.pipTotalMs)), this.volumeChanges > 0 && (i.volumeChanges = this.volumeChanges), this.muteChanges > 0 && (i.muteChanges = this.muteChanges);
2567
+ l && this.lastStallMs > 0 && n - this.lastStallMs <= An && (i.abandonAfterStallMs = Math.max(q, Math.round(n - this.lastStallMs))), l && this.lastErrorMs > 0 && n - this.lastErrorMs <= An && (i.abandonAfterErrorMs = Math.max(q, Math.round(n - this.lastErrorMs))), this.fullscreenTotalMs > 0 && (i.fullscreenTimeMs = Math.round(this.fullscreenTotalMs)), this.pipTotalMs > 0 && (i.pipTimeMs = Math.round(this.pipTotalMs)), this.volumeChanges > 0 && (i.volumeChanges = this.volumeChanges), this.muteChanges > 0 && (i.muteChanges = this.muteChanges);
2546
2568
  let u = r?.decodedFrames ?? this.lastDecoded;
2547
2569
  u > 0 && (i.decodedFramesTotal = u), e === "END_REASON_STOPPED" && this.startupTimeMs === 0 && this.lastErrorStage === null && (e = "END_REASON_ABORTED", i.reason = e), this.startupTimeMs === 0 && e !== "END_REASON_ABORTED" && (i.failedStage = this.lastErrorStage ?? (this.manifestLoadMs === 0 ? "ERROR_STAGE_MANIFEST" : this.firstFragLoadMs === 0 ? "ERROR_STAGE_FIRST_SEGMENT" : "ERROR_STAGE_OTHER")), this.pending.push(this.wrap({ sessionEnd: i })), this.flush(e === "END_REASON_PAGEHIDE");
2548
2570
  }
@@ -2562,13 +2584,13 @@ var zn = class {
2562
2584
  };
2563
2585
  }
2564
2586
  noteCspBlocked() {
2565
- this.cspBlockedNoted || this.endedFlag || !this.cspBlocked() || (this.cspBlockedNoted = !0, this.opts.onBlocked?.(Fn(new URL(this.url).origin)));
2587
+ this.cspBlockedNoted || this.endedFlag || !this.cspBlocked() || (this.cspBlockedNoted = !0, this.opts.onBlocked?.(zn(new URL(this.url).origin)));
2566
2588
  }
2567
2589
  cspBlocked() {
2568
2590
  return !!this.cspKey && Y.has(this.cspKey);
2569
2591
  }
2570
2592
  enqueue(e) {
2571
- if (!this.cspBlocked()) for (this.pending.push(this.wrap(e)); this.pending.length > wn;) {
2593
+ if (!this.cspBlocked()) for (this.pending.push(this.wrap(e)); this.pending.length > On;) {
2572
2594
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
2573
2595
  this.pending.splice(e >= 0 ? e : 0, 1);
2574
2596
  }
@@ -2595,7 +2617,7 @@ var zn = class {
2595
2617
  };
2596
2618
  //#endregion
2597
2619
  //#region src/core/environment.ts
2598
- function Bn() {
2620
+ function Un() {
2599
2621
  if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
2600
2622
  let e;
2601
2623
  try {
@@ -2620,14 +2642,14 @@ function Bn() {
2620
2642
  integration: "INTEGRATION_KIND_IFRAME"
2621
2643
  };
2622
2644
  }
2623
- function Vn() {
2645
+ function Wn() {
2624
2646
  if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
2625
2647
  let e = navigator.userAgentData, t = e?.platform ?? "";
2626
2648
  if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
2627
2649
  let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
2628
2650
  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";
2629
2651
  }
2630
- function Hn() {
2652
+ function Gn() {
2631
2653
  let e = typeof navigator < "u" ? navigator.connection : void 0;
2632
2654
  if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
2633
2655
  let t = {
@@ -2646,17 +2668,17 @@ function Hn() {
2646
2668
  }
2647
2669
  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;
2648
2670
  }
2649
- function Un(e) {
2650
- let t = Bn(), n = {
2651
- deviceClass: Vn(),
2671
+ function Kn(e) {
2672
+ let t = Un(), n = {
2673
+ deviceClass: Wn(),
2652
2674
  integration: t.integration,
2653
- ...Hn()
2675
+ ...Gn()
2654
2676
  };
2655
2677
  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);
2656
2678
  let r = e.getBoundingClientRect?.();
2657
2679
  return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
2658
2680
  }
2659
- async function Wn() {
2681
+ async function qn() {
2660
2682
  let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
2661
2683
  if (!e?.getHighEntropyValues) return {};
2662
2684
  try {
@@ -2668,7 +2690,7 @@ async function Wn() {
2668
2690
  }
2669
2691
  //#endregion
2670
2692
  //#region src/core/version.ts
2671
- var Gn = "2.21.0", Kn = {
2693
+ var Jn = "2.22.1", Yn = {
2672
2694
  accent: "#E1B600",
2673
2695
  accentText: "#1B1C23",
2674
2696
  background: "#0e0f13",
@@ -2679,7 +2701,7 @@ var Gn = "2.21.0", Kn = {
2679
2701
  border: "rgba(255, 255, 255, 0.1)",
2680
2702
  radius: "8px",
2681
2703
  fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
2682
- }, qn = {
2704
+ }, Xn = {
2683
2705
  accent: "--havik-accent",
2684
2706
  accentText: "--havik-accent-text",
2685
2707
  background: "--havik-bg",
@@ -2691,26 +2713,26 @@ var Gn = "2.21.0", Kn = {
2691
2713
  radius: "--havik-radius",
2692
2714
  fontFamily: "--havik-font"
2693
2715
  };
2694
- function Jn(e, t) {
2716
+ function Zn(e, t) {
2695
2717
  let n = {
2696
- ...Kn,
2718
+ ...Yn,
2697
2719
  ...t
2698
2720
  };
2699
- for (let t of Object.keys(qn)) e.style.setProperty(qn[t], n[t]);
2721
+ for (let t of Object.keys(Xn)) e.style.setProperty(Xn[t], n[t]);
2700
2722
  n.logoUrl && e.style.setProperty("--havik-logo", `url("${n.logoUrl}")`);
2701
2723
  }
2702
2724
  //#endregion
2703
2725
  //#region src/ui/styles.ts
2704
- var Z = "havik-player-styles", Yn = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer;\n touch-action:manipulation}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n/* Absolutely centred, NOT a flex sibling of .havik-center: the hidden center\n button (.havik-gone) still occupies its 74px in the row, which pushed an\n in-flow spinner 37px right of centre. */\n.havik-spinner{position:absolute;inset:0;margin:auto;width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n", Xn = /* @__PURE__ */ new WeakMap();
2705
- function Zn(e = document, t) {
2706
- let n = e.querySelector(`style#${Z}`);
2707
- if (n && (!t || Xn.get(n) === t)) return;
2726
+ var Qn = "havik-player-styles", $n = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer;\n touch-action:manipulation}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n/* Absolutely centred, NOT a flex sibling of .havik-center: the hidden center\n button (.havik-gone) still occupies its 74px in the row, which pushed an\n in-flow spinner 37px right of centre. */\n.havik-spinner{position:absolute;inset:0;margin:auto;width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n", er = /* @__PURE__ */ new WeakMap();
2727
+ function tr(e = document, t) {
2728
+ let n = e.querySelector(`style#${Qn}`);
2729
+ if (n && (!t || er.get(n) === t)) return;
2708
2730
  let r = e.createElement("style");
2709
- r.id = Z, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = Yn, Xn.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2731
+ r.id = Qn, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = $n, er.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2710
2732
  }
2711
2733
  //#endregion
2712
2734
  //#region src/ui/icons.ts
2713
- var Qn = {
2735
+ var nr = {
2714
2736
  play: "<path d=\"M8 5v14l11-7z\"/>",
2715
2737
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
2716
2738
  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\"/>",
@@ -2724,11 +2746,11 @@ var Qn = {
2724
2746
  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\"/>"
2725
2747
  };
2726
2748
  function Q(e) {
2727
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Qn[e]}</svg>`;
2749
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${nr[e]}</svg>`;
2728
2750
  }
2729
2751
  //#endregion
2730
2752
  //#region src/ui/controls.ts
2731
- var $n = 3e3, er = 250, tr = /* @__PURE__ */ new Set([
2753
+ var rr = 3e3, ir = 250, ar = /* @__PURE__ */ new Set([
2732
2754
  "GONE",
2733
2755
  "NOT_FOUND",
2734
2756
  "GEO_DENIED",
@@ -2736,15 +2758,15 @@ var $n = 3e3, er = 250, tr = /* @__PURE__ */ new Set([
2736
2758
  "UNAUTHORIZED",
2737
2759
  "DRM_DEVICE_REVOKED"
2738
2760
  ]);
2739
- function nr(e) {
2761
+ function or(e) {
2740
2762
  let t = "This match isn't available in your country";
2741
2763
  return e ? `${t} (${e})` : t;
2742
2764
  }
2743
- var rr = "This device can't play protected video";
2744
- function ir(e) {
2745
- return new ar(e);
2765
+ var sr = "This device can't play protected video";
2766
+ function cr(e) {
2767
+ return new lr(e);
2746
2768
  }
2747
- var ar = class {
2769
+ var lr = class {
2748
2770
  root;
2749
2771
  video;
2750
2772
  player;
@@ -2792,7 +2814,7 @@ var ar = class {
2792
2814
  scrubbing = !1;
2793
2815
  offFns = [];
2794
2816
  constructor(e) {
2795
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? $n, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.streamEnded = e.streamEnded ?? (() => !1), this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, Zn(this.doc, e.cspNonce), 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");
2817
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? rr, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.streamEnded = e.streamEnded ?? (() => !1), this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, tr(this.doc, e.cspNonce), 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");
2796
2818
  let t = (e, t, n) => {
2797
2819
  let r = this.doc.createElement(e);
2798
2820
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -2878,7 +2900,7 @@ var ar = class {
2878
2900
  let t = this.surfaceWasHidden;
2879
2901
  this.tapTimer = setTimeout(() => {
2880
2902
  this.tapTimer = void 0, this.toggleBar(t);
2881
- }, er);
2903
+ }, ir);
2882
2904
  }
2883
2905
  toggleBar(e) {
2884
2906
  e ? this.poke() : this.hideIfIdle();
@@ -2898,7 +2920,7 @@ var ar = class {
2898
2920
  this.statusOverlays && (e === "ended" ? t = "ended" : e === "error" ? t = "error" : e === "waiting" ? t = "waiting" : !this.hasPlayed && (e === "idle" || e === "paused") && (t = "preplay")), this.renderStatus(t);
2899
2921
  }
2900
2922
  statusMessageFor(e) {
2901
- return e === "ended" ? this.player.endedReason === "interrupted" ? this.interruptedMessage : this.endedMessage : e === "waiting" ? this.hasPlayed ? "Reconnecting — please stand by" : "Starting soon…" : e === "error" ? this.errorMessage === void 0 ? this.lastError?.code === "GEO_DENIED" ? nr(this.lastError.country) : this.lastError?.code === "DRM_DEVICE_REVOKED" ? rr : this.lastError?.message ?? "Playback unavailable" : this.errorMessage : null;
2923
+ return e === "ended" ? this.player.endedReason === "interrupted" ? this.interruptedMessage : this.endedMessage : e === "waiting" ? this.hasPlayed ? "Reconnecting — please stand by" : "Starting soon…" : e === "error" ? this.errorMessage === void 0 ? this.lastError?.code === "GEO_DENIED" ? or(this.lastError.country) : this.lastError?.code === "DRM_DEVICE_REVOKED" ? sr : this.lastError?.message ?? "Playback unavailable" : this.errorMessage : null;
2902
2924
  }
2903
2925
  renderStatus(e) {
2904
2926
  let t = this.statusMessageFor(e);
@@ -2913,7 +2935,7 @@ var ar = class {
2913
2935
  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);
2914
2936
  } else {
2915
2937
  let r = this.doc.createElement("div");
2916
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !tr.has(this.lastError.code)) {
2938
+ if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !ar.has(this.lastError.code)) {
2917
2939
  let e = this.doc.createElement("button");
2918
2940
  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);
2919
2941
  }
@@ -2954,7 +2976,7 @@ var ar = class {
2954
2976
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
2955
2977
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
2956
2978
  }
2957
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${or(this.video.currentTime)} / ${or(this.video.duration)}`);
2979
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${ur(this.video.currentTime)} / ${ur(this.video.duration)}`);
2958
2980
  }
2959
2981
  syncLive() {
2960
2982
  let e = this.player.getStats(), t = e.isLive !== !1, n = this.player.state === "ended", r = !t && !this.streamEnded();
@@ -3004,17 +3026,17 @@ var ar = class {
3004
3026
  renderQualityMenu() {
3005
3027
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
3006
3028
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
3007
- for (let n of e) this.menu.append(this.menuItem(sr(n), t === n.index, () => this.player.setQuality(n.index)));
3029
+ for (let n of e) this.menu.append(this.menuItem(dr(n), t === n.index, () => this.player.setQuality(n.index)));
3008
3030
  }
3009
3031
  renderAudioMenu() {
3010
3032
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
3011
3033
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
3012
- for (let t of e) this.menu.append(this.menuItem(cr(t), t.default, () => this.player.setAudioTrack(t.id)));
3034
+ for (let t of e) this.menu.append(this.menuItem(fr(t), t.default, () => this.player.setAudioTrack(t.id)));
3013
3035
  }
3014
3036
  renderCcMenu() {
3015
3037
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
3016
3038
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
3017
- for (let t of e) this.menu.append(this.menuItem(lr(t), !1, () => this.player.setTextTrack(t.id)));
3039
+ for (let t of e) this.menu.append(this.menuItem(pr(t), !1, () => this.player.setTextTrack(t.id)));
3018
3040
  }
3019
3041
  onTracksChanged() {
3020
3042
  this.openMenu && this.renderMenu(this.openMenu);
@@ -3119,41 +3141,41 @@ var ar = class {
3119
3141
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
3120
3142
  }
3121
3143
  };
3122
- function or(e) {
3144
+ function ur(e) {
3123
3145
  (!Number.isFinite(e) || e < 0) && (e = 0);
3124
3146
  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");
3125
3147
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
3126
3148
  }
3127
- function sr(e) {
3149
+ function dr(e) {
3128
3150
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
3129
3151
  }
3130
- function cr(e) {
3152
+ function fr(e) {
3131
3153
  return e.name || e.lang || `Track ${e.id}`;
3132
3154
  }
3133
- function lr(e) {
3155
+ function pr(e) {
3134
3156
  return e.name || e.lang || `Track ${e.id}`;
3135
3157
  }
3136
3158
  //#endregion
3137
3159
  //#region src/managed/index.ts
3138
- var ur = 6e3, dr = 45e3, fr = 2e3, pr = 3, mr = 5e3, hr = 2e3, gr = 48e4, _r = "keySystemLicenseRequestFailed";
3139
- function vr(e) {
3160
+ var mr = 6e3, hr = 45e3, gr = 2e3, _r = 3, vr = 5e3, yr = 2e3, br = 48e4, xr = "keySystemLicenseRequestFailed";
3161
+ function Sr(e) {
3140
3162
  if (e.env !== void 0 && e.baseUrl !== void 0) throw new n("INTERNAL", 0, `createPlayer: pass env OR baseUrl, not both (got env=${JSON.stringify(e.env)} and baseUrl=${JSON.stringify(e.baseUrl)})`);
3141
3163
  if (e.env !== void 0) return at(e.env);
3142
3164
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: env or baseUrl is required (env: 'integration' | 'production')");
3143
3165
  return e.baseUrl;
3144
3166
  }
3145
- async function yr(e) {
3167
+ async function Cr(e) {
3146
3168
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
3147
- let t = vr(e);
3169
+ let t = Sr(e);
3148
3170
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
3149
3171
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
3150
- let r = new br({
3172
+ let r = new wr({
3151
3173
  ...e,
3152
3174
  baseUrl: t
3153
3175
  });
3154
3176
  return await r.start(), r;
3155
3177
  }
3156
- var br = class {
3178
+ var wr = class {
3157
3179
  opts;
3158
3180
  abort = new AbortController();
3159
3181
  listeners = {};
@@ -3248,7 +3270,7 @@ var br = class {
3248
3270
  setTimeout(() => {
3249
3271
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
3250
3272
  phase: i ? "unavailable" : "tooEarly",
3251
- retryInMs: dr,
3273
+ retryInMs: hr,
3252
3274
  attempt: 1,
3253
3275
  elapsedMs: 0,
3254
3276
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -3287,13 +3309,13 @@ var br = class {
3287
3309
  !a && !p() && h();
3288
3310
  }, e);
3289
3311
  };
3290
- _(ur), this.armingSub = E({
3312
+ _(mr), this.armingSub = E({
3291
3313
  eventsBaseUrl: t,
3292
3314
  matchUrn: this.opts.matchUrn,
3293
3315
  credential: this.opts.credential,
3294
- onAlive: () => _(dr),
3316
+ onAlive: () => _(hr),
3295
3317
  onState: (e) => {
3296
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * fr);
3318
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * gr);
3297
3319
  },
3298
3320
  onError: () => h()
3299
3321
  });
@@ -3338,19 +3360,19 @@ var br = class {
3338
3360
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
3339
3361
  let i = e.analytics?.sid;
3340
3362
  if (!i) return;
3341
- let a = typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0, { endpoint: o, disabledReason: s } = Rn(this.opts.baseUrl, a, e.analytics?.endpoint);
3363
+ let a = typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0, { endpoint: o, disabledReason: s } = Vn(this.opts.baseUrl, a, e.analytics?.endpoint);
3342
3364
  if (!o) {
3343
3365
  s && setTimeout(() => {
3344
3366
  this.destroyed || r !== this.loadGen || this.emit("warning", new n("INTERNAL", 0, s));
3345
3367
  }, 0);
3346
3368
  return;
3347
3369
  }
3348
- let c = this.opts.video, l = new zn({
3370
+ let c = this.opts.video, l = new Hn({
3349
3371
  endpoint: o,
3350
3372
  sid: i,
3351
3373
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
3352
- sdkVersion: Gn,
3353
- environment: Un(c),
3374
+ sdkVersion: Jn,
3375
+ environment: Kn(c),
3354
3376
  idleMs: typeof this.opts.analytics == "object" ? this.opts.analytics.idleMs : void 0,
3355
3377
  onIdle: () => this.onAnalyticsIdle(t),
3356
3378
  onBlocked: (e) => setTimeout(() => {
@@ -3371,7 +3393,7 @@ var br = class {
3371
3393
  let u = () => l.noteSeek(), d = () => l.noteVolume(c.muted, c.volume);
3372
3394
  c.addEventListener("seeking", u), c.addEventListener("volumechange", d), this.detachMediaListeners = () => {
3373
3395
  c.removeEventListener("seeking", u), c.removeEventListener("volumechange", d);
3374
- }, l.start(), l.seedVolume(c.muted, c.volume), l.notePresentation(this.currentPresentation()), Wn().then((e) => l.noteEnvironment(e)), this.opts.autoplay === !1 && l.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
3396
+ }, l.start(), l.seedVolume(c.muted, c.volume), l.notePresentation(this.currentPresentation()), qn().then((e) => l.noteEnvironment(e)), this.opts.autoplay === !1 && l.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
3375
3397
  }
3376
3398
  currentPresentation() {
3377
3399
  let e = this.opts.video;
@@ -3391,6 +3413,7 @@ var br = class {
3391
3413
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
3392
3414
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
3393
3415
  rememberBandwidth: this.opts.rememberBandwidth,
3416
+ sizeCapHeadroom: this.opts.sizeCapHeadroom,
3394
3417
  mediaDuringKey: this.opts.mediaDuringKey,
3395
3418
  debug: this.opts.debug,
3396
3419
  deferLoad: r,
@@ -3399,9 +3422,9 @@ var br = class {
3399
3422
  freshLicenseUrl: () => this.refreshLicense()
3400
3423
  }, o = this.opts.engine ?? "auto";
3401
3424
  if (o === "native") return new W(a);
3402
- if (o === "hls") return new bn(a);
3425
+ if (o === "hls") return new wn(a);
3403
3426
  if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && H(this.opts.video) && U() || !e.drmEnabled && St() && H(this.opts.video)) return new W(a);
3404
- if (It()) return new bn(a);
3427
+ if (It()) return new wn(a);
3405
3428
  if (H(this.opts.video)) return new W(a);
3406
3429
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
3407
3430
  }
@@ -3464,7 +3487,7 @@ var br = class {
3464
3487
  r = 0, i = !1;
3465
3488
  },
3466
3489
  onError: () => {
3467
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= pr && (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.`))));
3490
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= _r && (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.`))));
3468
3491
  }
3469
3492
  });
3470
3493
  }
@@ -3478,7 +3501,7 @@ var br = class {
3478
3501
  return;
3479
3502
  }
3480
3503
  let e = this.hiddenAtMs;
3481
- this.hiddenAtMs = null, e != null && Date.now() - e >= mr && this.resyncLiveState();
3504
+ this.hiddenAtMs = null, e != null && Date.now() - e >= vr && this.resyncLiveState();
3482
3505
  }, this.onReturnPageShow = (e) => {
3483
3506
  e.persisted && this.resyncLiveState();
3484
3507
  }, document.addEventListener("visibilitychange", this.onReturnVisible), typeof window < "u" && window.addEventListener("pageshow", this.onReturnPageShow));
@@ -3510,7 +3533,7 @@ var br = class {
3510
3533
  scheduleRejoin(e) {
3511
3534
  this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
3512
3535
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
3513
- }, Math.random() * fr));
3536
+ }, Math.random() * gr));
3514
3537
  }
3515
3538
  cancelRejoin() {
3516
3539
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -3534,7 +3557,7 @@ var br = class {
3534
3557
  }
3535
3558
  t.controls = !1;
3536
3559
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
3537
- r && r.insertBefore(n, t), n.appendChild(t), Jn(n, this.opts.theme), this.container = n, this.controlBar = ir({
3560
+ r && r.insertBefore(n, t), n.appendChild(t), Zn(n, this.opts.theme), this.container = n, this.controlBar = cr({
3538
3561
  root: n,
3539
3562
  video: t,
3540
3563
  player: this,
@@ -3572,7 +3595,7 @@ var br = class {
3572
3595
  }
3573
3596
  startStats() {
3574
3597
  this.stopStats();
3575
- let e = Math.max(500, this.opts.statsIntervalMs ?? hr);
3598
+ let e = Math.max(500, this.opts.statsIntervalMs ?? yr);
3576
3599
  this.statsTimer = setInterval(() => {
3577
3600
  if (!this.engine) return;
3578
3601
  let e = this.engine.getStats();
@@ -3583,14 +3606,14 @@ var br = class {
3583
3606
  this.statsTimer && clearInterval(this.statsTimer), this.statsTimer = void 0;
3584
3607
  }
3585
3608
  isStaleLicenseDenial(e) {
3586
- return this.staleLicenseReloaded || this.opts.hlsConfig?.licenseXhrSetup || this._state === "ended" || this._state === "error" || e.detail !== _r || e.error.httpStatus !== 403 || e.error.code !== "FORBIDDEN" || e.error.serverCode === "license_refused" ? !1 : K(this._descriptor?.drm?.widevine?.licenseUrl);
3609
+ return this.staleLicenseReloaded || this.opts.hlsConfig?.licenseXhrSetup || this._state === "ended" || this._state === "error" || e.detail !== xr || e.error.httpStatus !== 403 || e.error.code !== "FORBIDDEN" || e.error.serverCode === "license_refused" ? !1 : K(this._descriptor?.drm?.widevine?.licenseUrl);
3587
3610
  }
3588
3611
  scheduleLicenseRefresh() {
3589
3612
  if (this.stopRefresh(), !this._descriptor?.drmEnabled) return;
3590
3613
  typeof document < "u" && this.engine?.name === "hls" && (this.onRefocus = () => {
3591
3614
  document.visibilityState !== "visible" || this.destroyed || K(this._descriptor?.drm?.widevine?.licenseUrl) && this.refreshLicense();
3592
3615
  }, document.addEventListener("visibilitychange", this.onRefocus));
3593
- let e = this.opts.licenseRefreshMs ?? gr;
3616
+ let e = this.opts.licenseRefreshMs ?? br;
3594
3617
  e && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
3595
3618
  }
3596
3619
  stopRefresh() {
@@ -3760,7 +3783,7 @@ function $(e) {
3760
3783
  }
3761
3784
  //#endregion
3762
3785
  //#region src/embed/host.ts
3763
- function xr(e) {
3786
+ function Tr(e) {
3764
3787
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
3765
3788
  if (!e.src) throw Error("mountEmbed: opts.src is required");
3766
3789
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -3819,15 +3842,15 @@ function xr(e) {
3819
3842
  }
3820
3843
  //#endregion
3821
3844
  //#region src/embed/page.ts
3822
- var Sr = /^[A-Za-z0-9:_.-]{1,128}$/;
3823
- function Cr(e) {
3845
+ var Er = /^[A-Za-z0-9:_.-]{1,128}$/;
3846
+ function Dr(e) {
3824
3847
  if (typeof e == "boolean") return e;
3825
3848
  if (typeof e == "object" && e) {
3826
3849
  let t = e.endpoint;
3827
3850
  return typeof t == "string" ? { endpoint: t } : {};
3828
3851
  }
3829
3852
  }
3830
- function wr() {
3853
+ function Or() {
3831
3854
  let e = window.HAVIK_EMBED_CONFIG;
3832
3855
  if (e) return {
3833
3856
  baseUrl: e.baseUrl ?? "",
@@ -3837,7 +3860,7 @@ function wr() {
3837
3860
  autoplay: e.autoplay ?? !0,
3838
3861
  muted: e.muted ?? !0,
3839
3862
  waitForLive: e.waitForLive ?? !0,
3840
- analytics: Cr(e.analytics)
3863
+ analytics: Dr(e.analytics)
3841
3864
  };
3842
3865
  let t = new URLSearchParams(location.search);
3843
3866
  return {
@@ -3850,9 +3873,9 @@ function wr() {
3850
3873
  waitForLive: !0
3851
3874
  };
3852
3875
  }
3853
- async function Tr() {
3854
- let e = wr();
3855
- Dr();
3876
+ async function kr() {
3877
+ let e = Or();
3878
+ jr();
3856
3879
  let t = document.createElement("video");
3857
3880
  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);
3858
3881
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -3908,12 +3931,12 @@ async function Tr() {
3908
3931
  message: e.message,
3909
3932
  ...e.country === void 0 ? {} : { country: e.country }
3910
3933
  })), e.on("autoplayblocked", () => {
3911
- i({ type: "oddin:autoplayblocked" }), Er(t, e);
3934
+ i({ type: "oddin:autoplayblocked" }), Ar(t, e);
3912
3935
  });
3913
3936
  }, s = async (r) => {
3914
3937
  a?.destroy(), a = null;
3915
3938
  try {
3916
- let n = await yr({
3939
+ let n = await Cr({
3917
3940
  video: t,
3918
3941
  baseUrl: e.baseUrl,
3919
3942
  matchUrn: r,
@@ -3976,7 +3999,7 @@ async function Tr() {
3976
3999
  a?.retry();
3977
4000
  break;
3978
4001
  case "oddin:load":
3979
- typeof e.matchUrn == "string" && Sr.test(e.matchUrn) && s(e.matchUrn);
4002
+ typeof e.matchUrn == "string" && Er.test(e.matchUrn) && s(e.matchUrn);
3980
4003
  break;
3981
4004
  case "oddin:destroy": c();
3982
4005
  }
@@ -3988,18 +4011,18 @@ async function Tr() {
3988
4011
  protocol: 1
3989
4012
  });
3990
4013
  }
3991
- function Er(e, t) {
4014
+ function Ar(e, t) {
3992
4015
  if (document.getElementById("havik-tap")) return;
3993
4016
  let n = document.createElement("button");
3994
4017
  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", () => {
3995
4018
  n.remove(), t.play();
3996
4019
  }), document.body.appendChild(n);
3997
4020
  }
3998
- function Dr() {
4021
+ function jr() {
3999
4022
  let e = document.createElement("style");
4000
4023
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
4001
4024
  }
4002
4025
  //#endregion
4003
- export { Kn as ODDIN_THEME, n as PlaybackError, Jn as applyTheme, at as baseUrlForEnv, Tr as bootEmbed, Ee as clearDeviceId, Re as clearRememberedBandwidth, yr as createPlayer, D as deriveEventsBaseUrl, tt as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, ve as fetchCatalog, be as fetchTournament, Se as flattenMatches, C as fullscreenSupported, A as getDeviceId, rt as isLowLatencyManifest, $ as isOddinMessage, Oe as licenseRequestHeaders, xr as mountEmbed, fe as pollToLive, y as resolveStream, E as subscribeLiveState, S as videoFullscreenActive, we as watchStatus };
4026
+ export { Yn as ODDIN_THEME, n as PlaybackError, Zn as applyTheme, at as baseUrlForEnv, kr as bootEmbed, Ee as clearDeviceId, Re as clearRememberedBandwidth, Cr as createPlayer, D as deriveEventsBaseUrl, tt as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, ve as fetchCatalog, be as fetchTournament, Se as flattenMatches, C as fullscreenSupported, A as getDeviceId, rt as isLowLatencyManifest, $ as isOddinMessage, Oe as licenseRequestHeaders, Tr as mountEmbed, fe as pollToLive, y as resolveStream, E as subscribeLiveState, S as videoFullscreenActive, we as watchStatus };
4004
4027
 
4005
4028
  //# sourceMappingURL=index.js.map