@oddin-gg/havik-player 2.15.1 → 2.15.3

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
@@ -174,7 +174,7 @@ function ue(e, t) {
174
174
  t?.addEventListener("abort", a, { once: !0 });
175
175
  });
176
176
  }
177
- async function de(e, t, r) {
177
+ async function v(e, t, r) {
178
178
  let i = ae(t), a = Date.now(), s = i.timeoutMs == null ? Infinity : a + i.timeoutMs, c = 0, l;
179
179
  for (;;) {
180
180
  if (r?.aborted) throw new n("ABORTED", 0, "wait-for-live aborted");
@@ -200,21 +200,21 @@ async function de(e, t, r) {
200
200
  }
201
201
  //#endregion
202
202
  //#region src/core/resolve.ts
203
- async function v(e) {
203
+ async function y(e) {
204
204
  if (!e.matchUrn || typeof e.matchUrn != "string") throw new n("INVALID_URN", 0, "resolveStream: a non-empty matchUrn is required");
205
205
  let t = await p(d(e.baseUrl, `/v1/playback/${encodeURIComponent(e.matchUrn)}`), {
206
206
  credential: e.credential,
207
207
  signal: e.signal,
208
208
  apiHeaders: e.apiHeaders
209
209
  });
210
- if (!t.ok) throw await fe(t);
211
- return he(await t.json(), e.matchUrn);
210
+ if (!t.ok) throw await de(t);
211
+ return me(await t.json(), e.matchUrn);
212
212
  }
213
- function y(e) {
214
- let t = () => v(e);
215
- return e.waitForLive ? de(t, e.waitForLive, e.signal) : t();
213
+ function b(e) {
214
+ let t = () => y(e);
215
+ return e.waitForLive ? v(t, e.waitForLive, e.signal) : t();
216
216
  }
217
- async function fe(e) {
217
+ async function de(e) {
218
218
  let t = f(e.headers.get("retry-after")), i = e.headers.get("x-request-id") ?? e.headers.get("x-amzn-requestid") ?? void 0, a = e.statusText || `HTTP ${e.status}`, o, s;
219
219
  try {
220
220
  let t = await e.json();
@@ -230,7 +230,7 @@ async function fe(e) {
230
230
  liveStartsAtMs: s
231
231
  });
232
232
  }
233
- var pe = /* @__PURE__ */ new Set([
233
+ var fe = /* @__PURE__ */ new Set([
234
234
  "matchUrn",
235
235
  "protocol",
236
236
  "drmEnabled",
@@ -241,7 +241,7 @@ var pe = /* @__PURE__ */ new Set([
241
241
  "expiresAt",
242
242
  "analytics"
243
243
  ]);
244
- function b(e, t) {
244
+ function x(e, t) {
245
245
  let r;
246
246
  try {
247
247
  r = new URL(t);
@@ -251,7 +251,7 @@ function b(e, t) {
251
251
  if (r.protocol !== "https:" && r.protocol !== "http:") throw new n("INTERNAL", 0, `playback ${e} has a disallowed scheme: ${r.protocol}`);
252
252
  return t;
253
253
  }
254
- function me(e) {
254
+ function pe(e) {
255
255
  if (e !== void 0) {
256
256
  if (typeof e == "string" && !e.endsWith("/")) try {
257
257
  let t = new URL(e);
@@ -260,41 +260,41 @@ function me(e) {
260
260
  g(`server analytics.endpoint ${JSON.stringify(e)} ignored: not a bare https origin.`);
261
261
  }
262
262
  }
263
- function he(e, t) {
263
+ function me(e, t) {
264
264
  if (!e.manifestUrl) throw new n("INTERNAL", 0, "playback response is missing manifestUrl");
265
265
  let r = e.drmEnabled ?? !!e.drm, i = {
266
266
  matchUrn: e.matchUrn ?? t,
267
267
  protocol: "hls",
268
268
  drmEnabled: r,
269
- manifestUrl: b("manifestUrl", e.manifestUrl),
269
+ manifestUrl: x("manifestUrl", e.manifestUrl),
270
270
  serverTime: e.serverTime ?? ""
271
271
  };
272
272
  if (e.liveStartsAt && (i.liveStartsAt = e.liveStartsAt), typeof e.analytics?.sid == "string" && e.analytics.sid) {
273
273
  i.analytics = { sid: e.analytics.sid };
274
- let t = me(e.analytics.endpoint);
274
+ let t = pe(e.analytics.endpoint);
275
275
  t && (i.analytics.endpoint = t);
276
276
  }
277
277
  if (r && e.drm) {
278
278
  let t = {};
279
- e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: b("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
280
- licenseUrl: b("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
281
- certificateUrl: b("fairplay certificateUrl", e.drm.fairplay.certificateUrl)
279
+ e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: x("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
280
+ licenseUrl: x("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
281
+ certificateUrl: x("fairplay certificateUrl", e.drm.fairplay.certificateUrl)
282
282
  }), (t.widevine || t.fairplay) && (i.drm = t);
283
283
  }
284
284
  let a = {};
285
- for (let [t, n] of Object.entries(e)) pe.has(t) || (a[t] = n);
285
+ for (let [t, n] of Object.entries(e)) fe.has(t) || (a[t] = n);
286
286
  return Object.keys(a).length > 0 && (i.extensions = a), i;
287
287
  }
288
288
  //#endregion
289
289
  //#region src/core/fullscreen.ts
290
- function ge(e) {
290
+ function he(e) {
291
291
  return e.ownerDocument ?? document;
292
292
  }
293
- function x(e) {
293
+ function ge(e) {
294
294
  return e.fullscreenElement ?? e.webkitFullscreenElement ?? null;
295
295
  }
296
296
  function S(e, t) {
297
- let n = x(ge(e));
297
+ let n = ge(he(e));
298
298
  return n != null && (n === e || t != null && n === t) || e.webkitDisplayingFullscreen === !0;
299
299
  }
300
300
  async function C(e, t) {
@@ -310,7 +310,7 @@ async function w(e, t) {
310
310
  n.webkitExitFullscreen?.();
311
311
  return;
312
312
  }
313
- let r = ge(e), i = x(r);
313
+ let r = he(e), i = ge(r);
314
314
  i != null && (i === e || t != null && i === t) && (typeof r.exitFullscreen == "function" ? await r.exitFullscreen() : r.webkitExitFullscreen?.());
315
315
  }
316
316
  //#endregion
@@ -671,14 +671,14 @@ function I(e) {
671
671
  function L(e) {
672
672
  return (e ?? "").trim().toLowerCase();
673
673
  }
674
- var je = {
674
+ var R = {
675
675
  hevc: "hvc1.1.6.L93.90",
676
676
  dv: "dvh1.05.06",
677
677
  av1: "av01.0.05M.08",
678
678
  vp9: "vp09.00.10.08"
679
- }, Me = /* @__PURE__ */ new Map(), R = /* @__PURE__ */ new Map();
680
- function Ne(e) {
681
- let t = L(e), n = Me.get(t);
679
+ }, z = /* @__PURE__ */ new Map(), B = /* @__PURE__ */ new Map();
680
+ function V(e) {
681
+ let t = L(e), n = z.get(t);
682
682
  if (n) return n;
683
683
  let r = {
684
684
  initDataTypes: ["cenc"],
@@ -696,23 +696,23 @@ function Ne(e) {
696
696
  } catch {
697
697
  return !1;
698
698
  }
699
- })().then((e) => (R.set(t, e), e));
700
- return Me.set(t, i), i;
699
+ })().then((e) => (B.set(t, e), e));
700
+ return z.set(t, i), i;
701
701
  }
702
- function Pe() {
703
- if (j()) for (let e of Object.values(je)) Ne(e);
702
+ function je() {
703
+ if (j()) for (let e of Object.values(R)) V(e);
704
704
  }
705
- function Fe(e) {
705
+ function H(e) {
706
706
  let t = I(e);
707
- if (t) return t === "avc" ? !0 : R.get(L(e)) ?? R.get(L(je[t]));
707
+ if (t) return t === "avc" ? !0 : B.get(L(e)) ?? B.get(L(R[t]));
708
708
  }
709
709
  //#endregion
710
710
  //#region src/core/drm/support.ts
711
- var Ie = 5e3, Le = "video/mp4; codecs=\"avc1.42E01E\"", Re = "audio/mp4; codecs=\"mp4a.40.2\"", ze = {
711
+ var Me = 5e3, U = "video/mp4; codecs=\"avc1.42E01E\"", Ne = "audio/mp4; codecs=\"mp4a.40.2\"", Pe = {
712
712
  widevine: ["com.widevine.alpha"],
713
713
  fairplay: ["com.apple.fps"]
714
714
  };
715
- function Be(e) {
715
+ function Fe(e) {
716
716
  return e === "fairplay" ? [{
717
717
  initDataTypes: [
718
718
  "sinf",
@@ -720,30 +720,30 @@ function Be(e) {
720
720
  "cenc"
721
721
  ],
722
722
  videoCapabilities: [{
723
- contentType: Le,
723
+ contentType: U,
724
724
  robustness: "",
725
725
  encryptionScheme: "cbcs"
726
726
  }],
727
727
  audioCapabilities: [{
728
- contentType: Re,
728
+ contentType: Ne,
729
729
  robustness: "",
730
730
  encryptionScheme: "cbcs"
731
731
  }]
732
732
  }] : N("com.widevine.alpha", [{
733
733
  initDataTypes: ["cenc"],
734
734
  videoCapabilities: [{
735
- contentType: Le,
735
+ contentType: U,
736
736
  robustness: F.video,
737
737
  encryptionScheme: F.scheme
738
738
  }],
739
739
  audioCapabilities: [{
740
- contentType: Re,
740
+ contentType: Ne,
741
741
  robustness: F.audio,
742
742
  encryptionScheme: F.scheme
743
743
  }]
744
744
  }]);
745
745
  }
746
- function Ve(e, t) {
746
+ function Ie(e, t) {
747
747
  return new Promise((n, r) => {
748
748
  let i = setTimeout(() => r(/* @__PURE__ */ Error("probe-timeout")), t);
749
749
  e.then((e) => {
@@ -753,8 +753,8 @@ function Ve(e, t) {
753
753
  });
754
754
  });
755
755
  }
756
- async function He(e = {}) {
757
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? Ie, r = {};
756
+ async function Le(e = {}) {
757
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? Me, r = {};
758
758
  if (typeof window < "u" && window.isSecureContext === !1) return {
759
759
  verdict: "insecure-context",
760
760
  detail: r
@@ -766,7 +766,7 @@ async function He(e = {}) {
766
766
  };
767
767
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
768
768
  for (let e of t) {
769
- let t = ze[e];
769
+ let t = Pe[e];
770
770
  if (!t) {
771
771
  r[e] = "unknown system";
772
772
  continue;
@@ -777,7 +777,7 @@ async function He(e = {}) {
777
777
  break;
778
778
  }
779
779
  try {
780
- return await Ve((await Ve(i(n, Be(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
780
+ return await Ie((await Ie(i(n, Fe(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
781
781
  verdict: "supported",
782
782
  system: e,
783
783
  detail: r
@@ -801,49 +801,49 @@ async function He(e = {}) {
801
801
  }
802
802
  //#endregion
803
803
  //#region src/core/manifest.ts
804
- var Ue = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
805
- function We(e) {
806
- return Ue.test(e);
804
+ var Re = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
805
+ function ze(e) {
806
+ return Re.test(e);
807
807
  }
808
808
  //#endregion
809
809
  //#region src/core/env.ts
810
- var z = {
810
+ var Be = {
811
811
  integration: "https://feed-dev.oddin-video.gg",
812
812
  production: "https://feed.oddin-video.gg"
813
813
  };
814
- function B(e) {
815
- let t = Object.prototype.hasOwnProperty.call(z, e) ? z[e] : void 0;
814
+ function Ve(e) {
815
+ let t = Object.prototype.hasOwnProperty.call(Be, e) ? Be[e] : void 0;
816
816
  if (!t) throw new n("INTERNAL", 0, `unknown env ${JSON.stringify(e)} — expected 'integration' or 'production'`);
817
817
  return t;
818
818
  }
819
819
  //#endregion
820
820
  //#region src/core/stats.ts
821
- var V = { droppedFrames: 0 };
822
- function H(e) {
821
+ var He = { droppedFrames: 0 };
822
+ function Ue(e) {
823
823
  try {
824
824
  return new URL(e).host;
825
825
  } catch {
826
826
  return "";
827
827
  }
828
828
  }
829
- var Ge = /\.(m3u8|ts|m4s|mp4|cmfv|cmfa)(\?|$)/i;
830
- function Ke(e) {
829
+ var We = /\.(m3u8|ts|m4s|mp4|cmfv|cmfa)(\?|$)/i;
830
+ function Ge(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" || Ge.test(i.name)) && i.startTime >= n) {
834
- let e = H(i.name);
833
+ for (let i of r) if (!(i.startTime < e) && (i.initiatorType === "video" || i.initiatorType === "audio" || We.test(i.name)) && i.startTime >= n) {
834
+ let e = Ue(i.name);
835
835
  e && (t = e, n = i.startTime);
836
836
  }
837
837
  return t;
838
838
  }
839
- function U(e) {
839
+ function W(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
- function qe(e, t) {
846
+ function Ke(e, t) {
847
847
  let n = [
848
848
  ["playing", () => t("playing")],
849
849
  ["waiting", () => t("buffering")],
@@ -860,7 +860,7 @@ function qe(e, t) {
860
860
  }
861
861
  //#endregion
862
862
  //#region src/core/engine/native.ts
863
- function Je(e) {
863
+ function qe(e) {
864
864
  try {
865
865
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
866
866
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -868,10 +868,10 @@ function Je(e) {
868
868
  return;
869
869
  }
870
870
  }
871
- function W(e) {
871
+ function G(e) {
872
872
  return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
873
873
  }
874
- function G() {
874
+ function Je() {
875
875
  if (typeof window > "u") return !1;
876
876
  let e = "WebKitMediaKeys" in window || typeof HTMLMediaElement < "u" && "webkitSetMediaKeys" in HTMLMediaElement.prototype, t = typeof navigator < "u" && /Apple/i.test(navigator.vendor ?? "");
877
877
  return e || t;
@@ -934,7 +934,7 @@ var K = class {
934
934
  });
935
935
  this.onEncrypted = (n) => {
936
936
  let i = n;
937
- i.initData && !this.fairplayArmed && (this.fairplayArmed = !0, this.fairplayKeyIV = Je(i.initData), (async () => {
937
+ i.initData && !this.fairplayArmed && (this.fairplayArmed = !0, this.fairplayKeyIV = qe(i.initData), (async () => {
938
938
  try {
939
939
  let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
940
940
  initDataTypes: [i.initDataType],
@@ -1029,11 +1029,11 @@ var K = class {
1029
1029
  e.seekable.length > 0 && (e.currentTime = e.seekable.end(e.seekable.length - 1));
1030
1030
  }
1031
1031
  getStats() {
1032
- let e = this.deps.video, t = U(e), n = {
1032
+ let e = this.deps.video, t = W(e), n = {
1033
1033
  droppedFrames: t.dropped,
1034
1034
  decodedFrames: t.totalFrames
1035
1035
  };
1036
- return this.cdnHost ||= Ke(this.builtAt), this.cdnHost && (n.cdnHost = this.cdnHost), this.deps.descriptor.drmEnabled && (n.keySystem = "fairplay"), e.duration === Infinity ? (n.isLive = !0, e.seekable.length > 0 && (n.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)) : Number.isFinite(e.duration) && e.duration > 0 && (n.isLive = !1), n;
1036
+ return this.cdnHost ||= Ge(this.builtAt), this.cdnHost && (n.cdnHost = this.cdnHost), this.deps.descriptor.drmEnabled && (n.keySystem = "fairplay"), e.duration === Infinity ? (n.isLive = !0, e.seekable.length > 0 && (n.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)) : Number.isFinite(e.duration) && e.duration > 0 && (n.isLive = !1), n;
1037
1037
  }
1038
1038
  stop() {
1039
1039
  try {
@@ -1219,21 +1219,21 @@ function it(e, t) {
1219
1219
  function at() {
1220
1220
  return e.isSupported();
1221
1221
  }
1222
- var ot = 3, st = 5, ct = 3, lt = 3e4, ut = 12e3, dt = 3e3, ft = 6e3, pt = 3, mt = .5, ht = 1.2, gt = .25, _t = 5e3, vt = 4e3, yt = 2, bt = 5, xt = 2, St = 2, Ct = 4, wt = 4, Tt = 2e3, Et = 3e4, Dt = 2, Ot = 9, kt = .5, At = 64;
1223
- function jt(e) {
1222
+ var ot = 3, st = 5, ct = 3, lt = 3e4, ut = 12e3, dt = 3e3, ft = 6e3, pt = 3, mt = .5, ht = 1.1, gt = .25, _t = 5e3, vt = 4e3, yt = 2, bt = 5, xt = 2, St = 2, Ct = 4, wt = 4, Tt = 2e3, Et = 3e4, Dt = .2, Ot = 30, kt = 2, At = 2, jt = 9, Mt = .5, Nt = 64;
1223
+ function Pt(e) {
1224
1224
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
1225
1225
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
1226
1226
  return;
1227
1227
  }
1228
- e.liveSyncDuration ??= Dt;
1229
- let t = Math.max(12, e.liveSyncDuration + Ot);
1230
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Mt(e, e.liveSyncDuration);
1228
+ e.liveSyncDuration ??= At;
1229
+ let t = Math.max(12, e.liveSyncDuration + jt);
1230
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Ft(e, e.liveSyncDuration);
1231
1231
  }
1232
- function Mt(e, t) {
1232
+ function Ft(e, t) {
1233
1233
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
1234
1234
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
1235
1235
  }
1236
- var Nt = class {
1236
+ var It = class {
1237
1237
  name = "hls";
1238
1238
  hls = null;
1239
1239
  deps;
@@ -1296,6 +1296,13 @@ var Nt = class {
1296
1296
  capTimer = null;
1297
1297
  sizeCapLevel = null;
1298
1298
  bitrateCapLevel = -1;
1299
+ fpsCapBitrate = null;
1300
+ fpsCapFailedHeight = null;
1301
+ fpsCapManaged = !0;
1302
+ fpsLastFrames = -1;
1303
+ fpsLastDropped = -1;
1304
+ fpsBadTicks = 0;
1305
+ fpsDownCaps = 0;
1299
1306
  sizeCapManaged = !0;
1300
1307
  dprMedia = null;
1301
1308
  onDprChange = null;
@@ -1304,7 +1311,7 @@ var Nt = class {
1304
1311
  this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl, this.loadStarted = e.deferLoad !== !0;
1305
1312
  }
1306
1313
  async load() {
1307
- let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = W(r) && G(), o = !!e.drm?.widevine?.licenseUrl && !a;
1314
+ let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = G(r) && Je(), o = !!e.drm?.widevine?.licenseUrl && !a;
1308
1315
  if (e.drmEnabled && !o) {
1309
1316
  i({
1310
1317
  type: "error",
@@ -1313,7 +1320,7 @@ var Nt = class {
1313
1320
  });
1314
1321
  return;
1315
1322
  }
1316
- e.drmEnabled && o && !this.deps.hlsConfig?.requestMediaKeySystemAccessFunc && (Ae(r), Pe()), this.startHls(), t !== !1 && typeof document < "u" && (this.onVisibility = () => {
1323
+ e.drmEnabled && o && !this.deps.hlsConfig?.requestMediaKeySystemAccessFunc && (Ae(r), je()), this.startHls(), t !== !1 && typeof document < "u" && (this.onVisibility = () => {
1317
1324
  let e = this.hls;
1318
1325
  if (document.visibilityState !== "visible" || !e) return;
1319
1326
  let t = e.liveSyncPosition;
@@ -1339,7 +1346,7 @@ var Nt = class {
1339
1346
  }, _t));
1340
1347
  }
1341
1348
  resumeLoading() {
1342
- if (this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null), this.resetChaseWindow(), this.loadSuspended && (this.loadSuspended = !1, !this.stopped && this.hls)) {
1349
+ if (this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null), this.resetChaseWindow(), this.resetFrameDropWindow(), this.loadSuspended && (this.loadSuspended = !1, !this.stopped && this.hls)) {
1343
1350
  this.lastAdvanceMs = Date.now(), this.lastFragLoadingMs = 0, this.resetStarvationWindow(), this.resetChaseWindow(), this.clearKick();
1344
1351
  try {
1345
1352
  this.hls.startLoad(-1);
@@ -1368,18 +1375,18 @@ var Nt = class {
1368
1375
  let t = e.liveSyncPosition;
1369
1376
  if (typeof t != "number" || !Number.isFinite(t)) return;
1370
1377
  let { video: n } = this.deps;
1371
- n.currentTime <= t + kt || (n.currentTime = t);
1378
+ n.currentTime <= t + Mt || (n.currentTime = t);
1372
1379
  }
1373
1380
  startHls() {
1374
1381
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
1375
- this.clearKick(), this.resetStarvationWindow(), this.resetChaseWindow(), this.lastFragLoadingMs = 0, this.lastEndSN = -1, this.lastAdvanceMs = 0;
1382
+ this.clearKick(), this.resetStarvationWindow(), this.resetChaseWindow(), this.resetFrameDropWindow(), this.lastFragLoadingMs = 0, this.lastEndSN = -1, this.lastAdvanceMs = 0;
1376
1383
  let u = n.drmEnabled && !!(n.drm?.widevine?.licenseUrl || n.drm?.fairplay?.licenseUrl), d = {
1377
1384
  debug: s === !0,
1378
1385
  enableWorker: !o,
1379
1386
  lowLatencyMode: o,
1380
1387
  backBufferLength: 90,
1381
1388
  maxBufferHole: .5,
1382
- maxLiveSyncPlaybackRate: 1.5,
1389
+ maxLiveSyncPlaybackRate: 1.1,
1383
1390
  abrEwmaDefaultEstimate: 1e6,
1384
1391
  capLevelToPlayerSize: !1,
1385
1392
  ...this.loadStarted ? {} : { autoStartLoad: !1 },
@@ -1415,7 +1422,7 @@ var Nt = class {
1415
1422
  }), t = tt(e);
1416
1423
  t && (this.prefetch = e, d.fLoader = t);
1417
1424
  }
1418
- jt(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
1425
+ Pt(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.fpsCapManaged = !(c && "capLevelOnFPSDrop" in c), this.loadStarted = d.autoStartLoad !== !1;
1419
1426
  let f = new e(d);
1420
1427
  if (this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
1421
1428
  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" });
@@ -1430,10 +1437,10 @@ var Nt = class {
1430
1437
  let n = t?.frag?.stats, r = n?.loaded;
1431
1438
  typeof r == "number" && (this.bytesLoaded += r), !this.reportedFirstBytes && typeof r == "number" && r > 0 && (this.reportedFirstBytes = !0, l({ type: "firstBytes" }));
1432
1439
  let i = n?.loading;
1433
- 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 > At && this.requestTimesMs.shift());
1440
+ 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 > Nt && this.requestTimesMs.shift());
1434
1441
  let a = t?.frag?.url;
1435
1442
  if (a) {
1436
- let e = H(a);
1443
+ let e = Ue(a);
1437
1444
  e && (this.cdnHost = e);
1438
1445
  }
1439
1446
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => {
@@ -1454,7 +1461,7 @@ var Nt = class {
1454
1461
  f.on(e.Events.KEY_LOADED, () => {
1455
1462
  u && !this.reportedDrmReady && (this.reportedDrmReady = !0, l({ type: "drmReady" }));
1456
1463
  }), this.endWatchdog && clearInterval(this.endWatchdog), this.endWatchdog = setInterval(() => {
1457
- this.checkEndStale(), this.checkStarvation(), this.checkFutileChase();
1464
+ this.checkEndStale(), this.checkStarvation(), this.checkFutileChase(), this.checkFrameDrops();
1458
1465
  }, dt), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1459
1466
  this.renditionSwitches += 1;
1460
1467
  let n = this.lastLevelIndex >= 0 ? f.levels[this.lastLevelIndex] : void 0, r = f.levels[t.level];
@@ -1529,9 +1536,9 @@ var Nt = class {
1529
1536
  this.ladderHeights = t.length ? [...new Set(t)].sort((e, t) => e - t) : void 0, this.ladderTopBitrateKbps = e.levels.length ? Math.round(Math.max(...e.levels.map((e) => e.bitrate)) / 1e3) : 0;
1530
1537
  }
1531
1538
  dropLevelsWidevineCannotPlay(e) {
1532
- this.removeLevelsWhere(e, (e) => Fe(e) !== !0);
1539
+ this.removeLevelsWhere(e, (e) => H(e) !== !0);
1533
1540
  let t = new Set(e.levels.map((e) => e.videoCodec).filter((e) => !!e && I(e) !== "avc" && !!I(e)));
1534
- for (let n of t) Ne(n).then((t) => {
1541
+ for (let n of t) V(n).then((t) => {
1535
1542
  t || this.hls !== e || this.removeLevelsWhere(e, (e) => e === n) && (this.measureLadder(e), this.applySizeCap());
1536
1543
  });
1537
1544
  }
@@ -1542,7 +1549,7 @@ var Nt = class {
1542
1549
  a && I(a) && t(a) && (r.push(n), i.add(a));
1543
1550
  }), !r.length) return !1;
1544
1551
  let a = [...i].join(", ");
1545
- if (!n.some((e, t) => r.includes(t) ? !1 : !I(e.videoCodec) || Fe(e.videoCodec) === !0)) return console.warn(`havik-player: every rendition is ${a}, which Widevine may not play on this browser; keeping them`), !1;
1552
+ if (!n.some((e, t) => r.includes(t) ? !1 : !I(e.videoCodec) || H(e.videoCodec) === !0)) return console.warn(`havik-player: every rendition is ${a}, which Widevine may not play on this browser; keeping them`), !1;
1546
1553
  for (let t = r.length - 1; t >= 0; --t) e.removeLevel(r[t]);
1547
1554
  return console.info(`havik-player: skipped ${r.length} rendition(s) Widevine cannot play on this browser (${a})`), !0;
1548
1555
  }
@@ -1555,8 +1562,12 @@ var Nt = class {
1555
1562
  pushCap() {
1556
1563
  let e = this.hls;
1557
1564
  if (!e) return;
1558
- let t = this.sizeCapLevel ?? -1, n = this.bitrateCapLevel;
1559
- e.autoLevelCapping = t < 0 ? n : n < 0 ? t : Math.min(t, n);
1565
+ let t = [
1566
+ this.sizeCapLevel ?? -1,
1567
+ this.bitrateCapLevel,
1568
+ this.fpsCapLevel(e)
1569
+ ].filter((e) => e >= 0);
1570
+ e.autoLevelCapping = t.length ? Math.min(...t) : -1;
1560
1571
  }
1561
1572
  getAudioTracks() {
1562
1573
  return this.hls ? this.hls.audioTracks.map((e) => ({
@@ -1588,7 +1599,7 @@ var Nt = class {
1588
1599
  e.widevine && (this.licenseUrl = e.widevine);
1589
1600
  }
1590
1601
  getStats() {
1591
- let e = this.hls, t = U(this.deps.video), n = {
1602
+ let e = this.hls, t = W(this.deps.video), n = {
1592
1603
  droppedFrames: t.dropped,
1593
1604
  decodedFrames: t.totalFrames,
1594
1605
  lowLatency: this.detectedLowLatency,
@@ -1596,6 +1607,7 @@ var Nt = class {
1596
1607
  playlistBytesLoaded: this.playlistBytesLoaded,
1597
1608
  renditionSwitches: this.renditionSwitches,
1598
1609
  liveRetargets: this.liveRetargets,
1610
+ fpsDownCaps: this.fpsDownCaps,
1599
1611
  licenseTimeMs: this.licenseTimeMs,
1600
1612
  upshifts: this.upshifts,
1601
1613
  downshifts: this.downshifts,
@@ -1650,7 +1662,7 @@ var Nt = class {
1650
1662
  this.resetStarvationWindow();
1651
1663
  return;
1652
1664
  }
1653
- let n = Date.now(), r = this.lastAdvanceMs !== 0 && n - this.lastAdvanceMs < ft, i = this.lastFragLoadingMs !== 0 && n - this.lastFragLoadingMs > vt, a = t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused, { totalFrames: o, dropped: s } = U(t), c = o - s, l = c !== this.starvedFrames;
1665
+ let n = Date.now(), r = this.lastAdvanceMs !== 0 && n - this.lastAdvanceMs < ft, i = this.lastFragLoadingMs !== 0 && n - this.lastFragLoadingMs > vt, a = t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused, { totalFrames: o, dropped: s } = W(t), c = o - s, l = c !== this.starvedFrames;
1654
1666
  if (this.starvedFrames = c, !a || l) {
1655
1667
  (t.readyState >= HTMLMediaElement.HAVE_FUTURE_DATA || l) && this.clearKick(), this.resetStarvationWindow();
1656
1668
  return;
@@ -1673,6 +1685,39 @@ var Nt = class {
1673
1685
  }
1674
1686
  }
1675
1687
  }
1688
+ fpsCapLevel(e) {
1689
+ if (this.fpsCapBitrate == null) return -1;
1690
+ let t = this.fpsCapFailedHeight, n = -1;
1691
+ return e.levels.forEach((e, r) => {
1692
+ let i = t == null || !(e.height > 0) || e.height < t;
1693
+ e.bitrate <= this.fpsCapBitrate && i && (n = r);
1694
+ }), n < 0 ? 0 : n;
1695
+ }
1696
+ resetFrameDropWindow() {
1697
+ this.fpsLastFrames = -1, this.fpsLastDropped = -1, this.fpsBadTicks = 0;
1698
+ }
1699
+ checkFrameDrops() {
1700
+ let e = this.hls, { video: t } = this.deps;
1701
+ if (!e || !this.fpsCapManaged || this.loadSuspended || t.paused || !e.autoLevelEnabled || typeof document < "u" && document.hidden) {
1702
+ this.resetFrameDropWindow();
1703
+ return;
1704
+ }
1705
+ let { totalFrames: n, dropped: r } = W(t), i = this.fpsLastFrames < 0, a = n - this.fpsLastFrames, o = r - this.fpsLastDropped;
1706
+ if (this.fpsLastFrames = n, this.fpsLastDropped = r, i || a < Ot || o < 0) {
1707
+ this.fpsBadTicks = 0;
1708
+ return;
1709
+ }
1710
+ if (o <= Dt * a) {
1711
+ this.fpsBadTicks = 0;
1712
+ return;
1713
+ }
1714
+ if (this.fpsBadTicks += 1, this.fpsBadTicks < kt) return;
1715
+ this.fpsBadTicks = 0;
1716
+ let s = e.levels, c = s[e.currentLevel];
1717
+ if (!c) return;
1718
+ let l = s.filter((e) => e.height > 0 && e.height < c.height), u = (l.length ? l : s.filter((e) => e.bitrate < c.bitrate)).map((e) => e.bitrate).sort((e, t) => t - e)[0];
1719
+ u != null && (this.fpsCapBitrate != null && u >= this.fpsCapBitrate || (this.fpsCapBitrate = u, l.length && (this.fpsCapFailedHeight = c.height), this.fpsDownCaps += 1, console.warn(`havik-player: dropping ${Math.round(100 * o / a)}% of frames at ${c.height}p — capping quality below it`), this.pushCap()));
1720
+ }
1676
1721
  resetChaseWindow() {
1677
1722
  this.chaseLatency = -1, this.chaseTicks = 0;
1678
1723
  }
@@ -1683,7 +1728,7 @@ var Nt = class {
1683
1728
  return;
1684
1729
  }
1685
1730
  let n = e.latency, r = t.playbackRate, i = e.config.maxLiveSyncPlaybackRate;
1686
- if (!(!t.paused && r >= ht && typeof i == "number" && i > 1 && r <= i && typeof n == "number" && Number.isFinite(n))) {
1731
+ if (!(!t.paused && typeof i == "number" && i > 1 && r >= Math.min(ht, i) && r <= i && typeof n == "number" && Number.isFinite(n))) {
1687
1732
  this.resetChaseWindow();
1688
1733
  return;
1689
1734
  }
@@ -1811,19 +1856,19 @@ var Nt = class {
1811
1856
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1812
1857
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1813
1858
  }
1814
- }, Pt = 15e3, Ft = 6e5, It = 3e5, Lt = 100, q = 1, Rt = 1e3, zt = 3e4, Bt = 1e3, Vt = {
1859
+ }, Lt = 15e3, Rt = 6e5, zt = 3e5, Bt = 100, q = 1, Vt = 1e3, Ht = 3e4, Ut = 1e3, Wt = {
1815
1860
  inline: "PRESENTATION_INLINE",
1816
1861
  fullscreen: "PRESENTATION_FULLSCREEN",
1817
1862
  pip: "PRESENTATION_PIP"
1818
- }, Ht = {
1863
+ }, Gt = {
1819
1864
  widevine: "KEY_SYSTEM_WIDEVINE",
1820
1865
  fairplay: "KEY_SYSTEM_FAIRPLAY",
1821
1866
  none: "KEY_SYSTEM_NONE"
1822
1867
  };
1823
- function Ut(e, t) {
1868
+ function Kt(e, t) {
1824
1869
  return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
1825
1870
  }
1826
- var Wt = /* @__PURE__ */ new Set([
1871
+ var qt = /* @__PURE__ */ new Set([
1827
1872
  "INVALID_URN",
1828
1873
  "NOT_FOUND",
1829
1874
  "GONE",
@@ -1843,7 +1888,7 @@ var Wt = /* @__PURE__ */ new Set([
1843
1888
  ended: "PLAYER_STATE_ENDED",
1844
1889
  error: "PLAYER_STATE_ERROR"
1845
1890
  };
1846
- function Gt(e) {
1891
+ function Jt(e) {
1847
1892
  try {
1848
1893
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1849
1894
  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);
@@ -1851,8 +1896,8 @@ function Gt(e) {
1851
1896
  return;
1852
1897
  }
1853
1898
  }
1854
- var Kt = (e, t) => e.replace(/\/$/, "").toLowerCase() === t.replace(/\/$/, "").toLowerCase(), qt = /* @__PURE__ */ new Set(), Y = /* @__PURE__ */ new Set(), X = /* @__PURE__ */ new Set(), Jt = (e) => `this page's Content-Security-Policy blocks QoE beacons. Add ${e} to connect-src; until then none are sent.`;
1855
- function Yt(e) {
1899
+ var Yt = (e, t) => e.replace(/\/$/, "").toLowerCase() === t.replace(/\/$/, "").toLowerCase(), Xt = /* @__PURE__ */ new Set(), Y = /* @__PURE__ */ new Set(), X = /* @__PURE__ */ new Set(), Zt = (e) => `this page's Content-Security-Policy blocks QoE beacons. Add ${e} to connect-src; until then none are sent.`;
1900
+ function Qt(e) {
1856
1901
  if (e.disposition !== "enforce" || e.effectiveDirective !== "connect-src") return;
1857
1902
  let t;
1858
1903
  try {
@@ -1861,19 +1906,19 @@ function Yt(e) {
1861
1906
  } catch {
1862
1907
  return;
1863
1908
  }
1864
- if (qt.has(t) && !Y.has(t)) {
1865
- Y.add(t), g(Jt(new URL(t).origin));
1909
+ if (Xt.has(t) && !Y.has(t)) {
1910
+ Y.add(t), g(Zt(new URL(t).origin));
1866
1911
  for (let e of X) e.noteCspBlocked();
1867
1912
  }
1868
1913
  }
1869
- function Xt(e, t) {
1914
+ function $t(e, t) {
1870
1915
  return g(t, e), { disabledReason: t };
1871
1916
  }
1872
- function Zt(e, t, n) {
1873
- if (n) return t && !Kt(t, n) && g(`analytics.endpoint "${t}" ignored; the server named "${n}".`), { endpoint: n };
1917
+ function en(e, t, n) {
1918
+ if (n) return t && !Yt(t, n) && g(`analytics.endpoint "${t}" ignored; the server named "${n}".`), { endpoint: n };
1874
1919
  if (t) return { endpoint: t };
1875
- if (t !== void 0) return Xt("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).");
1876
- let r = Gt(e);
1920
+ if (t !== void 0) return $t("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).");
1921
+ let r = Jt(e);
1877
1922
  if (r) return { endpoint: r };
1878
1923
  let i;
1879
1924
  try {
@@ -1881,9 +1926,9 @@ function Zt(e, t, n) {
1881
1926
  } catch {
1882
1927
  i = e;
1883
1928
  }
1884
- return Xt(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).`);
1929
+ return $t(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).`);
1885
1930
  }
1886
- var Qt = class {
1931
+ var tn = class {
1887
1932
  opts;
1888
1933
  url;
1889
1934
  cspKey;
@@ -1965,8 +2010,8 @@ var Qt = class {
1965
2010
  constructor(e) {
1966
2011
  this.opts = {
1967
2012
  ...e,
1968
- heartbeatMs: e.heartbeatMs ?? Pt,
1969
- idleMs: e.idleMs ?? Ft
2013
+ heartbeatMs: e.heartbeatMs ?? Lt,
2014
+ idleMs: e.idleMs ?? Rt
1970
2015
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`;
1971
2016
  try {
1972
2017
  let e = new URL(this.url);
@@ -1983,11 +2028,11 @@ var Qt = class {
1983
2028
  sdkVersion: this.opts.sdkVersion,
1984
2029
  ...this.opts.environment
1985
2030
  };
1986
- this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), Rt), 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 && qt.add(this.cspKey), document.addEventListener("securitypolicyviolation", Yt), X.add(this), this.noteCspBlocked(), this.onVisibility());
2031
+ this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), Vt), 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 && Xt.add(this.cspKey), document.addEventListener("securitypolicyviolation", Qt), X.add(this), this.noteCspBlocked(), this.onVisibility());
1987
2032
  }
1988
2033
  checkSuspension(e) {
1989
2034
  if (this.endedFlag || !this.opts.idleMs) return !1;
1990
- let t = Math.max(this.opts.idleMs, It);
2035
+ let t = Math.max(this.opts.idleMs, zt);
1991
2036
  return this.lastTickMs > 0 && e - this.lastTickMs >= t ? (this.endIdle(this.lastTickMs), !0) : (this.lastTickMs = e, !1);
1992
2037
  }
1993
2038
  syncIdle(e) {
@@ -2010,12 +2055,12 @@ var Qt = class {
2010
2055
  let a = {
2011
2056
  fromState: J[e] ?? "PLAYER_STATE_UNSPECIFIED",
2012
2057
  toState: J[t] ?? "PLAYER_STATE_UNSPECIFIED",
2013
- presentation: Vt[this.presentation]
2058
+ presentation: Wt[this.presentation]
2014
2059
  }, o = this.causeOf(e, t, i);
2015
2060
  o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
2016
2061
  }
2017
2062
  seekInduced(e) {
2018
- return this.lastSeekMs >= 0 && e - this.lastSeekMs < Bt;
2063
+ return this.lastSeekMs >= 0 && e - this.lastSeekMs < Ut;
2019
2064
  }
2020
2065
  causeOf(e, t, n) {
2021
2066
  if (t === "error") return "TRANSITION_CAUSE_ERROR";
@@ -2089,7 +2134,7 @@ var Qt = class {
2089
2134
  let r = n ?? "";
2090
2135
  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";
2091
2136
  if (e === "DRM_CLIENT") return "ERROR_STAGE_DRM";
2092
- if (Wt.has(e)) return "ERROR_STAGE_RESOLVE";
2137
+ if (qt.has(e)) return "ERROR_STAGE_RESOLVE";
2093
2138
  let i = t ?? "";
2094
2139
  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";
2095
2140
  }
@@ -2136,14 +2181,14 @@ var Qt = class {
2136
2181
  if (this.lastRequestCount = g, _ > 0) {
2137
2182
  o.requestCountDelta = _;
2138
2183
  let e = t.requestTimesMs ?? [], n = e.slice(Math.max(0, e.length - _)).sort((e, t) => e - t);
2139
- n.length > 0 && (o.requestTimeP50Ms = Ut(n, 50), o.requestTimeP95Ms = Ut(n, 95));
2184
+ n.length > 0 && (o.requestTimeP50Ms = Kt(n, 50), o.requestTimeP95Ms = Kt(n, 95));
2140
2185
  }
2141
2186
  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();
2142
2187
  }
2143
2188
  maybeEmitMediaInfo(e) {
2144
2189
  if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
2145
2190
  let t = {};
2146
- e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = Ht[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);
2191
+ e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = Gt[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);
2147
2192
  let n = JSON.stringify(t);
2148
2193
  n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
2149
2194
  }
@@ -2163,7 +2208,7 @@ var Qt = class {
2163
2208
  let c = r?.positionMs ?? 0;
2164
2209
  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));
2165
2210
  let l = e !== "END_REASON_ENDED";
2166
- 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);
2211
+ l && this.lastStallMs > 0 && n - this.lastStallMs <= Ht && (i.abandonAfterStallMs = Math.max(q, Math.round(n - this.lastStallMs))), l && this.lastErrorMs > 0 && n - this.lastErrorMs <= Ht && (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);
2167
2212
  let u = r?.decodedFrames ?? this.lastDecoded;
2168
2213
  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");
2169
2214
  }
@@ -2183,13 +2228,13 @@ var Qt = class {
2183
2228
  };
2184
2229
  }
2185
2230
  noteCspBlocked() {
2186
- this.cspBlockedNoted || this.endedFlag || !this.cspBlocked() || (this.cspBlockedNoted = !0, this.opts.onBlocked?.(Jt(new URL(this.url).origin)));
2231
+ this.cspBlockedNoted || this.endedFlag || !this.cspBlocked() || (this.cspBlockedNoted = !0, this.opts.onBlocked?.(Zt(new URL(this.url).origin)));
2187
2232
  }
2188
2233
  cspBlocked() {
2189
2234
  return !!this.cspKey && Y.has(this.cspKey);
2190
2235
  }
2191
2236
  enqueue(e) {
2192
- if (!this.cspBlocked()) for (this.pending.push(this.wrap(e)); this.pending.length > Lt;) {
2237
+ if (!this.cspBlocked()) for (this.pending.push(this.wrap(e)); this.pending.length > Bt;) {
2193
2238
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
2194
2239
  this.pending.splice(e >= 0 ? e : 0, 1);
2195
2240
  }
@@ -2216,7 +2261,7 @@ var Qt = class {
2216
2261
  };
2217
2262
  //#endregion
2218
2263
  //#region src/core/environment.ts
2219
- function $t() {
2264
+ function nn() {
2220
2265
  if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
2221
2266
  let e;
2222
2267
  try {
@@ -2241,14 +2286,14 @@ function $t() {
2241
2286
  integration: "INTEGRATION_KIND_IFRAME"
2242
2287
  };
2243
2288
  }
2244
- function en() {
2289
+ function rn() {
2245
2290
  if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
2246
2291
  let e = navigator.userAgentData, t = e?.platform ?? "";
2247
2292
  if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
2248
2293
  let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
2249
2294
  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";
2250
2295
  }
2251
- function tn() {
2296
+ function an() {
2252
2297
  let e = typeof navigator < "u" ? navigator.connection : void 0;
2253
2298
  if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
2254
2299
  let t = {
@@ -2267,17 +2312,17 @@ function tn() {
2267
2312
  }
2268
2313
  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;
2269
2314
  }
2270
- function nn(e) {
2271
- let t = $t(), n = {
2272
- deviceClass: en(),
2315
+ function on(e) {
2316
+ let t = nn(), n = {
2317
+ deviceClass: rn(),
2273
2318
  integration: t.integration,
2274
- ...tn()
2319
+ ...an()
2275
2320
  };
2276
2321
  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);
2277
2322
  let r = e.getBoundingClientRect?.();
2278
2323
  return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
2279
2324
  }
2280
- async function rn() {
2325
+ async function sn() {
2281
2326
  let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
2282
2327
  if (!e?.getHighEntropyValues) return {};
2283
2328
  try {
@@ -2289,7 +2334,7 @@ async function rn() {
2289
2334
  }
2290
2335
  //#endregion
2291
2336
  //#region src/core/version.ts
2292
- var an = "2.15.1", on = {
2337
+ var cn = "2.15.3", ln = {
2293
2338
  accent: "#E1B600",
2294
2339
  accentText: "#1B1C23",
2295
2340
  background: "#0e0f13",
@@ -2300,7 +2345,7 @@ var an = "2.15.1", on = {
2300
2345
  border: "rgba(255, 255, 255, 0.1)",
2301
2346
  radius: "8px",
2302
2347
  fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
2303
- }, sn = {
2348
+ }, un = {
2304
2349
  accent: "--havik-accent",
2305
2350
  accentText: "--havik-accent-text",
2306
2351
  background: "--havik-bg",
@@ -2312,26 +2357,26 @@ var an = "2.15.1", on = {
2312
2357
  radius: "--havik-radius",
2313
2358
  fontFamily: "--havik-font"
2314
2359
  };
2315
- function cn(e, t) {
2360
+ function dn(e, t) {
2316
2361
  let n = {
2317
- ...on,
2362
+ ...ln,
2318
2363
  ...t
2319
2364
  };
2320
- for (let t of Object.keys(sn)) e.style.setProperty(sn[t], n[t]);
2365
+ for (let t of Object.keys(un)) e.style.setProperty(un[t], n[t]);
2321
2366
  n.logoUrl && e.style.setProperty("--havik-logo", `url("${n.logoUrl}")`);
2322
2367
  }
2323
2368
  //#endregion
2324
2369
  //#region src/ui/styles.ts
2325
- var ln = "havik-player-styles", un = "\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", dn = /* @__PURE__ */ new WeakMap();
2326
- function fn(e = document, t) {
2327
- let n = e.querySelector(`style#${ln}`);
2328
- if (n && (!t || dn.get(n) === t)) return;
2370
+ var fn = "havik-player-styles", pn = "\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", mn = /* @__PURE__ */ new WeakMap();
2371
+ function hn(e = document, t) {
2372
+ let n = e.querySelector(`style#${fn}`);
2373
+ if (n && (!t || mn.get(n) === t)) return;
2329
2374
  let r = e.createElement("style");
2330
- r.id = ln, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = un, dn.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2375
+ r.id = fn, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = pn, mn.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
2331
2376
  }
2332
2377
  //#endregion
2333
2378
  //#region src/ui/icons.ts
2334
- var pn = {
2379
+ var gn = {
2335
2380
  play: "<path d=\"M8 5v14l11-7z\"/>",
2336
2381
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
2337
2382
  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\"/>",
@@ -2345,20 +2390,20 @@ var pn = {
2345
2390
  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\"/>"
2346
2391
  };
2347
2392
  function Z(e) {
2348
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${pn[e]}</svg>`;
2393
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${gn[e]}</svg>`;
2349
2394
  }
2350
2395
  //#endregion
2351
2396
  //#region src/ui/controls.ts
2352
- var mn = 3e3, hn = 250, gn = /* @__PURE__ */ new Set([
2397
+ var _n = 3e3, vn = 250, yn = /* @__PURE__ */ new Set([
2353
2398
  "GONE",
2354
2399
  "NOT_FOUND",
2355
2400
  "INVALID_URN",
2356
2401
  "UNAUTHORIZED"
2357
2402
  ]);
2358
- function _n(e) {
2359
- return new vn(e);
2403
+ function bn(e) {
2404
+ return new xn(e);
2360
2405
  }
2361
- var vn = class {
2406
+ var xn = class {
2362
2407
  root;
2363
2408
  video;
2364
2409
  player;
@@ -2404,7 +2449,7 @@ var vn = class {
2404
2449
  scrubbing = !1;
2405
2450
  offFns = [];
2406
2451
  constructor(e) {
2407
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? mn, 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, fn(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");
2452
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? _n, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, hn(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");
2408
2453
  let t = (e, t, n) => {
2409
2454
  let r = this.doc.createElement(e);
2410
2455
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -2490,7 +2535,7 @@ var vn = class {
2490
2535
  let t = this.surfaceWasHidden;
2491
2536
  this.tapTimer = setTimeout(() => {
2492
2537
  this.tapTimer = void 0, this.toggleBar(t);
2493
- }, hn);
2538
+ }, vn);
2494
2539
  }
2495
2540
  toggleBar(e) {
2496
2541
  e ? this.poke() : this.hideIfIdle();
@@ -2525,7 +2570,7 @@ var vn = class {
2525
2570
  e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Z("play"), e.addEventListener("click", () => void this.player.play()), n.append(e);
2526
2571
  } else {
2527
2572
  let r = this.doc.createElement("div");
2528
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !gn.has(this.lastError.code)) {
2573
+ if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !yn.has(this.lastError.code)) {
2529
2574
  let e = this.doc.createElement("button");
2530
2575
  e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Z("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
2531
2576
  }
@@ -2566,7 +2611,7 @@ var vn = class {
2566
2611
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
2567
2612
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
2568
2613
  }
2569
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${yn(this.video.currentTime)} / ${yn(this.video.duration)}`);
2614
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Sn(this.video.currentTime)} / ${Sn(this.video.duration)}`);
2570
2615
  }
2571
2616
  syncLive() {
2572
2617
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -2616,17 +2661,17 @@ var vn = class {
2616
2661
  renderQualityMenu() {
2617
2662
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
2618
2663
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
2619
- for (let n of e) this.menu.append(this.menuItem(bn(n), t === n.index, () => this.player.setQuality(n.index)));
2664
+ for (let n of e) this.menu.append(this.menuItem(Cn(n), t === n.index, () => this.player.setQuality(n.index)));
2620
2665
  }
2621
2666
  renderAudioMenu() {
2622
2667
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
2623
2668
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
2624
- for (let t of e) this.menu.append(this.menuItem(xn(t), t.default, () => this.player.setAudioTrack(t.id)));
2669
+ for (let t of e) this.menu.append(this.menuItem(wn(t), t.default, () => this.player.setAudioTrack(t.id)));
2625
2670
  }
2626
2671
  renderCcMenu() {
2627
2672
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
2628
2673
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
2629
- for (let t of e) this.menu.append(this.menuItem(Sn(t), !1, () => this.player.setTextTrack(t.id)));
2674
+ for (let t of e) this.menu.append(this.menuItem(Tn(t), !1, () => this.player.setTextTrack(t.id)));
2630
2675
  }
2631
2676
  onTracksChanged() {
2632
2677
  this.openMenu && this.renderMenu(this.openMenu);
@@ -2727,41 +2772,41 @@ var vn = class {
2727
2772
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
2728
2773
  }
2729
2774
  };
2730
- function yn(e) {
2775
+ function Sn(e) {
2731
2776
  (!Number.isFinite(e) || e < 0) && (e = 0);
2732
2777
  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");
2733
2778
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
2734
2779
  }
2735
- function bn(e) {
2780
+ function Cn(e) {
2736
2781
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
2737
2782
  }
2738
- function xn(e) {
2783
+ function wn(e) {
2739
2784
  return e.name || e.lang || `Track ${e.id}`;
2740
2785
  }
2741
- function Sn(e) {
2786
+ function Tn(e) {
2742
2787
  return e.name || e.lang || `Track ${e.id}`;
2743
2788
  }
2744
2789
  //#endregion
2745
2790
  //#region src/managed/index.ts
2746
- var Cn = 6e3, Q = 45e3, wn = 2e3, Tn = 3, En = 2e3, Dn = 48e4;
2747
- function On(e) {
2791
+ var En = 6e3, Dn = 45e3, Q = 2e3, On = 3, kn = 2e3, An = 48e4;
2792
+ function jn(e) {
2748
2793
  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)})`);
2749
- if (e.env !== void 0) return B(e.env);
2794
+ if (e.env !== void 0) return Ve(e.env);
2750
2795
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: env or baseUrl is required (env: 'integration' | 'production')");
2751
2796
  return e.baseUrl;
2752
2797
  }
2753
- async function kn(e) {
2798
+ async function Mn(e) {
2754
2799
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
2755
- let t = On(e);
2800
+ let t = jn(e);
2756
2801
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
2757
2802
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
2758
- let r = new An({
2803
+ let r = new Nn({
2759
2804
  ...e,
2760
2805
  baseUrl: t
2761
2806
  });
2762
2807
  return await r.start(), r;
2763
2808
  }
2764
- var An = class {
2809
+ var Nn = class {
2765
2810
  opts;
2766
2811
  abort = new AbortController();
2767
2812
  listeners = {};
@@ -2805,7 +2850,7 @@ var An = class {
2805
2850
  this.opts.muted && (this.opts.video.muted = !0), this.opts.poster && (this.opts.video.poster = this.opts.poster), this.mountControls(), this.attachPipListeners(), this.setState("loading");
2806
2851
  let e = ++this.loadGen, t = this.resolveOptions();
2807
2852
  try {
2808
- let n = await v(t);
2853
+ let n = await y(t);
2809
2854
  if (await this.onResolved(n, e), e === this.loadGen && this._state === "error" && this._lastError) {
2810
2855
  let e = this._lastError;
2811
2856
  throw this.destroy(), e;
@@ -2832,7 +2877,7 @@ var An = class {
2832
2877
  r ? this.armWithSse(e, r, t, n) : this.beginArmingByPolling(e);
2833
2878
  }
2834
2879
  beginArmingByPolling(e) {
2835
- y({
2880
+ b({
2836
2881
  ...this.resolveOptions(),
2837
2882
  signal: this.abort.signal,
2838
2883
  waitForLive: this.buildWaitConfig(e)
@@ -2849,7 +2894,7 @@ var An = class {
2849
2894
  setTimeout(() => {
2850
2895
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
2851
2896
  phase: i ? "unavailable" : "tooEarly",
2852
- retryInMs: Q,
2897
+ retryInMs: Dn,
2853
2898
  attempt: 1,
2854
2899
  elapsedMs: 0,
2855
2900
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -2866,7 +2911,7 @@ var An = class {
2866
2911
  }
2867
2912
  }
2868
2913
  }, h = () => {
2869
- a || s || p() || (s = !0, y({
2914
+ a || s || p() || (s = !0, b({
2870
2915
  ...this.resolveOptions(),
2871
2916
  signal: c.signal,
2872
2917
  waitForLive: this.buildWaitConfig(e)
@@ -2874,7 +2919,7 @@ var An = class {
2874
2919
  a || p() || c.signal.aborted || (a = !0, this.closeArming(), this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e))));
2875
2920
  }));
2876
2921
  }, g = () => {
2877
- a || p() || v(this.resolveOptions()).then((e) => m(e)).catch((e) => {
2922
+ a || p() || y(this.resolveOptions()).then((e) => m(e)).catch((e) => {
2878
2923
  if (!(a || p())) {
2879
2924
  if (e instanceof n && o(e.code)) {
2880
2925
  h();
@@ -2888,13 +2933,13 @@ var An = class {
2888
2933
  !a && !p() && h();
2889
2934
  }, e);
2890
2935
  };
2891
- _(Cn), this.armingSub = T({
2936
+ _(En), this.armingSub = T({
2892
2937
  eventsBaseUrl: t,
2893
2938
  matchUrn: this.opts.matchUrn,
2894
2939
  credential: this.opts.credential,
2895
- onAlive: () => _(Q),
2940
+ onAlive: () => _(Dn),
2896
2941
  onState: (e) => {
2897
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * wn);
2942
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Q);
2898
2943
  },
2899
2944
  onError: () => h()
2900
2945
  });
@@ -2917,7 +2962,7 @@ var An = class {
2917
2962
  let n = await s(this.opts.credential);
2918
2963
  if (this.destroyed || t !== this.loadGen) return;
2919
2964
  this.setState("loading");
2920
- let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = qe(this.opts.video, (e) => {
2965
+ let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = Ke(this.opts.video, (e) => {
2921
2966
  this._state !== "ended" && this._state !== "error" && this.setState(e);
2922
2967
  });
2923
2968
  if (await i.load(), this.destroyed || t !== this.loadGen) {
@@ -2939,26 +2984,26 @@ var An = class {
2939
2984
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
2940
2985
  let i = e.analytics?.sid;
2941
2986
  if (!i) return;
2942
- let a = typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0, { endpoint: o, disabledReason: s } = Zt(this.opts.baseUrl, a, e.analytics?.endpoint);
2987
+ let a = typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0, { endpoint: o, disabledReason: s } = en(this.opts.baseUrl, a, e.analytics?.endpoint);
2943
2988
  if (!o) {
2944
2989
  s && setTimeout(() => {
2945
2990
  this.destroyed || r !== this.loadGen || this.emit("warning", new n("INTERNAL", 0, s));
2946
2991
  }, 0);
2947
2992
  return;
2948
2993
  }
2949
- let c = this.opts.video, l = new Qt({
2994
+ let c = this.opts.video, l = new tn({
2950
2995
  endpoint: o,
2951
2996
  sid: i,
2952
2997
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
2953
- sdkVersion: an,
2954
- environment: nn(c),
2998
+ sdkVersion: cn,
2999
+ environment: on(c),
2955
3000
  idleMs: typeof this.opts.analytics == "object" ? this.opts.analytics.idleMs : void 0,
2956
3001
  onIdle: () => this.onAnalyticsIdle(t),
2957
3002
  onBlocked: (e) => setTimeout(() => {
2958
3003
  this.destroyed || r !== this.loadGen || this.emit("warning", new n("INTERNAL", 0, e));
2959
3004
  }, 0),
2960
3005
  sample: () => {
2961
- let e = this.engine?.getStats() ?? V, t = c.buffered.length ? Math.max(0, c.buffered.end(c.buffered.length - 1) - c.currentTime) : 0;
3006
+ let e = this.engine?.getStats() ?? He, t = c.buffered.length ? Math.max(0, c.buffered.end(c.buffered.length - 1) - c.currentTime) : 0;
2962
3007
  return {
2963
3008
  ...e,
2964
3009
  bufferedMs: Math.round(t * 1e3),
@@ -2972,7 +3017,7 @@ var An = class {
2972
3017
  let u = () => l.noteSeek(), d = () => l.noteVolume(c.muted, c.volume);
2973
3018
  c.addEventListener("seeking", u), c.addEventListener("volumechange", d), this.detachMediaListeners = () => {
2974
3019
  c.removeEventListener("seeking", u), c.removeEventListener("volumechange", d);
2975
- }, l.start(), l.seedVolume(c.muted, c.volume), l.notePresentation(this.currentPresentation()), rn().then((e) => l.noteEnvironment(e)), this.opts.autoplay === !1 && l.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
3020
+ }, l.start(), l.seedVolume(c.muted, c.volume), l.notePresentation(this.currentPresentation()), sn().then((e) => l.noteEnvironment(e)), this.opts.autoplay === !1 && l.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
2976
3021
  }
2977
3022
  currentPresentation() {
2978
3023
  let e = this.opts.video;
@@ -2998,10 +3043,10 @@ var An = class {
2998
3043
  emit: (e) => this.onEngineEvent(e)
2999
3044
  }, o = this.opts.engine ?? "auto";
3000
3045
  if (o === "native") return new K(a);
3001
- if (o === "hls") return new Nt(a);
3002
- if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && W(this.opts.video) && G()) return new K(a);
3003
- if (at()) return new Nt(a);
3004
- if (W(this.opts.video)) return new K(a);
3046
+ if (o === "hls") return new It(a);
3047
+ if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && G(this.opts.video) && Je()) return new K(a);
3048
+ if (at()) return new It(a);
3049
+ if (G(this.opts.video)) return new K(a);
3005
3050
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
3006
3051
  }
3007
3052
  onEngineEvent(e) {
@@ -3053,7 +3098,7 @@ var An = class {
3053
3098
  r = 0, i = !1;
3054
3099
  },
3055
3100
  onError: () => {
3056
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= Tn && (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.`))));
3101
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= On && (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.`))));
3057
3102
  }
3058
3103
  });
3059
3104
  }
@@ -3075,7 +3120,7 @@ var An = class {
3075
3120
  scheduleRejoin(e) {
3076
3121
  this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
3077
3122
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
3078
- }, Math.random() * wn));
3123
+ }, Math.random() * Q));
3079
3124
  }
3080
3125
  cancelRejoin() {
3081
3126
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -3099,7 +3144,7 @@ var An = class {
3099
3144
  }
3100
3145
  t.controls = !1;
3101
3146
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
3102
- r && r.insertBefore(n, t), n.appendChild(t), cn(n, this.opts.theme), this.container = n, this.controlBar = _n({
3147
+ r && r.insertBefore(n, t), n.appendChild(t), dn(n, this.opts.theme), this.container = n, this.controlBar = bn({
3103
3148
  root: n,
3104
3149
  video: t,
3105
3150
  player: this,
@@ -3130,7 +3175,7 @@ var An = class {
3130
3175
  }
3131
3176
  startStats() {
3132
3177
  this.stopStats();
3133
- let e = Math.max(500, this.opts.statsIntervalMs ?? En);
3178
+ let e = Math.max(500, this.opts.statsIntervalMs ?? kn);
3134
3179
  this.statsTimer = setInterval(() => {
3135
3180
  if (!this.engine) return;
3136
3181
  let e = this.engine.getStats();
@@ -3142,7 +3187,7 @@ var An = class {
3142
3187
  }
3143
3188
  scheduleLicenseRefresh() {
3144
3189
  this.stopRefresh();
3145
- let e = this.opts.licenseRefreshMs ?? Dn;
3190
+ let e = this.opts.licenseRefreshMs ?? An;
3146
3191
  e && this._descriptor?.drmEnabled && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
3147
3192
  }
3148
3193
  stopRefresh() {
@@ -3151,7 +3196,7 @@ var An = class {
3151
3196
  async refreshLicense() {
3152
3197
  let e = this.engine;
3153
3198
  if (!this.destroyed && e) try {
3154
- let t = await v(this.resolveOptions());
3199
+ let t = await y(this.resolveOptions());
3155
3200
  if (this.destroyed || this.engine !== e) return;
3156
3201
  let n = t.drm?.widevine?.licenseUrl, r = t.drm?.fairplay?.licenseUrl;
3157
3202
  (n || r) && (e.setLicenseUrls({
@@ -3187,7 +3232,7 @@ var An = class {
3187
3232
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
3188
3233
  }
3189
3234
  getStats() {
3190
- return this.engine?.getStats() ?? V;
3235
+ return this.engine?.getStats() ?? He;
3191
3236
  }
3192
3237
  getQualityLevels() {
3193
3238
  return this.engine?.getQualityLevels() ?? [];
@@ -3239,7 +3284,7 @@ var An = class {
3239
3284
  let e = ++this.loadGen;
3240
3285
  this.teardownPlayback();
3241
3286
  try {
3242
- let t = await y({
3287
+ let t = await b({
3243
3288
  ...this.resolveOptions(),
3244
3289
  waitForLive: this.opts.waitForLive ? this.buildWaitConfig(e) : void 0
3245
3290
  });
@@ -3297,7 +3342,7 @@ function $(e) {
3297
3342
  }
3298
3343
  //#endregion
3299
3344
  //#region src/embed/host.ts
3300
- function jn(e) {
3345
+ function Pn(e) {
3301
3346
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
3302
3347
  if (!e.src) throw Error("mountEmbed: opts.src is required");
3303
3348
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -3356,15 +3401,15 @@ function jn(e) {
3356
3401
  }
3357
3402
  //#endregion
3358
3403
  //#region src/embed/page.ts
3359
- var Mn = /^[A-Za-z0-9:_.-]{1,128}$/;
3360
- function Nn(e) {
3404
+ var Fn = /^[A-Za-z0-9:_.-]{1,128}$/;
3405
+ function In(e) {
3361
3406
  if (typeof e == "boolean") return e;
3362
3407
  if (typeof e == "object" && e) {
3363
3408
  let t = e.endpoint;
3364
3409
  return typeof t == "string" ? { endpoint: t } : {};
3365
3410
  }
3366
3411
  }
3367
- function Pn() {
3412
+ function Ln() {
3368
3413
  let e = window.HAVIK_EMBED_CONFIG;
3369
3414
  if (e) return {
3370
3415
  baseUrl: e.baseUrl ?? "",
@@ -3374,7 +3419,7 @@ function Pn() {
3374
3419
  autoplay: e.autoplay ?? !0,
3375
3420
  muted: e.muted ?? !0,
3376
3421
  waitForLive: e.waitForLive ?? !0,
3377
- analytics: Nn(e.analytics)
3422
+ analytics: In(e.analytics)
3378
3423
  };
3379
3424
  let t = new URLSearchParams(location.search);
3380
3425
  return {
@@ -3387,9 +3432,9 @@ function Pn() {
3387
3432
  waitForLive: !0
3388
3433
  };
3389
3434
  }
3390
- async function Fn() {
3391
- let e = Pn();
3392
- Ln();
3435
+ async function Rn() {
3436
+ let e = Ln();
3437
+ Bn();
3393
3438
  let t = document.createElement("video");
3394
3439
  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);
3395
3440
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -3444,12 +3489,12 @@ async function Fn() {
3444
3489
  code: e.code,
3445
3490
  message: e.message
3446
3491
  })), e.on("autoplayblocked", () => {
3447
- i({ type: "oddin:autoplayblocked" }), In(t, e);
3492
+ i({ type: "oddin:autoplayblocked" }), zn(t, e);
3448
3493
  });
3449
3494
  }, s = async (r) => {
3450
3495
  a?.destroy(), a = null;
3451
3496
  try {
3452
- let n = await kn({
3497
+ let n = await Mn({
3453
3498
  video: t,
3454
3499
  baseUrl: e.baseUrl,
3455
3500
  matchUrn: r,
@@ -3511,7 +3556,7 @@ async function Fn() {
3511
3556
  a?.retry();
3512
3557
  break;
3513
3558
  case "oddin:load":
3514
- typeof e.matchUrn == "string" && Mn.test(e.matchUrn) && s(e.matchUrn);
3559
+ typeof e.matchUrn == "string" && Fn.test(e.matchUrn) && s(e.matchUrn);
3515
3560
  break;
3516
3561
  case "oddin:destroy": c();
3517
3562
  }
@@ -3523,18 +3568,18 @@ async function Fn() {
3523
3568
  protocol: 1
3524
3569
  });
3525
3570
  }
3526
- function In(e, t) {
3571
+ function zn(e, t) {
3527
3572
  if (document.getElementById("havik-tap")) return;
3528
3573
  let n = document.createElement("button");
3529
3574
  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", () => {
3530
3575
  n.remove(), t.play();
3531
3576
  }), document.body.appendChild(n);
3532
3577
  }
3533
- function Ln() {
3578
+ function Bn() {
3534
3579
  let e = document.createElement("style");
3535
3580
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
3536
3581
  }
3537
3582
  //#endregion
3538
- export { on as ODDIN_THEME, n as PlaybackError, cn as applyTheme, B as baseUrlForEnv, Fn as bootEmbed, Te as clearDeviceId, kn 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, jn as mountEmbed, de as pollToLive, y as resolveStream, T as subscribeLiveState, S as videoFullscreenActive, Ce as watchStatus };
3583
+ export { ln as ODDIN_THEME, n as PlaybackError, dn as applyTheme, Ve as baseUrlForEnv, Rn as bootEmbed, Te as clearDeviceId, Mn as createPlayer, E as deriveEventsBaseUrl, Le as detectDrmSupport, C as enterVideoFullscreen, w as exitVideoFullscreen, _e as fetchCatalog, ye as fetchTournament, xe as flattenMatches, k as getDeviceId, ze as isLowLatencyManifest, $ as isOddinMessage, De as licenseRequestHeaders, Pn as mountEmbed, v as pollToLive, b as resolveStream, T as subscribeLiveState, S as videoFullscreenActive, Ce as watchStatus };
3539
3584
 
3540
3585
  //# sourceMappingURL=index.js.map