@oddin-gg/havik-player 1.8.0 → 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.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:rgba(255,255,255,.3);border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:70px;opacity:1}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-moz-range-thumb{width:12px;height:12px;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);
@@ -1244,7 +1249,7 @@ var Pe = class {
1244
1249
  e.target === this.overlay && this.togglePlay();
1245
1250
  }), this.on(this.muteBtn, "click", () => this.player.setMuted(!e.muted)), this.on(this.volSlider, "input", () => {
1246
1251
  let t = Number(this.volSlider.value);
1247
- this.player.setVolume(t), t > 0 && e.muted && this.player.setMuted(!1);
1252
+ this.setVolumeFill(t), this.player.setVolume(t), t > 0 && e.muted && this.player.setMuted(!1);
1248
1253
  }), this.on(this.liveGo, "click", () => this.player.seekToLive()), this.on(this.pipBtn, "click", () => void this.togglePip()), this.on(this.fsBtn, "click", () => void this.toggleFullscreen()), this.on(this.ccBtn, "click", () => this.toggleMenu("cc")), this.on(this.audioBtn, "click", () => this.toggleMenu("audio")), this.on(this.qualityBtn, "click", () => this.toggleMenu("quality")), this.on(this.seek, "pointerdown", (e) => this.onSeekDown(e)), this.on(this.seek, "keydown", (e) => this.onSeekKey(e)), this.on(e, "play", () => this.syncPlayPause()), this.on(e, "pause", () => this.syncPlayPause()), this.on(e, "volumechange", () => this.syncVolume()), this.on(e, "timeupdate", () => this.syncProgress()), this.on(e, "progress", () => this.syncProgress()), this.on(e, "durationchange", () => this.syncProgress()), this.offFns.push(this.player.on("statechange", () => this.syncState())), this.offFns.push(this.player.on("ready", () => this.refreshTracks())), this.offFns.push(this.player.on("qualitychange", () => this.onTracksChanged())), this.offFns.push(this.player.on("audiotrackchange", () => this.onTracksChanged())), this.offFns.push(this.player.on("texttrackchange", () => this.onTracksChanged())), this.offFns.push(this.player.on("pipchange", (e) => this.syncPip(e))), this.offFns.push(this.player.on("stats", () => this.syncLive())), this.offFns.push(this.player.on("error", (e) => {
1249
1254
  this.lastError = e, this.statusKind = null, this.syncState();
1250
1255
  })), this.on(this.root, "pointermove", () => this.poke()), this.on(this.root, "pointerleave", () => this.scheduleHide(0)), this.on(this.root, "focusin", () => this.poke()), this.on(this.root, "keydown", (e) => this.onKey(e)), this.on(this.doc, "pointerdown", (e) => this.onDocPointer(e), { capture: !0 }), this.on(this.doc, "fullscreenchange", () => this.syncFullscreen()), this.syncState();
@@ -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
  }
@@ -1287,7 +1292,12 @@ var Pe = class {
1287
1292
  }
1288
1293
  syncVolume() {
1289
1294
  let e = this.video, t = e.muted || e.volume === 0;
1290
- this.setIcon(this.muteBtn, t ? "volumeMute" : "volumeHigh"), this.muteBtn.setAttribute("aria-label", t ? "Unmute" : "Mute"), this.volSlider.value = String(t ? 0 : e.volume);
1295
+ this.setIcon(this.muteBtn, t ? "volumeMute" : "volumeHigh"), this.muteBtn.setAttribute("aria-label", t ? "Unmute" : "Mute");
1296
+ let n = t ? 0 : e.volume;
1297
+ this.volSlider.value = String(n), this.setVolumeFill(n);
1298
+ }
1299
+ setVolumeFill(e) {
1300
+ this.volSlider.style.setProperty("--havik-vol", String(Math.min(1, Math.max(0, e))));
1291
1301
  }
1292
1302
  seekableRange() {
1293
1303
  let e = this.video, t, n;
@@ -1364,17 +1374,17 @@ var Pe = class {
1364
1374
  renderQualityMenu() {
1365
1375
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1366
1376
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1367
- 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)));
1368
1378
  }
1369
1379
  renderAudioMenu() {
1370
1380
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1371
1381
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1372
- 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)));
1373
1383
  }
1374
1384
  renderCcMenu() {
1375
1385
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1376
1386
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1377
- 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)));
1378
1388
  }
