@oddin-gg/havik-player 1.15.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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 re(e, t, n) {
114
114
  return Math.min(n, Math.max(t, e));
115
115
  }
116
- function _(e) {
116
+ function ie(e) {
117
117
  return Math.round(e * (1 + .15 * Math.random()));
118
118
  }
119
- function re(e, t, n, r) {
119
+ function ae(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: re(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 oe(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 _(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 = ae(e.retryAfterMs, t, i, l), d = u.ttkMs, f = u.delay;
155
+ if (i.jitter && (f = ie(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 oe(f, r);
166
166
  }
167
167
  }
168
168
  }
169
169
  //#endregion
170
170
  //#region src/core/resolve.ts
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)}`), {
171
+ async function v(e) {
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 se(t);
179
+ return le(await t.json(), e.matchUrn);
180
180
  }
181
- function b(e) {
182
- let t = () => y(e);
183
- return e.waitForLive ? v(t, e.waitForLive, e.signal) : t();
181
+ function y(e) {
182
+ let t = () => v(e);
183
+ return e.waitForLive ? _(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 se(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 ce = /* @__PURE__ */ new Set([
202
202
  "matchUrn",
203
203
  "protocol",
204
204
  "drmEnabled",
@@ -209,62 +209,90 @@ var oe = /* @__PURE__ */ new Set([
209
209
  "expiresAt",
210
210
  "analytics"
211
211
  ]);
212
- function x(e, n) {
212
+ function b(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 le(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: b("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: b("widevine licenseUrl", e.drm.widevine.licenseUrl) }), e.drm.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && (t.fairplay = {
234
+ licenseUrl: b("fairplay licenseUrl", e.drm.fairplay.licenseUrl),
235
+ certificateUrl: b("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)) ce.has(t) || (a[t] = n);
240
240
  return Object.keys(a).length > 0 && (i.extensions = a), i;
241
241
  }
242
242
  //#endregion
243
+ //#region src/core/fullscreen.ts
244
+ function x(e) {
245
+ return e.ownerDocument ?? document;
246
+ }
247
+ function S(e) {
248
+ return e.fullscreenElement ?? e.webkitFullscreenElement ?? null;
249
+ }
250
+ function C(e, t) {
251
+ let n = S(x(e));
252
+ return n != null && (n === e || t != null && n === t) || e.webkitDisplayingFullscreen === !0;
253
+ }
254
+ async function w(e, t) {
255
+ let n = t ?? e;
256
+ if (typeof n.requestFullscreen == "function") return await n.requestFullscreen(), !0;
257
+ if (typeof n.webkitRequestFullscreen == "function") return n.webkitRequestFullscreen(), !0;
258
+ let r = e;
259
+ return r.webkitSupportsFullscreen && typeof r.webkitEnterFullscreen == "function" ? (r.webkitEnterFullscreen(), !0) : !1;
260
+ }
261
+ async function T(e, t) {
262
+ let n = e;
263
+ if (n.webkitDisplayingFullscreen) {
264
+ n.webkitExitFullscreen?.();
265
+ return;
266
+ }
267
+ let r = x(e), i = S(r);
268
+ i != null && (i === e || t != null && i === t) && (typeof r.exitFullscreen == "function" ? await r.exitFullscreen() : r.webkitExitFullscreen?.());
269
+ }
270
+ //#endregion
243
271
  //#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();
272
+ async function ue(e) {
273
+ let t = new URLSearchParams();
274
+ e.status && t.set("status", Array.isArray(e.status) ? e.status.join(",") : e.status), e.sport && t.set("sport", e.sport);
275
+ let i = t.toString(), a = c(e.baseUrl, "/v1/catalog" + (i ? `?${i}` : "")), o = new Headers();
248
276
  e.etag && o.set("If-None-Match", e.etag);
249
- let c = await l(a, {
277
+ let s = await u(a, {
250
278
  credential: e.credential,
251
279
  signal: e.signal,
252
280
  headers: o,
253
281
  apiHeaders: e.apiHeaders
254
282
  });
255
- if (c.status === 304) return {
283
+ if (s.status === 304) return {
256
284
  tournaments: [],
257
285
  etag: e.etag,
258
286
  notModified: !0
259
287
  };
260
- if (!c.ok) throw new t(n(c.status), c.status, `catalog request failed (HTTP ${c.status})`);
288
+ if (!s.ok) throw new n(r(s.status), s.status, `catalog request failed (HTTP ${s.status})`);
261
289
  return {
262
- tournaments: se(await c.json()),
263
- etag: c.headers.get("etag") ?? void 0,
290
+ tournaments: de(await s.json()),
291
+ etag: s.headers.get("etag") ?? void 0,
264
292
  notModified: !1
265
293
  };
266
294
  }
267
- function se(e) {
295
+ function de(e) {
268
296
  return (e.tournaments ?? []).map((e) => {
269
297
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
270
298
  return {
@@ -284,10 +312,10 @@ function se(e) {
284
312
  };
285
313
  });
286
314
  }
287
- async function ce(e) {
288
- let r = s(e.baseUrl, `/v1/tournaments/${encodeURIComponent(e.urn)}`), i = new Headers();
315
+ async function fe(e) {
316
+ let t = c(e.baseUrl, `/v1/tournaments/${encodeURIComponent(e.urn)}`), i = new Headers();
289
317
  e.etag && i.set("If-None-Match", e.etag);
290
- let a = await l(r, {
318
+ let a = await u(t, {
291
319
  credential: e.credential,
292
320
  signal: e.signal,
293
321
  headers: i,
@@ -302,15 +330,15 @@ async function ce(e) {
302
330
  notModified: !1,
303
331
  notFound: !0
304
332
  };
305
- if (!a.ok) throw new t(n(a.status), a.status, `tournament request failed (HTTP ${a.status})`);
333
+ if (!a.ok) throw new n(r(a.status), a.status, `tournament request failed (HTTP ${a.status})`);
306
334
  return {
307
- tournament: le(await a.json()),
335
+ tournament: pe(await a.json()),
308
336
  etag: a.headers.get("etag") ?? void 0,
309
337
  notModified: !1,
310
338
  notFound: !1
311
339
  };
312
340
  }
313
- function le(e) {
341
+ function pe(e) {
314
342
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
315
343
  return {
316
344
  urn: t,
@@ -328,55 +356,55 @@ function le(e) {
328
356
  }))
329
357
  };
330
358
  }
331
- function ue(e) {
359
+ function me(e) {
332
360
  return e.tournaments.flatMap((e) => e.matches);
333
361
  }
334
362
  //#endregion
335
363
  //#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));
364
+ function he(e) {
365
+ let t = Math.max(2e3, e.intervalMs ?? 1e4), i = new AbortController(), a, o, s, l = !1, d = () => {
366
+ l || (a = setTimeout(f, t));
339
367
  }, f = async () => {
340
368
  try {
341
- let r = s(e.baseUrl, `/v1/matches/${encodeURIComponent(e.matchUrn)}`), a = new Headers();
369
+ let t = c(e.baseUrl, `/v1/matches/${encodeURIComponent(e.matchUrn)}`), a = new Headers();
342
370
  o && a.set("If-None-Match", o);
343
- let u = await l(r, {
371
+ let l = await u(t, {
344
372
  credential: e.credential,
345
373
  signal: i.signal,
346
374
  headers: a,
347
375
  apiHeaders: e.apiHeaders
348
376
  });
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,
377
+ if (l.status !== 304) {
378
+ if (!l.ok) throw new n(r(l.status), l.status, `match status request failed (HTTP ${l.status})`);
379
+ o = l.headers.get("etag") ?? o;
380
+ let t = await l.json(), i = t.status ?? "upcoming";
381
+ i !== s && (s = i, e.onChange({
382
+ matchUrn: t.matchUrn ?? e.matchUrn,
383
+ matchName: t.matchName,
356
384
  status: i,
357
- datePlannedStart: r.datePlannedStart
385
+ datePlannedStart: t.datePlannedStart
358
386
  }));
359
387
  }
360
- } catch (n) {
361
- i.signal.aborted || e.onError?.(n instanceof t ? n : new t("NETWORK", 0, String(n)));
388
+ } catch (t) {
389
+ i.signal.aborted || e.onError?.(t instanceof n ? t : new n("NETWORK", 0, String(t)));
362
390
  } finally {
363
391
  d();
364
392
  }
365
393
  };
366
394
  return f(), { stop() {
367
- u = !0, i.abort(), a && clearTimeout(a);
395
+ l = !0, i.abort(), a && clearTimeout(a);
368
396
  } };
369
397
  }
370
398
  //#endregion
371
399
  //#region src/core/events.ts
372
- function w(e) {
400
+ function E(e) {
373
401
  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
402
  if (r.signal.aborted) return t();
375
403
  let n = setTimeout(t, e);
376
404
  r.signal.addEventListener("abort", () => {
377
405
  clearTimeout(n), t();
378
406
  }, { once: !0 });
379
- }), c = (t) => {
407
+ }), o = (t) => {
380
408
  e.onAlive?.();
381
409
  let n = "message", r = [];
382
410
  for (let e of t.split("\n")) {
@@ -385,19 +413,19 @@ function w(e) {
385
413
  let i = t.indexOf(":"), a = i < 0 ? t : t.slice(0, i), o = i < 0 ? "" : t.slice(i + 1);
386
414
  o.startsWith(" ") && (o = o.slice(1)), a === "event" ? n = o : a === "data" && r.push(o);
387
415
  }
388
- if (!(n !== "state" || r.length === 0)) try {
416
+ if (n === "state" && r.length !== 0) try {
389
417
  let t = JSON.parse(r.join("\n"));
390
418
  t && typeof t.state == "string" && e.onState(t);
391
419
  } catch {}
392
420
  }, l = /\r\n\r\n|\n\n|\r\r/, u = async (e, t) => {
393
- let n = e.getReader(), r = new TextDecoder(), a = "", o = !1;
421
+ let n = e.getReader(), r = new TextDecoder(), a = "", s = !1;
394
422
  try {
395
423
  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 });
424
+ let { value: e, done: c } = await n.read();
425
+ if (c) return;
426
+ !s && e && e.length > 0 && (s = !0, t()), a += r.decode(e, { stream: !0 });
399
427
  let u;
400
- for (; (u = l.exec(a)) !== null;) c(a.slice(0, u.index)), a = a.slice(u.index + u[0].length);
428
+ for (; (u = l.exec(a)) !== null;) o(a.slice(0, u.index)), a = a.slice(u.index + u[0].length);
401
429
  if (i) return;
402
430
  }
403
431
  } finally {
@@ -405,12 +433,12 @@ function w(e) {
405
433
  }
406
434
  };
407
435
  return (async () => {
408
- let c = t;
436
+ let o = t;
409
437
  for (; !i;) {
410
438
  try {
411
- let n = await o(e.credential);
439
+ let n = await s(e.credential);
412
440
  if (i) return;
413
- let a = s(e.eventsBaseUrl, `/v1/events/${encodeURIComponent(e.matchUrn)}`), l = await fetch(a, {
441
+ let a = c(e.eventsBaseUrl, `/v1/events/${encodeURIComponent(e.matchUrn)}`), l = await fetch(a, {
414
442
  method: "GET",
415
443
  headers: {
416
444
  "x-api-key": n.apiKey,
@@ -423,20 +451,20 @@ function w(e) {
423
451
  });
424
452
  if (i) return;
425
453
  !l.ok || !l.body ? e.onError?.(/* @__PURE__ */ Error(`live-state subscribe failed: HTTP ${l.status}`)) : await u(l.body, () => {
426
- c = t;
454
+ o = t;
427
455
  });
428
456
  } catch (t) {
429
457
  if (i || r.signal.aborted) return;
430
458
  e.onError?.(t);
431
459
  }
432
460
  if (i) return;
433
- await a(c), c = Math.min(n, c * 2);
461
+ await a(o), o = Math.min(n, o * 2);
434
462
  }
435
463
  })(), { close() {
436
464
  i || (i = !0, r.abort());
437
465
  } };
438
466
  }
439
- function T(e) {
467
+ function D(e) {
440
468
  try {
441
469
  let t = new URL(e);
442
470
  return t.hostname = `events.${t.hostname}`, t.pathname = "", t.search = "", t.origin;
@@ -446,27 +474,27 @@ function T(e) {
446
474
  }
447
475
  //#endregion
448
476
  //#region src/core/drm/deviceId.ts
449
- var E = "havik.player.deviceId", D;
450
- function O() {
451
- if (D) return D;
477
+ var O = "havik.player.deviceId", k;
478
+ function A() {
479
+ if (k) return k;
452
480
  try {
453
- let e = globalThis.localStorage?.getItem(E);
454
- if (e) return D = e, e;
481
+ let e = globalThis.localStorage?.getItem(O);
482
+ if (e) return k = e, e;
455
483
  } catch {}
456
- let e = pe();
457
- D = e;
484
+ let e = _e();
485
+ k = e;
458
486
  try {
459
- globalThis.localStorage?.setItem(E, e);
487
+ globalThis.localStorage?.setItem(O, e);
460
488
  } catch {}
461
489
  return e;
462
490
  }
463
- function fe() {
464
- D = void 0;
491
+ function ge() {
492
+ k = void 0;
465
493
  try {
466
- globalThis.localStorage?.removeItem(E);
494
+ globalThis.localStorage?.removeItem(O);
467
495
  } catch {}
468
496
  }
469
- function pe() {
497
+ function _e() {
470
498
  let e = globalThis.crypto;
471
499
  if (e && typeof e.randomUUID == "function") return e.randomUUID();
472
500
  let t = /* @__PURE__ */ new Uint8Array(16);
@@ -478,22 +506,22 @@ function pe() {
478
506
  }
479
507
  //#endregion
480
508
  //#region src/core/drm/headers.ts
481
- function me(e, t, n = {}) {
509
+ function ve(e, t, n = {}) {
482
510
  let r = {
483
511
  "x-api-key": t.apiKey,
484
512
  "X-Match-Urn": e.matchUrn,
485
- "X-Device-Id": n.deviceId ?? O(),
513
+ "X-Device-Id": n.deviceId ?? A(),
486
514
  "Content-Type": "application/octet-stream"
487
515
  };
488
516
  return n.userId && (r["X-User-Id"] = n.userId), r;
489
517
  }
490
518
  //#endregion
491
519
  //#region src/core/drm/support.ts
492
- var he = 5e3, k = "video/mp4; codecs=\"avc1.42E01E\"", A = "audio/mp4; codecs=\"mp4a.40.2\"", ge = {
520
+ var ye = 5e3, j = "video/mp4; codecs=\"avc1.42E01E\"", M = "audio/mp4; codecs=\"mp4a.40.2\"", be = {
493
521
  widevine: ["com.widevine.alpha"],
494
522
  fairplay: ["com.apple.fps"]
495
523
  };
496
- function _e(e) {
524
+ function N(e) {
497
525
  return e === "fairplay" ? [{
498
526
  initDataTypes: [
499
527
  "sinf",
@@ -501,30 +529,30 @@ function _e(e) {
501
529
  "cenc"
502
530
  ],
503
531
  videoCapabilities: [{
504
- contentType: k,
532
+ contentType: j,
505
533
  robustness: "",
506
534
  encryptionScheme: "cbcs"
507
535
  }],
508
536
  audioCapabilities: [{
509
- contentType: A,
537
+ contentType: M,
510
538
  robustness: "",
511
539
  encryptionScheme: "cbcs"
512
540
  }]
513
541
  }] : [{
514
542
  initDataTypes: ["cenc"],
515
543
  videoCapabilities: [{
516
- contentType: k,
544
+ contentType: j,
517
545
  robustness: "SW_SECURE_DECODE",
518
546
  encryptionScheme: "cbcs"
519
547
  }],
520
548
  audioCapabilities: [{
521
- contentType: A,
549
+ contentType: M,
522
550
  robustness: "SW_SECURE_CRYPTO",
523
551
  encryptionScheme: "cbcs"
524
552
  }]
525
553
  }];
526
554
  }
527
- function j(e, t) {
555
+ function P(e, t) {
528
556
  return new Promise((n, r) => {
529
557
  let i = setTimeout(() => r(/* @__PURE__ */ Error("probe-timeout")), t);
530
558
  e.then((e) => {
@@ -534,8 +562,8 @@ function j(e, t) {
534
562
  });
535
563
  });
536
564
  }
537
- async function M(e = {}) {
538
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? he, r = {};
565
+ async function xe(e = {}) {
566
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? ye, r = {};
539
567
  if (typeof window < "u" && window.isSecureContext === !1) return {
540
568
  verdict: "insecure-context",
541
569
  detail: r
@@ -547,7 +575,7 @@ async function M(e = {}) {
547
575
  };
548
576
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
549
577
  for (let e of t) {
550
- let t = ge[e];
578
+ let t = be[e];
551
579
  if (!t) {
552
580
  r[e] = "unknown system";
553
581
  continue;
@@ -558,7 +586,7 @@ async function M(e = {}) {
558
586
  break;
559
587
  }
560
588
  try {
561
- return await j((await j(i(n, _e(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
589
+ return await P((await P(i(n, N(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
562
590
  verdict: "supported",
563
591
  system: e,
564
592
  detail: r
@@ -582,9 +610,9 @@ async function M(e = {}) {
582
610
  }
583
611
  //#endregion
584
612
  //#region src/core/manifest.ts
585
- var N = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
586
- function P(e) {
587
- return N.test(e);
613
+ var Se = /^#EXT-X-(PART-INF|PART|PRELOAD-HINT)[:\s]/m;
614
+ function Ce(e) {
615
+ return Se.test(e);
588
616
  }
589
617
  //#endregion
590
618
  //#region src/core/stats.ts
@@ -592,7 +620,7 @@ var F = { droppedFrames: 0 };
592
620
  function I(e) {
593
621
  return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
594
622
  }
595
- function ve(e, t) {
623
+ function we(e, t) {
596
624
  let n = [
597
625
  ["playing", () => t("playing")],
598
626
  ["waiting", () => t("buffering")],
@@ -609,7 +637,7 @@ function ve(e, t) {
609
637
  }
610
638
  //#endregion
611
639
  //#region src/core/engine/native.ts
612
- function ye(e) {
640
+ function Te(e) {
613
641
  try {
614
642
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
615
643
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -639,12 +667,12 @@ var z = class {
639
667
  this.deps = e;
640
668
  }
641
669
  async load() {
642
- let { video: e, descriptor: n, emit: r } = this.deps;
643
- if (n.drmEnabled) {
644
- if (!n.drm?.fairplay) {
670
+ let { video: e, descriptor: t, emit: r } = this.deps;
671
+ if (t.drmEnabled) {
672
+ if (!t.drm?.fairplay) {
645
673
  r({
646
674
  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"),
675
+ 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
676
  fatal: !0
649
677
  });
650
678
  return;
@@ -653,25 +681,25 @@ var z = class {
653
681
  }
654
682
  this.onLoaded = () => r({ type: "ready" }), this.onError = () => r({
655
683
  type: "error",
656
- error: new t("INTERNAL", 0, "native HLS playback error"),
684
+ error: new n("INTERNAL", 0, "native HLS playback error"),
657
685
  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();
686
+ }), e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = Ee(t.manifestUrl, t.analytics?.sid), e.load();
659
687
  }
660
688
  armFairPlay() {
661
- let { video: e, descriptor: n, credential: i, deviceId: a, userId: o, emit: s } = this.deps, c = n.drm?.fairplay;
689
+ let { video: e, descriptor: t, credential: r, deviceId: a, userId: o, emit: s } = this.deps, c = t.drm?.fairplay;
662
690
  if (!c) return;
663
691
  this.fairplayLicenseUrl = c.licenseUrl;
664
- let l = (e, n, i) => s({
692
+ let l = (e, t, r) => s({
665
693
  type: "error",
666
- error: new t(r(n), n, e),
667
- fatal: i
694
+ error: new n(i(t), t, e),
695
+ fatal: r
668
696
  });
669
- this.onEncrypted = (t) => {
670
- let r = t;
671
- !r.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = ye(r.initData), (async () => {
697
+ this.onEncrypted = (n) => {
698
+ let i = n;
699
+ !i.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = Te(i.initData), (async () => {
672
700
  try {
673
- let t = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
674
- initDataTypes: [r.initDataType],
701
+ let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
702
+ initDataTypes: [i.initDataType],
675
703
  videoCapabilities: [{
676
704
  contentType: "application/vnd.apple.mpegurl",
677
705
  robustness: ""
@@ -679,37 +707,37 @@ var z = class {
679
707
  distinctiveIdentifier: "not-allowed",
680
708
  persistentState: "not-allowed",
681
709
  sessionTypes: ["temporary"]
682
- }])).createMediaKeys(), s = await fetch(c.certificateUrl, { headers: { "x-api-key": i.apiKey } });
710
+ }])).createMediaKeys(), s = await fetch(c.certificateUrl, { headers: { "x-api-key": r.apiKey } });
683
711
  if (!s.ok) {
684
712
  this.fairplayArmed = !1, l(`FairPlay certificate request failed: ${s.status}`, s.status, !0);
685
713
  return;
686
714
  }
687
- await t.setServerCertificate(await s.arrayBuffer()), await e.setMediaKeys(t);
688
- let u = t.createSession();
715
+ await n.setServerCertificate(await s.arrayBuffer()), await e.setMediaKeys(n);
716
+ let u = n.createSession();
689
717
  this.keySession = u, u.addEventListener("message", (e) => {
690
718
  (async () => {
691
719
  try {
692
- let t = await fetch(this.fairplayLicenseUrl ?? c.licenseUrl, {
720
+ let n = await fetch(this.fairplayLicenseUrl ?? c.licenseUrl, {
693
721
  method: "POST",
694
722
  body: e.message,
695
723
  headers: {
696
- "x-api-key": i.apiKey,
697
- "X-Match-Urn": n.matchUrn,
724
+ "x-api-key": r.apiKey,
725
+ "X-Match-Urn": t.matchUrn,
698
726
  "X-Device-Id": a,
699
727
  ...o ? { "X-User-Id": o } : {},
700
728
  ...this.fairplayKeyIV ? { "X-Key-Iv": this.fairplayKeyIV } : {}
701
729
  }
702
730
  });
703
- if (!t.ok) {
704
- l(`FairPlay license request failed: ${t.status}`, t.status, !0);
731
+ if (!n.ok) {
732
+ l(`FairPlay license request failed: ${n.status}`, n.status, !0);
705
733
  return;
706
734
  }
707
- await u.update(new Uint8Array(await t.arrayBuffer()));
735
+ await u.update(new Uint8Array(await n.arrayBuffer()));
708
736
  } catch (e) {
709
737
  l(`FairPlay license request failed: ${String(e)}`, 0, !0);
710
738
  }
711
739
  })();
712
- }), await u.generateRequest(r.initDataType, r.initData);
740
+ }), await u.generateRequest(i.initDataType, i.initData);
713
741
  } catch (e) {
714
742
  this.fairplayArmed = !1, l(`FairPlay key session failed: ${String(e)}`, 0, !0);
715
743
  }
@@ -783,29 +811,29 @@ var z = class {
783
811
  } catch {}
784
812
  }
785
813
  };
786
- function be(e, t) {
814
+ function Ee(e, t) {
787
815
  return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
788
816
  }
789
817
  //#endregion
790
818
  //#region src/core/engine/hls.ts
791
- function xe() {
819
+ function De() {
792
820
  return e.isSupported();
793
821
  }
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) {
822
+ var Oe = 3, ke = 5, Ae = 3, je = 3e4, Me = 12e3, Ne = 3e3, B = 6e3, Pe = 4e3, Fe = 2, Ie = 5, Le = 2e3, Re = 3e4, ze = 2, Be = 9, V = .5;
823
+ function Ve(e) {
796
824
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
797
825
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
798
826
  return;
799
827
  }
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);
828
+ e.liveSyncDuration ??= ze;
829
+ let t = Math.max(12, e.liveSyncDuration + Be);
830
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), He(e, e.liveSyncDuration);
803
831
  }
804
- function Fe(e, t) {
832
+ function He(e, t) {
805
833
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
806
834
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
807
835
  }
808
- var V = class {
836
+ var H = class {
809
837
  name = "hls";
810
838
  hls = null;
811
839
  deps;
@@ -834,27 +862,47 @@ var V = class {
834
862
  chaseSuspended = !1;
835
863
  savedLiveMaxLatency;
836
864
  savedMaxSyncRate;
865
+ capObserver = null;
866
+ capTimer = null;
867
+ sizeCapLevel = null;
868
+ bitrateCapLevel = -1;
869
+ sizeCapManaged = !0;
870
+ dprMedia = null;
871
+ onDprChange = null;
837
872
  constructor(e) {
838
873
  this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl;
839
874
  }
840
875
  async load() {
841
- let { descriptor: e, snapToLiveOnRefocus: n, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
876
+ let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
842
877
  if (e.drmEnabled && !o) {
843
878
  i({
844
879
  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)"),
880
+ 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
881
  fatal: !0
847
882
  });
848
883
  return;
849
884
  }
850
- this.startHls(), n !== !1 && typeof document < "u" && (this.onVisibility = () => {
885
+ this.startHls(), t !== !1 && typeof document < "u" && (this.onVisibility = () => {
851
886
  let e = this.hls;
852
887
  if (document.visibilityState !== "visible" || !e) return;
853
888
  let t = e.liveSyncPosition;
854
889
  if (typeof t != "number") return;
855
890
  let n = typeof e.targetLatency == "number" && e.targetLatency > 0 ? e.targetLatency : 3;
856
891
  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);
892
+ }, 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());
893
+ }
894
+ watchDprChanges() {
895
+ if (typeof matchMedia != "function") return;
896
+ this.unwatchDprChanges();
897
+ let e = typeof self < "u" && self.devicePixelRatio || 1, t = matchMedia(`(resolution: ${e}dppx)`);
898
+ if (typeof t.addEventListener != "function") return;
899
+ let n = () => {
900
+ this.onMediaResize(), this.watchDprChanges();
901
+ };
902
+ t.addEventListener("change", n), this.dprMedia = t, this.onDprChange = n;
903
+ }
904
+ unwatchDprChanges() {
905
+ this.dprMedia && this.onDprChange && this.dprMedia.removeEventListener("change", this.onDprChange), this.dprMedia = null, this.onDprChange = null;
858
906
  }
859
907
  clampOverSeek() {
860
908
  let e = this.hls;
@@ -862,7 +910,7 @@ var V = class {
862
910
  let t = e.liveSyncPosition;
863
911
  if (typeof t != "number" || !Number.isFinite(t)) return;
864
912
  let { video: n } = this.deps;
865
- n.currentTime <= t + Ne || (n.currentTime = t);
913
+ n.currentTime <= t + V || (n.currentTime = t);
866
914
  }
867
915
  startHls() {
868
916
  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 +921,7 @@ var V = class {
873
921
  maxBufferHole: .5,
874
922
  maxLiveSyncPlaybackRate: 1.5,
875
923
  abrEwmaDefaultEstimate: 1e6,
876
- capLevelToPlayerSize: !0,
924
+ capLevelToPlayerSize: !1,
877
925
  ...u ? {
878
926
  emeEnabled: !0,
879
927
  drmSystems: { ...this.licenseUrl ? { "com.widevine.alpha": { licenseUrl: this.licenseUrl } } : {} },
@@ -898,14 +946,14 @@ var V = class {
898
946
  } } : {},
899
947
  ...c
900
948
  };
901
- Pe(d);
949
+ Ve(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
902
950
  let f = new e(d);
903
951
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
904
- this.mediaRecoveries = 0, this.networkRecoveries = 0, l({ type: "ready" });
952
+ this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
905
953
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
906
954
  t?.frag?.type === "main" && (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
907
955
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.recoveryResetTimer = null;
908
- }, Te));
956
+ }, je));
909
957
  }), f.on(e.Events.FRAG_LOADING, (e, t) => {
910
958
  t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
911
959
  }), f.on(e.Events.FRAG_LOADED, (e, t) => {
@@ -921,7 +969,7 @@ var V = class {
921
969
  }));
922
970
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => {
923
971
  this.checkEndStale(), this.checkStarvation();
924
- }, De), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
972
+ }, Ne), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
925
973
  this.renditionSwitches += 1, l({
926
974
  type: "qualitychange",
927
975
  index: t.level
@@ -951,15 +999,54 @@ var V = class {
951
999
  }
952
1000
  setMaxBitrate(e) {
953
1001
  let t = this.hls;
954
- if (!t) return;
955
- if (e == null) {
956
- t.autoLevelCapping = -1;
1002
+ if (t) {
1003
+ if (e == null) this.bitrateCapLevel = -1;
1004
+ else {
1005
+ let n = -1;
1006
+ t.levels.forEach((t, r) => {
1007
+ t.bitrate <= e && (n = r);
1008
+ }), this.bitrateCapLevel = n;
1009
+ }
1010
+ this.pushCap();
1011
+ }
1012
+ }
1013
+ computeSizeCapTarget() {
1014
+ let e = this.hls;
1015
+ if (!e || e.levels.length === 0) return null;
1016
+ let { video: n } = this.deps, r = n.getBoundingClientRect(), i = r.width, a = r.height;
1017
+ if (!i && !a && (i = n.width || 0, a = n.height || 0), !(i > 0) || !(a > 0)) return null;
1018
+ let o = e.config ?? {}, s = 1;
1019
+ 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);
1020
+ }
1021
+ onMediaResize() {
1022
+ if (!this.sizeCapManaged) return;
1023
+ let e = this.computeSizeCapTarget();
1024
+ if (e == null) return;
1025
+ if (this.sizeCapLevel == null) {
1026
+ this.applySizeCap();
1027
+ return;
1028
+ }
1029
+ if (e === this.sizeCapLevel) {
1030
+ this.capTimer &&= (clearTimeout(this.capTimer), null);
957
1031
  return;
958
1032
  }
959
- let n = -1;
960
- t.levels.forEach((t, r) => {
961
- t.bitrate <= e && (n = r);
962
- }), t.autoLevelCapping = n;
1033
+ this.capTimer && clearTimeout(this.capTimer);
1034
+ let t = e > this.sizeCapLevel ? Le : Re;
1035
+ this.capTimer = setTimeout(() => {
1036
+ this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1037
+ }, t);
1038
+ }
1039
+ applySizeCap() {
1040
+ if (!this.sizeCapManaged) return;
1041
+ this.capTimer &&= (clearTimeout(this.capTimer), null);
1042
+ let e = this.computeSizeCapTarget();
1043
+ e != null && (this.sizeCapLevel = e, this.pushCap());
1044
+ }
1045
+ pushCap() {
1046
+ let e = this.hls;
1047
+ if (!e) return;
1048
+ let t = this.sizeCapLevel ?? -1, n = this.bitrateCapLevel;
1049
+ e.autoLevelCapping = t < 0 ? n : n < 0 ? t : Math.min(t, n);
963
1050
  }
964
1051
  getAudioTracks() {
965
1052
  return this.hls ? this.hls.audioTracks.map((e) => ({
@@ -1025,7 +1112,7 @@ var V = class {
1025
1112
  return;
1026
1113
  }
1027
1114
  let e = Date.now() - this.lastAdvanceMs;
1028
- !this.chaseSuspended && e > B && this.suspendChasing(), e > Ee && this.emitEnded("stalled");
1115
+ !this.chaseSuspended && e > B && this.suspendChasing(), e > Me && this.emitEnded("stalled");
1029
1116
  }
1030
1117
  checkStarvation() {
1031
1118
  let e = this.hls, { video: t } = this.deps;
@@ -1034,12 +1121,12 @@ var V = class {
1034
1121
  this.starvedTicks = 0;
1035
1122
  return;
1036
1123
  }
1037
- let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < B, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Oe;
1124
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < B, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Pe;
1038
1125
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1039
1126
  this.starvedTicks = 0;
1040
1127
  return;
1041
1128
  }
1042
- if (!(++this.starvedTicks < ke) && (this.starvedTicks = 0, !(this.starvationKicks >= Ae))) {
1129
+ if (!(++this.starvedTicks < Fe) && (this.starvedTicks = 0, !(this.starvationKicks >= Ie))) {
1043
1130
  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
1131
  try {
1045
1132
  e.stopLoad(), e.startLoad(-1);
@@ -1073,7 +1160,7 @@ var V = class {
1073
1160
  } catch {}
1074
1161
  }
1075
1162
  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) {
1163
+ 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
1164
  try {
1078
1165
  this.hls.destroy();
1079
1166
  } catch (e) {
@@ -1086,9 +1173,9 @@ var V = class {
1086
1173
  this.recoveryResetTimer &&= (clearTimeout(this.recoveryResetTimer), null);
1087
1174
  }
1088
1175
  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;
1176
+ if (t.details !== e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED && t.details !== e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED) return !1;
1090
1177
  let n = t.response?.code ?? 0;
1091
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= we) return !1;
1178
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= Ae) return !1;
1092
1179
  this.licenseRecoveries += 1;
1093
1180
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1094
1181
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1116,7 +1203,7 @@ var V = class {
1116
1203
  return;
1117
1204
  }
1118
1205
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1119
- if (this.networkRecoveries < Ce) {
1206
+ if (this.networkRecoveries < ke) {
1120
1207
  this.networkRecoveries += 1;
1121
1208
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1122
1209
  this.networkRetryTimer = setTimeout(() => {
@@ -1132,7 +1219,7 @@ var V = class {
1132
1219
  return;
1133
1220
  }
1134
1221
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1135
- if (this.mediaRecoveries < Se) {
1222
+ if (this.mediaRecoveries < Oe) {
1136
1223
  this.mediaRecoveries += 1, n.recoverMediaError();
1137
1224
  return;
1138
1225
  }
@@ -1150,11 +1237,11 @@ var V = class {
1150
1237
  });
1151
1238
  }
1152
1239
  }
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})` : ""}`);
1240
+ toError(e, t = "PLAYBACK") {
1241
+ let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1242
+ return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1156
1243
  }
1157
- }, Ie = 15e3, Le = 100, H = 1, U = {
1244
+ }, Ue = 15e3, We = 100, U = 1, W = {
1158
1245
  idle: "PLAYER_STATE_IDLE",
1159
1246
  waiting: "PLAYER_STATE_WAITING",
1160
1247
  loading: "PLAYER_STATE_LOADING",
@@ -1164,7 +1251,7 @@ var V = class {
1164
1251
  ended: "PLAYER_STATE_ENDED",
1165
1252
  error: "PLAYER_STATE_ERROR"
1166
1253
  };
1167
- function Re(e) {
1254
+ function Ge(e) {
1168
1255
  try {
1169
1256
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1170
1257
  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 +1259,7 @@ function Re(e) {
1172
1259
  return;
1173
1260
  }
1174
1261
  }
1175
- var ze = class {
1262
+ var Ke = class {
1176
1263
  opts;
1177
1264
  url;
1178
1265
  now;
@@ -1207,7 +1294,7 @@ var ze = class {
1207
1294
  constructor(e) {
1208
1295
  this.opts = {
1209
1296
  ...e,
1210
- heartbeatMs: e.heartbeatMs ?? Ie
1297
+ heartbeatMs: e.heartbeatMs ?? Ue
1211
1298
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
1212
1299
  }
1213
1300
  start() {
@@ -1219,18 +1306,18 @@ var ze = class {
1219
1306
  noteStateChange(e, t, n, r) {
1220
1307
  if (this.endedFlag || e === t) return;
1221
1308
  let i = this.now();
1222
- t === "playing" ? (this.playingSinceMs === null && (this.playingSinceMs = i), this.startupTimeMs === 0 && (this.startupTimeMs = Math.max(H, i - this.bornMs))) : this.playingSinceMs !== null && (this.playingTotalMs += i - this.playingSinceMs, this.playingSinceMs = null), t === "buffering" && e === "playing" ? (this.rebufferCount += 1, this.bufferingSinceMs = i) : e === "buffering" && this.bufferingSinceMs !== null && (this.rebufferTotalMs += i - this.bufferingSinceMs, this.bufferingSinceMs = null), t === "paused" && (this.pauseCount += 1);
1309
+ t === "playing" ? (this.playingSinceMs === null && (this.playingSinceMs = i), this.startupTimeMs === 0 && (this.startupTimeMs = Math.max(U, i - this.bornMs))) : this.playingSinceMs !== null && (this.playingTotalMs += i - this.playingSinceMs, this.playingSinceMs = null), t === "buffering" && e === "playing" ? (this.rebufferCount += 1, this.bufferingSinceMs = i) : e === "buffering" && this.bufferingSinceMs !== null && (this.rebufferTotalMs += i - this.bufferingSinceMs, this.bufferingSinceMs = null), t === "paused" && (this.pauseCount += 1);
1223
1310
  let a = {
1224
- fromState: U[e] ?? "PLAYER_STATE_UNSPECIFIED",
1225
- toState: U[t] ?? "PLAYER_STATE_UNSPECIFIED"
1311
+ fromState: W[e] ?? "PLAYER_STATE_UNSPECIFIED",
1312
+ toState: W[t] ?? "PLAYER_STATE_UNSPECIFIED"
1226
1313
  };
1227
1314
  n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1228
1315
  }
1229
1316
  noteReady() {
1230
- this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(H, this.now() - this.bornMs));
1317
+ this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(U, this.now() - this.bornMs));
1231
1318
  }
1232
1319
  noteFirstBytes() {
1233
- this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(H, this.now() - this.bornMs));
1320
+ this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(U, this.now() - this.bornMs));
1234
1321
  }
1235
1322
  noteSeek() {
1236
1323
  this.endedFlag || (this.seekCount += 1);
@@ -1265,7 +1352,7 @@ var ze = class {
1265
1352
  let c = e.renditionSwitches ?? 0;
1266
1353
  c > this.lastSwitches && (i.renditionSwitchesDelta = c - this.lastSwitches), this.lastSwitches = c, n > this.lastHbPlayingMs && (i.playingMsDelta = Math.round(n - this.lastHbPlayingMs)), this.lastHbPlayingMs = n, r > this.lastHbHiddenMs && (i.hiddenMsDelta = Math.round(r - this.lastHbHiddenMs)), this.lastHbHiddenMs = r, typeof e.latencySeconds == "number" && (i.latencyMs = Math.round(e.latencySeconds * 1e3)), typeof e.targetLatencySeconds == "number" && (i.targetLatencyMs = Math.round(e.targetLatencySeconds * 1e3)), typeof e.bandwidthKbps == "number" && (i.bandwidthEstKbps = e.bandwidthKbps), typeof e.levelHeight == "number" && (i.renditionHeight = e.levelHeight), typeof e.renditionBitrateKbps == "number" && (i.renditionBitrateKbps = e.renditionBitrateKbps), e.atLiveEdge === !0 && (i.atLiveEdge = !0);
1267
1354
  let l = this.bufferedMsFrom(e);
1268
- l > 0 && (i.bufferedMs = l), i.playerState = U[e.state] ?? "PLAYER_STATE_UNSPECIFIED", this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1355
+ l > 0 && (i.bufferedMs = l), i.playerState = W[e.state] ?? "PLAYER_STATE_UNSPECIFIED", this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1269
1356
  }
1270
1357
  bufferedMsFrom(e) {
1271
1358
  let t = e;
@@ -1299,7 +1386,7 @@ var ze = class {
1299
1386
  };
1300
1387
  }
1301
1388
  enqueue(e) {
1302
- for (this.pending.push(this.wrap(e)); this.pending.length > Le;) {
1389
+ for (this.pending.push(this.wrap(e)); this.pending.length > We;) {
1303
1390
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1304
1391
  this.pending.splice(e >= 0 ? e : 0, 1);
1305
1392
  }
@@ -1321,7 +1408,7 @@ var ze = class {
1321
1408
  keepalive: !0
1322
1409
  }).catch(() => {});
1323
1410
  }
1324
- }, Be = "1.15.0", W = {
1411
+ }, qe = "1.16.0", G = {
1325
1412
  accent: "#E1B600",
1326
1413
  accentText: "#1B1C23",
1327
1414
  background: "#0e0f13",
@@ -1332,7 +1419,7 @@ var ze = class {
1332
1419
  border: "rgba(255, 255, 255, 0.1)",
1333
1420
  radius: "8px",
1334
1421
  fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
1335
- }, G = {
1422
+ }, K = {
1336
1423
  accent: "--havik-accent",
1337
1424
  accentText: "--havik-accent-text",
1338
1425
  background: "--havik-bg",
@@ -1344,25 +1431,25 @@ var ze = class {
1344
1431
  radius: "--havik-radius",
1345
1432
  fontFamily: "--havik-font"
1346
1433
  };
1347
- function K(e, t) {
1434
+ function q(e, t) {
1348
1435
  let n = {
1349
- ...W,
1436
+ ...G,
1350
1437
  ...t
1351
1438
  };
1352
- for (let t of Object.keys(G)) e.style.setProperty(G[t], n[t]);
1439
+ for (let t of Object.keys(K)) e.style.setProperty(K[t], n[t]);
1353
1440
  n.logoUrl && e.style.setProperty("--havik-logo", `url("${n.logoUrl}")`);
1354
1441
  }
1355
1442
  //#endregion
1356
1443
  //#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) {
1359
- if (e.getElementById(q)) return;
1444
+ var J = "havik-player-styles", Je = "\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";
1445
+ function Ye(e = document) {
1446
+ if (e.getElementById(J)) return;
1360
1447
  let t = e.createElement("style");
1361
- t.id = q, t.textContent = Ve, e.head.appendChild(t);
1448
+ t.id = J, t.textContent = Je, e.head.appendChild(t);
1362
1449
  }
1363
1450
  //#endregion
1364
1451
  //#region src/ui/icons.ts
1365
- var Ue = {
1452
+ var Xe = {
1366
1453
  play: "<path d=\"M8 5v14l11-7z\"/>",
1367
1454
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1368
1455
  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\"/>",
@@ -1375,21 +1462,21 @@ var Ue = {
1375
1462
  liveDot: "<circle cx=\"12\" cy=\"12\" r=\"6\"/>",
1376
1463
  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
1464
  };
1378
- function J(e) {
1379
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Ue[e]}</svg>`;
1465
+ function Y(e) {
1466
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Xe[e]}</svg>`;
1380
1467
  }
1381
1468
  //#endregion
1382
1469
  //#region src/ui/controls.ts
1383
- var We = 3e3, Ge = /* @__PURE__ */ new Set([
1470
+ var Ze = 3e3, Qe = /* @__PURE__ */ new Set([
1384
1471
  "GONE",
1385
1472
  "NOT_FOUND",
1386
1473
  "INVALID_URN",
1387
1474
  "UNAUTHORIZED"
1388
1475
  ]);
1389
- function Ke(e) {
1390
- return new qe(e);
1476
+ function $e(e) {
1477
+ return new et(e);
1391
1478
  }
1392
- var qe = class {
1479
+ var et = class {
1393
1480
  root;
1394
1481
  video;
1395
1482
  player;
@@ -1429,7 +1516,7 @@ var qe = class {
1429
1516
  scrubbing = !1;
1430
1517
  offFns = [];
1431
1518
  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");
1519
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Ze, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, Ye(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
1520
  let t = (e, t, n) => {
1434
1521
  let r = this.doc.createElement(e);
1435
1522
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1440,12 +1527,12 @@ var qe = class {
1440
1527
  "aria-label": r,
1441
1528
  title: r
1442
1529
  });
1443
- return i.innerHTML = J(n), i;
1530
+ return i.innerHTML = Y(n), i;
1444
1531
  };
1445
1532
  this.overlay = t("div", "havik-overlay"), this.center = t("button", "havik-center", {
1446
1533
  type: "button",
1447
1534
  "aria-label": "Play"
1448
- }), this.center.innerHTML = J("play"), this.spinner = t("div", "havik-spinner"), this.spinner.style.display = "none", this.overlay.append(this.center, this.spinner), this.gradient = t("div", "havik-gradient"), this.bar = t("div", "havik-bar"), this.seek = t("div", "havik-seek", {
1535
+ }), this.center.innerHTML = Y("play"), this.spinner = t("div", "havik-spinner"), this.spinner.style.display = "none", this.overlay.append(this.center, this.spinner), this.gradient = t("div", "havik-gradient"), this.bar = t("div", "havik-bar"), this.seek = t("div", "havik-seek", {
1449
1536
  role: "slider",
1450
1537
  "aria-label": "Seek",
1451
1538
  "aria-valuemin": "0",
@@ -1463,7 +1550,7 @@ var qe = class {
1463
1550
  "aria-label": "Volume"
1464
1551
  });
1465
1552
  let a = t("div", "havik-vol");
1466
- a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = J("liveDot");
1553
+ a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = Y("liveDot");
1467
1554
  let o = this.doc.createElement("span");
1468
1555
  o.textContent = "LIVE", this.liveGo = t("button", "havik-live__go", { type: "button" }), this.liveGo.textContent = "Go live", this.live.append(o, this.liveGo), this.time = t("div", "havik-time"), this.time.textContent = "0:00", this.time.style.display = "none";
1469
1556
  let s = t("div", "havik-spacer");
@@ -1485,7 +1572,7 @@ var qe = class {
1485
1572
  this.setVolumeFill(t), this.player.setVolume(t), t > 0 && e.muted && this.player.setMuted(!1);
1486
1573
  }), 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
1574
  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();
1575
+ })), 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
1576
  }
1490
1577
  togglePlay() {
1491
1578
  this.video.paused || this.video.ended ? this.player.play() : this.player.pause();
@@ -1513,12 +1600,12 @@ var qe = class {
1513
1600
  }
1514
1601
  if (e === "preplay") {
1515
1602
  let e = this.doc.createElement("button");
1516
- 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);
1603
+ e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Y("play"), e.addEventListener("click", () => void this.player.play()), t.append(e);
1517
1604
  } else {
1518
1605
  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)) {
1606
+ 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 && !Qe.has(this.lastError.code)) {
1520
1607
  let e = this.doc.createElement("button");
1521
- 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);
1608
+ e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Y("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), t.append(e);
1522
1609
  }
1523
1610
  }
1524
1611
  this.root.append(t), this.statusBox = t, this.show();
@@ -1557,7 +1644,7 @@ var qe = class {
1557
1644
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
1558
1645
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
1559
1646
  }
1560
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Y(this.video.currentTime)} / ${Y(this.video.duration)}`);
1647
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${X(this.video.currentTime)} / ${X(this.video.duration)}`);
1561
1648
  }
1562
1649
  syncLive() {
1563
1650
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -1607,17 +1694,17 @@ var qe = class {
1607
1694
  renderQualityMenu() {
1608
1695
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1609
1696
  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)));
1697
+ for (let n of e) this.menu.append(this.menuItem(tt(n), t === n.index, () => this.player.setQuality(n.index)));
1611
1698
  }
1612
1699
  renderAudioMenu() {
1613
1700
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1614
1701
  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)));
1702
+ for (let t of e) this.menu.append(this.menuItem(nt(t), t.default, () => this.player.setAudioTrack(t.id)));
1616
1703
  }
1617
1704
  renderCcMenu() {
1618
1705
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1619
1706
  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)));
1707
+ for (let t of e) this.menu.append(this.menuItem(rt(t), !1, () => this.player.setTextTrack(t.id)));
1621
1708
  }
1622
1709
  onTracksChanged() {
1623
1710
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1636,13 +1723,12 @@ var qe = class {
1636
1723
  this.pipBtn.setAttribute("aria-pressed", e ? "true" : "false");
1637
1724
  }
1638
1725
  async toggleFullscreen() {
1639
- let e = this.root, t = this.doc;
1640
1726
  try {
1641
- t.fullscreenElement ? await t.exitFullscreen?.() : await e.requestFullscreen?.();
1727
+ C(this.video, this.root) ? await T(this.video, this.root) : await w(this.video, this.root);
1642
1728
  } catch {}
1643
1729
  }
1644
1730
  syncFullscreen() {
1645
- let e = this.doc.fullscreenElement === this.root;
1731
+ let e = C(this.video, this.root);
1646
1732
  this.setIcon(this.fsBtn, e ? "fullscreenExit" : "fullscreen"), this.fsBtn.setAttribute("aria-label", e ? "Exit fullscreen" : "Fullscreen");
1647
1733
  }
1648
1734
  poke() {
@@ -1701,7 +1787,7 @@ var qe = class {
1701
1787
  }
1702
1788
  }
1703
1789
  setIcon(e, t) {
1704
- e.innerHTML = J(t);
1790
+ e.innerHTML = Y(t);
1705
1791
  }
1706
1792
  destroy() {
1707
1793
  this.hideTimer && clearTimeout(this.hideTimer);
@@ -1718,32 +1804,32 @@ var qe = class {
1718
1804
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
1719
1805
  }
1720
1806
  };
1721
- function Y(e) {
1807
+ function X(e) {
1722
1808
  (!Number.isFinite(e) || e < 0) && (e = 0);
1723
1809
  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
1810
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1725
1811
  }
1726
- function Je(e) {
1812
+ function tt(e) {
1727
1813
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1728
1814
  }
1729
- function Ye(e) {
1815
+ function nt(e) {
1730
1816
  return e.name || e.lang || `Track ${e.id}`;
1731
1817
  }
1732
- function Xe(e) {
1818
+ function rt(e) {
1733
1819
  return e.name || e.lang || `Track ${e.id}`;
1734
1820
  }
1735
1821
  //#endregion
1736
1822
  //#region src/managed/index.ts
1737
- var Ze = 6e3, X = 45e3, Qe = 2e3, $e = 3, Z = 2e3, et = 480 * 1e3;
1823
+ var it = 6e3, Z = 45e3, at = 2e3, ot = 3, st = 2e3, ct = 48e4;
1738
1824
  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;
1745
- }
1746
- var tt = class {
1825
+ if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1826
+ if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
1827
+ if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
1828
+ if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
1829
+ let t = new lt(e);
1830
+ return await t.start(), t;
1831
+ }
1832
+ var lt = class {
1747
1833
  opts;
1748
1834
  abort = new AbortController();
1749
1835
  listeners = {};
@@ -1777,19 +1863,19 @@ var tt = class {
1777
1863
  }
1778
1864
  async start() {
1779
1865
  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();
1866
+ let e = ++this.loadGen, t = this.resolveOptions();
1781
1867
  try {
1782
- let t = await y(n);
1783
- if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) {
1868
+ let n = await v(t);
1869
+ if (await this.onResolved(n, e), e === this.loadGen && this._state === "error" && this._lastError) {
1784
1870
  let e = this._lastError;
1785
1871
  throw this.destroy(), e;
1786
1872
  }
1787
- } catch (n) {
1788
- if (this.opts.waitForLive && n instanceof t && a(n.code)) {
1789
- this.setState("waiting"), this.beginArming(e, n.liveStartsAtMs);
1873
+ } catch (t) {
1874
+ if (this.opts.waitForLive && t instanceof n && o(t.code)) {
1875
+ this.setState("waiting"), this.beginArming(e, t.liveStartsAtMs);
1790
1876
  return;
1791
1877
  }
1792
- throw n;
1878
+ throw t;
1793
1879
  }
1794
1880
  }
1795
1881
  resolveOptions() {
@@ -1802,67 +1888,67 @@ var tt = class {
1802
1888
  };
1803
1889
  }
1804
1890
  beginArming(e, t) {
1805
- let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1891
+ let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? D(this.opts.baseUrl);
1806
1892
  n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1807
1893
  }
1808
1894
  beginArmingByPolling(e) {
1809
- b({
1895
+ y({
1810
1896
  ...this.resolveOptions(),
1811
1897
  signal: this.abort.signal,
1812
1898
  waitForLive: this.buildWaitConfig(e)
1813
1899
  }).then((t) => {
1814
1900
  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)));
1901
+ }).catch((t) => {
1902
+ this.destroyed || this.abort.signal.aborted || e !== this.loadGen || this.fail(t instanceof n ? t : new n("INTERNAL", 0, String(t)));
1817
1903
  });
1818
1904
  }
1819
- armWithSse(e, n, r) {
1820
- let i = !1, o = !1, s = new AbortController();
1905
+ armWithSse(e, t, r) {
1906
+ let i = !1, a = !1, s = new AbortController();
1821
1907
  this.armFallbackCtrl = s, this.setState("waiting");
1822
1908
  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
1909
  setTimeout(() => {
1824
1910
  i || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1825
1911
  phase: "tooEarly",
1826
- retryInMs: X,
1912
+ retryInMs: Z,
1827
1913
  attempt: 1,
1828
1914
  elapsedMs: 0,
1829
1915
  ...d !== void 0 && d > Date.now() ? { ttkMs: d - Date.now() } : {}
1830
1916
  });
1831
1917
  }, 0);
1832
- let f = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, p = async (n) => {
1918
+ let f = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, p = async (t) => {
1833
1919
  if (!(i || f())) {
1834
1920
  i = !0, this.closeArming(), s.abort();
1835
1921
  try {
1836
- await this.onResolved(n, e);
1922
+ await this.onResolved(t, e);
1837
1923
  } catch (e) {
1838
1924
  if (f()) return;
1839
- this.fail(e instanceof t ? e : new t("INTERNAL", 0, String(e)));
1925
+ this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e)));
1840
1926
  }
1841
1927
  }
1842
1928
  }, m = () => {
1843
- i || o || f() || (o = !0, b({
1929
+ i || a || f() || (a = !0, y({
1844
1930
  ...this.resolveOptions(),
1845
1931
  signal: s.signal,
1846
1932
  waitForLive: this.buildWaitConfig(e)
1847
1933
  }).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))));
1934
+ i || f() || s.signal.aborted || (i = !0, this.closeArming(), this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e))));
1849
1935
  }));
1850
1936
  }, h = () => {
1851
- 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))));
1937
+ i || f() || v(this.resolveOptions()).then((e) => p(e)).catch((e) => {
1938
+ 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
1939
  });
1854
1940
  }, g = (e) => {
1855
1941
  this.armSseTimer && clearTimeout(this.armSseTimer), this.armSseTimer = setTimeout(() => {
1856
1942
  !i && !f() && m();
1857
1943
  }, e);
1858
1944
  };
1859
- g(Ze), this.armingSub = w({
1860
- eventsBaseUrl: n,
1945
+ g(it), this.armingSub = E({
1946
+ eventsBaseUrl: t,
1861
1947
  matchUrn: this.opts.matchUrn,
1862
1948
  credential: this.opts.credential,
1863
- onAlive: () => g(X),
1949
+ onAlive: () => g(Z),
1864
1950
  onState: (e) => {
1865
- i || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * Qe);
1951
+ i || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * at);
1866
1952
  },
1867
1953
  onError: () => m()
1868
1954
  });
@@ -1882,11 +1968,11 @@ var tt = class {
1882
1968
  async onResolved(e, t) {
1883
1969
  if (this.destroyed || t !== this.loadGen) return;
1884
1970
  this._descriptor = e, this.endedHandled = !1;
1885
- let n = await o(this.opts.credential);
1971
+ let n = await s(this.opts.credential);
1886
1972
  if (this.destroyed || t !== this.loadGen) return;
1887
1973
  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);
1974
+ let r = this.createEngine(e, n), i = we(this.opts.video, (e) => {
1975
+ this._state !== "ended" && this._state !== "error" && this.setState(e);
1890
1976
  });
1891
1977
  if (await r.load(), this.destroyed || t !== this.loadGen) {
1892
1978
  i(), r.destroy();
@@ -1902,14 +1988,14 @@ var tt = class {
1902
1988
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
1903
1989
  let r = e.analytics?.sid;
1904
1990
  if (!r) return;
1905
- let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Re(this.opts.baseUrl);
1991
+ let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Ge(this.opts.baseUrl);
1906
1992
  if (!i) return;
1907
- let a = this.opts.video, o = new ze({
1993
+ let a = this.opts.video, o = new Ke({
1908
1994
  endpoint: i,
1909
1995
  sid: r,
1910
1996
  apiKey: n,
1911
1997
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
1912
- sdkVersion: Be,
1998
+ sdkVersion: qe,
1913
1999
  sample: () => {
1914
2000
  let e = this.engine?.getStats() ?? F, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
1915
2001
  return {
@@ -1924,14 +2010,14 @@ var tt = class {
1924
2010
  let s = () => o.noteSeek();
1925
2011
  a.addEventListener("seeking", s), this.detachSeekListener = () => a.removeEventListener("seeking", s), o.start();
1926
2012
  }
1927
- createEngine(e, n) {
2013
+ createEngine(e, t) {
1928
2014
  let r = { ...this.opts.hlsConfig };
1929
2015
  this.opts.startLevel != null && (r.startLevel = this.opts.startLevel), this.opts.liveLatencyTarget != null && (r.liveSyncDuration = this.opts.liveLatencyTarget);
1930
2016
  let i = {
1931
2017
  video: this.opts.video,
1932
2018
  descriptor: e,
1933
- credential: n,
1934
- deviceId: O(),
2019
+ credential: t,
2020
+ deviceId: A(),
1935
2021
  userId: this.opts.userId,
1936
2022
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
1937
2023
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -1940,11 +2026,11 @@ var tt = class {
1940
2026
  emit: (e) => this.onEngineEvent(e)
1941
2027
  }, a = this.opts.engine ?? "auto";
1942
2028
  if (a === "native") return new z(i);
1943
- if (a === "hls") return new V(i);
2029
+ if (a === "hls") return new H(i);
1944
2030
  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);
2031
+ if (De()) return new H(i);
1946
2032
  if (L(this.opts.video)) return new z(i);
1947
- throw new t("INTERNAL", 0, "no supported HLS playback engine in this browser");
2033
+ throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
1948
2034
  }
1949
2035
  onEngineEvent(e) {
1950
2036
  if (!this.destroyed) switch (e.type) {
@@ -1966,19 +2052,17 @@ var tt = class {
1966
2052
  case "ended":
1967
2053
  this.onStreamEnded();
1968
2054
  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;
2055
+ 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
2056
  }
1973
2057
  }
1974
2058
  startLiveState(e) {
1975
2059
  if (this.opts.liveStateEvents === !1) return;
1976
- let n = this.opts.eventsBaseUrl ?? T(this.opts.baseUrl);
1977
- if (!n) return;
2060
+ let t = this.opts.eventsBaseUrl ?? D(this.opts.baseUrl);
2061
+ if (!t) return;
1978
2062
  this.closeLiveState();
1979
2063
  let r = 0, i = !1;
1980
- this.liveStateSub = w({
1981
- eventsBaseUrl: n,
2064
+ this.liveStateSub = E({
2065
+ eventsBaseUrl: t,
1982
2066
  matchUrn: this.opts.matchUrn,
1983
2067
  credential: this.opts.credential,
1984
2068
  onState: (t) => {
@@ -1988,7 +2072,7 @@ var tt = class {
1988
2072
  r = 0, i = !1;
1989
2073
  },
1990
2074
  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.`))));
