@oddin-gg/havik-player 2.0.4 → 2.1.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
@@ -616,11 +616,15 @@ function Ce(e) {
616
616
  }
617
617
  //#endregion
618
618
  //#region src/core/stats.ts
619
- var F = { droppedFrames: 0 };
620
- function I(e) {
621
- return e.getVideoPlaybackQuality?.()?.droppedVideoFrames ?? 0;
619
+ var we = { droppedFrames: 0 };
620
+ function F(e) {
621
+ let t = e.getVideoPlaybackQuality?.();
622
+ return {
623
+ dropped: t?.droppedVideoFrames ?? 0,
624
+ decoded: t?.totalVideoFrames ?? 0
625
+ };
622
626
  }
623
- function we(e, t) {
627
+ function Te(e, t) {
624
628
  let n = [
625
629
  ["playing", () => t("playing")],
626
630
  ["waiting", () => t("buffering")],
@@ -637,7 +641,7 @@ function we(e, t) {
637
641
  }
638
642
  //#endregion
639
643
  //#region src/core/engine/native.ts
640
- function Te(e) {
644
+ function Ee(e) {
641
645
  try {
642
646
  let t = new TextDecoder("utf-8").decode(new Uint8Array(e));
643
647
  return /^skd:\/\/[0-9a-fA-F-]{36}:([0-9a-fA-F]{32})$/.exec(t)?.[1];
@@ -645,15 +649,15 @@ function Te(e) {
645
649
  return;
646
650
  }
647
651
  }
648
- function L(e) {
652
+ function I(e) {
649
653
  return !!(e ?? (typeof document < "u" ? document.createElement("video") : void 0))?.canPlayType("application/vnd.apple.mpegurl");
650
654
  }
651
- function R() {
655
+ function L() {
652
656
  if (typeof window > "u") return !1;
653
657
  let e = "WebKitMediaKeys" in window || typeof HTMLMediaElement < "u" && "webkitSetMediaKeys" in HTMLMediaElement.prototype, t = typeof navigator < "u" && /Apple/i.test(navigator.vendor ?? "");
654
658
  return e || t;
655
659
  }
656
- var z = class {
660
+ var R = class {
657
661
  name = "native";
658
662
  deps;
659
663
  onLoaded;
@@ -687,7 +691,7 @@ var z = class {
687
691
  type: "error",
688
692
  error: new n("INTERNAL", 0, "native HLS playback error"),
689
693
  fatal: !0
690
- }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = Ee(t.manifestUrl, t.analytics?.sid), e.load();
694
+ }), this.onLoaded && e.addEventListener("loadedmetadata", this.onLoaded, { once: !0 }), e.addEventListener("error", this.onError), e.src = De(t.manifestUrl, t.analytics?.sid), e.load();
691
695
  }
692
696
  startLoad() {
693
697
  this.loadStarted || (this.loadStarted = !0, this.restorePreload());
@@ -709,7 +713,7 @@ var z = class {
709
713
  });
710
714
  this.onEncrypted = (n) => {
711
715
  let i = n;
712
- !i.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = Te(i.initData), (async () => {
716
+ !i.initData || this.fairplayArmed || (this.fairplayArmed = !0, this.fairplayKeyIV = Ee(i.initData), (async () => {
713
717
  try {
714
718
  let n = await (await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
715
719
  initDataTypes: [i.initDataType],
@@ -804,8 +808,11 @@ var z = class {
804
808
  e.seekable.length > 0 && (e.currentTime = e.seekable.end(e.seekable.length - 1));
805
809
  }
806
810
  getStats() {
807
- let e = this.deps.video, t = { droppedFrames: I(e) };
808
- return e.duration === Infinity && (t.isLive = !0, e.seekable.length > 0 && (t.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), t;
811
+ let e = this.deps.video, t = F(e), n = {
812
+ droppedFrames: t.dropped,
813
+ decodedFrames: t.decoded
814
+ };
815
+ return this.deps.descriptor.drmEnabled && (n.keySystem = "fairplay"), e.duration === Infinity && (n.isLive = !0, e.seekable.length > 0 && (n.atLiveEdge = e.currentTime >= e.seekable.end(e.seekable.length - 1) - 2)), n;
809
816
  }
810
817
  stop() {
811
818
  try {
@@ -824,29 +831,36 @@ var z = class {
824
831
  } catch {}
825
832
  }
826
833
  };
827
- function Ee(e, t) {
834
+ function De(e, t) {
828
835
  return t ? `${e}${e.includes("?") ? "&" : "?"}CMCD=${encodeURIComponent(`sid="${t}"`)}` : e;
829
836
  }
830
837
  //#endregion
831
838
  //#region src/core/engine/hls.ts
832
- function De() {
839
+ function Oe() {
833
840
  return e.isSupported();
834
841
  }
835
- var Oe = 3, ke = 5, Ae = 3, je = 3e4, Me = 12e3, Ne = 3e3, B = 6e3, Pe = 4e3, Fe = 2, Ie = 5, Le = 2, Re = 2, ze = 2e3, Be = 3e4, Ve = 2, He = 9, Ue = .5;
836
- function We(e) {
842
+ var ke = 3, Ae = 5, je = 3, Me = 3e4, Ne = 12e3, Pe = 3e3, z = 6e3, Fe = 4e3, Ie = 2, Le = 5, Re = 2, ze = 2, Be = 2e3, Ve = 3e4, He = 2, Ue = 9, We = .5, Ge = 64;
843
+ function Ke(e) {
844
+ try {
845
+ return new URL(e).host;
846
+ } catch {
847
+ return "";
848
+ }
849
+ }
850
+ function qe(e) {
837
851
  if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
838
852
  delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
839
853
  return;
840
854
  }
841
- e.liveSyncDuration ??= Ve;
842
- let t = Math.max(12, e.liveSyncDuration + He);
843
- (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Ge(e, e.liveSyncDuration);
855
+ e.liveSyncDuration ??= He;
856
+ let t = Math.max(12, e.liveSyncDuration + Ue);
857
+ (e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Je(e, e.liveSyncDuration);
844
858
  }
845
- function Ge(e, t) {
859
+ function Je(e, t) {
846
860
  let n = (e, t, n) => Math.min(n, Math.max(t, e));
847
861
  e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
848
862
  }
849
- var V = class {
863
+ var B = class {
850
864
  name = "hls";
851
865
  hls = null;
852
866
  deps;
@@ -856,6 +870,16 @@ var V = class {
856
870
  playlistBytesLoaded = 0;
857
871
  renditionSwitches = 0;
858
872
  licenseTimeMs = 0;
873
+ licenseRequested = !1;
874
+ upshifts = 0;
875
+ downshifts = 0;
876
+ lastLevelIndex = -1;
877
+ requestCount = 0;
878
+ requestErrors = 0;
879
+ requestTimesMs = [];
880
+ cdnHost = "";
881
+ ladderHeights;
882
+ ladderTopBitrateKbps = 0;
859
883
  mediaRecoveries = 0;
860
884
  networkRecoveries = 0;
861
885
  licenseRecoveries = 0;
@@ -890,7 +914,7 @@ var V = class {
890
914
  this.deps = e, this.licenseUrl = e.descriptor.drm?.widevine?.licenseUrl, this.loadStarted = e.deferLoad !== !0;
891
915
  }
892
916
  async load() {
893
- let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = L(r) && R(), o = !!e.drm?.widevine?.licenseUrl && !a;
917
+ let { descriptor: e, snapToLiveOnRefocus: t, video: r, emit: i } = this.deps, a = I(r) && L(), o = !!e.drm?.widevine?.licenseUrl && !a;
894
918
  if (e.drmEnabled && !o) {
895
919
  i({
896
920
  type: "error",
@@ -930,7 +954,7 @@ var V = class {
930
954
  let t = e.liveSyncPosition;
931
955
  if (typeof t != "number" || !Number.isFinite(t)) return;
932
956
  let { video: n } = this.deps;
933
- n.currentTime <= t + Ue || (n.currentTime = t);
957
+ n.currentTime <= t + We || (n.currentTime = t);
934
958
  }
935
959
  startHls() {
936
960
  let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
@@ -955,7 +979,7 @@ var V = class {
955
979
  audioEncryptionScheme: "cbcs"
956
980
  },
957
981
  licenseXhrSetup: (e, t) => {
958
- e.open("POST", this.licenseUrl ?? t, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a);
982
+ e.open("POST", this.licenseUrl ?? t, !0), e.setRequestHeader("x-api-key", r.apiKey), e.setRequestHeader("X-Match-Urn", n.matchUrn), e.setRequestHeader("X-Device-Id", i), a && e.setRequestHeader("X-User-Id", a), this.licenseRequested = !0;
959
983
  let o = performance.now();
960
984
  e.addEventListener("loadend", () => {
961
985
  this.licenseTimeMs = Math.max(1, Math.round(performance.now() - o));
@@ -969,19 +993,28 @@ var V = class {
969
993
  } } : {},
970
994
  ...c
971
995
  };
972
- We(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
996
+ qe(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0, this.loadStarted = d.autoStartLoad !== !1;
973
997
  let f = new e(d);
974
998
  this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
975
- this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
999
+ this.mediaRecoveries = 0, this.networkRecoveries = 0;
1000
+ let e = f.levels.map((e) => e.height).filter((e) => !!e);
1001
+ 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" });
976
1002
  }), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
977
1003
  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(() => {
978
1004
  this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.starvationRebuilds = 0, this.recoveryResetTimer = null;
979
- }, je)));
1005
+ }, Me)));
980
1006
  }), f.on(e.Events.FRAG_LOADING, (e, t) => {
981
1007
  t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
982
1008
  }), f.on(e.Events.FRAG_LOADED, (e, t) => {
983
- let n = t?.frag?.stats?.loaded;
984
- typeof n == "number" && (this.bytesLoaded += n);
1009
+ let n = t?.frag?.stats, r = n?.loaded;
1010
+ typeof r == "number" && (this.bytesLoaded += r);
1011
+ let i = n?.loading;
1012
+ 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 > Ge && this.requestTimesMs.shift());
1013
+ let a = t?.frag?.url;
1014
+ if (a) {
1015
+ let e = Ke(a);
1016
+ e && (this.cdnHost = e);
1017
+ }
985
1018
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => {
986
1019
  let n = t?.stats?.loaded;
987
1020
  if (typeof n == "number" && (this.playlistBytesLoaded += n), this.detectedLowLatency) return;
@@ -992,8 +1025,10 @@ var V = class {
992
1025
  }));
993
1026
  }), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog && clearInterval(this.endWatchdog), this.endWatchdog = setInterval(() => {
994
1027
  this.checkEndStale(), this.checkStarvation();
995
- }, Ne), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
996
- this.renditionSwitches += 1, l({
1028
+ }, Pe), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
1029
+ this.renditionSwitches += 1;
1030
+ let n = this.lastLevelIndex >= 0 ? f.levels[this.lastLevelIndex] : void 0, r = f.levels[t.level];
1031
+ n && r && r.bitrate !== n.bitrate && (r.bitrate > n.bitrate ? this.upshifts += 1 : this.downshifts += 1), this.lastLevelIndex = t.level, l({
997
1032
  type: "qualitychange",
998
1033
  index: t.level
999
1034
  });
@@ -1054,7 +1089,7 @@ var V = class {
1054
1089
  return;
1055
1090
  }
1056
1091
  this.capTimer && clearTimeout(this.capTimer);
1057
- let t = e > this.sizeCapLevel ? ze : Be;
1092
+ let t = e > this.sizeCapLevel ? Be : Ve;
1058
1093
  this.capTimer = setTimeout(() => {
1059
1094
  this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
1060
1095
  }, t);
@@ -1101,20 +1136,28 @@ var V = class {
1101
1136
  e.widevine && (this.licenseUrl = e.widevine);
1102
1137
  }
1103
1138
  getStats() {
1104
- let e = this.hls, t = {
1105
- droppedFrames: I(this.deps.video),
1139
+ let e = this.hls, t = F(this.deps.video), n = {
1140
+ droppedFrames: t.dropped,
1141
+ decodedFrames: t.decoded,
1106
1142
  lowLatency: this.detectedLowLatency,
1107
1143
  bytesLoaded: this.bytesLoaded,
1108
1144
  playlistBytesLoaded: this.playlistBytesLoaded,
1109
1145
  renditionSwitches: this.renditionSwitches,
1110
- licenseTimeMs: this.licenseTimeMs
1146
+ licenseTimeMs: this.licenseTimeMs,
1147
+ upshifts: this.upshifts,
1148
+ downshifts: this.downshifts,
1149
+ requestCount: this.requestCount,
1150
+ requestErrors: this.requestErrors,
1151
+ requestTimesMs: [...this.requestTimesMs],
1152
+ ladderHeights: this.ladderHeights,
1153
+ ladderTopBitrateKbps: this.ladderTopBitrateKbps || void 0
1111
1154
  };
1112
- if (e) {
1113
- typeof e.latency == "number" && (t.latencySeconds = e.latency), typeof e.targetLatency == "number" && e.targetLatency > 0 && (t.targetLatencySeconds = e.targetLatency), typeof e.bandwidthEstimate == "number" && (t.bandwidthKbps = Math.round(e.bandwidthEstimate / 1e3));
1114
- let n = e.currentLevel >= 0 ? e.levels[e.currentLevel] : void 0;
1115
- n && (t.levelHeight = n.height), n && typeof n.bitrate == "number" && (t.renditionBitrateKbps = Math.round(n.bitrate / 1e3)), typeof n?.details?.live == "boolean" && (t.isLive = n.details.live, typeof e.liveSyncPosition == "number" && (t.atLiveEdge = this.deps.video.currentTime >= e.liveSyncPosition - 1.5));
1155
+ if (this.cdnHost && (n.cdnHost = this.cdnHost), this.licenseRequested && (n.keySystem = "widevine", n.drmSecurityLevel = "L3"), e) {
1156
+ n.qualityPinned = !e.autoLevelEnabled, typeof e.latency == "number" && (n.latencySeconds = e.latency), typeof e.targetLatency == "number" && e.targetLatency > 0 && (n.targetLatencySeconds = e.targetLatency), typeof e.bandwidthEstimate == "number" && (n.bandwidthKbps = Math.round(e.bandwidthEstimate / 1e3));
1157
+ let t = e.currentLevel >= 0 ? e.levels[e.currentLevel] : void 0;
1158
+ t && (n.levelHeight = t.height), t?.videoCodec && (n.videoCodec = t.videoCodec), t?.audioCodec && (n.audioCodec = t.audioCodec), t && typeof t.bitrate == "number" && (n.renditionBitrateKbps = Math.round(t.bitrate / 1e3)), typeof t?.details?.live == "boolean" && (n.isLive = t.details.live, typeof e.liveSyncPosition == "number" && (n.atLiveEdge = this.deps.video.currentTime >= e.liveSyncPosition - 1.5));
1116
1159
  }
1117
- return t;
1160
+ return n;
1118
1161
  }
1119
1162
  trackEnd(e) {
1120
1163
  let t = e.details;
@@ -1135,7 +1178,7 @@ var V = class {
1135
1178
  return;
1136
1179
  }
1137
1180
  let e = Date.now() - this.lastAdvanceMs;
1138
- !this.chaseSuspended && e > B && this.suspendChasing(), e > Me && this.emitEnded("stalled");
1181
+ !this.chaseSuspended && e > z && this.suspendChasing(), e > Ne && this.emitEnded("stalled");
1139
1182
  }
1140
1183
  checkStarvation() {
1141
1184
  let e = this.hls, { video: t } = this.deps;
@@ -1144,17 +1187,17 @@ var V = class {
1144
1187
  this.starvedTicks = 0;
1145
1188
  return;
1146
1189
  }
1147
- let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < B, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Pe;
1190
+ let n = this.lastAdvanceMs !== 0 && Date.now() - this.lastAdvanceMs < z, r = this.lastFragLoadingMs !== 0 && Date.now() - this.lastFragLoadingMs > Fe;
1148
1191
  if (!(t.readyState < HTMLMediaElement.HAVE_FUTURE_DATA && !t.paused && n && r)) {
1149
1192
  this.starvedTicks = 0;
1150
1193
  return;
1151
1194
  }
1152
- if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Fe)) {
1153
- if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Le && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < Re)) {
1195
+ if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Ie)) {
1196
+ if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Re && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < ze)) {
1154
1197
  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");
1155
1198
  return;
1156
1199
  }
1157
- if (!(this.starvationKicks >= Ie)) {
1200
+ if (!(this.starvationKicks >= Le)) {
1158
1201
  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");
1159
1202
  try {
1160
1203
  e.stopLoad(), e.startLoad(-1);
@@ -1204,7 +1247,7 @@ var V = class {
1204
1247
  scheduleLicenseRetry(t) {
1205
1248
  if (t.details !== e.ErrorDetails.KEY_SYSTEM_LICENSE_REQUEST_FAILED && t.details !== e.ErrorDetails.KEY_SYSTEM_SERVER_CERTIFICATE_REQUEST_FAILED) return !1;
1206
1249
  let n = t.response?.code ?? 0;
1207
- if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= Ae) return !1;
1250
+ if (!(n === 0 || n === 408 || n === 429 || n >= 500) || this.licenseRecoveries >= je) return !1;
1208
1251
  this.licenseRecoveries += 1;
1209
1252
  let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
1210
1253
  return this.licenseRetryTimer = setTimeout(() => {
@@ -1222,7 +1265,7 @@ var V = class {
1222
1265
  }
1223
1266
  }
1224
1267
  onError(t) {
1225
- if (!t.fatal) return;
1268
+ if (t.type === e.ErrorTypes.NETWORK_ERROR && String(t.details ?? "").startsWith("frag") && (this.requestErrors += 1), !t.fatal) return;
1226
1269
  let n = this.hls;
1227
1270
  if (n) {
1228
1271
  if (this.clearRecoveryReset(), t.type === e.ErrorTypes.KEY_SYSTEM_ERROR) {
@@ -1230,12 +1273,13 @@ var V = class {
1230
1273
  this.deps.emit({
1231
1274
  type: "error",
1232
1275
  error: this.toError(t, "DRM"),
1233
- fatal: !0
1276
+ fatal: !0,
1277
+ detail: t.details
1234
1278
  });
1235
1279
  return;
1236
1280
  }
1237
1281
  if (t.type === e.ErrorTypes.NETWORK_ERROR) {
1238
- if (this.networkRecoveries < ke) {
1282
+ if (this.networkRecoveries < Ae) {
1239
1283
  this.networkRecoveries += 1;
1240
1284
  let e = Math.min(8e3, 1e3 * 2 ** (this.networkRecoveries - 1));
1241
1285
  this.networkRetryTimer = setTimeout(() => {
@@ -1246,26 +1290,29 @@ var V = class {
1246
1290
  this.deps.emit({
1247
1291
  type: "error",
1248
1292
  error: this.toError(t, "NETWORK"),
1249
- fatal: !0
1293
+ fatal: !0,
1294
+ detail: t.details
1250
1295
  });
1251
1296
  return;
1252
1297
  }
1253
1298
  if (t.type === e.ErrorTypes.MEDIA_ERROR) {
1254
- if (this.mediaRecoveries < Oe) {
1299
+ if (this.mediaRecoveries < ke) {
1255
1300
  this.mediaRecoveries += 1, n.recoverMediaError();
1256
1301
  return;
1257
1302
  }
1258
1303
  this.deps.emit({
1259
1304
  type: "error",
1260
1305
  error: this.toError(t),
1261
- fatal: !0
1306
+ fatal: !0,
1307
+ detail: t.details
1262
1308
  });
1263
1309
  return;
1264
1310
  }
1265
1311
  this.deps.emit({
1266
1312
  type: "error",
1267
1313
  error: this.toError(t),
1268
- fatal: !0
1314
+ fatal: !0,
1315
+ detail: t.details
1269
1316
  });
1270
1317
  }
1271
1318
  }
@@ -1273,7 +1320,29 @@ var V = class {
1273
1320
  let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
1274
1321
  return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
1275
1322
  }
1276
- }, Ke = 15e3, qe = 100, H = 1, U = {
1323
+ }, Ye = 15e3, Xe = 100, V = 1, Ze = 1e3, H = 3e4, Qe = {
1324
+ inline: "PRESENTATION_INLINE",
1325
+ fullscreen: "PRESENTATION_FULLSCREEN",
1326
+ pip: "PRESENTATION_PIP"
1327
+ }, $e = {
1328
+ widevine: "KEY_SYSTEM_WIDEVINE",
1329
+ fairplay: "KEY_SYSTEM_FAIRPLAY",
1330
+ none: "KEY_SYSTEM_NONE"
1331
+ };
1332
+ function U(e, t) {
1333
+ return e.length === 0 ? 0 : e[Math.min(e.length, Math.max(1, Math.ceil(t / 100 * e.length))) - 1] ?? 0;
1334
+ }
1335
+ var et = /* @__PURE__ */ new Set([
1336
+ "INVALID_URN",
1337
+ "NOT_FOUND",
1338
+ "GONE",
1339
+ "TOO_EARLY",
1340
+ "UNAVAILABLE",
1341
+ "UNAUTHORIZED",
1342
+ "FORBIDDEN",
1343
+ "RATE_LIMITED",
1344
+ "TIMEOUT"
1345
+ ]), W = {
1277
1346
  idle: "PLAYER_STATE_IDLE",
1278
1347
  waiting: "PLAYER_STATE_WAITING",
1279
1348
  loading: "PLAYER_STATE_LOADING",
@@ -1283,7 +1352,7 @@ var V = class {
1283
1352
  ended: "PLAYER_STATE_ENDED",
1284
1353
  error: "PLAYER_STATE_ERROR"
1285
1354
  };
1286
- function Je(e) {
1355
+ function tt(e) {
1287
1356
  try {
1288
1357
  let t = new URL(e), [n, ...r] = t.hostname.split(".");
1289
1358
  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);
@@ -1291,7 +1360,7 @@ function Je(e) {
1291
1360
  return;
1292
1361
  }
1293
1362
  }
1294
- var Ye = class {
1363
+ var nt = class {
1295
1364
  opts;
1296
1365
  url;
1297
1366
  now;
@@ -1315,50 +1384,133 @@ var Ye = class {
1315
1384
  errorCount = 0;
1316
1385
  seekCount = 0;
1317
1386
  pauseCount = 0;
1387
+ isPlaying = !1;
1388
+ isHidden = !1;
1389
+ isMuted = !1;
1390
+ activeSinceMs = null;
1391
+ activeTotalMs = 0;
1392
+ mutedSinceMs = null;
1393
+ mutedTotalMs = 0;
1394
+ lastHbMutedMs = 0;
1395
+ volumeChanges = 0;
1396
+ muteChanges = 0;
1397
+ presentation = "inline";
1398
+ presentationSinceMs;
1399
+ fullscreenTotalMs = 0;
1400
+ pipTotalMs = 0;
1401
+ waitingSinceMs = null;
1402
+ waitingTotalMs = 0;
1403
+ firstAudioMs = 0;
1404
+ timeToFirstStallMs = 0;
1405
+ lastStallMs = 0;
1406
+ lastErrorMs = 0;
1407
+ lastSeekMs = 0;
1408
+ lastErrorStage = null;
1409
+ sawFirstBytes = !1;
1410
+ mediaInfoSignature = "";
1411
+ lastUpshifts = 0;
1412
+ lastDownshifts = 0;
1413
+ lastDecoded = 0;
1414
+ lastRequestCount = 0;
1415
+ lastRequestErrors = 0;
1416
+ pendingStart = null;
1417
+ firstFlushTimer = null;
1318
1418
  bornMs;
1319
1419
  startupTimeMs = 0;
1320
1420
  manifestLoadMs = 0;
1321
1421
  firstFragLoadMs = 0;
1322
1422
  onPagehide = () => this.end("END_REASON_PAGEHIDE");
1323
1423
  onVisibility = () => {
1324
- typeof document > "u" || (document.visibilityState === "hidden" ? this.hiddenSinceMs === null && (this.hiddenSinceMs = this.now()) : this.hiddenSinceMs !== null && (this.hiddenTotalMs += this.now() - this.hiddenSinceMs, this.hiddenSinceMs = null));
1424
+ 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());
1325
1425
  };
1326
1426
  constructor(e) {
1327
1427
  this.opts = {
1328
1428
  ...e,
1329
- heartbeatMs: e.heartbeatMs ?? Ke
1330
- }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
1429
+ heartbeatMs: e.heartbeatMs ?? Ye
1430
+ }, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now(), this.presentationSinceMs = this.bornMs;
1331
1431
  }
1332
1432
  start() {
1333
- this.endedFlag || (this.enqueue({ sessionStart: {
1433
+ if (this.endedFlag) return;
1434
+ let e = {
1334
1435
  player: this.opts.player,
1335
- sdkVersion: this.opts.sdkVersion
1336
- } }), this.flush(), 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()));
1436
+ sdkVersion: this.opts.sdkVersion,
1437
+ ...this.opts.environment
1438
+ };
1439
+ this.pendingStart = e, this.enqueue({ sessionStart: e }), this.firstFlushTimer = setTimeout(() => this.flush(), Ze), 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());
1337
1440
  }
1338
1441
  noteStateChange(e, t, n, r) {
1339
1442
  if (this.endedFlag || e === t) return;
1340
1443
  let i = this.now();
1341
- 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);
1444
+ t === "playing" ? (this.playingSinceMs === null && (this.playingSinceMs = i), this.startupTimeMs === 0 && (this.startupTimeMs = Math.max(V, i - this.bornMs)), this.firstAudioMs === 0 && !this.isMuted && (this.firstAudioMs = Math.max(V, i - this.bornMs)), this.isMuted && this.mutedSinceMs === null && (this.mutedSinceMs = i)) : this.playingSinceMs !== null && (this.playingTotalMs += i - this.playingSinceMs, this.playingSinceMs = null, this.mutedSinceMs !== null && (this.mutedTotalMs += i - this.mutedSinceMs, this.mutedSinceMs = null)), this.isPlaying = t === "playing", this.syncActive(), t === "waiting" ? this.waitingSinceMs = i : this.waitingSinceMs !== null && (this.waitingTotalMs += i - this.waitingSinceMs, this.waitingSinceMs = null), t === "buffering" && e === "playing" ? (this.rebufferCount += 1, this.bufferingSinceMs = i, this.lastStallMs = i, this.timeToFirstStallMs === 0 && (this.timeToFirstStallMs = Math.max(V, Math.round(this.playingTotalMs)))) : e === "buffering" && this.bufferingSinceMs !== null && (this.rebufferTotalMs += i - this.bufferingSinceMs, this.bufferingSinceMs = null), t === "paused" && (this.pauseCount += 1);
1342
1445
  let a = {
1343
- fromState: U[e] ?? "PLAYER_STATE_UNSPECIFIED",
1344
- toState: U[t] ?? "PLAYER_STATE_UNSPECIFIED"
1345
- };
1346
- n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1446
+ fromState: W[e] ?? "PLAYER_STATE_UNSPECIFIED",
1447
+ toState: W[t] ?? "PLAYER_STATE_UNSPECIFIED",
1448
+ presentation: Qe[this.presentation]
1449
+ }, o = this.causeOf(e, t, i);
1450
+ o && (a.cause = o), n > 0 && (a.positionMs = Math.round(n)), r && (a.renditionHeight = r), this.enqueue({ stateChange: a });
1451
+ }
1452
+ causeOf(e, t, n) {
1453
+ if (t === "error") return "TRANSITION_CAUSE_ERROR";
1454
+ if (t === "buffering") return n - this.lastSeekMs < 1e3 ? "TRANSITION_CAUSE_SEEK" : "TRANSITION_CAUSE_NETWORK_STALL";
1455
+ if (t === "paused" || t === "playing" && e === "paused") return "TRANSITION_CAUSE_USER";
1456
+ }
1457
+ syncActive() {
1458
+ let e = this.isPlaying && !this.isHidden && !this.isMuted;
1459
+ e && this.activeSinceMs === null ? this.activeSinceMs = this.now() : !e && this.activeSinceMs !== null && (this.activeTotalMs += this.now() - this.activeSinceMs, this.activeSinceMs = null);
1460
+ }
1461
+ closePresentation() {
1462
+ let e = this.now(), t = e - this.presentationSinceMs;
1463
+ this.presentation === "fullscreen" ? this.fullscreenTotalMs += t : this.presentation === "pip" && (this.pipTotalMs += t), this.presentationSinceMs = e;
1464
+ }
1465
+ noteAutoplay(e) {
1466
+ this.pendingStart && (this.pendingStart.autoplay = e);
1467
+ }
1468
+ noteEnvironment(e) {
1469
+ this.pendingStart && Object.assign(this.pendingStart, e);
1470
+ }
1471
+ seedVolume(e, t) {
1472
+ this.endedFlag || (this.isMuted = e || t === 0, this.isMuted && this.isPlaying && this.mutedSinceMs === null && (this.mutedSinceMs = this.now()), this.syncActive());
1473
+ }
1474
+ noteVolume(e, t) {
1475
+ if (this.endedFlag) return;
1476
+ let n = e || t === 0;
1477
+ if (n === this.isMuted) {
1478
+ this.volumeChanges += 1;
1479
+ return;
1480
+ }
1481
+ this.muteChanges += 1;
1482
+ let r = this.now();
1483
+ n ? this.isPlaying && this.mutedSinceMs === null && (this.mutedSinceMs = r) : (this.mutedSinceMs !== null && (this.mutedTotalMs += r - this.mutedSinceMs, this.mutedSinceMs = null), this.firstAudioMs === 0 && this.isPlaying && (this.firstAudioMs = Math.max(V, r - this.bornMs))), this.isMuted = n, this.syncActive();
1484
+ }
1485
+ notePresentation(e) {
1486
+ this.endedFlag || e === this.presentation || (this.closePresentation(), this.presentation = e);
1347
1487
  }
1348
1488
  noteReady() {
1349
- this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(H, this.now() - this.bornMs));
1489
+ this.manifestLoadMs === 0 && (this.manifestLoadMs = Math.max(V, this.now() - this.bornMs));
1350
1490
  }
1351
1491
  noteFirstBytes() {
1352
- this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(H, this.now() - this.bornMs));
1492
+ this.sawFirstBytes = !0, this.firstFragLoadMs === 0 && (this.firstFragLoadMs = Math.max(V, this.now() - this.bornMs));
1353
1493
  }
1354
1494
  noteSeek() {
1355
- this.endedFlag || (this.seekCount += 1);
1495
+ this.endedFlag || (this.seekCount += 1, this.lastSeekMs = this.now());
1356
1496
  }
1357
- noteError(e, t, n, r, i) {
1497
+ noteError(e, t, n, r, i, a) {
1358
1498
  if (this.endedFlag) return;
1359
- this.errorCount += 1;
1360
- let a = { code: e };
1361
- t && (a.detail = t), n && (a.fatal = !0), r > 0 && (a.httpStatus = r), i > 0 && (a.positionMs = Math.round(i)), this.enqueue({ error: a }), this.flush();
1499
+ this.errorCount += 1, this.lastErrorMs = this.now();
1500
+ let o = { code: e };
1501
+ t && (o.detail = t), n && (o.fatal = !0), r > 0 && (o.httpStatus = r), i > 0 && (o.positionMs = Math.round(i));
1502
+ let s = this.stageOf(e, t, a);
1503
+ o.stage = s, this.lastErrorStage = s;
1504
+ let c = this.safeSample()?.cdnHost;
1505
+ c && (o.cdnHost = c), this.enqueue({ error: o }), this.flush();
1506
+ }
1507
+ stageOf(e, t, n) {
1508
+ let r = n ?? "";
1509
+ 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";
1510
+ if (e === "DRM_CLIENT") return "ERROR_STAGE_DRM";
1511
+ if (et.has(e)) return "ERROR_STAGE_RESOLVE";
1512
+ let i = t ?? "";
1513
+ 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";
1362
1514
  }
1363
1515
  noteEnded() {
1364
1516
  this.end("END_REASON_ENDED");
@@ -1382,25 +1534,51 @@ var Ye = class {
1382
1534
  let s = e.droppedFrames ?? 0;
1383
1535
  s > this.lastDropped && (i.droppedFramesDelta = s - this.lastDropped), this.lastDropped = s;
1384
1536
  let c = e.renditionSwitches ?? 0;
1385
- 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);
1386
- let l = this.bufferedMsFrom(e);
1387
- l > 0 && (i.bufferedMs = l), i.playerState = U[e.state] ?? "PLAYER_STATE_UNSPECIFIED", this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1537
+ 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), typeof e.bufferedMs == "number" && e.bufferedMs > 0 && (i.bufferedMs = Math.round(e.bufferedMs)), i.playerState = W[e.state] ?? "PLAYER_STATE_UNSPECIFIED";
1538
+ let l = this.mutedTotalMs;
1539
+ this.mutedSinceMs !== null && (l += t - this.mutedSinceMs), l > this.lastHbMutedMs && (i.mutedMsDelta = Math.round(l - this.lastHbMutedMs)), this.lastHbMutedMs = l;
1540
+ let u = e.upshifts ?? 0;
1541
+ u > this.lastUpshifts && (i.upshiftsDelta = u - this.lastUpshifts), this.lastUpshifts = u;
1542
+ let d = e.downshifts ?? 0;
1543
+ d > this.lastDownshifts && (i.downshiftsDelta = d - this.lastDownshifts), this.lastDownshifts = d;
1544
+ let f = e.decodedFrames ?? 0;
1545
+ f > this.lastDecoded && (i.decodedFramesDelta = f - this.lastDecoded), this.lastDecoded = f;
1546
+ let p = e.requestErrors ?? 0;
1547
+ p > this.lastRequestErrors && (i.requestErrorsDelta = p - this.lastRequestErrors), this.lastRequestErrors = p;
1548
+ let m = e.requestCount ?? 0, h = m - this.lastRequestCount;
1549
+ if (this.lastRequestCount = m, h > 0) {
1550
+ i.requestCountDelta = h;
1551
+ let t = e.requestTimesMs ?? [], n = t.slice(Math.max(0, t.length - h)).sort((e, t) => e - t);
1552
+ n.length > 0 && (i.requestTimeP50Ms = U(n, 50), i.requestTimeP95Ms = U(n, 95));
1553
+ }
1554
+ typeof e.latencySeconds == "number" && typeof e.targetLatencySeconds == "number" && (i.driftMs = Math.round((e.latencySeconds - e.targetLatencySeconds) * 1e3)), typeof e.playbackRate == "number" && e.playbackRate > 0 && (i.playbackRate = e.playbackRate), e.qualityPinned === !0 && (i.qualityPinned = !0), e.cdnHost && (i.cdnHost = e.cdnHost), this.maybeEmitMediaInfo(e), this.enqueue({ heartbeat: i }), this.flush(), a > 0 && this.noteFirstBytes();
1388
1555
  }
1389
- bufferedMsFrom(e) {
1390
- let t = e;
1391
- return typeof t.bufferedMs == "number" ? Math.round(t.bufferedMs) : 0;
1556
+ maybeEmitMediaInfo(e) {
1557
+ if (!(e.videoCodec ?? e.keySystem ?? e.ladderHeights)) return;
1558
+ let t = {};
1559
+ e.videoCodec && (t.videoCodec = e.videoCodec), e.audioCodec && (t.audioCodec = e.audioCodec), e.keySystem && (t.keySystem = $e[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);
1560
+ let n = JSON.stringify(t);
1561
+ n !== this.mediaInfoSignature && (this.mediaInfoSignature = n, this.enqueue({ mediaInfo: t }));
1392
1562
  }
1393
1563
  end(e) {
1394
1564
  if (this.endedFlag) return;
1395
1565
  this.endedFlag = !0;
1396
1566
  let t = this.now();
1397
- 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.timer &&= (clearInterval(this.timer), null), typeof window < "u" && window.removeEventListener("pagehide", this.onPagehide), typeof document < "u" && document.removeEventListener("visibilitychange", this.onVisibility);
1398
- let n = this.safeSample(), r = { reason: e }, i = n?.bytesLoaded ?? this.lastBytes;
1567
+ 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);
1568
+ let n = this.safeSample();
1569
+ n && this.maybeEmitMediaInfo(n);
1570
+ let r = { reason: e }, i = n?.bytesLoaded ?? this.lastBytes;
1399
1571
  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));
1400
1572
  let a = n?.licenseTimeMs ?? 0;
1401
1573
  a > 0 && (r.licenseTimeMs = Math.round(a));
1402
1574
  let o = n?.droppedFrames ?? this.lastDropped;
1403
- 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.pending.push(this.wrap({ sessionEnd: r })), this.flush(e === "END_REASON_PAGEHIDE");
1575
+ 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));
1576
+ let s = n?.positionMs ?? 0;
1577
+ 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));
1578
+ let c = e !== "END_REASON_ENDED";
1579
+ c && this.lastStallMs > 0 && t - this.lastStallMs <= H && (r.abandonAfterStallMs = Math.max(V, Math.round(t - this.lastStallMs))), c && this.lastErrorMs > 0 && t - this.lastErrorMs <= H && (r.abandonAfterErrorMs = Math.max(V, 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);
1580
+ let l = n?.decodedFrames ?? this.lastDecoded;
1581
+ 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");
1404
1582
  }
1405
1583
  safeSample() {
1406
1584
  try {
@@ -1418,13 +1596,13 @@ var Ye = class {
1418
1596
  };
1419
1597
  }
1420
1598
  enqueue(e) {
1421
- for (this.pending.push(this.wrap(e)); this.pending.length > qe;) {
1599
+ for (this.pending.push(this.wrap(e)); this.pending.length > Xe;) {
1422
1600
  let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
1423
1601
  this.pending.splice(e >= 0 ? e : 0, 1);
1424
1602
  }
1425
1603
  }
1426
1604
  flush(e = !1) {
1427
- if (this.pending.length === 0) return;
1605
+ if (this.firstFlushTimer &&= (clearTimeout(this.firstFlushTimer), null), this.pendingStart = null, this.pending.length === 0) return;
1428
1606
  let t = JSON.stringify({ beacons: this.pending });
1429
1607
  if (this.pending = [], e) {
1430
1608
  let e = this.opts.sendBeacon ?? (typeof navigator < "u" && navigator.sendBeacon ? navigator.sendBeacon.bind(navigator) : void 0);
@@ -1440,7 +1618,74 @@ var Ye = class {
1440
1618
  keepalive: !0
1441
1619
  }).catch(() => {});
1442
1620
  }
1443
- }, Xe = "2.0.4", W = {
1621
+ };
1622
+ //#endregion
1623
+ //#region src/core/environment.ts
1624
+ function rt() {
1625
+ if (typeof window > "u") return { integration: "INTEGRATION_KIND_SDK" };
1626
+ let e;
1627
+ try {
1628
+ e = window.top !== window.self;
1629
+ } catch {
1630
+ e = !0;
1631
+ }
1632
+ let t = window.location?.origin;
1633
+ if (!e) return {
1634
+ host: t && t !== "null" ? t : void 0,
1635
+ integration: "INTEGRATION_KIND_SDK"
1636
+ };
1637
+ let n;
1638
+ try {
1639
+ let e = window.location.ancestorOrigins;
1640
+ e && e.length > 0 && (n = e[e.length - 1] ?? void 0), !n && typeof document < "u" && document.referrer && (n = new URL(document.referrer).origin);
1641
+ } catch {
1642
+ n = void 0;
1643
+ }
1644
+ return n === "null" && (n = void 0), {
1645
+ host: n,
1646
+ integration: "INTEGRATION_KIND_IFRAME"
1647
+ };
1648
+ }
1649
+ function it() {
1650
+ if (typeof navigator > "u") return "DEVICE_CLASS_OTHER";
1651
+ let e = navigator.userAgentData, t = e?.platform ?? "";
1652
+ if (/tv|tizen|webos/i.test(t)) return "DEVICE_CLASS_TV";
1653
+ let n = typeof screen < "u" ? Math.min(screen.width, screen.height) : 0;
1654
+ 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";
1655
+ }
1656
+ function at() {
1657
+ let e = typeof navigator < "u" ? navigator.connection : void 0;
1658
+ if (!e) return { connectionType: "CONNECTION_TYPE_UNKNOWN" };
1659
+ let t = { connectionType: e.type ? {
1660
+ wifi: "CONNECTION_TYPE_WIFI",
1661
+ cellular: "CONNECTION_TYPE_CELLULAR",
1662
+ ethernet: "CONNECTION_TYPE_ETHERNET"
1663
+ }[e.type] ?? "CONNECTION_TYPE_OTHER" : "CONNECTION_TYPE_UNKNOWN" };
1664
+ return typeof e.downlink == "number" && e.downlink > 0 && (t.downlinkKbps = Math.round(e.downlink * 1e3)), e.saveData && (t.saveData = !0), t;
1665
+ }
1666
+ function ot(e) {
1667
+ let t = rt(), n = {
1668
+ deviceClass: it(),
1669
+ integration: t.integration,
1670
+ ...at()
1671
+ };
1672
+ 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);
1673
+ let r = e.getBoundingClientRect?.();
1674
+ return r && r.width > 0 && (n.viewportWidth = Math.round(r.width), n.viewportHeight = Math.round(r.height)), n;
1675
+ }
1676
+ async function st() {
1677
+ let e = typeof navigator < "u" ? navigator.userAgentData : void 0;
1678
+ if (!e?.getHighEntropyValues) return {};
1679
+ try {
1680
+ let t = await e.getHighEntropyValues(["model", "platformVersion"]), n = {};
1681
+ return t.model && (n.deviceModel = t.model), t.platformVersion && (n.osVersion = t.platformVersion), n;
1682
+ } catch {
1683
+ return {};
1684
+ }
1685
+ }
1686
+ //#endregion
1687
+ //#region src/core/version.ts
1688
+ var ct = "2.1.0", G = {
1444
1689
  accent: "#E1B600",
1445
1690
  accentText: "#1B1C23",
1446
1691
  background: "#0e0f13",
@@ -1451,7 +1696,7 @@ var Ye = class {
1451
1696
  border: "rgba(255, 255, 255, 0.1)",
1452
1697
  radius: "8px",
1453
1698
  fontFamily: "ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
1454
- }, G = {
1699
+ }, K = {
1455
1700
  accent: "--havik-accent",
1456
1701
  accentText: "--havik-accent-text",
1457
1702
  background: "--havik-bg",
@@ -1463,25 +1708,25 @@ var Ye = class {
1463
1708
  radius: "--havik-radius",
1464
1709
  fontFamily: "--havik-font"
1465
1710
  };
1466
- function K(e, t) {
1711
+ function q(e, t) {
1467
1712
  let n = {
1468
- ...W,
1713
+ ...G,
1469
1714
  ...t
1470
1715
  };
1471
- for (let t of Object.keys(G)) e.style.setProperty(G[t], n[t]);
1716
+ for (let t of Object.keys(K)) e.style.setProperty(K[t], n[t]);
1472
1717
  n.logoUrl && e.style.setProperty("--havik-logo", `url("${n.logoUrl}")`);
1473
1718
  }
1474
1719
  //#endregion
1475
1720
  //#region src/ui/styles.ts
1476
- var q = "havik-player-styles", Ze = "\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";
1477
- function Qe(e = document) {
1478
- if (e.getElementById(q)) return;
1721
+ var J = "havik-player-styles", lt = "\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";
1722
+ function ut(e = document) {
1723
+ if (e.getElementById(J)) return;
1479
1724
  let t = e.createElement("style");
1480
- t.id = q, t.textContent = Ze, e.head.appendChild(t);
1725
+ t.id = J, t.textContent = lt, e.head.appendChild(t);
1481
1726
  }
1482
1727
  //#endregion
1483
1728
  //#region src/ui/icons.ts
1484
- var $e = {
1729
+ var dt = {
1485
1730
  play: "<path d=\"M8 5v14l11-7z\"/>",
1486
1731
  pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
1487
1732
  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\"/>",
@@ -1494,21 +1739,21 @@ var $e = {
1494
1739
  liveDot: "<circle cx=\"12\" cy=\"12\" r=\"6\"/>",
1495
1740
  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\"/>"
1496
1741
  };
1497
- function J(e) {
1498
- return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${$e[e]}</svg>`;
1742
+ function Y(e) {
1743
+ return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${dt[e]}</svg>`;
1499
1744
  }
1500
1745
  //#endregion
1501
1746
  //#region src/ui/controls.ts
1502
- var et = 3e3, tt = /* @__PURE__ */ new Set([
1747
+ var ft = 3e3, pt = /* @__PURE__ */ new Set([
1503
1748
  "GONE",
1504
1749
  "NOT_FOUND",
1505
1750
  "INVALID_URN",
1506
1751
  "UNAUTHORIZED"
1507
1752
  ]);
1508
- function nt(e) {
1509
- return new rt(e);
1753
+ function mt(e) {
1754
+ return new ht(e);
1510
1755
  }
1511
- var rt = class {
1756
+ var ht = class {
1512
1757
  root;
1513
1758
  video;
1514
1759
  player;
@@ -1550,7 +1795,7 @@ var rt = class {
1550
1795
  scrubbing = !1;
1551
1796
  offFns = [];
1552
1797
  constructor(e) {
1553
- this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? et, 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, Qe(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");
1798
+ this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? ft, 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, ut(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");
1554
1799
  let t = (e, t, n) => {
1555
1800
  let r = this.doc.createElement(e);
1556
1801
  if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
@@ -1561,12 +1806,12 @@ var rt = class {
1561
1806
  "aria-label": r,
1562
1807
  title: r
1563
1808
  });
1564
- return i.innerHTML = J(n), i;
1809
+ return i.innerHTML = Y(n), i;
1565
1810
  };
1566
1811
  this.overlay = t("div", "havik-overlay"), this.center = t("button", "havik-center", {
1567
1812
  type: "button",
1568
1813
  "aria-label": "Play"
1569
- }), 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", {
1814
+ }), 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", {
1570
1815
  role: "slider",
1571
1816
  "aria-label": "Seek",
1572
1817
  "aria-valuemin": "0",
@@ -1584,7 +1829,7 @@ var rt = class {
1584
1829
  "aria-label": "Volume"
1585
1830
  });
1586
1831
  let a = t("div", "havik-vol");
1587
- a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = J("liveDot");
1832
+ a.append(this.muteBtn, this.volSlider), this.live = t("div", "havik-live"), this.live.innerHTML = Y("liveDot");
1588
1833
  let o = this.doc.createElement("span");
1589
1834
  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";
1590
1835
  let s = t("div", "havik-spacer");
@@ -1638,12 +1883,12 @@ var rt = class {
1638
1883
  }
1639
1884
  if (e === "preplay") {
1640
1885
  let e = this.doc.createElement("button");
1641
- 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()), n.append(e);
1886
+ 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()), n.append(e);
1642
1887
  } else {
1643
1888
  let r = this.doc.createElement("div");
1644
- if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !tt.has(this.lastError.code)) {
1889
+ if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !pt.has(this.lastError.code)) {
1645
1890
  let e = this.doc.createElement("button");
1646
- e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${J("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
1891
+ e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Y("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
1647
1892
  }
1648
1893
  }
1649
1894
  this.root.append(n), this.statusBox = n, this.show();
@@ -1682,7 +1927,7 @@ var rt = class {
1682
1927
  let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
1683
1928
  this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
1684
1929
  }
1685
- !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${Y(this.video.currentTime)} / ${Y(this.video.duration)}`);
1930
+ !this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${X(this.video.currentTime)} / ${X(this.video.duration)}`);
1686
1931
  }
1687
1932
  syncLive() {
1688
1933
  let e = this.player.getStats(), t = e.isLive !== !1;
@@ -1732,17 +1977,17 @@ var rt = class {
1732
1977
  renderQualityMenu() {
1733
1978
  let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
1734
1979
  n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
1735
- for (let n of e) this.menu.append(this.menuItem(it(n), t === n.index, () => this.player.setQuality(n.index)));
1980
+ for (let n of e) this.menu.append(this.menuItem(gt(n), t === n.index, () => this.player.setQuality(n.index)));
1736
1981
  }
1737
1982
  renderAudioMenu() {
1738
1983
  let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
1739
1984
  t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
1740
- for (let t of e) this.menu.append(this.menuItem(at(t), t.default, () => this.player.setAudioTrack(t.id)));
1985
+ for (let t of e) this.menu.append(this.menuItem(_t(t), t.default, () => this.player.setAudioTrack(t.id)));
1741
1986
  }
1742
1987
  renderCcMenu() {
1743
1988
  let e = this.player.getTextTracks(), t = this.doc.createElement("div");
1744
1989
  t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
1745
- for (let t of e) this.menu.append(this.menuItem(ot(t), !1, () => this.player.setTextTrack(t.id)));
1990
+ for (let t of e) this.menu.append(this.menuItem(vt(t), !1, () => this.player.setTextTrack(t.id)));
1746
1991
  }
1747
1992
  onTracksChanged() {
1748
1993
  this.openMenu && this.renderMenu(this.openMenu);
@@ -1825,7 +2070,7 @@ var rt = class {
1825
2070
  }
1826
2071
  }
1827
2072
  setIcon(e, t) {
1828
- e.innerHTML = J(t);
2073
+ e.innerHTML = Y(t);
1829
2074
  }
1830
2075
  destroy() {
1831
2076
  this.hideTimer && clearTimeout(this.hideTimer);
@@ -1842,32 +2087,32 @@ var rt = class {
1842
2087
  this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
1843
2088
  }
1844
2089
  };
1845
- function Y(e) {
2090
+ function X(e) {
1846
2091
  (!Number.isFinite(e) || e < 0) && (e = 0);
1847
2092
  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");
1848
2093
  return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
1849
2094
  }
1850
- function it(e) {
2095
+ function gt(e) {
1851
2096
  return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
1852
2097
  }
1853
- function at(e) {
2098
+ function _t(e) {
1854
2099
  return e.name || e.lang || `Track ${e.id}`;
1855
2100
  }
1856
- function ot(e) {
2101
+ function vt(e) {
1857
2102
  return e.name || e.lang || `Track ${e.id}`;
1858
2103
  }
1859
2104
  //#endregion
1860
2105
  //#region src/managed/index.ts
1861
- var st = 6e3, X = 45e3, Z = 2e3, ct = 3, lt = 2e3, ut = 48e4;
1862
- async function Q(e) {
2106
+ var yt = 6e3, Z = 45e3, Q = 2e3, bt = 3, xt = 2e3, St = 48e4;
2107
+ async function Ct(e) {
1863
2108
  if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
1864
2109
  if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
1865
2110
  if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
1866
2111
  if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
1867
- let t = new dt(e);
2112
+ let t = new wt(e);
1868
2113
  return await t.start(), t;
1869
2114
  }
1870
- var dt = class {
2115
+ var wt = class {
1871
2116
  opts;
1872
2117
  abort = new AbortController();
1873
2118
  listeners = {};
@@ -1891,7 +2136,7 @@ var dt = class {
1891
2136
  armFallbackCtrl = null;
1892
2137
  _lastError = null;
1893
2138
  analyticsSession = null;
1894
- detachSeekListener;
2139
+ detachMediaListeners;
1895
2140
  playRequested = !1;
1896
2141
  pendingStartLoad = !1;
1897
2142
  constructor(e) {
@@ -1954,7 +2199,7 @@ var dt = class {
1954
2199
  setTimeout(() => {
1955
2200
  a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
1956
2201
  phase: i ? "unavailable" : "tooEarly",
1957
- retryInMs: X,
2202
+ retryInMs: Z,
1958
2203
  attempt: 1,
1959
2204
  elapsedMs: 0,
1960
2205
  ...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
@@ -1993,13 +2238,13 @@ var dt = class {
1993
2238
  !a && !p() && h();
1994
2239
  }, e);
1995
2240
  };
1996
- _(st), this.armingSub = D({
2241
+ _(yt), this.armingSub = D({
1997
2242
  eventsBaseUrl: t,
1998
2243
  matchUrn: this.opts.matchUrn,
1999
2244
  credential: this.opts.credential,
2000
- onAlive: () => _(X),
2245
+ onAlive: () => _(Z),
2001
2246
  onState: (e) => {
2002
- a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Z);
2247
+ a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Q);
2003
2248
  },
2004
2249
  onError: () => h()
2005
2250
  });
@@ -2022,7 +2267,7 @@ var dt = class {
2022
2267
  let n = await s(this.opts.credential);
2023
2268
  if (this.destroyed || t !== this.loadGen) return;
2024
2269
  this.setState("loading");
2025
- let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = we(this.opts.video, (e) => {
2270
+ let r = this.opts.autoplay === !1 && !this.playRequested, i = this.createEngine(e, n, r), a = Te(this.opts.video, (e) => {
2026
2271
  this._state !== "ended" && this._state !== "error" && this.setState(e);
2027
2272
  });
2028
2273
  if (await i.load(), this.destroyed || t !== this.loadGen) {
@@ -2038,30 +2283,40 @@ var dt = class {
2038
2283
  }, 0));
2039
2284
  }
2040
2285
  startAnalytics(e, t, n) {
2041
- if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
2286
+ if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachMediaListeners?.(), this.opts.analytics === !1) return;
2042
2287
  let r = e.analytics?.sid;
2043
2288
  if (!r) return;
2044
- let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Je(this.opts.baseUrl);
2289
+ let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? tt(this.opts.baseUrl);
2045
2290
  if (!i) return;
2046
- let a = this.opts.video, o = new Ye({
2291
+ let a = this.opts.video, o = new nt({
2047
2292
  endpoint: i,
2048
2293
  sid: r,
2049
2294
  apiKey: n,
2050
2295
  player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
2051
- sdkVersion: Xe,
2296
+ sdkVersion: ct,
2297
+ environment: ot(a),
2052
2298
  sample: () => {
2053
- let e = this.engine?.getStats() ?? F, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
2299
+ let e = this.engine?.getStats() ?? we, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
2054
2300
  return {
2055
2301
  ...e,
2056
2302
  bufferedMs: Math.round(t * 1e3),
2057
2303
  state: this._state,
2058
- positionMs: Math.round(a.currentTime * 1e3)
2304
+ positionMs: Math.round(a.currentTime * 1e3),
2305
+ playbackRate: a.playbackRate
2059
2306
  };
2060
2307
  }
2061
2308
  });
2062
2309
  this.analyticsSession = o;
2063
- let s = () => o.noteSeek();
2064
- a.addEventListener("seeking", s), this.detachSeekListener = () => a.removeEventListener("seeking", s), o.start();
2310
+ let s = () => o.noteSeek(), c = () => o.noteVolume(a.muted, a.volume);
2311
+ a.addEventListener("seeking", s), a.addEventListener("volumechange", c), this.detachMediaListeners = () => {
2312
+ a.removeEventListener("seeking", s), a.removeEventListener("volumechange", c);
2313
+ }, o.start(), o.seedVolume(a.muted, a.volume), o.notePresentation(this.currentPresentation()), st().then((e) => o.noteEnvironment(e)), this.opts.autoplay === !1 && o.noteAutoplay("AUTOPLAY_RESULT_NOT_ATTEMPTED");
2314
+ }
2315
+ currentPresentation() {
2316
+ let e = this.opts.video;
2317
+ if (typeof document < "u" && document.pictureInPictureElement === e) return "pip";
2318
+ let t = typeof document < "u" ? document.fullscreenElement : null;
2319
+ return e.webkitDisplayingFullscreen === !0 || t && (t === e || t.contains(e)) ? "fullscreen" : "inline";
2065
2320
  }
2066
2321
  createEngine(e, t, r = !1) {
2067
2322
  let i = { ...this.opts.hlsConfig };
@@ -2079,11 +2334,11 @@ var dt = class {
2079
2334
  hlsConfig: i,
2080
2335
  emit: (e) => this.onEngineEvent(e)
2081
2336
  }, o = this.opts.engine ?? "auto";
2082
- if (o === "native") return new z(a);
2083
- if (o === "hls") return new V(a);
2084
- if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && L(this.opts.video) && R()) return new z(a);
2085
- if (De()) return new V(a);
2086
- if (L(this.opts.video)) return new z(a);
2337
+ if (o === "native") return new R(a);
2338
+ if (o === "hls") return new B(a);
2339
+ if (e.drmEnabled && e.drm?.fairplay?.licenseUrl && e.drm.fairplay.certificateUrl && I(this.opts.video) && L()) return new R(a);
2340
+ if (Oe()) return new B(a);
2341
+ if (I(this.opts.video)) return new R(a);
2087
2342
  throw new n("INTERNAL", 0, "no supported HLS playback engine in this browser");
2088
2343
  }
2089
2344
  onEngineEvent(e) {
@@ -2106,7 +2361,7 @@ var dt = class {
2106
2361
  case "ended":
2107
2362
  this.onStreamEnded();
2108
2363
  break;
2109
- case "error": this.analyticsSession?.noteError(e.error.code, e.error.message, e.fatal, e.error.httpStatus ?? 0, Math.round(this.opts.video.currentTime * 1e3)), e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
2364
+ case "error": this.analyticsSession?.noteError(e.error.code, e.error.message, e.fatal, e.error.httpStatus ?? 0, Math.round(this.opts.video.currentTime * 1e3), e.detail), e.fatal ? this.fail(e.error) : this.emit("warning", e.error);
2110
2365
  }
2111
2366
  }
2112
2367
  startLiveState(e) {
@@ -2126,7 +2381,7 @@ var dt = class {
2126
2381
  r = 0, i = !1;
2127
2382
  },
2128
2383
  onError: () => {
2129
- this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= ct && (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.`))));
2384
+ this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= bt && (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.`))));
2130
2385
  }
2131
2386
  });
2132
2387
  }
@@ -2148,7 +2403,7 @@ var dt = class {
2148
2403
  scheduleRejoin(e) {
2149
2404
  !this.rejoinEnabled() || this.rejoinTimer || (this.rejoinTimer = setTimeout(() => {
2150
2405
  this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
2151
- }, Math.random() * Z));
2406
+ }, Math.random() * Q));
2152
2407
  }
2153
2408
  cancelRejoin() {
2154
2409
  this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
@@ -2172,7 +2427,7 @@ var dt = class {
2172
2427
  }
2173
2428
  t.controls = !1;
2174
2429
  let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
2175
- r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = nt({
2430
+ r && r.insertBefore(n, t), n.appendChild(t), q(n, this.opts.theme), this.container = n, this.controlBar = mt({
2176
2431
  root: n,
2177
2432
  video: t,
2178
2433
  player: this,
@@ -2189,14 +2444,20 @@ var dt = class {
2189
2444
  e && (e.parentNode ? (e.parentNode.insertBefore(this.opts.video, e), e.remove()) : this.opts.video.parentNode === e && e.removeChild(this.opts.video)), this.container = null;
2190
2445
  }
2191
2446
  attachPipListeners() {
2192
- let e = this.opts.video, t = () => this.emit("pipchange", !0), n = () => this.emit("pipchange", !1);
2193
- e.addEventListener("enterpictureinpicture", t), e.addEventListener("leavepictureinpicture", n), this.detachPip = () => {
2194
- e.removeEventListener("enterpictureinpicture", t), e.removeEventListener("leavepictureinpicture", n);
2447
+ let e = this.opts.video, t = () => this.analyticsSession?.notePresentation(this.currentPresentation()), n = () => {
2448
+ t(), this.emit("pipchange", !0);
2449
+ }, r = () => {
2450
+ t(), this.emit("pipchange", !1);
2451
+ };
2452
+ e.addEventListener("enterpictureinpicture", n), e.addEventListener("leavepictureinpicture", r);
2453
+ let i = typeof document < "u" ? document : void 0;
2454
+ i?.addEventListener("fullscreenchange", t), i?.addEventListener("webkitfullscreenchange", t), this.detachPip = () => {
2455
+ e.removeEventListener("enterpictureinpicture", n), e.removeEventListener("leavepictureinpicture", r), i?.removeEventListener("fullscreenchange", t), i?.removeEventListener("webkitfullscreenchange", t);
2195
2456
  };
2196
2457
  }
2197
2458
  startStats() {
2198
2459
  this.stopStats();
2199
- let e = Math.max(500, this.opts.statsIntervalMs ?? lt);
2460
+ let e = Math.max(500, this.opts.statsIntervalMs ?? xt);
2200
2461
  this.statsTimer = setInterval(() => {
2201
2462
  if (!this.engine) return;
2202
2463
  let e = this.engine.getStats();
@@ -2208,7 +2469,7 @@ var dt = class {
2208
2469
  }
2209
2470
  scheduleLicenseRefresh() {
2210
2471
  this.stopRefresh();
2211
- let e = this.opts.licenseRefreshMs ?? ut;
2472
+ let e = this.opts.licenseRefreshMs ?? St;
2212
2473
  !e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
2213
2474
  }
2214
2475
  stopRefresh() {
@@ -2235,9 +2496,9 @@ var dt = class {
2235
2496
  let t = this.pendingStartLoad && this._state !== "ended" && this._state !== "error";
2236
2497
  t && (this.pendingStartLoad = !1, this.setState("loading"), this.engine?.startLoad());
2237
2498
  try {
2238
- await this.opts.video.play();
2499
+ await this.opts.video.play(), e && this.analyticsSession?.noteAutoplay(this.opts.video.muted ? "AUTOPLAY_RESULT_ALLOWED_MUTED" : "AUTOPLAY_RESULT_ALLOWED");
2239
2500
  } catch (n) {
2240
- n instanceof DOMException && n.name === "NotAllowedError" && (e && (this.playRequested = !1), t && !this.destroyed && this._state === "loading" && this.setState("paused"), this.emit("autoplayblocked"));
2501
+ n instanceof DOMException && n.name === "NotAllowedError" && (this.analyticsSession?.noteAutoplay("AUTOPLAY_RESULT_BLOCKED"), e && (this.playRequested = !1), t && !this.destroyed && this._state === "loading" && this.setState("paused"), this.emit("autoplayblocked"));
2241
2502
  }
2242
2503
  }
2243
2504
  pause() {
@@ -2250,7 +2511,7 @@ var dt = class {
2250
2511
  this.destroyed || (this.opts.video.volume = Math.min(1, Math.max(0, e)));
2251
2512
  }
2252
2513
  getStats() {
2253
- return this.engine?.getStats() ?? F;
2514
+ return this.engine?.getStats() ?? we;
2254
2515
  }
2255
2516
  getQualityLevels() {
2256
2517
  return this.engine?.getQualityLevels() ?? [];
@@ -2317,7 +2578,7 @@ var dt = class {
2317
2578
  return n.add(t), () => n.delete(t);
2318
2579
  }
2319
2580
  destroy() {
2320
- 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());
2581
+ 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.detachMediaListeners?.(), this.engine?.destroy(), this.engine = null, this.setState("idle"), this.clearListeners());
2321
2582
  }
2322
2583
  clearListeners() {
2323
2584
  for (let e of Object.keys(this.listeners)) this.listeners[e]?.clear();
@@ -2360,7 +2621,7 @@ function $(e) {
2360
2621
  }
2361
2622
  //#endregion
2362
2623
  //#region src/embed/host.ts
2363
- function ft(e) {
2624
+ function Tt(e) {
2364
2625
  if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
2365
2626
  if (!e.src) throw Error("mountEmbed: opts.src is required");
2366
2627
  if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
@@ -2419,8 +2680,8 @@ function ft(e) {
2419
2680
  }
2420
2681
  //#endregion
2421
2682
  //#region src/embed/page.ts
2422
- var pt = /^[A-Za-z0-9:_.-]{1,128}$/;
2423
- function mt() {
2683
+ var Et = /^[A-Za-z0-9:_.-]{1,128}$/;
2684
+ function Dt() {
2424
2685
  let e = window.HAVIK_EMBED_CONFIG;
2425
2686
  if (e) return {
2426
2687
  baseUrl: e.baseUrl ?? "",
@@ -2442,9 +2703,9 @@ function mt() {
2442
2703
  waitForLive: !0
2443
2704
  };
2444
2705
  }
2445
- async function ht() {
2446
- let e = mt();
2447
- _t();
2706
+ async function Ot() {
2707
+ let e = Dt();
2708
+ At();
2448
2709
  let t = document.createElement("video");
2449
2710
  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);
2450
2711
  let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
@@ -2499,12 +2760,12 @@ async function ht() {
2499
2760
  code: e.code,
2500
2761
  message: e.message
2501
2762
  })), e.on("autoplayblocked", () => {
2502
- i({ type: "oddin:autoplayblocked" }), gt(t, e);
2763
+ i({ type: "oddin:autoplayblocked" }), kt(t, e);
2503
2764
  });
2504
2765
  }, s = async (r) => {
2505
2766
  a?.destroy(), a = null;
2506
2767
  try {
2507
- let n = await Q({
2768
+ let n = await Ct({
2508
2769
  video: t,
2509
2770
  baseUrl: e.baseUrl,
2510
2771
  matchUrn: r,
@@ -2565,7 +2826,7 @@ async function ht() {
2565
2826
  a?.retry();
2566
2827
  break;
2567
2828
  case "oddin:load":
2568
- typeof e.matchUrn == "string" && pt.test(e.matchUrn) && s(e.matchUrn);
2829
+ typeof e.matchUrn == "string" && Et.test(e.matchUrn) && s(e.matchUrn);
2569
2830
  break;
2570
2831
  case "oddin:destroy": c();
2571
2832
  }
@@ -2577,18 +2838,18 @@ async function ht() {
2577
2838
  protocol: 1
2578
2839
  });
2579
2840
  }
2580
- function gt(e, t) {
2841
+ function kt(e, t) {
2581
2842
  if (document.getElementById("havik-tap")) return;
2582
2843
  let n = document.createElement("button");
2583
2844
  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", () => {
2584
2845
  n.remove(), t.play();
2585
2846
  }), document.body.appendChild(n);
2586
2847
  }
2587
- function _t() {
2848
+ function At() {
2588
2849
  let e = document.createElement("style");
2589
2850
  e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
2590
2851
  }
2591
2852
  //#endregion
2592
- export { W as ODDIN_THEME, n as PlaybackError, K as applyTheme, ht 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, ft as mountEmbed, v as pollToLive, b as resolveStream, D as subscribeLiveState, w as videoFullscreenActive, me as watchStatus };
2853
+ export { G as ODDIN_THEME, n as PlaybackError, q as applyTheme, Ot as bootEmbed, he as clearDeviceId, Ct 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, Tt as mountEmbed, v as pollToLive, b as resolveStream, D as subscribeLiveState, w as videoFullscreenActive, me as watchStatus };
2593
2854
 
2594
2855
  //# sourceMappingURL=index.js.map