@oddin-gg/havik-player 2.6.3 → 2.7.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
@@ -918,17 +918,17 @@ function Fe(e, t) {
918
918
  function Ie() {
919
919
  return e.isSupported();
920
920
  }
921
- var Le = 3, Re = 5, ze = 3, Be = 3e4, Ve = 12e3, He = 3e3, H = 6e3, Ue = 4e3, We = 2, Ge = 5, Ke = 2, qe = 2, Je = 2e3, Ye = 3e4, Xe = 2, Ze = 9, Qe = .5, $e = 64;
922
- function et(e) {
921
+ var Le = 3, Re = 5, ze = 3, Be = 3e4, Ve = 12e3, He = 3e3, H = 6e3, Ue = 5e3, We = 4e3, Ge = 2, Ke = 5, qe = 2, Je = 2, Ye = 2e3, Xe = 3e4, Ze = 2, Qe = 9, $e = .5, et = 64;
922
+ function tt(e) {
923
923
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
924
924
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
925
925
  return;
926
926
  }
927
- e.liveSyncDuration ??= Xe;
928
- let t = Math.max(12, e.liveSyncDuration + Ze);
929
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), tt(e, e.liveSyncDuration);
927
+ e.liveSyncDuration ??= Ze;
928
+ let t = Math.max(12, e.liveSyncDuration + Qe);
929
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), nt(e, e.liveSyncDuration);
930
930
  }
931
- function tt(e, t) {
931
+ function nt(e, t) {
932
932
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
933
933
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
934
934
  }
@@ -960,6 +960,11 @@ var U = class {
960
960
  licenseRetryTimer = null;
961
961
  onVisibility = null;
962
962
  onSeeking = null;
963
+ onPause = null;
964
+ onPlay = null;
965
+ pauseSuspendTimer = null;
966
+ loadSuspended = !1;
967
+ stopped = !1;
963
968
  sawLive = !1;
964
969
  lastEndSN = -1;
965
970
  lastAdvanceMs = 0;
@@ -1002,10 +1007,34 @@ var U = class {
1002
1007
  if (typeof t != "number") return;
1003
1008
  let n = typeof e.targetLatency == "number" && e.targetLatency > 0 ? e.targetLatency : 3;
1004
1009
  t - r.currentTime > Math.max(n * 3, 8) && (r.currentTime = t);
1005
- }, 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());
1010
+ }, document.addEventListener("visibilitychange", this.onVisibility)), this.onSeeking = () => {
1011
+ this.clampOverSeek(), this.loadSuspended && (this.resumeLoading(), this.schedulePauseSuspend());
1012
+ }, r.addEventListener("seeking", this.onSeeking), this.onPause = () => this.schedulePauseSuspend(), this.onPlay = () => this.resumeLoading(), r.addEventListener("pause", this.onPause), r.addEventListener("play", this.onPlay), this.sizeCapManaged && typeof ResizeObserver < "u" && (this.capObserver = new ResizeObserver(() => this.onMediaResize()), this.capObserver.observe(r), this.watchDprChanges());
1013
+ }
1014
+ schedulePauseSuspend() {
1015
+ this.pauseSuspendTimer || this.loadSuspended || this.stopped || !this.loadStarted || (this.pauseSuspendTimer = setTimeout(() => {
1016
+ this.pauseSuspendTimer = null;
1017
+ let { video: e } = this.deps;
1018
+ if (e.paused && !e.ended && !this.stopped && this.hls) {
1019
+ try {
1020
+ this.hls.stopLoad();
1021
+ } catch {
1022
+ return;
1023
+ }
1024
+ this.loadSuspended = !0;
1025
+ }
1026
+ }, Ue));
1027
+ }
1028
+ resumeLoading() {
1029
+ if (this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null), this.loadSuspended && (this.loadSuspended = !1, !this.stopped && this.hls)) {
1030
+ this.lastAdvanceMs = Date.now(), this.lastFragLoadingMs = 0, this.starvedTicks = 0, this.kickPending = !1, this.kickTicks = 0;
1031
+ try {
1032
+ this.hls.startLoad(-1);
1033
+ } catch {}
1034
+ }
1006
1035
  }
1007
1036
  startLoad() {
1008
- this.loadStarted || (this.loadStarted = !0, this.hls?.startLoad(-1));
1037
+ this.loadStarted || (this.loadStarted = !0, this.deps.video.paused && this.schedulePauseSuspend(), this.hls?.startLoad(-1));
1009
1038
  }
1010
1039
  watchDprChanges() {
1011
1040
  if (typeof matchMedia != "function") return;
@@ -1026,7 +1055,7 @@ var U = class {
1026
1055
  let t = e.liveSyncPosition;
1027
1056
  if (typeof t != "number" || !Number.isFinite(t)) return;
1028
1057
  let { video: n } = this.deps;
1029
- n.currentTime <= t + Qe || (n.currentTime = t);
1058
+ n.currentTime <= t + $e || (n.currentTime = t);
1030
1059
  }
1031
1060
  startHls() {
1032
1061
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
@@ -1066,12 +1095,12 @@ var U = class {
1066
1095
  } } : {},
1067
1096
  ...c
1068
1097
  };
1069
- et(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
1098
+ tt(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
1070
1099
  let f = new e(d);
1071
1100
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
1072
1101
  this.mediaRecoveries = 0, this.networkRecoveries = 0;
1073
1102
  let e = f.levels.map((e) => e.height).filter((e) => !!e);
1074
- this.ladderHeights = e.length ? [...new Set(e)].sort((e, t) => e - t) : void 0, this.ladderTopBitrateKbps = f.levels.length ? Math.round(Math.max(...f.levels.map((e) => e.bitrate)) / 1e3) : 0, this.applySizeCap(), l({ type: "ready" });
1103
+ this.ladderHeights = e.length ? [...new Set(e)].sort((e, t) => e - t) : void 0, this.ladderTopBitrateKbps = f.levels.length ? Math.round(Math.max(...f.levels.map((e) => e.bitrate)) / 1e3) : 0, this.applySizeCap(), this.loadSuspended = !1, t.paused && this.schedulePauseSuspend(), l({ type: "ready" });
1075
1104
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
1076
1105
  t?.frag?.type === "main" && (this.kickPending = !1, this.kickTicks = 0, (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0 || this.starvationRebuilds > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
1077
1106
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.starvationRebuilds = 0, this.recoveryResetTimer = null;
@@ -1082,7 +1111,7 @@ var U = class {
1082
1111
  let n = t?.frag?.stats, r = n?.loaded;
1083
1112
  typeof r == "number" && (this.bytesLoaded += r);
1084
1113
  let i = n?.loading;
1085
- i && i.end > 0 && i.start > 0 && (this.requestCount += 1, this.requestTimesMs.push(Math.max(0, Math.round(i.end - i.start))), this.requestTimesMs.length > $e && this.requestTimesMs.shift());
1114
+ i && i.end > 0 && i.start > 0 && (this.requestCount += 1, this.requestTimesMs.push(Math.max(0, Math.round(i.end - i.start))), this.requestTimesMs.length > et && this.requestTimesMs.shift());
1086
1115
  let a = t?.frag?.url;
1087
1116
  if (a) {
1088
1117
  let e = L(a);
@@ -1162,7 +1191,7 @@ var U = class {
1162
1191
  return;
1163
1192
  }
1164
1193
  this.capTimer && clearTimeout(this.capTimer);
1165
- let t = e > this.sizeCapLevel ? Je : Ye;
1194
+ let t = e > this.sizeCapLevel ? Ye : Xe;
1166
1195
  this.capTimer = setTimeout(() => {
1167
1196
  this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1168
1197
  }, t);
@@ -1250,27 +1279,31 @@ var U = class {
1250
1279
  this.lastAdvanceMs = Date.now();
1251
1280
  return;
1252
1281
  }
1282
+ if (this.loadSuspended) {
1283
+ this.lastAdvanceMs = Date.now();
1284
+ return;
1285
+ }
1253
1286
  let e = Date.now() - this.lastAdvanceMs;
1254
1287
  !this.chaseSuspended && e > H && this.suspendChasing(), e > Ve && this.emitEnded("stalled");
1255
1288
  }
1256
1289
  checkStarvation() {
1257
1290
  let e = this.hls, { video: t } = this.deps;
1258
1291
  if (!e || this.endedEmitted || !this.sawLive || this.chaseSuspended) return;
1259
- if (typeof document < "u" && document.hidden) {
1292
+ if (typeof document < "u" && document.hidden || this.loadSuspended) {
1260
1293
  this.starvedTicks = 0;
1261
1294
  return;
1262
1295
  }
1263
- let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < H, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Ue;
1296
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < H, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > We;
1264
1297
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1265
1298
  this.starvedTicks = 0;
1266
1299
  return;
1267
1300
  }
1268
- if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < We)) {
1269
- if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Ke && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < qe)) {
1301
+ if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Ge)) {
1302
+ if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= qe && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < Je)) {
1270
1303
  this.starvationRebuilds += 1, console.warn("havik-player: restarting the load did not resume video (the wedge is in hls.js state that only a rebuild clears) — rebuilding the player"), this.rebuildInstance("starvation rebuild");
1271
1304
  return;
1272
1305
  }
1273
- if (!(this.starvationKicks >= Ge)) {
1306
+ if (!(this.starvationKicks >= Ke)) {
1274
1307
  this.starvationKicks += 1, this.kickPending = !0, this.kickTicks = 0, console.warn("havik-player: video starved while the live playlist advances (wedged hls.js fragment pipeline) — restarting the load");
1275
1308
  try {
1276
1309
  e.stopLoad(), e.startLoad(-1);
@@ -1296,7 +1329,7 @@ var U = class {
1296
1329
  }));
1297
1330
  }
1298
1331
  stop() {
1299
- this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.kickPending = !1, this.kickTicks = 0;
1332
+ this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.kickPending = !1, this.kickTicks = 0, this.stopped = !0, this.loadSuspended = !1, this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null);
1300
1333
  try {
1301
1334
  this.hls?.stopLoad();
1302
1335
  } catch {}
@@ -1305,7 +1338,7 @@ var U = class {
1305
1338
  } catch {}
1306
1339
  }