1379
1389
  onTracksChanged() {
1380
1390
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1480,27 +1490,27 @@ function Y(e) {
1480
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");
1481
1491
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1482
1492
  }
1483
- function Fe(e) {
1493
+ function Ie(e) {
1484
1494
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1485
1495
  }
1486
- function Ie(e) {
1496
+ function Le(e) {
1487
1497
  return e.name || e.lang || `Track ${e.id}`;
1488
1498
  }
1489
- function Le(e) {
1499
+ function Re(e) {
1490
1500
  return e.name || e.lang || `Track ${e.id}`;
1491
1501
  }
1492
1502
  //#endregion
1493
1503
  //#region src/managed/index.ts
1494
- var Re = 6e3, X = 45e3, ze = 2e3, Z = 3, Be = 2e3, Ve = 480 * 1e3;
1495
- async function Q(e) {
1504
+ var ze = 6e3, X = 45e3, Be = 2e3, Ve = 3, He = 2e3, Ue = 480 * 1e3;
1505
+ async function Z(e) {
1496
1506
  if (!e.video || typeof e.video.play != "function") throw new t("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1497
1507
  if (!e.baseUrl) throw new t("INTERNAL", 0, "createPlayer: baseUrl is required");
1498
1508
  if (!e.matchUrn) throw new t("INVALID_URN", 0, "createPlayer: matchUrn is required");
1499
1509
  if (!e.credential) throw new t("INTERNAL", 0, "createPlayer: credential is required");
1500
- let n = new He(e);
1510
+ let n = new We(e);
1501
1511
  return await n.start(), n;
1502
1512
  }
1503
- var He = class {
1513
+ var We = class {
1504
1514
  opts;
1505
1515
  abort = new AbortController();
1506
1516
  listeners = {};
@@ -1556,7 +1566,7 @@ var He = class {
1556
1566
  };
1557
1567
  }
1558
1568
  beginArming(e, t) {
1559
- 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);
1560
1570
  n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1561
1571
  }
1562
1572
  beginArmingByPolling(e) {
@@ -1610,13 +1620,13 @@ var He = class {
1610
1620
  !a && !f() && m();
1611
1621
  }, e);
1612
1622
  };
1613
- g(Re), this.armingSub = T({
1623
+ g(ze), this.armingSub = w({
1614
1624
  eventsBaseUrl: n,
1615
1625
  matchUrn: this.opts.matchUrn,
1616
1626
  credential: this.opts.credential,
1617
1627
  onAlive: () => g(X),
1618
1628
  onState: (e) => {
1619
- 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);
1620
1630
  },
1621
1631
  onError: () => m()
1622
1632
  });
@@ -1639,7 +1649,7 @@ var He = class {
1639
1649
  let n = await a(this.opts.credential);
1640
1650
  if (this.destroyed || t !== this.loadGen) return;
1641
1651
  this.setState("loading");
1642
- 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) => {
1643
1653
  this._state === "ended" || this._state === "error" || this.setState(e);
1644
1654
  });
1645
1655
  if (await r.load(), this.destroyed || t !== this.loadGen) {
@@ -1659,7 +1669,7 @@ var He = class {
1659
1669
  video: this.opts.video,
1660
1670
  descriptor: e,
1661
1671
  credential: n,
1662
- deviceId: k(),
1672
+ deviceId: O(),
1663
1673
  userId: this.opts.userId,
1664
1674
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
1665
1675
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -1669,9 +1679,9 @@ var He = class {
1669
1679
  }, a = this.opts.engine ?? "auto";
1670
1680
  if (a === "native") return new H(i);
1671
1681
  if (a === "hls") return new U(i);
1672
- if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && V(this.opts.video)) return new H(i);
1673
- if (he()) return new U(i);
1674
- 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);
1675
1685
  throw new t("INTERNAL", 0, "no supported HLS playback engine in this browser");
1676
1686
  }
1677
1687
  onEngineEvent(e) {
@@ -1701,11 +1711,11 @@ var He = class {
1701
1711
  }
1702
1712
  startLiveState(e) {
1703
1713
  if (this.opts.liveStateEvents === !1) return;
1704
- let n = this.opts.eventsBaseUrl ?? E(this.opts.baseUrl);
1714
+ let n = this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1705
1715
  if (!n) return;
1706
1716
  this.closeLiveState();
1707
1717
  let r = 0, i = !1;
1708
- this.liveStateSub = T({
1718
+ this.liveStateSub = w({
1709
1719
  eventsBaseUrl: n,
1710
1720
  matchUrn: this.opts.matchUrn,
1711
1721
  credential: this.opts.credential,
@@ -1716,7 +1726,7 @@ var He = class {
1716
1726
  r = 0, i = !1;
1717
1727
  },
1718
1728
  onError: () => {
1719
- 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.`))));
1720
1730
  }
1721
1731
  });
1722
1732
  }
@@ -1738,7 +1748,7 @@ var He = class {
1738
1748
  }
1739
1749
  t.controls = !1;
1740
1750
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
1741
- 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({
1742
1752
  root: n,
1743
1753
  video: t,
1744
1754
  player: this,
@@ -1761,7 +1771,7 @@ var He = class {
1761
1771
  }
1762
1772
  startStats() {
1763
1773
  this.stopStats();
1764
- let e = Math.max(500, this.opts.statsIntervalMs ?? Be);
1774
+ let e = Math.max(500, this.opts.statsIntervalMs ?? He);
1765
1775
  this.statsTimer = setInterval(() => {
1766
1776
  this.engine && this.emit("stats", this.engine.getStats());
1767
1777
  }, e);
@@ -1771,7 +1781,7 @@ var He = class {
1771
1781
  }
1772
1782
  scheduleLicenseRefresh() {
1773
1783
  this.stopRefresh();
1774
- let e = this.opts.licenseRefreshMs ?? Ve;
1784
+ let e = this.opts.licenseRefreshMs ?? Ue;
1775
1785
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
1776
1786
  }
1777
1787
  stopRefresh() {
@@ -1808,7 +1818,7 @@ var He = class {
1808
1818
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
1809
1819
  }
1810
1820
  getStats() {
1811
- return this.engine?.getStats() ?? R;
1821
+ return this.engine?.getStats() ?? L;
1812
1822
  }
1813
1823
  getQualityLevels() {
1814
1824
  return this.engine?.getQualityLevels() ?? [];
@@ -1904,12 +1914,12 @@ var He = class {
1904
1914
  };
1905
1915
  //#endregion
1906
1916
  //#region src/embed/protocol.ts
1907
- function $(e) {
1917
+ function Q(e) {
1908
1918
  return !!e && typeof e == "object" && typeof e.type == "string" && e.type.startsWith("oddin:");
1909
1919
  }
1910
1920
  //#endregion
1911
1921
  //#region src/embed/host.ts
1912
- function Ue(e) {
1922
+ function $(e) {
1913
1923
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
1914
1924
  if (!e.src) throw Error("mountEmbed: opts.src is required");
1915
1925
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -1918,7 +1928,7 @@ function Ue(e) {
1918
1928
  let r = document.createElement("iframe");
1919
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);
1920
1930
  let i = /* @__PURE__ */ new Set(), a = (t) => {
1921
- if (t.source !== r.contentWindow || t.origin !== n || !$(t.data)) return;
1931
+ if (t.source !== r.contentWindow || t.origin !== n || !Q(t.data)) return;
1922
1932
  let a = t.data;
1923
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));
1924
1934
  };
@@ -1968,8 +1978,8 @@ function Ue(e) {
1968
1978
  }
1969
1979
  //#endregion
1970
1980
  //#region src/embed/page.ts
1971
- var We = /^[A-Za-z0-9:_.-]{1,128}$/;
1972
- function Ge() {
1981
+ var Ge = /^[A-Za-z0-9:_.-]{1,128}$/;
1982
+ function Ke() {
1973
1983
  let e = window.HAVIK_EMBED_CONFIG;
1974
1984
  if (e) return {
1975
1985
  baseUrl: e.baseUrl ?? "",
@@ -1991,9 +2001,9 @@ function Ge() {
1991
2001
  waitForLive: !0
1992
2002
  };
1993
2003
  }
1994
- async function Ke() {
1995
- let e = Ge();
1996
- Je();
2004
+ async function qe() {
2005
+ let e = Ke();
2006
+ Ye();
1997
2007
  let n = document.createElement("video");
1998
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);
1999
2009
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2048,12 +2058,12 @@ async function Ke() {
2048
2058
  code: e.code,
2049
2059
  message: e.message
2050
2060
  })), e.on("autoplayblocked", () => {
2051
- i({ type: "oddin:autoplayblocked" }), qe(n, e);
2061
+ i({ type: "oddin:autoplayblocked" }), Je(n, e);
2052
2062
  });
2053
2063
  }, s = async (r) => {
2054
2064
  a?.destroy(), a = null;
2055
2065
  try {
2056
- let t = await Q({
2066
+ let t = await Z({
2057
2067
  video: n,
2058
2068
  baseUrl: e.baseUrl,
2059
2069
  matchUrn: r,
@@ -2114,32 +2124,32 @@ async function Ke() {
2114
2124
  a?.retry();
2115
2125
  break;
2116
2126
  case "oddin:load":
2117
- typeof e.matchUrn == "string" && We.test(e.matchUrn) && s(e.matchUrn);
2127
+ typeof e.matchUrn == "string" && Ge.test(e.matchUrn) && s(e.matchUrn);
2118
2128
  break;
2119
2129
  case "oddin:destroy":
2120
2130
  c();
2121
2131
  break;
2122
2132
  }
2123
2133
  }, u = (e) => {
2124
- !r || e.origin !== r || $(e.data) && l(e.data);
2134
+ !r || e.origin !== r || Q(e.data) && l(e.data);
2125
2135
  };
2126
2136
  r && window.addEventListener("message", u), window.addEventListener("pagehide", c, { once: !0 }), await s(e.matchUrn), i({
2127
2137
  type: "oddin:ready",
2128
2138
  protocol: 1
2129
2139
  });
2130
2140
  }
2131
- function qe(e, t) {
2141
+ function Je(e, t) {
2132
2142
  if (document.getElementById("havik-tap")) return;
2133
2143
  let n = document.createElement("button");
2134
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", () => {
2135
2145
  n.remove(), t.play();
2136
2146
  }), document.body.appendChild(n);
2137
2147
  }
2138
- function Je() {
2148
+ function Ye() {
2139
2149
  let e = document.createElement("style");
2140
2150
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2141
2151
  }
2142
2152
  //#endregion
2143
- 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 };
2144
2154
 
2145
2155
  //# sourceMappingURL=index.js.map