2075
+ this.destroyed || e !== this.loadGen || i || (r += 1, r >= ot && (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
2076
  }
1993
2077
  });
1994
2078
  }
@@ -2010,7 +2094,7 @@ var tt = class {
2010
2094
  }
2011
2095
  t.controls = !1;
2012
2096
  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({
2097
+ r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = $e({
2014
2098
  root: n,
2015
2099
  video: t,
2016
2100
  player: this,
@@ -2033,7 +2117,7 @@ var tt = class {
2033
2117
  }
2034
2118
  startStats() {
2035
2119
  this.stopStats();
2036
- let e = Math.max(500, this.opts.statsIntervalMs ?? Z);
2120
+ let e = Math.max(500, this.opts.statsIntervalMs ?? st);
2037
2121
  this.statsTimer = setInterval(() => {
2038
2122
  if (!this.engine) return;
2039
2123
  let e = this.engine.getStats();
@@ -2045,7 +2129,7 @@ var tt = class {
2045
2129
  }
2046
2130
  scheduleLicenseRefresh() {
2047
2131
  this.stopRefresh();
2048
- let e = this.opts.licenseRefreshMs ?? et;
2132
+ let e = this.opts.licenseRefreshMs ?? ct;
2049
2133
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2050
2134
  }
2051
2135
  stopRefresh() {
@@ -2054,7 +2138,7 @@ var tt = class {
2054
2138
  async refreshLicense() {
2055
2139
  let e = this.engine;
2056
2140
  if (!(this.destroyed || !e)) try {
2057
- let t = await y(this.resolveOptions());
2141
+ let t = await v(this.resolveOptions());
2058
2142
  if (this.destroyed || this.engine !== e) return;
2059
2143
  let n = t.drm?.widevine?.licenseUrl, r = t.drm?.fairplay?.licenseUrl;
2060
2144
  (n || r) && (e.setLicenseUrls({
@@ -2062,7 +2146,7 @@ var tt = class {
2062
2146
  fairplay: r
2063
2147
  }), this._descriptor = t);
2064
2148
  } catch (e) {
2065
- e instanceof t && !a(e.code) && (this.stopRefresh(), this.destroyed || this.emit("warning", e));
2149
+ e instanceof n && !o(e.code) && (this.stopRefresh(), this.destroyed || this.emit("warning", e));
2066
2150
  }
2067
2151
  }
2068
2152
  async play() {
@@ -2120,6 +2204,12 @@ var tt = class {
2120
2204
  let e = document;
2121
2205
  e.pictureInPictureElement === this.opts.video && typeof e.exitPictureInPicture == "function" && await e.exitPictureInPicture();
2122
2206
  }
2207
+ async enterFullscreen() {
2208
+ return !this.destroyed && w(this.opts.video, this.container ?? void 0);
2209
+ }
2210
+ async exitFullscreen() {
2211
+ await T(this.opts.video, this.container ?? void 0);
2212
+ }
2123
2213
  retry() {
2124
2214
  return this.reload();
2125
2215
  }
@@ -2128,14 +2218,14 @@ var tt = class {
2128
2218
  let e = ++this.loadGen;
2129
2219
  this.stopStats(), this.stopRefresh(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null;
2130
2220
  try {
2131
- let t = await b({
2221
+ let t = await y({
2132
2222
  ...this.resolveOptions(),
2133
2223
  waitForLive: this.opts.waitForLive ? this.buildWaitConfig(e) : void 0
2134
2224
  });
2135
2225
  if (await this.onResolved(t, e), e === this.loadGen && this._state === "error" && this._lastError) throw this._lastError;
2136
- } catch (n) {
2226
+ } catch (t) {
2137
2227
  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;
2228
+ throw t !== this._lastError && this.fail(t instanceof n ? t : new n("INTERNAL", 0, String(t))), t;
2139
2229
  }
2140
2230
  }
2141
2231
  on(e, t) {
@@ -2168,10 +2258,7 @@ var tt = class {
2168
2258
  case "paused":
2169
2259
  this.emit("paused");
2170
2260
  break;
2171
- case "ended":
2172
- this.emit("ended");
2173
- break;
2174
- default: break;
2261
+ case "ended": this.emit("ended");
2175
2262
  }
2176
2263
  }
2177
2264
  emit(e, t) {
@@ -2189,7 +2276,7 @@ function $(e) {
2189
2276
  }
2190
2277
  //#endregion
2191
2278
  //#region src/embed/host.ts
2192
- function nt(e) {
2279
+ function ut(e) {
2193
2280
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2194
2281
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2195
2282
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2248,8 +2335,8 @@ function nt(e) {
2248
2335
  }
2249
2336
  //#endregion
2250
2337
  //#region src/embed/page.ts
2251
- var rt = /^[A-Za-z0-9:_.-]{1,128}$/;
2252
- function it() {
2338
+ var dt = /^[A-Za-z0-9:_.-]{1,128}$/;
2339
+ function ft() {
2253
2340
  let e = window.HAVIK_EMBED_CONFIG;
2254
2341
  if (e) return {
2255
2342
  baseUrl: e.baseUrl ?? "",
@@ -2271,11 +2358,11 @@ function it() {
2271
2358
  waitForLive: !0
2272
2359
  };
2273
2360
  }
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);
2361
+ async function pt() {
2362
+ let e = ft();
2363
+ ht();
2364
+ let t = document.createElement("video");
2365
+ 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
2366
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
2280
2367
  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
2368
  let i = (e) => {
@@ -2328,13 +2415,13 @@ async function at() {
2328
2415
  code: e.code,
2329
2416
  message: e.message
2330
2417
  })), e.on("autoplayblocked", () => {
2331
- i({ type: "oddin:autoplayblocked" }), ot(n, e);
2418
+ i({ type: "oddin:autoplayblocked" }), mt(t, e);
2332
2419
  });
2333
2420
  }, s = async (r) => {
2334
2421
  a?.destroy(), a = null;
2335
2422
  try {
2336
- let t = await Q({
2337
- video: n,
2423
+ let n = await Q({
2424
+ video: t,
2338
2425
  baseUrl: e.baseUrl,
2339
2426
  matchUrn: r,
2340
2427
  credential: { apiKey: e.apiKey },
@@ -2342,14 +2429,14 @@ async function at() {
2342
2429
  muted: e.muted,
2343
2430
  waitForLive: e.waitForLive
2344
2431
  });
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);
2432
+ a = n;
2433
+ let i = t.parentElement;
2434
+ i?.classList.contains("havik-player") && (i.style.cssText = "position:absolute;inset:0;width:100%;height:100%;aspect-ratio:auto"), o(n);
2348
2435
  } catch (e) {
2349
- let n = e instanceof t ? e.code : "INTERNAL", r = e instanceof Error ? e.message : String(e);
2436
+ let t = e instanceof n ? e.code : "INTERNAL", r = e instanceof Error ? e.message : String(e);
2350
2437
  i({
2351
2438
  type: "oddin:error",
2352
- code: n,
2439
+ code: t,
2353
2440
  message: r
2354
2441
  });
2355
2442
  }
@@ -2394,11 +2481,9 @@ async function at() {
2394
2481
  a?.retry();
2395
2482
  break;
2396
2483
  case "oddin:load":
2397
- typeof e.matchUrn == "string" && rt.test(e.matchUrn) && s(e.matchUrn);
2398
- break;
2399
- case "oddin:destroy":
2400
- c();
2484
+ typeof e.matchUrn == "string" && dt.test(e.matchUrn) && s(e.matchUrn);
2401
2485
  break;
2486
+ case "oddin:destroy": c();
2402
2487
  }
2403
2488
  }, u = (e) => {
2404
2489
  !r || e.origin !== r || $(e.data) && l(e.data);
@@ -2408,18 +2493,18 @@ async function at() {
2408
2493
  protocol: 1
2409
2494
  });
2410
2495
  }
2411
- function ot(e, t) {
2496
+ function mt(e, t) {
2412
2497
  if (document.getElementById("havik-tap")) return;
2413
2498
  let n = document.createElement("button");
2414
2499
  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
2500
  n.remove(), t.play();
2416
2501
  }), document.body.appendChild(n);
2417
2502
  }
2418
- function st() {
2503
+ function ht() {
2419
2504
  let e = document.createElement("style");
2420
2505
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2421
2506
  }
2422
2507
  //#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 };
2508
+ export { G as ODDIN_THEME, n as PlaybackError, q as applyTheme, pt as bootEmbed, ge as clearDeviceId, Q as createPlayer, D as deriveEventsBaseUrl, xe as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, ue as fetchCatalog, fe as fetchTournament, me as flattenMatches, A as getDeviceId, Ce as isLowLatencyManifest, $ as isOddinMessage, ve as licenseRequestHeaders, ut as mountEmbed, _ as pollToLive, y as resolveStream, E as subscribeLiveState, C as videoFullscreenActive, he as watchStatus };
2424
2509
 
2425
2510
  //# sourceMappingURL=index.js.map