@oddin-gg/havik-player 1.8.3 → 1.9.0

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
@@ -112,7 +112,7 @@ function te(e, t, n) {
112
112
  function ne(e) {
113
113
  return Math.round(e * (1 + .15 * Math.random()));
114
114
  }
115
- function _(e, t, n, r) {
115
+ function re(e, t, n, r) {
116
116
  let i = r ?? n.kickoffMs, a = i == null ? void 0 : Math.max(0, i - t), o = Math.max(n.floorMs, e ?? 0);
117
117
  if (a == null || a <= n.imminentMs) return {
118
118
  delay: te(e ?? n.floorMs, o, n.ceilMs),
@@ -124,7 +124,7 @@ function _(e, t, n, r) {
124
124
  ttkMs: a
125
125
  };
126
126
  }
127
- function re(e, n) {
127
+ function ie(e, n) {
128
128
  return new Promise((r, i) => {
129
129
  if (n?.aborted) {
130
130
  i(new t("ABORTED", 0, "wait-for-live aborted"));
@@ -138,7 +138,7 @@ function re(e, n) {
138
138
  n?.addEventListener("abort", a, { once: !0 });
139
139
  });
140
140
  }
141
- async function v(e, n, r) {
141
+ async function _(e, n, r) {
142
142
  let a = g(n), o = Date.now(), s = a.timeoutMs == null ? Infinity : o + a.timeoutMs, c = 0, l;
143
143
  for (;;) {
144
144
  if (r?.aborted) throw new t("ABORTED", 0, "wait-for-live aborted");
@@ -147,7 +147,7 @@ async function v(e, n, r) {
147
147
  } catch (e) {
148
148
  if (!(e instanceof t) || !i(e.code)) throw e;
149
149
  c += 1, e.liveStartsAtMs != null && Number.isFinite(e.liveStartsAtMs) && (l = e.liveStartsAtMs);
150
- let n = Date.now(), u = _(e.retryAfterMs, n, a, l), d = u.ttkMs, f = u.delay;
150
+ let n = Date.now(), u = re(e.retryAfterMs, n, a, l), d = u.ttkMs, f = u.delay;
151
151
  if (a.jitter && (f = ne(f)), n + f > s) throw new t("TIMEOUT", e.httpStatus, `timed out after ${a.timeoutMs}ms waiting for the stream to go live`, {
152
152
  retryAfterMs: e.retryAfterMs,
153
153
  cause: e
@@ -158,26 +158,26 @@ async function v(e, n, r) {
158
158
  attempt: c,
159
159
  elapsedMs: n - o,
160
160
  ttkMs: d
161
- }), await re(f, r);
161
+ }), await ie(f, r);
162
162
  }
163
163
  }
164
164
  }
165
165
  //#endregion
166
166
  //#region src/core/resolve.ts
167
- async function y(e) {
167
+ async function v(e) {
168
168
  if (!e.matchUrn || typeof e.matchUrn != "string") throw new t("INVALID_URN", 0, "resolveStream: a non-empty matchUrn is required");
169
169
  let n = await c(o(e.baseUrl, `/v1/playback/${encodeURIComponent(e.matchUrn)}`), {
170
170
  credential: e.credential,
171
171
  signal: e.signal
172
172
  });
173
- if (!n.ok) throw await ie(n);
174
- return S(await n.json(), e.matchUrn);
173
+ if (!n.ok) throw await ae(n);
174
+ return se(await n.json(), e.matchUrn);
175
175
  }
176
- function b(e) {
177
- let t = () => y(e);
178
- return e.waitForLive ? v(t, e.waitForLive, e.signal) : t();
176
+ function y(e) {
177
+ let t = () => v(e);
178
+ return e.waitForLive ? _(t, e.waitForLive, e.signal) : t();
179
179
  }
180
- async function ie(e) {
180
+ async function ae(e) {
181
181
  let r = s(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, c;
182
182
  try {
183
183
  let t = await e.json();
@@ -193,7 +193,7 @@ async function ie(e) {
193
193
  liveStartsAtMs: c
194
194
  });
195
195
  }
196
- var ae = /* @__PURE__ */ new Set([
196
+ var oe = /* @__PURE__ */ new Set([
197
197
  "matchUrn",
198
198
  "protocol",
199
199
  "drmEnabled",
@@ -201,9 +201,10 @@ var ae = /* @__PURE__ */ new Set([
201
201
  "drm",
202
202
  "serverTime",
203
203
  "liveStartsAt",
204
- "expiresAt"
204
+ "expiresAt",
205
+ "analytics"
205
206
  ]);
206
- function x(e, n) {
207
+ function b(e, n) {
207
208
  let r;
208
209
  try {
209
210
  r = new URL(n);
@@ -213,29 +214,29 @@ function x(e, n) {
213
214
  if (r.protocol !== "https:" && r.protocol !== "http:") throw new t("INTERNAL", 0, `playback ${e} has a disallowed scheme: ${r.protocol}`);
214
215
  return n;
215
216
  }
216
- function S(e, n) {
217
+ function se(e, n) {
217
218
  if (!e.manifestUrl) throw new t("INTERNAL", 0, "playback response is missing manifestUrl");
218
219
  let r = e.drmEnabled ?? !!e.drm, i = {
219
220
  matchUrn: e.matchUrn ?? n,
220
221
  protocol: "hls",
221
222
  drmEnabled: r,
222
- manifestUrl: x("manifestUrl", e.manifestUrl),
223
+ manifestUrl: b("manifestUrl", e.manifestUrl),
223
224
  serverTime: e.serverTime ?? ""
224
225
  };
225
- if (e.liveStartsAt && (i.liveStartsAt = e.liveStartsAt), r && e.drm) {
226
+ if (e.liveStartsAt && (i.liveStartsAt = e.liveStartsAt), typeof e.analytics?.sid == "string" && e.analytics.sid && (i.analytics = { sid: e.analytics.sid }), r && e.drm) {
226
227
  let t = {};
227
- e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: x("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
228
- licenseUrl: x("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
229
- certificateUrl: x("fairplay certificateUrl", e.drm.fairplay.certificateUrl)
228
+ e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: b("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
229
+ licenseUrl: b("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
230
+ certificateUrl: b("fairplay certificateUrl", e.drm.fairplay.certificateUrl)
230
231
  }), (t.widevine || t.fairplay) && (i.drm = t);
231
232
  }
232
233
  let a = {};
233
- for (let [t, n] of Object.entries(e)) ae.has(t) || (a[t] = n);
234
+ for (let [t, n] of Object.entries(e)) oe.has(t) || (a[t] = n);
234
235
  return Object.keys(a).length > 0 && (i.extensions = a), i;
235
236
  }
236
237
  //#endregion
237
238
  //#region src/core/catalog.ts
238
- async function C(e) {
239
+ async function x(e) {
239
240
  let r = new URLSearchParams();
240
241
  e.status && r.set("status", Array.isArray(e.status) ? e.status.join(",") : e.status), e.sport && r.set("sport", e.sport);
241
242
  let i = r.toString(), a = o(e.baseUrl, "/v1/catalog" + (i ? `?${i}` : "")), s = new Headers();
@@ -252,12 +253,12 @@ async function C(e) {
252
253
  };
253
254
  if (!l.ok) throw new t(n(l.status), l.status, `catalog request failed (HTTP ${l.status})`);
254
255
  return {
255
- tournaments: oe(await l.json()),
256
+ tournaments: S(await l.json()),
256
257
  etag: l.headers.get("etag") ?? void 0,
257
258
  notModified: !1
258
259
  };
259
260
  }
260
- function oe(e) {
261
+ function S(e) {
261
262
  return (e.tournaments ?? []).map((e) => {
262
263
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
263
264
  return {
@@ -277,7 +278,7 @@ function oe(e) {
277
278
  };
278
279
  });
279
280
  }
280
- async function se(e) {
281
+ async function ce(e) {
281
282
  let r = o(e.baseUrl, `/v1/tournaments/${encodeURIComponent(e.urn)}`), i = new Headers();
282
283
  e.etag && i.set("If-None-Match", e.etag);
283
284
  let a = await c(r, {
@@ -296,13 +297,13 @@ async function se(e) {
296
297
  };
297
298
  if (!a.ok) throw new t(n(a.status), a.status, `tournament request failed (HTTP ${a.status})`);
298
299
  return {
299
- tournament: ce(await a.json()),
300
+ tournament: le(await a.json()),
300
301
  etag: a.headers.get("etag") ?? void 0,
301
302
  notModified: !1,
302
303
  notFound: !1
303
304
  };
304
305
  }
305
- function ce(e) {
306
+ function le(e) {
306
307
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
307
308
  return {
308
309
  urn: t,
@@ -320,12 +321,12 @@ function ce(e) {
320
321
  }))
321
322
  };
322
323
  }
323
- function le(e) {
324
+ function ue(e) {
324
325
  return e.tournaments.flatMap((e) => e.matches);
325
326
  }
326
327
  //#endregion
327
328
  //#region src/core/poll.ts
328
- function ue(e) {
329
+ function de(e) {
329
330
  let r = Math.max(2e3, e.intervalMs ?? 1e4), i = new AbortController(), a, s, l, u = !1, d = () => {
330
331
  u || (a = setTimeout(f, r));
331
332
  }, f = async () => {
@@ -360,7 +361,7 @@ function ue(e) {
360
361
  }
361
362
  //#endregion
362
363
  //#region src/core/events.ts
363
- function w(e) {
364
+ function C(e) {
364
365
  let t = Math.max(250, e.minBackoffMs ?? 1e3), n = Math.max(t, e.maxBackoffMs ?? 15e3), r = new AbortController(), i = !1, s = (e) => new Promise((t) => {
365
366
  if (r.signal.aborted) return t();
366
367
  let n = setTimeout(t, e);
@@ -427,7 +428,7 @@ function w(e) {
427
428
  i || (i = !0, r.abort());
428
429
  } };
429
430
  }
430
- function T(e) {
431
+ function w(e) {
431
432
  try {
432
433
  let t = new URL(e);
433
434
  return t.hostname = `events.${t.hostname}`, t.pathname = "", t.search = "", t.origin;
@@ -437,27 +438,27 @@ function T(e) {
437
438
  }
438
439
  //#endregion
439
440
  //#region src/core/drm/deviceId.ts
440
- var E = "havik.player.deviceId", D;
441
- function O() {
442
- if (D) return D;
441
+ var T = "havik.player.deviceId", E;
442
+ function D() {
443
+ if (E) return E;
443
444
  try {
444
- let e = globalThis.localStorage?.getItem(E);
445
- if (e) return D = e, e;
445
+ let e = globalThis.localStorage?.getItem(T);
446
+ if (e) return E = e, e;
446
447
  } catch {}
447
- let e = fe();
448
- D = e;
448
+ let e = pe();
449
+ E = e;
449
450
  try {
450
- globalThis.localStorage?.setItem(E, e);
451
+ globalThis.localStorage?.setItem(T, e);
451
452
  } catch {}
452
453
  return e;
453
454
  }
454
- function de() {
455
- D = void 0;
455
+ function fe() {
456
+ E = void 0;
456
457
  try {
457
- globalThis.localStorage?.removeItem(E);
458
+ globalThis.localStorage?.removeItem(T);
458
459
  } catch {}
459
460
  }
460
- function fe() {
461
+ function pe() {
461
462
  let e = globalThis.crypto;
462
463
  if (e && typeof e.randomUUID == "function") return e.randomUUID();
463
464
  let t = /* @__PURE__ */ new Uint8Array(16);
@@ -469,22 +470,22 @@ function fe() {
469
470
  }
470
471
  //#endregion
471
472
  //#region src/core/drm/headers.ts
472
- function pe(e, t, n = {}) {
473
+ function me(e, t, n = {}) {
473
474
  let r = {
474
475
  "x-api-key": t.apiKey,
475
476
  "X-Match-Urn": e.matchUrn,
476
- "X-Device-Id": n.deviceId ?? O(),
477
+ "X-Device-Id": n.deviceId ?? D(),
477
478
  "Content-Type": "application/octet-stream"
478
479
  };
479
480
  return n.userId && (r["X-User-Id"] = n.userId), r;
480
481
  }
481
482
  //#endregion
482
483
  //#region src/core/drm/support.ts
483
- var me = 5e3, k = "video/mp4; codecs=\"avc1.42E01E\"", A = "audio/mp4; codecs=\"mp4a.40.2\"", j = {
484
+ var he = 5e3, O = "video/mp4; codecs=\"avc1.42E01E\"", k = "audio/mp4; codecs=\"mp4a.40.2\"", ge = {
484
485
  widevine: ["com.widevine.alpha"],
485
486
  fairplay: ["com.apple.fps"]
486
487
  };
487
- function M(e) {
488
+ function _e(e) {
488
489
  return e === "fairplay" ? [{
489
490
  initDataTypes: [
490
491
  "sinf",
@@ -492,30 +493,30 @@ function M(e) {
492
493
  "cenc"
493
494
  ],
494
495
  videoCapabilities: [{
495
- contentType: k,
496
+ contentType: O,
496
497
  robustness: "",
497
498
  encryptionScheme: "cbcs"
498
499
  }],
499
500
  audioCapabilities: [{
500
- contentType: A,
501
+ contentType: k,
501
502
  robustness: "",
502
503
  encryptionScheme: "cbcs"
503
504
  }]
504
505
  }] : [{
505
506
  initDataTypes: ["cenc"],
506
507
  videoCapabilities: [{
507
- contentType: k,
508
+ contentType: O,
508
509
  robustness: "SW_SECURE_DECODE",
509
510
  encryptionScheme: "cbcs"
510
511
  }],
511
512
  audioCapabilities: [{
512
- contentType: A,
513
+ contentType: k,
513
514
  robustness: "SW_SECURE_CRYPTO",
514
515
  encryptionScheme: "cbcs"
515
516
  }]
516
517
  }];
517
518
  }
518
- function N(e, t) {
519
+ function A(e, t) {
519
520
  return new Promise((n, r) => {
520
521
  let i = setTimeout(() => r(/* @__PURE__ */ Error("probe-timeout")), t);
521
522
  e.then((e) => {
@@ -525,8 +526,8 @@ function N(e, t) {
525
526
  });
526
527
  });
527
528
  }
528
- async function P(e = {}) {
529
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? me, r = {};
529
+ async function j(e = {}) {
530
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? he, r = {};
530
531
  if (typeof window < "u" && window.isSecureContext === !1) return {
531
532
  verdict: "insecure-context",
532
533
  detail: r
@@ -538,7 +539,7 @@ async function P(e = {}) {
538
539
  };
539
540
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
540
541
  for (let e of t) {
541
- let t = j[e];
542
+ let t = ge[e];
542
543
  if (!t) {
543
544
  r[e] = "unknown system";
544
545
  continue;
@@ -549,7 +550,7 @@ async function P(e = {}) {
549
550
  break;
550
551
  }
551
552
  try {
552
- return await N((await N(i(n, M(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
553
+ return await A((await A(i(n, _e(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
553
554
  verdict: "supported",
554
555
  system: e,
555
556
  detail: r
@@ -573,17 +574,17 @@ async function P(e = {}) {
573
574
  }
574
575
  //#endregion
575
576
  //#region src/core/manifest.ts
576
- var F = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
577
- function I(e) {
578
- return F.test(e);
577
+ var M = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
578
+ function N(e) {
579
+ return M.test(e);
579
580
  }
580
581
  //#endregion
581
582
  //#region src/core/stats.ts
582
- var L = { droppedFrames: 0 };
583
- function R(e) {
583
+ var P = { droppedFrames: 0 };
584
+ function F(e) {
584
585
  return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
585
586
  }
586
- function z(e, t) {
587
+ function I(e, t) {
587
588
  let n = [
588
589
  ["playing", () => t("playing")],
589
590
  ["waiting", () => t("buffering")],
@@ -600,7 +601,7 @@ function z(e, t) {
600
601
  }
601
602
  //#endregion
602
603
  //#region src/core/engine/native.ts
603
- function he(e) {
604
+ function ve(e) {
604
605
  try {
605
606
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
606
607
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -608,15 +609,15 @@ function he(e) {
608
609
  return;
609
610
  }
610
611
  }
611
- function B(e) {
612
+ function L(e) {
612
613
  return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
613
614
  }
614
- function V() {
615
+ function R() {
615
616
  if (typeof window > "u") return !1;
616
617
  let e = "WebKitMediaKeys" in window || typeof HTMLMediaElement < "u" && "webkitSetMediaKeys" in HTMLMediaElement.prototype, t = typeof navigator < "u" && /Apple/i.test(navigator.vendor ?? "");
617
618
  return e || t;
618
619
  }
619
- var H = class {
620
+ var z = class {
620
621
  name = "native";
621
622
  deps;
622
623
  onLoaded;
@@ -646,7 +647,7 @@ var H = class {
646
647
  type: "error",
647
648
  error: new t("INTERNAL", 0, "native HLS playback error"),
648
649
  fatal: !0
649
- }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = n.manifestUrl, e.load();
650
+ }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = ye(n.manifestUrl, n.analytics?.sid), e.load();
650
651
  }
651
652
  armFairPlay() {
652
653
  let { video: e, descriptor: n, credential: r, deviceId: i, userId: a, emit: o } = this.deps, s = n.drm?.fairplay;
@@ -659,7 +660,7 @@ var H = class {
659
660
  });
660
661
  this.onEncrypted = (t) => {
661
662
  let o = t;
662
- !o.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = he(o.initData), (async () => {
663
+ !o.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = ve(o.initData), (async () => {
663
664
  try {
664
665
  let t = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
665
666
  initDataTypes: [o.initDataType],
@@ -751,7 +752,7 @@ var H = class {
751
752
  e.seekable.length > 0 && (e.currentTime = e.seekable.end(e.seekable.length - 1));
752
753
  }
753
754
  getStats() {
754
- let e = this.deps.video, t = { droppedFrames: R(e) };
755
+ let e = this.deps.video, t = { droppedFrames: F(e) };
755
756
  return e.duration === Infinity && (t.isLive = !0, e.seekable.length > 0 && (t.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), t;
756
757
  }
757
758
  stop() {
@@ -771,31 +772,38 @@ var H = class {
771
772
  } catch {}
772
773
  }
773
774
  };
775
+ function ye(e, t) {
776
+ return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
777
+ }
774
778
  //#endregion
775
779
  //#region src/core/engine/hls.ts
776
- function ge() {
780
+ function be() {
777
781
  return e.isSupported();
778
782
  }
779
- var _e = 3, ve = 5, ye = 3, be = 3e4, xe = 12e3, Se = 3e3, Ce = 6e3, we = 2, Te = 9, Ee = .5;
780
- function De(e) {
783
+ var xe = 3, Se = 5, Ce = 3, we = 3e4, Te = 12e3, Ee = 3e3, B = 6e3, De = 4e3, Oe = 2, ke = 5, Ae = 2, je = 9, Me = .5;
784
+ function Ne(e) {
781
785
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
782
786
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
783
787
  return;
784
788
  }
785
- e.liveSyncDuration ??= we;
786
- let t = Math.max(12, e.liveSyncDuration + Te);
787
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Oe(e, e.liveSyncDuration);
789
+ e.liveSyncDuration ??= Ae;
790
+ let t = Math.max(12, e.liveSyncDuration + je);
791
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Pe(e, e.liveSyncDuration);
788
792
  }
789
- function Oe(e, t) {
793
+ function Pe(e, t) {
790
794
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
791
795
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
792
796
  }
793
- var U = class {
797
+ var V = class {
794
798
  name = "hls";
795
799
  hls = null;
796
800
  deps;
797
801
  licenseUrl;
798
802
  detectedLowLatency = !1;
803
+ bytesLoaded = 0;
804
+ playlistBytesLoaded = 0;
805
+ renditionSwitches = 0;
806
+ licenseTimeMs = 0;
799
807
  mediaRecoveries = 0;
800
808
  networkRecoveries = 0;
801
809
  licenseRecoveries = 0;
@@ -809,6 +817,9 @@ var U = class {
809
817
  lastAdvanceMs = 0;
810
818
  endWatchdog = null;
811
819
  endedEmitted = !1;
820
+ lastFragLoadingMs = 0;
821
+ starvedTicks = 0;
822
+ starvationKicks = 0;
812
823
  chaseSuspended = !1;
813
824
  savedLiveMaxLatency;
814
825
  savedMaxSyncRate;
@@ -816,7 +827,7 @@ var U = class {
816
827
  this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
817
828
  }
818
829
  async load() {
819
- let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps, a = B(r) && V(), o = !!e.drm?.widevine?.licenseUrl && !a;
830
+ let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
820
831
  if (e.drmEnabled && !o) {
821
832
  i({
822
833
  type: "error",
@@ -840,7 +851,7 @@ var U = class {
840
851
  let t = e.liveSyncPosition;
841
852
  if (typeof t != "number" || !Number.isFinite(t)) return;
842
853
  let { video: n } = this.deps;
843
- n.currentTime <= t + Ee || (n.currentTime = t);
854
+ n.currentTime <= t + Me || (n.currentTime = t);
844
855
  }
845
856
  startHls() {
846
857
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps, u = n.drmEnabled && !!(n.drm?.widevine?.licenseUrl || n.drm?.fairplay?.licenseUrl), d = {
@@ -863,29 +874,48 @@ var U = class {
863
874
  },
864
875
  licenseXhrSetup: (e, t) => {
865
876
  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);
877
+ let o = performance.now();
878
+ e.addEventListener("loadend", () => {
879
+ this.licenseTimeMs = Math.max(1, Math.round(performance.now() - o));
880
+ });
866
881
  }
867
882
  } : {},
883
+ ...n.analytics?.sid ? { cmcd: {
884
+ sessionId: n.analytics.sid,
885
+ contentId: n.matchUrn,
886
+ useHeaders: !1
887
+ } } : {},
868
888
  ...c
869
889
  };
870
- De(d);
890
+ Ne(d);
871
891
  let f = new e(d);
872
892
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
873
893
  this.mediaRecoveries = 0, this.networkRecoveries = 0, l({ type: "ready" });
874
- }), f.on(e.Events.FRAG_BUFFERED, () => {
875
- (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
876
- this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.recoveryResetTimer = null;
877
- }, be));
894
+ }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
895
+ t?.frag?.type === "main" && (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
896
+ this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.recoveryResetTimer = null;
897
+ }, we));
898
+ }), f.on(e.Events.FRAG_LOADING, (e, t) => {
899
+ t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
900
+ }), f.on(e.Events.FRAG_LOADED, (e, t) => {
901
+ let n = t?.frag?.stats?.loaded;
902
+ typeof n == "number" && (this.bytesLoaded += n);
878
903
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => {
879
- if (this.detectedLowLatency) return;
880
- let n = t.details;
881
- (n?.partList?.length || (n?.partTarget ?? 0) > 0) && (this.detectedLowLatency = !0, l({
904
+ let n = t?.stats?.loaded;
905
+ if (typeof n == "number" && (this.playlistBytesLoaded += n), this.detectedLowLatency) return;
906
+ let r = t.details;
907
+ (r?.partList?.length || (r?.partTarget ?? 0) > 0) && (this.detectedLowLatency = !0, l({
882
908
  type: "lowLatencyDetected",
883
909
  value: !0
884
910
  }));
885
- }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => this.checkEndStale(), Se), f.on(e.Events.LEVEL_SWITCHED, (e, t) => l({
886
- type: "qualitychange",
887
- index: t.level
888
- })), f.on(e.Events.AUDIO_TRACK_SWITCHED, (e, t) => l({
911
+ }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => {
912
+ this.checkEndStale(), this.checkStarvation();
913
+ }, Ee), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
914
+ this.renditionSwitches += 1, l({
915
+ type: "qualitychange",
916
+ index: t.level
917
+ });
918
+ }), f.on(e.Events.AUDIO_TRACK_SWITCHED, (e, t) => l({
889
919
  type: "audiotrackchange",
890
920
  id: t.id
891
921
  })), f.on(e.Events.SUBTITLE_TRACK_SWITCH, (e, t) => l({
@@ -951,13 +981,17 @@ var U = class {
951
981
  }
952
982
  getStats() {
953
983
  let e = this.hls, t = {
954
- droppedFrames: R(this.deps.video),
955
- lowLatency: this.detectedLowLatency
984
+ droppedFrames: F(this.deps.video),
985
+ lowLatency: this.detectedLowLatency,
986
+ bytesLoaded: this.bytesLoaded,
987
+ playlistBytesLoaded: this.playlistBytesLoaded,
988
+ renditionSwitches: this.renditionSwitches,
989
+ licenseTimeMs: this.licenseTimeMs
956
990
  };
957
991
  if (e) {
958
992
  typeof e.latency == "number" && (t.latencySeconds = e.latency), typeof e.targetLatency == "number" && e.targetLatency > 0 && (t.targetLatencySeconds = e.targetLatency), typeof e.bandwidthEstimate == "number" && (t.bandwidthKbps = Math.round(e.bandwidthEstimate / 1e3));
959
993
  let n = e.currentLevel >= 0 ? e.levels[e.currentLevel] : void 0;
960
- n && (t.levelHeight = n.height), typeof n?.details?.live == "boolean" && (t.isLive = n.details.live, typeof e.liveSyncPosition == "number" && (t.atLiveEdge = this.deps.video.currentTime >= e.liveSyncPosition - 1.5));
994
+ n && (t.levelHeight = n.height), n && typeof n.bitrate == "number" && (t.renditionBitrateKbps = Math.round(n.bitrate / 1e3)), typeof n?.details?.live == "boolean" && (t.isLive = n.details.live, typeof e.liveSyncPosition == "number" && (t.atLiveEdge = this.deps.video.currentTime >= e.liveSyncPosition - 1.5));
961
995
  }
962
996
  return t;
963
997
  }
@@ -980,7 +1014,28 @@ var U = class {
980
1014
  return;
981
1015
  }
982
1016
  let e = Date.now() - this.lastAdvanceMs;
983
- !this.chaseSuspended && e > Ce && this.suspendChasing(), e > xe && this.emitEnded("stalled");
1017
+ !this.chaseSuspended && e > B && this.suspendChasing(), e > Te && this.emitEnded("stalled");
1018
+ }
1019
+ checkStarvation() {
1020
+ let e = this.hls, { video: t } = this.deps;
1021
+ if (!e || this.endedEmitted || !this.sawLive || this.chaseSuspended) return;
1022
+ if (typeof document < "u" && document.hidden) {
1023
+ this.starvedTicks = 0;
1024
+ return;
1025
+ }
1026
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < B, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > De;
1027
+ if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1028
+ this.starvedTicks = 0;
1029
+ return;
1030
+ }
1031
+ if (!(++this.starvedTicks < Oe) && (this.starvedTicks = 0, !(this.starvationKicks >= ke))) {
1032
+ this.starvationKicks += 1, console.warn("havik-player: video starved while the live playlist advances (wedged hls.js fragment pipeline) — restarting load at the live edge");
1033
+ try {
1034
+ e.stopLoad(), e.startLoad(-1);
1035
+ } catch (e) {
1036
+ console.warn("havik-player: starvation kick failed", e);
1037
+ }
1038
+ }
984
1039
  }
985
1040
  suspendChasing() {
986
1041
  let e = this.hls;
@@ -1022,7 +1077,7 @@ var U = class {
1022
1077
  scheduleLicenseRetry(t) {
1023
1078
  if (!(t.details === e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED || t.details === e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED)) return !1;
1024
1079
  let n = t.response?.code ?? 0;
1025
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= ye) return !1;
1080
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= Ce) return !1;
1026
1081
  this.licenseRecoveries += 1;
1027
1082
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1028
1083
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1050,7 +1105,7 @@ var U = class {
1050
1105
  return;
1051
1106
  }
1052
1107
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1053
- if (this.networkRecoveries < ve) {
1108
+ if (this.networkRecoveries < Se) {
1054
1109
  this.networkRecoveries += 1;
1055
1110
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1056
1111
  this.networkRetryTimer = setTimeout(() => {
@@ -1066,7 +1121,7 @@ var U = class {
1066
1121
  return;
1067
1122
  }
1068
1123
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1069
- if (this.mediaRecoveries < _e) {
1124
+ if (this.mediaRecoveries < xe) {
1070
1125
  this.mediaRecoveries += 1, n.recoverMediaError();
1071
1126
  return;
1072
1127
  }
@@ -1088,7 +1143,172 @@ var U = class {
1088
1143
  let r = e.response?.code ?? 0, i = e.details ?? e.type, a = e.reason ?? e.error?.message ?? "";
1089
1144
  return new t(r === 401 ? "UNAUTHORIZED" : r === 403 || n === "DRM" ? "FORBIDDEN" : "INTERNAL", r, `${n} error: ${i}${a ? ` (${a})` : ""}`);
1090
1145
  }
1091
- }, W = {
1146
+ }, Fe = 15e3, Ie = 100, H = 1, U = {
1147
+ idle: "PLAYER_STATE_IDLE",
1148
+ waiting: "PLAYER_STATE_WAITING",
1149
+ loading: "PLAYER_STATE_LOADING",
1150
+ playing: "PLAYER_STATE_PLAYING",
1151
+ buffering: "PLAYER_STATE_BUFFERING",
1152
+ paused: "PLAYER_STATE_PAUSED",
1153
+ ended: "PLAYER_STATE_ENDED",
1154
+ error: "PLAYER_STATE_ERROR"
1155
+ };
1156
+ function Le(e) {
1157
+ try {
1158
+ let t = new URL(e), [n, ...r] = t.hostname.split(".");
1159
+ 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);
1160
+ } catch {
1161
+ return;
1162
+ }
1163
+ }
1164
+ var Re = class {
1165
+ opts;
1166
+ url;
1167
+ now;
1168
+ seq = 0;
1169
+ pending = [];
1170
+ timer = null;
1171
+ endedFlag = !1;
1172
+ lastBytes = 0;
1173
+ lastPlaylistBytes = 0;
1174
+ lastDropped = 0;
1175
+ lastSwitches = 0;
1176
+ playingSinceMs = null;
1177
+ hiddenSinceMs = null;
1178
+ playingTotalMs = 0;
1179
+ hiddenTotalMs = 0;
1180
+ lastHbPlayingMs = 0;
1181
+ lastHbHiddenMs = 0;
1182
+ rebufferCount = 0;
1183
+ rebufferTotalMs = 0;
1184
+ bufferingSinceMs = null;
1185
+ errorCount = 0;
1186
+ seekCount = 0;
1187
+ pauseCount = 0;
1188
+ bornMs;
1189
+ startupTimeMs = 0;
1190
+ manifestLoadMs = 0;
1191
+ firstFragLoadMs = 0;
1192
+ onPagehide = () => this.end("END_REASON_PAGEHIDE");
1193
+ onVisibility = () => {
1194
+ typeof document > "u" || (document.visibilityState === "hidden" ? this.hiddenSinceMs === null && (this.hiddenSinceMs = this.now()) : this.hiddenSinceMs !== null && (this.hiddenTotalMs += this.now() - this.hiddenSinceMs, this.hiddenSinceMs = null));
1195
+ };
1196
+ constructor(e) {
1197
+ this.opts = {
1198
+ ...e,
1199
+ heartbeatMs: e.heartbeatMs ?? Fe
1200
+ }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
1201
+ }
1202
+ start() {
1203
+ this.endedFlag || (this.enqueue({ sessionStart: {
1204
+ player: this.opts.player,
1205
+ sdkVersion: this.opts.sdkVersion
1206
+ } }), this.flush(), this.timer = setInterval(() => this.heartbeat(), this.opts.heartbeatMs), typeof window < "u" && window.addEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.addEventListener("visibilitychange", this.onVisibility), this.onVisibility()));
1207
+ }
1208
+ noteStateChange(e, t, n, r) {
1209
+ if (this.endedFlag || e === t) return;
1210
+ let i = this.now();
1211
+ t === "playing" ? (this.playingSinceMs === null && (this.playingSinceMs = i), this.startupTimeMs === 0 && (this.startupTimeMs = Math.max(H, i - this.bornMs))) : this.playingSinceMs !== null && (this.playingTotalMs += i - this.playingSinceMs, this.playingSinceMs = null), t === "buffering" && e === "playing" ? (this.rebufferCount += 1, this.bufferingSinceMs = i) : e === "buffering" && this.bufferingSinceMs !== null && (this.rebufferTotalMs += i - this.bufferingSinceMs, this.bufferingSinceMs = null), t === "paused" && (this.pauseCount += 1);
1212
+ let a = {
1213
+ fromState: U[e] ?? "PLAYER_STATE_UNSPECIFIED",
1214
+ toState: U[t] ?? "PLAYER_STATE_UNSPECIFIED"
1215
+ };
1216
+ n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1217
+ }
1218
+ noteReady() {
1219
+ this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(H, this.now() - this.bornMs));
1220
+ }
1221
+ noteFirstBytes() {
1222
+ this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(H, this.now() - this.bornMs));
1223
+ }
1224
+ noteSeek() {
1225
+ this.endedFlag || (this.seekCount += 1);
1226
+ }
1227
+ noteError(e, t, n, r, i) {
1228
+ if (this.endedFlag) return;
1229
+ this.errorCount += 1;
1230
+ let a = { code: e };
1231
+ t && (a.detail = t), n && (a.fatal = !0), r > 0 && (a.httpStatus = r), i > 0 && (a.positionMs = Math.round(i)), this.enqueue({ error: a }), this.flush();
1232
+ }
1233
+ noteEnded() {
1234
+ this.end("END_REASON_ENDED");
1235
+ }
1236
+ noteDestroyed() {
1237
+ this.end("END_REASON_STOPPED");
1238
+ }
1239
+ noteFatal() {
1240
+ this.end("END_REASON_FATAL_ERROR");
1241
+ }
1242
+ heartbeat() {
1243
+ if (this.endedFlag) return;
1244
+ let e = this.opts.sample(), t = this.now(), n = this.playingTotalMs;
1245
+ this.playingSinceMs !== null && (n += t - this.playingSinceMs);
1246
+ let r = this.hiddenTotalMs;
1247
+ this.hiddenSinceMs !== null && (r += t - this.hiddenSinceMs);
1248
+ let i = {}, a = e.bytesLoaded ?? 0;
1249
+ a > this.lastBytes && (i.bytesDownloadedDelta = a - this.lastBytes), this.lastBytes = a;
1250
+ let o = e.playlistBytesLoaded ?? 0;
1251
+ o > this.lastPlaylistBytes && (i.playlistBytesDelta = o - this.lastPlaylistBytes), this.lastPlaylistBytes = o;
1252
+ let s = e.droppedFrames ?? 0;
1253
+ s > this.lastDropped && (i.droppedFramesDelta = s - this.lastDropped), this.lastDropped = s;
1254
+ let c = e.renditionSwitches ?? 0;
1255
+ c > this.lastSwitches && (i.renditionSwitchesDelta = c - this.lastSwitches), this.lastSwitches = c, n > this.lastHbPlayingMs && (i.playingMsDelta = Math.round(n - this.lastHbPlayingMs)), this.lastHbPlayingMs = n, r > this.lastHbHiddenMs && (i.hiddenMsDelta = Math.round(r - this.lastHbHiddenMs)), this.lastHbHiddenMs = r, typeof e.latencySeconds == "number" && (i.latencyMs = Math.round(e.latencySeconds * 1e3)), typeof e.targetLatencySeconds == "number" && (i.targetLatencyMs = Math.round(e.targetLatencySeconds * 1e3)), typeof e.bandwidthKbps == "number" && (i.bandwidthEstKbps = e.bandwidthKbps), typeof e.levelHeight == "number" && (i.renditionHeight = e.levelHeight), typeof e.renditionBitrateKbps == "number" && (i.renditionBitrateKbps = e.renditionBitrateKbps), e.atLiveEdge === !0 && (i.atLiveEdge = !0);
1256
+ let l = this.bufferedMsFrom(e);
1257
+ l > 0 && (i.bufferedMs = l), i.playerState = U[e.state] ?? "PLAYER_STATE_UNSPECIFIED", this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1258
+ }
1259
+ bufferedMsFrom(e) {
1260
+ let t = e;
1261
+ return typeof t.bufferedMs == "number" ? Math.round(t.bufferedMs) : 0;
1262
+ }
1263
+ end(e) {
1264
+ if (this.endedFlag) return;
1265
+ this.endedFlag = !0;
1266
+ let t = this.now();
1267
+ this.playingSinceMs !== null && (this.playingTotalMs += t - this.playingSinceMs, this.playingSinceMs = null), this.hiddenSinceMs !== null && (this.hiddenTotalMs += t - this.hiddenSinceMs, this.hiddenSinceMs = null), this.bufferingSinceMs !== null && (this.rebufferTotalMs += t - this.bufferingSinceMs, this.bufferingSinceMs = null), this.timer &&= (clearInterval(this.timer), null), typeof window < "u" && window.removeEventListener("pagehide", this.onPagehide), typeof document < "u" && document.removeEventListener("visibilitychange", this.onVisibility);
1268
+ let n = this.safeSample(), r = { reason: e }, i = n?.bytesLoaded ?? this.lastBytes;
1269
+ i > 0 && (r.bytesDownloadedTotal = i), this.playingTotalMs > 0 && (r.watchTimeMs = Math.round(this.playingTotalMs)), this.rebufferCount > 0 && (r.rebufferCount = this.rebufferCount), this.rebufferTotalMs > 0 && (r.rebufferTimeMs = Math.round(this.rebufferTotalMs)), this.startupTimeMs > 0 && (r.startupTimeMs = Math.round(this.startupTimeMs)), this.manifestLoadMs > 0 && (r.manifestLoadMs = Math.round(this.manifestLoadMs)), this.firstFragLoadMs > 0 && (r.firstFragLoadMs = Math.round(this.firstFragLoadMs));
1270
+ let a = n?.licenseTimeMs ?? 0;
1271
+ a > 0 && (r.licenseTimeMs = Math.round(a));
1272
+ let o = n?.droppedFrames ?? this.lastDropped;
1273
+ o > 0 && (r.droppedFramesTotal = o), this.errorCount > 0 && (r.errorCount = this.errorCount), this.hiddenTotalMs > 0 && (r.hiddenTimeMs = Math.round(this.hiddenTotalMs)), this.seekCount > 0 && (r.seekCount = this.seekCount), this.pauseCount > 0 && (r.pauseCount = this.pauseCount), this.pending.push(this.wrap({ sessionEnd: r })), this.flush(e === "END_REASON_PAGEHIDE");
1274
+ }
1275
+ safeSample() {
1276
+ try {
1277
+ return this.opts.sample();
1278
+ } catch {
1279
+ return null;
1280
+ }
1281
+ }
1282
+ wrap(e) {
1283
+ return this.seq += 1, {
1284
+ sid: this.opts.sid,
1285
+ clientTime: new Date(this.now()).toISOString(),
1286
+ seq: this.seq,
1287
+ ...e
1288
+ };
1289
+ }
1290
+ enqueue(e) {
1291
+ for (this.pending.push(this.wrap(e)); this.pending.length > Ie;) {
1292
+ let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1293
+ this.pending.splice(e >= 0 ? e : 0, 1);
1294
+ }
1295
+ }
1296
+ flush(e = !1) {
1297
+ if (this.pending.length === 0) return;
1298
+ let t = JSON.stringify({ beacons: this.pending });
1299
+ if (this.pending = [], e) {
1300
+ let e = this.opts.sendBeacon ?? (typeof navigator < "u" && navigator.sendBeacon ? navigator.sendBeacon.bind(navigator) : void 0);
1301
+ if (e && e(this.url, t)) return;
1302
+ }
1303
+ let n = this.opts.fetchFn ?? (typeof fetch < "u" ? fetch : void 0);
1304
+ n && n(this.url, {
1305
+ method: "POST",
1306
+ headers: { "content-type": "application/json" },
1307
+ body: t,
1308
+ keepalive: !0
1309
+ }).catch(() => {});
1310
+ }
1311
+ }, ze = "1.9.0", W = {
1092
1312
  accent: "#E1B600",
1093
1313
  accentText: "#1B1C23",
1094
1314
  background: "#0e0f13",
@@ -1121,15 +1341,15 @@ function K(e, t) {
1121
1341
  }
1122
1342
  //#endregion
1123
1343
  //#region src/ui/styles.ts
1124
- var q = "havik-player-styles", ke = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n.havik-spinner{width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n";
1125
- function Ae(e = document) {
1344
+ var q = "havik-player-styles", Be = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n.havik-spinner{width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n";
1345
+ function Ve(e = document) {
1126
1346
  if (e.getElementById(q)) return;
1127
1347
  let t = e.createElement("style");
1128
- t.id = q, t.textContent = ke, e.head.appendChild(t);
1348
+ t.id = q, t.textContent = Be, e.head.appendChild(t);
1129
1349
  }
1130
1350
  //#endregion
1131
1351
  //#region src/ui/icons.ts
1132
- var je = {
1352
+ var He = {
1133
1353
  play: "<path d=\"M8 5v14l11-7z\"/>",
1134
1354
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1135
1355
  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\"/>",
@@ -1143,20 +1363,20 @@ var je = {
1143
1363
  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\"/>"
1144
1364
  };
1145
1365
  function J(e) {
1146
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${je[e]}</svg>`;
1366
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${He[e]}</svg>`;
1147
1367
  }
1148
1368
  //#endregion
1149
1369
  //#region src/ui/controls.ts
1150
- var Me = 3e3, Ne = /* @__PURE__ */ new Set([
1370
+ var Ue = 3e3, We = /* @__PURE__ */ new Set([
1151
1371
  "GONE",
1152
1372
  "NOT_FOUND",
1153
1373
  "INVALID_URN",
1154
1374
  "UNAUTHORIZED"
1155
1375
  ]);
1156
- function Pe(e) {
1157
- return new Fe(e);
1376
+ function Ge(e) {
1377
+ return new Ke(e);
1158
1378
  }
1159
- var Fe = class {
1379
+ var Ke = class {
1160
1380
  root;
1161
1381
  video;
1162
1382
  player;
@@ -1196,7 +1416,7 @@ var Fe = class {
1196
1416
  scrubbing = !1;
1197
1417
  offFns = [];
1198
1418
  constructor(e) {
1199
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Me, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, Ae(this.doc), this.root.classList.add("havik-player"), this.root.hasAttribute("tabindex") || (this.root.tabIndex = 0), this.root.setAttribute("role", "region"), this.root.setAttribute("aria-label", "Video player");
1419
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Ue, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, Ve(this.doc), this.root.classList.add("havik-player"), this.root.hasAttribute("tabindex") || (this.root.tabIndex = 0), this.root.setAttribute("role", "region"), this.root.setAttribute("aria-label", "Video player");
1200
1420
  let t = (e, t, n) => {
1201
1421
  let r = this.doc.createElement(e);
1202
1422
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1283,7 +1503,7 @@ var Fe = class {
1283
1503
  e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = J("play"), e.addEventListener("click", () => void this.player.play()), t.append(e);
1284
1504
  } else {
1285
1505
  let n = this.doc.createElement("div");
1286
- if (n.className = "havik-status__msg", n.textContent = e === "ended" ? this.endedMessage : e === "waiting" ? "Starting soon…" : this.errorMessage ?? this.lastError?.message ?? "Playback unavailable", t.append(n), e === "error" && this.lastError && !Ne.has(this.lastError.code)) {
1506
+ if (n.className = "havik-status__msg", n.textContent = e === "ended" ? this.endedMessage : e === "waiting" ? "Starting soon…" : this.errorMessage ?? this.lastError?.message ?? "Playback unavailable", t.append(n), e === "error" && this.lastError && !We.has(this.lastError.code)) {
1287
1507
  let e = this.doc.createElement("button");
1288
1508
  e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${J("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), t.append(e);
1289
1509
  }
@@ -1374,17 +1594,17 @@ var Fe = class {
1374
1594
  renderQualityMenu() {
1375
1595
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1376
1596
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1377
- for (let n of e) this.menu.append(this.menuItem(Ie(n), t === n.index, () => this.player.setQuality(n.index)));
1597
+ for (let n of e) this.menu.append(this.menuItem(qe(n), t === n.index, () => this.player.setQuality(n.index)));
1378
1598
  }
1379
1599
  renderAudioMenu() {
1380
1600
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1381
1601
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1382
- for (let t of e) this.menu.append(this.menuItem(Le(t), t.default, () => this.player.setAudioTrack(t.id)));
1602
+ for (let t of e) this.menu.append(this.menuItem(Je(t), t.default, () => this.player.setAudioTrack(t.id)));
1383
1603
  }
1384
1604
  renderCcMenu() {
1385
1605
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1386
1606
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1387
- for (let t of e) this.menu.append(this.menuItem(Re(t), !1, () => this.player.setTextTrack(t.id)));
1607
+ for (let t of e) this.menu.append(this.menuItem(Ye(t), !1, () => this.player.setTextTrack(t.id)));
1388
1608
  }
1389
1609
  onTracksChanged() {
1390
1610
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1490,27 +1710,27 @@ function Y(e) {
1490
1710
  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");
1491
1711
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1492
1712
  }
1493
- function Ie(e) {
1713
+ function qe(e) {
1494
1714
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1495
1715
  }
1496
- function Le(e) {
1716
+ function Je(e) {
1497
1717
  return e.name || e.lang || `Track ${e.id}`;
1498
1718
  }
1499
- function Re(e) {
1719
+ function Ye(e) {
1500
1720
  return e.name || e.lang || `Track ${e.id}`;
1501
1721
  }
1502
1722
  //#endregion
1503
1723
  //#region src/managed/index.ts
1504
- var ze = 6e3, X = 45e3, Be = 2e3, Ve = 3, He = 2e3, Ue = 480 * 1e3;
1505
- async function Z(e) {
1724
+ var Xe = 6e3, X = 45e3, Z = 2e3, Ze = 3, Qe = 2e3, $e = 480 * 1e3;
1725
+ async function Q(e) {
1506
1726
  if (!e.video || typeof e.video.play != "function") throw new t("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1507
1727
  if (!e.baseUrl) throw new t("INTERNAL", 0, "createPlayer: baseUrl is required");
1508
1728
  if (!e.matchUrn) throw new t("INVALID_URN", 0, "createPlayer: matchUrn is required");
1509
1729
  if (!e.credential) throw new t("INTERNAL", 0, "createPlayer: credential is required");
1510
- let n = new We(e);
1730
+ let n = new et(e);
1511
1731
  return await n.start(), n;
1512
1732
  }
1513
- var We = class {
1733
+ var et = class {
1514
1734
  opts;
1515
1735
  abort = new AbortController();
1516
1736
  listeners = {};
@@ -1531,6 +1751,8 @@ var We = class {
1531
1751
  armSseTimer = null;
1532
1752
  armFallbackCtrl = null;
1533
1753
  _lastError = null;
1754
+ analyticsSession = null;
1755
+ detachSeekListener;
1534
1756
  constructor(e) {
1535
1757
  this.opts = e;
1536
1758
  }
@@ -1544,7 +1766,7 @@ var We = class {
1544
1766
  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");
1545
1767
  let e = ++this.loadGen, n = this.resolveOptions();
1546
1768
  try {
1547
- let t = await y(n);
1769
+ let t = await v(n);
1548
1770
  if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) {
1549
1771
  let e = this._lastError;
1550
1772
  throw this.destroy(), e;
@@ -1566,11 +1788,11 @@ var We = class {
1566
1788
  };
1567
1789
  }
1568
1790
  beginArming(e, t) {
1569
- let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1791
+ let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? w(this.opts.baseUrl);
1570
1792
  n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1571
1793
  }
1572
1794
  beginArmingByPolling(e) {
1573
- b({
1795
+ y({
1574
1796
  ...this.resolveOptions(),
1575
1797
  signal: this.abort.signal,
1576
1798
  waitForLive: this.buildWaitConfig(e)
@@ -1604,7 +1826,7 @@ var We = class {
1604
1826
  }
1605
1827
  }
1606
1828
  }, m = () => {
1607
- a || o || f() || (o = !0, b({
1829
+ a || o || f() || (o = !0, y({
1608
1830
  ...this.resolveOptions(),
1609
1831
  signal: s.signal,
1610
1832
  waitForLive: this.buildWaitConfig(e)
@@ -1612,7 +1834,7 @@ var We = class {
1612
1834
  a || f() || s.signal.aborted || (a = !0, this.closeArming(), this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e))));
1613
1835
  }));
1614
1836
  }, h = () => {
1615
- a || f() || y(this.resolveOptions()).then((e) => p(e)).catch((e) => {
1837
+ a || f() || v(this.resolveOptions()).then((e) => p(e)).catch((e) => {
1616
1838
  a || f() || e instanceof t && i(e.code) || (a = !0, this.closeArming(), s.abort(), this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e))));
1617
1839
  });
1618
1840
  }, g = (e) => {
@@ -1620,13 +1842,13 @@ var We = class {
1620
1842
  !a && !f() && m();
1621
1843
  }, e);
1622
1844
  };
1623
- g(ze), this.armingSub = w({
1845
+ g(Xe), this.armingSub = C({
1624
1846
  eventsBaseUrl: n,
1625
1847
  matchUrn: this.opts.matchUrn,
1626
1848
  credential: this.opts.credential,
1627
1849
  onAlive: () => g(X),
1628
1850
  onState: (e) => {
1629
- a || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * Be);
1851
+ a || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * Z);
1630
1852
  },
1631
1853
  onError: () => m()
1632
1854
  });
@@ -1649,7 +1871,7 @@ var We = class {
1649
1871
  let n = await a(this.opts.credential);
1650
1872
  if (this.destroyed || t !== this.loadGen) return;
1651
1873
  this.setState("loading");
1652
- let r = this.createEngine(e, n), i = z(this.opts.video, (e) => {
1874
+ let r = this.createEngine(e, n), i = I(this.opts.video, (e) => {
1653
1875
  this._state === "ended" || this._state === "error" || this.setState(e);
1654
1876
  });
1655
1877
  if (await r.load(), this.destroyed || t !== this.loadGen) {
@@ -1660,7 +1882,32 @@ var We = class {
1660
1882
  i(), r.destroy();
1661
1883
  return;
1662
1884
  }
1663
- this.engine = r, this.detachMedia = i, this.opts.maxBitrate != null && r.setMaxBitrate(this.opts.maxBitrate), this.startStats(), this.scheduleLicenseRefresh(), this.startLiveState(t);
1885
+ this.engine = r, this.detachMedia = i, this.opts.maxBitrate != null && r.setMaxBitrate(this.opts.maxBitrate), this.startStats(), this.scheduleLicenseRefresh(), this.startLiveState(t), this.startAnalytics(e, r);
1886
+ }
1887
+ startAnalytics(e, t) {
1888
+ if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
1889
+ let n = e.analytics?.sid;
1890
+ if (!n) return;
1891
+ let r = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Le(this.opts.baseUrl);
1892
+ if (!r) return;
1893
+ let i = this.opts.video, a = new Re({
1894
+ endpoint: r,
1895
+ sid: n,
1896
+ player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
1897
+ sdkVersion: ze,
1898
+ sample: () => {
1899
+ let e = this.engine?.getStats() ?? P, t = i.buffered.length ? Math.max(0, i.buffered.end(i.buffered.length - 1) - i.currentTime) : 0;
1900
+ return {
1901
+ ...e,
1902
+ bufferedMs: Math.round(t * 1e3),
1903
+ state: this._state,
1904
+ positionMs: Math.round(i.currentTime * 1e3)
1905
+ };
1906
+ }
1907
+ });
1908
+ this.analyticsSession = a;
1909
+ let o = () => a.noteSeek();
1910
+ i.addEventListener("seeking", o), this.detachSeekListener = () => i.removeEventListener("seeking", o), a.start();
1664
1911
  }
1665
1912
  createEngine(e, n) {
1666
1913
  let r = { ...this.opts.hlsConfig };
@@ -1669,7 +1916,7 @@ var We = class {
1669
1916
  video: this.opts.video,
1670
1917
  descriptor: e,
1671
1918
  credential: n,
1672
- deviceId: O(),
1919
+ deviceId: D(),
1673
1920
  userId: this.opts.userId,
1674
1921
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
1675
1922
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -1677,17 +1924,17 @@ var We = class {
1677
1924
  hlsConfig: r,
1678
1925
  emit: (e) => this.onEngineEvent(e)
1679
1926
  }, a = this.opts.engine ?? "auto";
1680
- if (a === "native") return new H(i);
1681
- if (a === "hls") return new U(i);
1682
- if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && B(this.opts.video) && V()) return new H(i);
1683
- if (ge()) return new U(i);
1684
- if (B(this.opts.video)) return new H(i);
1927
+ if (a === "native") return new z(i);
1928
+ if (a === "hls") return new V(i);
1929
+ if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && L(this.opts.video) && R()) return new z(i);
1930
+ if (be()) return new V(i);
1931
+ if (L(this.opts.video)) return new z(i);
1685
1932
  throw new t("INTERNAL", 0, "no supported HLS playback engine in this browser");
1686
1933
  }
1687
1934
  onEngineEvent(e) {
1688
1935
  if (!this.destroyed) switch (e.type) {
1689
1936
  case "ready":
1690
- this.emit("ready"), this.opts.autoplay !== !1 && this.play();
1937
+ this.analyticsSession?.noteReady(), this.emit("ready"), this.opts.autoplay !== !1 && this.play();
1691
1938
  break;
1692
1939
  case "lowLatencyDetected":
1693
1940
  this._descriptor && (this._descriptor.isLowLatency = e.value);
@@ -1705,17 +1952,17 @@ var We = class {
1705
1952
  this.onStreamEnded();
1706
1953
  break;
1707
1954
  case "error":
1708
- e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
1955
+ this.analyticsSession?.noteError(e.error.code, e.error.message, e.fatal, e.error.httpStatus ?? 0, Math.round(this.opts.video.currentTime * 1e3)), e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
1709
1956
  break;
1710
1957
  }
1711
1958
  }
1712
1959
  startLiveState(e) {
1713
1960
  if (this.opts.liveStateEvents === !1) return;
1714
- let n = this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1961
+ let n = this.opts.eventsBaseUrl ?? w(this.opts.baseUrl);
1715
1962
  if (!n) return;
1716
1963
  this.closeLiveState();
1717
1964
  let r = 0, i = !1;
1718
- this.liveStateSub = w({
1965
+ this.liveStateSub = C({
1719
1966
  eventsBaseUrl: n,
1720
1967
  matchUrn: this.opts.matchUrn,
1721
1968
  credential: this.opts.credential,
@@ -1726,7 +1973,7 @@ var We = class {
1726
1973
  r = 0, i = !1;
1727
1974
  },
1728
1975
  onError: () => {
1729
- this.destroyed || e !== this.loadGen || i || (r += 1, r >= Ve && (i = !0, this.emit("warning", new t("NETWORK", 0, `live-state events unreachable at ${n} after ${r} attempts — end-of-stream detection degraded to the HLS staleness watchdog. Check the events host, the api-key's allowed origins, and CSP connect-src.`))));
1976
+ this.destroyed || e !== this.loadGen || i || (r += 1, r >= Ze && (i = !0, this.emit("warning", new t("NETWORK", 0, `live-state events unreachable at ${n} after ${r} attempts — end-of-stream detection degraded to the HLS staleness watchdog. Check the events host, the api-key's allowed origins, and CSP connect-src.`))));
1730
1977
  }
1731
1978
  });
1732
1979
  }
@@ -1748,7 +1995,7 @@ var We = class {
1748
1995
  }
1749
1996
  t.controls = !1;
1750
1997
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
1751
- r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = Pe({
1998
+ r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = Ge({
1752
1999
  root: n,
1753
2000
  video: t,
1754
2001
  player: this,
@@ -1771,9 +2018,11 @@ var We = class {
1771
2018
  }
1772
2019
  startStats() {
1773
2020
  this.stopStats();
1774
- let e = Math.max(500, this.opts.statsIntervalMs ?? He);
2021
+ let e = Math.max(500, this.opts.statsIntervalMs ?? Qe);
1775
2022
  this.statsTimer = setInterval(() => {
1776
- this.engine && this.emit("stats", this.engine.getStats());
2023
+ if (!this.engine) return;
2024
+ let e = this.engine.getStats();
2025
+ (e.bytesLoaded ?? 0) > 0 && this.analyticsSession?.noteFirstBytes(), this.emit("stats", e);
1777
2026
  }, e);
1778
2027
  }
1779
2028
  stopStats() {
@@ -1781,7 +2030,7 @@ var We = class {
1781
2030
  }
1782
2031
  scheduleLicenseRefresh() {
1783
2032
  this.stopRefresh();
1784
- let e = this.opts.licenseRefreshMs ?? Ue;
2033
+ let e = this.opts.licenseRefreshMs ?? $e;
1785
2034
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
1786
2035
  }
1787
2036
  stopRefresh() {
@@ -1790,7 +2039,7 @@ var We = class {
1790
2039
  async refreshLicense() {
1791
2040
  let e = this.engine;
1792
2041
  if (!(this.destroyed || !e)) try {
1793
- let t = await y(this.resolveOptions());
2042
+ let t = await v(this.resolveOptions());
1794
2043
  if (this.destroyed || this.engine !== e) return;
1795
2044
  let n = t.drm?.widevine?.licenseUrl, r = t.drm?.fairplay?.licenseUrl;
1796
2045
  (n || r) && (e.setLicenseUrls({
@@ -1818,7 +2067,7 @@ var We = class {
1818
2067
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
1819
2068
  }
1820
2069
  getStats() {
1821
- return this.engine?.getStats() ?? L;
2070
+ return this.engine?.getStats() ?? P;
1822
2071
  }
1823
2072
  getQualityLevels() {
1824
2073
  return this.engine?.getQualityLevels() ?? [];
@@ -1864,7 +2113,7 @@ var We = class {
1864
2113
  let e = ++this.loadGen;
1865
2114
  this.stopStats(), this.stopRefresh(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null;
1866
2115
  try {
1867
- let t = await b({
2116
+ let t = await y({
1868
2117
  ...this.resolveOptions(),
1869
2118
  waitForLive: this.opts.waitForLive ? this.buildWaitConfig(e) : void 0
1870
2119
  });
@@ -1879,16 +2128,22 @@ var We = class {
1879
2128
  return n.add(t), () => n.delete(t);
1880
2129
  }
1881
2130
  destroy() {
1882
- this.destroyed || (this.destroyed = !0, this.loadGen += 1, this.abort.abort(), this.stopStats(), this.stopRefresh(), this.unmountControls(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.detachPip?.(), this.engine?.destroy(), this.engine = null, this.setState("idle"), this.clearListeners());
2131
+ this.destroyed || (this.destroyed = !0, this.loadGen += 1, this.abort.abort(), this.stopStats(), this.stopRefresh(), this.unmountControls(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.detachPip?.(), this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.engine?.destroy(), this.engine = null, this.setState("idle"), this.clearListeners());
1883
2132
  }
1884
2133
  clearListeners() {
1885
2134
  for (let e of Object.keys(this.listeners)) this.listeners[e]?.clear();
1886
2135
  }
1887
2136
  fail(e) {
1888
- this._lastError = e, this.setState("error"), this.emit("error", e);
2137
+ this._lastError = e, this.setState("error"), this.analyticsSession?.noteFatal(), this.emit("error", e);
1889
2138
  }
1890
2139
  setState(e) {
1891
- if (e !== this._state) switch (this._state = e, this.emit("statechange", e), e) {
2140
+ if (e === this._state) return;
2141
+ let t = this._state;
2142
+ if (this._state = e, this.analyticsSession) {
2143
+ let n = this.opts.video;
2144
+ this.analyticsSession.noteStateChange(t, e, Math.round(n.currentTime * 1e3), this.engine?.getStats().levelHeight), e === "ended" && this.analyticsSession.noteEnded();
2145
+ }
2146
+ switch (this.emit("statechange", e), e) {
1892
2147
  case "playing":
1893
2148
  this.emit("playing");
1894
2149
  break;
@@ -1914,12 +2169,12 @@ var We = class {
1914
2169
  };
1915
2170
  //#endregion
1916
2171
  //#region src/embed/protocol.ts
1917
- function Q(e) {
2172
+ function $(e) {
1918
2173
  return !!e && typeof e == "object" && typeof e.type == "string" && e.type.startsWith("oddin:");
1919
2174
  }
1920
2175
  //#endregion
1921
2176
  //#region src/embed/host.ts
1922
- function $(e) {
2177
+ function tt(e) {
1923
2178
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
1924
2179
  if (!e.src) throw Error("mountEmbed: opts.src is required");
1925
2180
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -1928,7 +2183,7 @@ function $(e) {
1928
2183
  let r = document.createElement("iframe");
1929
2184
  r.src = t.toString(), r.allow = "autoplay; encrypted-media; fullscreen", r.setAttribute("allowfullscreen", ""), r.referrerPolicy = "no-referrer", r.setAttribute("sandbox", e.sandbox ?? "allow-scripts allow-same-origin allow-presentation"), r.style.cssText = "border:0;width:100%;height:100%;display:block", e.container.appendChild(r);
1930
2185
  let i = /* @__PURE__ */ new Set(), a = (t) => {
1931
- if (t.source !== r.contentWindow || t.origin !== n || !Q(t.data)) return;
2186
+ if (t.source !== r.contentWindow || t.origin !== n || !$(t.data)) return;
1932
2187
  let a = t.data;
1933
2188
  a.type === "oddin:ready" && a.protocol !== 1 && console.warn(`havik-player: embed protocol v${a.protocol} != host v1. Pin the embed page version to the SDK version to avoid skew.`), e.onEvent?.(a), i.forEach((e) => e(a));
1934
2189
  };
@@ -1978,8 +2233,8 @@ function $(e) {
1978
2233
  }
1979
2234
  //#endregion
1980
2235
  //#region src/embed/page.ts
1981
- var Ge = /^[A-Za-z0-9:_.-]{1,128}$/;
1982
- function Ke() {
2236
+ var nt = /^[A-Za-z0-9:_.-]{1,128}$/;
2237
+ function rt() {
1983
2238
  let e = window.HAVIK_EMBED_CONFIG;
1984
2239
  if (e) return {
1985
2240
  baseUrl: e.baseUrl ?? "",
@@ -2001,9 +2256,9 @@ function Ke() {
2001
2256
  waitForLive: !0
2002
2257
  };
2003
2258
  }
2004
- async function qe() {
2005
- let e = Ke();
2006
- Ye();
2259
+ async function it() {
2260
+ let e = rt();
2261
+ ot();
2007
2262
  let n = document.createElement("video");
2008
2263
  n.setAttribute("playsinline", ""), n.muted = e.muted ?? !0, n.style.cssText = "width:100%;height:100%;object-fit:contain;background:#000;aspect-ratio:auto", document.body.appendChild(n);
2009
2264
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2058,12 +2313,12 @@ async function qe() {
2058
2313
  code: e.code,
2059
2314
  message: e.message
2060
2315
  })), e.on("autoplayblocked", () => {
2061
- i({ type: "oddin:autoplayblocked" }), Je(n, e);
2316
+ i({ type: "oddin:autoplayblocked" }), at(n, e);
2062
2317
  });
2063
2318
  }, s = async (r) => {
2064
2319
  a?.destroy(), a = null;
2065
2320
  try {
2066
- let t = await Z({
2321
+ let t = await Q({
2067
2322
  video: n,
2068
2323
  baseUrl: e.baseUrl,
2069
2324
  matchUrn: r,
@@ -2124,32 +2379,32 @@ async function qe() {
2124
2379
  a?.retry();
2125
2380
  break;
2126
2381
  case "oddin:load":
2127
- typeof e.matchUrn == "string" && Ge.test(e.matchUrn) && s(e.matchUrn);
2382
+ typeof e.matchUrn == "string" && nt.test(e.matchUrn) && s(e.matchUrn);
2128
2383
  break;
2129
2384
  case "oddin:destroy":
2130
2385
  c();
2131
2386
  break;
2132
2387
  }
2133
2388
  }, u = (e) => {
2134
- !r || e.origin !== r || Q(e.data) && l(e.data);
2389
+ !r || e.origin !== r || $(e.data) && l(e.data);
2135
2390
  };
2136
2391
  r && window.addEventListener("message", u), window.addEventListener("pagehide", c, { once: !0 }), await s(e.matchUrn), i({
2137
2392
  type: "oddin:ready",
2138
2393
  protocol: 1
2139
2394
  });
2140
2395
  }
2141
- function Je(e, t) {
2396
+ function at(e, t) {
2142
2397
  if (document.getElementById("havik-tap")) return;
2143
2398
  let n = document.createElement("button");
2144
2399
  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", () => {
2145
2400
  n.remove(), t.play();
2146
2401
  }), document.body.appendChild(n);
2147
2402
  }
2148
- function Ye() {
2403
+ function ot() {
2149
2404
  let e = document.createElement("style");
2150
2405
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2151
2406
  }
2152
2407
  //#endregion
2153
- export { W as ODDIN_THEME, t as PlaybackError, K as applyTheme, qe as bootEmbed, de as clearDeviceId, Z as createPlayer, T as deriveEventsBaseUrl, P as detectDrmSupport, C as fetchCatalog, se as fetchTournament, le as flattenMatches, O as getDeviceId, I as isLowLatencyManifest, Q as isOddinMessage, pe as licenseRequestHeaders, $ as mountEmbed, v as pollToLive, b as resolveStream, w as subscribeLiveState, ue as watchStatus };
2408
+ export { W as ODDIN_THEME, t as PlaybackError, K as applyTheme, it as bootEmbed, fe as clearDeviceId, Q as createPlayer, w as deriveEventsBaseUrl, j as detectDrmSupport, x as fetchCatalog, ce as fetchTournament, ue as flattenMatches, D as getDeviceId, N as isLowLatencyManifest, $ as isOddinMessage, me as licenseRequestHeaders, tt as mountEmbed, _ as pollToLive, y as resolveStream, C as subscribeLiveState, de as watchStatus };
2154
2409
 
2155
2410
  //# sourceMappingURL=index.js.map