@oddin-gg/havik-player 1.16.0 → 1.17.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
@@ -85,13 +85,13 @@ function d(e) {
85
85
  }
86
86
  //#endregion
87
87
  //#region src/core/waitForLive.ts
88
- var f = 1e3, p = 3e4, m = 6e5, h = 3e5, g = 1e3, ee = {
88
+ var f = 1e3, p = 3e4, m = 6e5, h = 3e5, g = 1e3, _ = {
89
89
  TOO_EARLY: "tooEarly",
90
90
  UNAVAILABLE: "unavailable",
91
91
  RATE_LIMITED: "rateLimited",
92
92
  NETWORK: "network"
93
93
  };
94
- function te(e) {
94
+ function ee(e) {
95
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,
@@ -99,27 +99,27 @@ function te(e) {
99
99
  ceilMs: r,
100
100
  imminentMs: i,
101
101
  sanityFloorMs: a,
102
- kickoffMs: ne(t.kickoffAt),
102
+ kickoffMs: te(t.kickoffAt),
103
103
  jitter: t.jitter ?? !0,
104
104
  onState: t.onState
105
105
  };
106
106
  }
107
- function ne(e) {
107
+ function te(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 re(e, t, n) {
113
+ function ne(e, t, n) {
114
114
  return Math.min(n, Math.max(t, e));
115
115
  }
116
- function ie(e) {
116
+ function re(e) {
117
117
  return Math.round(e * (1 + .15 * Math.random()));
118
118
  }
119
- function ae(e, t, n, r) {
119
+ function ie(e, t, n, r) {
120
120
  let i = r ?? n.kickoffMs, a = i == null ? void 0 : Math.max(0, i - t), o = Math.max(n.floorMs, e ?? 0);
121
121
  if (a == null || a <= n.imminentMs) return {
122
- delay: re(e ?? n.floorMs, o, n.ceilMs),
122
+ delay: ne(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,7 +128,7 @@ function ae(e, t, n, r) {
128
128
  ttkMs: a
129
129
  };
130
130
  }
131
- function oe(e, t) {
131
+ function ae(e, t) {
132
132
  return new Promise((r, i) => {
133
133
  if (t?.aborted) {
134
134
  i(new n("ABORTED", 0, "wait-for-live aborted"));
@@ -142,8 +142,8 @@ function oe(e, t) {
142
142
  t?.addEventListener("abort", a, { once: !0 });
143
143
  });
144
144
  }
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;
145
+ async function v(e, t, r) {
146
+ let i = ee(t), a = Date.now(), s = i.timeoutMs == null ? Infinity : a + i.timeoutMs, c = 0, l;
147
147
  for (;;) {
148
148
  if (r?.aborted) throw new n("ABORTED", 0, "wait-for-live aborted");
149
149
  try {
@@ -151,38 +151,38 @@ async function _(e, t, r) {
151
151
  } catch (e) {
152
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 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`, {
154
+ let t = Date.now(), u = ie(e.retryAfterMs, t, i, l), d = u.ttkMs, f = u.delay;
155
+ if (i.jitter && (f = re(f)), t + f > s) throw new n("TIMEOUT", e.httpStatus, `timed out after ${i.timeoutMs}ms waiting for the stream to go live`, {
156
156
  retryAfterMs: e.retryAfterMs,
157
157
  cause: e
158
158
  });
159
159
  i.onState?.({
160
- phase: ee[e.code] ?? "unavailable",
160
+ phase: _[e.code] ?? "unavailable",
161
161
  retryInMs: f,
162
162
  attempt: c,
163
163
  elapsedMs: t - a,
164
164
  ttkMs: d
165
- }), await oe(f, r);
165
+ }), await ae(f, r);
166
166
  }
167
167
  }
168
168
  }
169
169
  //#endregion
170
170
  //#region src/core/resolve.ts
171
- async function v(e) {
171
+ async function y(e) {
172
172
  if (!e.matchUrn || typeof e.matchUrn != "string") throw new n("INVALID_URN", 0, "resolveStream: a non-empty matchUrn is required");
173
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 (!t.ok) throw await se(t);
179
- return le(await t.json(), e.matchUrn);
178
+ if (!t.ok) throw await oe(t);
179
+ return ce(await t.json(), e.matchUrn);
180
180
  }
181
- function y(e) {
182
- let t = () => v(e);
183
- return e.waitForLive ? _(t, e.waitForLive, e.signal) : t();
181
+ function b(e) {
182
+ let t = () => y(e);
183
+ return e.waitForLive ? v(t, e.waitForLive, e.signal) : t();
184
184
  }
185
- async function se(e) {
185
+ async function oe(e) {
186
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();
@@ -198,7 +198,7 @@ async function se(e) {
198
198
  liveStartsAtMs: s
199
199
  });
200
200
  }
201
- var ce = /* @__PURE__ */ new Set([
201
+ var se = /* @__PURE__ */ new Set([
202
202
  "matchUrn",
203
203
  "protocol",
204
204
  "drmEnabled",
@@ -209,7 +209,7 @@ var ce = /* @__PURE__ */ new Set([
209
209
  "expiresAt",
210
210
  "analytics"
211
211
  ]);
212
- function b(e, t) {
212
+ function x(e, t) {
213
213
  let r;
214
214
  try {
215
215
  r = new URL(t);
@@ -219,57 +219,57 @@ function b(e, t) {
219
219
  if (r.protocol !== "https:" && r.protocol !== "http:") throw new n("INTERNAL", 0, `playback ${e} has a disallowed scheme: ${r.protocol}`);
220
220
  return t;
221
221
  }
222
- function le(e, t) {
222
+ function ce(e, t) {
223
223
  if (!e.manifestUrl) throw new n("INTERNAL", 0, "playback response is missing manifestUrl");
224
224
  let r = e.drmEnabled ?? !!e.drm, i = {
225
225
  matchUrn: e.matchUrn ?? t,
226
226
  protocol: "hls",
227
227
  drmEnabled: r,
228
- manifestUrl: b("manifestUrl", e.manifestUrl),
228
+ manifestUrl: x("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: 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)
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)
236
236
  }), (t.widevine || t.fairplay) && (i.drm = t);
237
237
  }
238
238
  let a = {};
239
- for (let [t, n] of Object.entries(e)) ce.has(t) || (a[t] = n);
239
+ for (let [t, n] of Object.entries(e)) se.has(t) || (a[t] = n);
240
240
  return Object.keys(a).length > 0 && (i.extensions = a), i;
241
241
  }
242
242
  //#endregion
243
243
  //#region src/core/fullscreen.ts
244
- function x(e) {
244
+ function S(e) {
245
245
  return e.ownerDocument ?? document;
246
246
  }
247
- function S(e) {
247
+ function C(e) {
248
248
  return e.fullscreenElement ?? e.webkitFullscreenElement ?? null;
249
249
  }
250
- function C(e, t) {
251
- let n = S(x(e));
250
+ function w(e, t) {
251
+ let n = C(S(e));
252
252
  return n != null && (n === e || t != null && n === t) || e.webkitDisplayingFullscreen === !0;
253
253
  }
254
- async function w(e, t) {
254
+ async function T(e, t) {
255
255
  let n = t ?? e;
256
256
  if (typeof n.requestFullscreen == "function") return await n.requestFullscreen(), !0;
257
257
  if (typeof n.webkitRequestFullscreen == "function") return n.webkitRequestFullscreen(), !0;
258
258
  let r = e;
259
259
  return r.webkitSupportsFullscreen && typeof r.webkitEnterFullscreen == "function" ? (r.webkitEnterFullscreen(), !0) : !1;
260
260
  }
261
- async function T(e, t) {
261
+ async function E(e, t) {
262
262
  let n = e;
263
263
  if (n.webkitDisplayingFullscreen) {
264
264
  n.webkitExitFullscreen?.();
265
265
  return;
266
266
  }
267
- let r = x(e), i = S(r);
267
+ let r = S(e), i = C(r);
268
268
  i != null && (i === e || t != null && i === t) && (typeof r.exitFullscreen == "function" ? await r.exitFullscreen() : r.webkitExitFullscreen?.());
269
269
  }
270
270
  //#endregion
271
271
  //#region src/core/catalog.ts
272
- async function ue(e) {
272
+ async function le(e) {
273
273
  let t = new URLSearchParams();
274
274
  e.status && t.set("status", Array.isArray(e.status) ? e.status.join(",") : e.status), e.sport && t.set("sport", e.sport);
275
275
  let i = t.toString(), a = c(e.baseUrl, "/v1/catalog" + (i ? `?${i}` : "")), o = new Headers();
@@ -287,12 +287,12 @@ async function ue(e) {
287
287
  };
288
288
  if (!s.ok) throw new n(r(s.status), s.status, `catalog request failed (HTTP ${s.status})`);
289
289
  return {
290
- tournaments: de(await s.json()),
290
+ tournaments: ue(await s.json()),
291
291
  etag: s.headers.get("etag") ?? void 0,
292
292
  notModified: !1
293
293
  };
294
294
  }
295
- function de(e) {
295
+ function ue(e) {
296
296
  return (e.tournaments ?? []).map((e) => {
297
297
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
298
298
  return {
@@ -312,7 +312,7 @@ function de(e) {
312
312
  };
313
313
  });
314
314
  }
315
- async function fe(e) {
315
+ async function de(e) {
316
316
  let t = c(e.baseUrl, `/v1/tournaments/${encodeURIComponent(e.urn)}`), i = new Headers();
317
317
  e.etag && i.set("If-None-Match", e.etag);
318
318
  let a = await u(t, {
@@ -332,13 +332,13 @@ async function fe(e) {
332
332
  };
333
333
  if (!a.ok) throw new n(r(a.status), a.status, `tournament request failed (HTTP ${a.status})`);
334
334
  return {
335
- tournament: pe(await a.json()),
335
+ tournament: fe(await a.json()),
336
336
  etag: a.headers.get("etag") ?? void 0,
337
337
  notModified: !1,
338
338
  notFound: !1
339
339
  };
340
340
  }
341
- function pe(e) {
341
+ function fe(e) {
342
342
  let t = e.urn ?? "", n = e.name ?? "", r = e.sport ?? "";
343
343
  return {
344
344
  urn: t,
@@ -356,12 +356,12 @@ function pe(e) {
356
356
  }))
357
357
  };
358
358
  }
359
- function me(e) {
359
+ function pe(e) {
360
360
  return e.tournaments.flatMap((e) => e.matches);
361
361
  }
362
362
  //#endregion
363
363
  //#region src/core/poll.ts
364
- function he(e) {
364
+ function me(e) {
365
365
  let t = Math.max(2e3, e.intervalMs ?? 1e4), i = new AbortController(), a, o, s, l = !1, d = () => {
366
366
  l || (a = setTimeout(f, t));
367
367
  }, f = async () => {
@@ -397,7 +397,7 @@ function he(e) {
397
397
  }
398
398
  //#endregion
399
399
  //#region src/core/events.ts
400
- function E(e) {
400
+ function D(e) {
401
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) => {
402
402
  if (r.signal.aborted) return t();
403
403
  let n = setTimeout(t, e);
@@ -464,7 +464,7 @@ function E(e) {
464
464
  i || (i = !0, r.abort());
465
465
  } };
466
466
  }
467
- function D(e) {
467
+ function O(e) {
468
468
  try {
469
469
  let t = new URL(e);
470
470
  return t.hostname = `events.${t.hostname}`, t.pathname = "", t.search = "", t.origin;
@@ -474,27 +474,27 @@ function D(e) {
474
474
  }
475
475
  //#endregion
476
476
  //#region src/core/drm/deviceId.ts
477
- var O = "havik.player.deviceId", k;
478
- function A() {
479
- if (k) return k;
477
+ var k = "havik.player.deviceId", A;
478
+ function j() {
479
+ if (A) return A;
480
480
  try {
481
- let e = globalThis.localStorage?.getItem(O);
482
- if (e) return k = e, e;
481
+ let e = globalThis.localStorage?.getItem(k);
482
+ if (e) return A = e, e;
483
483
  } catch {}
484
- let e = _e();
485
- k = e;
484
+ let e = ge();
485
+ A = e;
486
486
  try {
487
- globalThis.localStorage?.setItem(O, e);
487
+ globalThis.localStorage?.setItem(k, e);
488
488
  } catch {}
489
489
  return e;
490
490
  }
491
- function ge() {
492
- k = void 0;
491
+ function he() {
492
+ A = void 0;
493
493
  try {
494
- globalThis.localStorage?.removeItem(O);
494
+ globalThis.localStorage?.removeItem(k);
495
495
  } catch {}
496
496
  }
497
- function _e() {
497
+ function ge() {
498
498
  let e = globalThis.crypto;
499
499
  if (e && typeof e.randomUUID == "function") return e.randomUUID();
500
500
  let t = /* @__PURE__ */ new Uint8Array(16);
@@ -506,22 +506,22 @@ function _e() {
506
506
  }
507
507
  //#endregion
508
508
  //#region src/core/drm/headers.ts
509
- function ve(e, t, n = {}) {
509
+ function _e(e, t, n = {}) {
510
510
  let r = {
511
511
  "x-api-key": t.apiKey,
512
512
  "X-Match-Urn": e.matchUrn,
513
- "X-Device-Id": n.deviceId ?? A(),
513
+ "X-Device-Id": n.deviceId ?? j(),
514
514
  "Content-Type": "application/octet-stream"
515
515
  };
516
516
  return n.userId && (r["X-User-Id"] = n.userId), r;
517
517
  }
518
518
  //#endregion
519
519
  //#region src/core/drm/support.ts
520
- var ye = 5e3, j = "video/mp4; codecs=\"avc1.42E01E\"", M = "audio/mp4; codecs=\"mp4a.40.2\"", be = {
520
+ var ve = 5e3, M = "video/mp4; codecs=\"avc1.42E01E\"", N = "audio/mp4; codecs=\"mp4a.40.2\"", ye = {
521
521
  widevine: ["com.widevine.alpha"],
522
522
  fairplay: ["com.apple.fps"]
523
523
  };
524
- function N(e) {
524
+ function be(e) {
525
525
  return e === "fairplay" ? [{
526
526
  initDataTypes: [
527
527
  "sinf",
@@ -529,24 +529,24 @@ function N(e) {
529
529
  "cenc"
530
530
  ],
531
531
  videoCapabilities: [{
532
- contentType: j,
532
+ contentType: M,
533
533
  robustness: "",
534
534
  encryptionScheme: "cbcs"
535
535
  }],
536
536
  audioCapabilities: [{
537
- contentType: M,
537
+ contentType: N,
538
538
  robustness: "",
539
539
  encryptionScheme: "cbcs"
540
540
  }]
541
541
  }] : [{
542
542
  initDataTypes: ["cenc"],
543
543
  videoCapabilities: [{
544
- contentType: j,
544
+ contentType: M,
545
545
  robustness: "SW_SECURE_DECODE",
546
546
  encryptionScheme: "cbcs"
547
547
  }],
548
548
  audioCapabilities: [{
549
- contentType: M,
549
+ contentType: N,
550
550
  robustness: "SW_SECURE_CRYPTO",
551
551
  encryptionScheme: "cbcs"
552
552
  }]
@@ -563,7 +563,7 @@ function P(e, t) {
563
563
  });
564
564
  }
565
565
  async function xe(e = {}) {
566
- let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? ye, r = {};
566
+ let t = e.systems ?? ["widevine", "fairplay"], n = e.timeoutMs ?? ve, r = {};
567
567
  if (typeof window < "u" && window.isSecureContext === !1) return {
568
568
  verdict: "insecure-context",
569
569
  detail: r
@@ -575,7 +575,7 @@ async function xe(e = {}) {
575
575
  };
576
576
  let a = !1, o = !1, s = Date.now() + n, c = () => Math.max(1, s - Date.now());
577
577
  for (let e of t) {
578
- let t = be[e];
578
+ let t = ye[e];
579
579
  if (!t) {
580
580
  r[e] = "unknown system";
581
581
  continue;
@@ -586,7 +586,7 @@ async function xe(e = {}) {
586
586
  break;
587
587
  }
588
588
  try {
589
- return await P((await P(i(n, N(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
589
+ return await P((await P(i(n, be(e)), c())).createMediaKeys(), c()), r[e] = `ok (${n})`, {
590
590
  verdict: "supported",
591
591
  system: e,
592
592
  detail: r
@@ -819,21 +819,21 @@ function Ee(e, t) {
819
819
  function De() {
820
820
  return e.isSupported();
821
821
  }
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) {
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, Ve = .5;
823
+ function He(e) {
824
824
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
825
825
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
826
826
  return;
827
827
  }
828
828
  e.liveSyncDuration ??= ze;
829
829
  let t = Math.max(12, e.liveSyncDuration + Be);
830
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), He(e, e.liveSyncDuration);
830
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Ue(e, e.liveSyncDuration);
831
831
  }
832
- function He(e, t) {
832
+ function Ue(e, t) {
833
833
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
834
834
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
835
835
  }
836
- var H = class {
836
+ var V = class {
837
837
  name = "hls";
838
838
  hls = null;
839
839
  deps;
@@ -910,7 +910,7 @@ var H = class {
910
910
  let t = e.liveSyncPosition;
911
911
  if (typeof t != "number" || !Number.isFinite(t)) return;
912
912
  let { video: n } = this.deps;
913
- n.currentTime <= t + V || (n.currentTime = t);
913
+ n.currentTime <= t + Ve || (n.currentTime = t);
914
914
  }
915
915
  startHls() {
916
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 = {
@@ -946,7 +946,7 @@ var H = class {
946
946
  } } : {},
947
947
  ...c
948
948
  };
949
- Ve(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
949
+ He(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
950
950
  let f = new e(d);
951
951
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
952
952
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
@@ -1241,7 +1241,7 @@ var H = class {
1241
1241
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1242
1242
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1243
1243
  }
1244
- }, Ue = 15e3, We = 100, U = 1, W = {
1244
+ }, We = 15e3, Ge = 100, H = 1, U = {
1245
1245
  idle: "PLAYER_STATE_IDLE",
1246
1246
  waiting: "PLAYER_STATE_WAITING",
1247
1247
  loading: "PLAYER_STATE_LOADING",
@@ -1251,7 +1251,7 @@ var H = class {
1251
1251
  ended: "PLAYER_STATE_ENDED",
1252
1252
  error: "PLAYER_STATE_ERROR"
1253
1253
  };
1254
- function Ge(e) {
1254
+ function Ke(e) {
1255
1255
  try {
1256
1256
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1257
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);
@@ -1259,7 +1259,7 @@ function Ge(e) {
1259
1259
  return;
1260
1260
  }
1261
1261
  }
1262
- var Ke = class {
1262
+ var qe = class {
1263
1263
  opts;
1264
1264
  url;
1265
1265
  now;
@@ -1294,7 +1294,7 @@ var Ke = class {
1294
1294
  constructor(e) {
1295
1295
  this.opts = {
1296
1296
  ...e,
1297
- heartbeatMs: e.heartbeatMs ?? Ue
1297
+ heartbeatMs: e.heartbeatMs ?? We
1298
1298
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
1299
1299
  }
1300
1300
  start() {
@@ -1306,18 +1306,18 @@ var Ke = class {
1306
1306
  noteStateChange(e, t, n, r) {
1307
1307
  if (this.endedFlag || e === t) return;
1308
1308
  let i = this.now();
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);
1309
+ 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);
1310
1310
  let a = {
1311
- fromState: W[e] ?? "PLAYER_STATE_UNSPECIFIED",
1312
- toState: W[t] ?? "PLAYER_STATE_UNSPECIFIED"
1311
+ fromState: U[e] ?? "PLAYER_STATE_UNSPECIFIED",
1312
+ toState: U[t] ?? "PLAYER_STATE_UNSPECIFIED"
1313
1313
  };
1314
1314
  n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1315
1315
  }
1316
1316
  noteReady() {
1317
- this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(U, this.now() - this.bornMs));
1317
+ this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(H, this.now() - this.bornMs));
1318
1318
  }
1319
1319
  noteFirstBytes() {
1320
- this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(U, this.now() - this.bornMs));
1320
+ this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(H, this.now() - this.bornMs));
1321
1321
  }
1322
1322
  noteSeek() {
1323
1323
  this.endedFlag || (this.seekCount += 1);
@@ -1352,7 +1352,7 @@ var Ke = class {
1352
1352
  let c = e.renditionSwitches ?? 0;
1353
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);
1354
1354
  let l = this.bufferedMsFrom(e);
1355
- l > 0 && (i.bufferedMs = l), i.playerState = W[e.state] ?? "PLAYER_STATE_UNSPECIFIED", this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1355
+ l > 0 && (i.bufferedMs = l), i.playerState = U[e.state] ?? "PLAYER_STATE_UNSPECIFIED", this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1356
1356
  }
1357
1357
  bufferedMsFrom(e) {
1358
1358
  let t = e;
@@ -1386,7 +1386,7 @@ var Ke = class {
1386
1386
  };
1387
1387
  }
1388
1388
  enqueue(e) {
1389
- for (this.pending.push(this.wrap(e)); this.pending.length > We;) {
1389
+ for (this.pending.push(this.wrap(e)); this.pending.length > Ge;) {
1390
1390
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1391
1391
  this.pending.splice(e >= 0 ? e : 0, 1);
1392
1392
  }
@@ -1408,7 +1408,7 @@ var Ke = class {
1408
1408
  keepalive: !0
1409
1409
  }).catch(() => {});
1410
1410
  }
1411
- }, qe = "1.16.0", G = {
1411
+ }, Je = "1.17.0", W = {
1412
1412
  accent: "#E1B600",
1413
1413
  accentText: "#1B1C23",
1414
1414
  background: "#0e0f13",
@@ -1419,7 +1419,7 @@ var Ke = class {
1419
1419
  border: "rgba(255, 255, 255, 0.1)",
1420
1420
  radius: "8px",
1421
1421
  fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
1422
- }, K = {
1422
+ }, G = {
1423
1423
  accent: "--havik-accent",
1424
1424
  accentText: "--havik-accent-text",
1425
1425
  background: "--havik-bg",
@@ -1431,25 +1431,25 @@ var Ke = class {
1431
1431
  radius: "--havik-radius",
1432
1432
  fontFamily: "--havik-font"
1433
1433
  };
1434
- function q(e, t) {
1434
+ function K(e, t) {
1435
1435
  let n = {
1436
- ...G,
1436
+ ...W,
1437
1437
  ...t
1438
1438
  };
1439
- for (let t of Object.keys(K)) e.style.setProperty(K[t], n[t]);
1439
+ for (let t of Object.keys(G)) e.style.setProperty(G[t], n[t]);
1440
1440
  n.logoUrl && e.style.setProperty("--havik-logo", `url("${n.logoUrl}")`);
1441
1441
  }
1442
1442
  //#endregion
1443
1443
  //#region src/ui/styles.ts
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;
1444
+ var q = "havik-player-styles", Ye = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n.havik-spinner{width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* 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 Xe(e = document) {
1446
+ if (e.getElementById(q)) return;
1447
1447
  let t = e.createElement("style");
1448
- t.id = J, t.textContent = Je, e.head.appendChild(t);
1448
+ t.id = q, t.textContent = Ye, e.head.appendChild(t);
1449
1449
  }
1450
1450
  //#endregion
1451
1451
  //#region src/ui/icons.ts
1452
- var Xe = {
1452
+ var Ze = {
1453
1453
  play: "<path d=\"M8 5v14l11-7z\"/>",
1454
1454
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1455
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\"/>",
@@ -1462,21 +1462,21 @@ var Xe = {
1462
1462
  liveDot: "<circle cx=\"12\" cy=\"12\" r=\"6\"/>",
1463
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\"/>"
1464
1464
  };
1465
- function Y(e) {
1466
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Xe[e]}</svg>`;
1465
+ function J(e) {
1466
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${Ze[e]}</svg>`;
1467
1467
  }
1468
1468
  //#endregion
1469
1469
  //#region src/ui/controls.ts
1470
- var Ze = 3e3, Qe = /* @__PURE__ */ new Set([
1470
+ var Qe = 3e3, $e = /* @__PURE__ */ new Set([
1471
1471
  "GONE",
1472
1472
  "NOT_FOUND",
1473
1473
  "INVALID_URN",
1474
1474
  "UNAUTHORIZED"
1475
1475
  ]);
1476
- function $e(e) {
1477
- return new et(e);
1476
+ function et(e) {
1477
+ return new tt(e);
1478
1478
  }
1479
- var et = class {
1479
+ var tt = class {
1480
1480
  root;
1481
1481
  video;
1482
1482
  player;
@@ -1516,7 +1516,7 @@ var et = class {
1516
1516
  scrubbing = !1;
1517
1517
  offFns = [];
1518
1518
  constructor(e) {
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");
1519
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Qe, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.errorMessage = e.errorMessage, Xe(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");
1520
1520
  let t = (e, t, n) => {
1521
1521
  let r = this.doc.createElement(e);
1522
1522
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1527,12 +1527,12 @@ var et = class {
1527
1527
  "aria-label": r,
1528
1528
  title: r
1529
1529
  });
1530
- return i.innerHTML = Y(n), i;
1530
+ return i.innerHTML = J(n), i;
1531
1531
  };
1532
1532
  this.overlay = t("div", "havik-overlay"), this.center = t("button", "havik-center", {
1533
1533
  type: "button",
1534
1534
  "aria-label": "Play"
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", {
1535
+ }), 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", {
1536
1536
  role: "slider",
1537
1537
  "aria-label": "Seek",
1538
1538
  "aria-valuemin": "0",
@@ -1550,7 +1550,7 @@ var et = class {
1550
1550
  "aria-label": "Volume"
1551
1551
  });
1552
1552
  let a = t("div", "havik-vol");
1553
- a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = Y("liveDot");
1553
+ a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = J("liveDot");
1554
1554
  let o = this.doc.createElement("span");
1555
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";
1556
1556
  let s = t("div", "havik-spacer");
@@ -1600,12 +1600,12 @@ var et = class {
1600
1600
  }
1601
1601
  if (e === "preplay") {
1602
1602
  let e = this.doc.createElement("button");
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);
1603
+ 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);
1604
1604
  } else {
1605
1605
  let n = this.doc.createElement("div");
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)) {
1606
+ if (n.className = "havik-status__msg", n.textContent = e === "ended" ? this.endedMessage : e === "waiting" ? this.hasPlayed ? "Reconnecting — please stand by" : "Starting soon…" : this.errorMessage ?? this.lastError?.message ?? "Playback unavailable", t.append(n), e === "error" && this.lastError && !$e.has(this.lastError.code)) {
1607
1607
  let e = this.doc.createElement("button");
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);
1608
+ 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);
1609
1609
  }
1610
1610
  }
1611
1611
  this.root.append(t), this.statusBox = t, this.show();
@@ -1644,7 +1644,7 @@ var et = class {
1644
1644
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
1645
1645
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
1646
1646
  }
1647
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${X(this.video.currentTime)} / ${X(this.video.duration)}`);
1647
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Y(this.video.currentTime)} / ${Y(this.video.duration)}`);
1648
1648
  }
1649
1649
  syncLive() {
1650
1650
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -1694,17 +1694,17 @@ var et = class {
1694
1694
  renderQualityMenu() {
1695
1695
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1696
1696
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1697
- for (let n of e) this.menu.append(this.menuItem(tt(n), t === n.index, () => this.player.setQuality(n.index)));
1697
+ for (let n of e) this.menu.append(this.menuItem(nt(n), t === n.index, () => this.player.setQuality(n.index)));
1698
1698
  }
1699
1699
  renderAudioMenu() {
1700
1700
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1701
1701
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1702
- for (let t of e) this.menu.append(this.menuItem(nt(t), t.default, () => this.player.setAudioTrack(t.id)));
1702
+ for (let t of e) this.menu.append(this.menuItem(rt(t), t.default, () => this.player.setAudioTrack(t.id)));
1703
1703
  }
1704
1704
  renderCcMenu() {
1705
1705
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1706
1706
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1707
- for (let t of e) this.menu.append(this.menuItem(rt(t), !1, () => this.player.setTextTrack(t.id)));
1707
+ for (let t of e) this.menu.append(this.menuItem(it(t), !1, () => this.player.setTextTrack(t.id)));
1708
1708
  }
1709
1709
  onTracksChanged() {
1710
1710
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1724,11 +1724,11 @@ var et = class {
1724
1724
  }
1725
1725
  async toggleFullscreen() {
1726
1726
  try {
1727
- C(this.video, this.root) ? await T(this.video, this.root) : await w(this.video, this.root);
1727
+ w(this.video, this.root) ? await E(this.video, this.root) : await T(this.video, this.root);
1728
1728
  } catch {}
1729
1729
  }
1730
1730
  syncFullscreen() {
1731
- let e = C(this.video, this.root);
1731
+ let e = w(this.video, this.root);
1732
1732
  this.setIcon(this.fsBtn, e ? "fullscreenExit" : "fullscreen"), this.fsBtn.setAttribute("aria-label", e ? "Exit fullscreen" : "Fullscreen");
1733
1733
  }
1734
1734
  poke() {
@@ -1787,7 +1787,7 @@ var et = class {
1787
1787
  }
1788
1788
  }
1789
1789
  setIcon(e, t) {
1790
- e.innerHTML = Y(t);
1790
+ e.innerHTML = J(t);
1791
1791
  }
1792
1792
  destroy() {
1793
1793
  this.hideTimer && clearTimeout(this.hideTimer);
@@ -1804,23 +1804,23 @@ var et = class {
1804
1804
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
1805
1805
  }
1806
1806
  };
1807
- function X(e) {
1807
+ function Y(e) {
1808
1808
  (!Number.isFinite(e) || e < 0) && (e = 0);
1809
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");
1810
1810
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1811
1811
  }
1812
- function tt(e) {
1812
+ function nt(e) {
1813
1813
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1814
1814
  }
1815
- function nt(e) {
1815
+ function rt(e) {
1816
1816
  return e.name || e.lang || `Track ${e.id}`;
1817
1817
  }
1818
- function rt(e) {
1818
+ function it(e) {
1819
1819
  return e.name || e.lang || `Track ${e.id}`;
1820
1820
  }
1821
1821
  //#endregion
1822
1822
  //#region src/managed/index.ts
1823
- var it = 6e3, Z = 45e3, at = 2e3, ot = 3, st = 2e3, ct = 48e4;
1823
+ var at = 6e3, X = 45e3, Z = 2e3, ot = 3, st = 2e3, ct = 48e4;
1824
1824
  async function Q(e) {
1825
1825
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1826
1826
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
@@ -1846,6 +1846,7 @@ var lt = class {
1846
1846
  loadGen = 0;
1847
1847
  liveStateSub = null;
1848
1848
  endedHandled = !1;
1849
+ rejoinTimer = null;
1849
1850
  armingSub = null;
1850
1851
  armSseTimer = null;
1851
1852
  armFallbackCtrl = null;
@@ -1865,7 +1866,7 @@ var lt = class {
1865
1866
  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");
1866
1867
  let e = ++this.loadGen, t = this.resolveOptions();
1867
1868
  try {
1868
- let n = await v(t);
1869
+ let n = await y(t);
1869
1870
  if (await this.onResolved(n, e), e === this.loadGen && this._state === "error" && this._lastError) {
1870
1871
  let e = this._lastError;
1871
1872
  throw this.destroy(), e;
@@ -1887,12 +1888,12 @@ var lt = class {
1887
1888
  apiHeaders: this.opts.apiHeaders
1888
1889
  };
1889
1890
  }
1890
- beginArming(e, t) {
1891
- let n = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? D(this.opts.baseUrl);
1892
- n ? this.armWithSse(e, n, t) : this.beginArmingByPolling(e);
1891
+ beginArming(e, t, n = !1) {
1892
+ let r = this.opts.liveStateEvents === !1 ? void 0 : this.opts.eventsBaseUrl ?? O(this.opts.baseUrl);
1893
+ r ? this.armWithSse(e, r, t, n) : this.beginArmingByPolling(e);
1893
1894
  }
1894
1895
  beginArmingByPolling(e) {
1895
- y({
1896
+ b({
1896
1897
  ...this.resolveOptions(),
1897
1898
  signal: this.abort.signal,
1898
1899
  waitForLive: this.buildWaitConfig(e)
@@ -1902,55 +1903,55 @@ var lt = class {
1902
1903
  this.destroyed || this.abort.signal.aborted || e !== this.loadGen || this.fail(t instanceof n ? t : new n("INTERNAL", 0, String(t)));
1903
1904
  });
1904
1905
  }
1905
- armWithSse(e, t, r) {
1906
- let i = !1, a = !1, s = new AbortController();
1907
- this.armFallbackCtrl = s, this.setState("waiting");
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);
1906
+ armWithSse(e, t, r, i = !1) {
1907
+ let a = !1, s = !1, c = new AbortController();
1908
+ this.armFallbackCtrl = c, this.setState("waiting");
1909
+ let l = this.opts.waitForLive, u = l && l !== !0 ? l.kickoffAt : void 0, d = typeof u == "number" ? u : typeof u == "string" ? Date.parse(u) : NaN, f = r ?? (Number.isFinite(d) ? d : void 0);
1909
1910
  setTimeout(() => {
1910
- i || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1911
- phase: "tooEarly",
1912
- retryInMs: Z,
1911
+ a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1912
+ phase: i ? "unavailable" : "tooEarly",
1913
+ retryInMs: X,
1913
1914
  attempt: 1,
1914
1915
  elapsedMs: 0,
1915
- ...d !== void 0 && d > Date.now() ? { ttkMs: d - Date.now() } : {}
1916
+ ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
1916
1917
  });
1917
1918
  }, 0);
1918
- let f = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, p = async (t) => {
1919
- if (!(i || f())) {
1920
- i = !0, this.closeArming(), s.abort();
1919
+ let p = () => this.destroyed || this.abort.signal.aborted || e !== this.loadGen, m = async (t) => {
1920
+ if (!(a || p())) {
1921
+ a = !0, this.closeArming(), c.abort();
1921
1922
  try {
1922
1923
  await this.onResolved(t, e);
1923
1924
  } catch (e) {
1924
- if (f()) return;
1925
+ if (p()) return;
1925
1926
  this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e)));
1926
1927
  }
1927
1928
  }
1928
- }, m = () => {
1929
- i || a || f() || (a = !0, y({
1929
+ }, h = () => {
1930
+ a || s || p() || (s = !0, b({
1930
1931
  ...this.resolveOptions(),
1931
- signal: s.signal,
1932
+ signal: c.signal,
1932
1933
  waitForLive: this.buildWaitConfig(e)
1933
- }).then((e) => p(e)).catch((e) => {
1934
- i || f() || s.signal.aborted || (i = !0, this.closeArming(), this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e))));
1934
+ }).then((e) => m(e)).catch((e) => {
1935
+ a || p() || c.signal.aborted || (a = !0, this.closeArming(), this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e))));
1935
1936
  }));
1936
- }, h = () => {
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))));
1937
+ }, g = () => {
1938
+ a || p() || y(this.resolveOptions()).then((e) => m(e)).catch((e) => {
1939
+ a || p() || e instanceof n && o(e.code) || (a = !0, this.closeArming(), c.abort(), this.fail(e instanceof n ? e : new n("INTERNAL", 0, String(e))));
1939
1940
  });
1940
- }, g = (e) => {
1941
+ }, _ = (e) => {
1941
1942
  this.armSseTimer && clearTimeout(this.armSseTimer), this.armSseTimer = setTimeout(() => {
1942
- !i && !f() && m();
1943
+ !a && !p() && h();
1943
1944
  }, e);
1944
1945
  };
1945
- g(it), this.armingSub = E({
1946
+ _(at), this.armingSub = D({
1946
1947
  eventsBaseUrl: t,
1947
1948
  matchUrn: this.opts.matchUrn,
1948
1949
  credential: this.opts.credential,
1949
- onAlive: () => g(Z),
1950
+ onAlive: () => _(X),
1950
1951
  onState: (e) => {
1951
- i || f() || (e.state === "live" || e.state === "gone") && setTimeout(h, Math.random() * at);
1952
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Z);
1952
1953
  },
1953
- onError: () => m()
1954
+ onError: () => h()
1954
1955
  });
1955
1956
  }
1956
1957
  closeArming() {
@@ -1988,14 +1989,14 @@ var lt = class {
1988
1989
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
1989
1990
  let r = e.analytics?.sid;
1990
1991
  if (!r) return;
1991
- let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Ge(this.opts.baseUrl);
1992
+ let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Ke(this.opts.baseUrl);
1992
1993
  if (!i) return;
1993
- let a = this.opts.video, o = new Ke({
1994
+ let a = this.opts.video, o = new qe({
1994
1995
  endpoint: i,
1995
1996
  sid: r,
1996
1997
  apiKey: n,
1997
1998
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
1998
- sdkVersion: qe,
1999
+ sdkVersion: Je,
1999
2000
  sample: () => {
2000
2001
  let e = this.engine?.getStats() ?? F, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
2001
2002
  return {
@@ -2017,7 +2018,7 @@ var lt = class {
2017
2018
  video: this.opts.video,
2018
2019
  descriptor: e,
2019
2020
  credential: t,
2020
- deviceId: A(),
2021
+ deviceId: j(),
2021
2022
  userId: this.opts.userId,
2022
2023
  lowLatency: this.opts.lowLatency === void 0 || this.opts.lowLatency === "auto" || this.opts.lowLatency,
2023
2024
  snapToLiveOnRefocus: this.opts.snapToLiveOnRefocus,
@@ -2026,9 +2027,9 @@ var lt = class {
2026
2027
  emit: (e) => this.onEngineEvent(e)
2027
2028
  }, a = this.opts.engine ?? "auto";
2028
2029
  if (a === "native") return new z(i);
2029
- if (a === "hls") return new H(i);
2030
+ if (a === "hls") return new V(i);
2030
2031
  if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && L(this.opts.video) && R()) return new z(i);
2031
- if (De()) return new H(i);
2032
+ if (De()) return new V(i);
2032
2033
  if (L(this.opts.video)) return new z(i);
2033
2034
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
2034
2035
  }
@@ -2057,30 +2058,48 @@ var lt = class {
2057
2058
  }
2058
2059
  startLiveState(e) {
2059
2060
  if (this.opts.liveStateEvents === !1) return;
2060
- let t = this.opts.eventsBaseUrl ?? D(this.opts.baseUrl);
2061
+ let t = this.opts.eventsBaseUrl ?? O(this.opts.baseUrl);
2061
2062
  if (!t) return;
2062
2063
  this.closeLiveState();
2063
2064
  let r = 0, i = !1;
2064
- this.liveStateSub = E({
2065
+ this.liveStateSub = D({
2065
2066
  eventsBaseUrl: t,
2066
2067
  matchUrn: this.opts.matchUrn,
2067
2068
  credential: this.opts.credential,
2068
2069
  onState: (t) => {
2069
- this.destroyed || e !== this.loadGen || (t.state === "ended" || t.state === "gone") && this.onStreamEnded();
2070
+ this.destroyed || e !== this.loadGen || (t.state === "gone" ? this.onStreamEnded(!0) : t.state === "ended" ? this.onStreamEnded() : t.state === "live" && this.endedHandled && this.scheduleRejoin(e));
2070
2071
  },
2071
2072
  onAlive: () => {
2072
2073
  r = 0, i = !1;
2073
2074
  },
2074
2075
  onError: () => {
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.`))));
2076
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (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.`))));
2076
2077
  }
2077
2078
  });
2078
2079
  }
2079
2080
  closeLiveState() {
2080
2081
  this.liveStateSub?.close(), this.liveStateSub = null;
2081
2082
  }
2082
- onStreamEnded() {
2083
- this.destroyed || this.endedHandled || (this.endedHandled = !0, this.closeLiveState(), this.stopRefresh(), this.engine?.stop(), this.setState("ended"));
2083
+ onStreamEnded(e = !1) {
2084
+ this.destroyed || ((e || !this.rejoinEnabled()) && (this.cancelRejoin(), this.closeLiveState()), !this.endedHandled && (this.endedHandled = !0, this.stopRefresh(), this.engine?.stop(), this.setState("ended")));
2085
+ }
2086
+ rejoinEnabled() {
2087
+ return this.opts.rejoinOnLive !== !1 && this.opts.liveStateEvents !== !1;
2088
+ }
2089
+ scheduleRejoin(e) {
2090
+ !this.rejoinEnabled() || this.rejoinTimer || (this.rejoinTimer = setTimeout(() => {
2091
+ this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
2092
+ }, Math.random() * Z));
2093
+ }
2094
+ cancelRejoin() {
2095
+ this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
2096
+ }
2097
+ rejoin() {
2098
+ let e = ++this.loadGen;
2099
+ this.teardownPlayback(), this.setState("waiting"), this.beginArming(e, void 0, !0);
2100
+ }
2101
+ teardownPlayback() {
2102
+ this.stopStats(), this.stopRefresh(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null;
2084
2103
  }
2085
2104
  mountControls() {
2086
2105
  let e = this.opts.controls ?? "custom", t = this.opts.video;
@@ -2094,7 +2113,7 @@ var lt = class {
2094
2113
  }
2095
2114
  t.controls = !1;
2096
2115
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2097
- r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = $e({
2116
+ r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = et({
2098
2117
  root: n,
2099
2118
  video: t,
2100
2119
  player: this,
@@ -2138,7 +2157,7 @@ var lt = class {
2138
2157
  async refreshLicense() {
2139
2158
  let e = this.engine;
2140
2159
  if (!(this.destroyed || !e)) try {
2141
- let t = await v(this.resolveOptions());
2160
+ let t = await y(this.resolveOptions());
2142
2161
  if (this.destroyed || this.engine !== e) return;
2143
2162
  let n = t.drm?.widevine?.licenseUrl, r = t.drm?.fairplay?.licenseUrl;
2144
2163
  (n || r) && (e.setLicenseUrls({
@@ -2205,10 +2224,10 @@ var lt = class {
2205
2224
  e.pictureInPictureElement === this.opts.video && typeof e.exitPictureInPicture == "function" && await e.exitPictureInPicture();
2206
2225
  }
2207
2226
  async enterFullscreen() {
2208
- return !this.destroyed && w(this.opts.video, this.container ?? void 0);
2227
+ return !this.destroyed && T(this.opts.video, this.container ?? void 0);
2209
2228
  }
2210
2229
  async exitFullscreen() {
2211
- await T(this.opts.video, this.container ?? void 0);
2230
+ await E(this.opts.video, this.container ?? void 0);
2212
2231
  }
2213
2232
  retry() {
2214
2233
  return this.reload();
@@ -2216,9 +2235,9 @@ var lt = class {
2216
2235
  async reload() {
2217
2236
  if (this.destroyed) return;
2218
2237
  let e = ++this.loadGen;
2219
- this.stopStats(), this.stopRefresh(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null;
2238
+ this.teardownPlayback();
2220
2239
  try {
2221
- let t = await y({
2240
+ let t = await b({
2222
2241
  ...this.resolveOptions(),
2223
2242
  waitForLive: this.opts.waitForLive ? this.buildWaitConfig(e) : void 0
2224
2243
  });
@@ -2233,7 +2252,7 @@ var lt = class {
2233
2252
  return n.add(t), () => n.delete(t);
2234
2253
  }
2235
2254
  destroy() {
2236
- this.destroyed || (this.destroyed = !0, this.loadGen += 1, this.abort.abort(), this.stopStats(), this.stopRefresh(), this.unmountControls(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.detachPip?.(), this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.engine?.destroy(), this.engine = null, this.setState("idle"), this.clearListeners());
2255
+ this.destroyed || (this.destroyed = !0, this.loadGen += 1, this.abort.abort(), this.stopStats(), this.stopRefresh(), this.unmountControls(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.detachPip?.(), this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.engine?.destroy(), this.engine = null, this.setState("idle"), this.clearListeners());
2237
2256
  }
2238
2257
  clearListeners() {
2239
2258
  for (let e of Object.keys(this.listeners)) this.listeners[e]?.clear();
@@ -2505,6 +2524,6 @@ function ht() {
2505
2524
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2506
2525
  }
2507
2526
  //#endregion
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 };
2527
+ export { W as ODDIN_THEME, n as PlaybackError, K as applyTheme, pt as bootEmbed, he as clearDeviceId, Q as createPlayer, O as deriveEventsBaseUrl, xe as detectDrmSupport, T as enterVideoFullscreen, E as exitVideoFullscreen, le as fetchCatalog, de as fetchTournament, pe as flattenMatches, j as getDeviceId, Ce as isLowLatencyManifest, $ as isOddinMessage, _e as licenseRequestHeaders, ut as mountEmbed, v as pollToLive, b as resolveStream, D as subscribeLiveState, w as videoFullscreenActive, me as watchStatus };
2509
2528
 
2510
2529
  //# sourceMappingURL=index.js.map