1307
1340
  destroy() {
1308
- 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) {
1341
+ 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.onPause &&= (this.deps.video.removeEventListener("pause", this.onPause), null), this.onPlay &&= (this.deps.video.removeEventListener("play", this.onPlay), null), this.pauseSuspendTimer &&= (clearTimeout(this.pauseSuspendTimer), null), this.networkRetryTimer &&= (clearTimeout(this.networkRetryTimer), null), this.licenseRetryTimer &&= (clearTimeout(this.licenseRetryTimer), null), this.hls) {
1309
1342
  try {
1310
1343
  this.hls.destroy();
1311
1344
  } catch (e) {
@@ -1393,11 +1426,11 @@ var U = class {
1393
1426
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1394
1427
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1395
1428
  }
1396
- }, nt = 15e3, rt = 100, W = 1, it = 1e3, G = 3e4, at = 1e3, ot = {
1429
+ }, rt = 15e3, it = 6e5, at = 3e5, ot = 100, W = 1, st = 1e3, G = 3e4, ct = 1e3, lt = {
1397
1430
  inline: "PRESENTATION_INLINE",
1398
1431
  fullscreen: "PRESENTATION_FULLSCREEN",
1399
1432
  pip: "PRESENTATION_PIP"
1400
- }, st = {
1433
+ }, ut = {
1401
1434
  widevine: "KEY_SYSTEM_WIDEVINE",
1402
1435
  fairplay: "KEY_SYSTEM_FAIRPLAY",
1403
1436
  none: "KEY_SYSTEM_NONE"
@@ -1405,7 +1438,7 @@ var U = class {
1405
1438
  function K(e, t) {
1406
1439
  return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
1407
1440
  }
1408
- var ct = /* @__PURE__ */ new Set([
1441
+ var dt = /* @__PURE__ */ new Set([
1409
1442
  "INVALID_URN",
1410
1443
  "NOT_FOUND",
1411
1444
  "GONE",
@@ -1425,7 +1458,7 @@ var ct = /* @__PURE__ */ new Set([
1425
1458
  ended: "PLAYER_STATE_ENDED",
1426
1459
  error: "PLAYER_STATE_ERROR"
1427
1460
  };
1428
- function lt(e) {
1461
+ function ft(e) {
1429
1462
  try {
1430
1463
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1431
1464
  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);
@@ -1433,7 +1466,7 @@ function lt(e) {
1433
1466
  return;
1434
1467
  }
1435
1468
  }
1436
- var ut = class {
1469
+ var pt = class {
1437
1470
  opts;
1438
1471
  url;
1439
1472
  now;
@@ -1493,19 +1526,26 @@ var ut = class {
1493
1526
  lastRequestErrors = 0;
1494
1527
  pendingStart = null;
1495
1528
  firstFlushTimer = null;
1529
+ idleTimer = null;
1530
+ lastTickMs = 0;
1496
1531
  bornMs;
1497
1532
  startupAnchorMs;
1498
1533
  startupTimeMs = 0;
1499
1534
  manifestLoadMs = 0;
1500
1535
  firstFragLoadMs = 0;
1501
1536
  onPagehide = () => this.end("END_REASON_PAGEHIDE");
1537
+ onFreeze = () => {
1538
+ this.endedFlag || this.flush(!0);
1539
+ };
1540
+ onResume = () => this.checkSuspension(this.now());
1502
1541
  onVisibility = () => {
1503
1542
  typeof document > "u" || (document.visibilityState === "hidden" ? (this.hiddenSinceMs === null && (this.hiddenSinceMs = this.now()), this.isHidden = !0) : (this.hiddenSinceMs !== null && (this.hiddenTotalMs += this.now() - this.hiddenSinceMs, this.hiddenSinceMs = null), this.isHidden = !1), this.syncActive(), this.syncBuffering());
1504
1543
  };
1505
1544
  constructor(e) {
1506
1545
  this.opts = {
1507
1546
  ...e,
1508
- heartbeatMs: e.heartbeatMs ?? nt
1547
+ heartbeatMs: e.heartbeatMs ?? rt,
1548
+ idleMs: e.idleMs ?? it
1509
1549
  }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now(), this.presentationSinceMs = this.bornMs, this.startupAnchorMs = this.bornMs;
1510
1550
  }
1511
1551
  start() {
@@ -1515,7 +1555,20 @@ var ut = class {
1515
1555
  sdkVersion: this.opts.sdkVersion,
1516
1556
  ...this.opts.environment
1517
1557
  };
1518
- this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), it), this.timer = setInterval(() => this.heartbeat(), this.opts.heartbeatMs), typeof window < "u" && window.addEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.addEventListener("visibilitychange", this.onVisibility), this.onVisibility());
1558
+ this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), st), this.lastTickMs = this.now(), this.timer = setInterval(() => this.heartbeat(), this.opts.heartbeatMs), typeof window < "u" && window.addEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.addEventListener("visibilitychange", this.onVisibility), document.addEventListener("freeze", this.onFreeze), document.addEventListener("resume", this.onResume), this.onVisibility());
1559
+ }
1560
+ checkSuspension(e) {
1561
+ if (this.endedFlag || !this.opts.idleMs) return !1;
1562
+ let t = Math.max(this.opts.idleMs, at);
1563
+ return this.lastTickMs > 0 && e - this.lastTickMs >= t ? (this.endIdle(this.lastTickMs), !0) : (this.lastTickMs = e, !1);
1564
+ }
1565
+ syncIdle(e) {
1566
+ e === "paused" && this.opts.idleMs ? this.idleTimer === null && (this.idleTimer = setTimeout(() => {
1567
+ this.idleTimer = null, this.endIdle();
1568
+ }, this.opts.idleMs)) : this.idleTimer !== null && (clearTimeout(this.idleTimer), this.idleTimer = null);
1569
+ }
1570
+ endIdle(e) {
1571
+ this.endedFlag || (this.end("END_REASON_IDLE", e), this.opts.onIdle?.());
1519
1572
  }
1520
1573
  noteStateChange(e, t, n, r) {
1521
1574
  if (this.endedFlag || e === t) return;
@@ -1525,32 +1578,32 @@ var ut = class {
1525
1578
  let e = !this.isHidden && !this.stallIsSeekInduced;
1526
1579
  e && (this.rebufferCount += 1), this.isBuffering = !0, this.lastStallMs = i, e && this.timeToFirstStallMs === 0 && (this.timeToFirstStallMs = Math.max(W, Math.round(this.playingTotalMs)));
1527
1580
  } else e === "buffering" && (this.isBuffering = !1, this.stallIsSeekInduced = !1);
1528
- this.syncBuffering(), t === "paused" && (this.pauseCount += 1);
1581
+ this.syncBuffering(), t === "paused" && (this.pauseCount += 1), this.syncIdle(t);
1529
1582
  let a = {
1530
1583
  fromState: q[e] ?? "PLAYER_STATE_UNSPECIFIED",
1531
1584
  toState: q[t] ?? "PLAYER_STATE_UNSPECIFIED",
1532
- presentation: ot[this.presentation]
1585
+ presentation: lt[this.presentation]
1533
1586
  }, o = this.causeOf(e, t, i);
1534
1587
  o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1535
1588
  }
1536
1589
  seekInduced(e) {
1537
- return this.lastSeekMs >= 0 && e - this.lastSeekMs < at;
1590
+ return this.lastSeekMs >= 0 && e - this.lastSeekMs < ct;
1538
1591
  }
1539
1592
  causeOf(e, t, n) {
1540
1593
  if (t === "error") return "TRANSITION_CAUSE_ERROR";
1541
1594
  if (t === "buffering") return this.seekInduced(n) ? "TRANSITION_CAUSE_SEEK" : "TRANSITION_CAUSE_NETWORK_STALL";
1542
1595
  if (t === "paused" || t === "playing" && e === "paused") return "TRANSITION_CAUSE_USER";
1543
1596
  }
1544
- syncActive() {
1545
- let e = this.isPlaying && !this.isHidden && !this.isMuted;
1546
- e && this.activeSinceMs === null ? this.activeSinceMs = this.now() : !e && this.activeSinceMs !== null && (this.activeTotalMs += this.now() - this.activeSinceMs, this.activeSinceMs = null);
1597
+ syncActive(e = this.now()) {
1598
+ let t = this.isPlaying && !this.isHidden && !this.isMuted;
1599
+ t && this.activeSinceMs === null ? this.activeSinceMs = e : !t && this.activeSinceMs !== null && (this.activeTotalMs += e - this.activeSinceMs, this.activeSinceMs = null);
1547
1600
  }
1548
1601
  syncBuffering() {
1549
1602
  let e = this.isBuffering && !this.isHidden && !this.stallIsSeekInduced;
1550
1603
  e && this.bufferingSinceMs === null ? this.bufferingSinceMs = this.now() : !e && this.bufferingSinceMs !== null && (this.rebufferTotalMs += this.now() - this.bufferingSinceMs, this.bufferingSinceMs = null);
1551
1604
  }
1552
- closePresentation() {
1553
- let e = this.now(), t = e - this.presentationSinceMs;
1605
+ closePresentation(e = this.now()) {
1606
+ let t = e - this.presentationSinceMs;
1554
1607
  this.presentation === "fullscreen" ? this.fullscreenTotalMs += t : this.presentation === "pip" && (this.pipTotalMs += t), this.presentationSinceMs = e;
1555
1608
  }
1556
1609
  noteAutoplay(e) {
@@ -1602,7 +1655,7 @@ var ut = class {
1602
1655
  let r = n ?? "";
1603
1656
  if (r !== "") return /^fragLoad/i.test(r) ? this.sawFirstBytes ? "ERROR_STAGE_SEGMENT" : "ERROR_STAGE_FIRST_SEGMENT" : /^(manifest|level|audioTrack|subtitleTrack)/i.test(r) ? "ERROR_STAGE_MANIFEST" : /key|licen[cs]e/i.test(r) ? "ERROR_STAGE_DRM" : /parsing|append|buffer|remux|decrypt|media/i.test(r) ? "ERROR_STAGE_DECODE" : "ERROR_STAGE_OTHER";
1604
1657
  if (e === "DRM_CLIENT") return "ERROR_STAGE_DRM";
1605
- if (ct.has(e)) return "ERROR_STAGE_RESOLVE";
1658
+ if (dt.has(e)) return "ERROR_STAGE_RESOLVE";
1606
1659
  let i = t ?? "";
1607
1660
  return /frag|segment/i.test(i) ? this.sawFirstBytes ? "ERROR_STAGE_SEGMENT" : "ERROR_STAGE_FIRST_SEGMENT" : /manifest|level|playlist/i.test(i) ? "ERROR_STAGE_MANIFEST" : /key|licen[cs]e/i.test(i) ? "ERROR_STAGE_DRM" : /buffer|decode|media|append/i.test(i) ? "ERROR_STAGE_DECODE" : "ERROR_STAGE_OTHER";
1608
1661
  }
@@ -1617,66 +1670,68 @@ var ut = class {
1617
1670
  }
1618
1671
  heartbeat() {
1619
1672
  if (this.endedFlag) return;
1620
- let e = this.opts.sample(), t = this.now(), n = this.playingTotalMs;
1621
- this.playingSinceMs !== null && (n += t - this.playingSinceMs);
1673
+ let e = this.now();
1674
+ if (this.checkSuspension(e)) return;
1675
+ let t = this.opts.sample(), n = this.playingTotalMs;
1676
+ this.playingSinceMs !== null && (n += e - this.playingSinceMs);
1622
1677
  let r = this.hiddenTotalMs;
1623
- this.hiddenSinceMs !== null && (r += t - this.hiddenSinceMs);
1678
+ this.hiddenSinceMs !== null && (r += e - this.hiddenSinceMs);
1624
1679
  let i = this.rebufferTotalMs;
1625
- this.bufferingSinceMs !== null && (i += t - this.bufferingSinceMs);
1680
+ this.bufferingSinceMs !== null && (i += e - this.bufferingSinceMs);
1626
1681
  let a = this.activeTotalMs;
1627
- this.activeSinceMs !== null && (a += t - this.activeSinceMs);
1628
- let o = {}, s = e.bytesLoaded ?? 0;
1682
+ this.activeSinceMs !== null && (a += e - this.activeSinceMs);
1683
+ let o = {}, s = t.bytesLoaded ?? 0;
1629
1684
  s > this.lastBytes && (o.bytesDownloadedDelta = s - this.lastBytes), this.lastBytes = s;
1630
- let c = e.playlistBytesLoaded ?? 0;
1685
+ let c = t.playlistBytesLoaded ?? 0;
1631
1686
  c > this.lastPlaylistBytes && (o.playlistBytesDelta = c - this.lastPlaylistBytes), this.lastPlaylistBytes = c;
1632
- let l = e.droppedFrames ?? 0;
1687
+ let l = t.droppedFrames ?? 0;
1633
1688
  l > this.lastDropped && (o.droppedFramesDelta = l - this.lastDropped), this.lastDropped = l;
1634
- let u = e.renditionSwitches ?? 0;
1635
- u > this.lastSwitches && (o.renditionSwitchesDelta = u - this.lastSwitches), this.lastSwitches = u, n > this.lastHbPlayingMs && (o.playingMsDelta = Math.round(n - this.lastHbPlayingMs)), this.lastHbPlayingMs = n, r > this.lastHbHiddenMs && (o.hiddenMsDelta = Math.round(r - this.lastHbHiddenMs)), this.lastHbHiddenMs = r, i > this.lastHbRebufferMs && (o.rebufferMsDelta = Math.round(i - this.lastHbRebufferMs)), this.lastHbRebufferMs = i, this.rebufferCount > this.lastHbRebufferCount && (o.rebufferCountDelta = this.rebufferCount - this.lastHbRebufferCount), this.lastHbRebufferCount = this.rebufferCount, a > this.lastHbActiveMs && (o.activeMsDelta = Math.round(a - this.lastHbActiveMs)), this.lastHbActiveMs = a, typeof e.latencySeconds == "number" && (o.latencyMs = Math.round(e.latencySeconds * 1e3)), typeof e.targetLatencySeconds == "number" && (o.targetLatencyMs = Math.round(e.targetLatencySeconds * 1e3)), typeof e.bandwidthKbps == "number" && (o.bandwidthEstKbps = e.bandwidthKbps), typeof e.levelHeight == "number" && (o.renditionHeight = e.levelHeight), typeof e.renditionBitrateKbps == "number" && (o.renditionBitrateKbps = e.renditionBitrateKbps), e.atLiveEdge === !0 && (o.atLiveEdge = !0), typeof e.bufferedMs == "number" && e.bufferedMs > 0 && (o.bufferedMs = Math.round(e.bufferedMs)), o.playerState = q[e.state] ?? "PLAYER_STATE_UNSPECIFIED";
1689
+ let u = t.renditionSwitches ?? 0;
1690
+ u > this.lastSwitches && (o.renditionSwitchesDelta = u - this.lastSwitches), this.lastSwitches = u, n > this.lastHbPlayingMs && (o.playingMsDelta = Math.round(n - this.lastHbPlayingMs)), this.lastHbPlayingMs = n, r > this.lastHbHiddenMs && (o.hiddenMsDelta = Math.round(r - this.lastHbHiddenMs)), this.lastHbHiddenMs = r, i > this.lastHbRebufferMs && (o.rebufferMsDelta = Math.round(i - this.lastHbRebufferMs)), this.lastHbRebufferMs = i, this.rebufferCount > this.lastHbRebufferCount && (o.rebufferCountDelta = this.rebufferCount - this.lastHbRebufferCount), this.lastHbRebufferCount = this.rebufferCount, a > this.lastHbActiveMs && (o.activeMsDelta = Math.round(a - this.lastHbActiveMs)), this.lastHbActiveMs = a, typeof t.latencySeconds == "number" && (o.latencyMs = Math.round(t.latencySeconds * 1e3)), typeof t.targetLatencySeconds == "number" && (o.targetLatencyMs = Math.round(t.targetLatencySeconds * 1e3)), typeof t.bandwidthKbps == "number" && (o.bandwidthEstKbps = t.bandwidthKbps), typeof t.levelHeight == "number" && (o.renditionHeight = t.levelHeight), typeof t.renditionBitrateKbps == "number" && (o.renditionBitrateKbps = t.renditionBitrateKbps), t.atLiveEdge === !0 && (o.atLiveEdge = !0), typeof t.bufferedMs == "number" && t.bufferedMs > 0 && (o.bufferedMs = Math.round(t.bufferedMs)), o.playerState = q[t.state] ?? "PLAYER_STATE_UNSPECIFIED";
1636
1691
  let d = this.mutedTotalMs;
1637
- this.mutedSinceMs !== null && (d += t - this.mutedSinceMs), d > this.lastHbMutedMs && (o.mutedMsDelta = Math.round(d - this.lastHbMutedMs)), this.lastHbMutedMs = d;
1638
- let f = e.upshifts ?? 0;
1692
+ this.mutedSinceMs !== null && (d += e - this.mutedSinceMs), d > this.lastHbMutedMs && (o.mutedMsDelta = Math.round(d - this.lastHbMutedMs)), this.lastHbMutedMs = d;
1693
+ let f = t.upshifts ?? 0;
1639
1694
  f > this.lastUpshifts && (o.upshiftsDelta = f - this.lastUpshifts), this.lastUpshifts = f;
1640
- let p = e.downshifts ?? 0;
1695
+ let p = t.downshifts ?? 0;
1641
1696
  p > this.lastDownshifts && (o.downshiftsDelta = p - this.lastDownshifts), this.lastDownshifts = p;
1642
- let m = e.decodedFrames ?? 0;
1697
+ let m = t.decodedFrames ?? 0;
1643
1698
  m > this.lastDecoded && (o.decodedFramesDelta = m - this.lastDecoded), this.lastDecoded = m;
1644
- let h = e.requestErrors ?? 0;
1699
+ let h = t.requestErrors ?? 0;
1645
1700
  h > this.lastRequestErrors && (o.requestErrorsDelta = h - this.lastRequestErrors), this.lastRequestErrors = h;
1646
- let g = e.requestCount ?? 0, _ = g - this.lastRequestCount;
1701
+ let g = t.requestCount ?? 0, _ = g - this.lastRequestCount;
1647
1702
  if (this.lastRequestCount = g, _ > 0) {
1648
1703
  o.requestCountDelta = _;
1649
- let t = e.requestTimesMs ?? [], n = t.slice(Math.max(0, t.length - _)).sort((e, t) => e - t);
1704
+ let e = t.requestTimesMs ?? [], n = e.slice(Math.max(0, e.length - _)).sort((e, t) => e - t);
1650
1705
  n.length > 0 && (o.requestTimeP50Ms = K(n, 50), o.requestTimeP95Ms = K(n, 95));
1651
1706
  }
1652
- typeof e.latencySeconds == "number" && typeof e.targetLatencySeconds == "number" && (o.driftMs = Math.round((e.latencySeconds - e.targetLatencySeconds) * 1e3)), typeof e.playbackRate == "number" && e.playbackRate > 0 && (o.playbackRate = e.playbackRate), e.qualityPinned === !0 && (o.qualityPinned = !0), e.cdnHost && (o.cdnHost = e.cdnHost), this.maybeEmitMediaInfo(e), this.enqueue({ heartbeat: o }), this.flush(), s > 0 && this.noteFirstBytes();
1707
+ typeof t.latencySeconds == "number" && typeof t.targetLatencySeconds == "number" && (o.driftMs = Math.round((t.latencySeconds - t.targetLatencySeconds) * 1e3)), typeof t.playbackRate == "number" && t.playbackRate > 0 && (o.playbackRate = t.playbackRate), t.qualityPinned === !0 && (o.qualityPinned = !0), t.cdnHost && (o.cdnHost = t.cdnHost), this.maybeEmitMediaInfo(t), this.enqueue({ heartbeat: o }), this.flush(), s > 0 && this.noteFirstBytes();
1653
1708
  }
1654
1709
  maybeEmitMediaInfo(e) {
1655
1710
  if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
1656
1711
  let t = {};
1657
- e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = st[e.keySystem]), e.drmSecurityLevel && (t.drmSecurityLevel = e.drmSecurityLevel), e.ladderHeights?.length && (t.ladderHeights = e.ladderHeights), e.ladderTopBitrateKbps && (t.ladderTopBitrateKbps = e.ladderTopBitrateKbps), e.lowLatency && (t.lowLatency = !0);
1712
+ e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = ut[e.keySystem]), e.drmSecurityLevel && (t.drmSecurityLevel = e.drmSecurityLevel), e.ladderHeights?.length && (t.ladderHeights = e.ladderHeights), e.ladderTopBitrateKbps && (t.ladderTopBitrateKbps = e.ladderTopBitrateKbps), e.lowLatency && (t.lowLatency = !0);
1658
1713
  let n = JSON.stringify(t);
1659
1714
  n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
1660
1715
  }
1661
- end(e) {
1716
+ end(e, t) {
1662
1717
  if (this.endedFlag) return;
1663
1718
  this.endedFlag = !0;
1664
- let t = this.now();
1665
- this.playingSinceMs !== null && (this.playingTotalMs += t - this.playingSinceMs, this.playingSinceMs = null), this.hiddenSinceMs !== null && (this.hiddenTotalMs += t - this.hiddenSinceMs, this.hiddenSinceMs = null), this.bufferingSinceMs !== null && (this.rebufferTotalMs += t - this.bufferingSinceMs, this.bufferingSinceMs = null), this.mutedSinceMs !== null && (this.mutedTotalMs += t - this.mutedSinceMs, this.mutedSinceMs = null), this.waitingSinceMs !== null && (this.waitingTotalMs += t - this.waitingSinceMs, this.waitingSinceMs = null), this.isPlaying = !1, this.syncActive(), this.closePresentation(), this.firstFlushTimer &&= (clearTimeout(this.firstFlushTimer), null), this.timer &&= (clearInterval(this.timer), null), typeof window < "u" && window.removeEventListener("pagehide", this.onPagehide), typeof document < "u" && document.removeEventListener("visibilitychange", this.onVisibility);
1666
- let n = this.safeSample();
1667
- n && this.maybeEmitMediaInfo(n);
1668
- let r = { reason: e }, i = n?.bytesLoaded ?? this.lastBytes;
1669
- i > 0 && (r.bytesDownloadedTotal = i), this.playingTotalMs > 0 && (r.watchTimeMs = Math.round(this.playingTotalMs)), this.rebufferCount > 0 && (r.rebufferCount = this.rebufferCount), this.rebufferTotalMs > 0 && (r.rebufferTimeMs = Math.round(this.rebufferTotalMs)), this.startupTimeMs > 0 && (r.startupTimeMs = Math.round(this.startupTimeMs)), this.manifestLoadMs > 0 && (r.manifestLoadMs = Math.round(this.manifestLoadMs)), this.firstFragLoadMs > 0 && (r.firstFragLoadMs = Math.round(this.firstFragLoadMs));
1670
- let a = n?.licenseTimeMs ?? 0;
1671
- a > 0 && (r.licenseTimeMs = Math.round(a));
1672
- let o = n?.droppedFrames ?? this.lastDropped;
1673
- o > 0 && (r.droppedFramesTotal = o), this.errorCount > 0 && (r.errorCount = this.errorCount), this.hiddenTotalMs > 0 && (r.hiddenTimeMs = Math.round(this.hiddenTotalMs)), this.seekCount > 0 && (r.seekCount = this.seekCount), this.pauseCount > 0 && (r.pauseCount = this.pauseCount), this.firstAudioMs > 0 && (r.firstAudioMs = Math.round(this.firstAudioMs)), this.waitingTotalMs > 0 && (r.waitingForLiveMs = Math.round(this.waitingTotalMs)), this.timeToFirstStallMs > 0 && (r.timeToFirstStallMs = Math.round(this.timeToFirstStallMs));
1674
- let s = n?.positionMs ?? 0;
1675
- s > 0 && (r.exitPositionMs = Math.round(s)), this.activeTotalMs > 0 && (r.activeWatchTimeMs = Math.round(this.activeTotalMs)), this.mutedTotalMs > 0 && (r.mutedTimeMs = Math.round(this.mutedTotalMs));
1676
- let c = e !== "END_REASON_ENDED";
1677
- c && this.lastStallMs > 0 && t - this.lastStallMs <= G && (r.abandonAfterStallMs = Math.max(W, Math.round(t - this.lastStallMs))), c && this.lastErrorMs > 0 && t - this.lastErrorMs <= G && (r.abandonAfterErrorMs = Math.max(W, Math.round(t - this.lastErrorMs))), this.fullscreenTotalMs > 0 && (r.fullscreenTimeMs = Math.round(this.fullscreenTotalMs)), this.pipTotalMs > 0 && (r.pipTimeMs = Math.round(this.pipTotalMs)), this.volumeChanges > 0 && (r.volumeChanges = this.volumeChanges), this.muteChanges > 0 && (r.muteChanges = this.muteChanges);
1678
- let l = n?.decodedFrames ?? this.lastDecoded;
1679
- l > 0 && (r.decodedFramesTotal = l), this.startupTimeMs === 0 && (r.failedStage = this.lastErrorStage ?? (this.manifestLoadMs === 0 ? "ERROR_STAGE_MANIFEST" : this.firstFragLoadMs === 0 ? "ERROR_STAGE_FIRST_SEGMENT" : "ERROR_STAGE_OTHER")), this.pending.push(this.wrap({ sessionEnd: r })), this.flush(e === "END_REASON_PAGEHIDE");
1719
+ let n = t ?? this.now();
1720
+ this.playingSinceMs !== null && (this.playingTotalMs += n - this.playingSinceMs, this.playingSinceMs = null), this.hiddenSinceMs !== null && (this.hiddenTotalMs += n - this.hiddenSinceMs, this.hiddenSinceMs = null), this.bufferingSinceMs !== null && (this.rebufferTotalMs += n - this.bufferingSinceMs, this.bufferingSinceMs = null), this.mutedSinceMs !== null && (this.mutedTotalMs += n - this.mutedSinceMs, this.mutedSinceMs = null), this.waitingSinceMs !== null && (this.waitingTotalMs += n - this.waitingSinceMs, this.waitingSinceMs = null), this.isPlaying = !1, this.syncActive(n), this.closePresentation(n), this.firstFlushTimer &&= (clearTimeout(this.firstFlushTimer), null), this.timer &&= (clearInterval(this.timer), null), this.idleTimer &&= (clearTimeout(this.idleTimer), null), typeof window < "u" && window.removeEventListener("pagehide", this.onPagehide), typeof document < "u" && (document.removeEventListener("visibilitychange", this.onVisibility), document.removeEventListener("freeze", this.onFreeze), document.removeEventListener("resume", this.onResume));
1721
+ let r = this.safeSample();
1722
+ r && this.maybeEmitMediaInfo(r);
1723
+ let i = { reason: e }, a = r?.bytesLoaded ?? this.lastBytes;
1724
+ a > 0 && (i.bytesDownloadedTotal = a), this.playingTotalMs > 0 && (i.watchTimeMs = Math.round(this.playingTotalMs)), this.rebufferCount > 0 && (i.rebufferCount = this.rebufferCount), this.rebufferTotalMs > 0 && (i.rebufferTimeMs = Math.round(this.rebufferTotalMs)), this.startupTimeMs > 0 && (i.startupTimeMs = Math.round(this.startupTimeMs)), this.manifestLoadMs > 0 && (i.manifestLoadMs = Math.round(this.manifestLoadMs)), this.firstFragLoadMs > 0 && (i.firstFragLoadMs = Math.round(this.firstFragLoadMs));
1725
+ let o = r?.licenseTimeMs ?? 0;
1726
+ o > 0 && (i.licenseTimeMs = Math.round(o));
1727
+ let s = r?.droppedFrames ?? this.lastDropped;
1728
+ s > 0 && (i.droppedFramesTotal = s), this.errorCount > 0 && (i.errorCount = this.errorCount), this.hiddenTotalMs > 0 && (i.hiddenTimeMs = Math.round(this.hiddenTotalMs)), this.seekCount > 0 && (i.seekCount = this.seekCount), this.pauseCount > 0 && (i.pauseCount = this.pauseCount), this.firstAudioMs > 0 && (i.firstAudioMs = Math.round(this.firstAudioMs)), this.waitingTotalMs > 0 && (i.waitingForLiveMs = Math.round(this.waitingTotalMs)), this.timeToFirstStallMs > 0 && (i.timeToFirstStallMs = Math.round(this.timeToFirstStallMs));
1729
+ let c = r?.positionMs ?? 0;
1730
+ c > 0 && (i.exitPositionMs = Math.round(c)), this.activeTotalMs > 0 && (i.activeWatchTimeMs = Math.round(this.activeTotalMs)), this.mutedTotalMs > 0 && (i.mutedTimeMs = Math.round(this.mutedTotalMs));
1731
+ let l = e !== "END_REASON_ENDED";
1732
+ l && this.lastStallMs > 0 && n - this.lastStallMs <= G && (i.abandonAfterStallMs = Math.max(W, Math.round(n - this.lastStallMs))), l && this.lastErrorMs > 0 && n - this.lastErrorMs <= G && (i.abandonAfterErrorMs = Math.max(W, Math.round(n - this.lastErrorMs))), this.fullscreenTotalMs > 0 && (i.fullscreenTimeMs = Math.round(this.fullscreenTotalMs)), this.pipTotalMs > 0 && (i.pipTimeMs = Math.round(this.pipTotalMs)), this.volumeChanges > 0 && (i.volumeChanges = this.volumeChanges), this.muteChanges > 0 && (i.muteChanges = this.muteChanges);
1733
+ let u = r?.decodedFrames ?? this.lastDecoded;
1734
+ u > 0 && (i.decodedFramesTotal = u), this.startupTimeMs === 0 && (i.failedStage = this.lastErrorStage ?? (this.manifestLoadMs === 0 ? "ERROR_STAGE_MANIFEST" : this.firstFragLoadMs === 0 ? "ERROR_STAGE_FIRST_SEGMENT" : "ERROR_STAGE_OTHER")), this.pending.push(this.wrap({ sessionEnd: i })), this.flush(e === "END_REASON_PAGEHIDE");
1680
1735
  }
1681
1736
  safeSample() {
1682
1737
  try {
@@ -1694,7 +1749,7 @@ var ut = class {
1694
1749
  };
1695
1750
  }
1696
1751
  enqueue(e) {
1697
- for (this.pending.push(this.wrap(e)); this.pending.length > rt;) {
1752
+ for (this.pending.push(this.wrap(e)); this.pending.length > ot;) {
1698
1753
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1699
1754
  this.pending.splice(e >= 0 ? e : 0, 1);
1700
1755
  }
@@ -1717,7 +1772,7 @@ var ut = class {
1717
1772
  };
1718
1773
  //#endregion
1719
1774
  //#region src/core/environment.ts
1720
- function dt() {
1775
+ function mt() {
1721
1776
  if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
1722
1777
  let e;
1723
1778
  try {
@@ -1742,14 +1797,14 @@ function dt() {
1742
1797
  integration: "INTEGRATION_KIND_IFRAME"
1743
1798
  };
1744
1799
  }
1745
- function ft() {
1800
+ function ht() {
1746
1801
  if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
1747
1802
  let e = navigator.userAgentData, t = e?.platform ?? "";
1748
1803
  if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
1749
1804
  let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
1750
1805
  return e?.mobile ?? (typeof navigator.maxTouchPoints == "number" && navigator.maxTouchPoints > 1 && n > 0 && n < 900) ? n >= 600 ? "DEVICE_CLASS_TABLET" : "DEVICE_CLASS_PHONE" : e || n >= 900 || /mac|win|linux|chrome os/i.test(t) ? "DEVICE_CLASS_DESKTOP" : "DEVICE_CLASS_OTHER";
1751
1806
  }
1752
- function pt() {
1807
+ function gt() {
1753
1808
  let e = typeof navigator < "u" ? navigator.connection : void 0;
1754
1809
  if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
1755
1810
  let t = {
@@ -1768,17 +1823,17 @@ function pt() {
1768
1823
  }
1769
1824
  return typeof e.downlink == "number" && e.downlink > 0 && (i.downlinkKbps = Math.round(e.downlink * 1e3)), typeof e.rtt == "number" && e.rtt > 0 && (i.rttMs = Math.round(e.rtt)), e.saveData && (i.saveData = !0), i;
1770
1825
  }
1771
- function mt(e) {
1772
- let t = dt(), n = {
1773
- deviceClass: ft(),
1826
+ function _t(e) {
1827
+ let t = mt(), n = {
1828
+ deviceClass: ht(),
1774
1829
  integration: t.integration,
1775
- ...pt()
1830
+ ...gt()
1776
1831
  };
1777
1832
  t.host && (n.host = t.host), typeof screen < "u" && screen.width > 0 && (n.screenWidth = screen.width, n.screenHeight = screen.height), typeof window < "u" && window.devicePixelRatio > 0 && (n.devicePixelRatio = Math.round(window.devicePixelRatio * 100) / 100);
1778
1833
  let r = e.getBoundingClientRect?.();
1779
1834
  return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
1780
1835
  }
1781
- async function ht() {
1836
+ async function vt() {
1782
1837
  let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
1783
1838
  if (!e?.getHighEntropyValues) return {};
1784
1839
  try {
@@ -1790,7 +1845,7 @@ async function ht() {
1790
1845
  }
1791
1846
  //#endregion
1792
1847
  //#region src/core/version.ts
1793
- var gt = "2.6.3", J = {
1848
+ var yt = "2.7.0", J = {
1794
1849
  accent: "#E1B600",
1795
1850
  accentText: "#1B1C23",
1796
1851
  background: "#0e0f13",
@@ -1823,15 +1878,15 @@ function X(e, t) {
1823
1878
  }
1824
1879
  //#endregion
1825
1880
  //#region src/ui/styles.ts
1826
- var Z = "havik-player-styles", _t = "\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";
1827
- function vt(e = document) {
1881
+ var Z = "havik-player-styles", bt = "\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";
1882
+ function xt(e = document) {
1828
1883
  if (e.getElementById(Z)) return;
1829
1884
  let t = e.createElement("style");
1830
- t.id = Z, t.textContent = _t, e.head.appendChild(t);
1885
+ t.id = Z, t.textContent = bt, e.head.appendChild(t);
1831
1886
  }
1832
1887
  //#endregion
1833
1888
  //#region src/ui/icons.ts
1834
- var yt = {
1889
+ var St = {
1835
1890
  play: "<path d=\"M8 5v14l11-7z\"/>",
1836
1891
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1837
1892
  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\"/>",
@@ -1845,20 +1900,20 @@ var yt = {
1845
1900
  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\"/>"
1846
1901
  };
1847
1902
  function Q(e) {
1848
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${yt[e]}</svg>`;
1903
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${St[e]}</svg>`;
1849
1904
  }
1850
1905
  //#endregion
1851
1906
  //#region src/ui/controls.ts
1852
- var bt = 3e3, xt = /* @__PURE__ */ new Set([
1907
+ var Ct = 3e3, wt = /* @__PURE__ */ new Set([
1853
1908
  "GONE",
1854
1909
  "NOT_FOUND",
1855
1910
  "INVALID_URN",
1856
1911
  "UNAUTHORIZED"
1857
1912
  ]);
1858
- function St(e) {
1859
- return new Ct(e);
1913
+ function Tt(e) {
1914
+ return new Et(e);
1860
1915
  }
1861
- var Ct = class {
1916
+ var Et = class {
1862
1917
  root;
1863
1918
  video;
1864
1919
  player;
@@ -1900,7 +1955,7 @@ var Ct = class {
1900
1955
  scrubbing = !1;
1901
1956
  offFns = [];
1902
1957
  constructor(e) {
1903
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? bt, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, vt(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");
1958
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? Ct, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, xt(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");
1904
1959
  let t = (e, t, n) => {
1905
1960
  let r = this.doc.createElement(e);
1906
1961
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1991,7 +2046,7 @@ var Ct = class {
1991
2046
  e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Q("play"), e.addEventListener("click", () => void this.player.play()), n.append(e);
1992
2047
  } else {
1993
2048
  let r = this.doc.createElement("div");
1994
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !xt.has(this.lastError.code)) {
2049
+ if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !wt.has(this.lastError.code)) {
1995
2050
  let e = this.doc.createElement("button");
1996
2051
  e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Q("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
1997
2052
  }
@@ -2032,7 +2087,7 @@ var Ct = class {
2032
2087
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
2033
2088
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
2034
2089
  }
2035
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${wt(this.video.currentTime)} / ${wt(this.video.duration)}`);
2090
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Dt(this.video.currentTime)} / ${Dt(this.video.duration)}`);
2036
2091
  }
2037
2092
  syncLive() {
2038
2093
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -2082,17 +2137,17 @@ var Ct = class {
2082
2137
  renderQualityMenu() {
2083
2138
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
2084
2139
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
2085
- for (let n of e) this.menu.append(this.menuItem(Tt(n), t === n.index, () => this.player.setQuality(n.index)));
2140
+ for (let n of e) this.menu.append(this.menuItem(Ot(n), t === n.index, () => this.player.setQuality(n.index)));
2086
2141
  }
2087
2142
  renderAudioMenu() {
2088
2143
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
2089
2144
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
2090
- for (let t of e) this.menu.append(this.menuItem(Et(t), t.default, () => this.player.setAudioTrack(t.id)));
2145
+ for (let t of e) this.menu.append(this.menuItem(kt(t), t.default, () => this.player.setAudioTrack(t.id)));
2091
2146
  }
2092
2147
  renderCcMenu() {
2093
2148
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
2094
2149
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
2095
- for (let t of e) this.menu.append(this.menuItem(Dt(t), !1, () => this.player.setTextTrack(t.id)));
2150
+ for (let t of e) this.menu.append(this.menuItem(At(t), !1, () => this.player.setTextTrack(t.id)));
2096
2151
  }
2097
2152
  onTracksChanged() {
2098
2153
  this.openMenu && this.renderMenu(this.openMenu);
@@ -2192,32 +2247,32 @@ var Ct = class {
2192
2247
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
2193
2248
  }
2194
2249
  };
2195
- function wt(e) {
2250
+ function Dt(e) {
2196
2251
  (!Number.isFinite(e) || e < 0) && (e = 0);
2197
2252
  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");
2198
2253
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
2199
2254
  }
2200
- function Tt(e) {
2255
+ function Ot(e) {
2201
2256
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
2202
2257
  }
2203
- function Et(e) {
2258
+ function kt(e) {
2204
2259
  return e.name || e.lang || `Track ${e.id}`;
2205
2260
  }
2206
- function Dt(e) {
2261
+ function At(e) {
2207
2262
  return e.name || e.lang || `Track ${e.id}`;
2208
2263
  }
2209
2264
  //#endregion
2210
2265
  //#region src/managed/index.ts
2211
- var Ot = 6e3, kt = 45e3, At = 2e3, jt = 3, Mt = 2e3, Nt = 48e4;
2212
- async function Pt(e) {
2266
+ var jt = 6e3, Mt = 45e3, Nt = 2e3, Pt = 3, Ft = 2e3, It = 48e4;
2267
+ async function Lt(e) {
2213
2268
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
2214
2269
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
2215
2270
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
2216
2271
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
2217
- let t = new Ft(e);
2272
+ let t = new Rt(e);
2218
2273
  return await t.start(), t;
2219
2274
  }
2220
- var Ft = class {
2275
+ var Rt = class {
2221
2276
  opts;
2222
2277
  abort = new AbortController();
2223
2278
  listeners = {};
@@ -2243,6 +2298,7 @@ var Ft = class {
2243
2298
  analyticsSession = null;
2244
2299
  detachMediaListeners;
2245
2300
  playRequested = !1;
2301
+ idleClosed = !1;
2246
2302
  pendingStartLoad = !1;
2247
2303
  constructor(e) {
2248
2304
  this.opts = e;
@@ -2304,7 +2360,7 @@ var Ft = class {
2304
2360
  setTimeout(() => {
2305
2361
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
2306
2362
  phase: i ? "unavailable" : "tooEarly",
2307
- retryInMs: kt,
2363
+ retryInMs: Mt,
2308
2364
  attempt: 1,
2309
2365
  elapsedMs: 0,
2310
2366
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -2343,13 +2399,13 @@ var Ft = class {
2343
2399
  !a && !p() && h();
2344
2400
  }, e);
2345
2401
  };
2346
- _(Ot), this.armingSub = E({
2402
+ _(jt), this.armingSub = E({
2347
2403
  eventsBaseUrl: t,
2348
2404
  matchUrn: this.opts.matchUrn,
2349
2405
  credential: this.opts.credential,
2350
- onAlive: () => _(kt),
2406
+ onAlive: () => _(Mt),
2351
2407
  onState: (e) => {
2352
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * At);
2408
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Nt);
2353
2409
  },
2354
2410
  onError: () => h()
2355
2411
  });
@@ -2387,18 +2443,23 @@ var Ft = class {
2387
2443
  this.destroyed || t !== this.loadGen || this.engine !== i || (this.analyticsSession?.noteReady(), this.emit("ready"));
2388
2444
  }, 0));
2389
2445
  }
2446
+ onAnalyticsIdle(e) {
2447
+ this.destroyed || this.engine !== e || (this.idleClosed = !0, this.pendingStartLoad = !1, e.stop());
2448
+ }
2390
2449
  startAnalytics(e, t) {
2391
2450
  if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
2392
2451
  let n = e.analytics?.sid;
2393
2452
  if (!n) return;
2394
- let r = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? lt(this.opts.baseUrl);
2453
+ let r = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? ft(this.opts.baseUrl);
2395
2454
  if (!r) return;
2396
- let i = this.opts.video, a = new ut({
2455
+ let i = this.opts.video, a = new pt({
2397
2456
  endpoint: r,
2398
2457
  sid: n,
2399
2458
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
2400
- sdkVersion: gt,
2401
- environment: mt(i),
2459
+ sdkVersion: yt,
2460
+ environment: _t(i),
2461
+ idleMs: typeof this.opts.analytics == "object" ? this.opts.analytics.idleMs : void 0,
2462
+ onIdle: () => this.onAnalyticsIdle(t),
2402
2463
  sample: () => {
2403
2464
  let e = this.engine?.getStats() ?? I, t = i.buffered.length ? Math.max(0, i.buffered.end(i.buffered.length - 1) - i.currentTime) : 0;
2404
2465
  return {
@@ -2414,7 +2475,7 @@ var Ft = class {
2414
2475
  let o = () => a.noteSeek(), s = () => a.noteVolume(i.muted, i.volume);
2415
2476
  i.addEventListener("seeking", o), i.addEventListener("volumechange", s), this.detachMediaListeners = () => {
2416
2477
  i.removeEventListener("seeking", o), i.removeEventListener("volumechange", s);
2417
- }, a.start(), a.seedVolume(i.muted, i.volume), a.notePresentation(this.currentPresentation()), ht().then((e) => a.noteEnvironment(e)), this.opts.autoplay === !1 && a.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
2478
+ }, a.start(), a.seedVolume(i.muted, i.volume), a.notePresentation(this.currentPresentation()), vt().then((e) => a.noteEnvironment(e)), this.opts.autoplay === !1 && a.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
2418
2479
  }
2419
2480
  currentPresentation() {
2420
2481
  let e = this.opts.video;
@@ -2485,7 +2546,7 @@ var Ft = class {
2485
2546
  r = 0, i = !1;
2486
2547
  },
2487
2548
  onError: () => {
2488
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= jt && (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.`))));
2549
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= Pt && (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.`))));
2489
2550
  }
2490
2551
  });
2491
2552
  }
@@ -2507,7 +2568,7 @@ var Ft = class {
2507
2568
  scheduleRejoin(e) {
2508
2569
  this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
2509
2570
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
2510
- }, Math.random() * At));
2571
+ }, Math.random() * Nt));
2511
2572
  }
2512
2573
  cancelRejoin() {
2513
2574
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -2517,7 +2578,7 @@ var Ft = class {
2517
2578
  this.teardownPlayback(), this.setState("waiting"), this.beginArming(e, void 0, !0);
2518
2579
  }
2519
2580
  teardownPlayback() {
2520
- this.stopStats(), this.stopRefresh(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null, this.pendingStartLoad = !1;
2581
+ this.stopStats(), this.stopRefresh(), this.cancelRejoin(), this.closeArming(), this.closeLiveState(), this.detachMedia?.(), this.engine?.destroy(), this.engine = null, this.pendingStartLoad = !1, this.idleClosed = !1;
2521
2582
  }
2522
2583
  mountControls() {
2523
2584
  let e = this.opts.controls ?? "custom", t = this.opts.video;
@@ -2531,7 +2592,7 @@ var Ft = class {
2531
2592
  }
2532
2593
  t.controls = !1;
2533
2594
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2534
- r && r.insertBefore(n, t), n.appendChild(t), X(n, this.opts.theme), this.container = n, this.controlBar = St({
2595
+ r && r.insertBefore(n, t), n.appendChild(t), X(n, this.opts.theme), this.container = n, this.controlBar = Tt({
2535
2596
  root: n,
2536
2597
  video: t,
2537
2598
  player: this,
@@ -2561,7 +2622,7 @@ var Ft = class {
2561
2622
  }
2562
2623
  startStats() {
2563
2624
  this.stopStats();
2564
- let e = Math.max(500, this.opts.statsIntervalMs ?? Mt);
2625
+ let e = Math.max(500, this.opts.statsIntervalMs ?? Ft);
2565
2626
  this.statsTimer = setInterval(() => {
2566
2627
  if (!this.engine) return;
2567
2628
  let e = this.engine.getStats();
@@ -2573,7 +2634,7 @@ var Ft = class {
2573
2634
  }
2574
2635
  scheduleLicenseRefresh() {
2575
2636
  this.stopRefresh();
2576
- let e = this.opts.licenseRefreshMs ?? Nt;
2637
+ let e = this.opts.licenseRefreshMs ?? It;
2577
2638
  e && this._descriptor?.drmEnabled && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2578
2639
  }
2579
2640
  stopRefresh() {
@@ -2596,7 +2657,10 @@ var Ft = class {
2596
2657
  async play() {
2597
2658
  if (this.destroyed) return;
2598
2659
  let e = !this.playRequested;
2599
- this.playRequested = !0;
2660
+ if (this.playRequested = !0, this.idleClosed && this._state !== "ended" && this._state !== "error") {
2661
+ this.idleClosed = !1, await this.reload();
2662
+ return;
2663
+ }
2600
2664
  let t = this.pendingStartLoad && this._state !== "ended" && this._state !== "error";
2601
2665
  this.opts.autoplay === !1 && this.analyticsSession?.notePlayIntent(), t && (this.pendingStartLoad = !1, this.setState("loading"), this.engine?.startLoad());
2602
2666
  try {
@@ -2725,7 +2789,7 @@ function $(e) {
2725
2789
  }
2726
2790
  //#endregion
2727
2791
  //#region src/embed/host.ts
2728
- function It(e) {
2792
+ function zt(e) {
2729
2793
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2730
2794
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2731
2795
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2784,8 +2848,8 @@ function It(e) {
2784
2848
  }
2785
2849
  //#endregion
2786
2850
  //#region src/embed/page.ts
2787
- var Lt = /^[A-Za-z0-9:_.-]{1,128}$/;
2788
- function Rt() {
2851
+ var Bt = /^[A-Za-z0-9:_.-]{1,128}$/;
2852
+ function Vt() {
2789
2853
  let e = window.HAVIK_EMBED_CONFIG;
2790
2854
  if (e) return {
2791
2855
  baseUrl: e.baseUrl ?? "",
@@ -2807,9 +2871,9 @@ function Rt() {
2807
2871
  waitForLive: !0
2808
2872
  };
2809
2873
  }
2810
- async function zt() {
2811
- let e = Rt();
2812
- Vt();
2874
+ async function Ht() {
2875
+ let e = Vt();
2876
+ Wt();
2813
2877
  let t = document.createElement("video");
2814
2878
  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);
2815
2879
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2864,12 +2928,12 @@ async function zt() {
2864
2928
  code: e.code,
2865
2929
  message: e.message
2866
2930
  })), e.on("autoplayblocked", () => {
2867
- i({ type: "oddin:autoplayblocked" }), Bt(t, e);
2931
+ i({ type: "oddin:autoplayblocked" }), Ut(t, e);
2868
2932
  });
2869
2933
  }, s = async (r) => {
2870
2934
  a?.destroy(), a = null;
2871
2935
  try {
2872
- let n = await Pt({
2936
+ let n = await Lt({
2873
2937
  video: t,
2874
2938
  baseUrl: e.baseUrl,
2875
2939
  matchUrn: r,
@@ -2930,7 +2994,7 @@ async function zt() {
2930
2994
  a?.retry();
2931
2995
  break;
2932
2996
  case "oddin:load":
2933
- typeof e.matchUrn == "string" && Lt.test(e.matchUrn) && s(e.matchUrn);
2997
+ typeof e.matchUrn == "string" && Bt.test(e.matchUrn) && s(e.matchUrn);
2934
2998
  break;
2935
2999
  case "oddin:destroy": c();
2936
3000
  }
@@ -2942,18 +3006,18 @@ async function zt() {
2942
3006
  protocol: 1
2943
3007
  });
2944
3008
  }
2945
- function Bt(e, t) {
3009
+ function Ut(e, t) {
2946
3010
  if (document.getElementById("havik-tap")) return;
2947
3011
  let n = document.createElement("button");
2948
3012
  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", () => {
2949
3013
  n.remove(), t.play();
2950
3014
  }), document.body.appendChild(n);
2951
3015
  }
2952
- function Vt() {
3016
+ function Wt() {
2953
3017
  let e = document.createElement("style");
2954
3018
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2955
3019
  }
2956
3020
  //#endregion
2957
- export { J as ODDIN_THEME, n as PlaybackError, X as applyTheme, zt as bootEmbed, _e as clearDeviceId, Pt as createPlayer, D as deriveEventsBaseUrl, Oe as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, ue as fetchCatalog, fe as fetchTournament, me as flattenMatches, A as getDeviceId, Ae as isLowLatencyManifest, $ as isOddinMessage, ye as licenseRequestHeaders, It as mountEmbed, oe as pollToLive, y as resolveStream, E as subscribeLiveState, C as videoFullscreenActive, he as watchStatus };
3021
+ export { J as ODDIN_THEME, n as PlaybackError, X as applyTheme, Ht as bootEmbed, _e as clearDeviceId, Lt as createPlayer, D as deriveEventsBaseUrl, Oe as detectDrmSupport, w as enterVideoFullscreen, T as exitVideoFullscreen, ue as fetchCatalog, fe as fetchTournament, me as flattenMatches, A as getDeviceId, Ae as isLowLatencyManifest, $ as isOddinMessage, ye as licenseRequestHeaders, zt as mountEmbed, oe as pollToLive, y as resolveStream, E as subscribeLiveState, C as videoFullscreenActive, he as watchStatus };
2958
3022
 
2959
3023
  //# sourceMappingURL=index.js.map