@obipascal/player 1.0.8 → 1.0.10

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.
@@ -1,24 +1,32 @@
1
1
  var V = Object.defineProperty;
2
- var N = (c, t, e) => t in c ? V(c, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[t] = e;
3
- var o = (c, t, e) => N(c, typeof t != "symbol" ? t + "" : t, e);
2
+ var H = (c, t, e) => t in c ? V(c, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[t] = e;
3
+ var r = (c, t, e) => H(c, typeof t != "symbol" ? t + "" : t, e);
4
4
  import p from "hls.js";
5
- import { jsx as F } from "react/jsx-runtime";
6
- import * as $ from "react";
7
- import { useRef as b, useEffect as E, useState as k } from "react";
8
- class W {
5
+ import { jsx as P } from "react/jsx-runtime";
6
+ import * as B from "react";
7
+ import { useRef as E, useEffect as x, useState as S } from "react";
8
+ class N {
9
9
  constructor(t) {
10
- o(this, "config");
11
- o(this, "sessionId");
12
- o(this, "events", []);
13
- o(this, "sessionStartTime");
14
- o(this, "playbackStartTime", null);
15
- o(this, "totalPlayTime", 0);
16
- o(this, "totalBufferTime", 0);
17
- o(this, "bufferStartTime", null);
18
- o(this, "rebufferCount", 0);
19
- o(this, "seekCount", 0);
20
- var e;
21
- this.config = t, this.sessionId = (t == null ? void 0 : t.sessionId) || this.generateSessionId(), this.sessionStartTime = Date.now(), (e = this.config) != null && e.enabled && this.trackEvent("session_start", this.getSessionData());
10
+ r(this, "config");
11
+ r(this, "sessionId");
12
+ r(this, "events", []);
13
+ r(this, "sessionStartTime");
14
+ r(this, "playbackStartTime", null);
15
+ r(this, "totalPlayTime", 0);
16
+ r(this, "totalBufferTime", 0);
17
+ r(this, "bufferStartTime", null);
18
+ r(this, "rebufferCount", 0);
19
+ r(this, "seekCount", 0);
20
+ r(this, "webSocket", null);
21
+ r(this, "socketIO", null);
22
+ r(this, "wsReconnectTimeout", null);
23
+ r(this, "isDestroyed", !1);
24
+ var e, i;
25
+ if (this.config = t, this.sessionId = (t == null ? void 0 : t.sessionId) || this.generateSessionId(), this.sessionStartTime = Date.now(), (e = this.config) != null && e.webSocket) {
26
+ const n = this.config.webSocket;
27
+ "type" in n ? n.type === "socket.io" ? this.initializeSocketIO() : this.initializeWebSocket() : this.initializeWebSocket();
28
+ }
29
+ (i = this.config) != null && i.enabled && this.trackEvent("session_start", this.getSessionData());
22
30
  }
23
31
  trackEvent(t, e = {}) {
24
32
  var n;
@@ -34,7 +42,7 @@ class W {
34
42
  ...this.getQoEMetrics()
35
43
  }
36
44
  };
37
- this.events.push(i), this.updateMetrics(t, e), this.config.endpoint && this.sendEvent(i), process.env.NODE_ENV === "development" && console.log("[Analytics]", t, i.data);
45
+ this.events.push(i), this.updateMetrics(t, e), this.webSocket && this.webSocket.readyState === WebSocket.OPEN && this.sendToWebSocket(i), this.socketIO && this.socketIO.connected && this.sendToSocketIO(i), this.config.endpoint && this.sendEvent(i), process.env.NODE_ENV === "development" && console.log("[Analytics]", t, i.data);
38
46
  }
39
47
  updateMetrics(t, e) {
40
48
  switch (t) {
@@ -104,6 +112,72 @@ class W {
104
112
  generateSessionId() {
105
113
  return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
106
114
  }
115
+ async initializeSocketIO() {
116
+ var e;
117
+ if (!((e = this.config) != null && e.webSocket) || !("type" in this.config.webSocket)) return;
118
+ const t = this.config.webSocket;
119
+ if (t.type === "socket.io")
120
+ try {
121
+ if (typeof t.connection == "string") {
122
+ const n = (await import("socket.io-client")).default;
123
+ this.socketIO = n(t.connection, t.options || {});
124
+ } else
125
+ this.socketIO = t.connection;
126
+ if (!this.socketIO) return;
127
+ this.socketIO.on("connect", () => {
128
+ process.env.NODE_ENV === "development" && console.log("[Analytics Socket.IO] Connected"), t.onConnect && t.onConnect();
129
+ }), this.socketIO.on("connect_error", (i) => {
130
+ console.error("[Analytics Socket.IO] Connection error:", i), t.onError && t.onError(i);
131
+ }), this.socketIO.on("disconnect", (i) => {
132
+ process.env.NODE_ENV === "development" && console.log("[Analytics Socket.IO] Disconnected:", i), t.onDisconnect && t.onDisconnect(i);
133
+ }), this.socketIO.on("error", (i) => {
134
+ console.error("[Analytics Socket.IO] Error:", i), t.onError && t.onError(i);
135
+ });
136
+ } catch (i) {
137
+ console.error("[Analytics Socket.IO] Failed to initialize:", i);
138
+ }
139
+ }
140
+ sendToSocketIO(t) {
141
+ var e;
142
+ if (!(!this.socketIO || !this.socketIO.connected))
143
+ try {
144
+ const i = (e = this.config) == null ? void 0 : e.webSocket, n = i != null && i.transform ? i.transform(t) : t, o = (i == null ? void 0 : i.eventName) || "analytics";
145
+ this.socketIO.emit(o, n), process.env.NODE_ENV === "development" && console.log(`[Analytics Socket.IO] Emitted (${o}):`, t.eventType);
146
+ } catch (i) {
147
+ console.error("[Analytics Socket.IO] Failed to emit event:", i);
148
+ }
149
+ }
150
+ initializeWebSocket() {
151
+ var e;
152
+ if (!((e = this.config) != null && e.webSocket)) return;
153
+ const t = this.config.webSocket;
154
+ try {
155
+ typeof t.connection == "string" ? this.webSocket = new WebSocket(t.connection) : this.webSocket = t.connection, this.webSocket.onopen = (i) => {
156
+ process.env.NODE_ENV === "development" && console.log("[Analytics WebSocket] Connected"), t.onOpen && t.onOpen(i);
157
+ }, this.webSocket.onerror = (i) => {
158
+ console.error("[Analytics WebSocket] Error:", i), t.onError && t.onError(i);
159
+ }, this.webSocket.onclose = (i) => {
160
+ if (process.env.NODE_ENV === "development" && console.log("[Analytics WebSocket] Disconnected"), t.onClose && t.onClose(i), t.autoReconnect !== !1 && !this.isDestroyed) {
161
+ const o = t.reconnectDelay || 3e3;
162
+ process.env.NODE_ENV === "development" && console.log(`[Analytics WebSocket] Reconnecting in ${o}ms...`), this.wsReconnectTimeout = window.setTimeout(() => {
163
+ this.initializeWebSocket();
164
+ }, o);
165
+ }
166
+ };
167
+ } catch (i) {
168
+ console.error("[Analytics WebSocket] Failed to initialize:", i);
169
+ }
170
+ }
171
+ sendToWebSocket(t) {
172
+ var e;
173
+ if (!(!this.webSocket || this.webSocket.readyState !== WebSocket.OPEN))
174
+ try {
175
+ const i = (e = this.config) == null ? void 0 : e.webSocket, n = i != null && i.transform ? i.transform(t) : t;
176
+ this.webSocket.send(JSON.stringify(n)), process.env.NODE_ENV === "development" && console.log("[Analytics WebSocket] Sent:", t.eventType);
177
+ } catch (i) {
178
+ console.error("[Analytics WebSocket] Failed to send event:", i);
179
+ }
180
+ }
107
181
  getEvents() {
108
182
  return [...this.events];
109
183
  }
@@ -116,46 +190,46 @@ class W {
116
190
  }
117
191
  destroy() {
118
192
  var t;
119
- (t = this.config) != null && t.enabled && this.trackEvent("session_end", this.getSessionData()), this.events = [];
193
+ this.isDestroyed = !0, (t = this.config) != null && t.enabled && this.trackEvent("session_end", this.getSessionData()), this.wsReconnectTimeout && (clearTimeout(this.wsReconnectTimeout), this.wsReconnectTimeout = null), this.webSocket && (this.webSocket.close(), this.webSocket = null), this.socketIO && (this.socketIO.removeAllListeners(), this.socketIO.disconnect(), this.socketIO = null), this.events = [];
120
194
  }
121
195
  }
122
- class j {
196
+ class W {
123
197
  constructor(t, e) {
124
- o(this, "container");
125
- o(this, "player");
126
- o(this, "controlsContainer");
127
- o(this, "progressContainer");
128
- o(this, "progressBar");
129
- o(this, "playButton");
130
- o(this, "skipBackwardButton");
131
- o(this, "skipForwardButton");
132
- o(this, "volumeButton");
133
- o(this, "volumeContainer");
134
- o(this, "fullscreenButton");
135
- o(this, "pipButton");
136
- o(this, "settingsButton");
198
+ r(this, "container");
199
+ r(this, "player");
200
+ r(this, "controlsContainer");
201
+ r(this, "progressContainer");
202
+ r(this, "progressBar");
203
+ r(this, "playButton");
204
+ r(this, "skipBackwardButton");
205
+ r(this, "skipForwardButton");
206
+ r(this, "volumeButton");
207
+ r(this, "volumeContainer");
208
+ r(this, "fullscreenButton");
209
+ r(this, "pipButton");
210
+ r(this, "settingsButton");
137
211
  // private timeDisplay: HTMLElement
138
- o(this, "volumeSlider");
139
- o(this, "progressInput");
212
+ r(this, "volumeSlider");
213
+ r(this, "progressInput");
140
214
  // private controlsVisible = true
141
- o(this, "hideControlsTimeout", null);
142
- o(this, "stickyControls", !1);
143
- o(this, "isVolumeSliderActive", !1);
215
+ r(this, "hideControlsTimeout", null);
216
+ r(this, "stickyControls", !1);
217
+ r(this, "isVolumeSliderActive", !1);
144
218
  this.container = t, this.player = e, this.injectStyles(), this.createProgressBar(), this.controlsContainer = this.createControls(), this.container.appendChild(this.controlsContainer), this.playButton = this.controlsContainer.querySelector(".wontum-play-btn"), this.skipBackwardButton = this.controlsContainer.querySelector(".wontum-skip-backward-btn"), this.skipForwardButton = this.controlsContainer.querySelector(".wontum-skip-forward-btn"), this.volumeButton = this.controlsContainer.querySelector(".wontum-volume-btn"), this.volumeContainer = this.controlsContainer.querySelector(".wontum-volume-container"), this.fullscreenButton = this.controlsContainer.querySelector(".wontum-fullscreen-btn"), this.pipButton = this.controlsContainer.querySelector(".wontum-pip-btn"), this.settingsButton = this.controlsContainer.querySelector(".wontum-settings-btn"), this.volumeSlider = this.controlsContainer.querySelector(".wontum-volume-slider"), this.progressInput = this.container.querySelector(".wontum-progress-input"), this.progressBar = this.container.querySelector(".wontum-progress-filled"), this.stickyControls = this.player.config.stickyControls || !1, this.stickyControls && this.controlsContainer.classList.add("sticky"), this.setupEventListeners(), this.setupPlayerEventListeners();
145
219
  }
146
220
  injectStyles() {
147
221
  const t = "wontum-player-styles";
148
222
  if (document.getElementById(t)) return;
149
- const e = this.player.config.theme || {}, i = e.primaryColor || "#3b82f6", n = e.accentColor || "#2563eb", s = e.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", r = e.controlsBackground || "linear-gradient(to top, rgba(0,0,0,0.8), transparent)", a = e.buttonHoverBg || "rgba(255, 255, 255, 0.1)", l = e.progressHeight || "6px", u = e.borderRadius || "4px", m = document.createElement("style");
223
+ const e = this.player.config.theme || {}, i = e.primaryColor || "#3b82f6", n = e.accentColor || "#2563eb", o = e.fontFamily || "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif", s = e.controlsBackground || "linear-gradient(to top, rgba(0,0,0,0.8), transparent)", a = e.buttonHoverBg || "rgba(255, 255, 255, 0.1)", l = e.progressHeight || "6px", u = e.borderRadius || "4px", m = document.createElement("style");
150
224
  m.id = t, m.textContent = `
151
225
  .wontum-player-container {
152
226
  position: relative;
153
227
  background: #000;
154
- font-family: ${s};
228
+ font-family: ${o};
155
229
  overflow: hidden;
156
230
  --primary-color: ${i};
157
231
  --accent-color: ${n};
158
- --controls-bg: ${r};
232
+ --controls-bg: ${s};
159
233
  --button-hover: ${a};
160
234
  --progress-height: ${l};
161
235
  --border-radius: ${u};
@@ -729,11 +803,11 @@ class j {
729
803
  }), this.skipForwardButton.addEventListener("click", () => {
730
804
  this.player.skipForward(10);
731
805
  }), this.progressInput.addEventListener("input", (i) => {
732
- const n = i.target, s = parseFloat(n.value), r = this.player.getState(), a = s / 100 * r.duration;
806
+ const n = i.target, o = parseFloat(n.value), s = this.player.getState(), a = o / 100 * s.duration;
733
807
  this.player.seek(a);
734
808
  }), this.volumeSlider.addEventListener("input", (i) => {
735
- const n = i.target, s = parseFloat(n.value) / 100;
736
- this.player.setVolume(s);
809
+ const n = i.target, o = parseFloat(n.value) / 100;
810
+ this.player.setVolume(o);
737
811
  }), this.volumeButton.addEventListener("click", () => {
738
812
  this.player.getState().muted ? this.player.unmute() : this.player.mute();
739
813
  }), this.volumeContainer.addEventListener("mouseenter", () => {
@@ -761,9 +835,9 @@ class j {
761
835
  const t = this.controlsContainer.querySelectorAll(".wontum-tab");
762
836
  t.forEach((i) => {
763
837
  i.addEventListener("click", (n) => {
764
- const s = n.currentTarget, r = s.getAttribute("data-tab");
765
- t.forEach((u) => u.classList.remove("active")), s.classList.add("active"), this.controlsContainer.querySelectorAll(".wontum-tab-panel").forEach((u) => u.classList.remove("active"));
766
- const l = this.controlsContainer.querySelector(`[data-panel="${r}"]`);
838
+ const o = n.currentTarget, s = o.getAttribute("data-tab");
839
+ t.forEach((u) => u.classList.remove("active")), o.classList.add("active"), this.controlsContainer.querySelectorAll(".wontum-tab-panel").forEach((u) => u.classList.remove("active"));
840
+ const l = this.controlsContainer.querySelector(`[data-panel="${s}"]`);
767
841
  l == null || l.classList.add("active");
768
842
  });
769
843
  }), this.player.getVideoElement().addEventListener("click", () => {
@@ -782,8 +856,8 @@ class j {
782
856
  }), this.player.on("timeupdate", (t) => {
783
857
  const { currentTime: e } = t.data, i = this.player.getState();
784
858
  if (i.duration > 0) {
785
- const s = e / i.duration * 100;
786
- this.progressBar.style.width = `${s}%`, this.progressInput.value = s.toString();
859
+ const o = e / i.duration * 100;
860
+ this.progressBar.style.width = `${o}%`, this.progressInput.value = o.toString();
787
861
  }
788
862
  const n = this.controlsContainer.querySelector(".wontum-current-time");
789
863
  n.textContent = this.formatTime(e);
@@ -811,30 +885,30 @@ class j {
811
885
  t.innerHTML = `
812
886
  <div class="wontum-subtitle-option ${i === -1 ? "active" : ""}" data-track="-1">Off</div>
813
887
  ${e.map(
814
- (n, s) => `
815
- <div class="wontum-subtitle-option ${s === i ? "active" : ""}" data-track="${s}">
816
- ${n.label || n.language || `Track ${s + 1}`}
888
+ (n, o) => `
889
+ <div class="wontum-subtitle-option ${o === i ? "active" : ""}" data-track="${o}">
890
+ ${n.label || n.language || `Track ${o + 1}`}
817
891
  </div>
818
892
  `
819
893
  ).join("")}
820
894
  `, t.querySelectorAll(".wontum-subtitle-option").forEach((n) => {
821
- n.addEventListener("click", (s) => {
822
- const r = s.target, a = parseInt(r.dataset.track || "-1");
823
- a === -1 ? this.player.disableSubtitles() : this.player.enableSubtitles(a), t.querySelectorAll(".wontum-subtitle-option").forEach((l) => l.classList.remove("active")), r.classList.add("active");
895
+ n.addEventListener("click", (o) => {
896
+ const s = o.target, a = parseInt(s.dataset.track || "-1");
897
+ a === -1 ? this.player.disableSubtitles() : this.player.enableSubtitles(a), t.querySelectorAll(".wontum-subtitle-option").forEach((l) => l.classList.remove("active")), s.classList.add("active");
824
898
  });
825
899
  });
826
900
  }
827
901
  updateSpeedMenu() {
828
902
  const t = this.controlsContainer.querySelector(".wontum-speed-menu"), i = this.player.getState().playbackRate || 1, n = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
829
903
  t.innerHTML = n.map(
830
- (s) => `
831
- <div class="wontum-speed-option ${i === s ? "active" : ""}" data-speed="${s}">
832
- ${s === 1 ? "Normal" : s + "x"}
904
+ (o) => `
905
+ <div class="wontum-speed-option ${i === o ? "active" : ""}" data-speed="${o}">
906
+ ${o === 1 ? "Normal" : o + "x"}
833
907
  </div>
834
908
  `
835
- ).join(""), t.querySelectorAll(".wontum-speed-option").forEach((s) => {
836
- s.addEventListener("click", (r) => {
837
- const a = r.target, l = parseFloat(a.dataset.speed || "1");
909
+ ).join(""), t.querySelectorAll(".wontum-speed-option").forEach((o) => {
910
+ o.addEventListener("click", (s) => {
911
+ const a = s.target, l = parseFloat(a.dataset.speed || "1");
838
912
  this.player.setPlaybackRate(l), t.querySelectorAll(".wontum-speed-option").forEach((u) => u.classList.remove("active")), a.classList.add("active");
839
913
  });
840
914
  });
@@ -861,14 +935,14 @@ class j {
861
935
  e.innerHTML = `
862
936
  <div class="wontum-quality-option active" data-quality="-1">Auto</div>
863
937
  ${i.map(
864
- (n, s) => `
865
- <div class="wontum-quality-option" data-quality="${s}">${n.name}</div>
938
+ (n, o) => `
939
+ <div class="wontum-quality-option" data-quality="${o}">${n.name}</div>
866
940
  `
867
941
  ).join("")}
868
942
  `, e.querySelectorAll(".wontum-quality-option").forEach((n) => {
869
- n.addEventListener("click", (s) => {
870
- const r = s.target, a = parseInt(r.dataset.quality || "-1");
871
- this.player.setQuality(a), e.querySelectorAll(".wontum-quality-option").forEach((l) => l.classList.remove("active")), r.classList.add("active");
943
+ n.addEventListener("click", (o) => {
944
+ const s = o.target, a = parseInt(s.dataset.quality || "-1");
945
+ this.player.setQuality(a), e.querySelectorAll(".wontum-quality-option").forEach((l) => l.classList.remove("active")), s.classList.add("active");
872
946
  });
873
947
  });
874
948
  }
@@ -933,11 +1007,11 @@ class j {
933
1007
  this.hideControlsTimeout && clearTimeout(this.hideControlsTimeout), this.controlsContainer.remove();
934
1008
  }
935
1009
  }
936
- class Q {
1010
+ class _ {
937
1011
  constructor(t) {
938
- o(this, "config");
939
- o(this, "urlCache", /* @__PURE__ */ new Map());
940
- o(this, "signedUrls", /* @__PURE__ */ new Set());
1012
+ r(this, "config");
1013
+ r(this, "urlCache", /* @__PURE__ */ new Map());
1014
+ r(this, "signedUrls", /* @__PURE__ */ new Set());
941
1015
  this.config = t;
942
1016
  }
943
1017
  /**
@@ -971,15 +1045,15 @@ class Q {
971
1045
  * The endpoint should set signed cookies and return the URL
972
1046
  */
973
1047
  async signCloudFrontUrl(t, e = 0) {
974
- var s, r;
1048
+ var o, s;
975
1049
  if (this.signedUrls.has(t))
976
1050
  return t;
977
- if ((s = this.config) != null && s.signUrl)
1051
+ if ((o = this.config) != null && o.signUrl)
978
1052
  try {
979
1053
  const a = await this.config.signUrl(t);
980
1054
  return this.signedUrls.add(t), a;
981
1055
  } catch (a) {
982
- const l = (a == null ? void 0 : a.name) === "AbortError" || ((r = a == null ? void 0 : a.message) == null ? void 0 : r.includes("aborted"));
1056
+ const l = (a == null ? void 0 : a.name) === "AbortError" || ((s = a == null ? void 0 : a.message) == null ? void 0 : s.includes("aborted"));
983
1057
  if (l && e < 2)
984
1058
  return console.warn(`Sign URL aborted, retrying (${e + 1}/2)...`), await new Promise((u) => setTimeout(u, 300)), this.signCloudFrontUrl(t, e + 1);
985
1059
  throw console.error("Failed to sign CloudFront URL:", a), l ? new Error(
@@ -1010,13 +1084,13 @@ class Q {
1010
1084
  return i.url;
1011
1085
  if ((n = this.config) != null && n.getPresignedUrl)
1012
1086
  try {
1013
- const s = await this.config.getPresignedUrl(e);
1087
+ const o = await this.config.getPresignedUrl(e);
1014
1088
  return this.urlCache.set(e, {
1015
- url: s,
1089
+ url: o,
1016
1090
  expiresAt: Date.now() + 50 * 60 * 1e3
1017
- }), s;
1018
- } catch (s) {
1019
- throw console.error("Failed to generate presigned URL:", s), new Error("Failed to generate presigned URL for S3 object");
1091
+ }), o;
1092
+ } catch (o) {
1093
+ throw console.error("Failed to generate presigned URL:", o), new Error("Failed to generate presigned URL for S3 object");
1020
1094
  }
1021
1095
  return console.warn("No getPresignedUrl function provided. Using direct S3 URL (requires public bucket)"), t;
1022
1096
  }
@@ -1042,18 +1116,18 @@ class Q {
1042
1116
  this.urlCache.clear(), this.signedUrls.clear();
1043
1117
  }
1044
1118
  }
1045
- class A {
1119
+ class $ {
1046
1120
  constructor(t) {
1047
- o(this, "container");
1048
- o(this, "videoElement");
1049
- o(this, "hls", null);
1050
- o(this, "config");
1051
- o(this, "eventListeners", /* @__PURE__ */ new Map());
1052
- o(this, "analytics");
1053
- o(this, "s3Handler");
1054
- o(this, "uiController");
1055
- o(this, "qualities", []);
1056
- o(this, "state", {
1121
+ r(this, "container");
1122
+ r(this, "videoElement");
1123
+ r(this, "hls", null);
1124
+ r(this, "config");
1125
+ r(this, "eventListeners", /* @__PURE__ */ new Map());
1126
+ r(this, "analytics");
1127
+ r(this, "s3Handler");
1128
+ r(this, "uiController");
1129
+ r(this, "qualities", []);
1130
+ r(this, "state", {
1057
1131
  playing: !1,
1058
1132
  paused: !0,
1059
1133
  ended: !1,
@@ -1069,7 +1143,7 @@ class A {
1069
1143
  });
1070
1144
  if (this.config = t, this.container = typeof t.container == "string" ? document.querySelector(t.container) : t.container, !this.container)
1071
1145
  throw new Error("Container element not found");
1072
- this.analytics = new W(t.analytics), this.s3Handler = new Q(t.s3Config), this.videoElement = this.createVideoElement(), this.container.appendChild(this.videoElement), this.uiController = new j(this.container, this), this.setupVideoListeners(), this.loadSource(t.src), t.autoplay && (this.videoElement.autoplay = !0), t.muted && this.mute(), t.poster && (this.videoElement.poster = t.poster), t.preload && (this.videoElement.preload = t.preload), t.subtitles && this.addSubtitleTracks(t.subtitles);
1146
+ this.analytics = new N(t.analytics), this.s3Handler = new _(t.s3Config), this.videoElement = this.createVideoElement(), this.container.appendChild(this.videoElement), this.uiController = new W(this.container, this), this.setupVideoListeners(), this.loadSource(t.src), t.autoplay && (this.videoElement.autoplay = !0), t.muted && this.mute(), t.poster && (this.videoElement.poster = t.poster), t.preload && (this.videoElement.preload = t.preload), t.subtitles && this.addSubtitleTracks(t.subtitles);
1073
1147
  }
1074
1148
  addSubtitleTracks(t) {
1075
1149
  t.forEach((e) => {
@@ -1139,21 +1213,21 @@ class A {
1139
1213
  try {
1140
1214
  const i = await this.s3Handler.processUrl(t);
1141
1215
  if (p.isSupported()) {
1142
- const n = ((e = this.config.s3Config) == null ? void 0 : e.withCredentials) ?? !1, s = {
1216
+ const n = ((e = this.config.s3Config) == null ? void 0 : e.withCredentials) ?? !1, o = {
1143
1217
  ...this.config.hlsConfig,
1144
- xhrSetup: (r, a) => {
1218
+ xhrSetup: (s, a) => {
1145
1219
  var l;
1146
- n && (r.withCredentials = !0), (l = this.config.hlsConfig) != null && l.xhrSetup && this.config.hlsConfig.xhrSetup(r, a);
1220
+ n && (s.withCredentials = !0), (l = this.config.hlsConfig) != null && l.xhrSetup && this.config.hlsConfig.xhrSetup(s, a);
1147
1221
  }
1148
1222
  };
1149
- this.hls = new p(s), this.hls.loadSource(i), this.hls.attachMedia(this.videoElement), this.hls.on(p.Events.MANIFEST_PARSED, (r, a) => {
1223
+ this.hls = new p(o), this.hls.loadSource(i), this.hls.attachMedia(this.videoElement), this.hls.on(p.Events.MANIFEST_PARSED, (s, a) => {
1150
1224
  const l = this.extractQualities(a.levels);
1151
1225
  this.qualities = l;
1152
- }), this.hls.on(p.Events.LEVEL_SWITCHED, (r, a) => {
1226
+ }), this.hls.on(p.Events.LEVEL_SWITCHED, (s, a) => {
1153
1227
  var u;
1154
1228
  const l = (u = this.hls) == null ? void 0 : u.levels[a.level];
1155
1229
  l && (this.state.quality = `${l.height}p`, this.emit("qualitychange", { quality: this.state.quality }));
1156
- }), this.hls.on(p.Events.ERROR, (r, a) => {
1230
+ }), this.hls.on(p.Events.ERROR, (s, a) => {
1157
1231
  a.fatal && this.handleHlsError(a);
1158
1232
  });
1159
1233
  } else if (this.videoElement.canPlayType("application/vnd.apple.mpegurl"))
@@ -1313,8 +1387,8 @@ class A {
1313
1387
  data: e,
1314
1388
  timestamp: Date.now()
1315
1389
  };
1316
- (n = this.eventListeners.get(t)) == null || n.forEach((s) => {
1317
- s(i);
1390
+ (n = this.eventListeners.get(t)) == null || n.forEach((o) => {
1391
+ o(i);
1318
1392
  });
1319
1393
  }
1320
1394
  destroy() {
@@ -1323,9 +1397,10 @@ class A {
1323
1397
  }
1324
1398
  class G {
1325
1399
  constructor(t) {
1326
- o(this, "file");
1327
- o(this, "videoElement", null);
1328
- o(this, "info", null);
1400
+ r(this, "file");
1401
+ r(this, "videoElement", null);
1402
+ r(this, "audioContext", null);
1403
+ r(this, "info", null);
1329
1404
  if (!this.isVideoFile(t))
1330
1405
  throw new Error(`Invalid file type: ${t.type}. Expected a video file.`);
1331
1406
  this.file = t;
@@ -1347,29 +1422,32 @@ class G {
1347
1422
  try {
1348
1423
  this.videoElement = document.createElement("video"), this.videoElement.preload = "metadata", this.videoElement.muted = !0;
1349
1424
  const i = URL.createObjectURL(this.file);
1350
- this.videoElement.onloadedmetadata = () => {
1425
+ this.videoElement.onloadedmetadata = async () => {
1351
1426
  try {
1352
1427
  if (!this.videoElement) {
1353
1428
  e(new Error("Video element not initialized"));
1354
1429
  return;
1355
1430
  }
1356
- const n = this.videoElement.videoWidth, s = this.videoElement.videoHeight, r = this.videoElement.duration, a = this.calculateAspectRatio(n, s), l = this.file.size, u = this.formatBytes(l), m = this.formatDuration(r), g = this.getFileExtension(this.file.name), v = r > 0 ? Math.round(l * 8 / r / 1e3) : void 0;
1431
+ const n = this.videoElement.videoWidth, o = this.videoElement.videoHeight, s = this.videoElement.duration, a = this.calculateAspectRatio(n, o), l = this.file.size, u = this.formatBytes(l), m = this.formatDuration(s), f = this.getFileExtension(this.file.name), y = s > 0 ? Math.round(l * 8 / s / 1e3) : void 0, v = await this.detectFrameRate(), g = await this.detectAudioInfo(i);
1357
1432
  this.info = {
1358
1433
  width: n,
1359
- height: s,
1434
+ height: o,
1360
1435
  aspectRatio: a,
1361
1436
  size: l,
1362
1437
  sizeInBytes: l,
1363
1438
  // raw value alias
1364
1439
  sizeFormatted: u,
1365
- duration: r,
1366
- durationInSeconds: r,
1440
+ duration: s,
1441
+ durationInSeconds: s,
1367
1442
  // raw value alias
1368
1443
  durationFormatted: m,
1369
1444
  mimeType: this.file.type || "video/unknown",
1370
1445
  fileName: this.file.name,
1371
- fileExtension: g,
1372
- bitrate: v
1446
+ fileExtension: f,
1447
+ bitrate: y,
1448
+ frameRate: v,
1449
+ audioChannels: g.channels,
1450
+ hasAudio: g.hasAudio
1373
1451
  }, URL.revokeObjectURL(i), this.videoElement.remove(), t(this.info);
1374
1452
  } catch (n) {
1375
1453
  URL.revokeObjectURL(i), e(n);
@@ -1386,8 +1464,59 @@ class G {
1386
1464
  * Calculate aspect ratio (e.g., "16:9", "4:3")
1387
1465
  */
1388
1466
  calculateAspectRatio(t, e) {
1389
- const i = this.getGCD(t, e), n = t / i, s = e / i, r = n / s;
1390
- return Math.abs(r - 16 / 9) < 0.01 ? "16:9" : Math.abs(r - 4 / 3) < 0.01 ? "4:3" : Math.abs(r - 21 / 9) < 0.01 ? "21:9" : Math.abs(r - 1) < 0.01 ? "1:1" : `${n}:${s}`;
1467
+ const i = this.getGCD(t, e), n = t / i, o = e / i, s = n / o;
1468
+ return Math.abs(s - 16 / 9) < 0.01 ? "16:9" : Math.abs(s - 4 / 3) < 0.01 ? "4:3" : Math.abs(s - 21 / 9) < 0.01 ? "21:9" : Math.abs(s - 1) < 0.01 ? "1:1" : `${n}:${o}`;
1469
+ }
1470
+ /**
1471
+ * Detect frame rate by analyzing video playback
1472
+ */
1473
+ async detectFrameRate() {
1474
+ if (this.videoElement)
1475
+ try {
1476
+ return "requestVideoFrameCallback" in this.videoElement ? new Promise((t) => {
1477
+ let e = 0, i = 0;
1478
+ const n = 10, o = (s, a) => {
1479
+ if (!this.videoElement) {
1480
+ t(void 0);
1481
+ return;
1482
+ }
1483
+ if (e++, e === 1)
1484
+ i = s, this.videoElement.requestVideoFrameCallback(o);
1485
+ else if (e < n)
1486
+ this.videoElement.requestVideoFrameCallback(o);
1487
+ else {
1488
+ const l = (s - i) / 1e3, u = Math.round((e - 1) / l);
1489
+ t(u);
1490
+ }
1491
+ };
1492
+ this.videoElement ? (this.videoElement.currentTime = 1, this.videoElement.play().catch(() => t(void 0)), this.videoElement.requestVideoFrameCallback(o)) : t(void 0);
1493
+ }) : void 0;
1494
+ } catch {
1495
+ return;
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Detect audio channel information using Web Audio API
1500
+ */
1501
+ async detectAudioInfo(t) {
1502
+ var e;
1503
+ if (!this.videoElement) return { hasAudio: !1 };
1504
+ try {
1505
+ if (!(this.videoElement.mozHasAudio || (this.videoElement.webkitAudioDecodedByteCount ?? 0) > 0 || (((e = this.videoElement.audioTracks) == null ? void 0 : e.length) ?? 0) > 0))
1506
+ return { hasAudio: !1 };
1507
+ try {
1508
+ const n = window.AudioContext || window.webkitAudioContext;
1509
+ if (!n)
1510
+ return { hasAudio: !0 };
1511
+ this.audioContext = new n();
1512
+ const o = this.audioContext.createMediaElementSource(this.videoElement), s = this.audioContext.createAnalyser();
1513
+ return o.connect(s), s.connect(this.audioContext.destination), { hasAudio: !0, channels: o.channelCount };
1514
+ } catch {
1515
+ return { hasAudio: !0 };
1516
+ }
1517
+ } catch {
1518
+ return { hasAudio: !1 };
1519
+ }
1391
1520
  }
1392
1521
  /**
1393
1522
  * Get Greatest Common Divisor
@@ -1470,6 +1599,18 @@ class G {
1470
1599
  var t;
1471
1600
  return (t = this.info) == null ? void 0 : t.bitrate;
1472
1601
  }
1602
+ get frameRate() {
1603
+ var t;
1604
+ return (t = this.info) == null ? void 0 : t.frameRate;
1605
+ }
1606
+ get audioChannels() {
1607
+ var t;
1608
+ return (t = this.info) == null ? void 0 : t.audioChannels;
1609
+ }
1610
+ get hasAudio() {
1611
+ var t;
1612
+ return ((t = this.info) == null ? void 0 : t.hasAudio) || !1;
1613
+ }
1473
1614
  get quality() {
1474
1615
  if (!this.info) return "unknown";
1475
1616
  const t = this.info.height;
@@ -1485,7 +1626,8 @@ class G {
1485
1626
  * Clean up resources
1486
1627
  */
1487
1628
  destroy() {
1488
- this.videoElement && (this.videoElement.remove(), this.videoElement = null), this.info = null;
1629
+ this.videoElement && (this.videoElement.pause(), this.videoElement.remove(), this.videoElement = null), this.audioContext && (this.audioContext.close().catch(() => {
1630
+ }), this.audioContext = null), this.info = null;
1489
1631
  }
1490
1632
  }
1491
1633
  const K = (c) => {
@@ -1494,107 +1636,107 @@ const K = (c) => {
1494
1636
  autoplay: e,
1495
1637
  muted: i,
1496
1638
  controls: n = !0,
1497
- poster: s,
1498
- preload: r,
1639
+ poster: o,
1640
+ preload: s,
1499
1641
  theme: a,
1500
1642
  s3Config: l,
1501
1643
  analytics: u,
1502
1644
  hlsConfig: m,
1503
- subtitles: g,
1504
- stickyControls: v,
1505
- onReady: x,
1506
- onPlay: S,
1645
+ subtitles: f,
1646
+ stickyControls: y,
1647
+ onReady: v,
1648
+ onPlay: g,
1507
1649
  onPause: C,
1508
1650
  onEnded: L,
1509
1651
  onTimeUpdate: T,
1510
1652
  onVolumeChange: I,
1511
- onError: M,
1512
- onLoadedMetadata: q,
1513
- onQualityChange: P,
1514
- style: U,
1515
- className: D,
1516
- width: f = "100%",
1517
- height: w = "500px"
1518
- } = c, y = b(null), B = b(null);
1519
- return E(() => {
1520
- if (!y.current) return;
1521
- const H = {
1653
+ onError: A,
1654
+ onLoadedMetadata: R,
1655
+ onQualityChange: M,
1656
+ style: D,
1657
+ className: O,
1658
+ width: w = "100%",
1659
+ height: b = "500px"
1660
+ } = c, k = E(null), q = E(null);
1661
+ return x(() => {
1662
+ if (!k.current) return;
1663
+ const U = {
1522
1664
  src: t,
1523
- container: y.current,
1665
+ container: k.current,
1524
1666
  autoplay: e,
1525
1667
  muted: i,
1526
1668
  controls: n,
1527
- poster: s,
1528
- preload: r,
1669
+ poster: o,
1670
+ preload: s,
1529
1671
  theme: a,
1530
1672
  s3Config: l,
1531
1673
  analytics: u,
1532
1674
  hlsConfig: m,
1533
- subtitles: g,
1534
- stickyControls: v
1535
- }, d = new A(H);
1536
- return B.current = d, S && d.on("play", S), C && d.on("pause", C), L && d.on("ended", L), M && d.on("error", (h) => {
1537
- var R;
1538
- return M((R = h.data) == null ? void 0 : R.error);
1539
- }), q && d.on("loadedmetadata", q), P && d.on("qualitychange", (h) => P(h.data.level)), T && d.on("timeupdate", (h) => T(h.data.currentTime)), I && d.on("volumechange", (h) => I(h.data.volume, h.data.muted)), x && x(d), () => {
1540
- d.destroy(), B.current = null;
1675
+ subtitles: f,
1676
+ stickyControls: y
1677
+ }, d = new $(U);
1678
+ return q.current = d, g && d.on("play", g), C && d.on("pause", C), L && d.on("ended", L), A && d.on("error", (h) => {
1679
+ var F;
1680
+ return A((F = h.data) == null ? void 0 : F.error);
1681
+ }), R && d.on("loadedmetadata", R), M && d.on("qualitychange", (h) => M(h.data.level)), T && d.on("timeupdate", (h) => T(h.data.currentTime)), I && d.on("volumechange", (h) => I(h.data.volume, h.data.muted)), v && v(d), () => {
1682
+ d.destroy(), q.current = null;
1541
1683
  };
1542
- }, [t]), /* @__PURE__ */ F(
1684
+ }, [t]), /* @__PURE__ */ P(
1543
1685
  "div",
1544
1686
  {
1545
- ref: y,
1546
- className: D,
1687
+ ref: k,
1688
+ className: O,
1547
1689
  style: {
1548
- width: typeof f == "number" ? `${f}px` : f,
1549
- height: typeof w == "number" ? `${w}px` : w,
1550
- ...U
1690
+ width: typeof w == "number" ? `${w}px` : w,
1691
+ height: typeof b == "number" ? `${b}px` : b,
1692
+ ...D
1551
1693
  }
1552
1694
  }
1553
1695
  );
1554
1696
  }, J = (c) => {
1555
- const [t, e] = k(null), [i, n] = k(null), s = b(null);
1556
- return E(() => {
1557
- if (!s.current) return;
1558
- const r = new A({
1697
+ const [t, e] = S(null), [i, n] = S(null), o = E(null);
1698
+ return x(() => {
1699
+ if (!o.current) return;
1700
+ const s = new $({
1559
1701
  ...c,
1560
- container: s.current
1702
+ container: o.current
1561
1703
  });
1562
- e(r);
1704
+ e(s);
1563
1705
  const a = () => {
1564
- n(r.getState());
1706
+ n(s.getState());
1565
1707
  };
1566
- return r.on("play", a), r.on("pause", a), r.on("timeupdate", a), r.on("volumechange", a), r.on("loadedmetadata", a), () => {
1567
- r.destroy();
1708
+ return s.on("play", a), s.on("pause", a), s.on("timeupdate", a), s.on("volumechange", a), s.on("loadedmetadata", a), () => {
1709
+ s.destroy();
1568
1710
  };
1569
1711
  }, [c.src]), {
1570
- containerRef: s,
1712
+ containerRef: o,
1571
1713
  player: t,
1572
1714
  state: i
1573
1715
  };
1574
- }, z = $.createContext({
1716
+ }, z = B.createContext({
1575
1717
  player: null,
1576
1718
  state: null
1577
1719
  }), Z = (c) => {
1578
- const { player: t, children: e } = c, [i, n] = k(t.getState());
1579
- return E(() => {
1580
- const s = () => {
1720
+ const { player: t, children: e } = c, [i, n] = S(t.getState());
1721
+ return x(() => {
1722
+ const o = () => {
1581
1723
  n(t.getState());
1582
1724
  };
1583
- return t.on("play", s), t.on("pause", s), t.on("timeupdate", s), t.on("volumechange", s), t.on("loadedmetadata", s), () => {
1725
+ return t.on("play", o), t.on("pause", o), t.on("timeupdate", o), t.on("volumechange", o), t.on("loadedmetadata", o), () => {
1584
1726
  };
1585
- }, [t]), /* @__PURE__ */ F(z.Provider, { value: { player: t, state: i }, children: e });
1727
+ }, [t]), /* @__PURE__ */ P(z.Provider, { value: { player: t, state: i }, children: e });
1586
1728
  }, tt = () => {
1587
- const c = $.useContext(z);
1729
+ const c = B.useContext(z);
1588
1730
  if (!c.player)
1589
1731
  throw new Error("useWontumPlayerContext must be used within WontumPlayerProvider");
1590
1732
  return c;
1591
1733
  };
1592
1734
  export {
1593
- W as Analytics,
1594
- Q as S3Handler,
1595
- j as UIController,
1735
+ N as Analytics,
1736
+ _ as S3Handler,
1737
+ W as UIController,
1596
1738
  G as WontumFileInfo,
1597
- A as WontumPlayer,
1739
+ $ as WontumPlayer,
1598
1740
  Z as WontumPlayerProvider,
1599
1741
  K as WontumPlayerReact,
1600
1742
  J as useWontumPlayer,