@oddin-gg/havik-player 1.14.0 → 1.15.1

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
@@ -1,6 +1,6 @@
1
- import e from "hls.js";
1
+ import e, { CapLevelController as t } from "hls.js";
2
2
  //#region src/core/errors.ts
3
- var t = class extends Error {
3
+ var n = class extends Error {
4
4
  code;
5
5
  httpStatus;
6
6
  retryAfterMs;
@@ -11,7 +11,7 @@ var t = class extends Error {
11
11
  super(n), this.name = "PlaybackError", this.code = e, this.httpStatus = t, this.retryAfterMs = r?.retryAfterMs, this.requestId = r?.requestId, this.serverCode = r?.serverCode, this.liveStartsAtMs = r?.liveStartsAtMs, r?.cause !== void 0 && (this.cause = r.cause);
12
12
  }
13
13
  };
14
- function n(e) {
14
+ function r(e) {
15
15
  switch (e) {
16
16
  case 400: return "INVALID_URN";
17
17
  case 401: return "UNAUTHORIZED";
@@ -24,101 +24,102 @@ function n(e) {
24
24
  default: return "INTERNAL";
25
25
  }
26
26
  }
27
- function r(e) {
27
+ function i(e) {
28
28
  return e === 401 ? "UNAUTHORIZED" : e === 403 ? "FORBIDDEN" : "DRM_CLIENT";
29
29
  }
30
- var i = /* @__PURE__ */ new Set([
30
+ var a = /* @__PURE__ */ new Set([
31
31
  "TOO_EARLY",
32
32
  "UNAVAILABLE",
33
33
  "RATE_LIMITED",
34
34
  "NETWORK"
35
35
  ]);
36
- function a(e) {
37
- return i.has(e);
36
+ function o(e) {
37
+ return a.has(e);
38
38
  }
39
39
  //#endregion
40
40
  //#region src/core/types.ts
41
- async function o(e) {
41
+ async function s(e) {
42
42
  let t = typeof e == "function" ? await e() : e;
43
43
  if (!t || !t.apiKey) throw Error("havik-player: credential is missing an apiKey");
44
44
  return t;
45
45
  }
46
46
  //#endregion
47
47
  //#region src/core/http.ts
48
- function s(e, n) {
48
+ function c(e, t) {
49
49
  let r = e.replace(/\/+$/, ""), i;
50
50
  try {
51
- i = new URL(r + n);
51
+ i = new URL(r + t);
52
52
  } catch {
53
- throw new t("INTERNAL", 0, `invalid baseUrl: ${JSON.stringify(e)}`);
53
+ throw new n("INTERNAL", 0, `invalid baseUrl: ${JSON.stringify(e)}`);
54
54
  }
55
- if (i.protocol !== "https:" && i.protocol !== "http:") throw new t("INTERNAL", 0, `baseUrl must be http(s), got: ${JSON.stringify(e)}`);
55
+ if (i.protocol !== "https:" && i.protocol !== "http:") throw new n("INTERNAL", 0, `baseUrl must be http(s), got: ${JSON.stringify(e)}`);
56
56
  return i.toString();
57
57
  }
58
- function c(e) {
58
+ function l(e) {
59
59
  if (!e) return;
60
60
  let t = Number(e);
61
61
  if (Number.isFinite(t)) return Math.max(0, Math.round(t * 1e3));
62
62
  let n = Date.parse(e);
63
63
  if (!Number.isNaN(n)) return Math.max(0, n - Date.now());
64
64
  }
65
- async function l(e, n) {
66
- let r = await o(n.credential), i = n.headers ?? new Headers();
65
+ async function u(e, t) {
66
+ let r = await s(t.credential), i = t.headers ?? new Headers();
67
+ if (t.apiHeaders) for (let [e, n] of Object.entries(t.apiHeaders)) i.has(e) || i.set(e, n);
67
68
  i.set("x-api-key", r.apiKey);
68
69
  try {
69
70
  return await fetch(e, {
70
- method: n.method ?? "GET",
71
+ method: t.method ?? "GET",
71
72
  headers: i,
72
- body: n.body,
73
- signal: n.signal,
73
+ body: t.body,
74
+ signal: t.signal,
74
75
  cache: "no-store",
75
76
  credentials: "omit",
76
77
  mode: "cors"
77
78
  });
78
79
  } catch (e) {
79
- throw n.signal?.aborted ? new t("ABORTED", 0, "request aborted", { cause: e }) : new t("NETWORK", 0, `network request failed: ${u(e)}`, { cause: e });
80
+ throw t.signal?.aborted ? new n("ABORTED", 0, "request aborted", { cause: e }) : new n("NETWORK", 0, `network request failed: ${d(e)}`, { cause: e });
80
81
  }
81
82
  }
82
- function u(e) {
83
+ function d(e) {
83
84
  return e instanceof Error ? e.message : String(e);
84
85
  }
85
86
  //#endregion
86
87
  //#region src/core/waitForLive.ts
87
- var d = 1e3, f = 3e4, p = 600 * 1e3, m = 300 * 1e3, h = 1e3, g = {
88
+ var f = 1e3, p = 3e4, m = 6e5, h = 3e5, g = 1e3, ee = {
88
89
  TOO_EARLY: "tooEarly",
89
90
  UNAVAILABLE: "unavailable",
90
91
  RATE_LIMITED: "rateLimited",
91
92
  NETWORK: "network"
92
93
  };
93
- function ee(e) {
94
- let t = e === !0 || e === !1 ? {} : e, n = Math.max(h, t.floorMs ?? d), r = Math.max(n, t.ceilMs ?? f), i = Math.max(0, t.imminentMs ?? p), a = Math.max(r, t.sanityFloorMs ?? m);
94
+ function te(e) {
95
+ let t = e === !0 || e === !1 ? {} : e, n = Math.max(g, t.floorMs ?? f), r = Math.max(n, t.ceilMs ?? p), i = Math.max(0, t.imminentMs ?? m), a = Math.max(r, t.sanityFloorMs ?? h);
95
96
  return {
96
97
  timeoutMs: t.timeoutMs,
97
98
  floorMs: n,
98
99
  ceilMs: r,
99
100
  imminentMs: i,
100
101
  sanityFloorMs: a,
101
- kickoffMs: te(t.kickoffAt),
102
+ kickoffMs: ne(t.kickoffAt),
102
103
  jitter: t.jitter ?? !0,
103
104
  onState: t.onState
104
105
  };
105
106
  }
106
- function te(e) {
107
+ function ne(e) {
107
108
  if (e == null) return;
108
109
  if (typeof e == "number") return Number.isFinite(e) ? e : void 0;
109
110
  let t = Date.parse(e);
110
111
  return Number.isFinite(t) ? t : void 0;
111
112
  }
112
- function ne(e, t, n) {
113
+ function _(e, t, n) {
113
114
  return Math.min(n, Math.max(t, e));
114
115
  }
115
- function _(e) {
116
+ function re(e) {
116
117
  return Math.round(e * (1 + .15 * Math.random()));
117
118
  }
118
- function re(e, t, n, r) {
119
+ function ie(e, t, n, r) {
119
120
  let i = r ?? n.kickoffMs, a = i == null ? void 0 : Math.max(0, i - t), o = Math.max(n.floorMs, e ?? 0);
120
121
  if (a == null || a <= n.imminentMs) return {
121
- delay: ne(e ?? n.floorMs, o, n.ceilMs),
122
+ delay: _(e ?? n.floorMs, o, n.ceilMs),
122
123
  ttkMs: a
123
124
  };
124
125
  let s = Math.min(Math.floor(a / 10), n.sanityFloorMs, a);
@@ -127,61 +128,62 @@ function re(e, t, n, r) {
127
128
  ttkMs: a
128
129
  };
129
130
  }
130
- function ie(e, n) {
131
+ function ae(e, t) {
131
132
  return new Promise((r, i) => {
132
- if (n?.aborted) {
133
- i(new t("ABORTED", 0, "wait-for-live aborted"));
133
+ if (t?.aborted) {
134
+ i(new n("ABORTED", 0, "wait-for-live aborted"));
134
135
  return;
135
136
  }
136
137
  let a = () => {
137
- clearTimeout(o), i(new t("ABORTED", 0, "wait-for-live aborted"));
138
+ clearTimeout(o), i(new n("ABORTED", 0, "wait-for-live aborted"));
138
139
  }, o = setTimeout(() => {
139
- n?.removeEventListener("abort", a), r();
140
+ t?.removeEventListener("abort", a), r();
140
141
  }, e);
141
- n?.addEventListener("abort", a, { once: !0 });
142
+ t?.addEventListener("abort", a, { once: !0 });
142
143
  });
143
144
  }
144
- async function v(e, n, r) {
145
- let i = ee(n), o = Date.now(), s = i.timeoutMs == null ? Infinity : o + i.timeoutMs, c = 0, l;
145
+ async function v(e, t, r) {
146
+ let i = te(t), a = Date.now(), s = i.timeoutMs == null ? Infinity : a + i.timeoutMs, c = 0, l;
146
147
  for (;;) {
147
- if (r?.aborted) throw new t("ABORTED", 0, "wait-for-live aborted");
148
+ if (r?.aborted) throw new n("ABORTED", 0, "wait-for-live aborted");
148
149
  try {
149
150
  return await e();
150
151
  } catch (e) {
151
- if (!(e instanceof t) || !a(e.code)) throw e;
152
+ if (!(e instanceof n) || !o(e.code)) throw e;
152
153
  c += 1, e.liveStartsAtMs != null && Number.isFinite(e.liveStartsAtMs) && (l = e.liveStartsAtMs);
153
- let n = Date.now(), u = re(e.retryAfterMs, n, i, l), d = u.ttkMs, f = u.delay;
154
- if (i.jitter && (f = _(f)), n + f > s) throw new t("TIMEOUT", e.httpStatus, `timed out after ${i.timeoutMs}ms waiting for the stream to go live`, {
154
+ let t = Date.now(), u = ie(e.retryAfterMs, t, i, l), d = u.ttkMs, f = u.delay;
155
+ if (i.jitter && (f = re(f)), t + f > s) throw new n("TIMEOUT", e.httpStatus, `timed out after ${i.timeoutMs}ms waiting for the stream to go live`, {
155
156
  retryAfterMs: e.retryAfterMs,
156
157
  cause: e
157
158
  });
158
159
  i.onState?.({
159
- phase: g[e.code] ?? "unavailable",
160
+ phase: ee[e.code] ?? "unavailable",
160
161
  retryInMs: f,
161
162
  attempt: c,
162
- elapsedMs: n - o,
163
+ elapsedMs: t - a,
163
164
  ttkMs: d
164
- }), await ie(f, r);
165
+ }), await ae(f, r);
165
166
  }
166
167
  }
167
168
  }
168
169
  //#endregion
169
170
  //#region src/core/resolve.ts
170
171
  async function y(e) {
171
- if (!e.matchUrn || typeof e.matchUrn != "string") throw new t("INVALID_URN", 0, "resolveStream: a non-empty matchUrn is required");
172
- let n = await l(s(e.baseUrl, `/v1/playback/${encodeURIComponent(e.matchUrn)}`), {
172
+ if (!e.matchUrn || typeof e.matchUrn != "string") throw new n("INVALID_URN", 0, "resolveStream: a non-empty matchUrn is required");
173
+ let t = await u(c(e.baseUrl, `/v1/playback/${encodeURIComponent(e.matchUrn)}`), {
173
174
  credential: e.credential,
174
- signal: e.signal
175
+ signal: e.signal,
176
+ apiHeaders: e.apiHeaders
175
177
  });
176
- if (!n.ok) throw await ae(n);
177
- return S(await n.json(), e.matchUrn);
178
+ if (!t.ok) throw await oe(t);
179
+ return se(await t.json(), e.matchUrn);
178
180
  }
179
181
  function b(e) {
180
182
  let t = () => y(e);
181
183
  return e.waitForLive ? v(t, e.waitForLive, e.signal) : t();
182
184
  }
183
- async function ae(e) {
184
- let r = c(e.headers.get("retry-after")), i = e.headers.get("x-request-id") ?? e.headers.get("x-amzn-requestid") ?? void 0, a = e.statusText || `HTTP ${e.status}`, o, s;
185
+ async function oe(e) {
186
+ let t = l(e.headers.get("retry-after")), i = e.headers.get("x-request-id") ?? e.headers.get("x-amzn-requestid") ?? void 0, a = e.statusText || `HTTP ${e.status}`, o, s;
185
187
  try {
186
188
  let t = await e.json();
187
189
  if (t?.error?.message && (a = t.error.message), t?.error?.code && (o = t.error.code), typeof t?.liveStartsAt == "string") {
@@ -189,14 +191,14 @@ async function ae(e) {
189
191
  Number.isFinite(e) && (s = e);
190
192
  }
191
193
  } catch {}
192
- return new t(n(e.status), e.status, a, {
193
- retryAfterMs: r,
194
+ return new n(r(e.status), e.status, a, {
195
+ retryAfterMs: t,
194
196
  requestId: i,
195
197
  serverCode: o,
196
198
  liveStartsAtMs: s
197
199
  });
198
200
  }
199
- var oe = /* @__PURE__ */ new Set([
201
+ var x = /* @__PURE__ */ new Set([
200
202
  "matchUrn",
201
203
  "protocol",
202
204
  "drmEnabled",
@@ -207,61 +209,62 @@ var oe = /* @__PURE__ */ new Set([
207
209
  "expiresAt",
208
210
  "analytics"
209
211
  ]);
210
- function x(e, n) {
212
+ function S(e, t) {
211
213
  let r;
212
214
  try {
213
- r = new URL(n);
215
+ r = new URL(t);
214
216
  } catch {
215
- throw new t("INTERNAL", 0, `playback ${e} is not a valid URL`);
217
+ throw new n("INTERNAL", 0, `playback ${e} is not a valid URL`);
216
218
  }
217
- if (r.protocol !== "https:" && r.protocol !== "http:") throw new t("INTERNAL", 0, `playback ${e} has a disallowed scheme: ${r.protocol}`);
218
- return n;
219
+ if (r.protocol !== "https:" && r.protocol !== "http:") throw new n("INTERNAL", 0, `playback ${e} has a disallowed scheme: ${r.protocol}`);
220
+ return t;
219
221
  }
220
- function S(e, n) {
221
- if (!e.manifestUrl) throw new t("INTERNAL", 0, "playback response is missing manifestUrl");
222
+ function se(e, t) {
223
+ if (!e.manifestUrl) throw new n("INTERNAL", 0, "playback response is missing manifestUrl");
222
224
  let r = e.drmEnabled ?? !!e.drm, i = {
223
- matchUrn: e.matchUrn ?? n,
225
+ matchUrn: e.matchUrn ?? t,
224
226
  protocol: "hls",
225
227
  drmEnabled: r,
226
- manifestUrl: x("manifestUrl", e.manifestUrl),
228
+ manifestUrl: S("manifestUrl", e.manifestUrl),
227
229
  serverTime: e.serverTime ?? ""
228
230
  };
229
231
  if (e.liveStartsAt && (i.liveStartsAt = e.liveStartsAt), typeof e.analytics?.sid == "string" && e.analytics.sid && (i.analytics = { sid: e.analytics.sid }), r && e.drm) {
230
232
  let t = {};
231
- e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: x("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
232
- licenseUrl: x("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
233
- certificateUrl: x("fairplay certificateUrl", e.drm.fairplay.certificateUrl)
233
+ e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: S("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
234
+ licenseUrl: S("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
235
+ certificateUrl: S("fairplay certificateUrl", e.drm.fairplay.certificateUrl)
234
236
  }), (t.widevine || t.fairplay) && (i.drm = t);
235
237
  }
236
238
  let a = {};
237
- for (let [t, n] of Object.entries(e)) oe.has(t) || (a[t] = n);
239
+ for (let [t, n] of Object.entries(e)) x.has(t) || (a[t] = n);
238
240
  return Object.keys(a).length > 0 && (i.extensions = a), i;
239
241
  }
240
242
  //#endregion
241
243
  //#region src/core/catalog.ts
242
- async function C(e) {
243
- let r = new URLSearchParams();
244
- e.status && r.set("status", Array.isArray(e.status) ? e.status.join(",") : e.status), e.sport && r.set("sport", e.sport);
245
- let i = r.toString(), a = s(e.baseUrl, "/v1/catalog" + (i ? `?${i}` : "")), o = new Headers();
244
+ async function ce(e) {
245
+ let t = new URLSearchParams();
246
+ e.status && t.set("status", Array.isArray(e.status) ? e.status.join(",") : e.status), e.sport && t.set("sport", e.sport);
247
+ let i = t.toString(), a = c(e.baseUrl, "/v1/catalog" + (i ? `?${i}` : "")), o = new Headers();
246
248
  e.etag && o.set("If-None-Match", e.etag);
247
- let c = await l(a, {
249
+ let s = await u(a, {
248
250
  credential: e.credential,
249
251
  signal: e.signal,
250
- headers: o
252
+ headers: o,
253
+ apiHeaders: e.apiHeaders
251
254
  });
252
- if (c.status === 304) return {
255
+ if (s.status === 304) return {
253
256
  tournaments: [],
254
257
  etag: e.etag,
255
258
  notModified: !0
256
259
  };
257
- if (!c.ok) throw new t(n(c.status), c.status, `catalog request failed (HTTP ${c.status})`);
260
+ if (!s.ok) throw new n(r(s.status), s.status, `catalog request failed (HTTP ${s.status})`);
258
261
  return {
259
- tournaments: se(await c.json()),
260
- etag: c.headers.get("etag") ?? void 0,
262
+ tournaments: le(await s.json()),
263
+ etag: s.headers.get("etag") ?? void 0,
261
264
  notModified: !1
262
265
  };
263
266
  }
264
- function se(e) {
267
+ function le(e) {
265
268
  return (e.tournaments ?? []).map((e) => {
266
269
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
267
270
  return {
@@ -281,13 +284,14 @@ function se(e) {
281
284
  };
282
285
  });
283
286
  }
284
- async function ce(e) {
285
- let r = s(e.baseUrl, `/v1/tournaments/${encodeURIComponent(e.urn)}`), i = new Headers();
287
+ async function ue(e) {
288
+ let t = c(e.baseUrl, `/v1/tournaments/${encodeURIComponent(e.urn)}`), i = new Headers();
286
289
  e.etag && i.set("If-None-Match", e.etag);
287
- let a = await l(r, {
290
+ let a = await u(t, {
288
291
  credential: e.credential,
289
292
  signal: e.signal,
290
- headers: i
293
+ headers: i,
294
+ apiHeaders: e.apiHeaders
291
295
  });
292
296
  if (a.status === 304) return {
293
297
  etag: e.etag,
@@ -298,15 +302,15 @@ async function ce(e) {
298
302
  notModified: !1,
299
303
  notFound: !0
300
304
  };
301
- if (!a.ok) throw new t(n(a.status), a.status, `tournament request failed (HTTP ${a.status})`);
305
+ if (!a.ok) throw new n(r(a.status), a.status, `tournament request failed (HTTP ${a.status})`);
302
306
  return {
303
- tournament: le(await a.json()),
307
+ tournament: de(await a.json()),
304
308
  etag: a.headers.get("etag") ?? void 0,
305
309
  notModified: !1,
306
310
  notFound: !1
307
311
  };
308
312
  }
309
- function le(e) {
313
+ function de(e) {
310
314
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
311
315
  return {
312
316
  urn: t,
@@ -324,54 +328,55 @@ function le(e) {
324
328
  }))
325
329
  };
326
330
  }
327
- function ue(e) {
331
+ function fe(e) {
328
332
  return e.tournaments.flatMap((e) => e.matches);
329
333
  }
330
334
  //#endregion
331
335
  //#region src/core/poll.ts
332
- function de(e) {
333
- let r = Math.max(2e3, e.intervalMs ?? 1e4), i = new AbortController(), a, o, c, u = !1, d = () => {
334
- u || (a = setTimeout(f, r));
336
+ function pe(e) {
337
+ let t = Math.max(2e3, e.intervalMs ?? 1e4), i = new AbortController(), a, o, s, l = !1, d = () => {
338
+ l || (a = setTimeout(f, t));
335
339
  }, f = async () => {
336
340
  try {
337
- let r = s(e.baseUrl, `/v1/matches/${encodeURIComponent(e.matchUrn)}`), a = new Headers();
341
+ let t = c(e.baseUrl, `/v1/matches/${encodeURIComponent(e.matchUrn)}`), a = new Headers();
338
342
  o && a.set("If-None-Match", o);
339
- let u = await l(r, {
343
+ let l = await u(t, {
340
344
  credential: e.credential,
341
345
  signal: i.signal,
342
- headers: a
346
+ headers: a,
347
+ apiHeaders: e.apiHeaders
343
348
  });
344
- if (u.status !== 304) {
345
- if (!u.ok) throw new t(n(u.status), u.status, `match status request failed (HTTP ${u.status})`);
346
- o = u.headers.get("etag") ?? o;
347
- let r = await u.json(), i = r.status ?? "upcoming";
348
- i !== c && (c = i, e.onChange({
349
- matchUrn: r.matchUrn ?? e.matchUrn,
350
- matchName: r.matchName,
349
+ if (l.status !== 304) {
350
+ if (!l.ok) throw new n(r(l.status), l.status, `match status request failed (HTTP ${l.status})`);
351
+ o = l.headers.get("etag") ?? o;
352
+ let t = await l.json(), i = t.status ?? "upcoming";
353
+ i !== s && (s = i, e.onChange({
354
+ matchUrn: t.matchUrn ?? e.matchUrn,
355
+ matchName: t.matchName,
351
356
  status: i,
352
- datePlannedStart: r.datePlannedStart
357
+ datePlannedStart: t.datePlannedStart
353
358
  }));
354
359
  }
355
- } catch (n) {
356
- i.signal.aborted || e.onError?.(n instanceof t ? n : new t("NETWORK", 0, String(n)));
360
+ } catch (t) {
361
+ i.signal.aborted || e.onError?.(t instanceof n ? t : new n("NETWORK", 0, String(t)));
357
362
  } finally {
358
363
  d();
359
364
  }
360
365
  };
361
366
  return f(), { stop() {
362
- u = !0, i.abort(), a && clearTimeout(a);
367
+ l = !0, i.abort(), a && clearTimeout(a);
363
368
  } };
364
369
  }
365
370
  //#endregion
366
371
  //#region src/core/events.ts
367
- function w(e) {
372
+ function C(e) {
368
373
  let t = Math.max(250, e.minBackoffMs ?? 1e3), n = Math.max(t, e.maxBackoffMs ?? 15e3), r = new AbortController(), i = !1, a = (e) => new Promise((t) => {
369
374
  if (r.signal.aborted) return t();
370
375
  let n = setTimeout(t, e);
371
376
  r.signal.addEventListener("abort", () => {
372
377
  clearTimeout(n), t();
373
378
  }, { once: !0 });
374
- }), c = (t) => {
379
+ }), o = (t) => {
375
380
  e.onAlive?.();
376
381
  let n = "message", r = [];
377
382
  for (let e of t.split("\n")) {
@@ -380,19 +385,19 @@ function w(e) {
380
385
  let i = t.indexOf(":"), a = i < 0 ? t : t.slice(0, i), o = i < 0 ? "" : t.slice(i + 1);
381
386
  o.startsWith(" ") && (o = o.slice(1)), a === "event" ? n = o : a === "data" && r.push(o);
382
387
  }
383
- if (!(n !== "state" || r.length === 0)) try {
388
+ if (n === "state" && r.length !== 0) try {
384
389
  let t = JSON.parse(r.join("\n"));
385
390
  t && typeof t.state == "string" && e.onState(t);
386
391
  } catch {}
387
392
  }, l = /\r\n\r\n|\n\n|\r\r/, u = async (e, t) => {
388
- let n = e.getReader(), r = new TextDecoder(), a = "", o = !1;
393
+ let n = e.getReader(), r = new TextDecoder(), a = "", s = !1;
389
394
  try {
390
395
  for (;;) {
391
- let { value: e, done: s } = await n.read();
392
- if (s) return;
393
- !o && e && e.length > 0 && (o = !0, t()), a += r.decode(e, { stream: !0 });
396
+ let { value: e, done: c } = await n.read();
397
+ if (c) return;
398
+ !s && e && e.length > 0 && (s = !0, t()), a += r.decode(e, { stream: !0 });
394
399
  let u;
395
- for (; (u = l.exec(a)) !== null;) c(a.slice(0, u.index)), a = a.slice(u.index + u[0].length);
400
+ for (; (u = l.exec(a)) !== null;) o(a.slice(0, u.index)), a = a.slice(u.index + u[0].length);
396
401
  if (i) return;
397
402
  }
398
403
  } finally {
@@ -400,12 +405,12 @@ function w(e) {
400
405
  }
401
406
  };
402
407
  return (async () => {
403
- let c = t;
408
+ let o = t;
404
409
  for (; !i;) {
405
410
  try {
406
- let n = await o(e.credential);
411
+ let n = await s(e.credential);
407
412
  if (i) return;
408
- let a = s(e.eventsBaseUrl, `/v1/events/${encodeURIComponent(e.matchUrn)}`), l = await fetch(a, {
413
+ let a = c(e.eventsBaseUrl, `/v1/events/${encodeURIComponent(e.matchUrn)}`), l = await fetch(a, {
409
414
  method: "GET",
410
415
  headers: {
411
416
  "x-api-key": n.apiKey,
@@ -418,20 +423,20 @@ function w(e) {
418
423
  });
419
424
  if (i) return;
420
425
  !l.ok || !l.body ? e.onError?.(/* @__PURE__ */ Error(`live-state subscribe failed: HTTP ${l.status}`)) : await u(l.body, () => {
421
- c = t;
426
+ o = t;
422
427
  });
423
428
  } catch (t) {
424
429
  if (i || r.signal.aborted) return;
425
430
  e.onError?.(t);
426
431
  }
427
432
  if (i) return;
428
- await a(c), c = Math.min(n, c * 2);
433
+ await a(o), o = Math.min(n, o * 2);
429
434
  }
430
435
  })(), { close() {
431
436
  i || (i = !0, r.abort());
432
437
  } };
433
438
  }
434
- function T(e) {
439
+ function w(e) {
435
440
  try {
436
441
  let t = new URL(e);
437
442
  return t.hostname = `events.${t.hostname}`, t.pathname = "", t.search = "", t.origin;
@@ -441,27 +446,27 @@ function T(e) {
441
446
  }
442
447
  //#endregion
443
448
  //#region src/core/drm/deviceId.ts
444
- var E = "havik.player.deviceId", D;
445
- function O() {
446
- if (D) return D;
449
+ var T = "havik.player.deviceId", E;
450
+ function D() {
451
+ if (E) return E;
447
452
  try {
448
- let e = globalThis.localStorage?.getItem(E);
449
- if (e) return D = e, e;
453
+ let e = globalThis.localStorage?.getItem(T);
454
+ if (e) return E = e, e;
450
455
  } catch {}
451
- let e = pe();
452
- D = e;
456
+ let e = he();
457
+ E = e;
453
458
  try {
454
- globalThis.localStorage?.setItem(E, e);
459
+ globalThis.localStorage?.setItem(T, e);
455
460
  } catch {}
456
461
  return e;
457
462
  }
458
- function fe() {
459
- D = void 0;
463
+ function me() {
464
+ E = void 0;
460
465
  try {
461
- globalThis.localStorage?.removeItem(E);
466
+ globalThis.localStorage?.removeItem(T);
462
467
  } catch {}
463
468
  }
464
- function pe() {
469
+ function he() {
465
470
  let e = globalThis.crypto;
466
471
  if (e && typeof e.randomUUID == "function") return e.randomUUID();
467
472
  let t = /* @__PURE__ */ new Uint8Array(16);
@@ -473,22 +478,22 @@ function pe() {
473
478
  }
474
479
  //#endregion
475
480
  //#region src/core/drm/headers.ts
476
- function me(e, t, n = {}) {
481
+ function ge(e, t, n = {}) {
477
482
  let r = {
478
483
  "x-api-key": t.apiKey,
479
484
  "X-Match-Urn": e.matchUrn,
480
- "X-Device-Id": n.deviceId ?? O(),
485
+ "X-Device-Id": n.deviceId ?? D(),
481
486
  "Content-Type": "application/octet-stream"
482
487
  };
483
488
  return n.userId && (r["X-User-Id"] = n.userId), r;
484
489
  }
485
490
  //#endregion
486
491
  //#region src/core/drm/support.ts
487
- var he = 5e3, k = "video/mp4; codecs=\"avc1.42E01E\"", A = "audio/mp4; codecs=\"mp4a.40.2\"", ge = {
492
+ var _e = 5e3, O = "video/mp4; codecs=\"avc1.42E01E\"", k = "audio/mp4; codecs=\"mp4a.40.2\"", ve = {
488
493
  widevine: ["com.widevine.alpha"],
489
494
  fairplay: ["com.apple.fps"]
490
495
  };
491
- function _e(e) {
496
+ function A(e) {
492
497
  return e === "fairplay" ? [{
493
498
  initDataTypes: [
494
499
  "sinf",
@@ -496,24 +501,24 @@ function _e(e) {
496
501
  "cenc"
497
502
  ],
498
503
  videoCapabilities: [{
499
- contentType: k,
504
+ contentType: O,
500
505
  robustness: "",
501
506
  encryptionScheme: "cbcs"
502
507
  }],
503
508
  audioCapabilities: [{
504
- contentType: A,
509
+ contentType: k,
505
510
  robustness: "",
506
511
  encryptionScheme: "cbcs"
507
512
  }]
508
513
  }] : [{
509
514
  initDataTypes: ["cenc"],
510
515
  videoCapabilities: [{
511
- contentType: k,
516
+ contentType: O,
512
517
  robustness: "SW_SECURE_DECODE",
513
518
  encryptionScheme: "cbcs"
514
519
  }],
515
520
  audioCapabilities: [{
516
- contentType: A,
521
+ contentType: k,
517
522
  robustness: "SW_SECURE_CRYPTO",
518
523
  encryptionScheme: "cbcs"
519
524
  }]
@@ -530,7 +535,7 @@ function j(e, t) {
530
535
  });
531
536
  }
532
537
  async function M(e = {}) {
533
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? he, r = {};
538
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? _e, r = {};
534
539
  if (typeof window < "u" && window.isSecureContext === !1) return {
535
540
  verdict: "insecure-context",
536
541
  detail: r
@@ -542,7 +547,7 @@ async function M(e = {}) {
542
547
  };
543
548
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
544
549
  for (let e of t) {
545
- let t = ge[e];
550
+ let t = ve[e];
546
551
  if (!t) {
547
552
  r[e] = "unknown system";
548
553
  continue;
@@ -553,7 +558,7 @@ async function M(e = {}) {
553
558
  break;
554
559
  }
555
560
  try {
556
- return await j((await j(i(n, _e(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
561
+ return await j((await j(i(n, A(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
557
562
  verdict: "supported",
558
563
  system: e,
559
564
  detail: r
@@ -587,7 +592,7 @@ var F = { droppedFrames: 0 };
587
592
  function I(e) {
588
593
  return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
589
594
  }
590
- function ve(e, t) {
595
+ function ye(e, t) {
591
596
  let n = [
592
597
  ["playing", () => t("playing")],
593
598
  ["waiting", () => t("buffering")],
@@ -604,7 +609,7 @@ function ve(e, t) {
604
609
  }
605
610
  //#endregion
606
611
  //#region src/core/engine/native.ts
607
- function ye(e) {
612
+ function be(e) {
608
613
  try {
609
614
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
610
615
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -634,12 +639,12 @@ var z = class {
634
639
  this.deps = e;
635
640
  }
636
641
  async load() {
637
- let { video: e, descriptor: n, emit: r } = this.deps;
638
- if (n.drmEnabled) {
639
- if (!n.drm?.fairplay) {
642
+ let { video: e, descriptor: t, emit: r } = this.deps;
643
+ if (t.drmEnabled) {
644
+ if (!t.drm?.fairplay) {
640
645
  r({
641
646
  type: "error",
642
- error: new t("DRM_CLIENT", 0, "native HLS engine cannot satisfy this DRM (Widevine requires the hls.js engine); use engine \"auto\"/\"hls\" on a Widevine-capable browser"),
647
+ error: new n("DRM_CLIENT", 0, "native HLS engine cannot satisfy this DRM (Widevine requires the hls.js engine); use engine \"auto\"/\"hls\" on a Widevine-capable browser"),
643
648
  fatal: !0
644
649
  });
645
650
  return;
@@ -648,25 +653,25 @@ var z = class {
648
653
  }
649
654
  this.onLoaded = () => r({ type: "ready" }), this.onError = () => r({
650
655
  type: "error",
651
- error: new t("INTERNAL", 0, "native HLS playback error"),
656
+ error: new n("INTERNAL", 0, "native HLS playback error"),
652
657
  fatal: !0
653
- }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = be(n.manifestUrl, n.analytics?.sid), e.load();
658
+ }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = xe(t.manifestUrl, t.analytics?.sid), e.load();
654
659
  }
655
660
  armFairPlay() {
656
- let { video: e, descriptor: n, credential: i, deviceId: a, userId: o, emit: s } = this.deps, c = n.drm?.fairplay;
661
+ let { video: e, descriptor: t, credential: r, deviceId: a, userId: o, emit: s } = this.deps, c = t.drm?.fairplay;
657
662
  if (!c) return;
658
663
  this.fairplayLicenseUrl = c.licenseUrl;
659
- let l = (e, n, i) => s({
664
+ let l = (e, t, r) => s({
660
665
  type: "error",
661
- error: new t(r(n), n, e),
662
- fatal: i
666
+ error: new n(i(t), t, e),
667
+ fatal: r
663
668
  });
664
- this.onEncrypted = (t) => {
665
- let r = t;
666
- !r.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = ye(r.initData), (async () => {
669
+ this.onEncrypted = (n) => {
670
+ let i = n;
671
+ !i.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = be(i.initData), (async () => {
667
672
  try {
668
- let t = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
669
- initDataTypes: [r.initDataType],
673
+ let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
674
+ initDataTypes: [i.initDataType],
670
675
  videoCapabilities: [{
671
676
  contentType: "application/vnd.apple.mpegurl",
672
677
  robustness: ""
@@ -674,37 +679,37 @@ var z = class {
674
679
  distinctiveIdentifier: "not-allowed",
675
680
  persistentState: "not-allowed",
676
681
  sessionTypes: ["temporary"]
677
- }])).createMediaKeys(), s = await fetch(c.certificateUrl, { headers: { "x-api-key": i.apiKey } });
682
+ }])).createMediaKeys(), s = await fetch(c.certificateUrl, { headers: { "x-api-key": r.apiKey } });
678
683
  if (!s.ok) {
679
684
  this.fairplayArmed = !1, l(`FairPlay certificate request failed: ${s.status}`, s.status, !0);
680
685
  return;
681
686
  }
682
- await t.setServerCertificate(await s.arrayBuffer()), await e.setMediaKeys(t);
683
- let u = t.createSession();
687
+ await n.setServerCertificate(await s.arrayBuffer()), await e.setMediaKeys(n);
688
+ let u = n.createSession();
684
689
  this.keySession = u, u.addEventListener("message", (e) => {
685
690
  (async () => {
686
691
  try {
687
- let t = await fetch(this.fairplayLicenseUrl ?? c.licenseUrl, {
692
+ let n = await fetch(this.fairplayLicenseUrl ?? c.licenseUrl, {
688
693
  method: "POST",
689
694
  body: e.message,
690
695
  headers: {
691
- "x-api-key": i.apiKey,
692
- "X-Match-Urn": n.matchUrn,
696
+ "x-api-key": r.apiKey,
697
+ "X-Match-Urn": t.matchUrn,
693
698
  "X-Device-Id": a,
694
699
  ...o ? { "X-User-Id": o } : {},
695
700
  ...this.fairplayKeyIV ? { "X-Key-Iv": this.fairplayKeyIV } : {}
696
701
  }
697
702
  });
698
- if (!t.ok) {
699
- l(`FairPlay license request failed: ${t.status}`, t.status, !0);
703
+ if (!n.ok) {
704
+ l(`FairPlay license request failed: ${n.status}`, n.status, !0);
700
705
  return;
701
706
  }
702
- await u.update(new Uint8Array(await t.arrayBuffer()));
707
+ await u.update(new Uint8Array(await n.arrayBuffer()));
703
708
  } catch (e) {
704
709
  l(`FairPlay license request failed: ${String(e)}`, 0, !0);
705
710
  }
706
711
  })();
707
- }), await u.generateRequest(r.initDataType, r.initData);
712
+ }), await u.generateRequest(i.initDataType, i.initData);
708
713
  } catch (e) {
709
714
  this.fairplayArmed = !1, l(`FairPlay key session failed: ${String(e)}`, 0, !0);
710
715
  }
@@ -778,25 +783,25 @@ var z = class {
778
783
  } catch {}
779
784
  }
780
785
  };
781
- function be(e, t) {
786
+ function xe(e, t) {
782
787
  return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
783
788
  }
784
789
  //#endregion
785
790
  //#region src/core/engine/hls.ts
786
- function xe() {
791
+ function Se() {
787
792
  return e.isSupported();
788
793
  }
789
- var Se = 3, Ce = 5, we = 3, Te = 3e4, Ee = 12e3, De = 3e3, B = 6e3, Oe = 4e3, ke = 2, Ae = 5, je = 2, Me = 9, Ne = .5;
790
- function Pe(e) {
794
+ var Ce = 3, we = 5, Te = 3, Ee = 3e4, De = 12e3, Oe = 3e3, B = 6e3, ke = 4e3, Ae = 2, je = 5, Me = 2e3, Ne = 3e4, Pe = 2, Fe = 9, Ie = .5;
795
+ function Le(e) {
791
796
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
792
797
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
793
798
  return;
794
799
  }
795
- e.liveSyncDuration ??= je;
796
- let t = Math.max(12, e.liveSyncDuration + Me);
797
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Fe(e, e.liveSyncDuration);
800
+ e.liveSyncDuration ??= Pe;
801
+ let t = Math.max(12, e.liveSyncDuration + Fe);
802
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Re(e, e.liveSyncDuration);
798
803
  }
799
- function Fe(e, t) {
804
+ function Re(e, t) {
800
805
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
801
806
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
802
807
  }
@@ -829,27 +834,47 @@ var V = class {
829
834
  chaseSuspended = !1;
830
835
  savedLiveMaxLatency;
831
836
  savedMaxSyncRate;
837
+ capObserver = null;
838
+ capTimer = null;
839
+ sizeCapLevel = null;
840
+ bitrateCapLevel = -1;
841
+ sizeCapManaged = !0;
842
+ dprMedia = null;
843
+ onDprChange = null;
832
844
  constructor(e) {
833
845
  this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
834
846
  }
835
847
  async load() {
836
- let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
848
+ let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
837
849
  if (e.drmEnabled && !o) {
838
850
  i({
839
851
  type: "error",
840
- error: new t("DRM_CLIENT", 0, e.drm?.fairplay?.licenseUrl && a ? "FairPlay cannot be played on the hls.js engine (WebKit binds keys via the media sinf box); use engine \"auto\" or \"native\"" : "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)"),
852
+ error: new n("DRM_CLIENT", 0, e.drm?.fairplay?.licenseUrl && a ? "FairPlay cannot be played on the hls.js engine (WebKit binds keys via the media sinf box); use engine \"auto\" or \"native\"" : "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)"),
841
853
  fatal: !0
842
854
  });
843
855
  return;
844
856
  }
845
- this.startHls(), n !== !1 && typeof document < "u" && (this.onVisibility = () => {
857
+ this.startHls(), t !== !1 && typeof document < "u" && (this.onVisibility = () => {
846
858
  let e = this.hls;
847
859
  if (document.visibilityState !== "visible" || !e) return;
848
860
  let t = e.liveSyncPosition;
849
861
  if (typeof t != "number") return;
850
862
  let n = typeof e.targetLatency == "number" && e.targetLatency > 0 ? e.targetLatency : 3;
851
863
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
852
- }, document.addEventListener("visibilitychange", this.onVisibility)), this.onSeeking = () => this.clampOverSeek(), r.addEventListener("seeking", this.onSeeking);
864
+ }, document.addEventListener("visibilitychange", this.onVisibility)), this.onSeeking = () => this.clampOverSeek(), r.addEventListener("seeking", this.onSeeking), this.sizeCapManaged && typeof ResizeObserver < "u" && (this.capObserver = new ResizeObserver(() => this.onMediaResize()), this.capObserver.observe(r), this.watchDprChanges());
865
+ }
866
+ watchDprChanges() {
867
+ if (typeof matchMedia != "function") return;
868
+ this.unwatchDprChanges();
869
+ let e = typeof self < "u" && self.devicePixelRatio || 1, t = matchMedia(`(resolution: ${e}dppx)`);
870
+ if (typeof t.addEventListener != "function") return;
871
+ let n = () => {
872
+ this.onMediaResize(), this.watchDprChanges();
873
+ };
874
+ t.addEventListener("change", n), this.dprMedia = t, this.onDprChange = n;
875
+ }
876
+ unwatchDprChanges() {
877
+ this.dprMedia && this.onDprChange && this.dprMedia.removeEventListener("change", this.onDprChange), this.dprMedia = null, this.onDprChange = null;
853
878
  }
854
879
  clampOverSeek() {
855
880
  let e = this.hls;
@@ -857,7 +882,7 @@ var V = class {
857
882
  let t = e.liveSyncPosition;
858
883
  if (typeof t != "number" || !Number.isFinite(t)) return;
859
884
  let { video: n } = this.deps;
860
- n.currentTime <= t + Ne || (n.currentTime = t);
885
+ n.currentTime <= t + Ie || (n.currentTime = t);
861
886
  }
862
887
  startHls() {
863
888
  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 = {
@@ -868,7 +893,7 @@ var V = class {
868
893
  maxBufferHole: .5,
869
894
  maxLiveSyncPlaybackRate: 1.5,
870
895
  abrEwmaDefaultEstimate: 1e6,
871
- capLevelToPlayerSize: !0,
896
+ capLevelToPlayerSize: !1,
872
897
  ...u ? {
873
898
  emeEnabled: !0,
874
899
  drmSystems: { ...this.licenseUrl ? { "com.widevine.alpha": { licenseUrl: this.licenseUrl } } : {} },
@@ -893,14 +918,14 @@ var V = class {
893
918
  } } : {},
894
919
  ...c
895
920
  };
896
- Pe(d);
921
+ Le(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
897
922
  let f = new e(d);
898
923
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
899
- this.mediaRecoveries = 0, this.networkRecoveries = 0, l({ type: "ready" });
924
+ this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
900
925
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
901
926
  t?.frag?.type === "main" && (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
902
927
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.recoveryResetTimer = null;
903
- }, Te));
928
+ }, Ee));
904
929
  }), f.on(e.Events.FRAG_LOADING, (e, t) => {
905
930
  t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
906
931
  }), f.on(e.Events.FRAG_LOADED, (e, t) => {
@@ -916,7 +941,7 @@ var V = class {
916
941
  }));
917
942
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => {
918
943
  this.checkEndStale(), this.checkStarvation();
919
- }, De), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
944
+ }, Oe), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
920
945
  this.renditionSwitches += 1, l({
921
946
  type: "qualitychange",
922
947
  index: t.level
@@ -946,15 +971,54 @@ var V = class {
946
971
  }
947
972
  setMaxBitrate(e) {
948
973
  let t = this.hls;
949
- if (!t) return;
950
- if (e == null) {
951
- t.autoLevelCapping = -1;
974
+ if (t) {
975
+ if (e == null) this.bitrateCapLevel = -1;
976
+ else {
977
+ let n = -1;
978
+ t.levels.forEach((t, r) => {
979
+ t.bitrate <= e && (n = r);
980
+ }), this.bitrateCapLevel = n;
981
+ }
982
+ this.pushCap();
983
+ }
984
+ }
985
+ computeSizeCapTarget() {
986
+ let e = this.hls;
987
+ if (!e || e.levels.length === 0) return null;
988
+ let { video: n } = this.deps, r = n.getBoundingClientRect(), i = r.width, a = r.height;
989
+ if (!i && !a && (i = n.width || 0, a = n.height || 0), !(i > 0) || !(a > 0)) return null;
990
+ let o = e.config ?? {}, s = 1;
991
+ return !o.ignoreDevicePixelRatio && typeof self < "u" && self.devicePixelRatio > 0 && (s = self.devicePixelRatio), s = Math.min(s, o.maxDevicePixelRatio ?? Infinity), t.getMaxLevelByMediaSize(e.levels, i * s, a * s);
992
+ }
993
+ onMediaResize() {
994
+ if (!this.sizeCapManaged) return;
995
+ let e = this.computeSizeCapTarget();
996
+ if (e == null) return;
997
+ if (this.sizeCapLevel == null) {
998
+ this.applySizeCap();
952
999
  return;
953
1000
  }
954
- let n = -1;
955
- t.levels.forEach((t, r) => {
956
- t.bitrate <= e && (n = r);
957
- }), t.autoLevelCapping = n;
1001
+ if (e === this.sizeCapLevel) {
1002
+ this.capTimer &&= (clearTimeout(this.capTimer), null);
1003
+ return;
1004
+ }
1005
+ this.capTimer && clearTimeout(this.capTimer);
1006
+ let t = e > this.sizeCapLevel ? Me : Ne;
1007
+ this.capTimer = setTimeout(() => {
1008
+ this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1009
+ }, t);
1010
+ }
1011
+ applySizeCap() {
1012
+ if (!this.sizeCapManaged) return;
1013
+ this.capTimer &&= (clearTimeout(this.capTimer), null);
1014
+ let e = this.computeSizeCapTarget();
1015
+ e != null && (this.sizeCapLevel = e, this.pushCap());
1016
+ }
1017
+ pushCap() {
1018
+ let e = this.hls;
1019
+ if (!e) return;
1020
+ let t = this.sizeCapLevel ?? -1, n = this.bitrateCapLevel;
1021
+ e.autoLevelCapping = t < 0 ? n : n < 0 ? t : Math.min(t, n);
958
1022
  }
959
1023
  getAudioTracks() {
960
1024
  return this.hls ? this.hls.audioTracks.map((e) => ({
@@ -1020,7 +1084,7 @@ var V = class {
1020
1084
  return;
1021
1085
  }
1022
1086
  let e = Date.now() - this.lastAdvanceMs;
1023
- !this.chaseSuspended && e > B && this.suspendChasing(), e > Ee && this.emitEnded("stalled");
1087
+ !this.chaseSuspended && e > B && this.suspendChasing(), e > De && this.emitEnded("stalled");
1024
1088
  }
1025
1089
  checkStarvation() {
1026
1090
  let e = this.hls, { video: t } = this.deps;
@@ -1029,12 +1093,12 @@ var V = class {
1029
1093
  this.starvedTicks = 0;
1030
1094
  return;
1031
1095
  }
1032
- let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < B, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Oe;
1096
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < B, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > ke;
1033
1097
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1034
1098
  this.starvedTicks = 0;
1035
1099
  return;
1036
1100
  }
1037
- if (!(++this.starvedTicks < ke) && (this.starvedTicks = 0, !(this.starvationKicks >= Ae))) {
1101
+ if (!(++this.starvedTicks < Ae) && (this.starvedTicks = 0, !(this.starvationKicks >= je))) {
1038
1102
  this.starvationKicks += 1, console.warn("havik-player: video starved while the live playlist advances (wedged hls.js fragment pipeline) — restarting load at the live edge");
1039
1103
  try {
1040
1104
  e.stopLoad(), e.startLoad(-1);
@@ -1068,7 +1132,7 @@ var V = class {
1068
1132
  } catch {}
1069
1133
  }
1070
1134
  destroy() {
1071
- if (this.clearRecoveryReset(), this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.onVisibility && typeof document < "u" && (document.removeEventListener("visibilitychange", this.onVisibility), this.onVisibility = null), this.onSeeking &&= (this.deps.video.removeEventListener("seeking", this.onSeeking), null), this.networkRetryTimer &&= (clearTimeout(this.networkRetryTimer), null), this.licenseRetryTimer &&= (clearTimeout(this.licenseRetryTimer), null), this.hls) {
1135
+ if (this.clearRecoveryReset(), this.capTimer &&= (clearTimeout(this.capTimer), null), this.capObserver &&= (this.capObserver.disconnect(), null), this.unwatchDprChanges(), this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.onVisibility && typeof document < "u" && (document.removeEventListener("visibilitychange", this.onVisibility), this.onVisibility = null), this.onSeeking &&= (this.deps.video.removeEventListener("seeking", this.onSeeking), null), this.networkRetryTimer &&= (clearTimeout(this.networkRetryTimer), null), this.licenseRetryTimer &&= (clearTimeout(this.licenseRetryTimer), null), this.hls) {
1072
1136
  try {
1073
1137
  this.hls.destroy();
1074
1138
  } catch (e) {
@@ -1081,9 +1145,9 @@ var V = class {
1081
1145
  this.recoveryResetTimer &&= (clearTimeout(this.recoveryResetTimer), null);
1082
1146
  }
1083
1147
  scheduleLicenseRetry(t) {
1084
- if (!(t.details === e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED || t.details === e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED)) return !1;
1148
+ if (t.details !== e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED && t.details !== e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED) return !1;
1085
1149
  let n = t.response?.code ?? 0;
1086
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= we) return !1;
1150
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= Te) return !1;
1087
1151
  this.licenseRecoveries += 1;
1088
1152
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1089
1153
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1111,7 +1175,7 @@ var V = class {
1111
1175
  return;
1112
1176
  }
1113
1177
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1114
- if (this.networkRecoveries < Ce) {
1178
+ if (this.networkRecoveries < we) {
1115
1179
  this.networkRecoveries += 1;
1116
1180
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1117
1181
  this.networkRetryTimer = setTimeout(() => {
@@ -1127,7 +1191,7 @@ var V = class {
1127
1191
  return;
1128
1192
  }
1129
1193
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1130
- if (this.mediaRecoveries < Se) {
1194
+ if (this.mediaRecoveries < Ce) {
1131
1195
  this.mediaRecoveries += 1, n.recoverMediaError();
1132
1196
  return;
1133
1197
  }
@@ -1145,11 +1209,11 @@ var V = class {
1145
1209
  });
1146
1210
  }
1147
1211
  }
1148
- toError(e, n = "PLAYBACK") {
1149
- let i = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1150
- return new t(n === "DRM" ? r(i) : i === 401 ? "UNAUTHORIZED" : i === 403 ? "FORBIDDEN" : "INTERNAL", i, `${n} error: ${a}${o ? ` (${o})` : ""}`);
1212
+ toError(e, t = "PLAYBACK") {
1213
+ let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1214
+ return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1151
1215
  }
1152
- }, Ie = 15e3, Le = 100, H = 1, U = {
1216
+ }, ze = 15e3, Be = 100, H = 1, U = {
1153
1217
  idle: "PLAYER_STATE_IDLE",
1154
1218
  waiting: "PLAYER_STATE_WAITING",
1155
1219
  loading: "PLAYER_STATE_LOADING",
@@ -1159,7 +1223,7 @@ var V = class {
1159
1223
  ended: "PLAYER_STATE_ENDED",
1160
1224
  error: "PLAYER_STATE_ERROR"
1161
1225
  };
1162
- function Re(e) {
1226
+ function Ve(e) {
1163
1227
  try {
1164
1228
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1165
1229
  return !n || r.length === 0 || n !== "feed" && !n.startsWith("feed-") ? void 0 : (t.hostname = [n.replace(/^feed/, "beacons"), ...r].join("."), t.pathname = "", t.search = "", t.origin);
@@ -1167,7 +1231,7 @@ function Re(e) {
1167
1231
  return;
1168
1232
  }
1169
1233
  }
1170
- var ze = class {
1234
+ var He = class {
1171
1235
  opts;
1172
1236
  url;
1173
1237
  now;
@@ -1202,7 +1266,7 @@ var ze = class {
1202
1266
  constructor(e) {
1203
1267
  this.opts = {
1204
1268
  ...e,
1205
- heartbeatMs: e.heartbeatMs ?? Ie
1269
+ heartbeatMs: e.heartbeatMs ?? ze
1206
1270
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
1207
1271
  }
1208
1272
  start() {
@@ -1294,7 +1358,7 @@ var ze = class {
1294
1358
  };
1295
1359
  }
1296
1360
  enqueue(e) {
1297
- for (this.pending.push(this.wrap(e)); this.pending.length > Le;) {
1361
+ for (this.pending.push(this.wrap(e)); this.pending.length > Be;) {
1298
1362
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1299
1363
  this.pending.splice(e >= 0 ? e : 0, 1);
1300
1364
  }
@@ -1316,7 +1380,7 @@ var ze = class {
1316
1380
  keepalive: !0
1317
1381
  }).catch(() => {});
1318
1382
  }
1319
- }, Be = "1.14.0", W = {
1383
+ }, Ue = "1.15.1", W = {
1320
1384
  accent: "#E1B600",
1321
1385
  accentText: "#1B1C23",
1322
1386
  background: "#0e0f13",
@@ -1349,15 +1413,15 @@ function K(e, t) {
1349
1413
  }
1350
1414
  //#endregion
1351
1415
  //#region src/ui/styles.ts
1352
- var q = "havik-player-styles", Ve = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n.havik-spinner{width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n";
1353
- function He(e = document) {
1416
+ var q = "havik-player-styles", We = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n.havik-spinner{width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n";
1417
+ function Ge(e = document) {
1354
1418
  if (e.getElementById(q)) return;
1355
1419
  let t = e.createElement("style");
1356
- t.id = q, t.textContent = Ve, e.head.appendChild(t);
1420
+ t.id = q, t.textContent = We, e.head.appendChild(t);
1357
1421
  }
1358
1422
  //#endregion
1359
1423
  //#region src/ui/icons.ts
1360
- var Ue = {
1424
+ var Ke = {
1361
1425
  play: "<path d=\"M8 5v14l11-7z\"/>",
1362
1426
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1363
1427
  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\"/>",
@@ -1371,20 +1435,20 @@ var Ue = {
1371
1435
  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\"/>"
1372
1436
  };
1373
1437
  function J(e) {
1374
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Ue[e]}</svg>`;
1438
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Ke[e]}</svg>`;
1375
1439
  }
1376
1440
  //#endregion
1377
1441
  //#region src/ui/controls.ts
1378
- var We = 3e3, Ge = /* @__PURE__ */ new Set([
1442
+ var qe = 3e3, Je = /* @__PURE__ */ new Set([
1379
1443
  "GONE",
1380
1444
  "NOT_FOUND",
1381
1445
  "INVALID_URN",
1382
1446
  "UNAUTHORIZED"
1383
1447
  ]);
1384
- function Ke(e) {
1385
- return new qe(e);
1448
+ function Ye(e) {
1449
+ return new Xe(e);
1386
1450
  }
1387
- var qe = class {
1451
+ var Xe = class {
1388
1452
  root;
1389
1453
  video;
1390
1454
  player;
@@ -1424,7 +1488,7 @@ var qe = class {
1424
1488
  scrubbing = !1;
1425
1489
  offFns = [];
1426
1490
  constructor(e) {
1427
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? We, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, He(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");
1491
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? qe, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, Ge(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");
1428
1492
  let t = (e, t, n) => {
1429
1493
  let r = this.doc.createElement(e);
1430
1494
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1480,7 +1544,7 @@ var qe = class {
1480
1544
  this.setVolumeFill(t), this.player.setVolume(t), t > 0 && e.muted && this.player.setMuted(!1);
1481
1545
  }), 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) => {
1482
1546
  this.lastError = e, this.statusKind = null, this.syncState();
1483
- })), 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();
1547
+ })), 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.on(this.doc, "webkitfullscreenchange", () => this.syncFullscreen()), this.on(e, "webkitbeginfullscreen", () => this.syncFullscreen()), this.on(e, "webkitendfullscreen", () => this.syncFullscreen()), this.syncState();
1484
1548
  }
1485
1549
  togglePlay() {
1486
1550
  this.video.paused || this.video.ended ? this.player.play() : this.player.pause();
@@ -1511,7 +1575,7 @@ var qe = class {
1511
1575
  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);
1512
1576
  } else {
1513
1577
  let n = this.doc.createElement("div");
1514
- 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 && !Ge.has(this.lastError.code)) {
1578
+ 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 && !Je.has(this.lastError.code)) {
1515
1579
  let e = this.doc.createElement("button");
1516
1580
  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);
1517
1581
  }
@@ -1602,17 +1666,17 @@ var qe = class {
1602
1666
  renderQualityMenu() {
1603
1667
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1604
1668
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1605
- for (let n of e) this.menu.append(this.menuItem(Je(n), t === n.index, () => this.player.setQuality(n.index)));
1669
+ for (let n of e) this.menu.append(this.menuItem(Ze(n), t === n.index, () => this.player.setQuality(n.index)));
1606
1670
  }
1607
1671
  renderAudioMenu() {
1608
1672
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1609
1673
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1610
- for (let t of e) this.menu.append(this.menuItem(Ye(t), t.default, () => this.player.setAudioTrack(t.id)));
1674
+ for (let t of e) this.menu.append(this.menuItem(X(t), t.default, () => this.player.setAudioTrack(t.id)));
1611
1675
  }
1612
1676
  renderCcMenu() {
1613
1677
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1614
1678
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1615
- for (let t of e) this.menu.append(this.menuItem(Xe(t), !1, () => this.player.setTextTrack(t.id)));
1679
+ for (let t of e) this.menu.append(this.menuItem(Qe(t), !1, () => this.player.setTextTrack(t.id)));
1616
1680
  }
1617
1681
  onTracksChanged() {
1618
1682
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1631,14 +1695,18 @@ var qe = class {
1631
1695
  this.pipBtn.setAttribute("aria-pressed", e ? "true" : "false");
1632
1696
  }
1633
1697
  async toggleFullscreen() {
1634
- let e = this.root, t = this.doc;
1698
+ let e = this.root, t = this.doc, n = this.video;
1635
1699
  try {
1636
- t.fullscreenElement ? await t.exitFullscreen?.() : await e.requestFullscreen?.();
1700
+ if (typeof e.requestFullscreen == "function" || typeof e.webkitRequestFullscreen == "function") {
1701
+ t.fullscreenElement ?? t.webkitFullscreenElement ? typeof t.exitFullscreen == "function" ? await t.exitFullscreen() : t.webkitExitFullscreen?.() : typeof e.requestFullscreen == "function" ? await e.requestFullscreen() : e.webkitRequestFullscreen?.();
1702
+ return;
1703
+ }
1704
+ n.webkitSupportsFullscreen && typeof n.webkitEnterFullscreen == "function" && (n.webkitDisplayingFullscreen ? n.webkitExitFullscreen?.() : n.webkitEnterFullscreen());
1637
1705
  } catch {}
1638
1706
  }
1639
1707
  syncFullscreen() {
1640
- let e = this.doc.fullscreenElement === this.root;
1641
- this.setIcon(this.fsBtn, e ? "fullscreenExit" : "fullscreen"), this.fsBtn.setAttribute("aria-label", e ? "Exit fullscreen" : "Fullscreen");
1708
+ let e = this.doc, t = this.video, n = (e.fullscreenElement ?? e.webkitFullscreenElement) === this.root || t.webkitDisplayingFullscreen === !0;
1709
+ this.setIcon(this.fsBtn, n ? "fullscreenExit" : "fullscreen"), this.fsBtn.setAttribute("aria-label", n ? "Exit fullscreen" : "Fullscreen");
1642
1710
  }
1643
1711
  poke() {
1644
1712
  this.show(), this.scheduleHide();
@@ -1718,27 +1786,27 @@ function Y(e) {
1718
1786
  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");
1719
1787
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1720
1788
  }
1721
- function Je(e) {
1789
+ function Ze(e) {
1722
1790
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1723
1791
  }
1724
- function Ye(e) {
1792
+ function X(e) {
1725
1793
  return e.name || e.lang || `Track ${e.id}`;
1726
1794
  }
1727
- function Xe(e) {
1795
+ function Qe(e) {
1728
1796
  return e.name || e.lang || `Track ${e.id}`;
1729
1797
  }
1730
1798
  //#endregion
1731
1799
  //#region src/managed/index.ts
1732
- var Ze = 6e3, X = 45e3, Qe = 2e3, $e = 3, Z = 2e3, et = 480 * 1e3;
1800
+ var $e = 6e3, Z = 45e3, et = 2e3, tt = 3, nt = 2e3, rt = 48e4;
1733
1801
  async function Q(e) {
1734
- if (!e.video || typeof e.video.play != "function") throw new t("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1735
- if (!e.baseUrl) throw new t("INTERNAL", 0, "createPlayer: baseUrl is required");
1736
- if (!e.matchUrn) throw new t("INVALID_URN", 0, "createPlayer: matchUrn is required");
1737
- if (!e.credential) throw new t("INTERNAL", 0, "createPlayer: credential is required");
1738
- let n = new tt(e);
1739
- return await n.start(), n;
1802
+ if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1803
+ if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
1804
+ if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
1805
+ if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
1806
+ let t = new it(e);
1807
+ return await t.start(), t;
1740
1808
  }
1741
- var tt = class {
1809
+ var it = class {
1742
1810
  opts;
1743
1811
  abort = new AbortController();
1744
1812
  listeners = {};
@@ -1772,19 +1840,19 @@ var tt = class {
1772
1840
  }
1773
1841
  async start() {
1774
1842
  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");
1775
- let e = ++this.loadGen, n = this.resolveOptions();
1843
+ let e = ++this.loadGen, t = this.resolveOptions();
1776
1844
  try {
1777
- let t = await y(n);
1778
- if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) {
1845
+ let n = await y(t);
1846
+ if (await this.onResolved(n, e), e === this.loadGen && this._state === "error" && this._lastError) {
1779
1847
  let e = this._lastError;
1780
1848
  throw this.destroy(), e;
1781
1849
  }
1782
- } catch (n) {
1783
- if (this.opts.waitForLive && n instanceof t && a(n.code)) {
1784
- this.setState("waiting"), this.beginArming(e, n.liveStartsAtMs);
1850
+ } catch (t) {
1851
+ if (this.opts.waitForLive && t instanceof n && o(t.code)) {
1852
+ this.setState("waiting"), this.beginArming(e, t.liveStartsAtMs);
1785
1853
  return;
1786
1854
  }
1787
- throw n;
1855
+ throw t;
1788
1856
  }
1789
1857
  }
1790
1858
  resolveOptions() {
@@ -1792,11 +1860,12 @@ var tt = class {
1792
1860
  baseUrl: this.opts.baseUrl,
1793
1861
  matchUrn: this.opts.matchUrn,
1794
1862
  credential: this.opts.credential,
1795
- signal: this.abort.signal
1863
+ signal: this.abort.signal,
1864
+ apiHeaders: this.opts.apiHeaders
1796
1865
  };
1797
1866
  }
1798
1867
  beginArming(e, t) {
1799
- let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1868
+ let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? w(this.opts.baseUrl);
1800
1869
  n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1801
1870
  }
1802
1871
  beginArmingByPolling(e) {
@@ -1806,57 +1875,57 @@ var tt = class {
1806
1875
  waitForLive: this.buildWaitConfig(e)
1807
1876
  }).then((t) => {
1808
1877
  if (!(this.destroyed || this.abort.signal.aborted || e !== this.loadGen)) return this.onResolved(t, e);
1809
- }).catch((n) => {
1810
- this.destroyed || this.abort.signal.aborted || e !== this.loadGen || this.fail(n instanceof t ? n : new t("INTERNAL", 0, String(n)));
1878
+ }).catch((t) => {
1879
+ this.destroyed || this.abort.signal.aborted || e !== this.loadGen || this.fail(t instanceof n ? t : new n("INTERNAL", 0, String(t)));
1811
1880
  });
1812
1881
  }
1813
- armWithSse(e, n, r) {
1814
- let i = !1, o = !1, s = new AbortController();
1882
+ armWithSse(e, t, r) {
1883
+ let i = !1, a = !1, s = new AbortController();
1815
1884
  this.armFallbackCtrl = s, this.setState("waiting");
1816
1885
  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);
1817
1886
  setTimeout(() => {
1818
1887
  i || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1819
1888
  phase: "tooEarly",
1820
- retryInMs: X,
1889
+ retryInMs: Z,
1821
1890
  attempt: 1,
1822
1891
  elapsedMs: 0,
1823
1892
  ...d !== void 0 && d > Date.now() ? { ttkMs: d - Date.now() } : {}
1824
1893
  });
1825
1894
  }, 0);
1826
- let f = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, p = async (n) => {
1895
+ let f = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, p = async (t) => {
1827
1896
  if (!(i || f())) {
1828
1897
  i = !0, this.closeArming(), s.abort();
1829
1898
  try {
1830
- await this.onResolved(n, e);
1899
+ await this.onResolved(t, e);
1831
1900
  } catch (e) {
1832
1901
  if (f()) return;
1833
- this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e)));
1902
+ this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e)));
1834
1903
  }
1835
1904
  }
1836
1905
  }, m = () => {
1837
- i || o || f() || (o = !0, b({
1906
+ i || a || f() || (a = !0, b({
1838
1907
  ...this.resolveOptions(),
1839
1908
  signal: s.signal,
1840
1909
  waitForLive: this.buildWaitConfig(e)
1841
1910
  }).then((e) => p(e)).catch((e) => {
1842
- i || f() || s.signal.aborted || (i = !0, this.closeArming(), this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e))));
1911
+ i || f() || s.signal.aborted || (i = !0, this.closeArming(), this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e))));
1843
1912
  }));
1844
1913
  }, h = () => {
1845
1914
  i || f() || y(this.resolveOptions()).then((e) => p(e)).catch((e) => {
1846
- i || f() || e instanceof t && a(e.code) || (i = !0, this.closeArming(), s.abort(), this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e))));
1915
+ i || f() || e instanceof n && o(e.code) || (i = !0, this.closeArming(), s.abort(), this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e))));
1847
1916
  });
1848
1917
  }, g = (e) => {
1849
1918
  this.armSseTimer && clearTimeout(this.armSseTimer), this.armSseTimer = setTimeout(() => {
1850
1919
  !i && !f() && m();
1851
1920
  }, e);
1852
1921
  };
1853
- g(Ze), this.armingSub = w({
1854
- eventsBaseUrl: n,
1922
+ g($e), this.armingSub = C({
1923
+ eventsBaseUrl: t,
1855
1924
  matchUrn: this.opts.matchUrn,
1856
1925
  credential: this.opts.credential,
1857
- onAlive: () => g(X),
1926
+ onAlive: () => g(Z),
1858
1927
  onState: (e) => {
1859
- i || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * Qe);
1928
+ i || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * et);
1860
1929
  },
1861
1930
  onError: () => m()
1862
1931
  });
@@ -1876,11 +1945,11 @@ var tt = class {
1876
1945
  async onResolved(e, t) {
1877
1946
  if (this.destroyed || t !== this.loadGen) return;
1878
1947
  this._descriptor = e, this.endedHandled = !1;
1879
- let n = await o(this.opts.credential);
1948
+ let n = await s(this.opts.credential);
1880
1949
  if (this.destroyed || t !== this.loadGen) return;
1881
1950
  this.setState("loading");
1882
- let r = this.createEngine(e, n), i = ve(this.opts.video, (e) => {
1883
- this._state === "ended" || this._state === "error" || this.setState(e);
1951
+ let r = this.createEngine(e, n), i = ye(this.opts.video, (e) => {
1952
+ this._state !== "ended" && this._state !== "error" && this.setState(e);
1884
1953
  });
1885
1954
  if (await r.load(), this.destroyed || t !== this.loadGen) {
1886
1955
  i(), r.destroy();
@@ -1896,14 +1965,14 @@ var tt = class {
1896
1965
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
1897
1966
  let r = e.analytics?.sid;
1898
1967
  if (!r) return;
1899
- let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Re(this.opts.baseUrl);
1968
+ let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Ve(this.opts.baseUrl);
1900
1969
  if (!i) return;
1901
- let a = this.opts.video, o = new ze({
1970
+ let a = this.opts.video, o = new He({
1902
1971
  endpoint: i,
1903
1972
  sid: r,
1904
1973
  apiKey: n,
1905
1974
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
1906
- sdkVersion: Be,
1975
+ sdkVersion: Ue,
1907
1976
  sample: () => {
1908
1977
  let e = this.engine?.getStats() ?? F, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
1909
1978
  return {
@@ -1918,14 +1987,14 @@ var tt = class {
1918
1987
  let s = () => o.noteSeek();
1919
1988
  a.addEventListener("seeking", s), this.detachSeekListener = () => a.removeEventListener("seeking", s), o.start();
1920
1989
  }
1921
- createEngine(e, n) {
1990
+ createEngine(e, t) {
1922
1991
  let r = { ...this.opts.hlsConfig };
1923
1992
  this.opts.startLevel != null && (r.startLevel = this.opts.startLevel), this.opts.liveLatencyTarget != null && (r.liveSyncDuration = this.opts.liveLatencyTarget);
1924
1993
  let i = {
1925
1994
  video: this.opts.video,
1926
1995
  descriptor: e,
1927
- credential: n,
1928
- deviceId: O(),
1996
+ credential: t,
1997
+ deviceId: D(),
1929
1998
  userId: this.opts.userId,
1930
1999
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
1931
2000
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -1936,9 +2005,9 @@ var tt = class {
1936
2005
  if (a === "native") return new z(i);
1937
2006
  if (a === "hls") return new V(i);
1938
2007
  if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && L(this.opts.video) && R()) return new z(i);
1939
- if (xe()) return new V(i);
2008
+ if (Se()) return new V(i);
1940
2009
  if (L(this.opts.video)) return new z(i);
1941
- throw new t("INTERNAL", 0, "no supported HLS playback engine in this browser");
2010
+ throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
1942
2011
  }
1943
2012
  onEngineEvent(e) {
1944
2013
  if (!this.destroyed) switch (e.type) {
@@ -1960,19 +2029,17 @@ var tt = class {
1960
2029
  case "ended":
1961
2030
  this.onStreamEnded();
1962
2031
  break;
1963
- case "error":
1964
- this.analyticsSession?.noteError(e.error.code, e.error.message, e.fatal, e.error.httpStatus ?? 0, Math.round(this.opts.video.currentTime * 1e3)), e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
1965
- break;
2032
+ case "error": this.analyticsSession?.noteError(e.error.code, e.error.message, e.fatal, e.error.httpStatus ?? 0, Math.round(this.opts.video.currentTime * 1e3)), e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
1966
2033
  }
1967
2034
  }
1968
2035
  startLiveState(e) {
1969
2036
  if (this.opts.liveStateEvents === !1) return;
1970
- let n = this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1971
- if (!n) return;
2037
+ let t = this.opts.eventsBaseUrl ?? w(this.opts.baseUrl);
2038
+ if (!t) return;
1972
2039
  this.closeLiveState();
1973
2040
  let r = 0, i = !1;
1974
- this.liveStateSub = w({
1975
- eventsBaseUrl: n,
2041
+ this.liveStateSub = C({
2042
+ eventsBaseUrl: t,
1976
2043
  matchUrn: this.opts.matchUrn,
1977
2044
  credential: this.opts.credential,
1978
2045
  onState: (t) => {
@@ -1982,7 +2049,7 @@ var tt = class {
1982
2049
  r = 0, i = !1;
1983
2050
  },
1984
2051
  onError: () => {
1985
- this.destroyed || e !== this.loadGen || i || (r += 1, r >= $e && (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.`))));
2052
+ this.destroyed || e !== this.loadGen || i || (r += 1, r >= tt && (i = !0, this.emit("warning", new n("NETWORK", 0, `live-state events unreachable at ${t} after ${r} attempts — end-of-stream detection degraded to the HLS staleness watchdog. Check the events host, the api-key's allowed origins, and CSP connect-src.`))));
1986
2053
  }
1987
2054
  });
1988
2055
  }
@@ -2004,7 +2071,7 @@ var tt = class {
2004
2071
  }
2005
2072
  t.controls = !1;
2006
2073
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2007
- r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = Ke({
2074
+ r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = Ye({
2008
2075
  root: n,
2009
2076
  video: t,
2010
2077
  player: this,
@@ -2027,7 +2094,7 @@ var tt = class {
2027
2094
  }
2028
2095
  startStats() {
2029
2096
  this.stopStats();
2030
- let e = Math.max(500, this.opts.statsIntervalMs ?? Z);
2097
+ let e = Math.max(500, this.opts.statsIntervalMs ?? nt);
2031
2098
  this.statsTimer = setInterval(() => {
2032
2099
  if (!this.engine) return;
2033
2100
  let e = this.engine.getStats();
@@ -2039,7 +2106,7 @@ var tt = class {
2039
2106
  }
2040
2107
  scheduleLicenseRefresh() {
2041
2108
  this.stopRefresh();
2042
- let e = this.opts.licenseRefreshMs ?? et;
2109
+ let e = this.opts.licenseRefreshMs ?? rt;
2043
2110
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2044
2111
  }
2045
2112
  stopRefresh() {
@@ -2056,7 +2123,7 @@ var tt = class {
2056
2123
  fairplay: r
2057
2124
  }), this._descriptor = t);
2058
2125
  } catch (e) {
2059
- e instanceof t && !a(e.code) && (this.stopRefresh(), this.destroyed || this.emit("warning", e));
2126
+ e instanceof n && !o(e.code) && (this.stopRefresh(), this.destroyed || this.emit("warning", e));
2060
2127
  }
2061
2128
  }
2062
2129
  async play() {
@@ -2127,9 +2194,9 @@ var tt = class {
2127
2194
  waitForLive: this.opts.waitForLive ? this.buildWaitConfig(e) : void 0
2128
2195
  });
2129
2196
  if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) throw this._lastError;
2130
- } catch (n) {
2197
+ } catch (t) {
2131
2198
  if (this.destroyed || this.abort.signal.aborted || e !== this.loadGen) return;
2132
- throw n !== this._lastError && this.fail(n instanceof t ? n : new t("INTERNAL", 0, String(n))), n;
2199
+ throw t !== this._lastError && this.fail(t instanceof n ? t : new n("INTERNAL", 0, String(t))), t;
2133
2200
  }
2134
2201
  }
2135
2202
  on(e, t) {
@@ -2162,10 +2229,7 @@ var tt = class {
2162
2229
  case "paused":
2163
2230
  this.emit("paused");
2164
2231
  break;
2165
- case "ended":
2166
- this.emit("ended");
2167
- break;
2168
- default: break;
2232
+ case "ended": this.emit("ended");
2169
2233
  }
2170
2234
  }
2171
2235
  emit(e, t) {
@@ -2183,7 +2247,7 @@ function $(e) {
2183
2247
  }
2184
2248
  //#endregion
2185
2249
  //#region src/embed/host.ts
2186
- function nt(e) {
2250
+ function at(e) {
2187
2251
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2188
2252
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2189
2253
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2242,8 +2306,8 @@ function nt(e) {
2242
2306
  }
2243
2307
  //#endregion
2244
2308
  //#region src/embed/page.ts
2245
- var rt = /^[A-Za-z0-9:_.-]{1,128}$/;
2246
- function it() {
2309
+ var ot = /^[A-Za-z0-9:_.-]{1,128}$/;
2310
+ function st() {
2247
2311
  let e = window.HAVIK_EMBED_CONFIG;
2248
2312
  if (e) return {
2249
2313
  baseUrl: e.baseUrl ?? "",
@@ -2265,11 +2329,11 @@ function it() {
2265
2329
  waitForLive: !0
2266
2330
  };
2267
2331
  }
2268
- async function at() {
2269
- let e = it();
2270
- st();
2271
- let n = document.createElement("video");
2272
- 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);
2332
+ async function ct() {
2333
+ let e = st();
2334
+ ut();
2335
+ let t = document.createElement("video");
2336
+ t.setAttribute("playsinline", ""), t.muted = e.muted ?? !0, t.style.cssText = "width:100%;height:100%;object-fit:contain;background:#000;aspect-ratio:auto", document.body.appendChild(t);
2273
2337
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
2274
2338
  r || console.warn("havik-player: embed has no parentOrigin — the postMessage bridge is disabled (no events posted, no commands accepted). mountEmbed sets parentOrigin automatically.");
2275
2339
  let i = (e) => {
@@ -2322,13 +2386,13 @@ async function at() {
2322
2386
  code: e.code,
2323
2387
  message: e.message
2324
2388
  })), e.on("autoplayblocked", () => {
2325
- i({ type: "oddin:autoplayblocked" }), ot(n, e);
2389
+ i({ type: "oddin:autoplayblocked" }), lt(t, e);
2326
2390
  });
2327
2391
  }, s = async (r) => {
2328
2392
  a?.destroy(), a = null;
2329
2393
  try {
2330
- let t = await Q({
2331
- video: n,
2394
+ let n = await Q({
2395
+ video: t,
2332
2396
  baseUrl: e.baseUrl,
2333
2397
  matchUrn: r,
2334
2398
  credential: { apiKey: e.apiKey },
@@ -2336,14 +2400,14 @@ async function at() {
2336
2400
  muted: e.muted,
2337
2401
  waitForLive: e.waitForLive
2338
2402
  });
2339
- a = t;
2340
- let i = n.parentElement;
2341
- i?.classList.contains("havik-player") && (i.style.cssText = "position:absolute;inset:0;width:100%;height:100%;aspect-ratio:auto"), o(t);
2403
+ a = n;
2404
+ let i = t.parentElement;
2405
+ i?.classList.contains("havik-player") && (i.style.cssText = "position:absolute;inset:0;width:100%;height:100%;aspect-ratio:auto"), o(n);
2342
2406
  } catch (e) {
2343
- let n = e instanceof t ? e.code : "INTERNAL", r = e instanceof Error ? e.message : String(e);
2407
+ let t = e instanceof n ? e.code : "INTERNAL", r = e instanceof Error ? e.message : String(e);
2344
2408
  i({
2345
2409
  type: "oddin:error",
2346
- code: n,
2410
+ code: t,
2347
2411
  message: r
2348
2412
  });
2349
2413
  }
@@ -2388,11 +2452,9 @@ async function at() {
2388
2452
  a?.retry();
2389
2453
  break;
2390
2454
  case "oddin:load":
2391
- typeof e.matchUrn == "string" && rt.test(e.matchUrn) && s(e.matchUrn);
2392
- break;
2393
- case "oddin:destroy":
2394
- c();
2455
+ typeof e.matchUrn == "string" && ot.test(e.matchUrn) && s(e.matchUrn);
2395
2456
  break;
2457
+ case "oddin:destroy": c();
2396
2458
  }
2397
2459
  }, u = (e) => {
2398
2460
  !r || e.origin !== r || $(e.data) && l(e.data);
@@ -2402,18 +2464,18 @@ async function at() {
2402
2464
  protocol: 1
2403
2465
  });
2404
2466
  }
2405
- function ot(e, t) {
2467
+ function lt(e, t) {
2406
2468
  if (document.getElementById("havik-tap")) return;
2407
2469
  let n = document.createElement("button");
2408
2470
  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", () => {
2409
2471
  n.remove(), t.play();
2410
2472
  }), document.body.appendChild(n);
2411
2473
  }
2412
- function st() {
2474
+ function ut() {
2413
2475
  let e = document.createElement("style");
2414
2476
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2415
2477
  }
2416
2478
  //#endregion
2417
- export { W as ODDIN_THEME, t as PlaybackError, K as applyTheme, at as bootEmbed, fe as clearDeviceId, Q as createPlayer, T as deriveEventsBaseUrl, M as detectDrmSupport, C as fetchCatalog, ce as fetchTournament, ue as flattenMatches, O as getDeviceId, P as isLowLatencyManifest, $ as isOddinMessage, me as licenseRequestHeaders, nt as mountEmbed, v as pollToLive, b as resolveStream, w as subscribeLiveState, de as watchStatus };
2479
+ export { W as ODDIN_THEME, n as PlaybackError, K as applyTheme, ct as bootEmbed, me as clearDeviceId, Q as createPlayer, w as deriveEventsBaseUrl, M as detectDrmSupport, ce as fetchCatalog, ue as fetchTournament, fe as flattenMatches, D as getDeviceId, P as isLowLatencyManifest, $ as isOddinMessage, ge as licenseRequestHeaders, at as mountEmbed, v as pollToLive, b as resolveStream, C as subscribeLiveState, pe as watchStatus };
2418
2480
 
2419
2481
  //# sourceMappingURL=index.js.map