@oddin-gg/havik-player 2.16.5 → 2.16.7

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
@@ -818,32 +818,32 @@ function Ke(e) {
818
818
  }
819
819
  //#endregion
820
820
  //#region src/core/stats.ts
821
- var z = { droppedFrames: 0 };
822
- function B(e) {
821
+ var qe = { droppedFrames: 0 };
822
+ function z(e) {
823
823
  try {
824
824
  return new URL(e).host;
825
825
  } catch {
826
826
  return "";
827
827
  }
828
828
  }
829
- var qe = /\.(m3u8|ts|m4s|mp4|cmfv|cmfa)(\?|$)/i;
830
- function Je(e) {
829
+ var Je = /\.(m3u8|ts|m4s|mp4|cmfv|cmfa)(\?|$)/i;
830
+ function Ye(e) {
831
831
  if (typeof performance > "u" || typeof performance.getEntriesByType != "function") return "";
832
832
  let t = "", n = -1, r = performance.getEntriesByType("resource");
833
- for (let i of r) if (!(i.startTime < e) && (i.initiatorType === "video" || i.initiatorType === "audio" || qe.test(i.name)) && i.startTime >= n) {
834
- let e = B(i.name);
833
+ for (let i of r) if (!(i.startTime < e) && (i.initiatorType === "video" || i.initiatorType === "audio" || Je.test(i.name)) && i.startTime >= n) {
834
+ let e = z(i.name);
835
835
  e && (t = e, n = i.startTime);
836
836
  }
837
837
  return t;
838
838
  }
839
- function V(e) {
839
+ function B(e) {
840
840
  let t = e.getVideoPlaybackQuality?.();
841
841
  return {
842
842
  dropped: t?.droppedVideoFrames ?? 0,
843
843
  totalFrames: t?.totalVideoFrames ?? 0
844
844
  };
845
845
  }
846
- var H = class {
846
+ var V = class {
847
847
  video;
848
848
  baseFrames = 0;
849
849
  baseDropped = 0;
@@ -853,7 +853,7 @@ var H = class {
853
853
  this.video = e;
854
854
  }
855
855
  read() {
856
- let e = V(this.video);
856
+ let e = B(this.video);
857
857
  return e.totalFrames < this.lastFrames && (this.baseFrames += this.lastFrames), e.dropped < this.lastDropped && (this.baseDropped += this.lastDropped), this.lastFrames = e.totalFrames, this.lastDropped = e.dropped, {
858
858
  dropped: this.baseDropped + e.dropped,
859
859
  totalFrames: this.baseFrames + e.totalFrames
@@ -866,7 +866,7 @@ var H = class {
866
866
  };
867
867
  }
868
868
  };
869
- function Ye(e, t) {
869
+ function Xe(e, t) {
870
870
  let n = [
871
871
  ["playing", () => t("playing")],
872
872
  ["waiting", () => t("buffering")],
@@ -883,8 +883,8 @@ function Ye(e, t) {
883
883
  }
884
884
  //#endregion
885
885
  //#region src/core/engine/native.ts
886
- var Xe = 2e3, Ze = 3, U = 500, Qe = 6e3, $e = .25;
887
- function et(e) {
886
+ var Ze = 2e3, Qe = 3, H = 500, $e = 6e3, et = .25;
887
+ function tt(e) {
888
888
  try {
889
889
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
890
890
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -892,20 +892,20 @@ function et(e) {
892
892
  return;
893
893
  }
894
894
  }
895
- function W(e) {
895
+ function U(e) {
896
896
  return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
897
897
  }
898
- function G() {
898
+ function W() {
899
899
  if (typeof window > "u") return !1;
900
900
  let e = "WebKitMediaKeys" in window || typeof HTMLMediaElement < "u" && "webkitSetMediaKeys" in HTMLMediaElement.prototype, t = typeof navigator < "u" && /Apple/i.test(navigator.vendor ?? "");
901
901
  return e || t;
902
902
  }
903
- function tt() {
904
- if (typeof navigator > "u" || !G()) return !1;
903
+ function nt() {
904
+ if (typeof navigator > "u" || !W()) return !1;
905
905
  let e = navigator.userAgent ?? "";
906
906
  return /iPhone|iPad|iPod/.test(e) ? !0 : /Macintosh/.test(e) && typeof navigator.maxTouchPoints == "number" && navigator.maxTouchPoints > 1;
907
907
  }
908
- var K = class {
908
+ var G = class {
909
909
  name = "native";
910
910
  deps;
911
911
  onLoaded;
@@ -927,7 +927,7 @@ var K = class {
927
927
  frameCounter;
928
928
  detachFrames = null;
929
929
  constructor(e) {
930
- this.deps = e, this.frameCounter = new H(e.video), this.loadStarted = e.deferLoad !== !0, this.builtAt = typeof performance < "u" ? performance.now() : 0;
930
+ this.deps = e, this.frameCounter = new V(e.video), this.loadStarted = e.deferLoad !== !0, this.builtAt = typeof performance < "u" ? performance.now() : 0;
931
931
  }
932
932
  async load() {
933
933
  this.detachFrames ??= this.frameCounter.attach();
@@ -948,7 +948,7 @@ var K = class {
948
948
  type: "error",
949
949
  error: new n("INTERNAL", 0, "native HLS playback error"),
950
950
  fatal: !0
951
- }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = nt(t.manifestUrl, t.analytics?.sid), e.load();
951
+ }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = rt(t.manifestUrl, t.analytics?.sid), e.load();
952
952
  }
953
953
  startLoad() {
954
954
  this.loadStarted || (this.loadStarted = !0, this.restorePreload());
@@ -970,7 +970,7 @@ var K = class {
970
970
  });
971
971
  this.onEncrypted = (n) => {
972
972
  let i = n;
973
- i.initData && !this.fairplayArmed && (this.fairplayArmed = !0, this.fairplayKeyIV = et(i.initData), (async () => {
973
+ i.initData && !this.fairplayArmed && (this.fairplayArmed = !0, this.fairplayKeyIV = tt(i.initData), (async () => {
974
974
  try {
975
975
  let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
976
976
  initDataTypes: [i.initDataType],
@@ -1069,7 +1069,7 @@ var K = class {
1069
1069
  droppedFrames: t.dropped,
1070
1070
  decodedFrames: t.totalFrames
1071
1071
  };
1072
- if (this.cdnHost ||= Je(this.builtAt), this.cdnHost && (n.cdnHost = this.cdnHost), this.deps.descriptor.drmEnabled && (n.keySystem = "fairplay"), e.duration === Infinity) {
1072
+ if (this.cdnHost ||= Ye(this.builtAt), this.cdnHost && (n.cdnHost = this.cdnHost), this.deps.descriptor.drmEnabled && (n.keySystem = "fairplay"), e.duration === Infinity) {
1073
1073
  if (n.isLive = !0, e.seekable.length > 0) {
1074
1074
  let t = e.seekable.end(e.seekable.length - 1);
1075
1075
  n.atLiveEdge = e.currentTime >= t - 2;
@@ -1092,7 +1092,7 @@ var K = class {
1092
1092
  return;
1093
1093
  }
1094
1094
  let t = this.hiddenSince;
1095
- this.hiddenSince = null, t != null && e - t >= Xe && this.armRefocus();
1095
+ this.hiddenSince = null, t != null && e - t >= Ze && this.armRefocus();
1096
1096
  }, this.onPageShow = (e) => {
1097
1097
  e.persisted && this.armRefocus();
1098
1098
  }, document.addEventListener("visibilitychange", this.onVisibility), typeof window < "u" && window.addEventListener("pageshow", this.onPageShow));
@@ -1101,15 +1101,15 @@ var K = class {
1101
1101
  if (this.cancelRefocus(), Number.isFinite(this.deps.video.duration)) return;
1102
1102
  let e = this.liveEnd(), t = () => typeof performance < "u" ? performance.now() : Date.now(), n = t(), r = 0, i = () => {
1103
1103
  if (this.refocusTimer = null, Number.isFinite(this.deps.video.duration)) return;
1104
- r += U;
1105
- let a = this.liveEnd(), o = a != null && (e == null || a >= e + $e), s = Math.max(r, t() - n) >= Qe;
1104
+ r += H;
1105
+ let a = this.liveEnd(), o = a != null && (e == null || a >= e + et), s = Math.max(r, t() - n) >= $e;
1106
1106
  if (o || s && a != null) {
1107
1107
  this.snapToLive();
1108
1108
  return;
1109
1109
  }
1110
- this.refocusTimer = setTimeout(i, U);
1110
+ this.refocusTimer = setTimeout(i, H);
1111
1111
  };
1112
- this.refocusTimer = setTimeout(i, U);
1112
+ this.refocusTimer = setTimeout(i, H);
1113
1113
  }
1114
1114
  cancelRefocus() {
1115
1115
  this.refocusTimer != null && clearTimeout(this.refocusTimer), this.refocusTimer = null;
@@ -1123,7 +1123,7 @@ var K = class {
1123
1123
  snapToLive() {
1124
1124
  let e = this.deps.video, t = this.liveEnd();
1125
1125
  if (t == null) return;
1126
- let n = e.seekable.start(e.seekable.length - 1), r = Math.min(e.currentTime, t), i = Math.max(n, t - Ze, r);
1126
+ let n = e.seekable.start(e.seekable.length - 1), r = Math.min(e.currentTime, t), i = Math.max(n, t - Qe, r);
1127
1127
  try {
1128
1128
  e.currentTime = i;
1129
1129
  } catch {}
@@ -1141,13 +1141,25 @@ var K = class {
1141
1141
  } catch {}
1142
1142
  }
1143
1143
  };
1144
- function nt(e, t) {
1144
+ function rt(e, t) {
1145
1145
  return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
1146
1146
  }
1147
1147
  //#endregion
1148
+ //#region src/core/drm/signedUrl.ts
1149
+ function K(e, t = Date.now()) {
1150
+ if (!e) return !1;
1151
+ let n;
1152
+ try {
1153
+ n = new URL(e).searchParams.get("exp");
1154
+ } catch {
1155
+ return !1;
1156
+ }
1157
+ return !n || !/^\d+$/.test(n) ? !1 : Number(n) * 1e3 <= t;
1158
+ }
1159
+ //#endregion
1148
1160
  //#region src/core/engine/prefetch.ts
1149
- var rt = 15e3, it = 5e3, at = 6;
1150
- function ot(e) {
1161
+ var it = 15e3, at = 5e3, ot = 6;
1162
+ function st(e) {
1151
1163
  try {
1152
1164
  let t = new URL(e);
1153
1165
  return t.searchParams.delete("CMCD"), t.toString();
@@ -1155,7 +1167,7 @@ function ot(e) {
1155
1167
  return e;
1156
1168
  }
1157
1169
  }
1158
- var st = class {
1170
+ var ct = class {
1159
1171
  cmcd;
1160
1172
  entries = /* @__PURE__ */ new Map();
1161
1173
  keyed = /* @__PURE__ */ new Set();
@@ -1178,10 +1190,10 @@ var st = class {
1178
1190
  }
1179
1191
  prefetch(e, t, n) {
1180
1192
  if (!this.isActive(t)) return;
1181
- let r = ot(e);
1193
+ let r = st(e);
1182
1194
  if (this.entries.has(r)) return;
1183
1195
  let i = this.perTrack.get(t) ?? 0;
1184
- if (i >= at) return;
1196
+ if (i >= ot) return;
1185
1197
  this.perTrack.set(t, i + 1);
1186
1198
  let a = new AbortController(), o = {
1187
1199
  started: performance.now(),
@@ -1195,10 +1207,10 @@ var st = class {
1195
1207
  signal: a.signal
1196
1208
  }).then(async (e) => (o.first = performance.now(), e.ok ? e.arrayBuffer() : null)).catch(() => null), this.entries.set(r, o), this.stats.started += 1, setTimeout(() => {
1197
1209
  this.entries.get(r) === o && (this.entries.delete(r), a.abort(), this.stats.expired += 1);
1198
- }, rt);
1210
+ }, it);
1199
1211
  }
1200
1212
  take(e) {
1201
- let t = ot(e), n = this.entries.get(t);
1213
+ let t = st(e), n = this.entries.get(t);
1202
1214
  return n && this.entries.delete(t), n;
1203
1215
  }
1204
1216
  destroy() {
@@ -1218,13 +1230,13 @@ var st = class {
1218
1230
  }
1219
1231
  }
1220
1232
  };
1221
- function ct(t) {
1233
+ function lt(t) {
1222
1234
  let n = e.DefaultConfig?.loader;
1223
1235
  if (typeof n != "function") return;
1224
1236
  class r extends n {
1225
1237
  served = !1;
1226
1238
  cancelled = !1;
1227
- stats = lt();
1239
+ stats = ut();
1228
1240
  load(e, n, r) {
1229
1241
  let i = e.rangeEnd ? void 0 : t.take(e.url);
1230
1242
  if (!i) {
@@ -1232,7 +1244,7 @@ function ct(t) {
1232
1244
  return;
1233
1245
  }
1234
1246
  this.served = !0;
1235
- let a = performance.now(), o = n?.loadPolicy?.maxTimeToFirstByteMs || it, s = setTimeout(() => {
1247
+ let a = performance.now(), o = n?.loadPolicy?.maxTimeToFirstByteMs || at, s = setTimeout(() => {
1236
1248
  !this.cancelled && this.served && (i.abort.abort(), t.stats.failed += 1, this.served = !1, super.load(e, n, r));
1237
1249
  }, o);
1238
1250
  i.bytes.then((o) => {
@@ -1259,7 +1271,7 @@ function ct(t) {
1259
1271
  }
1260
1272
  return r;
1261
1273
  }
1262
- function lt() {
1274
+ function ut() {
1263
1275
  return {
1264
1276
  aborted: !1,
1265
1277
  loaded: 0,
@@ -1283,10 +1295,10 @@ function lt() {
1283
1295
  }
1284
1296
  };
1285
1297
  }
1286
- function ut(e) {
1298
+ function dt(e) {
1287
1299
  return e === "audio" ? "audio" : "main";
1288
1300
  }
1289
- function dt(e, t) {
1301
+ function ft(e, t) {
1290
1302
  let n = [];
1291
1303
  t.initSegment?.url && !t.initSegment.data && n.push({
1292
1304
  url: t.initSegment.url,
@@ -1305,24 +1317,24 @@ function dt(e, t) {
1305
1317
  }
1306
1318
  //#endregion
1307
1319
  //#region src/core/engine/hls.ts
1308
- function ft() {
1320
+ function pt() {
1309
1321
  return e.isSupported();
1310
1322
  }
1311
- var pt = 3, mt = 5, ht = 3, gt = 3e4, _t = 12e3, vt = 3e3, yt = 6e3, bt = .5, xt = 3, St = .5, Ct = 1.1, wt = .25, Tt = 5e3, Et = 4e3, Dt = 2, Ot = 5, kt = 2, At = 2, jt = 4, Mt = 4, Nt = 2e3, Pt = 3e4, Ft = .2, It = 30, Lt = 2, Rt = 2, zt = 9, Bt = .5, Vt = 64;
1312
- function Ht(e) {
1323
+ var mt = 3, ht = 5, gt = 3, _t = 1e4, vt = 3e4, yt = 12e3, bt = 3e3, xt = .02, St = 6e3, Ct = .5, wt = 3, Tt = .5, Et = 1.1, Dt = .25, Ot = 5e3, kt = 4e3, At = 2, jt = 5, Mt = 2, Nt = 2, Pt = 4, Ft = 4, It = 2e3, Lt = 3e4, Rt = .2, zt = 30, Bt = 2, Vt = 2, Ht = 9, Ut = .5, Wt = 64;
1324
+ function Gt(e) {
1313
1325
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
1314
1326
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
1315
1327
  return;
1316
1328
  }
1317
- e.liveSyncDuration ??= Rt;
1318
- let t = Math.max(12, e.liveSyncDuration + zt);
1319
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Ut(e, e.liveSyncDuration);
1329
+ e.liveSyncDuration ??= Vt;
1330
+ let t = Math.max(12, e.liveSyncDuration + Ht);
1331
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Kt(e, e.liveSyncDuration);
1320
1332
  }
1321
- function Ut(e, t) {
1333
+ function Kt(e, t) {
1322
1334
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
1323
1335
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
1324
1336
  }
1325
- var Wt = class {
1337
+ var qt = class {
1326
1338
  name = "hls";
1327
1339
  hls = null;
1328
1340
  deps;
@@ -1402,11 +1414,11 @@ var Wt = class {
1402
1414
  frameCounter;
1403
1415
  detachFrames = null;
1404
1416
  constructor(e) {
1405
- this.deps = e, this.frameCounter = new H(e.video), this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl, this.loadStarted = e.deferLoad !== !0;
1417
+ this.deps = e, this.frameCounter = new V(e.video), this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl, this.loadStarted = e.deferLoad !== !0;
1406
1418
  }
1407
1419
  async load() {
1408
1420
  this.detachFrames ??= this.frameCounter.attach();
1409
- let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = W(r) && G(), o = !!e.drm?.widevine?.licenseUrl && !a;
1421
+ let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = U(r) && W(), o = !!e.drm?.widevine?.licenseUrl && !a;
1410
1422
  if (e.drmEnabled && !o) {
1411
1423
  i({
1412
1424
  type: "error",
@@ -1438,7 +1450,7 @@ var Wt = class {
1438
1450
  }
1439
1451
  this.loadSuspended = !0;
1440
1452
  }
1441
- }, Tt));
1453
+ }, Ot));
1442
1454
  }
1443
1455
  resumeLoading() {
1444
1456
  if (this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null), this.resetChaseWindow(), this.resetFrameDropWindow(), this.loadSuspended && (this.loadSuspended = !1, !this.stopped && this.hls)) {
@@ -1470,7 +1482,22 @@ var Wt = class {
1470
1482
  let t = e.liveSyncPosition;
1471
1483
  if (typeof t != "number" || !Number.isFinite(t)) return;
1472
1484
  let { video: n } = this.deps;
1473
- n.currentTime <= t + Bt || (n.currentTime = t);
1485
+ n.currentTime <= t + Ut || (n.currentTime = t);
1486
+ }
1487
+ reanchorPartTimeline(e, t) {
1488
+ let { frag: n, part: r } = t;
1489
+ if (!r || r.index !== 0 || n.type !== "main" || typeof n.sn != "number") return;
1490
+ let i = e.levels[n.level]?.details;
1491
+ if (!i?.live) return;
1492
+ let a = r.elementaryStreams.video ?? r.elementaryStreams.audiovideo ?? r.elementaryStreams.audio;
1493
+ if (!a) return;
1494
+ let o = n.sn, s = i.fragments.find((e) => e && e.sn === o) ?? (i.fragmentHint?.sn === o ? i.fragmentHint : void 0);
1495
+ if (!s) return;
1496
+ let c = a.startPTS - s.start;
1497
+ if (!(!Number.isFinite(c) || Math.abs(c) < xt)) {
1498
+ for (let e of i.fragments) e && e.setStart(e.start + c);
1499
+ i.fragmentHint?.setStart(i.fragmentHint.start + c), this.deps.debug === !0 && console.info(`havik-player: LL-HLS timeline re-anchored by ${c >= 0 ? "+" : ""}${c.toFixed(3)}s at sn ${n.sn} (level ${n.level})`);
1500
+ }
1474
1501
  }
1475
1502
  startHls() {
1476
1503
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
@@ -1496,10 +1523,21 @@ var Wt = class {
1496
1523
  },
1497
1524
  ...j() ? { requestMediaKeySystemAccessFunc: P(t) } : {},
1498
1525
  licenseXhrSetup: (e, t) => {
1499
- e.open("POST", this.licenseUrl ?? t, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a), this.licenseRequested = !0;
1500
- let o = performance.now();
1501
- e.addEventListener("loadend", () => {
1502
- this.licenseTimeMs === 0 && (this.licenseTimeMs = Math.max(1, Math.round(performance.now() - o)));
1526
+ let o = performance.now(), s = () => {
1527
+ e.open("POST", this.licenseUrl ?? t, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a), this.licenseRequested = !0, e.addEventListener("loadend", () => {
1528
+ this.licenseTimeMs === 0 && (this.licenseTimeMs = Math.max(1, Math.round(performance.now() - o)));
1529
+ });
1530
+ }, { freshLicenseUrl: c } = this.deps;
1531
+ if (!c || !K(this.licenseUrl)) {
1532
+ s();
1533
+ return;
1534
+ }
1535
+ let l = this.hls, u, d = new Promise((e) => {
1536
+ u = setTimeout(() => e(void 0), _t);
1537
+ });
1538
+ return Promise.race([c().catch(() => void 0), d]).finally(() => clearTimeout(u)).then((e) => {
1539
+ if (!this.hls || this.hls !== l) throw Error("havik-player: engine torn down during refresh");
1540
+ e && (this.licenseUrl = e), s();
1503
1541
  });
1504
1542
  }
1505
1543
  } : {},
@@ -1511,20 +1549,20 @@ var Wt = class {
1511
1549
  ...c
1512
1550
  };
1513
1551
  if (this.prefetch?.destroy(), this.prefetch = null, u && this.deps.mediaDuringKey !== !1 && !d.fLoader) {
1514
- let e = new st({
1552
+ let e = new ct({
1515
1553
  sessionId: n.analytics?.sid,
1516
1554
  contentId: n.matchUrn
1517
- }), t = ct(e);
1555
+ }), t = lt(e);
1518
1556
  t && (this.prefetch = e, d.fLoader = t);
1519
1557
  }
1520
- Ht(d), this.catchUpRate = typeof d.maxLiveSyncPlaybackRate == "number" ? d.maxLiveSyncPlaybackRate : 1, this.catchUpEngaged = !1, this.catchUpRate > 1 && (d.maxLiveSyncPlaybackRate = 1), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.fpsCapManaged = !(c && "capLevelOnFPSDrop" in c), this.loadStarted = d.autoStartLoad !== !1;
1558
+ Gt(d), this.catchUpRate = typeof d.maxLiveSyncPlaybackRate == "number" ? d.maxLiveSyncPlaybackRate : 1, this.catchUpEngaged = !1, this.catchUpRate > 1 && (d.maxLiveSyncPlaybackRate = 1), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.fpsCapManaged = !(c && "capLevelOnFPSDrop" in c), this.loadStarted = d.autoStartLoad !== !1;
1521
1559
  let f = new e(d);
1522
- if (this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
1560
+ if (this.hls = f, f.on(e.Events.FRAG_PARSED, (e, t) => this.reanchorPartTimeline(f, t)), f.on(e.Events.MANIFEST_PARSED, () => {
1523
1561
  this.mediaRecoveries = 0, this.networkRecoveries = 0, u && !this.deps.hlsConfig?.requestMediaKeySystemAccessFunc && this.dropLevelsWidevineCannotPlay(f), this.measureLadder(f), this.applySizeCap(), this.loadSuspended = !1, t.paused && this.schedulePauseSuspend(), l({ type: "ready" });
1524
1562
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
1525
1563
  t?.frag?.type === "main" && (this.kickKind !== "dead" && this.clearKick(), (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0 || this.starvationRebuilds > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
1526
1564
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.starvationRebuilds = 0, this.recoveryResetTimer = null;
1527
- }, gt)));
1565
+ }, vt)));
1528
1566
  }), f.on(e.Events.FRAG_LOADING, (e, t) => {
1529
1567
  t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
1530
1568
  }), f.on(e.Events.FRAG_LOADED, (e, t) => {
@@ -1532,10 +1570,10 @@ var Wt = class {
1532
1570
  let n = t?.frag?.stats, r = n?.loaded;
1533
1571
  typeof r == "number" && (this.bytesLoaded += r), !this.reportedFirstBytes && typeof r == "number" && r > 0 && (this.reportedFirstBytes = !0, l({ type: "firstBytes" }));
1534
1572
  let i = n?.loading;
1535
- 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 > Vt && this.requestTimesMs.shift());
1573
+ 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 > Wt && this.requestTimesMs.shift());
1536
1574
  let a = t?.frag?.url;
1537
1575
  if (a) {
1538
- let e = B(a);
1576
+ let e = z(a);
1539
1577
  e && (this.cdnHost = e);
1540
1578
  }
1541
1579
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => {
@@ -1549,15 +1587,15 @@ var Wt = class {
1549
1587
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.prefetch) {
1550
1588
  let t = this.prefetch;
1551
1589
  f.on(e.Events.KEY_LOADING, (e, n) => {
1552
- let r = n?.frag, i = ut(r?.type);
1553
- if (r && t.isActive(i)) for (let e of dt(f, r)) t.prefetch(e.url, i, e.kind);
1554
- }), f.on(e.Events.KEY_LOADED, (e, n) => t.keyUsable(ut(n?.frag?.type)));
1590
+ let r = n?.frag, i = dt(r?.type);
1591
+ if (r && t.isActive(i)) for (let e of ft(f, r)) t.prefetch(e.url, i, e.kind);
1592
+ }), f.on(e.Events.KEY_LOADED, (e, n) => t.keyUsable(dt(n?.frag?.type)));
1555
1593
  }
1556
1594
  f.on(e.Events.KEY_LOADED, () => {
1557
1595
  u && !this.reportedDrmReady && (this.reportedDrmReady = !0, l({ type: "drmReady" }));
1558
1596
  }), this.endWatchdog && clearInterval(this.endWatchdog), this.endWatchdog = setInterval(() => {
1559
1597
  this.checkEndStale(), this.checkStarvation(), this.updateCatchUpBand(), this.checkFutileChase(), this.checkFrameDrops();
1560
- }, vt), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1598
+ }, bt), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1561
1599
  this.renditionSwitches += 1;
1562
1600
  let n = this.lastLevelIndex >= 0 ? f.levels[this.lastLevelIndex] : void 0, r = f.levels[t.level];
1563
1601
  n && r && r.bitrate !== n.bitrate && (r.bitrate > n.bitrate ? this.upshifts += 1 : this.downshifts += 1), this.lastLevelIndex = t.level, l({
@@ -1621,7 +1659,7 @@ var Wt = class {
1621
1659
  return;
1622
1660
  }
1623
1661
  this.capTimer && clearTimeout(this.capTimer);
1624
- let t = e > this.sizeCapLevel ? Nt : Pt;
1662
+ let t = e > this.sizeCapLevel ? It : Lt;
1625
1663
  this.capTimer = setTimeout(() => {
1626
1664
  this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1627
1665
  }, t);
@@ -1742,7 +1780,7 @@ var Wt = class {
1742
1780
  return;
1743
1781
  }
1744
1782
  let e = Date.now() - this.lastAdvanceMs;
1745
- !this.chaseSuspended && e > yt && this.suspendChasing(), e > _t && this.emitEnded("stalled");
1783
+ !this.chaseSuspended && e > St && this.suspendChasing(), e > yt && this.emitEnded("stalled");
1746
1784
  }
1747
1785
  resetStarvationWindow() {
1748
1786
  this.starvedTicks = 0, this.deadTicks = 0, this.mainFragsSinceTick = 0, this.recentMainFrags = [];
@@ -1757,7 +1795,7 @@ var Wt = class {
1757
1795
  this.resetStarvationWindow();
1758
1796
  return;
1759
1797
  }
1760
- let n = Date.now(), r = this.lastAdvanceMs !== 0 && n - this.lastAdvanceMs < yt, i = this.lastFragLoadingMs !== 0 && n - this.lastFragLoadingMs > Et, a = t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused, { totalFrames: o, dropped: s } = V(t), c = o - s, l = c !== this.starvedFrames;
1798
+ let n = Date.now(), r = this.lastAdvanceMs !== 0 && n - this.lastAdvanceMs < St, i = this.lastFragLoadingMs !== 0 && n - this.lastFragLoadingMs > kt, a = t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused, { totalFrames: o, dropped: s } = B(t), c = o - s, l = c !== this.starvedFrames;
1761
1799
  if (this.starvedFrames = c, !a || l) {
1762
1800
  (t.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA || l) && this.clearKick(), this.resetStarvationWindow();
1763
1801
  return;
@@ -1766,12 +1804,12 @@ var Wt = class {
1766
1804
  this.resetStarvationWindow();
1767
1805
  return;
1768
1806
  }
1769
- if (this.kickPending && (this.kickKind === "dead" || i) && (this.kickTicks += 1), this.starvedTicks = i ? this.starvedTicks + 1 : 0, this.deadTicks += 1, this.recentMainFrags.push(this.mainFragsSinceTick), this.mainFragsSinceTick = 0, this.recentMainFrags.length > jt && this.recentMainFrags.shift(), this.clearRecoveryReset(), this.kickPending && this.kickTicks >= kt && (this.clearKick(), this.starvationRebuilds < At)) {
1807
+ if (this.kickPending && (this.kickKind === "dead" || i) && (this.kickTicks += 1), this.starvedTicks = i ? this.starvedTicks + 1 : 0, this.deadTicks += 1, this.recentMainFrags.push(this.mainFragsSinceTick), this.mainFragsSinceTick = 0, this.recentMainFrags.length > Pt && this.recentMainFrags.shift(), this.clearRecoveryReset(), this.kickPending && this.kickTicks >= Mt && (this.clearKick(), this.starvationRebuilds < Nt)) {
1770
1808
  this.starvationRebuilds += 1, this.resetStarvationWindow(), this.clearRecoveryReset(), 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");
1771
1809
  return;
1772
1810
  }
1773
- let u = i && this.starvedTicks >= Dt, d = !i && this.deadTicks >= jt && this.recentMainFrags.reduce((e, t) => e + t, 0) >= Mt;
1774
- if ((u || d) && (this.resetStarvationWindow(), !(this.starvationKicks >= Ot))) {
1811
+ let u = i && this.starvedTicks >= At, d = !i && this.deadTicks >= Pt && this.recentMainFrags.reduce((e, t) => e + t, 0) >= Ft;
1812
+ if ((u || d) && (this.resetStarvationWindow(), !(this.starvationKicks >= jt))) {
1775
1813
  this.starvationKicks += 1, this.kickPending = !0, this.kickTicks = 0, this.kickKind = d ? "dead" : "quiet", this.clearRecoveryReset(), console.warn(d ? "havik-player: video starved while hls.js keeps loading media it never plays (dead pipeline at the live edge) — restarting the load" : "havik-player: video starved while the live playlist advances (wedged hls.js fragment pipeline) — restarting the load");
1776
1814
  try {
1777
1815
  e.stopLoad(), e.startLoad(-1);
@@ -1786,7 +1824,7 @@ var Wt = class {
1786
1824
  let t = e.latency, n = e.targetLatency;
1787
1825
  if (typeof t != "number" || typeof n != "number" || !Number.isFinite(t)) return;
1788
1826
  let r = t - n;
1789
- !this.catchUpEngaged && r > bt ? this.catchUpEngaged = !0 : this.catchUpEngaged && r <= bt && this.deps.video.playbackRate === 1 && (this.catchUpEngaged = !1);
1827
+ !this.catchUpEngaged && r > Ct ? this.catchUpEngaged = !0 : this.catchUpEngaged && r <= Ct && this.deps.video.playbackRate === 1 && (this.catchUpEngaged = !1);
1790
1828
  let i = this.catchUpEngaged ? this.catchUpRate : 1;
1791
1829
  e.config.maxLiveSyncPlaybackRate !== i && (e.config.maxLiveSyncPlaybackRate = i);
1792
1830
  }
@@ -1807,16 +1845,16 @@ var Wt = class {
1807
1845
  this.resetFrameDropWindow();
1808
1846
  return;
1809
1847
  }
1810
- let { totalFrames: n, dropped: r } = V(t), i = this.fpsLastFrames < 0, a = n - this.fpsLastFrames, o = r - this.fpsLastDropped;
1811
- if (this.fpsLastFrames = n, this.fpsLastDropped = r, i || a < It || o < 0) {
1848
+ let { totalFrames: n, dropped: r } = B(t), i = this.fpsLastFrames < 0, a = n - this.fpsLastFrames, o = r - this.fpsLastDropped;
1849
+ if (this.fpsLastFrames = n, this.fpsLastDropped = r, i || a < zt || o < 0) {
1812
1850
  this.fpsBadTicks = 0;
1813
1851
  return;
1814
1852
  }
1815
- if (o <= Ft * a) {
1853
+ if (o <= Rt * a) {
1816
1854
  this.fpsBadTicks = 0;
1817
1855
  return;
1818
1856
  }
1819
- if (this.fpsBadTicks += 1, this.fpsBadTicks < Lt) return;
1857
+ if (this.fpsBadTicks += 1, this.fpsBadTicks < Bt) return;
1820
1858
  this.fpsBadTicks = 0;
1821
1859
  let s = e.levels, c = s[e.currentLevel];
1822
1860
  if (!c) return;
@@ -1833,20 +1871,20 @@ var Wt = class {
1833
1871
  return;
1834
1872
  }
1835
1873
  let n = e.latency, r = t.playbackRate, i = e.config.maxLiveSyncPlaybackRate;
1836
- if (!(!t.paused && typeof i == "number" && i > 1 && r >= Math.min(Ct, i) && r <= i && typeof n == "number" && Number.isFinite(n))) {
1874
+ if (!(!t.paused && typeof i == "number" && i > 1 && r >= Math.min(Et, i) && r <= i && typeof n == "number" && Number.isFinite(n))) {
1837
1875
  this.resetChaseWindow();
1838
1876
  return;
1839
1877
  }
1840
- if (this.chaseLatency < 0 || this.chaseLatency - n >= St) {
1878
+ if (this.chaseLatency < 0 || this.chaseLatency - n >= Tt) {
1841
1879
  this.chaseLatency = n, this.chaseTicks = 0;
1842
1880
  return;
1843
1881
  }
1844
- if (this.chaseTicks += 1, this.chaseTicks < xt) return;
1882
+ if (this.chaseTicks += 1, this.chaseTicks < wt) return;
1845
1883
  this.resetChaseWindow();
1846
1884
  let a = typeof e.config.liveMaxLatencyDuration == "number" ? e.config.liveMaxLatencyDuration : e.maxLatency;
1847
1885
  if (!(a > 1)) return;
1848
1886
  let o = typeof e.targetLatency == "number" ? e.targetLatency : 0, s = Math.min(n, a - 1);
1849
- s < o + wt || (console.warn(`havik-player: catch-up at ${r}x not gaining on the live edge (latency ${n.toFixed(1)}s); holding at ${s.toFixed(1)}s`), this.liveRetargets += 1, e.targetLatency = s);
1887
+ s < o + Dt || (console.warn(`havik-player: catch-up at ${r}x not gaining on the live edge (latency ${n.toFixed(1)}s); holding at ${s.toFixed(1)}s`), this.liveRetargets += 1, e.targetLatency = s);
1850
1888
  }
1851
1889
  suspendChasing() {
1852
1890
  let e = this.hls;
@@ -1891,7 +1929,7 @@ var Wt = class {
1891
1929
  scheduleLicenseRetry(t) {
1892
1930
  if (t.details !== e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED && t.details !== e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED) return !1;
1893
1931
  let n = t.response?.code ?? 0;
1894
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= ht) return !1;
1932
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= gt) return !1;
1895
1933
  this.licenseRecoveries += 1;
1896
1934
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1897
1935
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1923,7 +1961,7 @@ var Wt = class {
1923
1961
  return;
1924
1962
  }
1925
1963
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1926
- if (this.networkRecoveries < mt) {
1964
+ if (this.networkRecoveries < ht) {
1927
1965
  this.networkRecoveries += 1;
1928
1966
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1929
1967
  this.networkRetryTimer = setTimeout(() => {
@@ -1940,7 +1978,7 @@ var Wt = class {
1940
1978
  return;
1941
1979
  }
1942
1980
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1943
- if (this.mediaRecoveries < pt) {
1981
+ if (this.mediaRecoveries < mt) {
1944
1982
  this.mediaRecoveries += 1, n.recoverMediaError();
1945
1983
  return;
1946
1984
  }
@@ -1964,34 +2002,19 @@ var Wt = class {
1964
2002
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1965
2003
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1966
2004
  }
1967
- };
1968
- //#endregion
1969
- //#region src/core/drm/signedUrl.ts
1970
- function Gt(e, t = Date.now()) {
1971
- if (!e) return !1;
1972
- let n;
1973
- try {
1974
- n = new URL(e).searchParams.get("exp");
1975
- } catch {
1976
- return !1;
1977
- }
1978
- return !n || !/^\d+$/.test(n) ? !1 : Number(n) * 1e3 <= t;
1979
- }
1980
- //#endregion
1981
- //#region src/core/analytics.ts
1982
- var Kt = 15e3, qt = 6e5, Jt = 3e5, Yt = 100, q = 1, Xt = 1e3, Zt = 3e4, Qt = 1e3, $t = {
2005
+ }, Jt = 15e3, Yt = 6e5, Xt = 3e5, Zt = 100, q = 1, Qt = 1e3, $t = 3e4, en = 1e3, tn = {
1983
2006
  inline: "PRESENTATION_INLINE",
1984
2007
  fullscreen: "PRESENTATION_FULLSCREEN",
1985
2008
  pip: "PRESENTATION_PIP"
1986
- }, en = {
2009
+ }, nn = {
1987
2010
  widevine: "KEY_SYSTEM_WIDEVINE",
1988
2011
  fairplay: "KEY_SYSTEM_FAIRPLAY",
1989
2012
  none: "KEY_SYSTEM_NONE"
1990
2013
  };
1991
- function tn(e, t) {
2014
+ function rn(e, t) {
1992
2015
  return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
1993
2016
  }
1994
- var nn = /* @__PURE__ */ new Set([
2017
+ var an = /* @__PURE__ */ new Set([
1995
2018
  "INVALID_URN",
1996
2019
  "NOT_FOUND",
1997
2020
  "GONE",
@@ -2011,7 +2034,7 @@ var nn = /* @__PURE__ */ new Set([
2011
2034
  ended: "PLAYER_STATE_ENDED",
2012
2035
  error: "PLAYER_STATE_ERROR"
2013
2036
  };
2014
- function rn(e) {
2037
+ function on(e) {
2015
2038
  try {
2016
2039
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
2017
2040
  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);
@@ -2019,8 +2042,8 @@ function rn(e) {
2019
2042
  return;
2020
2043
  }
2021
2044
  }
2022
- var an = (e, t) => e.replace(/\/$/, "").toLowerCase() === t.replace(/\/$/, "").toLowerCase(), on = /* @__PURE__ */ new Set(), Y = /* @__PURE__ */ new Set(), X = /* @__PURE__ */ new Set(), sn = (e) => `this page's Content-Security-Policy blocks QoE beacons. Add ${e} to connect-src; until then none are sent.`;
2023
- function cn(e) {
2045
+ var sn = (e, t) => e.replace(/\/$/, "").toLowerCase() === t.replace(/\/$/, "").toLowerCase(), cn = /* @__PURE__ */ new Set(), Y = /* @__PURE__ */ new Set(), X = /* @__PURE__ */ new Set(), ln = (e) => `this page's Content-Security-Policy blocks QoE beacons. Add ${e} to connect-src; until then none are sent.`;
2046
+ function un(e) {
2024
2047
  if (e.disposition !== "enforce" || e.effectiveDirective !== "connect-src") return;
2025
2048
  let t;
2026
2049
  try {
@@ -2029,19 +2052,19 @@ function cn(e) {
2029
2052
  } catch {
2030
2053
  return;
2031
2054
  }
2032
- if (on.has(t) && !Y.has(t)) {
2033
- Y.add(t), g(sn(new URL(t).origin));
2055
+ if (cn.has(t) && !Y.has(t)) {
2056
+ Y.add(t), g(ln(new URL(t).origin));
2034
2057
  for (let e of X) e.noteCspBlocked();
2035
2058
  }
2036
2059
  }
2037
- function ln(e, t) {
2060
+ function dn(e, t) {
2038
2061
  return g(t, e), { disabledReason: t };
2039
2062
  }
2040
- function un(e, t, n) {
2041
- if (n) return t && !an(t, n) && g(`analytics.endpoint "${t}" ignored; the server named "${n}".`), { endpoint: n };
2063
+ function fn(e, t, n) {
2064
+ if (n) return t && !sn(t, n) && g(`analytics.endpoint "${t}" ignored; the server named "${n}".`), { endpoint: n };
2042
2065
  if (t) return { endpoint: t };
2043
- 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).");
2044
- let r = rn(e);
2066
+ if (t !== void 0) return dn("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).");
2067
+ let r = on(e);
2045
2068
  if (r) return { endpoint: r };
2046
2069
  let i;
2047
2070
  try {
@@ -2049,9 +2072,9 @@ function un(e, t, n) {
2049
2072
  } catch {
2050
2073
  i = e;
2051
2074
  }
2052
- 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).`);
2075
+ return dn(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).`);
2053
2076
  }
2054
- var dn = class {
2077
+ var pn = class {
2055
2078
  opts;
2056
2079
  url;
2057
2080
  cspKey;
@@ -2133,8 +2156,8 @@ var dn = class {
2133
2156
  constructor(e) {
2134
2157
  this.opts = {
2135
2158
  ...e,
2136
- heartbeatMs: e.heartbeatMs ?? Kt,
2137
- idleMs: e.idleMs ?? qt
2159
+ heartbeatMs: e.heartbeatMs ?? Jt,
2160
+ idleMs: e.idleMs ?? Yt
2138
2161
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`;
2139
2162
  try {
2140
2163
  let e = new URL(this.url);
@@ -2151,11 +2174,11 @@ var dn = class {
2151
2174
  sdkVersion: this.opts.sdkVersion,
2152
2175
  ...this.opts.environment
2153
2176
  };
2154
- this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), Xt), 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 && on.add(this.cspKey), document.addEventListener("securitypolicyviolation", cn), X.add(this), this.noteCspBlocked(), this.onVisibility());
2177
+ this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), Qt), 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 && cn.add(this.cspKey), document.addEventListener("securitypolicyviolation", un), X.add(this), this.noteCspBlocked(), this.onVisibility());
2155
2178
  }
2156
2179
  checkSuspension(e) {
2157
2180
  if (this.endedFlag || !this.opts.idleMs) return !1;
2158
- let t = Math.max(this.opts.idleMs, Jt);
2181
+ let t = Math.max(this.opts.idleMs, Xt);
2159
2182
  return this.lastTickMs > 0 && e - this.lastTickMs >= t ? (this.endIdle(this.lastTickMs), !0) : (this.lastTickMs = e, !1);
2160
2183
  }
2161
2184
  syncIdle(e) {
@@ -2178,12 +2201,12 @@ var dn = class {
2178
2201
  let a = {
2179
2202
  fromState: J[e] ?? "PLAYER_STATE_UNSPECIFIED",
2180
2203
  toState: J[t] ?? "PLAYER_STATE_UNSPECIFIED",
2181
- presentation: $t[this.presentation]
2204
+ presentation: tn[this.presentation]
2182
2205
  }, o = this.causeOf(e, t, i);
2183
2206
  o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
2184
2207
  }
2185
2208
  seekInduced(e) {
2186
- return this.lastSeekMs >= 0 && e - this.lastSeekMs < Qt;
2209
+ return this.lastSeekMs >= 0 && e - this.lastSeekMs < en;
2187
2210
  }
2188
2211
  causeOf(e, t, n) {
2189
2212
  if (t === "error") return "TRANSITION_CAUSE_ERROR";
@@ -2257,7 +2280,7 @@ var dn = class {
2257
2280
  let r = n ?? "";
2258
2281
  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";
2259
2282
  if (e === "DRM_CLIENT") return "ERROR_STAGE_DRM";
2260
- if (nn.has(e)) return "ERROR_STAGE_RESOLVE";
2283
+ if (an.has(e)) return "ERROR_STAGE_RESOLVE";
2261
2284
  let i = t ?? "";
2262
2285
  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";
2263
2286
  }
@@ -2304,14 +2327,14 @@ var dn = class {
2304
2327
  if (this.lastRequestCount = g, _ > 0) {
2305
2328
  o.requestCountDelta = _;
2306
2329
  let e = t.requestTimesMs ?? [], n = e.slice(Math.max(0, e.length - _)).sort((e, t) => e - t);
2307
- n.length > 0 && (o.requestTimeP50Ms = tn(n, 50), o.requestTimeP95Ms = tn(n, 95));
2330
+ n.length > 0 && (o.requestTimeP50Ms = rn(n, 50), o.requestTimeP95Ms = rn(n, 95));
2308
2331
  }
2309
2332
  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();
2310
2333
  }
2311
2334
  maybeEmitMediaInfo(e) {
2312
2335
  if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
2313
2336
  let t = {};
2314
- e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = en[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);
2337
+ 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);
2315
2338
  let n = JSON.stringify(t);
2316
2339
  n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
2317
2340
  }
@@ -2331,7 +2354,7 @@ var dn = class {
2331
2354
  let c = r?.positionMs ?? 0;
2332
2355
  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));
2333
2356
  let l = e !== "END_REASON_ENDED";
2334
- l && this.lastStallMs > 0 && n - this.lastStallMs <= Zt && (i.abandonAfterStallMs = Math.max(q, Math.round(n - this.lastStallMs))), l && this.lastErrorMs > 0 && n - this.lastErrorMs <= Zt && (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);
2357
+ l && this.lastStallMs > 0 && n - this.lastStallMs <= $t && (i.abandonAfterStallMs = Math.max(q, Math.round(n - this.lastStallMs))), l && this.lastErrorMs > 0 && n - this.lastErrorMs <= $t && (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);
2335
2358
  let u = r?.decodedFrames ?? this.lastDecoded;
2336
2359
  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");
2337
2360
  }
@@ -2351,13 +2374,13 @@ var dn = class {
2351
2374
  };
2352
2375
  }
2353
2376
  noteCspBlocked() {
2354
- this.cspBlockedNoted || this.endedFlag || !this.cspBlocked() || (this.cspBlockedNoted = !0, this.opts.onBlocked?.(sn(new URL(this.url).origin)));
2377
+ this.cspBlockedNoted || this.endedFlag || !this.cspBlocked() || (this.cspBlockedNoted = !0, this.opts.onBlocked?.(ln(new URL(this.url).origin)));
2355
2378
  }
2356
2379
  cspBlocked() {
2357
2380
  return !!this.cspKey && Y.has(this.cspKey);
2358
2381
  }
2359
2382
  enqueue(e) {
2360
- if (!this.cspBlocked()) for (this.pending.push(this.wrap(e)); this.pending.length > Yt;) {
2383
+ if (!this.cspBlocked()) for (this.pending.push(this.wrap(e)); this.pending.length > Zt;) {
2361
2384
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
2362
2385
  this.pending.splice(e >= 0 ? e : 0, 1);
2363
2386
  }
@@ -2384,7 +2407,7 @@ var dn = class {
2384
2407
  };
2385
2408
  //#endregion
2386
2409
  //#region src/core/environment.ts
2387
- function fn() {
2410
+ function mn() {
2388
2411
  if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
2389
2412
  let e;
2390
2413
  try {
@@ -2409,14 +2432,14 @@ function fn() {
2409
2432
  integration: "INTEGRATION_KIND_IFRAME"
2410
2433
  };
2411
2434
  }
2412
- function pn() {
2435
+ function hn() {
2413
2436
  if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
2414
2437
  let e = navigator.userAgentData, t = e?.platform ?? "";
2415
2438
  if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
2416
2439
  let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
2417
2440
  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";
2418
2441
  }
2419
- function mn() {
2442
+ function gn() {
2420
2443
  let e = typeof navigator < "u" ? navigator.connection : void 0;
2421
2444
  if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
2422
2445
  let t = {
@@ -2435,17 +2458,17 @@ function mn() {
2435
2458
  }
2436
2459
  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;
2437
2460
  }
2438
- function hn(e) {
2439
- let t = fn(), n = {
2440
- deviceClass: pn(),
2461
+ function _n(e) {
2462
+ let t = mn(), n = {
2463
+ deviceClass: hn(),
2441
2464
  integration: t.integration,
2442
- ...mn()
2465
+ ...gn()
2443
2466
  };
2444
2467
  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);
2445
2468
  let r = e.getBoundingClientRect?.();
2446
2469
  return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
2447
2470
  }
2448
- async function gn() {
2471
+ async function vn() {
2449
2472
  let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
2450
2473
  if (!e?.getHighEntropyValues) return {};
2451
2474
  try {
@@ -2457,7 +2480,7 @@ async function gn() {
2457
2480
  }
2458
2481
  //#endregion
2459
2482
  //#region src/core/version.ts
2460
- var _n = "2.16.5", vn = {
2483
+ var yn = "2.16.7", bn = {
2461
2484
  accent: "#E1B600",
2462
2485
  accentText: "#1B1C23",
2463
2486
  background: "#0e0f13",
@@ -2468,7 +2491,7 @@ var _n = "2.16.5", vn = {
2468
2491
  border: "rgba(255, 255, 255, 0.1)",
2469
2492
  radius: "8px",
2470
2493
  fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
2471
- }, yn = {
2494
+ }, xn = {
2472
2495
  accent: "--havik-accent",
2473
2496
  accentText: "--havik-accent-text",
2474
2497
  background: "--havik-bg",
@@ -2480,26 +2503,26 @@ var _n = "2.16.5", vn = {
2480
2503
  radius: "--havik-radius",
2481
2504
  fontFamily: "--havik-font"
2482
2505
  };
2483
- function Z(e, t) {
2506
+ function Sn(e, t) {
2484
2507
  let n = {
2485
- ...vn,
2508
+ ...bn,
2486
2509
  ...t
2487
2510
  };
2488
- for (let t of Object.keys(yn)) e.style.setProperty(yn[t], n[t]);
2511
+ for (let t of Object.keys(xn)) e.style.setProperty(xn[t], n[t]);
2489
2512
  n.logoUrl && e.style.setProperty("--havik-logo", `url("${n.logoUrl}")`);
2490
2513
  }
2491
2514
  //#endregion
2492
2515
  //#region src/ui/styles.ts
2493
- var bn = "havik-player-styles", xn = "\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", Sn = /* @__PURE__ */ new WeakMap();
2494
- function Cn(e = document, t) {
2495
- let n = e.querySelector(`style#${bn}`);
2496
- if (n && (!t || Sn.get(n) === t)) return;
2516
+ var Cn = "havik-player-styles", wn = "\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", Z = /* @__PURE__ */ new WeakMap();
2517
+ function Tn(e = document, t) {
2518
+ let n = e.querySelector(`style#${Cn}`);
2519
+ if (n && (!t || Z.get(n) === t)) return;
2497
2520
  let r = e.createElement("style");
2498
- r.id = bn, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = xn, Sn.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2521
+ r.id = Cn, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = wn, Z.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2499
2522
  }
2500
2523
  //#endregion
2501
2524
  //#region src/ui/icons.ts
2502
- var wn = {
2525
+ var En = {
2503
2526
  play: "<path d=\"M8 5v14l11-7z\"/>",
2504
2527
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
2505
2528
  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\"/>",
@@ -2513,20 +2536,20 @@ var wn = {
2513
2536
  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\"/>"
2514
2537
  };
2515
2538
  function Q(e) {
2516
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${wn[e]}</svg>`;
2539
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${En[e]}</svg>`;
2517
2540
  }
2518
2541
  //#endregion
2519
2542
  //#region src/ui/controls.ts
2520
- var Tn = 3e3, En = 250, Dn = /* @__PURE__ */ new Set([
2543
+ var Dn = 3e3, On = 250, kn = /* @__PURE__ */ new Set([
2521
2544
  "GONE",
2522
2545
  "NOT_FOUND",
2523
2546
  "INVALID_URN",
2524
2547
  "UNAUTHORIZED"
2525
2548
  ]);
2526
- function On(e) {
2527
- return new kn(e);
2549
+ function An(e) {
2550
+ return new jn(e);
2528
2551
  }
2529
- var kn = class {
2552
+ var jn = class {
2530
2553
  root;
2531
2554
  video;
2532
2555
  player;
@@ -2572,7 +2595,7 @@ var kn = class {
2572
2595
  scrubbing = !1;
2573
2596
  offFns = [];
2574
2597
  constructor(e) {
2575
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Tn, 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, Cn(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");
2598
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Dn, 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, Tn(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");
2576
2599
  let t = (e, t, n) => {
2577
2600
  let r = this.doc.createElement(e);
2578
2601
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -2658,7 +2681,7 @@ var kn = class {
2658
2681
  let t = this.surfaceWasHidden;
2659
2682
  this.tapTimer = setTimeout(() => {
2660
2683
  this.tapTimer = void 0, this.toggleBar(t);
2661
- }, En);
2684
+ }, On);
2662
2685
  }
2663
2686
  toggleBar(e) {
2664
2687
  e ? this.poke() : this.hideIfIdle();
@@ -2693,7 +2716,7 @@ var kn = class {
2693
2716
  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);
2694
2717
  } else {
2695
2718
  let r = this.doc.createElement("div");
2696
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !Dn.has(this.lastError.code)) {
2719
+ if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !kn.has(this.lastError.code)) {
2697
2720
  let e = this.doc.createElement("button");
2698
2721
  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);
2699
2722
  }
@@ -2734,7 +2757,7 @@ var kn = class {
2734
2757
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
2735
2758
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
2736
2759
  }
2737
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${An(this.video.currentTime)} / ${An(this.video.duration)}`);
2760
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Mn(this.video.currentTime)} / ${Mn(this.video.duration)}`);
2738
2761
  }
2739
2762
  syncLive() {
2740
2763
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -2784,17 +2807,17 @@ var kn = class {
2784
2807
  renderQualityMenu() {
2785
2808
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
2786
2809
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
2787
- for (let n of e) this.menu.append(this.menuItem(jn(n), t === n.index, () => this.player.setQuality(n.index)));
2810
+ for (let n of e) this.menu.append(this.menuItem(Nn(n), t === n.index, () => this.player.setQuality(n.index)));
2788
2811
  }
2789
2812
  renderAudioMenu() {
2790
2813
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
2791
2814
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
2792
- for (let t of e) this.menu.append(this.menuItem(Mn(t), t.default, () => this.player.setAudioTrack(t.id)));
2815
+ for (let t of e) this.menu.append(this.menuItem(Pn(t), t.default, () => this.player.setAudioTrack(t.id)));
2793
2816
  }
2794
2817
  renderCcMenu() {
2795
2818
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
2796
2819
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
2797
- for (let t of e) this.menu.append(this.menuItem(Nn(t), !1, () => this.player.setTextTrack(t.id)));
2820
+ for (let t of e) this.menu.append(this.menuItem(Fn(t), !1, () => this.player.setTextTrack(t.id)));
2798
2821
  }
2799
2822
  onTracksChanged() {
2800
2823
  this.openMenu && this.renderMenu(this.openMenu);
@@ -2895,41 +2918,41 @@ var kn = class {
2895
2918
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
2896
2919
  }
2897
2920
  };
2898
- function An(e) {
2921
+ function Mn(e) {
2899
2922
  (!Number.isFinite(e) || e < 0) && (e = 0);
2900
2923
  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");
2901
2924
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
2902
2925
  }
2903
- function jn(e) {
2926
+ function Nn(e) {
2904
2927
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
2905
2928
  }
2906
- function Mn(e) {
2929
+ function Pn(e) {
2907
2930
  return e.name || e.lang || `Track ${e.id}`;
2908
2931
  }
2909
- function Nn(e) {
2932
+ function Fn(e) {
2910
2933
  return e.name || e.lang || `Track ${e.id}`;
2911
2934
  }
2912
2935
  //#endregion
2913
2936
  //#region src/managed/index.ts
2914
- var Pn = 6e3, Fn = 45e3, In = 2e3, Ln = 3, Rn = 2e3, zn = 48e4, Bn = "keySystemLicenseRequestFailed";
2915
- function Vn(e) {
2937
+ var In = 6e3, Ln = 45e3, Rn = 2e3, zn = 3, Bn = 2e3, Vn = 48e4, Hn = "keySystemLicenseRequestFailed";
2938
+ function Un(e) {
2916
2939
  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)})`);
2917
2940
  if (e.env !== void 0) return Ke(e.env);
2918
2941
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: env or baseUrl is required (env: 'integration' | 'production')");
2919
2942
  return e.baseUrl;
2920
2943
  }
2921
- async function Hn(e) {
2944
+ async function Wn(e) {
2922
2945
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
2923
- let t = Vn(e);
2946
+ let t = Un(e);
2924
2947
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
2925
2948
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
2926
- let r = new Un({
2949
+ let r = new Gn({
2927
2950
  ...e,
2928
2951
  baseUrl: t
2929
2952
  });
2930
2953
  return await r.start(), r;
2931
2954
  }
2932
- var Un = class {
2955
+ var Gn = class {
2933
2956
  opts;
2934
2957
  abort = new AbortController();
2935
2958
  listeners = {};
@@ -2940,6 +2963,8 @@ var Un = class {
2940
2963
  detachPip;
2941
2964
  statsTimer;
2942
2965
  refreshTimer;
2966
+ refreshInFlight;
2967
+ onRefocus;
2943
2968
  staleLicenseReloaded = !1;
2944
2969
  _state = "idle";
2945
2970
  _descriptor = null;
@@ -3018,7 +3043,7 @@ var Un = class {
3018
3043
  setTimeout(() => {
3019
3044
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
3020
3045
  phase: i ? "unavailable" : "tooEarly",
3021
- retryInMs: Fn,
3046
+ retryInMs: Ln,
3022
3047
  attempt: 1,
3023
3048
  elapsedMs: 0,
3024
3049
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -3057,13 +3082,13 @@ var Un = class {
3057
3082
  !a && !p() && h();
3058
3083
  }, e);
3059
3084
  };
3060
- _(Pn), this.armingSub = T({
3085
+ _(In), this.armingSub = T({
3061
3086
  eventsBaseUrl: t,
3062
3087
  matchUrn: this.opts.matchUrn,
3063
3088
  credential: this.opts.credential,
3064
- onAlive: () => _(Fn),
3089
+ onAlive: () => _(Ln),
3065
3090
  onState: (e) => {
3066
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * In);
3091
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Rn);
3067
3092
  },
3068
3093
  onError: () => h()
3069
3094
  });
@@ -3086,7 +3111,7 @@ var Un = class {
3086
3111
  let n = await s(this.opts.credential);
3087
3112
  if (this.destroyed || t !== this.loadGen) return;
3088
3113
  this.setState("loading");
3089
- let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = Ye(this.opts.video, (e) => {
3114
+ let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = Xe(this.opts.video, (e) => {
3090
3115
  this._state !== "ended" && this._state !== "error" && this.setState(e);
3091
3116
  });
3092
3117
  if (await i.load(), this.destroyed || t !== this.loadGen) {
@@ -3108,26 +3133,26 @@ var Un = class {
3108
3133
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
3109
3134
  let i = e.analytics?.sid;
3110
3135
  if (!i) return;
3111
- let a = typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0, { endpoint: o, disabledReason: s } = un(this.opts.baseUrl, a, e.analytics?.endpoint);
3136
+ let a = typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0, { endpoint: o, disabledReason: s } = fn(this.opts.baseUrl, a, e.analytics?.endpoint);
3112
3137
  if (!o) {
3113
3138
  s && setTimeout(() => {
3114
3139
  this.destroyed || r !== this.loadGen || this.emit("warning", new n("INTERNAL", 0, s));
3115
3140
  }, 0);
3116
3141
  return;
3117
3142
  }
3118
- let c = this.opts.video, l = new dn({
3143
+ let c = this.opts.video, l = new pn({
3119
3144
  endpoint: o,
3120
3145
  sid: i,
3121
3146
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
3122
- sdkVersion: _n,
3123
- environment: hn(c),
3147
+ sdkVersion: yn,
3148
+ environment: _n(c),
3124
3149
  idleMs: typeof this.opts.analytics == "object" ? this.opts.analytics.idleMs : void 0,
3125
3150
  onIdle: () => this.onAnalyticsIdle(t),
3126
3151
  onBlocked: (e) => setTimeout(() => {
3127
3152
  this.destroyed || r !== this.loadGen || this.emit("warning", new n("INTERNAL", 0, e));
3128
3153
  }, 0),
3129
3154
  sample: () => {
3130
- let e = this.engine?.getStats() ?? z, t = c.buffered.length ? Math.max(0, c.buffered.end(c.buffered.length - 1) - c.currentTime) : 0;
3155
+ let e = this.engine?.getStats() ?? qe, t = c.buffered.length ? Math.max(0, c.buffered.end(c.buffered.length - 1) - c.currentTime) : 0;
3131
3156
  return {
3132
3157
  ...e,
3133
3158
  bufferedMs: Math.round(t * 1e3),
@@ -3141,7 +3166,7 @@ var Un = class {
3141
3166
  let u = () => l.noteSeek(), d = () => l.noteVolume(c.muted, c.volume);
3142
3167
  c.addEventListener("seeking", u), c.addEventListener("volumechange", d), this.detachMediaListeners = () => {
3143
3168
  c.removeEventListener("seeking", u), c.removeEventListener("volumechange", d);
3144
- }, l.start(), l.seedVolume(c.muted, c.volume), l.notePresentation(this.currentPresentation()), gn().then((e) => l.noteEnvironment(e)), this.opts.autoplay === !1 && l.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
3169
+ }, l.start(), l.seedVolume(c.muted, c.volume), l.notePresentation(this.currentPresentation()), vn().then((e) => l.noteEnvironment(e)), this.opts.autoplay === !1 && l.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
3145
3170
  }
3146
3171
  currentPresentation() {
3147
3172
  let e = this.opts.video;
@@ -3164,13 +3189,14 @@ var Un = class {
3164
3189
  debug: this.opts.debug,
3165
3190
  deferLoad: r,
3166
3191
  hlsConfig: i,
3167
- emit: (e) => this.onEngineEvent(e)
3192
+ emit: (e) => this.onEngineEvent(e),
3193
+ freshLicenseUrl: () => this.refreshLicense()
3168
3194
  }, o = this.opts.engine ?? "auto";
3169
- if (o === "native") return new K(a);
3170
- if (o === "hls") return new Wt(a);
3171
- if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && W(this.opts.video) && G() || !e.drmEnabled && tt() && W(this.opts.video)) return new K(a);
3172
- if (ft()) return new Wt(a);
3173
- if (W(this.opts.video)) return new K(a);
3195
+ if (o === "native") return new G(a);
3196
+ if (o === "hls") return new qt(a);
3197
+ if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && U(this.opts.video) && W() || !e.drmEnabled && nt() && U(this.opts.video)) return new G(a);
3198
+ if (pt()) return new qt(a);
3199
+ if (U(this.opts.video)) return new G(a);
3174
3200
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
3175
3201
  }
3176
3202
  onEngineEvent(e) {
@@ -3232,7 +3258,7 @@ var Un = class {
3232
3258
  r = 0, i = !1;
3233
3259
  },
3234
3260
  onError: () => {
3235
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= Ln && (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.`))));
3261
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= zn && (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.`))));
3236
3262
  }
3237
3263
  });
3238
3264
  }
@@ -3254,7 +3280,7 @@ var Un = class {
3254
3280
  scheduleRejoin(e) {
3255
3281
  this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
3256
3282
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
3257
- }, Math.random() * In));
3283
+ }, Math.random() * Rn));
3258
3284
  }
3259
3285
  cancelRejoin() {
3260
3286
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -3278,7 +3304,7 @@ var Un = class {
3278
3304
  }
3279
3305
  t.controls = !1;
3280
3306
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
3281
- r && r.insertBefore(n, t), n.appendChild(t), Z(n, this.opts.theme), this.container = n, this.controlBar = On({
3307
+ r && r.insertBefore(n, t), n.appendChild(t), Sn(n, this.opts.theme), this.container = n, this.controlBar = An({
3282
3308
  root: n,
3283
3309
  video: t,
3284
3310
  player: this,
@@ -3309,7 +3335,7 @@ var Un = class {
3309
3335
  }
3310
3336
  startStats() {
3311
3337
  this.stopStats();
3312
- let e = Math.max(500, this.opts.statsIntervalMs ?? Rn);
3338
+ let e = Math.max(500, this.opts.statsIntervalMs ?? Bn);
3313
3339
  this.statsTimer = setInterval(() => {
3314
3340
  if (!this.engine) return;
3315
3341
  let e = this.engine.getStats();
@@ -3320,29 +3346,43 @@ var Un = class {
3320
3346
  this.statsTimer && clearInterval(this.statsTimer), this.statsTimer = void 0;
3321
3347
  }
3322
3348
  isStaleLicenseDenial(e) {
3323
- return this.staleLicenseReloaded || this.opts.hlsConfig?.licenseXhrSetup || this._state === "ended" || this._state === "error" || e.detail !== Bn || e.error.httpStatus !== 403 ? !1 : Gt(this._descriptor?.drm?.widevine?.licenseUrl);
3349
+ return this.staleLicenseReloaded || this.opts.hlsConfig?.licenseXhrSetup || this._state === "ended" || this._state === "error" || e.detail !== Hn || e.error.httpStatus !== 403 ? !1 : K(this._descriptor?.drm?.widevine?.licenseUrl);
3324
3350
  }
3325
3351
  scheduleLicenseRefresh() {
3326
- this.stopRefresh();
3327
- let e = this.opts.licenseRefreshMs ?? zn;
3328
- e && this._descriptor?.drmEnabled && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
3352
+ if (this.stopRefresh(), !this._descriptor?.drmEnabled) return;
3353
+ typeof document < "u" && this.engine?.name === "hls" && (this.onRefocus = () => {
3354
+ document.visibilityState !== "visible" || this.destroyed || K(this._descriptor?.drm?.widevine?.licenseUrl) && this.refreshLicense();
3355
+ }, document.addEventListener("visibilitychange", this.onRefocus));
3356
+ let e = this.opts.licenseRefreshMs ?? Vn;
3357
+ e && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
3329
3358
  }
3330
3359
  stopRefresh() {
3331
- this.refreshTimer && clearInterval(this.refreshTimer), this.refreshTimer = void 0;
3360
+ this.refreshTimer && clearInterval(this.refreshTimer), this.refreshTimer = void 0, this.onRefocus && typeof document < "u" && (document.removeEventListener("visibilitychange", this.onRefocus), this.onRefocus = void 0);
3332
3361
  }
3333
- async refreshLicense() {
3362
+ refreshLicense() {
3334
3363
  let e = this.engine;
3335
- if (!this.destroyed && e) try {
3336
- let t = await v(this.resolveOptions());
3337
- if (this.destroyed || this.engine !== e) return;
3338
- let n = t.drm?.widevine?.licenseUrl, r = t.drm?.fairplay?.licenseUrl;
3339
- (n || r) && (e.setLicenseUrls({
3340
- widevine: n,
3341
- fairplay: r
3342
- }), this._descriptor = t);
3343
- } catch (e) {
3344
- e instanceof n && !o(e.code) && (this.stopRefresh(), this.destroyed || this.emit("warning", e));
3345
- }
3364
+ if (this.destroyed || !e) return Promise.resolve(void 0);
3365
+ if (this.refreshInFlight?.engine === e) return this.refreshInFlight.promise;
3366
+ let t = (async () => {
3367
+ try {
3368
+ let t = await v(this.resolveOptions());
3369
+ if (this.destroyed || this.engine !== e) return;
3370
+ let n = t.drm?.widevine?.licenseUrl, r = t.drm?.fairplay?.licenseUrl;
3371
+ return (n || r) && (e.setLicenseUrls({
3372
+ widevine: n,
3373
+ fairplay: r
3374
+ }), this._descriptor = t), n;
3375
+ } catch (e) {
3376
+ e instanceof n && !o(e.code) && (this.stopRefresh(), this.destroyed || this.emit("warning", e));
3377
+ return;
3378
+ } finally {
3379
+ this.refreshInFlight?.promise === t && (this.refreshInFlight = void 0);
3380
+ }
3381
+ })();
3382
+ return this.refreshInFlight = {
3383
+ engine: e,
3384
+ promise: t
3385
+ }, t;
3346
3386
  }
3347
3387
  async play() {
3348
3388
  if (this.destroyed) return;
@@ -3369,7 +3409,7 @@ var Un = class {
3369
3409
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
3370
3410
  }
3371
3411
  getStats() {
3372
- return this.engine?.getStats() ?? z;
3412
+ return this.engine?.getStats() ?? qe;
3373
3413
  }
3374
3414
  getQualityLevels() {
3375
3415
  return this.engine?.getQualityLevels() ?? [];
@@ -3479,7 +3519,7 @@ function $(e) {
3479
3519
  }
3480
3520
  //#endregion
3481
3521
  //#region src/embed/host.ts
3482
- function Wn(e) {
3522
+ function Kn(e) {
3483
3523
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
3484
3524
  if (!e.src) throw Error("mountEmbed: opts.src is required");
3485
3525
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -3538,15 +3578,15 @@ function Wn(e) {
3538
3578
  }
3539
3579
  //#endregion
3540
3580
  //#region src/embed/page.ts
3541
- var Gn = /^[A-Za-z0-9:_.-]{1,128}$/;
3542
- function Kn(e) {
3581
+ var qn = /^[A-Za-z0-9:_.-]{1,128}$/;
3582
+ function Jn(e) {
3543
3583
  if (typeof e == "boolean") return e;
3544
3584
  if (typeof e == "object" && e) {
3545
3585
  let t = e.endpoint;
3546
3586
  return typeof t == "string" ? { endpoint: t } : {};
3547
3587
  }
3548
3588
  }
3549
- function qn() {
3589
+ function Yn() {
3550
3590
  let e = window.HAVIK_EMBED_CONFIG;
3551
3591
  if (e) return {
3552
3592
  baseUrl: e.baseUrl ?? "",
@@ -3556,7 +3596,7 @@ function qn() {
3556
3596
  autoplay: e.autoplay ?? !0,
3557
3597
  muted: e.muted ?? !0,
3558
3598
  waitForLive: e.waitForLive ?? !0,
3559
- analytics: Kn(e.analytics)
3599
+ analytics: Jn(e.analytics)
3560
3600
  };
3561
3601
  let t = new URLSearchParams(location.search);
3562
3602
  return {
@@ -3569,9 +3609,9 @@ function qn() {
3569
3609
  waitForLive: !0
3570
3610
  };
3571
3611
  }
3572
- async function Jn() {
3573
- let e = qn();
3574
- Xn();
3612
+ async function Xn() {
3613
+ let e = Yn();
3614
+ Qn();
3575
3615
  let t = document.createElement("video");
3576
3616
  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);
3577
3617
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -3626,12 +3666,12 @@ async function Jn() {
3626
3666
  code: e.code,
3627
3667
  message: e.message
3628
3668
  })), e.on("autoplayblocked", () => {
3629
- i({ type: "oddin:autoplayblocked" }), Yn(t, e);
3669
+ i({ type: "oddin:autoplayblocked" }), Zn(t, e);
3630
3670
  });
3631
3671
  }, s = async (r) => {
3632
3672
  a?.destroy(), a = null;
3633
3673
  try {
3634
- let n = await Hn({
3674
+ let n = await Wn({
3635
3675
  video: t,
3636
3676
  baseUrl: e.baseUrl,
3637
3677
  matchUrn: r,
@@ -3693,7 +3733,7 @@ async function Jn() {
3693
3733
  a?.retry();
3694
3734
  break;
3695
3735
  case "oddin:load":
3696
- typeof e.matchUrn == "string" && Gn.test(e.matchUrn) && s(e.matchUrn);
3736
+ typeof e.matchUrn == "string" && qn.test(e.matchUrn) && s(e.matchUrn);
3697
3737
  break;
3698
3738
  case "oddin:destroy": c();
3699
3739
  }
@@ -3705,18 +3745,18 @@ async function Jn() {
3705
3745
  protocol: 1
3706
3746
  });
3707
3747
  }
3708
- function Yn(e, t) {
3748
+ function Zn(e, t) {
3709
3749
  if (document.getElementById("havik-tap")) return;
3710
3750
  let n = document.createElement("button");
3711
3751
  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", () => {
3712
3752
  n.remove(), t.play();
3713
3753
  }), document.body.appendChild(n);
3714
3754
  }
3715
- function Xn() {
3755
+ function Qn() {
3716
3756
  let e = document.createElement("style");
3717
3757
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
3718
3758
  }
3719
3759
  //#endregion
3720
- export { vn as ODDIN_THEME, n as PlaybackError, Z as applyTheme, Ke as baseUrlForEnv, Jn as bootEmbed, Te as clearDeviceId, Hn as createPlayer, E as deriveEventsBaseUrl, He as detectDrmSupport, C as enterVideoFullscreen, w as exitVideoFullscreen, _e as fetchCatalog, ye as fetchTournament, xe as flattenMatches, k as getDeviceId, We as isLowLatencyManifest, $ as isOddinMessage, De as licenseRequestHeaders, Wn as mountEmbed, de as pollToLive, y as resolveStream, T as subscribeLiveState, S as videoFullscreenActive, Ce as watchStatus };
3760
+ export { bn as ODDIN_THEME, n as PlaybackError, Sn as applyTheme, Ke as baseUrlForEnv, Xn as bootEmbed, Te as clearDeviceId, Wn as createPlayer, E as deriveEventsBaseUrl, He as detectDrmSupport, C as enterVideoFullscreen, w as exitVideoFullscreen, _e as fetchCatalog, ye as fetchTournament, xe as flattenMatches, k as getDeviceId, We as isLowLatencyManifest, $ as isOddinMessage, De as licenseRequestHeaders, Kn as mountEmbed, de as pollToLive, y as resolveStream, T as subscribeLiveState, S as videoFullscreenActive, Ce as watchStatus };
3721
3761
 
3722
3762
  //# sourceMappingURL=index.js.map