stormcloud-video-player 0.2.11 → 0.2.13

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.
@@ -75,7 +75,166 @@ var canPlay = {
75
75
  var import_react2 = require("react");
76
76
 
77
77
  // src/player/StormcloudVideoPlayer.ts
78
- var import_hls = __toESM(require("hls.js"), 1);
78
+ var import_hls2 = __toESM(require("hls.js"), 1);
79
+
80
+ // src/utils/browserCompat.ts
81
+ function getChromeVersion(ua) {
82
+ const match = ua.match(/Chrome\/(\d+)/);
83
+ return match && match[1] ? parseInt(match[1], 10) : 0;
84
+ }
85
+ function getWebKitVersion(ua) {
86
+ const match = ua.match(/AppleWebKit\/(\d+)/);
87
+ return match && match[1] ? parseInt(match[1], 10) : 0;
88
+ }
89
+ function getPlatform() {
90
+ if ("userAgentData" in navigator && navigator.userAgentData?.platform) {
91
+ return navigator.userAgentData.platform;
92
+ }
93
+ const ua = navigator.userAgent;
94
+ if (/Mac|iPhone|iPad|iPod/i.test(ua)) {
95
+ return /iPhone|iPad|iPod/i.test(ua) ? "iPhone" : "MacIntel";
96
+ }
97
+ if (/Win/i.test(ua)) {
98
+ return "Win32";
99
+ }
100
+ if (/Linux/i.test(ua)) {
101
+ return /Android/i.test(ua) ? "Linux armv8l" : "Linux x86_64";
102
+ }
103
+ if (/CrOS/i.test(ua)) {
104
+ return "CrOS";
105
+ }
106
+ return navigator.platform || "Unknown";
107
+ }
108
+ function detectBrowser() {
109
+ const ua = navigator.userAgent;
110
+ const platform = getPlatform();
111
+ let name = "Unknown";
112
+ let version = "0";
113
+ let majorVersion = 0;
114
+ let isSmartTV = false;
115
+ let isLegacyTV = false;
116
+ let supportsIMA = true;
117
+ let supportsModernJS = true;
118
+ let recommendedAdPlayer = "ima";
119
+ if (/Web0S|webOS/i.test(ua)) {
120
+ name = "LG WebOS";
121
+ isSmartTV = true;
122
+ const match = ua.match(/Web0S[/\s]*([\d.]+)/i);
123
+ version = match && match[1] ? match[1] : "Unknown";
124
+ if (version !== "Unknown") {
125
+ const parts = version.split(".");
126
+ majorVersion = parts[0] ? parseInt(parts[0], 10) : 0;
127
+ }
128
+ } else if (/Tizen/i.test(ua)) {
129
+ name = "Samsung Tizen";
130
+ isSmartTV = true;
131
+ const match = ua.match(/Tizen[/\s]*([\d.]+)/i);
132
+ version = match && match[1] ? match[1] : "Unknown";
133
+ if (version !== "Unknown") {
134
+ const parts = version.split(".");
135
+ majorVersion = parts[0] ? parseInt(parts[0], 10) : 0;
136
+ }
137
+ } else if (/SMART-TV|SmartTV/i.test(ua)) {
138
+ name = "Smart TV";
139
+ isSmartTV = true;
140
+ } else if (/NetCast/i.test(ua)) {
141
+ name = "LG NetCast";
142
+ isSmartTV = true;
143
+ isLegacyTV = true;
144
+ } else if (/BRAVIA/i.test(ua)) {
145
+ name = "Sony BRAVIA";
146
+ isSmartTV = true;
147
+ }
148
+ const chromeVersion = getChromeVersion(ua);
149
+ const webkitVersion = getWebKitVersion(ua);
150
+ if (chromeVersion > 0) {
151
+ if (!isSmartTV) {
152
+ name = "Chrome";
153
+ version = chromeVersion.toString();
154
+ majorVersion = chromeVersion;
155
+ }
156
+ if (chromeVersion < 50) {
157
+ supportsIMA = false;
158
+ supportsModernJS = false;
159
+ isLegacyTV = true;
160
+ recommendedAdPlayer = "hls";
161
+ }
162
+ }
163
+ if (webkitVersion > 0 && webkitVersion < 600) {
164
+ supportsModernJS = false;
165
+ if (isSmartTV) {
166
+ isLegacyTV = true;
167
+ supportsIMA = false;
168
+ recommendedAdPlayer = "hls";
169
+ }
170
+ }
171
+ if (typeof Promise === "undefined" || typeof Map === "undefined" || typeof Set === "undefined") {
172
+ supportsModernJS = false;
173
+ supportsIMA = false;
174
+ recommendedAdPlayer = "hls";
175
+ }
176
+ if (typeof URLSearchParams === "undefined") {
177
+ supportsModernJS = false;
178
+ }
179
+ return {
180
+ name,
181
+ version,
182
+ majorVersion,
183
+ isSmartTV,
184
+ isLegacyTV,
185
+ platform,
186
+ supportsIMA,
187
+ supportsModernJS,
188
+ recommendedAdPlayer
189
+ };
190
+ }
191
+ function supportsGoogleIMA() {
192
+ const browser = detectBrowser();
193
+ if (browser.isLegacyTV) {
194
+ return false;
195
+ }
196
+ if (typeof document === "undefined" || typeof document.createElement !== "function") {
197
+ return false;
198
+ }
199
+ try {
200
+ const video = document.createElement("video");
201
+ if (!video) {
202
+ return false;
203
+ }
204
+ } catch (e) {
205
+ return false;
206
+ }
207
+ if (typeof Promise === "undefined") {
208
+ return false;
209
+ }
210
+ return browser.supportsIMA;
211
+ }
212
+ function logBrowserInfo(debug = false) {
213
+ if (!debug) return;
214
+ const browser = detectBrowser();
215
+ const imaSupport = supportsGoogleIMA();
216
+ console.log("[StormcloudVideoPlayer] Browser Compatibility Info:", {
217
+ browser: `${browser.name} ${browser.version}`,
218
+ platform: browser.platform,
219
+ isSmartTV: browser.isSmartTV,
220
+ isLegacyTV: browser.isLegacyTV,
221
+ supportsIMA: imaSupport,
222
+ supportsModernJS: browser.supportsModernJS,
223
+ recommendedAdPlayer: browser.recommendedAdPlayer,
224
+ userAgent: navigator.userAgent
225
+ });
226
+ }
227
+ function getBrowserConfigOverrides() {
228
+ const browser = detectBrowser();
229
+ const overrides = {};
230
+ if (browser.isLegacyTV || !browser.supportsIMA) {
231
+ overrides.adPlayerType = "hls";
232
+ }
233
+ if (browser.isSmartTV) {
234
+ overrides.allowNativeHls = true;
235
+ }
236
+ return overrides;
237
+ }
79
238
 
80
239
  // src/sdk/ima.ts
81
240
  function createImaController(video, options) {
@@ -93,6 +252,14 @@ function createImaController(video, options) {
93
252
  }
94
253
  }
95
254
  function ensureImaLoaded() {
255
+ if (!supportsGoogleIMA()) {
256
+ console.warn(
257
+ "[IMA] Google IMA SDK is not supported on this browser. Please use HLS ad player instead."
258
+ );
259
+ return Promise.reject(
260
+ new Error("Google IMA SDK not supported on this browser")
261
+ );
262
+ }
96
263
  try {
97
264
  const frameEl = window.frameElement;
98
265
  const sandboxAttr = frameEl?.getAttribute?.("sandbox") || "";
@@ -588,6 +755,456 @@ function createImaController(video, options) {
588
755
  };
589
756
  }
590
757
 
758
+ // src/sdk/hlsAdPlayer.ts
759
+ var import_hls = __toESM(require("hls.js"), 1);
760
+ function createHlsAdPlayer(contentVideo, options) {
761
+ let adPlaying = false;
762
+ let originalMutedState = false;
763
+ const listeners = /* @__PURE__ */ new Map();
764
+ const licenseKey = options?.licenseKey;
765
+ let adVideoElement;
766
+ let adHls;
767
+ let adContainerEl;
768
+ let currentAd;
769
+ let sessionId;
770
+ let trackingFired = {
771
+ impression: false,
772
+ start: false,
773
+ firstQuartile: false,
774
+ midpoint: false,
775
+ thirdQuartile: false,
776
+ complete: false
777
+ };
778
+ function emit(event, payload) {
779
+ const set = listeners.get(event);
780
+ if (!set) return;
781
+ for (const fn of Array.from(set)) {
782
+ try {
783
+ fn(payload);
784
+ } catch (error) {
785
+ console.warn(`[HlsAdPlayer] Error in event listener for ${event}:`, error);
786
+ }
787
+ }
788
+ }
789
+ function generateSessionId() {
790
+ return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
791
+ }
792
+ function fireTrackingPixels(urls) {
793
+ if (!urls || urls.length === 0) return;
794
+ urls.forEach((url) => {
795
+ try {
796
+ let trackingUrl = url;
797
+ if (sessionId) {
798
+ trackingUrl = `${trackingUrl}${trackingUrl.includes("?") ? "&" : "?"}session_id=${sessionId}`;
799
+ }
800
+ if (licenseKey) {
801
+ trackingUrl = `${trackingUrl}${trackingUrl.includes("?") ? "&" : "?"}license_key=${licenseKey}`;
802
+ }
803
+ const img = new Image(1, 1);
804
+ img.src = trackingUrl;
805
+ console.log(`[HlsAdPlayer] Fired tracking pixel: ${trackingUrl}`);
806
+ } catch (error) {
807
+ console.warn(`[HlsAdPlayer] Error firing tracking pixel:`, error);
808
+ }
809
+ });
810
+ }
811
+ function parseVastXml(xmlString) {
812
+ try {
813
+ const parser = new DOMParser();
814
+ const xmlDoc = parser.parseFromString(xmlString, "text/xml");
815
+ const parserError = xmlDoc.querySelector("parsererror");
816
+ if (parserError) {
817
+ console.error("[HlsAdPlayer] XML parsing error:", parserError.textContent);
818
+ return null;
819
+ }
820
+ const adElement = xmlDoc.querySelector("Ad");
821
+ if (!adElement) {
822
+ console.warn("[HlsAdPlayer] No Ad element found in VAST XML");
823
+ return null;
824
+ }
825
+ const adId = adElement.getAttribute("id") || "unknown";
826
+ const title = xmlDoc.querySelector("AdTitle")?.textContent || "Ad";
827
+ const durationText = xmlDoc.querySelector("Duration")?.textContent || "00:00:30";
828
+ const durationParts = durationText.split(":");
829
+ const duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
830
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
831
+ const mediaFiles = [];
832
+ mediaFileElements.forEach((mf) => {
833
+ const type = mf.getAttribute("type") || "";
834
+ if (type === "application/x-mpegURL" || type.includes("m3u8")) {
835
+ const bitrateAttr = mf.getAttribute("bitrate");
836
+ const bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
837
+ mediaFiles.push({
838
+ url: mf.textContent?.trim() || "",
839
+ type,
840
+ width: parseInt(mf.getAttribute("width") || "1920", 10),
841
+ height: parseInt(mf.getAttribute("height") || "1080", 10),
842
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
843
+ });
844
+ }
845
+ });
846
+ if (mediaFiles.length === 0) {
847
+ console.warn("[HlsAdPlayer] No HLS media files found in VAST XML");
848
+ return null;
849
+ }
850
+ const trackingUrls = {
851
+ impression: [],
852
+ start: [],
853
+ firstQuartile: [],
854
+ midpoint: [],
855
+ thirdQuartile: [],
856
+ complete: [],
857
+ mute: [],
858
+ unmute: [],
859
+ pause: [],
860
+ resume: [],
861
+ fullscreen: [],
862
+ exitFullscreen: [],
863
+ skip: [],
864
+ error: []
865
+ };
866
+ xmlDoc.querySelectorAll("Impression").forEach((el) => {
867
+ const url = el.textContent?.trim();
868
+ if (url) trackingUrls.impression.push(url);
869
+ });
870
+ xmlDoc.querySelectorAll("Tracking").forEach((el) => {
871
+ const event = el.getAttribute("event");
872
+ const url = el.textContent?.trim();
873
+ if (event && url) {
874
+ const eventKey = event;
875
+ if (trackingUrls[eventKey]) {
876
+ trackingUrls[eventKey].push(url);
877
+ }
878
+ }
879
+ });
880
+ const clickThrough = xmlDoc.querySelector("ClickThrough")?.textContent?.trim();
881
+ return {
882
+ id: adId,
883
+ title,
884
+ duration,
885
+ mediaFiles,
886
+ trackingUrls,
887
+ clickThrough
888
+ };
889
+ } catch (error) {
890
+ console.error("[HlsAdPlayer] Error parsing VAST XML:", error);
891
+ return null;
892
+ }
893
+ }
894
+ function createAdVideoElement() {
895
+ const video = document.createElement("video");
896
+ video.style.position = "absolute";
897
+ video.style.left = "0";
898
+ video.style.top = "0";
899
+ video.style.width = "100%";
900
+ video.style.height = "100%";
901
+ video.style.objectFit = "contain";
902
+ video.style.backgroundColor = "#000";
903
+ video.playsInline = true;
904
+ video.muted = false;
905
+ return video;
906
+ }
907
+ function setupAdEventListeners() {
908
+ if (!adVideoElement || !currentAd) return;
909
+ adVideoElement.addEventListener("timeupdate", () => {
910
+ if (!currentAd || !adVideoElement) return;
911
+ const progress = adVideoElement.currentTime / currentAd.duration;
912
+ if (progress >= 0.25 && !trackingFired.firstQuartile) {
913
+ trackingFired.firstQuartile = true;
914
+ fireTrackingPixels(currentAd.trackingUrls.firstQuartile);
915
+ }
916
+ if (progress >= 0.5 && !trackingFired.midpoint) {
917
+ trackingFired.midpoint = true;
918
+ fireTrackingPixels(currentAd.trackingUrls.midpoint);
919
+ }
920
+ if (progress >= 0.75 && !trackingFired.thirdQuartile) {
921
+ trackingFired.thirdQuartile = true;
922
+ fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
923
+ }
924
+ });
925
+ adVideoElement.addEventListener("playing", () => {
926
+ if (!currentAd || trackingFired.start) return;
927
+ trackingFired.start = true;
928
+ fireTrackingPixels(currentAd.trackingUrls.start);
929
+ console.log("[HlsAdPlayer] Ad started playing");
930
+ });
931
+ adVideoElement.addEventListener("ended", () => {
932
+ if (!currentAd || trackingFired.complete) return;
933
+ trackingFired.complete = true;
934
+ fireTrackingPixels(currentAd.trackingUrls.complete);
935
+ console.log("[HlsAdPlayer] Ad completed");
936
+ handleAdComplete();
937
+ });
938
+ adVideoElement.addEventListener("error", (e) => {
939
+ console.error("[HlsAdPlayer] Ad video error:", e);
940
+ if (currentAd) {
941
+ fireTrackingPixels(currentAd.trackingUrls.error);
942
+ }
943
+ handleAdError();
944
+ });
945
+ adVideoElement.addEventListener("volumechange", () => {
946
+ if (!currentAd) return;
947
+ if (adVideoElement.muted) {
948
+ fireTrackingPixels(currentAd.trackingUrls.mute);
949
+ } else {
950
+ fireTrackingPixels(currentAd.trackingUrls.unmute);
951
+ }
952
+ });
953
+ adVideoElement.addEventListener("pause", () => {
954
+ if (currentAd && !adVideoElement.ended) {
955
+ fireTrackingPixels(currentAd.trackingUrls.pause);
956
+ }
957
+ });
958
+ adVideoElement.addEventListener("play", () => {
959
+ if (currentAd && adVideoElement.currentTime > 0) {
960
+ fireTrackingPixels(currentAd.trackingUrls.resume);
961
+ }
962
+ });
963
+ }
964
+ function handleAdComplete() {
965
+ console.log("[HlsAdPlayer] Handling ad completion");
966
+ adPlaying = false;
967
+ contentVideo.muted = originalMutedState;
968
+ if (adContainerEl) {
969
+ adContainerEl.style.display = "none";
970
+ adContainerEl.style.pointerEvents = "none";
971
+ }
972
+ if (!options?.continueLiveStreamDuringAds) {
973
+ contentVideo.play().catch(() => {
974
+ });
975
+ console.log("[HlsAdPlayer] Content resumed (VOD mode)");
976
+ } else {
977
+ console.log("[HlsAdPlayer] Content unmuted (Live mode)");
978
+ }
979
+ emit("content_resume");
980
+ emit("all_ads_completed");
981
+ }
982
+ function handleAdError() {
983
+ console.log("[HlsAdPlayer] Handling ad error");
984
+ adPlaying = false;
985
+ contentVideo.muted = originalMutedState;
986
+ if (adContainerEl) {
987
+ adContainerEl.style.display = "none";
988
+ adContainerEl.style.pointerEvents = "none";
989
+ }
990
+ if (!options?.continueLiveStreamDuringAds) {
991
+ if (contentVideo.paused) {
992
+ contentVideo.play().catch(() => {
993
+ });
994
+ }
995
+ }
996
+ emit("ad_error");
997
+ }
998
+ return {
999
+ initialize() {
1000
+ console.log("[HlsAdPlayer] Initializing");
1001
+ if (!adContainerEl) {
1002
+ const container = document.createElement("div");
1003
+ container.style.position = "absolute";
1004
+ container.style.left = "0";
1005
+ container.style.top = "0";
1006
+ container.style.right = "0";
1007
+ container.style.bottom = "0";
1008
+ container.style.display = "none";
1009
+ container.style.alignItems = "center";
1010
+ container.style.justifyContent = "center";
1011
+ container.style.pointerEvents = "none";
1012
+ container.style.zIndex = "2";
1013
+ container.style.backgroundColor = "#000";
1014
+ contentVideo.parentElement?.appendChild(container);
1015
+ adContainerEl = container;
1016
+ }
1017
+ },
1018
+ async requestAds(vastTagUrl) {
1019
+ console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
1020
+ if (adPlaying) {
1021
+ console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
1022
+ return Promise.reject(new Error("Ad already playing"));
1023
+ }
1024
+ try {
1025
+ sessionId = generateSessionId();
1026
+ const response = await fetch(vastTagUrl);
1027
+ if (!response.ok) {
1028
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
1029
+ }
1030
+ const vastXml = await response.text();
1031
+ console.log("[HlsAdPlayer] VAST XML received");
1032
+ const ad = parseVastXml(vastXml);
1033
+ if (!ad) {
1034
+ throw new Error("Failed to parse VAST XML or no ads available");
1035
+ }
1036
+ currentAd = ad;
1037
+ console.log(`[HlsAdPlayer] Ad parsed: ${ad.title}, duration: ${ad.duration}s`);
1038
+ fireTrackingPixels(ad.trackingUrls.impression);
1039
+ trackingFired.impression = true;
1040
+ return Promise.resolve();
1041
+ } catch (error) {
1042
+ console.error("[HlsAdPlayer] Error requesting ads:", error);
1043
+ emit("ad_error");
1044
+ return Promise.reject(error);
1045
+ }
1046
+ },
1047
+ async play() {
1048
+ if (!currentAd) {
1049
+ console.warn("[HlsAdPlayer] Cannot play: No ad loaded");
1050
+ return Promise.reject(new Error("No ad loaded"));
1051
+ }
1052
+ console.log("[HlsAdPlayer] Starting ad playback");
1053
+ try {
1054
+ if (!adVideoElement) {
1055
+ adVideoElement = createAdVideoElement();
1056
+ adContainerEl?.appendChild(adVideoElement);
1057
+ setupAdEventListeners();
1058
+ }
1059
+ trackingFired = {
1060
+ impression: trackingFired.impression,
1061
+ start: false,
1062
+ firstQuartile: false,
1063
+ midpoint: false,
1064
+ thirdQuartile: false,
1065
+ complete: false
1066
+ };
1067
+ if (!options?.continueLiveStreamDuringAds) {
1068
+ contentVideo.pause();
1069
+ console.log("[HlsAdPlayer] Content paused (VOD mode)");
1070
+ } else {
1071
+ console.log("[HlsAdPlayer] Content continues (Live mode)");
1072
+ }
1073
+ contentVideo.muted = true;
1074
+ adPlaying = true;
1075
+ if (adContainerEl) {
1076
+ adContainerEl.style.display = "flex";
1077
+ adContainerEl.style.pointerEvents = "auto";
1078
+ }
1079
+ emit("content_pause");
1080
+ const mediaFile = currentAd.mediaFiles[0];
1081
+ if (!mediaFile) {
1082
+ throw new Error("No media file available for ad");
1083
+ }
1084
+ console.log(`[HlsAdPlayer] Loading ad from: ${mediaFile.url}`);
1085
+ if (import_hls.default.isSupported()) {
1086
+ if (adHls) {
1087
+ adHls.destroy();
1088
+ }
1089
+ adHls = new import_hls.default({
1090
+ enableWorker: true,
1091
+ lowLatencyMode: false
1092
+ });
1093
+ adHls.loadSource(mediaFile.url);
1094
+ adHls.attachMedia(adVideoElement);
1095
+ adHls.on(import_hls.default.Events.MANIFEST_PARSED, () => {
1096
+ console.log("[HlsAdPlayer] HLS manifest parsed, starting playback");
1097
+ adVideoElement.play().catch((error) => {
1098
+ console.error("[HlsAdPlayer] Error starting ad playback:", error);
1099
+ handleAdError();
1100
+ });
1101
+ });
1102
+ adHls.on(import_hls.default.Events.ERROR, (event, data) => {
1103
+ console.error("[HlsAdPlayer] HLS error:", data);
1104
+ if (data.fatal) {
1105
+ handleAdError();
1106
+ }
1107
+ });
1108
+ } else if (adVideoElement.canPlayType("application/vnd.apple.mpegurl")) {
1109
+ adVideoElement.src = mediaFile.url;
1110
+ adVideoElement.play().catch((error) => {
1111
+ console.error("[HlsAdPlayer] Error starting ad playback:", error);
1112
+ handleAdError();
1113
+ });
1114
+ } else {
1115
+ throw new Error("HLS not supported");
1116
+ }
1117
+ return Promise.resolve();
1118
+ } catch (error) {
1119
+ console.error("[HlsAdPlayer] Error playing ad:", error);
1120
+ handleAdError();
1121
+ return Promise.reject(error);
1122
+ }
1123
+ },
1124
+ async stop() {
1125
+ console.log("[HlsAdPlayer] Stopping ad");
1126
+ adPlaying = false;
1127
+ contentVideo.muted = originalMutedState;
1128
+ if (adContainerEl) {
1129
+ adContainerEl.style.display = "none";
1130
+ adContainerEl.style.pointerEvents = "none";
1131
+ }
1132
+ if (adHls) {
1133
+ adHls.destroy();
1134
+ adHls = void 0;
1135
+ }
1136
+ if (adVideoElement) {
1137
+ adVideoElement.pause();
1138
+ adVideoElement.src = "";
1139
+ }
1140
+ if (!options?.continueLiveStreamDuringAds) {
1141
+ contentVideo.play().catch(() => {
1142
+ });
1143
+ }
1144
+ currentAd = void 0;
1145
+ },
1146
+ destroy() {
1147
+ console.log("[HlsAdPlayer] Destroying");
1148
+ adPlaying = false;
1149
+ contentVideo.muted = originalMutedState;
1150
+ if (adHls) {
1151
+ adHls.destroy();
1152
+ adHls = void 0;
1153
+ }
1154
+ if (adVideoElement) {
1155
+ adVideoElement.pause();
1156
+ adVideoElement.src = "";
1157
+ adVideoElement.remove();
1158
+ adVideoElement = void 0;
1159
+ }
1160
+ if (adContainerEl?.parentElement) {
1161
+ adContainerEl.parentElement.removeChild(adContainerEl);
1162
+ }
1163
+ adContainerEl = void 0;
1164
+ currentAd = void 0;
1165
+ listeners.clear();
1166
+ },
1167
+ isAdPlaying() {
1168
+ return adPlaying;
1169
+ },
1170
+ resize(width, height) {
1171
+ console.log(`[HlsAdPlayer] Resizing to ${width}x${height}`);
1172
+ if (adContainerEl) {
1173
+ adContainerEl.style.width = `${width}px`;
1174
+ adContainerEl.style.height = `${height}px`;
1175
+ }
1176
+ if (adVideoElement) {
1177
+ adVideoElement.style.width = `${width}px`;
1178
+ adVideoElement.style.height = `${height}px`;
1179
+ }
1180
+ },
1181
+ on(event, listener) {
1182
+ if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
1183
+ listeners.get(event).add(listener);
1184
+ },
1185
+ off(event, listener) {
1186
+ listeners.get(event)?.delete(listener);
1187
+ },
1188
+ updateOriginalMutedState(muted) {
1189
+ originalMutedState = muted;
1190
+ },
1191
+ getOriginalMutedState() {
1192
+ return originalMutedState;
1193
+ },
1194
+ setAdVolume(volume) {
1195
+ if (adVideoElement && adPlaying) {
1196
+ adVideoElement.volume = Math.max(0, Math.min(1, volume));
1197
+ }
1198
+ },
1199
+ getAdVolume() {
1200
+ if (adVideoElement && adPlaying) {
1201
+ return adVideoElement.volume;
1202
+ }
1203
+ return 1;
1204
+ }
1205
+ };
1206
+ }
1207
+
591
1208
  // src/utils/tracking.ts
592
1209
  var cachedBrowserId = null;
593
1210
  function getClientInfo() {
@@ -739,10 +1356,18 @@ async function getBrowserID(clientInfo) {
739
1356
  if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
740
1357
  try {
741
1358
  await crypto.subtle.digest("SHA-256", new Uint8Array([1, 2, 3]));
742
- const hashBuffer = await crypto.subtle.digest(
743
- "SHA-256",
744
- new TextEncoder().encode(fingerprintString)
745
- );
1359
+ let encodedData;
1360
+ if (typeof TextEncoder !== "undefined") {
1361
+ encodedData = new TextEncoder().encode(fingerprintString);
1362
+ } else {
1363
+ const utf8 = unescape(encodeURIComponent(fingerprintString));
1364
+ const buffer = new Uint8Array(utf8.length);
1365
+ for (let i = 0; i < utf8.length; i++) {
1366
+ buffer[i] = utf8.charCodeAt(i);
1367
+ }
1368
+ encodedData = buffer;
1369
+ }
1370
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encodedData);
746
1371
  const hashArray = Array.from(new Uint8Array(hashBuffer));
747
1372
  const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
748
1373
  cachedBrowserId = hashHex;
@@ -829,6 +1454,215 @@ async function sendHeartbeat(licenseKey) {
829
1454
  }
830
1455
  }
831
1456
 
1457
+ // src/utils/polyfills.ts
1458
+ function polyfillURLSearchParams() {
1459
+ if (typeof URLSearchParams !== "undefined") {
1460
+ return;
1461
+ }
1462
+ class URLSearchParamsPolyfill {
1463
+ constructor(init) {
1464
+ this.params = /* @__PURE__ */ new Map();
1465
+ if (typeof init === "string") {
1466
+ this.parseQueryString(init);
1467
+ } else if (init instanceof URLSearchParamsPolyfill) {
1468
+ init.forEach((value, key) => {
1469
+ this.append(key, value);
1470
+ });
1471
+ }
1472
+ }
1473
+ parseQueryString(query) {
1474
+ const cleanQuery = query.startsWith("?") ? query.slice(1) : query;
1475
+ if (!cleanQuery) return;
1476
+ cleanQuery.split("&").forEach((param) => {
1477
+ const [key, value] = param.split("=");
1478
+ if (key) {
1479
+ const decodedKey = this.safeDecodeURIComponent(key);
1480
+ const decodedValue = value ? this.safeDecodeURIComponent(value) : "";
1481
+ this.append(decodedKey, decodedValue);
1482
+ }
1483
+ });
1484
+ }
1485
+ safeDecodeURIComponent(str) {
1486
+ try {
1487
+ return decodeURIComponent(str.replace(/\+/g, " "));
1488
+ } catch (e) {
1489
+ return str;
1490
+ }
1491
+ }
1492
+ append(name, value) {
1493
+ const values = this.params.get(name) || [];
1494
+ values.push(String(value));
1495
+ this.params.set(name, values);
1496
+ }
1497
+ delete(name) {
1498
+ this.params.delete(name);
1499
+ }
1500
+ get(name) {
1501
+ const values = this.params.get(name);
1502
+ return values && values.length > 0 && values[0] !== void 0 ? values[0] : null;
1503
+ }
1504
+ getAll(name) {
1505
+ return this.params.get(name) || [];
1506
+ }
1507
+ has(name) {
1508
+ return this.params.has(name);
1509
+ }
1510
+ set(name, value) {
1511
+ this.params.set(name, [String(value)]);
1512
+ }
1513
+ forEach(callback) {
1514
+ this.params.forEach((values, key) => {
1515
+ values.forEach((value) => {
1516
+ callback(value, key, this);
1517
+ });
1518
+ });
1519
+ }
1520
+ toString() {
1521
+ const parts = [];
1522
+ this.params.forEach((values, key) => {
1523
+ values.forEach((value) => {
1524
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
1525
+ });
1526
+ });
1527
+ return parts.join("&");
1528
+ }
1529
+ }
1530
+ window.URLSearchParams = URLSearchParamsPolyfill;
1531
+ }
1532
+ function polyfillTextEncoder() {
1533
+ if (typeof TextEncoder !== "undefined") {
1534
+ return;
1535
+ }
1536
+ class TextEncoderPolyfill {
1537
+ constructor() {
1538
+ this.encoding = "utf-8";
1539
+ }
1540
+ encode(str) {
1541
+ const utf8 = [];
1542
+ for (let i = 0; i < str.length; i++) {
1543
+ let charcode = str.charCodeAt(i);
1544
+ if (charcode < 128) {
1545
+ utf8.push(charcode);
1546
+ } else if (charcode < 2048) {
1547
+ utf8.push(192 | charcode >> 6, 128 | charcode & 63);
1548
+ } else if (charcode < 55296 || charcode >= 57344) {
1549
+ utf8.push(
1550
+ 224 | charcode >> 12,
1551
+ 128 | charcode >> 6 & 63,
1552
+ 128 | charcode & 63
1553
+ );
1554
+ } else {
1555
+ i++;
1556
+ charcode = 65536 + ((charcode & 1023) << 10 | str.charCodeAt(i) & 1023);
1557
+ utf8.push(
1558
+ 240 | charcode >> 18,
1559
+ 128 | charcode >> 12 & 63,
1560
+ 128 | charcode >> 6 & 63,
1561
+ 128 | charcode & 63
1562
+ );
1563
+ }
1564
+ }
1565
+ return new Uint8Array(utf8);
1566
+ }
1567
+ }
1568
+ window.TextEncoder = TextEncoderPolyfill;
1569
+ }
1570
+ function polyfillPromiseFinally() {
1571
+ if (typeof Promise !== "undefined" && !Promise.prototype.finally) {
1572
+ Promise.prototype.finally = function(callback) {
1573
+ const constructor = this.constructor;
1574
+ return this.then(
1575
+ (value) => constructor.resolve(callback()).then(() => value),
1576
+ (reason) => constructor.resolve(callback()).then(() => {
1577
+ throw reason;
1578
+ })
1579
+ );
1580
+ };
1581
+ }
1582
+ }
1583
+ function polyfillObjectAssign() {
1584
+ if (typeof Object.assign !== "function") {
1585
+ Object.assign = function(target, ...sources) {
1586
+ if (target == null) {
1587
+ throw new TypeError("Cannot convert undefined or null to object");
1588
+ }
1589
+ const to = Object(target);
1590
+ for (let i = 0; i < sources.length; i++) {
1591
+ const nextSource = sources[i];
1592
+ if (nextSource != null) {
1593
+ for (const nextKey in nextSource) {
1594
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
1595
+ to[nextKey] = nextSource[nextKey];
1596
+ }
1597
+ }
1598
+ }
1599
+ }
1600
+ return to;
1601
+ };
1602
+ }
1603
+ }
1604
+ function polyfillArrayFrom() {
1605
+ if (!Array.from) {
1606
+ Array.from = function(arrayLike, mapFn, thisArg) {
1607
+ const items = Object(arrayLike);
1608
+ if (arrayLike == null) {
1609
+ throw new TypeError("Array.from requires an array-like object");
1610
+ }
1611
+ const len = items.length >>> 0;
1612
+ const result = new Array(len);
1613
+ for (let i = 0; i < len; i++) {
1614
+ if (mapFn) {
1615
+ result[i] = mapFn.call(thisArg, items[i], i);
1616
+ } else {
1617
+ result[i] = items[i];
1618
+ }
1619
+ }
1620
+ return result;
1621
+ };
1622
+ }
1623
+ }
1624
+ function polyfillStringStartsWith() {
1625
+ if (!String.prototype.startsWith) {
1626
+ String.prototype.startsWith = function(search, pos) {
1627
+ pos = !pos || pos < 0 ? 0 : +pos;
1628
+ return this.substring(pos, pos + search.length) === search;
1629
+ };
1630
+ }
1631
+ }
1632
+ function polyfillStringEndsWith() {
1633
+ if (!String.prototype.endsWith) {
1634
+ String.prototype.endsWith = function(search, length) {
1635
+ if (length === void 0 || length > this.length) {
1636
+ length = this.length;
1637
+ }
1638
+ return this.substring(length - search.length, length) === search;
1639
+ };
1640
+ }
1641
+ }
1642
+ function polyfillStringIncludes() {
1643
+ if (!String.prototype.includes) {
1644
+ String.prototype.includes = function(search, start) {
1645
+ if (typeof start !== "number") {
1646
+ start = 0;
1647
+ }
1648
+ if (start + search.length > this.length) {
1649
+ return false;
1650
+ }
1651
+ return this.indexOf(search, start) !== -1;
1652
+ };
1653
+ }
1654
+ }
1655
+ function initializePolyfills() {
1656
+ polyfillObjectAssign();
1657
+ polyfillArrayFrom();
1658
+ polyfillStringStartsWith();
1659
+ polyfillStringEndsWith();
1660
+ polyfillStringIncludes();
1661
+ polyfillURLSearchParams();
1662
+ polyfillTextEncoder();
1663
+ polyfillPromiseFinally();
1664
+ }
1665
+
832
1666
  // src/player/StormcloudVideoPlayer.ts
833
1667
  var StormcloudVideoPlayer = class {
834
1668
  constructor(config) {
@@ -841,11 +1675,40 @@ var StormcloudVideoPlayer = class {
841
1675
  this.totalAdsInBreak = 0;
842
1676
  this.showAds = false;
843
1677
  this.isLiveStream = false;
844
- this.config = config;
1678
+ initializePolyfills();
1679
+ const browserOverrides = getBrowserConfigOverrides();
1680
+ this.config = { ...config, ...browserOverrides };
845
1681
  this.video = config.videoElement;
846
- this.ima = createImaController(this.video, {
847
- continueLiveStreamDuringAds: false
848
- });
1682
+ logBrowserInfo(config.debugAdTiming);
1683
+ this.ima = this.createAdPlayer(false);
1684
+ }
1685
+ createAdPlayer(continueLiveStreamDuringAds) {
1686
+ const vastMode = this.config.vastMode || "default";
1687
+ let adPlayerType = this.config.adPlayerType || (vastMode === "adstorm" ? "hls" : "ima");
1688
+ if (adPlayerType === "ima" && !supportsGoogleIMA()) {
1689
+ if (this.config.debugAdTiming) {
1690
+ console.warn(
1691
+ "[StormcloudVideoPlayer] Google IMA SDK not supported on this browser, falling back to HLS ad player"
1692
+ );
1693
+ }
1694
+ adPlayerType = "hls";
1695
+ }
1696
+ if (adPlayerType === "hls") {
1697
+ if (this.config.debugAdTiming) {
1698
+ console.log("[StormcloudVideoPlayer] Creating HLS ad player (AdStorm mode)");
1699
+ }
1700
+ return createHlsAdPlayer(this.video, {
1701
+ continueLiveStreamDuringAds,
1702
+ ...this.config.licenseKey ? { licenseKey: this.config.licenseKey } : {}
1703
+ });
1704
+ } else {
1705
+ if (this.config.debugAdTiming) {
1706
+ console.log("[StormcloudVideoPlayer] Creating Google IMA ad player (Default mode)");
1707
+ }
1708
+ return createImaController(this.video, {
1709
+ continueLiveStreamDuringAds
1710
+ });
1711
+ }
849
1712
  }
850
1713
  async load() {
851
1714
  if (!this.attached) {
@@ -876,9 +1739,7 @@ var StormcloudVideoPlayer = class {
876
1739
  );
877
1740
  }
878
1741
  this.ima.destroy();
879
- this.ima = createImaController(this.video, {
880
- continueLiveStreamDuringAds: false
881
- });
1742
+ this.ima = this.createAdPlayer(false);
882
1743
  this.ima.initialize();
883
1744
  if (this.config.autoplay) {
884
1745
  await this.video.play()?.catch(() => {
@@ -886,7 +1747,7 @@ var StormcloudVideoPlayer = class {
886
1747
  }
887
1748
  return;
888
1749
  }
889
- this.hls = new import_hls.default({
1750
+ this.hls = new import_hls2.default({
890
1751
  enableWorker: true,
891
1752
  backBufferLength: 30,
892
1753
  liveDurationInfinity: true,
@@ -894,10 +1755,10 @@ var StormcloudVideoPlayer = class {
894
1755
  maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
895
1756
  ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
896
1757
  });
897
- this.hls.on(import_hls.default.Events.MEDIA_ATTACHED, () => {
1758
+ this.hls.on(import_hls2.default.Events.MEDIA_ATTACHED, () => {
898
1759
  this.hls?.loadSource(this.config.src);
899
1760
  });
900
- this.hls.on(import_hls.default.Events.MANIFEST_PARSED, async (_, data) => {
1761
+ this.hls.on(import_hls2.default.Events.MANIFEST_PARSED, async (_, data) => {
901
1762
  this.isLiveStream = this.hls?.levels?.some(
902
1763
  (level) => level?.details?.live === true || level?.details?.type === "LIVE"
903
1764
  ) ?? false;
@@ -910,16 +1771,14 @@ var StormcloudVideoPlayer = class {
910
1771
  });
911
1772
  }
912
1773
  this.ima.destroy();
913
- this.ima = createImaController(this.video, {
914
- continueLiveStreamDuringAds: this.shouldContinueLiveStreamDuringAds()
915
- });
1774
+ this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
916
1775
  this.ima.initialize();
917
1776
  if (this.config.autoplay) {
918
1777
  await this.video.play()?.catch(() => {
919
1778
  });
920
1779
  }
921
1780
  });
922
- this.hls.on(import_hls.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
1781
+ this.hls.on(import_hls2.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
923
1782
  const id3Tags = (data?.samples || []).map((s) => ({
924
1783
  key: "ID3",
925
1784
  value: s?.data,
@@ -927,7 +1786,7 @@ var StormcloudVideoPlayer = class {
927
1786
  }));
928
1787
  id3Tags.forEach((tag) => this.onId3Tag(tag));
929
1788
  });
930
- this.hls.on(import_hls.default.Events.FRAG_CHANGED, (_evt, data) => {
1789
+ this.hls.on(import_hls2.default.Events.FRAG_CHANGED, (_evt, data) => {
931
1790
  const frag = data?.frag;
932
1791
  const tagList = frag?.tagList;
933
1792
  if (!Array.isArray(tagList)) return;
@@ -987,13 +1846,13 @@ var StormcloudVideoPlayer = class {
987
1846
  }
988
1847
  }
989
1848
  });
990
- this.hls.on(import_hls.default.Events.ERROR, (_evt, data) => {
1849
+ this.hls.on(import_hls2.default.Events.ERROR, (_evt, data) => {
991
1850
  if (data?.fatal) {
992
1851
  switch (data.type) {
993
- case import_hls.default.ErrorTypes.NETWORK_ERROR:
1852
+ case import_hls2.default.ErrorTypes.NETWORK_ERROR:
994
1853
  this.hls?.startLoad();
995
1854
  break;
996
- case import_hls.default.ErrorTypes.MEDIA_ERROR:
1855
+ case import_hls2.default.ErrorTypes.MEDIA_ERROR:
997
1856
  this.hls?.recoverMediaError();
998
1857
  break;
999
1858
  default:
@@ -1504,6 +2363,42 @@ var StormcloudVideoPlayer = class {
1504
2363
  }
1505
2364
  }
1506
2365
  async fetchAdConfiguration() {
2366
+ const vastMode = this.config.vastMode || "default";
2367
+ if (this.config.debugAdTiming) {
2368
+ console.log(
2369
+ "[StormcloudVideoPlayer] VAST mode:",
2370
+ vastMode
2371
+ );
2372
+ }
2373
+ if (vastMode === "adstorm") {
2374
+ if (!this.config.licenseKey) {
2375
+ if (this.config.debugAdTiming) {
2376
+ console.warn(
2377
+ "[StormcloudVideoPlayer] AdStorm mode requires a license key"
2378
+ );
2379
+ }
2380
+ return;
2381
+ }
2382
+ const vastEndpoint = `https://adstorm.co/api-adstorm-dev/adstorm/vast/${this.config.licenseKey}`;
2383
+ this.apiVastTagUrl = vastEndpoint;
2384
+ if (this.config.debugAdTiming) {
2385
+ console.log(
2386
+ "[StormcloudVideoPlayer] Using AdStorm VAST endpoint:",
2387
+ vastEndpoint
2388
+ );
2389
+ }
2390
+ return;
2391
+ }
2392
+ if (this.config.vastTagUrl) {
2393
+ this.apiVastTagUrl = this.config.vastTagUrl;
2394
+ if (this.config.debugAdTiming) {
2395
+ console.log(
2396
+ "[StormcloudVideoPlayer] Using custom VAST tag URL:",
2397
+ this.apiVastTagUrl
2398
+ );
2399
+ }
2400
+ return;
2401
+ }
1507
2402
  const apiUrl = "https://adstorm.co/api-adstorm-dev/adstorm/ads/web";
1508
2403
  if (this.config.debugAdTiming) {
1509
2404
  console.log(
@@ -1517,7 +2412,12 @@ var StormcloudVideoPlayer = class {
1517
2412
  }
1518
2413
  const response = await fetch(apiUrl, { headers });
1519
2414
  if (!response.ok) {
1520
- throw new Error(`Failed to fetch ad configuration: ${response.status}`);
2415
+ if (this.config.debugAdTiming) {
2416
+ console.warn(
2417
+ `[StormcloudVideoPlayer] Failed to fetch ad configuration: ${response.status}`
2418
+ );
2419
+ }
2420
+ return;
1521
2421
  }
1522
2422
  const data = await response.json();
1523
2423
  const imaPayload = data.response?.ima?.["publisherdesk.ima"]?.payload;
@@ -1525,17 +2425,16 @@ var StormcloudVideoPlayer = class {
1525
2425
  this.apiVastTagUrl = decodeURIComponent(imaPayload);
1526
2426
  if (this.config.debugAdTiming) {
1527
2427
  console.log(
1528
- "[StormcloudVideoPlayer] Extracted VAST tag URL:",
2428
+ "[StormcloudVideoPlayer] Extracted VAST tag URL from /ads/web:",
1529
2429
  this.apiVastTagUrl
1530
2430
  );
1531
2431
  }
1532
- }
1533
- this.vastConfig = data.response?.options?.vast;
1534
- if (this.config.debugAdTiming) {
1535
- console.log("[StormcloudVideoPlayer] Ad configuration loaded:", {
1536
- vastTagUrl: this.apiVastTagUrl,
1537
- vastConfig: this.vastConfig
1538
- });
2432
+ } else {
2433
+ if (this.config.debugAdTiming) {
2434
+ console.warn(
2435
+ "[StormcloudVideoPlayer] No VAST tag URL found in /ads/web response"
2436
+ );
2437
+ }
1539
2438
  }
1540
2439
  }
1541
2440
  getCurrentAdIndex() {
@@ -1579,23 +2478,14 @@ var StormcloudVideoPlayer = class {
1579
2478
  );
1580
2479
  const tags = this.selectVastTagsForBreak(scheduled);
1581
2480
  let vastTagUrl;
1582
- let adsNumber = 1;
1583
2481
  if (this.apiVastTagUrl) {
1584
2482
  vastTagUrl = this.apiVastTagUrl;
1585
- if (this.vastConfig) {
1586
- const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1587
- if (isHls && this.vastConfig.cue_tones?.number_ads) {
1588
- adsNumber = this.vastConfig.cue_tones.number_ads;
1589
- } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1590
- adsNumber = this.vastConfig.timer_vod.number_ads;
1591
- }
1592
- }
1593
- this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
2483
+ this.adPodQueue = [];
1594
2484
  this.currentAdIndex = 0;
1595
- this.totalAdsInBreak = adsNumber;
2485
+ this.totalAdsInBreak = 1;
1596
2486
  if (this.config.debugAdTiming) {
1597
2487
  console.log(
1598
- `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
2488
+ "[StormcloudVideoPlayer] Using VAST endpoint:",
1599
2489
  vastTagUrl
1600
2490
  );
1601
2491
  }