@oddin-gg/havik-player 1.15.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,102 +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();
67
- if (n.apiHeaders) for (let [e, t] of Object.entries(n.apiHeaders)) i.has(e) || i.set(e, t);
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);
68
68
  i.set("x-api-key", r.apiKey);
69
69
  try {
70
70
  return await fetch(e, {
71
- method: n.method ?? "GET",
71
+ method: t.method ?? "GET",
72
72
  headers: i,
73
- body: n.body,
74
- signal: n.signal,
73
+ body: t.body,
74
+ signal: t.signal,
75
75
  cache: "no-store",
76
76
  credentials: "omit",
77
77
  mode: "cors"
78
78
  });
79
79
  } catch (e) {
80
- 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 });
81
81
  }
82
82
  }
83
- function u(e) {
83
+ function d(e) {
84
84
  return e instanceof Error ? e.message : String(e);
85
85
  }
86
86
  //#endregion
87
87
  //#region src/core/waitForLive.ts
88
- 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 = {
89
89
  TOO_EARLY: "tooEarly",
90
90
  UNAVAILABLE: "unavailable",
91
91
  RATE_LIMITED: "rateLimited",
92
92
  NETWORK: "network"
93
93
  };
94
- function ee(e) {
95
- 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);
96
96
  return {
97
97
  timeoutMs: t.timeoutMs,
98
98
  floorMs: n,
99
99
  ceilMs: r,
100
100
  imminentMs: i,
101
101
  sanityFloorMs: a,
102
- kickoffMs: te(t.kickoffAt),
102
+ kickoffMs: ne(t.kickoffAt),
103
103
  jitter: t.jitter ?? !0,
104
104
  onState: t.onState
105
105
  };
106
106
  }
