@oddin-gg/havik-player 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -81,41 +81,41 @@ function l(e) {
81
81
  }
82
82
  //#endregion
83
83
  //#region src/core/waitForLive.ts
84
- var u = 1e3, d = 3e4, f = 600 * 1e3, p = 300 * 1e3, ee = 1e3, te = {
84
+ var u = 1e3, d = 3e4, f = 600 * 1e3, p = 300 * 1e3, m = 1e3, h = {
85
85
  TOO_EARLY: "tooEarly",
86
86
  UNAVAILABLE: "unavailable",
87
87
  RATE_LIMITED: "rateLimited",
88
88
  NETWORK: "network"
89
89
  };
90
- function ne(e) {
91
- let t = e === !0 || e === !1 ? {} : e, n = Math.max(ee, t.floorMs ?? u), r = Math.max(n, t.ceilMs ?? d), i = Math.max(0, t.imminentMs ?? f), a = Math.max(r, t.sanityFloorMs ?? p);
90
+ function g(e) {
91
+ let t = e === !0 || e === !1 ? {} : e, n = Math.max(m, t.floorMs ?? u), r = Math.max(n, t.ceilMs ?? d), i = Math.max(0, t.imminentMs ?? f), a = Math.max(r, t.sanityFloorMs ?? p);
92
92
  return {
93
93
  timeoutMs: t.timeoutMs,
94
94
  floorMs: n,
95
95
  ceilMs: r,
96
96
  imminentMs: i,
97
97
  sanityFloorMs: a,
98
- kickoffMs: m(t.kickoffAt),
98
+ kickoffMs: _(t.kickoffAt),
99
99
  jitter: t.jitter ?? !0,
100
100
  onState: t.onState
101
101
  };
102
102
  }
103
- function m(e) {
103
+ function _(e) {
104
104
  if (e == null) return;
105
105
  if (typeof e == "number") return Number.isFinite(e) ? e : void 0;
106
106
  let t = Date.parse(e);
107
107
  return Number.isFinite(t) ? t : void 0;
108
108
  }
109
- function h(e, t, n) {
109
+ function ee(e, t, n) {
110
110
  return Math.min(n, Math.max(t, e));
111
111
  }
112
- function g(e) {
112
+ function te(e) {
113
113
  return Math.round(e * (1 + .15 * Math.random()));
114
114
  }
115
- function _(e, t, n, r) {
115
+ function ne(e, t, n, r) {
116
116
  let i = r ?? n.kickoffMs, a = i == null ? void 0 : Math.max(0, i - t), o = Math.max(n.floorMs, e ?? 0);
117
117
  if (a == null || a <= n.imminentMs) return {
118
- delay: h(e ?? n.floorMs, o, n.ceilMs),
118
+ delay: ee(e ?? n.floorMs, o, n.ceilMs),
119
119
  ttkMs: a
120
120
  };
121
121
  let s = Math.min(Math.floor(a / 10), n.sanityFloorMs, a);
@@ -124,7 +124,7 @@ function _(e, t, n, r) {
124
124
  ttkMs: a
125
125
  };
126
126
  }
127
- function v(e, n) {
127
+ function re(e, n) {
128
128
  return new Promise((r, i) => {
129
129
  if (n?.aborted) {
130
130
  i(new t("ABORTED", 0, "wait-for-live aborted"));
@@ -138,8 +138,8 @@ function v(e, n) {
138
138
  n?.addEventListener("abort", a, { once: !0 });
139
139
  });
140
140
  }
141
- async function y(e, n, r) {
142
- let a = ne(n), o = Date.now(), s = a.timeoutMs == null ? Infinity : o + a.timeoutMs, c = 0, l;
141
+ async function v(e, n, r) {
142
+ let a = g(n), o = Date.now(), s = a.timeoutMs == null ? Infinity : o + a.timeoutMs, c = 0, l;
143
143
  for (;;) {
144
144
  if (r?.aborted) throw new t("ABORTED", 0, "wait-for-live aborted");
145
145
  try {
@@ -147,37 +147,37 @@ async function y(e, n, r) {
147
147
  } catch (e) {
148
148
  if (!(e instanceof t) || !i(e.code)) throw e;
149
149
  c += 1, e.liveStartsAtMs != null && Number.isFinite(e.liveStartsAtMs) && (l = e.liveStartsAtMs);
150
- let n = Date.now(), u = _(e.retryAfterMs, n, a, l), d = u.ttkMs, f = u.delay;
151
- if (a.jitter && (f = g(f)), n + f > s) throw new t("TIMEOUT", e.httpStatus, `timed out after ${a.timeoutMs}ms waiting for the stream to go live`, {
150
+ let n = Date.now(), u = ne(e.retryAfterMs, n, a, l), d = u.ttkMs, f = u.delay;
151
+ if (a.jitter && (f = te(f)), n + f > s) throw new t("TIMEOUT", e.httpStatus, `timed out after ${a.timeoutMs}ms waiting for the stream to go live`, {
152
152
  retryAfterMs: e.retryAfterMs,
153
153
  cause: e
154
154
  });
155
155
  a.onState?.({
156
- phase: te[e.code] ?? "unavailable",
156
+ phase: h[e.code] ?? "unavailable",
157
157
  retryInMs: f,
158
158
  attempt: c,
159
159
  elapsedMs: n - o,
160
160
  ttkMs: d
161
- }), await v(f, r);
161
+ }), await re(f, r);
162
162
  }
163
163
  }
164
164
  }
165
165
  //#endregion
166
166
  //#region src/core/resolve.ts
167
- async function b(e) {
167
+ async function y(e) {
168
168
  if (!e.matchUrn || typeof e.matchUrn != "string") throw new t("INVALID_URN", 0, "resolveStream: a non-empty matchUrn is required");
169
169
  let n = await c(o(e.baseUrl, `/v1/playback/${encodeURIComponent(e.matchUrn)}`), {
170
170
  credential: e.credential,
171
171
  signal: e.signal
172
172
  });
173
- if (!n.ok) throw await re(n);
174
- return ae(await n.json(), e.matchUrn);
173
+ if (!n.ok) throw await ie(n);
174
+ return oe(await n.json(), e.matchUrn);
175
175
  }
176
- function x(e) {
177
- let t = () => b(e);
178
- return e.waitForLive ? y(t, e.waitForLive, e.signal) : t();
176
+ function b(e) {
177
+ let t = () => y(e);
178
+ return e.waitForLive ? v(t, e.waitForLive, e.signal) : t();
179
179
  }
180
- async function re(e) {
180
+ async function ie(e) {
181
181
  let r = s(e.headers.get("retry-after")), i = e.headers.get("x-request-id") ?? e.headers.get("x-amzn-requestid") ?? void 0, a = e.statusText || `HTTP ${e.status}`, o, c;
182
182
  try {
183
183
  let t = await e.json();
@@ -193,7 +193,7 @@ async function re(e) {
193
193
  liveStartsAtMs: c
194
194
  });
195
195
  }
196
- var ie = /* @__PURE__ */ new Set([
196
+ var ae = /* @__PURE__ */ new Set([
197
197
  "matchUrn",
198
198
  "protocol",
199
199
  "drmEnabled",
@@ -203,7 +203,7 @@ var ie = /* @__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 ae(e, n) {
216
+ function oe(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)) ie.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 oe(e) {
238
+ async function se(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 oe(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: se(await l.json()),
255
+ tournaments: ce(await l.json()),
256
256
  etag: l.headers.get("etag") ?? void 0,
257
257
  notModified: !1
258
258
  };
259
259
  }
260
- function se(e) {
260
+ function ce(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 se(e) {
277
277
  };
278
278
  });
279
279
  }
280
- async function ce(e) {
280
+ async function le(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 ce(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: le(await a.json()),
299
+ tournament: S(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 le(e) {
305
+ function S(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 le(e) {
320
320
  }))
321
321
  };
322
322
  }
323
- function ue(e) {
323
+ function C(e) {
324
324
  return e.tournaments.flatMap((e) => e.matches);
325
325
  }
326
326
  //#endregion
327
327
  //#region src/core/poll.ts
328
- function C(e) {
328
+ function w(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 C(e) {
360
360
  }
361
361
  //#endregion
362
362
  //#region src/core/events.ts
363
- function w(e) {
363
+ function T(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 w(e) {
427
427
  i || (i = !0, r.abort());
428
428
  } };
429
429
  }
430
- function T(e) {
430
+ function E(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 T(e) {
437
437
  }
438
438
  //#endregion
439
439
  //#region src/core/drm/deviceId.ts
440
- var E = "havik.player.deviceId", D;
441
- function O() {
442
- if (D) return D;
440
+ var D = "havik.player.deviceId", O;
441
+ function k() {
442
+ if (O) return O;
443
443
  try {
444
- let e = globalThis.localStorage?.getItem(E);
445
- if (e) return D = e, e;
444
+ let e = globalThis.localStorage?.getItem(D);
445
+ if (e) return O = e, e;
446
446
  } catch {}
447
- let e = A();
448
- D = e;
447
+ let e = de();
448
+ O = e;
449
449
  try {
450
- globalThis.localStorage?.setItem(E, e);
450
+ globalThis.localStorage?.setItem(D, e);
451
451
  } catch {}
452
452
  return e;
453
453
  }
454
- function k() {
455
- D = void 0;
454
+ function ue() {
455
+ O = void 0;
456
456
  try {
457
- globalThis.localStorage?.removeItem(E);
457
+ globalThis.localStorage?.removeItem(D);
458
458
  } catch {}
459
459
  }
460
- function A() {
460
+ function de() {
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,28 +469,121 @@ function A() {
469
469
  }
470
470
  //#endregion
471
471
  //#region src/core/drm/headers.ts
472
- function j(e, t, n = {}) {
472
+ function fe(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 ?? O(),
476
+ "X-Device-Id": n.deviceId ?? k(),
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
+ //#region src/core/drm/support.ts
483
+ var A = 5e3, j = "video/mp4; codecs=\"avc1.42E01E\"", M = "audio/mp4; codecs=\"mp4a.40.2\"", N = {
484
+ widevine: ["com.widevine.alpha"],
485
+ fairplay: ["com.apple.fps"]
486
+ };
487
+ function P(e) {
488
+ return e === "fairplay" ? [{
489
+ initDataTypes: [
490
+ "sinf",
491
+ "skd",
492
+ "cenc"
493
+ ],
494
+ videoCapabilities: [{
495
+ contentType: j,
496
+ robustness: "",
497
+ encryptionScheme: "cbcs"
498
+ }],
499
+ audioCapabilities: [{
500
+ contentType: M,
501
+ robustness: "",
502
+ encryptionScheme: "cbcs"
503
+ }]
504
+ }] : [{
505
+ initDataTypes: ["cenc"],
506
+ videoCapabilities: [{
507
+ contentType: j,
508
+ robustness: "SW_SECURE_DECODE",
509
+ encryptionScheme: "cbcs"
510
+ }],
511
+ audioCapabilities: [{
512
+ contentType: M,
513
+ robustness: "SW_SECURE_CRYPTO",
514
+ encryptionScheme: "cbcs"
515
+ }]
516
+ }];
517
+ }
518
+ function F(e, t) {
519
+ return new Promise((n, r) => {
520
+ let i = setTimeout(() => r(/* @__PURE__ */ Error("probe-timeout")), t);
521
+ e.then((e) => {
522
+ clearTimeout(i), n(e);
523
+ }, (e) => {
524
+ clearTimeout(i), r(e);
525
+ });
526
+ });
527
+ }
528
+ async function I(e = {}) {
529
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? A, r = {};
530
+ if (typeof window < "u" && window.isSecureContext === !1) return {
531
+ verdict: "insecure-context",
532
+ detail: r
533
+ };
534
+ let i = typeof navigator < "u" ? navigator.requestMediaKeySystemAccess?.bind(navigator) : void 0;
535
+ if (!i) return {
536
+ verdict: "no-cdm",
537
+ detail: r
538
+ };
539
+ let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
540
+ for (let e of t) {
541
+ let t = N[e];
542
+ if (!t) {
543
+ r[e] = "unknown system";
544
+ continue;
545
+ }
546
+ for (let n of t) {
547
+ if (Date.now() >= s) {
548
+ a = !0, r[e] ??= `${n}: probe-timeout`;
549
+ break;
550
+ }
551
+ try {
552
+ return await F((await F(i(n, P(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
553
+ verdict: "supported",
554
+ system: e,
555
+ detail: r
556
+ };
557
+ } catch (t) {
558
+ let i = t;
559
+ r[e] = `${n}: ${(i?.name && i.name !== "Error" ? i.name : i?.message) || String(t)}`, i?.message === "probe-timeout" && (a = !0), i?.name === "SecurityError" && (o = !0);
560
+ }
561
+ }
562
+ }
563
+ return o ? {
564
+ verdict: "blocked-by-policy",
565
+ detail: r
566
+ } : a ? {
567
+ verdict: "probe-timeout",
568
+ detail: r
569
+ } : {
570
+ verdict: "no-cdm",
571
+ detail: r
572
+ };
573
+ }
574
+ //#endregion
482
575
  //#region src/core/manifest.ts
483
- var M = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
484
- function N(e) {
485
- return M.test(e);
576
+ var L = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
577
+ function R(e) {
578
+ return L.test(e);
486
579
  }
487
580
  //#endregion
488
581
  //#region src/core/stats.ts
489
- var P = { droppedFrames: 0 };
490
- function F(e) {
582
+ var z = { droppedFrames: 0 };
583
+ function B(e) {
491
584
  return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
492
585
  }
493
- function I(e, t) {
586
+ function V(e, t) {
494
587
  let n = [
495
588
  ["playing", () => t("playing")],
496
589
  ["waiting", () => t("buffering")],
@@ -506,29 +599,132 @@ function I(e, t) {
506
599
  };
507
600
  }
508
601
  //#endregion
602
+ //#region src/core/engine/native.ts
603
+ function H(e) {
604
+ return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
605
+ }
606
+ var U = class {
607
+ name = "native";
608
+ deps;
609
+ onLoaded;
610
+ onError;
611
+ constructor(e) {
612
+ this.deps = e;
613
+ }
614
+ async load() {
615
+ let { video: e, descriptor: n, emit: r } = this.deps;
616
+ if (n.drmEnabled) {
617
+ if (!n.drm?.fairplay) {
618
+ r({
619
+ type: "error",
620
+ error: new t("INTERNAL", 0, "native HLS engine cannot satisfy this DRM (Widevine requires the hls.js engine); use engine \"auto\"/\"hls\" on a Widevine-capable browser"),
621
+ fatal: !0
622
+ });
623
+ return;
624
+ }
625
+ r({
626
+ type: "error",
627
+ error: new t("INTERNAL", 0, "the native HLS fallback engine does not drive FairPlay EME; relying on OS-trusted passthrough"),
628
+ fatal: !1
629
+ });
630
+ }
631
+ this.onLoaded = () => r({ type: "ready" }), this.onError = () => r({
632
+ type: "error",
633
+ error: new t("INTERNAL", 0, "native HLS playback error"),
634
+ fatal: !0
635
+ }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = n.manifestUrl, e.load();
636
+ }
637
+ setLicenseUrls(e) {}
638
+ getQualityLevels() {
639
+ return [];
640
+ }
641
+ getCurrentQuality() {
642
+ return -1;
643
+ }
644
+ setQuality(e) {}
645
+ setMaxBitrate(e) {}
646
+ getAudioTracks() {
647
+ let e = this.deps.video.audioTracks;
648
+ return e ? Array.from({ length: e.length }, (t, n) => {
649
+ let r = e[n];
650
+ return {
651
+ id: n,
652
+ name: r.label || r.language || `Audio ${n + 1}`,
653
+ lang: r.language || void 0,
654
+ default: r.enabled
655
+ };
656
+ }) : [];
657
+ }
658
+ setAudioTrack(e) {
659
+ let t = this.deps.video.audioTracks;
660
+ if (t) for (let n = 0; n < t.length; n += 1) t[n].enabled = n === e;
661
+ }
662
+ getTextTracks() {
663
+ let e = this.deps.video.textTracks;
664
+ return Array.from({ length: e.length }, (t, n) => {
665
+ let r = e[n];
666
+ return {
667
+ id: n,
668
+ name: r.label || r.language || `Text ${n + 1}`,
669
+ lang: r.language || void 0
670
+ };
671
+ });
672
+ }
673
+ setTextTrack(e) {
674
+ let t = this.deps.video.textTracks;
675
+ for (let n = 0; n < t.length; n += 1) t[n].mode = n === e ? "showing" : "disabled";
676
+ }
677
+ seekToLive() {
678
+ let e = this.deps.video;
679
+ e.seekable.length > 0 && (e.currentTime = e.seekable.end(e.seekable.length - 1));
680
+ }
681
+ getStats() {
682
+ let e = this.deps.video, t = { droppedFrames: B(e) };
683
+ return e.duration === Infinity && (t.isLive = !0, e.seekable.length > 0 && (t.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), t;
684
+ }
685
+ stop() {
686
+ try {
687
+ this.deps.video.pause();
688
+ } catch {}
689
+ }
690
+ destroy() {
691
+ let { video: e } = this.deps;
692
+ this.onLoaded && e.removeEventListener("loadedmetadata", this.onLoaded), this.onError && e.removeEventListener("error", this.onError);
693
+ try {
694
+ e.pause();
695
+ } catch {}
696
+ e.removeAttribute("src");
697
+ try {
698
+ e.load();
699
+ } catch {}
700
+ }
701
+ };
702
+ //#endregion
509
703
  //#region src/core/engine/hls.ts
510
- function L() {
704
+ function pe() {
511
705
  return e.isSupported();
512
706
  }
513
- var R = 3, z = 5, B = 3, V = 3e4, H = 12e3, U = 3e3, de = 6e3, fe = 2, pe = 9, me = .5;
514
- function he(e) {
707
+ var me = 3, he = 5, ge = 3, _e = 3e4, ve = 12e3, ye = 3e3, be = 6e3, xe = 2, Se = 9, Ce = .5;
708
+ function W(e) {
515
709
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
516
710
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
517
711
  return;
518
712
  }
519
- e.liveSyncDuration ??= fe;
520
- let t = Math.max(12, e.liveSyncDuration + pe);
521
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), ge(e, e.liveSyncDuration);
713
+ e.liveSyncDuration ??= xe;
714
+ let t = Math.max(12, e.liveSyncDuration + Se);
715
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), we(e, e.liveSyncDuration);
522
716
  }
523
- function ge(e, t) {
717
+ function we(e, t) {
524
718
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
525
719
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
526
720
  }
527
- var _e = class {
721
+ var Te = class {
528
722
  name = "hls";
529
723
  hls = null;
530
724
  deps;
531
725
  licenseUrl;
726
+ fairplayLicenseUrl;
727
+ fairplayCertUrl;
532
728
  detectedLowLatency = !1;
533
729
  mediaRecoveries = 0;
534
730
  networkRecoveries = 0;
@@ -547,14 +743,14 @@ var _e = class {
547
743
  savedLiveMaxLatency;
548
744
  savedMaxSyncRate;
549
745
  constructor(e) {
550
- this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
746
+ this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl, this.fairplayLicenseUrl = e.descriptor.drm?.fairplay?.licenseUrl, this.fairplayCertUrl = e.descriptor.drm?.fairplay?.certificateUrl;
551
747
  }
552
748
  async load() {
553
- let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps;
554
- if (e.drmEnabled && !e.drm?.widevine?.licenseUrl) {
749
+ let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps, a = H(r), o = !!e.drm?.widevine?.licenseUrl && !a, s = !!(e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && a);
750
+ if (e.drmEnabled && !o && !s) {
555
751
  i({
556
752
  type: "error",
557
- error: new t("INTERNAL", 0, "DRM is required but no Widevine license is available for this stream on this browser"),
753
+ error: new t("INTERNAL", 0, "DRM is required but none of the stream’s key systems is usable on this platform (WebKit takes FairPlay only; other browsers take Widevine only)"),
558
754
  fatal: !0
559
755
  });
560
756
  return;
@@ -574,10 +770,10 @@ var _e = class {
574
770
  let t = e.liveSyncPosition;
575
771
  if (typeof t != "number" || !Number.isFinite(t)) return;
576
772
  let { video: n } = this.deps;
577
- n.currentTime <= t + me || (n.currentTime = t);
773
+ n.currentTime <= t + Ce || (n.currentTime = t);
578
774
  }
579
775
  startHls() {
580
- 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, d = {
776
+ 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 = !!(this.fairplayLicenseUrl && this.fairplayCertUrl && H(t)), f = {
581
777
  debug: s === !0,
582
778
  enableWorker: !o,
583
779
  lowLatencyMode: o,
@@ -588,44 +784,89 @@ var _e = class {
588
784
  capLevelToPlayerSize: !0,
589
785
  ...u ? {
590
786
  emeEnabled: !0,
591
- drmSystems: { "com.widevine.alpha": { licenseUrl: this.licenseUrl } },
592
- drmSystemOptions: {
787
+ drmSystems: {
788
+ ...this.licenseUrl ? { "com.widevine.alpha": { licenseUrl: this.licenseUrl } } : {},
789
+ ...d ? { "com.apple.fps": {
790
+ licenseUrl: this.fairplayLicenseUrl,
791
+ serverCertificateUrl: this.fairplayCertUrl,
792
+ generateRequest(e, t, n) {
793
+ if (t) return {
794
+ initDataType: e,
795
+ initData: t
796
+ };
797
+ let r = n.decryptdata?.uri;
798
+ if (r?.startsWith("skd://")) {
799
+ let e = /* @__PURE__ */ new ArrayBuffer(r.length * 2), t = new DataView(e);
800
+ for (let e = 0; e < r.length; e++) t.setUint16(e * 2, r.charCodeAt(e), !0);
801
+ return {
802
+ initDataType: "skd",
803
+ initData: e
804
+ };
805
+ }
806
+ return {
807
+ initDataType: e,
808
+ initData: t
809
+ };
810
+ }
811
+ } } : {}
812
+ },
813
+ drmSystemOptions: d ? {
814
+ videoEncryptionScheme: "cbcs",
815
+ audioEncryptionScheme: "cbcs"
816
+ } : {
593
817
  videoRobustness: "SW_SECURE_DECODE",
594
818
  audioRobustness: "SW_SECURE_CRYPTO",
595
819
  videoEncryptionScheme: "cbcs",
596
820
  audioEncryptionScheme: "cbcs"
597
821
  },
822
+ ...d ? { requestMediaKeySystemAccessFunc: (e, t) => {
823
+ let n = e === "com.apple.fps" ? t.map((e) => ({
824
+ ...e,
825
+ initDataTypes: [.../* @__PURE__ */ new Set([...e.initDataTypes ?? [], "skd"])]
826
+ })) : t;
827
+ return navigator.requestMediaKeySystemAccess(e, n);
828
+ } } : {},
598
829
  licenseXhrSetup: (e, t) => {
599
- e.open("POST", this.licenseUrl ?? t, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a);
830
+ let o = t.includes("/license/fairplay") ? this.fairplayLicenseUrl ?? t : this.licenseUrl ?? t;
831
+ e.open("POST", o, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a);
600
832
  }
601
833
  } : {},
602
834
  ...c
603
835
  };
604
- he(d);
605
- let f = new e(d);
606
- this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
836
+ if (d) {
837
+ let e = f.xhrSetup, t = () => this.fairplayCertUrl;
838
+ f.xhrSetup = (n, i) => {
839
+ let a = e?.(n, i), o = () => {
840
+ i === t() && (n.readyState === XMLHttpRequest.UNSENT && n.open("GET", i, !0), n.setRequestHeader("x-api-key", r.apiKey));
841
+ };
842
+ return a && typeof a.then == "function" ? a.then(() => o()) : (o(), a);
843
+ };
844
+ }
845
+ W(f);
846
+ let p = new e(f);
847
+ this.hls = p, p.on(e.Events.MANIFEST_PARSED, () => {
607
848
  this.mediaRecoveries = 0, this.networkRecoveries = 0, l({ type: "ready" });
608
- }), f.on(e.Events.FRAG_BUFFERED, () => {
849
+ }), p.on(e.Events.FRAG_BUFFERED, () => {
609
850
  (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
610
851
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.recoveryResetTimer = null;
611
- }, V));
612
- }), f.on(e.Events.LEVEL_LOADED, (e, t) => {
852
+ }, _e));
853
+ }), p.on(e.Events.LEVEL_LOADED, (e, t) => {
613
854
  if (this.detectedLowLatency) return;
614
855
  let n = t.details;
615
856
  (n?.partList?.length || (n?.partTarget ?? 0) > 0) && (this.detectedLowLatency = !0, l({
616
857
  type: "lowLatencyDetected",
617
858
  value: !0
618
859
  }));
619
- }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => this.checkEndStale(), U), f.on(e.Events.LEVEL_SWITCHED, (e, t) => l({
860
+ }), p.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => this.checkEndStale(), ye), p.on(e.Events.LEVEL_SWITCHED, (e, t) => l({
620
861
  type: "qualitychange",
621
862
  index: t.level
622
- })), f.on(e.Events.AUDIO_TRACK_SWITCHED, (e, t) => l({
863
+ })), p.on(e.Events.AUDIO_TRACK_SWITCHED, (e, t) => l({
623
864
  type: "audiotrackchange",
624
865
  id: t.id
625
- })), f.on(e.Events.SUBTITLE_TRACK_SWITCH, (e, t) => l({
866
+ })), p.on(e.Events.SUBTITLE_TRACK_SWITCH, (e, t) => l({
626
867
  type: "texttrackchange",
627
868
  id: t.id
628
- })), f.on(e.Events.ERROR, (e, t) => this.onError(t)), f.loadSource(n.manifestUrl), f.attachMedia(t);
869
+ })), p.on(e.Events.ERROR, (e, t) => this.onError(t)), p.loadSource(n.manifestUrl), p.attachMedia(t);
629
870
  }
630
871
  getQualityLevels() {
631
872
  return this.hls ? this.hls.levels.map((e, t) => ({
@@ -680,12 +921,12 @@ var _e = class {
680
921
  let e = this.hls;
681
922
  e && typeof e.liveSyncPosition == "number" && (this.deps.video.currentTime = e.liveSyncPosition);
682
923
  }
683
- setLicenseUrl(e) {
684
- this.licenseUrl = e;
924
+ setLicenseUrls(e) {
925
+ e.widevine && (this.licenseUrl = e.widevine), e.fairplay && (this.fairplayLicenseUrl = e.fairplay);
685
926
  }
686
927
  getStats() {
687
928
  let e = this.hls, t = {
688
- droppedFrames: F(this.deps.video),
929
+ droppedFrames: B(this.deps.video),
689
930
  lowLatency: this.detectedLowLatency
690
931
  };
691
932
  if (e) {
@@ -714,7 +955,7 @@ var _e = class {
714
955
  return;
715
956
  }
716
957
  let e = Date.now() - this.lastAdvanceMs;
717
- !this.chaseSuspended && e > de && this.suspendChasing(), e > H && this.emitEnded("stalled");
958
+ !this.chaseSuspended && e > be && this.suspendChasing(), e > ve && this.emitEnded("stalled");
718
959
  }
719
960
  suspendChasing() {
720
961
  let e = this.hls;
@@ -756,7 +997,7 @@ var _e = class {
756
997
  scheduleLicenseRetry(t) {
757
998
  if (!(t.details === e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED || t.details === e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED)) return !1;
758
999
  let n = t.response?.code ?? 0;
759
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= B) return !1;
1000
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= ge) return !1;
760
1001
  this.licenseRecoveries += 1;
761
1002
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
762
1003
  return this.licenseRetryTimer = setTimeout(() => {
@@ -784,7 +1025,7 @@ var _e = class {
784
1025
  return;
785
1026
  }
786
1027
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
787
- if (this.networkRecoveries < z) {
1028
+ if (this.networkRecoveries < he) {
788
1029
  this.networkRecoveries += 1;
789
1030
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
790
1031
  this.networkRetryTimer = setTimeout(() => {
@@ -800,7 +1041,7 @@ var _e = class {
800
1041
  return;
801
1042
  }
802
1043
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
803
- if (this.mediaRecoveries < R) {
1044
+ if (this.mediaRecoveries < me) {
804
1045
  this.mediaRecoveries += 1, n.recoverMediaError();
805
1046
  return;
806
1047
  }
@@ -822,107 +1063,6 @@ var _e = class {
822
1063
  let r = e.response?.code ?? 0, i = e.details ?? e.type, a = e.reason ?? e.error?.message ?? "";
823
1064
  return new t(r === 401 ? "UNAUTHORIZED" : r === 403 || n === "DRM" ? "FORBIDDEN" : "INTERNAL", r, `${n} error: ${i}${a ? ` (${a})` : ""}`);
824
1065
  }
825
- };
826
- //#endregion
827
- //#region src/core/engine/native.ts
828
- function ve(e) {
829
- return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
830
- }
831
- var W = class {
832
- name = "native";
833
- deps;
834
- onLoaded;
835
- onError;
836
- constructor(e) {
837
- this.deps = e;
838
- }
839
- async load() {
840
- let { video: e, descriptor: n, emit: r } = this.deps;
841
- if (n.drmEnabled) {
842
- if (!n.drm?.fairplay) {
843
- r({
844
- type: "error",
845
- error: new t("INTERNAL", 0, "native HLS engine cannot satisfy this DRM (Widevine requires the hls.js engine); use engine \"auto\"/\"hls\" on a Widevine-capable browser"),
846
- fatal: !0
847
- });
848
- return;
849
- }
850
- r({
851
- type: "error",
852
- error: new t("INTERNAL", 0, "FairPlay EME is not implemented in this SDK version; relying on native passthrough"),
853
- fatal: !1
854
- });
855
- }
856
- this.onLoaded = () => r({ type: "ready" }), this.onError = () => r({
857
- type: "error",
858
- error: new t("INTERNAL", 0, "native HLS playback error"),
859
- fatal: !0
860
- }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = n.manifestUrl, e.load();
861
- }
862
- setLicenseUrl(e) {}
863
- getQualityLevels() {
864
- return [];
865
- }
866
- getCurrentQuality() {
867
- return -1;
868
- }
869
- setQuality(e) {}
870
- setMaxBitrate(e) {}
871
- getAudioTracks() {
872
- let e = this.deps.video.audioTracks;
873
- return e ? Array.from({ length: e.length }, (t, n) => {
874
- let r = e[n];
875
- return {
876
- id: n,
877
- name: r.label || r.language || `Audio ${n + 1}`,
878
- lang: r.language || void 0,
879
- default: r.enabled
880
- };
881
- }) : [];
882
- }
883
- setAudioTrack(e) {
884
- let t = this.deps.video.audioTracks;
885
- if (t) for (let n = 0; n < t.length; n += 1) t[n].enabled = n === e;
886
- }
887
- getTextTracks() {
888
- let e = this.deps.video.textTracks;
889
- return Array.from({ length: e.length }, (t, n) => {
890
- let r = e[n];
891
- return {
892
- id: n,
893
- name: r.label || r.language || `Text ${n + 1}`,
894
- lang: r.language || void 0
895
- };
896
- });
897
- }
898
- setTextTrack(e) {
899
- let t = this.deps.video.textTracks;
900
- for (let n = 0; n < t.length; n += 1) t[n].mode = n === e ? "showing" : "disabled";
901
- }
902
- seekToLive() {
903
- let e = this.deps.video;
904
- e.seekable.length > 0 && (e.currentTime = e.seekable.end(e.seekable.length - 1));
905
- }
906
- getStats() {
907
- let e = this.deps.video, t = { droppedFrames: F(e) };
908
- return e.duration === Infinity && (t.isLive = !0, e.seekable.length > 0 && (t.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), t;
909
- }
910
- stop() {
911
- try {
912
- this.deps.video.pause();
913
- } catch {}
914
- }
915
- destroy() {
916
- let { video: e } = this.deps;
917
- this.onLoaded && e.removeEventListener("loadedmetadata", this.onLoaded), this.onError && e.removeEventListener("error", this.onError);
918
- try {
919
- e.pause();
920
- } catch {}
921
- e.removeAttribute("src");
922
- try {
923
- e.load();
924
- } catch {}
925
- }
926
1066
  }, G = {
927
1067
  accent: "#E1B600",
928
1068
  accentText: "#1B1C23",
@@ -956,21 +1096,21 @@ function q(e, t) {
956
1096
  }
957
1097
  //#endregion
958
1098
  //#region src/ui/styles.ts
959
- var J = "havik-player-styles", ye = "\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";
960
- function be(e = document) {
1099
+ var J = "havik-player-styles", Ee = "\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";
1100
+ function De(e = document) {
961
1101
  if (e.getElementById(J)) return;
962
1102
  let t = e.createElement("style");
963
- t.id = J, t.textContent = ye, e.head.appendChild(t);
1103
+ t.id = J, t.textContent = Ee, e.head.appendChild(t);
964
1104
  }
965
1105
  //#endregion
966
1106
  //#region src/ui/icons.ts
967
- var xe = {
1107
+ var Oe = {
968
1108
  play: "<path d=\"M8 5v14l11-7z\"/>",
969
1109
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
970
1110
  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\"/>",
971
1111
  volumeMute: "<path d=\"M3 10v4h4l5 5V5L7 10H3zm16.5 2 2.5-2.5-1.4-1.4L18 10.6 15.4 8 14 9.4l2.6 2.6L14 14.6l1.4 1.4 2.6-2.6 2.6 2.6 1.4-1.4z\"/>",
972
1112
  cc: "<path d=\"M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zM10 11H8.5v-.5h-2v3h2V13H10v1a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H17v1a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1z\"/>",
973
- settings: "<path d=\"m19.4 13 .1-1-.1-1 2-1.6-2-3.4-2.4 1a7 7 0 0 0-1.7-1l-.4-2.5H9.1L8.7 6a7 7 0 0 0-1.7 1l-2.4-1-2 3.4L2.6 11l-.1 1 .1 1-2 1.6 2 3.4 2.4-1c.5.4 1 .7 1.7 1l.4 2.5h5.8l.4-2.5c.6-.3 1.2-.6 1.7-1l2.4 1 2-3.4-2-1.6zM12 15.5a3.5 3.5 0 1 1 0-7 3.5 3.5 0 0 1 0 7z\"/>",
1113
+ settings: "<path d=\"M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.11-.2.06-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z\"/>",
974
1114
  pip: "<path d=\"M19 7h-8v6h8V7zm4 12V5a2 2 0 0 0-2-2H3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2zm-2 .02H3V4.98h18v14.04z\"/>",
975
1115
  fullscreen: "<path d=\"M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z\"/>",
976
1116
  fullscreenExit: "<path d=\"M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z\"/>",
@@ -978,20 +1118,20 @@ var xe = {
978
1118
  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\"/>"
979
1119
  };
980
1120
  function Y(e) {
981
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${xe[e]}</svg>`;
1121
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Oe[e]}</svg>`;
982
1122
  }
983
1123
  //#endregion
984
1124
  //#region src/ui/controls.ts
985
- var Se = 3e3, Ce = /* @__PURE__ */ new Set([
1125
+ var ke = 3e3, Ae = /* @__PURE__ */ new Set([
986
1126
  "GONE",
987
1127
  "NOT_FOUND",
988
1128
  "INVALID_URN",
989
1129
  "UNAUTHORIZED"
990
1130
  ]);
991
- function we(e) {
992
- return new Te(e);
1131
+ function je(e) {
1132
+ return new Me(e);
993
1133
  }
994
- var Te = class {
1134
+ var Me = class {
995
1135
  root;
996
1136
  video;
997
1137
  player;
@@ -1031,7 +1171,7 @@ var Te = class {
1031
1171
  scrubbing = !1;
1032
1172
  offFns = [];
1033
1173
  constructor(e) {
1034
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Se, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, be(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");
1174
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? ke, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, De(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");
1035
1175
  let t = (e, t, n) => {
1036
1176
  let r = this.doc.createElement(e);
1037
1177
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1118,7 +1258,7 @@ var Te = class {
1118
1258
  e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Y("play"), e.addEventListener("click", () => void this.player.play()), t.append(e);
1119
1259
  } else {
1120
1260
  let n = this.doc.createElement("div");
1121
- 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 && !Ce.has(this.lastError.code)) {
1261
+ 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 && !Ae.has(this.lastError.code)) {
1122
1262
  let e = this.doc.createElement("button");
1123
1263
  e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Y("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), t.append(e);
1124
1264
  }
@@ -1204,17 +1344,17 @@ var Te = class {
1204
1344
  renderQualityMenu() {
1205
1345
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1206
1346
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1207
- for (let n of e) this.menu.append(this.menuItem(Ee(n), t === n.index, () => this.player.setQuality(n.index)));
1347
+ for (let n of e) this.menu.append(this.menuItem(Ne(n), t === n.index, () => this.player.setQuality(n.index)));
1208
1348
  }
1209
1349
  renderAudioMenu() {
1210
1350
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1211
1351
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1212
- for (let t of e) this.menu.append(this.menuItem(De(t), t.default, () => this.player.setAudioTrack(t.id)));
1352
+ for (let t of e) this.menu.append(this.menuItem(Pe(t), t.default, () => this.player.setAudioTrack(t.id)));
1213
1353
  }
1214
1354
  renderCcMenu() {
1215
1355
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1216
1356
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1217
- for (let t of e) this.menu.append(this.menuItem(Oe(t), !1, () => this.player.setTextTrack(t.id)));
1357
+ for (let t of e) this.menu.append(this.menuItem(Fe(t), !1, () => this.player.setTextTrack(t.id)));
1218
1358
  }
1219
1359
  onTracksChanged() {
1220
1360
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1320,27 +1460,27 @@ function X(e) {
1320
1460
  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");
1321
1461
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1322
1462
  }
1323
- function Ee(e) {
1463
+ function Ne(e) {
1324
1464
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1325
1465
  }
1326
- function De(e) {
1466
+ function Pe(e) {
1327
1467
  return e.name || e.lang || `Track ${e.id}`;
1328
1468
  }
1329
- function Oe(e) {
1469
+ function Fe(e) {
1330
1470
  return e.name || e.lang || `Track ${e.id}`;
1331
1471
  }
1332
1472
  //#endregion
1333
1473
  //#region src/managed/index.ts
1334
- var ke = 6e3, Z = 45e3, Ae = 2e3, je = 3, Me = 2e3, Ne = 480 * 1e3;
1474
+ var Ie = 6e3, Z = 45e3, Le = 2e3, Re = 3, ze = 2e3, Be = 480 * 1e3;
1335
1475
  async function Q(e) {
1336
1476
  if (!e.video || typeof e.video.play != "function") throw new t("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1337
1477
  if (!e.baseUrl) throw new t("INTERNAL", 0, "createPlayer: baseUrl is required");
1338
1478
  if (!e.matchUrn) throw new t("INVALID_URN", 0, "createPlayer: matchUrn is required");
1339
1479
  if (!e.credential) throw new t("INTERNAL", 0, "createPlayer: credential is required");
1340
- let n = new Pe(e);
1480
+ let n = new Ve(e);
1341
1481
  return await n.start(), n;
1342
1482
  }
1343
- var Pe = class {
1483
+ var Ve = class {
1344
1484
  opts;
1345
1485
  abort = new AbortController();
1346
1486
  listeners = {};
@@ -1359,6 +1499,8 @@ var Pe = class {
1359
1499
  endedHandled = !1;
1360
1500
  armingSub = null;
1361
1501
  armSseTimer = null;
1502
+ armFallbackCtrl = null;
1503
+ _lastError = null;
1362
1504
  constructor(e) {
1363
1505
  this.opts = e;
1364
1506
  }
@@ -1372,11 +1514,14 @@ var Pe = class {
1372
1514
  this.opts.muted && (this.opts.video.muted = !0), this.opts.poster && (this.opts.video.poster = this.opts.poster), this.mountControls(), this.attachPipListeners(), this.setState("loading");
1373
1515
  let e = ++this.loadGen, n = this.resolveOptions();
1374
1516
  try {
1375
- let t = await b(n);
1376
- await this.onResolved(t, e);
1517
+ let t = await y(n);
1518
+ if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) {
1519
+ let e = this._lastError;
1520
+ throw this.destroy(), e;
1521
+ }
1377
1522
  } catch (n) {
1378
1523
  if (this.opts.waitForLive && n instanceof t && i(n.code)) {
1379
- this.setState("waiting"), this.beginArming(e);
1524
+ this.setState("waiting"), this.beginArming(e, n.liveStartsAtMs);
1380
1525
  return;
1381
1526
  }
1382
1527
  throw n;
@@ -1390,55 +1535,81 @@ var Pe = class {
1390
1535
  signal: this.abort.signal
1391
1536
  };
1392
1537
  }
1393
- beginArming(e) {
1394
- let t = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1395
- t ? this.armWithSse(e, t) : this.beginArmingByPolling(e);
1538
+ beginArming(e, t) {
1539
+ let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? E(this.opts.baseUrl);
1540
+ n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1396
1541
  }
1397
- beginArmingByPolling(e, t) {
1398
- x({
1542
+ beginArmingByPolling(e) {
1543
+ b({
1399
1544
  ...this.resolveOptions(),
1400
- signal: t ?? this.abort.signal,
1401
- waitForLive: this.buildWaitConfig()
1402
- }).then((t) => this.onResolved(t, e)).catch((n) => {
1403
- this.destroyed || this.abort.signal.aborted || t?.aborted || e !== this.loadGen || this.fail(n);
1545
+ signal: this.abort.signal,
1546
+ waitForLive: this.buildWaitConfig(e)
1547
+ }).then((t) => {
1548
+ if (!(this.destroyed || this.abort.signal.aborted || e !== this.loadGen)) return this.onResolved(t, e);
1549
+ }).catch((n) => {
1550
+ this.destroyed || this.abort.signal.aborted || e !== this.loadGen || this.fail(n instanceof t ? n : new t("INTERNAL", 0, String(n)));
1404
1551
  });
1405
1552
  }
1406
- armWithSse(e, n) {
1407
- let r = !1, a = !1, o = new AbortController();
1408
- this.abort.signal.addEventListener("abort", () => o.abort(), { once: !0 }), this.setState("waiting");
1409
- let s = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, c = async (t) => {
1410
- r || s() || (r = !0, this.closeArming(), o.abort(), await this.onResolved(t, e));
1411
- }, l = () => {
1412
- r || a || s() || (a = !0, this.beginArmingByPolling(e, o.signal));
1413
- }, u = () => {
1414
- r || s() || b(this.resolveOptions()).then((e) => c(e)).catch((e) => {
1415
- r || s() || e instanceof t && i(e.code) || (r = !0, this.closeArming(), o.abort(), this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e))));
1553
+ armWithSse(e, n, r) {
1554
+ let a = !1, o = !1, s = new AbortController();
1555
+ this.armFallbackCtrl = s, this.setState("waiting");
1556
+ let c = this.opts.waitForLive, l = c && c !== !0 ? c.kickoffAt : void 0, u = typeof l == "number" ? l : typeof l == "string" ? Date.parse(l) : NaN, d = r ?? (Number.isFinite(u) ? u : void 0);
1557
+ setTimeout(() => {
1558
+ a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1559
+ phase: "tooEarly",
1560
+ retryInMs: Z,
1561
+ attempt: 1,
1562
+ elapsedMs: 0,
1563
+ ...d !== void 0 && d > Date.now() ? { ttkMs: d - Date.now() } : {}
1416
1564
  });
1417
- }, d = (e) => {
1565
+ }, 0);
1566
+ let f = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, p = async (n) => {
1567
+ if (!(a || f())) {
1568
+ a = !0, this.closeArming(), s.abort();
1569
+ try {
1570
+ await this.onResolved(n, e);
1571
+ } catch (e) {
1572
+ if (f()) return;
1573
+ this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e)));
1574
+ }
1575
+ }
1576
+ }, m = () => {
1577
+ a || o || f() || (o = !0, b({
1578
+ ...this.resolveOptions(),
1579
+ signal: s.signal,
1580
+ waitForLive: this.buildWaitConfig(e)
1581
+ }).then((e) => p(e)).catch((e) => {
1582
+ a || f() || s.signal.aborted || (a = !0, this.closeArming(), this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e))));
1583
+ }));
1584
+ }, h = () => {
1585
+ a || f() || y(this.resolveOptions()).then((e) => p(e)).catch((e) => {
1586
+ a || f() || e instanceof t && i(e.code) || (a = !0, this.closeArming(), s.abort(), this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e))));
1587
+ });
1588
+ }, g = (e) => {
1418
1589
  this.armSseTimer && clearTimeout(this.armSseTimer), this.armSseTimer = setTimeout(() => {
1419
- !r && !s() && l();
1590
+ !a && !f() && m();
1420
1591
  }, e);
1421
1592
  };
1422
- d(ke), this.armingSub = w({
1593
+ g(Ie), this.armingSub = T({
1423
1594
  eventsBaseUrl: n,
1424
1595
  matchUrn: this.opts.matchUrn,
1425
1596
  credential: this.opts.credential,
1426
- onAlive: () => d(Z),
1597
+ onAlive: () => g(Z),
1427
1598
  onState: (e) => {
1428
- r || s() || (e.state === "live" || e.state === "gone") && setTimeout(u, Math.random() * Ae);
1599
+ a || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * Le);
1429
1600
  },
1430
- onError: () => l()
1601
+ onError: () => m()
1431
1602
  });
1432
1603
  }
1433
1604
  closeArming() {
1434
- this.armSseTimer &&= (clearTimeout(this.armSseTimer), null), this.armingSub?.close(), this.armingSub = null;
1605
+ this.armSseTimer &&= (clearTimeout(this.armSseTimer), null), this.armFallbackCtrl?.abort(), this.armFallbackCtrl = null, this.armingSub?.close(), this.armingSub = null;
1435
1606
  }
1436
- buildWaitConfig() {
1437
- let e = this.opts.waitForLive && this.opts.waitForLive !== !0 ? this.opts.waitForLive : {};
1607
+ buildWaitConfig(e) {
1608
+ let t = this.opts.waitForLive && this.opts.waitForLive !== !0 ? this.opts.waitForLive : {};
1438
1609
  return {
1439
- ...e,
1440
- onState: (t) => {
1441
- this.setState("waiting"), this.emit("waiting", t), e.onState?.(t);
1610
+ ...t,
1611
+ onState: (n) => {
1612
+ this.destroyed || e !== this.loadGen || (this.setState("waiting"), this.emit("waiting", n), t.onState?.(n));
1442
1613
  }
1443
1614
  };
1444
1615
  }
@@ -1448,11 +1619,17 @@ var Pe = class {
1448
1619
  let n = await a(this.opts.credential);
1449
1620
  if (this.destroyed || t !== this.loadGen) return;
1450
1621
  this.setState("loading");
1451
- let r = this.createEngine(e, n), i = I(this.opts.video, (e) => this.setState(e));
1622
+ let r = this.createEngine(e, n), i = V(this.opts.video, (e) => {
1623
+ this._state === "ended" || this._state === "error" || this.setState(e);
1624
+ });
1452
1625
  if (await r.load(), this.destroyed || t !== this.loadGen) {
1453
1626
  i(), r.destroy();
1454
1627
  return;
1455
1628
  }
1629
+ if (this._state === "error") {
1630
+ i(), r.destroy();
1631
+ return;
1632
+ }
1456
1633
  this.engine = r, this.detachMedia = i, this.opts.maxBitrate != null && r.setMaxBitrate(this.opts.maxBitrate), this.startStats(), this.scheduleLicenseRefresh(), this.startLiveState(t);
1457
1634
  }
1458
1635
  createEngine(e, n) {
@@ -1462,7 +1639,7 @@ var Pe = class {
1462
1639
  video: this.opts.video,
1463
1640
  descriptor: e,
1464
1641
  credential: n,
1465
- deviceId: O(),
1642
+ deviceId: k(),
1466
1643
  userId: this.opts.userId,
1467
1644
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
1468
1645
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -1470,9 +1647,9 @@ var Pe = class {
1470
1647
  hlsConfig: r,
1471
1648
  emit: (e) => this.onEngineEvent(e)
1472
1649
  }, a = this.opts.engine ?? "auto";
1473
- if (a === "native") return new W(i);
1474
- if (a === "hls" || L()) return new _e(i);
1475
- if (ve(this.opts.video)) return new W(i);
1650
+ if (a === "native") return new U(i);
1651
+ if (a === "hls" || pe()) return new Te(i);
1652
+ if (H(this.opts.video)) return new U(i);
1476
1653
  throw new t("INTERNAL", 0, "no supported HLS playback engine in this browser");
1477
1654
  }
1478
1655
  onEngineEvent(e) {
@@ -1502,11 +1679,11 @@ var Pe = class {
1502
1679
  }
1503
1680
  startLiveState(e) {
1504
1681
  if (this.opts.liveStateEvents === !1) return;
1505
- let n = this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1682
+ let n = this.opts.eventsBaseUrl ?? E(this.opts.baseUrl);
1506
1683
  if (!n) return;
1507
1684
  this.closeLiveState();
1508
1685
  let r = 0, i = !1;
1509
- this.liveStateSub = w({
1686
+ this.liveStateSub = T({
1510
1687
  eventsBaseUrl: n,
1511
1688
  matchUrn: this.opts.matchUrn,
1512
1689
  credential: this.opts.credential,
@@ -1517,7 +1694,7 @@ var Pe = class {
1517
1694
  r = 0, i = !1;
1518
1695
  },
1519
1696
  onError: () => {
1520
- this.destroyed || e !== this.loadGen || i || (r += 1, r >= je && (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.`))));
1697
+ this.destroyed || e !== this.loadGen || i || (r += 1, r >= Re && (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.`))));
1521
1698
  }
1522
1699
  });
1523
1700
  }
@@ -1539,7 +1716,7 @@ var Pe = class {
1539
1716
  }
1540
1717
  t.controls = !1;
1541
1718
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
1542
- r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = we({
1719
+ r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = je({
1543
1720
  root: n,
1544
1721
  video: t,
1545
1722
  player: this,
@@ -1552,7 +1729,7 @@ var Pe = class {
1552
1729
  unmountControls() {
1553
1730
  this.controlBar?.destroy(), this.controlBar = null;
1554
1731
  let e = this.container;
1555
- e?.parentNode && (e.parentNode.insertBefore(this.opts.video, e), e.remove()), this.container = null;
1732
+ e && (e.parentNode ? (e.parentNode.insertBefore(this.opts.video, e), e.remove()) : this.opts.video.parentNode === e && e.removeChild(this.opts.video)), this.container = null;
1556
1733
  }
1557
1734
  attachPipListeners() {
1558
1735
  let e = this.opts.video, t = () => this.emit("pipchange", !0), n = () => this.emit("pipchange", !1);
@@ -1562,7 +1739,7 @@ var Pe = class {
1562
1739
  }
1563
1740
  startStats() {
1564
1741
  this.stopStats();
1565
- let e = Math.max(500, this.opts.statsIntervalMs ?? Me);
1742
+ let e = Math.max(500, this.opts.statsIntervalMs ?? ze);
1566
1743
  this.statsTimer = setInterval(() => {
1567
1744
  this.engine && this.emit("stats", this.engine.getStats());
1568
1745
  }, e);
@@ -1572,7 +1749,7 @@ var Pe = class {
1572
1749
  }
1573
1750
  scheduleLicenseRefresh() {
1574
1751
  this.stopRefresh();
1575
- let e = this.opts.licenseRefreshMs ?? Ne;
1752
+ let e = this.opts.licenseRefreshMs ?? Be;
1576
1753
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
1577
1754
  }
1578
1755
  stopRefresh() {
@@ -1581,10 +1758,13 @@ var Pe = class {
1581
1758
  async refreshLicense() {
1582
1759
  let e = this.engine;
1583
1760
  if (!(this.destroyed || !e)) try {
1584
- let t = await b(this.resolveOptions());
1761
+ let t = await y(this.resolveOptions());
1585
1762
  if (this.destroyed || this.engine !== e) return;
1586
- let n = t.drm?.widevine?.licenseUrl;
1587
- n && (e.setLicenseUrl(n), this._descriptor = t);
1763
+ let n = t.drm?.widevine?.licenseUrl, r = t.drm?.fairplay?.licenseUrl;
1764
+ (n || r) && (e.setLicenseUrls({
1765
+ widevine: n,
1766
+ fairplay: r
1767
+ }), this._descriptor = t);
1588
1768
  } catch (e) {
1589
1769
  e instanceof t && !i(e.code) && (this.stopRefresh(), this.destroyed || this.emit("warning", e));
1590
1770
  }
@@ -1606,7 +1786,7 @@ var Pe = class {
1606
1786
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
1607
1787
  }
1608
1788
  getStats() {
1609
- return this.engine?.getStats() ?? P;
1789
+ return this.engine?.getStats() ?? z;
1610
1790
  }
1611
1791
  getQualityLevels() {
1612
1792
  return this.engine?.getQualityLevels() ?? [];
@@ -1652,14 +1832,14 @@ var Pe = class {
1652
1832
  let e = ++this.loadGen;
1653
1833
  this.stopStats(), this.stopRefresh(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null;
1654
1834
  try {
1655
- let t = await x({
1835
+ let t = await b({
1656
1836
  ...this.resolveOptions(),
1657
- waitForLive: this.opts.waitForLive ? this.buildWaitConfig() : void 0
1837
+ waitForLive: this.opts.waitForLive ? this.buildWaitConfig(e) : void 0
1658
1838
  });
1659
- await this.onResolved(t, e);
1839
+ if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) throw this._lastError;
1660
1840
  } catch (n) {
1661
1841
  if (this.destroyed || this.abort.signal.aborted || e !== this.loadGen) return;
1662
- throw this.fail(n instanceof t ? n : new t("INTERNAL", 0, String(n))), n;
1842
+ throw n !== this._lastError && this.fail(n instanceof t ? n : new t("INTERNAL", 0, String(n))), n;
1663
1843
  }
1664
1844
  }
1665
1845
  on(e, t) {
@@ -1673,7 +1853,7 @@ var Pe = class {
1673
1853
  for (let e of Object.keys(this.listeners)) this.listeners[e]?.clear();
1674
1854
  }
1675
1855
  fail(e) {
1676
- this.setState("error"), this.emit("error", e);
1856
+ this._lastError = e, this.setState("error"), this.emit("error", e);
1677
1857
  }
1678
1858
  setState(e) {
1679
1859
  if (e !== this._state) switch (this._state = e, this.emit("statechange", e), e) {
@@ -1707,7 +1887,7 @@ function $(e) {
1707
1887
  }
1708
1888
  //#endregion
1709
1889
  //#region src/embed/host.ts
1710
- function Fe(e) {
1890
+ function He(e) {
1711
1891
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
1712
1892
  if (!e.src) throw Error("mountEmbed: opts.src is required");
1713
1893
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -1766,8 +1946,8 @@ function Fe(e) {
1766
1946
  }
1767
1947
  //#endregion
1768
1948
  //#region src/embed/page.ts
1769
- var Ie = /^[A-Za-z0-9:_.-]{1,128}$/;
1770
- function Le() {
1949
+ var Ue = /^[A-Za-z0-9:_.-]{1,128}$/;
1950
+ function We() {
1771
1951
  let e = window.HAVIK_EMBED_CONFIG;
1772
1952
  if (e) return {
1773
1953
  baseUrl: e.baseUrl ?? "",
@@ -1789,9 +1969,9 @@ function Le() {
1789
1969
  waitForLive: !0
1790
1970
  };
1791
1971
  }
1792
- async function Re() {
1793
- let e = Le();
1794
- Be();
1972
+ async function Ge() {
1973
+ let e = We();
1974
+ qe();
1795
1975
  let n = document.createElement("video");
1796
1976
  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);
1797
1977
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -1846,7 +2026,7 @@ async function Re() {
1846
2026
  code: e.code,
1847
2027
  message: e.message
1848
2028
  })), e.on("autoplayblocked", () => {
1849
- i({ type: "oddin:autoplayblocked" }), ze(n, e);
2029
+ i({ type: "oddin:autoplayblocked" }), Ke(n, e);
1850
2030
  });
1851
2031
  }, s = async (r) => {
1852
2032
  a?.destroy(), a = null;
@@ -1912,7 +2092,7 @@ async function Re() {
1912
2092
  a?.retry();
1913
2093
  break;
1914
2094
  case "oddin:load":
1915
- typeof e.matchUrn == "string" && Ie.test(e.matchUrn) && s(e.matchUrn);
2095
+ typeof e.matchUrn == "string" && Ue.test(e.matchUrn) && s(e.matchUrn);
1916
2096
  break;
1917
2097
  case "oddin:destroy":
1918
2098
  c();
@@ -1926,18 +2106,18 @@ async function Re() {
1926
2106
  protocol: 1
1927
2107
  });
1928
2108
  }
1929
- function ze(e, t) {
2109
+ function Ke(e, t) {
1930
2110
  if (document.getElementById("havik-tap")) return;
1931
2111
  let n = document.createElement("button");
1932
2112
  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", () => {
1933
2113
  n.remove(), t.play();
1934
2114
  }), document.body.appendChild(n);
1935
2115
  }
1936
- function Be() {
2116
+ function qe() {
1937
2117
  let e = document.createElement("style");
1938
2118
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
1939
2119
  }
1940
2120
  //#endregion
1941
- export { G as ODDIN_THEME, t as PlaybackError, q as applyTheme, Re as bootEmbed, k as clearDeviceId, Q as createPlayer, T as deriveEventsBaseUrl, oe as fetchCatalog, ce as fetchTournament, ue as flattenMatches, O as getDeviceId, N as isLowLatencyManifest, $ as isOddinMessage, j as licenseRequestHeaders, Fe as mountEmbed, y as pollToLive, x as resolveStream, w as subscribeLiveState, C as watchStatus };
2121
+ export { G as ODDIN_THEME, t as PlaybackError, q as applyTheme, Ge as bootEmbed, ue as clearDeviceId, Q as createPlayer, E as deriveEventsBaseUrl, I as detectDrmSupport, se as fetchCatalog, le as fetchTournament, C as flattenMatches, k as getDeviceId, R as isLowLatencyManifest, $ as isOddinMessage, fe as licenseRequestHeaders, He as mountEmbed, v as pollToLive, b as resolveStream, T as subscribeLiveState, w as watchStatus };
1942
2122
 
1943
2123
  //# sourceMappingURL=index.js.map