@oddin-gg/havik-player 1.8.1 → 1.8.2

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
@@ -171,7 +171,7 @@ async function y(e) {
171
171
  signal: e.signal
172
172
  });
173
173
  if (!n.ok) throw await ie(n);
174
- return C(await n.json(), e.matchUrn);
174
+ return S(await n.json(), e.matchUrn);
175
175
  }
176
176
  function b(e) {
177
177
  let t = () => y(e);
@@ -193,7 +193,7 @@ async function ie(e) {
193
193
  liveStartsAtMs: c
194
194
  });
195
195
  }
196
- var x = /* @__PURE__ */ new Set([
196
+ var ae = /* @__PURE__ */ new Set([
197
197
  "matchUrn",
198
198
  "protocol",
199
199
  "drmEnabled",
@@ -203,7 +203,7 @@ var x = /* @__PURE__ */ new Set([
203
203
  "liveStartsAt",
204
204
  "expiresAt"
205
205
  ]);
206
- function S(e, n) {
206
+ function x(e, n) {
207
207
  let r;
208
208
  try {
209
209
  r = new URL(n);
@@ -213,29 +213,29 @@ function S(e, n) {
213
213
  if (r.protocol !== "https:" && r.protocol !== "http:") throw new t("INTERNAL", 0, `playback ${e} has a disallowed scheme: ${r.protocol}`);
214
214
  return n;
215
215
  }
216
- function C(e, n) {
216
+ function S(e, n) {
217
217
  if (!e.manifestUrl) throw new t("INTERNAL", 0, "playback response is missing manifestUrl");
218
218
  let r = e.drmEnabled ?? !!e.drm, i = {
219
219
  matchUrn: e.matchUrn ?? n,
220
220
  protocol: "hls",
221
221
  drmEnabled: r,
222
- manifestUrl: S("manifestUrl", e.manifestUrl),
222
+ manifestUrl: x("manifestUrl", e.manifestUrl),
223
223
  serverTime: e.serverTime ?? ""
224
224
  };
225
225
  if (e.liveStartsAt && (i.liveStartsAt = e.liveStartsAt), r && e.drm) {
226
226
  let t = {};
227
- e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: S("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
228
- licenseUrl: S("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
229
- certificateUrl: S("fairplay certificateUrl", e.drm.fairplay.certificateUrl)
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)
230
230
  }), (t.widevine || t.fairplay) && (i.drm = t);
231
231
  }
232
232
  let a = {};
233
- for (let [t, n] of Object.entries(e)) x.has(t) || (a[t] = n);
233
+ for (let [t, n] of Object.entries(e)) ae.has(t) || (a[t] = n);
234
234
  return Object.keys(a).length > 0 && (i.extensions = a), i;
235
235
  }
236
236
  //#endregion
237
237
  //#region src/core/catalog.ts
238
- async function w(e) {
238
+ async function C(e) {
239
239
  let r = new URLSearchParams();
240
240
  e.status && r.set("status", Array.isArray(e.status) ? e.status.join(",") : e.status), e.sport && r.set("sport", e.sport);
241
241
  let i = r.toString(), a = o(e.baseUrl, "/v1/catalog" + (i ? `?${i}` : "")), s = new Headers();
@@ -252,12 +252,12 @@ async function w(e) {
252
252
  };
253
253
  if (!l.ok) throw new t(n(l.status), l.status, `catalog request failed (HTTP ${l.status})`);
254
254
  return {
255
- tournaments: ae(await l.json()),
255
+ tournaments: oe(await l.json()),
256
256
  etag: l.headers.get("etag") ?? void 0,
257
257
  notModified: !1
258
258
  };
259
259
  }
260
- function ae(e) {
260
+ function oe(e) {
261
261
  return (e.tournaments ?? []).map((e) => {
262
262
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
263
263
  return {
@@ -277,7 +277,7 @@ function ae(e) {
277
277
  };
278
278
  });
279
279
  }
280
- async function oe(e) {
280
+ async function se(e) {
281
281
  let r = o(e.baseUrl, `/v1/tournaments/${encodeURIComponent(e.urn)}`), i = new Headers();
282
282
  e.etag && i.set("If-None-Match", e.etag);
283
283
  let a = await c(r, {
@@ -296,13 +296,13 @@ async function oe(e) {
296
296
  };
297
297
  if (!a.ok) throw new t(n(a.status), a.status, `tournament request failed (HTTP ${a.status})`);
298
298
  return {
299
- tournament: se(await a.json()),
299
+ tournament: ce(await a.json()),
300
300
  etag: a.headers.get("etag") ?? void 0,
301
301
  notModified: !1,
302
302
  notFound: !1
303
303
  };
304
304
  }
305
- function se(e) {
305
+ function ce(e) {
306
306
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
307
307
  return {
308
308
  urn: t,
@@ -320,12 +320,12 @@ function se(e) {
320
320
  }))
321
321
  };
322
322
  }
323
- function ce(e) {
323
+ function le(e) {
324
324
  return e.tournaments.flatMap((e) => e.matches);
325
325
  }
326
326
  //#endregion
327
327
  //#region src/core/poll.ts
328
- function le(e) {
328
+ function ue(e) {
329
329
  let r = Math.max(2e3, e.intervalMs ?? 1e4), i = new AbortController(), a, s, l, u = !1, d = () => {
330
330
  u || (a = setTimeout(f, r));
331
331
  }, f = async () => {
@@ -360,7 +360,7 @@ function le(e) {
360
360
  }
361
361
  //#endregion
362
362
  //#region src/core/events.ts
363
- function T(e) {
363
+ function w(e) {
364
364
  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
365
  if (r.signal.aborted) return t();
366
366
  let n = setTimeout(t, e);
@@ -427,7 +427,7 @@ function T(e) {
427
427
  i || (i = !0, r.abort());
428
428
  } };
429
429
  }
430
- function E(e) {
430
+ function T(e) {
431
431
  try {
432
432
  let t = new URL(e);
433
433
  return t.hostname = `events.${t.hostname}`, t.pathname = "", t.search = "", t.origin;
@@ -437,27 +437,27 @@ function E(e) {
437
437
  }
438
438
  //#endregion
439
439
  //#region src/core/drm/deviceId.ts
440
- var D = "havik.player.deviceId", O;
441
- function k() {
442
- if (O) return O;
440
+ var E = "havik.player.deviceId", D;
441
+ function O() {
442
+ if (D) return D;
443
443
  try {
444
- let e = globalThis.localStorage?.getItem(D);
445
- if (e) return O = e, e;
444
+ let e = globalThis.localStorage?.getItem(E);
445
+ if (e) return D = e, e;
446
446
  } catch {}
447
- let e = de();
448
- O = e;
447
+ let e = fe();
448
+ D = e;
449
449
  try {
450
- globalThis.localStorage?.setItem(D, e);
450
+ globalThis.localStorage?.setItem(E, e);
451
451
  } catch {}
452
452
  return e;
453
453
  }
454
- function ue() {
455
- O = void 0;
454
+ function de() {
455
+ D = void 0;
456
456
  try {
457
- globalThis.localStorage?.removeItem(D);
457
+ globalThis.localStorage?.removeItem(E);
458
458
  } catch {}
459
459
  }
460
- function de() {
460
+ function fe() {
461
461
  let e = globalThis.crypto;
462
462
  if (e && typeof e.randomUUID == "function") return e.randomUUID();
463
463
  let t = /* @__PURE__ */ new Uint8Array(16);
@@ -469,22 +469,22 @@ function de() {
469
469
  }
470
470
  //#endregion
471
471
  //#region src/core/drm/headers.ts
472
- function fe(e, t, n = {}) {
472
+ function pe(e, t, n = {}) {
473
473
  let r = {
474
474
  "x-api-key": t.apiKey,
475
475
  "X-Match-Urn": e.matchUrn,
476
- "X-Device-Id": n.deviceId ?? k(),
476
+ "X-Device-Id": n.deviceId ?? O(),
477
477
  "Content-Type": "application/octet-stream"
478
478
  };
479
479
  return n.userId && (r["X-User-Id"] = n.userId), r;
480
480
  }
481
481
  //#endregion
482
482
  //#region src/core/drm/support.ts
483
- var pe = 5e3, A = "video/mp4; codecs=\"avc1.42E01E\"", j = "audio/mp4; codecs=\"mp4a.40.2\"", M = {
483
+ var me = 5e3, k = "video/mp4; codecs=\"avc1.42E01E\"", A = "audio/mp4; codecs=\"mp4a.40.2\"", j = {
484
484
  widevine: ["com.widevine.alpha"],
485
485
  fairplay: ["com.apple.fps"]
486
486
  };
487
- function N(e) {
487
+ function M(e) {
488
488
  return e === "fairplay" ? [{
489
489
  initDataTypes: [
490
490
  "sinf",
@@ -492,30 +492,30 @@ function N(e) {
492
492
  "cenc"
493
493
  ],
494
494
  videoCapabilities: [{
495
- contentType: A,
495
+ contentType: k,
496
496
  robustness: "",
497
497
  encryptionScheme: "cbcs"
498
498
  }],
499
499
  audioCapabilities: [{
500
- contentType: j,
500
+ contentType: A,
501
501
  robustness: "",
502
502
  encryptionScheme: "cbcs"
503
503
  }]
504
504
  }] : [{
505
505
  initDataTypes: ["cenc"],
506
506
  videoCapabilities: [{
507
- contentType: A,
507
+ contentType: k,
508
508
  robustness: "SW_SECURE_DECODE",
509
509
  encryptionScheme: "cbcs"
510
510
  }],
511
511
  audioCapabilities: [{
512
- contentType: j,
512
+ contentType: A,
513
513
  robustness: "SW_SECURE_CRYPTO",
514
514
  encryptionScheme: "cbcs"
515
515
  }]
516
516
  }];
517
517
  }
518
- function P(e, t) {
518
+ function N(e, t) {
519
519
  return new Promise((n, r) => {
520
520
  let i = setTimeout(() => r(/* @__PURE__ */ Error("probe-timeout")), t);
521
521
  e.then((e) => {
@@ -525,8 +525,8 @@ function P(e, t) {
525
525
  });
526
526
  });
527
527
  }
528
- async function F(e = {}) {
529
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? pe, r = {};
528
+ async function P(e = {}) {
529
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? me, r = {};
530
530
  if (typeof window < "u" && window.isSecureContext === !1) return {
531
531
  verdict: "insecure-context",
532
532
  detail: r
@@ -538,7 +538,7 @@ async function F(e = {}) {
538
538
  };
539
539
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
540
540
  for (let e of t) {
541
- let t = M[e];
541
+ let t = j[e];
542
542
  if (!t) {
543
543
  r[e] = "unknown system";
544
544
  continue;
@@ -549,7 +549,7 @@ async function F(e = {}) {
549
549
  break;
550
550
  }
551
551
  try {
552
- return await P((await P(i(n, N(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
552
+ return await N((await N(i(n, M(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
553
553
  verdict: "supported",
554
554
  system: e,
555
555
  detail: r
@@ -573,17 +573,17 @@ async function F(e = {}) {
573
573
  }
574
574
  //#endregion
575
575
  //#region src/core/manifest.ts
576
- var I = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
577
- function L(e) {
578
- return I.test(e);
576
+ var F = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
577
+ function I(e) {
578
+ return F.test(e);
579
579
  }
580
580
  //#endregion
581
581
  //#region src/core/stats.ts
582
- var R = { droppedFrames: 0 };
583
- function z(e) {
582
+ var L = { droppedFrames: 0 };
583
+ function R(e) {
584
584
  return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
585
585
  }
586
- function B(e, t) {
586
+ function z(e, t) {
587
587
  let n = [
588
588
  ["playing", () => t("playing")],
589
589
  ["waiting", () => t("buffering")],
@@ -600,7 +600,7 @@ function B(e, t) {
600
600
  }
601
601
  //#endregion
602
602
  //#region src/core/engine/native.ts
603
- function me(e) {
603
+ function he(e) {
604
604
  try {
605
605
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
606
606
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -608,9 +608,14 @@ function me(e) {
608
608
  return;
609
609
  }
610
610
  }
611
- function V(e) {
611
+ function B(e) {
612
612
  return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
613
613
  }
614
+ function V() {
615
+ if (typeof window > "u") return !1;
616
+ let e = "WebKitMediaKeys" in window || typeof HTMLMediaElement < "u" && "webkitSetMediaKeys" in HTMLMediaElement.prototype, t = typeof navigator < "u" && /Apple/i.test(navigator.vendor ?? "");
617
+ return e || t;
618
+ }
614
619
  var H = class {
615
620
  name = "native";
616
621
  deps;
@@ -654,7 +659,7 @@ var H = class {
654
659
  });
655
660
  this.onEncrypted = (t) => {
656
661
  let o = t;
657
- !o.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = me(o.initData), (async () => {
662
+ !o.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = he(o.initData), (async () => {
658
663
  try {
659
664
  let t = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
660
665
  initDataTypes: [o.initDataType],
@@ -746,7 +751,7 @@ var H = class {
746
751
  e.seekable.length > 0 && (e.currentTime = e.seekable.end(e.seekable.length - 1));
747
752
  }
748
753
  getStats() {
749
- let e = this.deps.video, t = { droppedFrames: z(e) };
754
+ let e = this.deps.video, t = { droppedFrames: R(e) };
750
755
  return e.duration === Infinity && (t.isLive = !0, e.seekable.length > 0 && (t.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), t;
751
756
  }
752
757
  stop() {
@@ -768,20 +773,20 @@ var H = class {
768
773
  };
769
774
  //#endregion
770
775
  //#region src/core/engine/hls.ts
771
- function he() {
776
+ function ge() {
772
777
  return e.isSupported();
773
778
  }
774
- var ge = 3, _e = 5, ve = 3, ye = 3e4, be = 12e3, xe = 3e3, Se = 6e3, Ce = 2, we = 9, Te = .5;
775
- function Ee(e) {
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) {
776
781
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
777
782
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
778
783
  return;
779
784
  }
780
- e.liveSyncDuration ??= Ce;
781
- let t = Math.max(12, e.liveSyncDuration + we);
782
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), De(e, e.liveSyncDuration);
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);
783
788
  }
784
- function De(e, t) {
789
+ function Oe(e, t) {
785
790
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
786
791
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
787
792
  }
@@ -811,7 +816,7 @@ var U = class {
811
816
  this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
812
817
  }
813
818
  async load() {
814
- let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps, a = V(r), o = !!e.drm?.widevine?.licenseUrl && !a;
819
+ let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps, a = B(r) && V(), o = !!e.drm?.widevine?.licenseUrl && !a;
815
820
  if (e.drmEnabled && !o) {
816
821
  i({
817
822
  type: "error",
@@ -835,7 +840,7 @@ var U = class {
835
840
  let t = e.liveSyncPosition;
836
841
  if (typeof t != "number" || !Number.isFinite(t)) return;
837
842
  let { video: n } = this.deps;
838
- n.currentTime <= t + Te || (n.currentTime = t);
843
+ n.currentTime <= t + Ee || (n.currentTime = t);
839
844
  }
840
845
  startHls() {
841
846
  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 = {
@@ -862,14 +867,14 @@ var U = class {
862
867
  } : {},
863
868
  ...c
864
869
  };
865
- Ee(d);
870
+ De(d);
866
871
  let f = new e(d);
867
872
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
868
873
  this.mediaRecoveries = 0, this.networkRecoveries = 0, l({ type: "ready" });
869
874
  }), f.on(e.Events.FRAG_BUFFERED, () => {
870
875
  (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
871
876
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.recoveryResetTimer = null;
872
- }, ye));
877
+ }, be));
873
878
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => {
874
879
  if (this.detectedLowLatency) return;
875
880
  let n = t.details;
@@ -877,7 +882,7 @@ var U = class {
877
882
  type: "lowLatencyDetected",
878
883
  value: !0
879
884
  }));
880
- }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => this.checkEndStale(), xe), f.on(e.Events.LEVEL_SWITCHED, (e, t) => l({
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({
881
886
  type: "qualitychange",
882
887
  index: t.level
883
888
  })), f.on(e.Events.AUDIO_TRACK_SWITCHED, (e, t) => l({
@@ -946,7 +951,7 @@ var U = class {
946
951
  }
947
952
  getStats() {
948
953
  let e = this.hls, t = {
949
- droppedFrames: z(this.deps.video),
954
+ droppedFrames: R(this.deps.video),
950
955
  lowLatency: this.detectedLowLatency
951
956
  };
952
957
  if (e) {
@@ -975,7 +980,7 @@ var U = class {
975
980
  return;
976
981
  }
977
982
  let e = Date.now() - this.lastAdvanceMs;
978
- !this.chaseSuspended && e > Se && this.suspendChasing(), e > be && this.emitEnded("stalled");
983
+ !this.chaseSuspended && e > Ce && this.suspendChasing(), e > xe && this.emitEnded("stalled");
979
984
  }
980
985
  suspendChasing() {
981
986
  let e = this.hls;
@@ -1017,7 +1022,7 @@ var U = class {
1017
1022
  scheduleLicenseRetry(t) {
1018
1023
  if (!(t.details === e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED || t.details === e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED)) return !1;
1019
1024
  let n = t.response?.code ?? 0;
1020
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= ve) return !1;
1025
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= ye) return !1;
1021
1026
  this.licenseRecoveries += 1;
1022
1027
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1023
1028
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1045,7 +1050,7 @@ var U = class {
1045
1050
  return;
1046
1051
  }
1047
1052
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1048
- if (this.networkRecoveries < _e) {
1053
+ if (this.networkRecoveries < ve) {
1049
1054
  this.networkRecoveries += 1;
1050
1055
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1051
1056
  this.networkRetryTimer = setTimeout(() => {
@@ -1061,7 +1066,7 @@ var U = class {
1061
1066
  return;
1062
1067
  }
1063
1068
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1064
- if (this.mediaRecoveries < ge) {
1069
+ if (this.mediaRecoveries < _e) {
1065
1070
  this.mediaRecoveries += 1, n.recoverMediaError();
1066
1071
  return;
1067
1072
  }
@@ -1116,15 +1121,15 @@ function K(e, t) {
1116
1121
  }
1117
1122
  //#endregion
1118
1123
  //#region src/ui/styles.ts
1119
- var q = "havik-player-styles", Oe = "\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\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";
1120
- function ke(e = document) {
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\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) {
1121
1126
  if (e.getElementById(q)) return;
1122
1127
  let t = e.createElement("style");
1123
- t.id = q, t.textContent = Oe, e.head.appendChild(t);
1128
+ t.id = q, t.textContent = ke, e.head.appendChild(t);
1124
1129
  }
1125
1130
  //#endregion
1126
1131
  //#region src/ui/icons.ts
1127
- var Ae = {
1132
+ var je = {
1128
1133
  play: "<path d=\"M8 5v14l11-7z\"/>",
1129
1134
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1130
1135
  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\"/>",
@@ -1138,20 +1143,20 @@ var Ae = {
1138
1143
  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\"/>"
1139
1144
  };
1140
1145
  function J(e) {
1141
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Ae[e]}</svg>`;
1146
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${je[e]}</svg>`;
1142
1147
  }
1143
1148
  //#endregion
1144
1149
  //#region src/ui/controls.ts
1145
- var je = 3e3, Me = /* @__PURE__ */ new Set([
1150
+ var Me = 3e3, Ne = /* @__PURE__ */ new Set([
1146
1151
  "GONE",
1147
1152
  "NOT_FOUND",
1148
1153
  "INVALID_URN",
1149
1154
  "UNAUTHORIZED"
1150
1155
  ]);
1151
- function Ne(e) {
1152
- return new Pe(e);
1156
+ function Pe(e) {
1157
+ return new Fe(e);
1153
1158
  }
1154
- var Pe = class {
1159
+ var Fe = class {
1155
1160
  root;
1156
1161
  video;
1157
1162
  player;
@@ -1191,7 +1196,7 @@ var Pe = class {
1191
1196
  scrubbing = !1;
1192
1197
  offFns = [];
1193
1198
  constructor(e) {
1194
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? je, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, ke(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");
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");
1195
1200
  let t = (e, t, n) => {
1196
1201
  let r = this.doc.createElement(e);
1197
1202
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1278,7 +1283,7 @@ var Pe = class {
1278
1283
  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);
1279
1284
  } else {
1280
1285
  let n = this.doc.createElement("div");
1281
- 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 && !Me.has(this.lastError.code)) {
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)) {
1282
1287
  let e = this.doc.createElement("button");
1283
1288
  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);
1284
1289
  }
@@ -1369,17 +1374,17 @@ var Pe = class {
1369
1374
  renderQualityMenu() {
1370
1375
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1371
1376
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1372
- for (let n of e) this.menu.append(this.menuItem(Fe(n), t === n.index, () => this.player.setQuality(n.index)));
1377
+ for (let n of e) this.menu.append(this.menuItem(Ie(n), t === n.index, () => this.player.setQuality(n.index)));
1373
1378
  }
1374
1379
  renderAudioMenu() {
1375
1380
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1376
1381
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1377
- for (let t of e) this.menu.append(this.menuItem(Ie(t), t.default, () => this.player.setAudioTrack(t.id)));
1382
+ for (let t of e) this.menu.append(this.menuItem(Le(t), t.default, () => this.player.setAudioTrack(t.id)));
1378
1383
  }
1379
1384
  renderCcMenu() {
1380
1385
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1381
1386
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1382
- for (let t of e) this.menu.append(this.menuItem(Le(t), !1, () => this.player.setTextTrack(t.id)));
1387
+ for (let t of e) this.menu.append(this.menuItem(Re(t), !1, () => this.player.setTextTrack(t.id)));
1383
1388
  }
1384
1389
  onTracksChanged() {
1385
1390
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1485,27 +1490,27 @@ function Y(e) {
1485
1490
  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");
1486
1491
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1487
1492
  }
1488
- function Fe(e) {
1493
+ function Ie(e) {
1489
1494
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1490
1495
  }
1491
- function Ie(e) {
1496
+ function Le(e) {
1492
1497
  return e.name || e.lang || `Track ${e.id}`;
1493
1498
  }
1494
- function Le(e) {
1499
+ function Re(e) {
1495
1500
  return e.name || e.lang || `Track ${e.id}`;
1496
1501
  }
1497
1502
  //#endregion
1498
1503
  //#region src/managed/index.ts
1499
- var Re = 6e3, X = 45e3, ze = 2e3, Z = 3, Be = 2e3, Ve = 480 * 1e3;
1500
- async function Q(e) {
1504
+ var ze = 6e3, X = 45e3, Be = 2e3, Ve = 3, He = 2e3, Ue = 480 * 1e3;
1505
+ async function Z(e) {
1501
1506
  if (!e.video || typeof e.video.play != "function") throw new t("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1502
1507
  if (!e.baseUrl) throw new t("INTERNAL", 0, "createPlayer: baseUrl is required");
1503
1508
  if (!e.matchUrn) throw new t("INVALID_URN", 0, "createPlayer: matchUrn is required");
1504
1509
  if (!e.credential) throw new t("INTERNAL", 0, "createPlayer: credential is required");
1505
- let n = new He(e);
1510
+ let n = new We(e);
1506
1511
  return await n.start(), n;
1507
1512
  }
1508
- var He = class {
1513
+ var We = class {
1509
1514
  opts;
1510
1515
  abort = new AbortController();
1511
1516
  listeners = {};
@@ -1561,7 +1566,7 @@ var He = class {
1561
1566
  };
1562
1567
  }
1563
1568
  beginArming(e, t) {
1564
- let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? E(this.opts.baseUrl);
1569
+ let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1565
1570
  n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1566
1571
  }
1567
1572
  beginArmingByPolling(e) {
@@ -1615,13 +1620,13 @@ var He = class {
1615
1620
  !a && !f() && m();
1616
1621
  }, e);
1617
1622
  };
1618
- g(Re), this.armingSub = T({
1623
+ g(ze), this.armingSub = w({
1619
1624
  eventsBaseUrl: n,
1620
1625
  matchUrn: this.opts.matchUrn,
1621
1626
  credential: this.opts.credential,
1622
1627
  onAlive: () => g(X),
1623
1628
  onState: (e) => {
1624
- a || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * ze);
1629
+ a || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * Be);
1625
1630
  },
1626
1631
  onError: () => m()
1627
1632
  });
@@ -1644,7 +1649,7 @@ var He = class {
1644
1649
  let n = await a(this.opts.credential);
1645
1650
  if (this.destroyed || t !== this.loadGen) return;
1646
1651
  this.setState("loading");
1647
- let r = this.createEngine(e, n), i = B(this.opts.video, (e) => {
1652
+ let r = this.createEngine(e, n), i = z(this.opts.video, (e) => {
1648
1653
  this._state === "ended" || this._state === "error" || this.setState(e);
1649
1654
  });
1650
1655
  if (await r.load(), this.destroyed || t !== this.loadGen) {
@@ -1664,7 +1669,7 @@ var He = class {
1664
1669
  video: this.opts.video,
1665
1670
  descriptor: e,
1666
1671
  credential: n,
1667
- deviceId: k(),
1672
+ deviceId: O(),
1668
1673
  userId: this.opts.userId,
1669
1674
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
1670
1675
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -1674,9 +1679,9 @@ var He = class {
1674
1679
  }, a = this.opts.engine ?? "auto";
1675
1680
  if (a === "native") return new H(i);
1676
1681
  if (a === "hls") return new U(i);
1677
- if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && V(this.opts.video)) return new H(i);
1678
- if (he()) return new U(i);
1679
- if (V(this.opts.video)) return new H(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);
1680
1685
  throw new t("INTERNAL", 0, "no supported HLS playback engine in this browser");
1681
1686
  }
1682
1687
  onEngineEvent(e) {
@@ -1706,11 +1711,11 @@ var He = class {
1706
1711
  }
1707
1712
  startLiveState(e) {
1708
1713
  if (this.opts.liveStateEvents === !1) return;
1709
- let n = this.opts.eventsBaseUrl ?? E(this.opts.baseUrl);
1714
+ let n = this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1710
1715
  if (!n) return;
1711
1716
  this.closeLiveState();
1712
1717
  let r = 0, i = !1;
1713
- this.liveStateSub = T({
1718
+ this.liveStateSub = w({
1714
1719
  eventsBaseUrl: n,
1715
1720
  matchUrn: this.opts.matchUrn,
1716
1721
  credential: this.opts.credential,
@@ -1721,7 +1726,7 @@ var He = class {
1721
1726
  r = 0, i = !1;
1722
1727
  },
1723
1728
  onError: () => {
1724
- this.destroyed || e !== this.loadGen || i || (r += 1, r >= Z && (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.`))));
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.`))));
1725
1730
  }
1726
1731
  });
1727
1732
  }
@@ -1743,7 +1748,7 @@ var He = class {
1743
1748
  }
1744
1749
  t.controls = !1;
1745
1750
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
1746
- r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = Ne({
1751
+ r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = Pe({
1747
1752
  root: n,
1748
1753
  video: t,
1749
1754
  player: this,
@@ -1766,7 +1771,7 @@ var He = class {
1766
1771
  }
1767
1772
  startStats() {
1768
1773
  this.stopStats();
1769
- let e = Math.max(500, this.opts.statsIntervalMs ?? Be);
1774
+ let e = Math.max(500, this.opts.statsIntervalMs ?? He);
1770
1775
  this.statsTimer = setInterval(() => {
1771
1776
  this.engine && this.emit("stats", this.engine.getStats());
1772
1777
  }, e);
@@ -1776,7 +1781,7 @@ var He = class {
1776
1781
  }
1777
1782
  scheduleLicenseRefresh() {
1778
1783
  this.stopRefresh();
1779
- let e = this.opts.licenseRefreshMs ?? Ve;
1784
+ let e = this.opts.licenseRefreshMs ?? Ue;
1780
1785
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
1781
1786
  }
1782
1787
  stopRefresh() {
@@ -1813,7 +1818,7 @@ var He = class {
1813
1818
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
1814
1819
  }
1815
1820
  getStats() {
1816
- return this.engine?.getStats() ?? R;
1821
+ return this.engine?.getStats() ?? L;
1817
1822
  }
1818
1823
  getQualityLevels() {
1819
1824
  return this.engine?.getQualityLevels() ?? [];
@@ -1909,12 +1914,12 @@ var He = class {
1909
1914
  };
1910
1915
  //#endregion
1911
1916
  //#region src/embed/protocol.ts
1912
- function $(e) {
1917
+ function Q(e) {
1913
1918
  return !!e && typeof e == "object" && typeof e.type == "string" && e.type.startsWith("oddin:");
1914
1919
  }
1915
1920
  //#endregion
1916
1921
  //#region src/embed/host.ts
1917
- function Ue(e) {
1922
+ function $(e) {
1918
1923
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
1919
1924
  if (!e.src) throw Error("mountEmbed: opts.src is required");
1920
1925
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -1923,7 +1928,7 @@ function Ue(e) {
1923
1928
  let r = document.createElement("iframe");
1924
1929
  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);
1925
1930
  let i = /* @__PURE__ */ new Set(), a = (t) => {
1926
- if (t.source !== r.contentWindow || t.origin !== n || !$(t.data)) return;
1931
+ if (t.source !== r.contentWindow || t.origin !== n || !Q(t.data)) return;
1927
1932
  let a = t.data;
1928
1933
  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));
1929
1934
  };
@@ -1973,8 +1978,8 @@ function Ue(e) {
1973
1978
  }
1974
1979
  //#endregion
1975
1980
  //#region src/embed/page.ts
1976
- var We = /^[A-Za-z0-9:_.-]{1,128}$/;
1977
- function Ge() {
1981
+ var Ge = /^[A-Za-z0-9:_.-]{1,128}$/;
1982
+ function Ke() {
1978
1983
  let e = window.HAVIK_EMBED_CONFIG;
1979
1984
  if (e) return {
1980
1985
  baseUrl: e.baseUrl ?? "",
@@ -1996,9 +2001,9 @@ function Ge() {
1996
2001
  waitForLive: !0
1997
2002
  };
1998
2003
  }
1999
- async function Ke() {
2000
- let e = Ge();
2001
- Je();
2004
+ async function qe() {
2005
+ let e = Ke();
2006
+ Ye();
2002
2007
  let n = document.createElement("video");
2003
2008
  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);
2004
2009
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2053,12 +2058,12 @@ async function Ke() {
2053
2058
  code: e.code,
2054
2059
  message: e.message
2055
2060
  })), e.on("autoplayblocked", () => {
2056
- i({ type: "oddin:autoplayblocked" }), qe(n, e);
2061
+ i({ type: "oddin:autoplayblocked" }), Je(n, e);
2057
2062
  });
2058
2063
  }, s = async (r) => {
2059
2064
  a?.destroy(), a = null;
2060
2065
  try {
2061
- let t = await Q({
2066
+ let t = await Z({
2062
2067
  video: n,
2063
2068
  baseUrl: e.baseUrl,
2064
2069
  matchUrn: r,
@@ -2119,32 +2124,32 @@ async function Ke() {
2119
2124
  a?.retry();
2120
2125
  break;
2121
2126
  case "oddin:load":
2122
- typeof e.matchUrn == "string" && We.test(e.matchUrn) && s(e.matchUrn);
2127
+ typeof e.matchUrn == "string" && Ge.test(e.matchUrn) && s(e.matchUrn);
2123
2128
  break;
2124
2129
  case "oddin:destroy":
2125
2130
  c();
2126
2131
  break;
2127
2132
  }
2128
2133
  }, u = (e) => {
2129
- !r || e.origin !== r || $(e.data) && l(e.data);
2134
+ !r || e.origin !== r || Q(e.data) && l(e.data);
2130
2135
  };
2131
2136
  r && window.addEventListener("message", u), window.addEventListener("pagehide", c, { once: !0 }), await s(e.matchUrn), i({
2132
2137
  type: "oddin:ready",
2133
2138
  protocol: 1
2134
2139
  });
2135
2140
  }
2136
- function qe(e, t) {
2141
+ function Je(e, t) {
2137
2142
  if (document.getElementById("havik-tap")) return;
2138
2143
  let n = document.createElement("button");
2139
2144
  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", () => {
2140
2145
  n.remove(), t.play();
2141
2146
  }), document.body.appendChild(n);
2142
2147
  }
2143
- function Je() {
2148
+ function Ye() {
2144
2149
  let e = document.createElement("style");
2145
2150
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2146
2151
  }
2147
2152
  //#endregion
2148
- export { W as ODDIN_THEME, t as PlaybackError, K as applyTheme, Ke as bootEmbed, ue as clearDeviceId, Q as createPlayer, E as deriveEventsBaseUrl, F as detectDrmSupport, w as fetchCatalog, oe as fetchTournament, ce as flattenMatches, k as getDeviceId, L as isLowLatencyManifest, $ as isOddinMessage, fe as licenseRequestHeaders, Ue as mountEmbed, v as pollToLive, b as resolveStream, T as subscribeLiveState, le as watchStatus };
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 };
2149
2154
 
2150
2155
  //# sourceMappingURL=index.js.map