107
- function te(e) {
107
+ function ne(e) {
108
108
  if (e == null) return;
109
109
  if (typeof e == "number") return Number.isFinite(e) ? e : void 0;
110
110
  let t = Date.parse(e);
111
111
  return Number.isFinite(t) ? t : void 0;
112
112
  }
113
- function ne(e, t, n) {
113
+ function _(e, t, n) {
114
114
  return Math.min(n, Math.max(t, e));
115
115
  }
116
- function _(e) {
116
+ function re(e) {
117
117
  return Math.round(e * (1 + .15 * Math.random()));
118
118
  }
119
- function re(e, t, n, r) {
119
+ function ie(e, t, n, r) {
120
120
  let i = r ?? n.kickoffMs, a = i == null ? void 0 : Math.max(0, i - t), o = Math.max(n.floorMs, e ?? 0);
121
121
  if (a == null || a <= n.imminentMs) return {
122
- delay: ne(e ?? n.floorMs, o, n.ceilMs),
122
+ delay: _(e ?? n.floorMs, o, n.ceilMs),
123
123
  ttkMs: a
124
124
  };
125
125
  let s = Math.min(Math.floor(a / 10), n.sanityFloorMs, a);
@@ -128,62 +128,62 @@ function re(e, t, n, r) {
128
128
  ttkMs: a
129
129
  };
130
130
  }
131
- function ie(e, n) {
131
+ function ae(e, t) {
132
132
  return new Promise((r, i) => {
133
- if (n?.aborted) {
134
- i(new t("ABORTED", 0, "wait-for-live aborted"));
133
+ if (t?.aborted) {
134
+ i(new n("ABORTED", 0, "wait-for-live aborted"));
135
135
  return;
136
136
  }
137
137
  let a = () => {
138
- clearTimeout(o), i(new t("ABORTED", 0, "wait-for-live aborted"));
138
+ clearTimeout(o), i(new n("ABORTED", 0, "wait-for-live aborted"));
139
139
  }, o = setTimeout(() => {
140
- n?.removeEventListener("abort", a), r();
140
+ t?.removeEventListener("abort", a), r();
141
141
  }, e);
142
- n?.addEventListener("abort", a, { once: !0 });
142
+ t?.addEventListener("abort", a, { once: !0 });
143
143
  });
144
144
  }
145
- async function v(e, n, r) {
146
- 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;
147
147
  for (;;) {
148
- 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");
149
149
  try {
150
150
  return await e();
151
151
  } catch (e) {
152
- if (!(e instanceof t) || !a(e.code)) throw e;
152
+ if (!(e instanceof n) || !o(e.code)) throw e;
153
153
  c += 1, e.liveStartsAtMs != null && Number.isFinite(e.liveStartsAtMs) && (l = e.liveStartsAtMs);
154
- let n = Date.now(), u = re(e.retryAfterMs, n, i, l), d = u.ttkMs, f = u.delay;
155
- 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`, {
156
156
  retryAfterMs: e.retryAfterMs,
157
157
  cause: e
158
158
  });
159
159
  i.onState?.({
160
- phase: g[e.code] ?? "unavailable",
160
+ phase: ee[e.code] ?? "unavailable",
161
161
  retryInMs: f,
162
162
  attempt: c,
163
- elapsedMs: n - o,
163
+ elapsedMs: t - a,
164
164
  ttkMs: d
165
- }), await ie(f, r);
165
+ }), await ae(f, r);
166
166
  }
167
167
  }
168
168
  }
169
169
  //#endregion
170
170
  //#region src/core/resolve.ts
171
171
  async function y(e) {
172
- if (!e.matchUrn || typeof e.matchUrn != "string") throw new t("INVALID_URN", 0, "resolveStream: a non-empty matchUrn is required");
173
- 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)}`), {
174
174
  credential: e.credential,
175
175
  signal: e.signal,
176
176
  apiHeaders: e.apiHeaders
177
177
  });
178
- if (!n.ok) throw await ae(n);
179
- return S(await n.json(), e.matchUrn);
178
+ if (!t.ok) throw await oe(t);
179
+ return se(await t.json(), e.matchUrn);
180
180
  }
181
181
  function b(e) {
182
182
  let t = () => y(e);
183
183
  return e.waitForLive ? v(t, e.waitForLive, e.signal) : t();
184
184
  }
185
- async function ae(e) {
186
- 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;
187
187
  try {
188
188
  let t = await e.json();
189
189
  if (t?.error?.message && (a = t.error.message), t?.error?.code && (o = t.error.code), typeof t?.liveStartsAt == "string") {
@@ -191,14 +191,14 @@ async function ae(e) {
191
191
  Number.isFinite(e) && (s = e);
192
192
  }
193
193
  } catch {}
194
- return new t(n(e.status), e.status, a, {
195
- retryAfterMs: r,
194
+ return new n(r(e.status), e.status, a, {
195
+ retryAfterMs: t,
196
196
  requestId: i,
197
197
  serverCode: o,
198
198
  liveStartsAtMs: s
199
199
  });
200
200
  }
201
- var oe = /* @__PURE__ */ new Set([
201
+ var x = /* @__PURE__ */ new Set([
202
202
  "matchUrn",
203
203
  "protocol",
204
204
  "drmEnabled",
@@ -209,62 +209,62 @@ var oe = /* @__PURE__ */ new Set([
209
209
  "expiresAt",
210
210
  "analytics"
211
211
  ]);
212
- function x(e, n) {
212
+ function S(e, t) {
213
213
  let r;
214
214
  try {
215
- r = new URL(n);
215
+ r = new URL(t);
216
216
  } catch {
217
- 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`);
218
218
  }
219
- if (r.protocol !== "https:" && r.protocol !== "http:") throw new t("INTERNAL", 0, `playback ${e} has a disallowed scheme: ${r.protocol}`);
220
- 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;
221
221
  }
222
- function S(e, n) {
223
- 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");
224
224
  let r = e.drmEnabled ?? !!e.drm, i = {
225
- matchUrn: e.matchUrn ?? n,
225
+ matchUrn: e.matchUrn ?? t,
226
226
  protocol: "hls",
227
227
  drmEnabled: r,
228
- manifestUrl: x("manifestUrl", e.manifestUrl),
228
+ manifestUrl: S("manifestUrl", e.manifestUrl),
229
229
  serverTime: e.serverTime ?? ""
230
230
  };
231
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) {
232
232
  let t = {};
233
- e.drm.widevine?.licenseUrl && (t.widevine = { licenseUrl: x("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
234
- licenseUrl: x("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
235
- 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)
236
236
  }), (t.widevine || t.fairplay) && (i.drm = t);
237
237
  }
238
238
  let a = {};
239
- 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);
240
240
  return Object.keys(a).length > 0 && (i.extensions = a), i;
241
241
  }
242
242
  //#endregion
243
243
  //#region src/core/catalog.ts
244
- async function C(e) {
245
- let r = new URLSearchParams();
246
- e.status && r.set("status", Array.isArray(e.status) ? e.status.join(",") : e.status), e.sport && r.set("sport", e.sport);
247
- 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();
248
248
  e.etag && o.set("If-None-Match", e.etag);
249
- let c = await l(a, {
249
+ let s = await u(a, {
250
250
  credential: e.credential,
251
251
  signal: e.signal,
252
252
  headers: o,
253
253
  apiHeaders: e.apiHeaders
254
254
  });
255
- if (c.status === 304) return {
255
+ if (s.status === 304) return {
256
256
  tournaments: [],
257
257
  etag: e.etag,
258
258
  notModified: !0
259
259
  };
260
- 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})`);
261
261
  return {
262
- tournaments: se(await c.json()),
263
- etag: c.headers.get("etag") ?? void 0,
262
+ tournaments: le(await s.json()),
263
+ etag: s.headers.get("etag") ?? void 0,
264
264
  notModified: !1
265
265
  };
266
266
  }
267
- function se(e) {
267
+ function le(e) {
268
268
  return (e.tournaments ?? []).map((e) => {
269
269
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
270
270
  return {
@@ -284,10 +284,10 @@ function se(e) {
284
284
  };
285
285
  });
286
286
  }
287
- async function ce(e) {
288
- 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();
289
289
  e.etag && i.set("If-None-Match", e.etag);
290
- let a = await l(r, {
290
+ let a = await u(t, {
291
291
  credential: e.credential,
292
292
  signal: e.signal,
293
293
  headers: i,
@@ -302,15 +302,15 @@ async function ce(e) {
302
302
  notModified: !1,
303
303
  notFound: !0
304
304
  };
305
- 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})`);
306
306
  return {
307
- tournament: le(await a.json()),
307
+ tournament: de(await a.json()),
308
308
  etag: a.headers.get("etag") ?? void 0,
309
309
  notModified: !1,
310
310
  notFound: !1
311
311
  };
312
312
  }
313
- function le(e) {
313
+ function de(e) {
314
314
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
315
315
  return {
316
316
  urn: t,
@@ -328,55 +328,55 @@ function le(e) {
328
328
  }))
329
329
  };
330
330
  }
331
- function ue(e) {
331
+ function fe(e) {
332
332
  return e.tournaments.flatMap((e) => e.matches);
333
333
  }
334
334
  //#endregion
335
335
  //#region src/core/poll.ts
336
- function de(e) {
337
- let r = Math.max(2e3, e.intervalMs ?? 1e4), i = new AbortController(), a, o, c, u = !1, d = () => {
338
- 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));
339
339
  }, f = async () => {
340
340
  try {
341
- 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();
342
342
  o && a.set("If-None-Match", o);
343
- let u = await l(r, {
343
+ let l = await u(t, {
344
344
  credential: e.credential,
345
345
  signal: i.signal,
346
346
  headers: a,
347
347
  apiHeaders: e.apiHeaders
348
348
  });
349
- if (u.status !== 304) {
350
- if (!u.ok) throw new t(n(u.status), u.status, `match status request failed (HTTP ${u.status})`);
351
- o = u.headers.get("etag") ?? o;
352
- let r = await u.json(), i = r.status ?? "upcoming";
353
- i !== c && (c = i, e.onChange({
354
- matchUrn: r.matchUrn ?? e.matchUrn,
355
- 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,
356
356
  status: i,
357
- datePlannedStart: r.datePlannedStart
357
+ datePlannedStart: t.datePlannedStart
358
358
  }));
359
359
  }
360
- } catch (n) {
361
- 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)));
362
362
  } finally {
363
363
  d();
364
364
  }
365
365
  };
366
366
  return f(), { stop() {
367
- u = !0, i.abort(), a && clearTimeout(a);
367
+ l = !0, i.abort(), a && clearTimeout(a);
368
368
  } };
369
369
  }
370
370
  //#endregion
371
371
  //#region src/core/events.ts
372
- function w(e) {
372
+ function C(e) {
373
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) => {
374
374
  if (r.signal.aborted) return t();
375
375
  let n = setTimeout(t, e);
376
376
  r.signal.addEventListener("abort", () => {
377
377
  clearTimeout(n), t();
378
378
  }, { once: !0 });
379
- }), c = (t) => {
379
+ }), o = (t) => {
380
380
  e.onAlive?.();
381
381
  let n = "message", r = [];
382
382
  for (let e of t.split("\n")) {
@@ -385,19 +385,19 @@ function w(e) {
385
385
  let i = t.indexOf(":"), a = i < 0 ? t : t.slice(0, i), o = i < 0 ? "" : t.slice(i + 1);
386
386
  o.startsWith(" ") && (o = o.slice(1)), a === "event" ? n = o : a === "data" && r.push(o);
387
387
  }
388
- if (!(n !== "state" || r.length === 0)) try {
388
+ if (n === "state" && r.length !== 0) try {
389
389
  let t = JSON.parse(r.join("\n"));
390
390
  t && typeof t.state == "string" && e.onState(t);
391
391
  } catch {}
392
392
  }, l = /\r\n\r\n|\n\n|\r\r/, u = async (e, t) => {
393
- let n = e.getReader(), r = new TextDecoder(), a = "", o = !1;
393
+ let n = e.getReader(), r = new TextDecoder(), a = "", s = !1;
394
394
  try {
395
395
  for (;;) {
396
- let { value: e, done: s } = await n.read();
397
- if (s) return;
398
- !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 });
399
399
  let u;
400
- 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);
401
401
  if (i) return;
402
402
  }
403
403
  } finally {
@@ -405,12 +405,12 @@ function w(e) {
405
405
  }
406
406
  };
407
407
  return (async () => {
408
- let c = t;
408
+ let o = t;
409
409
  for (; !i;) {
410
410
  try {
411
- let n = await o(e.credential);
411
+ let n = await s(e.credential);
412
412
  if (i) return;
413
- 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, {
414
414
  method: "GET",
415
415
  headers: {
416
416
  "x-api-key": n.apiKey,
@@ -423,20 +423,20 @@ function w(e) {
423
423
  });
424
424
  if (i) return;
425
425
  !l.ok || !l.body ? e.onError?.(/* @__PURE__ */ Error(`live-state subscribe failed: HTTP ${l.status}`)) : await u(l.body, () => {
426
- c = t;
426
+ o = t;
427
427
  });
428
428
  } catch (t) {
429
429
  if (i || r.signal.aborted) return;
430
430
  e.onError?.(t);
431
431
  }
432
432
  if (i) return;
433
- await a(c), c = Math.min(n, c * 2);
433
+ await a(o), o = Math.min(n, o * 2);
434
434
  }
435
435
  })(), { close() {
436
436
  i || (i = !0, r.abort());
437
437
  } };
438
438
  }
439
- function T(e) {
439
+ function w(e) {
440
440
  try {
441
441
  let t = new URL(e);
442
442
  return t.hostname = `events.${t.hostname}`, t.pathname = "", t.search = "", t.origin;
@@ -446,27 +446,27 @@ function T(e) {
446
446
  }
447
447
  //#endregion
448
448
  //#region src/core/drm/deviceId.ts
449
- var E = "havik.player.deviceId", D;
450
- function O() {
451
- if (D) return D;
449
+ var T = "havik.player.deviceId", E;
450
+ function D() {
451
+ if (E) return E;
452
452
  try {
453
- let e = globalThis.localStorage?.getItem(E);
454
- if (e) return D = e, e;
453
+ let e = globalThis.localStorage?.getItem(T);
454
+ if (e) return E = e, e;
455
455
  } catch {}
456
- let e = pe();
457
- D = e;
456
+ let e = he();
457
+ E = e;
458
458
  try {
459
- globalThis.localStorage?.setItem(E, e);
459
+ globalThis.localStorage?.setItem(T, e);
460
460
  } catch {}
461
461
  return e;
462
462
  }
463
- function fe() {
464
- D = void 0;
463
+ function me() {
464
+ E = void 0;
465
465
  try {
466
- globalThis.localStorage?.removeItem(E);
466
+ globalThis.localStorage?.removeItem(T);
467
467
  } catch {}
468
468
  }
469
- function pe() {
469
+ function he() {
470
470
  let e = globalThis.crypto;
471
471
  if (e && typeof e.randomUUID == "function") return e.randomUUID();
472
472
  let t = /* @__PURE__ */ new Uint8Array(16);
@@ -478,22 +478,22 @@ function pe() {
478
478
  }
479
479
  //#endregion
480
480
  //#region src/core/drm/headers.ts
481
- function me(e, t, n = {}) {
481
+ function ge(e, t, n = {}) {
482
482
  let r = {
483
483
  "x-api-key": t.apiKey,
484
484
  "X-Match-Urn": e.matchUrn,
485
- "X-Device-Id": n.deviceId ?? O(),
485
+ "X-Device-Id": n.deviceId ?? D(),
486
486
  "Content-Type": "application/octet-stream"
487
487
  };
488
488
  return n.userId && (r["X-User-Id"] = n.userId), r;
489
489
  }
490
490
  //#endregion
491
491
  //#region src/core/drm/support.ts
492
- 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 = {
493
493
  widevine: ["com.widevine.alpha"],
494
494
  fairplay: ["com.apple.fps"]
495
495
  };
496
- function _e(e) {
496
+ function A(e) {
497
497
  return e === "fairplay" ? [{
498
498
  initDataTypes: [
499
499
  "sinf",
@@ -501,24 +501,24 @@ function _e(e) {
501
501
  "cenc"
502
502
  ],
503
503
  videoCapabilities: [{
504
- contentType: k,
504
+ contentType: O,
505
505
  robustness: "",
506
506
  encryptionScheme: "cbcs"
507
507
  }],
508
508
  audioCapabilities: [{
509
- contentType: A,
509
+ contentType: k,
510
510
  robustness: "",
511
511
  encryptionScheme: "cbcs"
512
512
  }]
513
513
  }] : [{
514
514
  initDataTypes: ["cenc"],
515
515
  videoCapabilities: [{
516
- contentType: k,
516
+ contentType: O,
517
517
  robustness: "SW_SECURE_DECODE",
518
518
  encryptionScheme: "cbcs"
519
519
  }],
520
520
  audioCapabilities: [{
521
- contentType: A,
521
+ contentType: k,
522
522
  robustness: "SW_SECURE_CRYPTO",
523
523
  encryptionScheme: "cbcs"
524
524
  }]
@@ -535,7 +535,7 @@ function j(e, t) {
535
535
  });
536
536
  }
537
537
  async function M(e = {}) {
538
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? he, r = {};
538
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? _e, r = {};
539
539
  if (typeof window < "u" && window.isSecureContext === !1) return {
540
540
  verdict: "insecure-context",
541
541
  detail: r
@@ -547,7 +547,7 @@ async function M(e = {}) {
547
547
  };
548
548
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
549
549
  for (let e of t) {
550
- let t = ge[e];
550
+ let t = ve[e];
551
551
  if (!t) {
552
552
  r[e] = "unknown system";
553
553
  continue;
@@ -558,7 +558,7 @@ async function M(e = {}) {
558
558
  break;
559
559
  }
560
560
  try {
561
- 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})`, {
562
562
  verdict: "supported",
563
563
  system: e,
564
564
  detail: r
@@ -592,7 +592,7 @@ var F = { droppedFrames: 0 };
592
592
  function I(e) {
593
593
  return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
594
594
  }
595
- function ve(e, t) {
595
+ function ye(e, t) {
596
596
  let n = [
597
597
  ["playing", () => t("playing")],
598
598
  ["waiting", () => t("buffering")],
@@ -609,7 +609,7 @@ function ve(e, t) {
609
609
  }
610
610
  //#endregion
611
611
  //#region src/core/engine/native.ts
612
- function ye(e) {
612
+ function be(e) {
613
613
  try {
614
614
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
615
615
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -639,12 +639,12 @@ var z = class {
639
639
  this.deps = e;
640
640
  }
641
641
  async load() {
642
- let { video: e, descriptor: n, emit: r } = this.deps;
643
- if (n.drmEnabled) {
644
- if (!n.drm?.fairplay) {
642
+ let { video: e, descriptor: t, emit: r } = this.deps;
643
+ if (t.drmEnabled) {
644
+ if (!t.drm?.fairplay) {
645
645
  r({
646
646
  type: "error",
647
- 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"),
648
648
  fatal: !0
649
649
  });
650
650
  return;
@@ -653,25 +653,25 @@ var z = class {
653
653
  }
654
654
  this.onLoaded = () => r({ type: "ready" }), this.onError = () => r({
655
655
  type: "error",
656
- error: new t("INTERNAL", 0, "native HLS playback error"),
656
+ error: new n("INTERNAL", 0, "native HLS playback error"),
657
657
  fatal: !0
658
- }), 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();
659
659
  }
660
660
  armFairPlay() {
661
- 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;
662
662
  if (!c) return;
663
663
  this.fairplayLicenseUrl = c.licenseUrl;
664
- let l = (e, n, i) => s({
664
+ let l = (e, t, r) => s({
665
665
  type: "error",
666
- error: new t(r(n), n, e),
667
- fatal: i
666
+ error: new n(i(t), t, e),
667
+ fatal: r
668
668
  });
669
- this.onEncrypted = (t) => {
670
- let r = t;
671
- !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 () => {
672
672
  try {
673
- let t = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
674
- initDataTypes: [r.initDataType],
673
+ let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
674
+ initDataTypes: [i.initDataType],
675
675
  videoCapabilities: [{
676
676
  contentType: "application/vnd.apple.mpegurl",
677
677
  robustness: ""
@@ -679,37 +679,37 @@ var z = class {
679
679
  distinctiveIdentifier: "not-allowed",
680
680
  persistentState: "not-allowed",
681
681
  sessionTypes: ["temporary"]
682
- }])).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 } });
683
683
  if (!s.ok) {
684
684
  this.fairplayArmed = !1, l(`FairPlay certificate request failed: ${s.status}`, s.status, !0);
685
685
  return;
686
686
  }
687
- await t.setServerCertificate(await s.arrayBuffer()), await e.setMediaKeys(t);
688
- let u = t.createSession();
687
+ await n.setServerCertificate(await s.arrayBuffer()), await e.setMediaKeys(n);
688
+ let u = n.createSession();
689
689
  this.keySession = u, u.addEventListener("message", (e) => {
690
690
  (async () => {
691
691
  try {
692
- let t = await fetch(this.fairplayLicenseUrl ?? c.licenseUrl, {
692
+ let n = await fetch(this.fairplayLicenseUrl ?? c.licenseUrl, {
693
693
  method: "POST",
694
694
  body: e.message,
695
695
  headers: {
696
- "x-api-key": i.apiKey,
697
- "X-Match-Urn": n.matchUrn,
696
+ "x-api-key": r.apiKey,
697
+ "X-Match-Urn": t.matchUrn,
698
698
  "X-Device-Id": a,
699
699
  ...o ? { "X-User-Id": o } : {},
700
700
  ...this.fairplayKeyIV ? { "X-Key-Iv": this.fairplayKeyIV } : {}
701
701
  }
702
702
  });
703
- if (!t.ok) {
704
- 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);
705
705
  return;
706
706
  }
707
- await u.update(new Uint8Array(await t.arrayBuffer()));
707
+ await u.update(new Uint8Array(await n.arrayBuffer()));
708
708
  } catch (e) {
709
709
  l(`FairPlay license request failed: ${String(e)}`, 0, !0);
710
710
  }
711
711
  })();
712
- }), await u.generateRequest(r.initDataType, r.initData);
712
+ }), await u.generateRequest(i.initDataType, i.initData);
713
713
  } catch (e) {
714
714
  this.fairplayArmed = !1, l(`FairPlay key session failed: ${String(e)}`, 0, !0);
715
715
  }
@@ -783,25 +783,25 @@ var z = class {
783
783
  } catch {}
784
784
  }
785
785
  };
786
- function be(e, t) {
786
+ function xe(e, t) {
787
787
  return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
788
788
  }
789
789
  //#endregion
790
790
  //#region src/core/engine/hls.ts
791
- function xe() {
791
+ function Se() {
792
792
  return e.isSupported();
793
793
  }
794
- 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;
795
- 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) {
796
796
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
797
797
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
798
798
  return;
799
799
  }
800
- e.liveSyncDuration ??= je;
801
- let t = Math.max(12, e.liveSyncDuration + Me);
802
- (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);
803
803
  }
804
- function Fe(e, t) {
804
+ function Re(e, t) {
805
805
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
806
806
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
807
807
  }
@@ -834,27 +834,47 @@ var V = class {
834
834
  chaseSuspended = !1;
835
835
  savedLiveMaxLatency;
836
836
  savedMaxSyncRate;
837
+ capObserver = null;
838
+ capTimer = null;
839
+ sizeCapLevel = null;
840
+ bitrateCapLevel = -1;
841
+ sizeCapManaged = !0;
842
+ dprMedia = null;
843
+ onDprChange = null;
837
844
  constructor(e) {
838
845
  this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
839
846
  }
840
847
  async load() {
841
- 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;
842
849
  if (e.drmEnabled && !o) {
843
850
  i({
844
851
  type: "error",
845
- 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)"),
846
853
  fatal: !0
847
854
  });
848
855
  return;
849
856
  }
850
- this.startHls(), n !== !1 && typeof document < "u" && (this.onVisibility = () => {
857
+ this.startHls(), t !== !1 && typeof document < "u" && (this.onVisibility = () => {
851
858
  let e = this.hls;
852
859
  if (document.visibilityState !== "visible" || !e) return;
853
860
  let t = e.liveSyncPosition;
854
861
  if (typeof t != "number") return;
855
862
  let n = typeof e.targetLatency == "number" && e.targetLatency > 0 ? e.targetLatency : 3;
856
863
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
857
- }, 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;
858
878
  }
859
879
  clampOverSeek() {
860
880
  let e = this.hls;
@@ -862,7 +882,7 @@ var V = class {
862
882
  let t = e.liveSyncPosition;
863
883
  if (typeof t != "number" || !Number.isFinite(t)) return;
864
884
  let { video: n } = this.deps;
865
- n.currentTime <= t + Ne || (n.currentTime = t);
885
+ n.currentTime <= t + Ie || (n.currentTime = t);
866
886
  }
867
887
  startHls() {
868
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 = {
@@ -873,7 +893,7 @@ var V = class {
873
893
  maxBufferHole: .5,
874
894
  maxLiveSyncPlaybackRate: 1.5,
875
895
  abrEwmaDefaultEstimate: 1e6,
876
- capLevelToPlayerSize: !0,
896
+ capLevelToPlayerSize: !1,
877
897
  ...u ? {
878
898
  emeEnabled: !0,
879
899
  drmSystems: { ...this.licenseUrl ? { "com.widevine.alpha": { licenseUrl: this.licenseUrl } } : {} },
@@ -898,14 +918,14 @@ var V = class {
898
918
  } } : {},
899
919
  ...c
900
920
  };
901
- Pe(d);
921
+ Le(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
902
922
  let f = new e(d);
903
923
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
904
- this.mediaRecoveries = 0, this.networkRecoveries = 0, l({ type: "ready" });
924
+ this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
905
925
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
906
926
  t?.frag?.type === "main" && (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
907
927
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.recoveryResetTimer = null;
908
- }, Te));
928
+ }, Ee));
909
929
  }), f.on(e.Events.FRAG_LOADING, (e, t) => {
910
930
  t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
911
931
  }), f.on(e.Events.FRAG_LOADED, (e, t) => {
@@ -921,7 +941,7 @@ var V = class {
921
941
  }));
922
942
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => {
923
943
  this.checkEndStale(), this.checkStarvation();
924
- }, De), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
944
+ }, Oe), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
925
945
  this.renditionSwitches += 1, l({
926
946
  type: "qualitychange",
927
947
  index: t.level
@@ -951,15 +971,54 @@ var V = class {
951
971
  }
952
972
  setMaxBitrate(e) {
953
973
  let t = this.hls;
954
- if (!t) return;
955
- if (e == null) {
956
- 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();
957
999
  return;
958
1000
  }
959
- let n = -1;
960
- t.levels.forEach((t, r) => {
961
- t.bitrate <= e && (n = r);
962
- }), 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);
963
1022
  }
964
1023
  getAudioTracks() {
965
1024
  return this.hls ? this.hls.audioTracks.map((e) => ({
@@ -1025,7 +1084,7 @@ var V = class {
1025
1084
  return;
1026
1085
  }
1027
1086
  let e = Date.now() - this.lastAdvanceMs;
1028
- !this.chaseSuspended && e > B && this.suspendChasing(), e > Ee && this.emitEnded("stalled");
1087
+ !this.chaseSuspended && e > B && this.suspendChasing(), e > De && this.emitEnded("stalled");
1029
1088
  }
1030
1089
  checkStarvation() {
1031
1090
  let e = this.hls, { video: t } = this.deps;
@@ -1034,12 +1093,12 @@ var V = class {
1034
1093
  this.starvedTicks = 0;
1035
1094
  return;
1036
1095
  }
1037
- 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;
1038
1097
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1039
1098
  this.starvedTicks = 0;
1040
1099
  return;
1041
1100
  }
1042
- if (!(++this.starvedTicks < ke) && (this.starvedTicks = 0, !(this.starvationKicks >= Ae))) {
1101
+ if (!(++this.starvedTicks < Ae) && (this.starvedTicks = 0, !(this.starvationKicks >= je))) {
1043
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");
1044
1103
  try {
1045
1104
  e.stopLoad(), e.startLoad(-1);
@@ -1073,7 +1132,7 @@ var V = class {
1073
1132
  } catch {}
1074
1133
  }
1075
1134
  destroy() {
1076
- 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) {
1077
1136
  try {
1078
1137
  this.hls.destroy();
1079
1138
  } catch (e) {
@@ -1086,9 +1145,9 @@ var V = class {
1086
1145
  this.recoveryResetTimer &&= (clearTimeout(this.recoveryResetTimer), null);
1087
1146
  }
1088
1147
  scheduleLicenseRetry(t) {
1089
- 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;
1090
1149
  let n = t.response?.code ?? 0;
1091
- 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;
1092
1151
  this.licenseRecoveries += 1;
1093
1152
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1094
1153
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1116,7 +1175,7 @@ var V = class {
1116
1175
  return;
1117
1176
  }
1118
1177
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1119
- if (this.networkRecoveries < Ce) {
1178
+ if (this.networkRecoveries < we) {
1120
1179
  this.networkRecoveries += 1;
1121
1180
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1122
1181
  this.networkRetryTimer = setTimeout(() => {
@@ -1132,7 +1191,7 @@ var V = class {
1132
1191
  return;
1133
1192
  }
1134
1193
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1135
- if (this.mediaRecoveries < Se) {
1194
+ if (this.mediaRecoveries < Ce) {
1136
1195
  this.mediaRecoveries += 1, n.recoverMediaError();
1137
1196
  return;
1138
1197
  }
@@ -1150,11 +1209,11 @@ var V = class {
1150
1209
  });
1151
1210
  }
1152
1211
  }
1153
- toError(e, n = "PLAYBACK") {
1154
- let i = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1155
- 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})` : ""}`);
1156
1215
  }
1157
- }, Ie = 15e3, Le = 100, H = 1, U = {
1216
+ }, ze = 15e3, Be = 100, H = 1, U = {
1158
1217
  idle: "PLAYER_STATE_IDLE",
1159
1218
  waiting: "PLAYER_STATE_WAITING",
1160
1219
  loading: "PLAYER_STATE_LOADING",
@@ -1164,7 +1223,7 @@ var V = class {
1164
1223
  ended: "PLAYER_STATE_ENDED",
1165
1224
  error: "PLAYER_STATE_ERROR"
1166
1225
  };
1167
- function Re(e) {
1226
+ function Ve(e) {
1168
1227
  try {
1169
1228
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1170
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);
@@ -1172,7 +1231,7 @@ function Re(e) {
1172
1231
  return;
1173
1232
  }
1174
1233
  }
1175
- var ze = class {
1234
+ var He = class {
1176
1235
  opts;
1177
1236
  url;
1178
1237
  now;
@@ -1207,7 +1266,7 @@ var ze = class {
1207
1266
  constructor(e) {
1208
1267
  this.opts = {
1209
1268
  ...e,
1210
- heartbeatMs: e.heartbeatMs ?? Ie
1269
+ heartbeatMs: e.heartbeatMs ?? ze
1211
1270
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
1212
1271
  }
1213
1272
  start() {
@@ -1299,7 +1358,7 @@ var ze = class {
1299
1358
  };
1300
1359
  }
1301
1360
  enqueue(e) {
1302
- for (this.pending.push(this.wrap(e)); this.pending.length > Le;) {
1361
+ for (this.pending.push(this.wrap(e)); this.pending.length > Be;) {
1303
1362
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1304
1363
  this.pending.splice(e >= 0 ? e : 0, 1);
1305
1364
  }
@@ -1321,7 +1380,7 @@ var ze = class {
1321
1380
  keepalive: !0
1322
1381
  }).catch(() => {});
1323
1382
  }
1324
- }, Be = "1.15.0", W = {
1383
+ }, Ue = "1.15.1", W = {
1325
1384
  accent: "#E1B600",
1326
1385
  accentText: "#1B1C23",
1327
1386
  background: "#0e0f13",
@@ -1354,15 +1413,15 @@ function K(e, t) {
1354
1413
  }
1355
1414
  //#endregion
1356
1415
  //#region src/ui/styles.ts
1357
- 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";
1358
- 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) {
1359
1418
  if (e.getElementById(q)) return;
1360
1419
  let t = e.createElement("style");
1361
- t.id = q, t.textContent = Ve, e.head.appendChild(t);
1420
+ t.id = q, t.textContent = We, e.head.appendChild(t);
1362
1421
  }
1363
1422
  //#endregion
1364
1423
  //#region src/ui/icons.ts
1365
- var Ue = {
1424
+ var Ke = {
1366
1425
  play: "<path d=\"M8 5v14l11-7z\"/>",
1367
1426
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1368
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\"/>",
@@ -1376,20 +1435,20 @@ var Ue = {
1376
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\"/>"
1377
1436
  };
1378
1437
  function J(e) {
1379
- 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>`;
1380
1439
  }
1381
1440
  //#endregion
1382
1441
  //#region src/ui/controls.ts
1383
- var We = 3e3, Ge = /* @__PURE__ */ new Set([
1442
+ var qe = 3e3, Je = /* @__PURE__ */ new Set([
1384
1443
  "GONE",
1385
1444
  "NOT_FOUND",
1386
1445
  "INVALID_URN",
1387
1446
  "UNAUTHORIZED"
1388
1447
  ]);
1389
- function Ke(e) {
1390
- return new qe(e);
1448
+ function Ye(e) {
1449
+ return new Xe(e);
1391
1450
  }
1392
- var qe = class {
1451
+ var Xe = class {
1393
1452
  root;
1394
1453
  video;
1395
1454
  player;
@@ -1429,7 +1488,7 @@ var qe = class {
1429
1488
  scrubbing = !1;
1430
1489
  offFns = [];
1431
1490
  constructor(e) {
1432
- 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");
1433
1492
  let t = (e, t, n) => {
1434
1493
  let r = this.doc.createElement(e);
1435
1494
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1485,7 +1544,7 @@ var qe = class {
1485
1544
  this.setVolumeFill(t), this.player.setVolume(t), t > 0 && e.muted && this.player.setMuted(!1);
1486
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) => {
1487
1546
  this.lastError = e, this.statusKind = null, this.syncState();
1488
- })), 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();
1489
1548
  }
1490
1549
  togglePlay() {
1491
1550
  this.video.paused || this.video.ended ? this.player.play() : this.player.pause();
@@ -1516,7 +1575,7 @@ var qe = class {
1516
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);
1517
1576
  } else {
1518
1577
  let n = this.doc.createElement("div");
1519
- 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)) {
1520
1579
  let e = this.doc.createElement("button");
1521
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);
1522
1581
  }
@@ -1607,17 +1666,17 @@ var qe = class {
1607
1666
  renderQualityMenu() {
1608
1667
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1609
1668
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1610
- 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)));
1611
1670
  }
1612
1671
  renderAudioMenu() {
1613
1672
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1614
1673
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1615
- 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)));
1616
1675
  }
1617
1676
  renderCcMenu() {
1618
1677
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1619
1678
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1620
- 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)));
1621
1680
  }
1622
1681
  onTracksChanged() {
1623
1682
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1636,14 +1695,18 @@ var qe = class {
1636
1695
  this.pipBtn.setAttribute("aria-pressed", e ? "true" : "false");
1637
1696
  }
1638
1697
  async toggleFullscreen() {
1639
- let e = this.root, t = this.doc;
1698
+ let e = this.root, t = this.doc, n = this.video;
1640
1699
  try {
1641
- 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());
1642
1705
  } catch {}
1643
1706
  }
1644
1707
  syncFullscreen() {
1645
- let e = this.doc.fullscreenElement === this.root;
1646
- 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");
1647
1710
  }
1648
1711
  poke() {
1649
1712
  this.show(), this.scheduleHide();
@@ -1723,27 +1786,27 @@ function Y(e) {
1723
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");
1724
1787
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1725
1788
  }
1726
- function Je(e) {
1789
+ function Ze(e) {
1727
1790
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1728
1791
  }
1729
- function Ye(e) {
1792
+ function X(e) {
1730
1793
  return e.name || e.lang || `Track ${e.id}`;
1731
1794
  }
1732
- function Xe(e) {
1795
+ function Qe(e) {
1733
1796
  return e.name || e.lang || `Track ${e.id}`;
1734
1797
  }
1735
1798
  //#endregion
1736
1799
  //#region src/managed/index.ts
1737
- 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;
1738
1801
  async function Q(e) {
1739
- if (!e.video || typeof e.video.play != "function") throw new t("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1740
- if (!e.baseUrl) throw new t("INTERNAL", 0, "createPlayer: baseUrl is required");
1741
- if (!e.matchUrn) throw new t("INVALID_URN", 0, "createPlayer: matchUrn is required");
1742
- if (!e.credential) throw new t("INTERNAL", 0, "createPlayer: credential is required");
1743
- let n = new tt(e);
1744
- 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;
1745
1808
  }
1746
- var tt = class {
1809
+ var it = class {
1747
1810
  opts;
1748
1811
  abort = new AbortController();
1749
1812
  listeners = {};
@@ -1777,19 +1840,19 @@ var tt = class {
1777
1840
  }
1778
1841
  async start() {
1779
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");
1780
- let e = ++this.loadGen, n = this.resolveOptions();
1843
+ let e = ++this.loadGen, t = this.resolveOptions();
1781
1844
  try {
1782
- let t = await y(n);
1783
- 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) {
1784
1847
  let e = this._lastError;
1785
1848
  throw this.destroy(), e;
1786
1849
  }
1787
- } catch (n) {
1788
- if (this.opts.waitForLive && n instanceof t && a(n.code)) {
1789
- 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);
1790
1853
  return;
1791
1854
  }
1792
- throw n;
1855
+ throw t;
1793
1856
  }
1794
1857
  }
1795
1858
  resolveOptions() {
@@ -1802,7 +1865,7 @@ var tt = class {
1802
1865
  };
1803
1866
  }
1804
1867
  beginArming(e, t) {
1805
- 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);
1806
1869
  n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1807
1870
  }
1808
1871
  beginArmingByPolling(e) {
@@ -1812,57 +1875,57 @@ var tt = class {
1812
1875
  waitForLive: this.buildWaitConfig(e)
1813
1876
  }).then((t) => {
1814
1877
  if (!(this.destroyed || this.abort.signal.aborted || e !== this.loadGen)) return this.onResolved(t, e);
1815
- }).catch((n) => {
1816
- 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)));
1817
1880
  });
1818
1881
  }
1819
- armWithSse(e, n, r) {
1820
- let i = !1, o = !1, s = new AbortController();
1882
+ armWithSse(e, t, r) {
1883
+ let i = !1, a = !1, s = new AbortController();
1821
1884
  this.armFallbackCtrl = s, this.setState("waiting");
1822
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);
1823
1886
  setTimeout(() => {
1824
1887
  i || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1825
1888
  phase: "tooEarly",
1826
- retryInMs: X,
1889
+ retryInMs: Z,
1827
1890
  attempt: 1,
1828
1891
  elapsedMs: 0,
1829
1892
  ...d !== void 0 && d > Date.now() ? { ttkMs: d - Date.now() } : {}
1830
1893
  });
1831
1894
  }, 0);
1832
- 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) => {
1833
1896
  if (!(i || f())) {
1834
1897
  i = !0, this.closeArming(), s.abort();
1835
1898
  try {
1836
- await this.onResolved(n, e);
1899
+ await this.onResolved(t, e);
1837
1900
  } catch (e) {
1838
1901
  if (f()) return;
1839
- 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)));
1840
1903
  }
1841
1904
  }
1842
1905
  }, m = () => {
1843
- i || o || f() || (o = !0, b({
1906
+ i || a || f() || (a = !0, b({
1844
1907
  ...this.resolveOptions(),
1845
1908
  signal: s.signal,
1846
1909
  waitForLive: this.buildWaitConfig(e)
1847
1910
  }).then((e) => p(e)).catch((e) => {
1848
- 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))));
1849
1912
  }));
1850
1913
  }, h = () => {
1851
1914
  i || f() || y(this.resolveOptions()).then((e) => p(e)).catch((e) => {
1852
- 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))));
1853
1916
  });
1854
1917
  }, g = (e) => {
1855
1918
  this.armSseTimer && clearTimeout(this.armSseTimer), this.armSseTimer = setTimeout(() => {
1856
1919
  !i && !f() && m();
1857
1920
  }, e);
1858
1921
  };
1859
- g(Ze), this.armingSub = w({
1860
- eventsBaseUrl: n,
1922
+ g($e), this.armingSub = C({
1923
+ eventsBaseUrl: t,
1861
1924
  matchUrn: this.opts.matchUrn,
1862
1925
  credential: this.opts.credential,
1863
- onAlive: () => g(X),
1926
+ onAlive: () => g(Z),
1864
1927
  onState: (e) => {
1865
- 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);
1866
1929
  },
1867
1930
  onError: () => m()
1868
1931
  });
@@ -1882,11 +1945,11 @@ var tt = class {
1882
1945
  async onResolved(e, t) {
1883
1946
  if (this.destroyed || t !== this.loadGen) return;
1884
1947
  this._descriptor = e, this.endedHandled = !1;
1885
- let n = await o(this.opts.credential);
1948
+ let n = await s(this.opts.credential);
1886
1949
  if (this.destroyed || t !== this.loadGen) return;
1887
1950
  this.setState("loading");
1888
- let r = this.createEngine(e, n), i = ve(this.opts.video, (e) => {
1889
- 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);
1890
1953
  });
1891
1954
  if (await r.load(), this.destroyed || t !== this.loadGen) {
1892
1955
  i(), r.destroy();
@@ -1902,14 +1965,14 @@ var tt = class {
1902
1965
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
1903
1966
  let r = e.analytics?.sid;
1904
1967
  if (!r) return;
1905
- 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);
1906
1969
  if (!i) return;
1907
- let a = this.opts.video, o = new ze({
1970
+ let a = this.opts.video, o = new He({
1908
1971
  endpoint: i,
1909
1972
  sid: r,
1910
1973
  apiKey: n,
1911
1974
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
1912
- sdkVersion: Be,
1975
+ sdkVersion: Ue,
1913
1976
  sample: () => {
1914
1977
  let e = this.engine?.getStats() ?? F, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
1915
1978
  return {
@@ -1924,14 +1987,14 @@ var tt = class {
1924
1987
  let s = () => o.noteSeek();
1925
1988
  a.addEventListener("seeking", s), this.detachSeekListener = () => a.removeEventListener("seeking", s), o.start();
1926
1989
  }
1927
- createEngine(e, n) {
1990
+ createEngine(e, t) {
1928
1991
  let r = { ...this.opts.hlsConfig };
1929
1992
  this.opts.startLevel != null && (r.startLevel = this.opts.startLevel), this.opts.liveLatencyTarget != null && (r.liveSyncDuration = this.opts.liveLatencyTarget);
1930
1993
  let i = {
1931
1994
  video: this.opts.video,
1932
1995
  descriptor: e,
1933
- credential: n,
1934
- deviceId: O(),
1996
+ credential: t,
1997
+ deviceId: D(),
1935
1998
  userId: this.opts.userId,
1936
1999
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
1937
2000
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -1942,9 +2005,9 @@ var tt = class {
1942
2005
  if (a === "native") return new z(i);
1943
2006
  if (a === "hls") return new V(i);
1944
2007
  if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && L(this.opts.video) && R()) return new z(i);
1945
- if (xe()) return new V(i);
2008
+ if (Se()) return new V(i);
1946
2009
  if (L(this.opts.video)) return new z(i);
1947
- 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");
1948
2011
  }
1949
2012
  onEngineEvent(e) {
1950
2013
  if (!this.destroyed) switch (e.type) {
@@ -1966,19 +2029,17 @@ var tt = class {
1966
2029
  case "ended":
1967
2030
  this.onStreamEnded();
1968
2031
  break;
1969
- case "error":
1970
- 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);
1971
- 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);
1972
2033
  }
1973
2034
  }
1974
2035
  startLiveState(e) {
1975
2036
  if (this.opts.liveStateEvents === !1) return;
1976
- let n = this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1977
- if (!n) return;
2037
+ let t = this.opts.eventsBaseUrl ?? w(this.opts.baseUrl);
2038
+ if (!t) return;
1978
2039
  this.closeLiveState();
1979
2040
  let r = 0, i = !1;
1980
- this.liveStateSub = w({
1981
- eventsBaseUrl: n,
2041
+ this.liveStateSub = C({
2042
+ eventsBaseUrl: t,
1982
2043
  matchUrn: this.opts.matchUrn,
1983
2044
  credential: this.opts.credential,
1984
2045
  onState: (t) => {
@@ -1988,7 +2049,7 @@ var tt = class {
1988
2049
  r = 0, i = !1;
1989
2050
  },
1990
2051
  onError: () => {
1991
- 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.`))));
1992
2053
  }
1993
2054
  });
1994
2055
  }
@@ -2010,7 +2071,7 @@ var tt = class {
2010
2071
  }
2011
2072
  t.controls = !1;
2012
2073
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2013
- 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({
2014
2075
  root: n,
2015
2076
  video: t,
2016
2077
  player: this,
@@ -2033,7 +2094,7 @@ var tt = class {
2033
2094
  }
2034
2095
  startStats() {
2035
2096
  this.stopStats();
2036
- let e = Math.max(500, this.opts.statsIntervalMs ?? Z);
2097
+ let e = Math.max(500, this.opts.statsIntervalMs ?? nt);
2037
2098
  this.statsTimer = setInterval(() => {
2038
2099
  if (!this.engine) return;
2039
2100
  let e = this.engine.getStats();
@@ -2045,7 +2106,7 @@ var tt = class {
2045
2106
  }
2046
2107
  scheduleLicenseRefresh() {
2047
2108
  this.stopRefresh();
2048
- let e = this.opts.licenseRefreshMs ?? et;
2109
+ let e = this.opts.licenseRefreshMs ?? rt;
2049
2110
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2050
2111
  }
2051
2112
  stopRefresh() {
@@ -2062,7 +2123,7 @@ var tt = class {
2062
2123
  fairplay: r
2063
2124
  }), this._descriptor = t);
2064
2125
  } catch (e) {
2065
- 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));
2066
2127
  }
2067
2128
  }
2068
2129
  async play() {
@@ -2133,9 +2194,9 @@ var tt = class {
2133
2194
  waitForLive: this.opts.waitForLive ? this.buildWaitConfig(e) : void 0
2134
2195
  });
2135
2196
  if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) throw this._lastError;
2136
- } catch (n) {
2197
+ } catch (t) {
2137
2198
  if (this.destroyed || this.abort.signal.aborted || e !== this.loadGen) return;
2138
- 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;
2139
2200
  }
2140
2201
  }
2141
2202
  on(e, t) {
@@ -2168,10 +2229,7 @@ var tt = class {
2168
2229
  case "paused":
2169
2230
  this.emit("paused");
2170
2231
  break;
2171
- case "ended":
2172
- this.emit("ended");
2173
- break;
2174
- default: break;
2232
+ case "ended": this.emit("ended");
2175
2233
  }
2176
2234
  }
2177
2235
  emit(e, t) {
@@ -2189,7 +2247,7 @@ function $(e) {
2189
2247
  }
2190
2248
  //#endregion
2191
2249
  //#region src/embed/host.ts
2192
- function nt(e) {
2250
+ function at(e) {
2193
2251
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2194
2252
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2195
2253
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2248,8 +2306,8 @@ function nt(e) {
2248
2306
  }
2249
2307
  //#endregion
2250
2308
  //#region src/embed/page.ts
2251
- var rt = /^[A-Za-z0-9:_.-]{1,128}$/;
2252
- function it() {
2309
+ var ot = /^[A-Za-z0-9:_.-]{1,128}$/;
2310
+ function st() {
2253
2311
  let e = window.HAVIK_EMBED_CONFIG;
2254
2312
  if (e) return {
2255
2313
  baseUrl: e.baseUrl ?? "",
@@ -2271,11 +2329,11 @@ function it() {
2271
2329
  waitForLive: !0
2272
2330
  };
2273
2331
  }
2274
- async function at() {
2275
- let e = it();
2276
- st();
2277
- let n = document.createElement("video");
2278
- 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);
2279
2337
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
2280
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.");
2281
2339
  let i = (e) => {
@@ -2328,13 +2386,13 @@ async function at() {
2328
2386
  code: e.code,
2329
2387
  message: e.message
2330
2388
  })), e.on("autoplayblocked", () => {
2331
- i({ type: "oddin:autoplayblocked" }), ot(n, e);
2389
+ i({ type: "oddin:autoplayblocked" }), lt(t, e);
2332
2390
  });
2333
2391
  }, s = async (r) => {
2334
2392
  a?.destroy(), a = null;
2335
2393
  try {
2336
- let t = await Q({
2337
- video: n,
2394
+ let n = await Q({
2395
+ video: t,
2338
2396
  baseUrl: e.baseUrl,
2339
2397
  matchUrn: r,
2340
2398
  credential: { apiKey: e.apiKey },
@@ -2342,14 +2400,14 @@ async function at() {
2342
2400
  muted: e.muted,
2343
2401
  waitForLive: e.waitForLive
2344
2402
  });
2345
- a = t;
2346
- let i = n.parentElement;
2347
- 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);
2348
2406
  } catch (e) {
2349
- 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);
2350
2408
  i({
2351
2409
  type: "oddin:error",
2352
- code: n,
2410
+ code: t,
2353
2411
  message: r
2354
2412
  });
2355
2413
  }
@@ -2394,11 +2452,9 @@ async function at() {
2394
2452
  a?.retry();
2395
2453
  break;
2396
2454
  case "oddin:load":
2397
- typeof e.matchUrn == "string" && rt.test(e.matchUrn) && s(e.matchUrn);
2398
- break;
2399
- case "oddin:destroy":
2400
- c();
2455
+ typeof e.matchUrn == "string" && ot.test(e.matchUrn) && s(e.matchUrn);
2401
2456
  break;
2457
+ case "oddin:destroy": c();
2402
2458
  }
2403
2459
  }, u = (e) => {
2404
2460
  !r || e.origin !== r || $(e.data) && l(e.data);
@@ -2408,18 +2464,18 @@ async function at() {
2408
2464
  protocol: 1
2409
2465
  });
2410
2466
  }
2411
- function ot(e, t) {
2467
+ function lt(e, t) {
2412
2468
  if (document.getElementById("havik-tap")) return;
2413
2469
  let n = document.createElement("button");
2414
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", () => {
2415
2471
  n.remove(), t.play();
2416
2472
  }), document.body.appendChild(n);
2417
2473
  }
2418
- function st() {
2474
+ function ut() {
2419
2475
  let e = document.createElement("style");
2420
2476
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2421
2477
  }
2422
2478
  //#endregion
2423
- 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 };
2424
2480
 
2425
2481
  //# sourceMappingURL=index.js.map