stormcloud-video-player 0.2.12 → 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() {
@@ -837,6 +1454,215 @@ async function sendHeartbeat(licenseKey) {
837
1454
  }
838
1455
  }
839
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
+
840
1666
  // src/player/StormcloudVideoPlayer.ts
841
1667
  var StormcloudVideoPlayer = class {
842
1668
  constructor(config) {
@@ -849,11 +1675,40 @@ var StormcloudVideoPlayer = class {
849
1675
  this.totalAdsInBreak = 0;
850
1676
  this.showAds = false;
851
1677
  this.isLiveStream = false;
852
- this.config = config;
1678
+ initializePolyfills();
1679
+ const browserOverrides = getBrowserConfigOverrides();
1680
+ this.config = { ...config, ...browserOverrides };
853
1681
  this.video = config.videoElement;
854
- this.ima = createImaController(this.video, {
855
- continueLiveStreamDuringAds: false
856
- });
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
+ }
857
1712
  }
858
1713
  async load() {
859
1714
  if (!this.attached) {
@@ -884,9 +1739,7 @@ var StormcloudVideoPlayer = class {
884
1739
  );
885
1740
  }
886
1741
  this.ima.destroy();
887
- this.ima = createImaController(this.video, {
888
- continueLiveStreamDuringAds: false
889
- });
1742
+ this.ima = this.createAdPlayer(false);
890
1743
  this.ima.initialize();
891
1744
  if (this.config.autoplay) {
892
1745
  await this.video.play()?.catch(() => {
@@ -894,7 +1747,7 @@ var StormcloudVideoPlayer = class {
894
1747
  }
895
1748
  return;
896
1749
  }
897
- this.hls = new import_hls.default({
1750
+ this.hls = new import_hls2.default({
898
1751
  enableWorker: true,
899
1752
  backBufferLength: 30,
900
1753
  liveDurationInfinity: true,
@@ -902,10 +1755,10 @@ var StormcloudVideoPlayer = class {
902
1755
  maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
903
1756
  ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
904
1757
  });
905
- this.hls.on(import_hls.default.Events.MEDIA_ATTACHED, () => {
1758
+ this.hls.on(import_hls2.default.Events.MEDIA_ATTACHED, () => {
906
1759
  this.hls?.loadSource(this.config.src);
907
1760
  });
908
- this.hls.on(import_hls.default.Events.MANIFEST_PARSED, async (_, data) => {
1761
+ this.hls.on(import_hls2.default.Events.MANIFEST_PARSED, async (_, data) => {
909
1762
  this.isLiveStream = this.hls?.levels?.some(
910
1763
  (level) => level?.details?.live === true || level?.details?.type === "LIVE"
911
1764
  ) ?? false;
@@ -918,16 +1771,14 @@ var StormcloudVideoPlayer = class {
918
1771
  });
919
1772
  }
920
1773
  this.ima.destroy();
921
- this.ima = createImaController(this.video, {
922
- continueLiveStreamDuringAds: this.shouldContinueLiveStreamDuringAds()
923
- });
1774
+ this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
924
1775
  this.ima.initialize();
925
1776
  if (this.config.autoplay) {
926
1777
  await this.video.play()?.catch(() => {
927
1778
  });
928
1779
  }
929
1780
  });
930
- 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) => {
931
1782
  const id3Tags = (data?.samples || []).map((s) => ({
932
1783
  key: "ID3",
933
1784
  value: s?.data,
@@ -935,7 +1786,7 @@ var StormcloudVideoPlayer = class {
935
1786
  }));
936
1787
  id3Tags.forEach((tag) => this.onId3Tag(tag));
937
1788
  });
938
- this.hls.on(import_hls.default.Events.FRAG_CHANGED, (_evt, data) => {
1789
+ this.hls.on(import_hls2.default.Events.FRAG_CHANGED, (_evt, data) => {
939
1790
  const frag = data?.frag;
940
1791
  const tagList = frag?.tagList;
941
1792
  if (!Array.isArray(tagList)) return;
@@ -995,13 +1846,13 @@ var StormcloudVideoPlayer = class {
995
1846
  }
996
1847
  }
997
1848
  });
998
- this.hls.on(import_hls.default.Events.ERROR, (_evt, data) => {
1849
+ this.hls.on(import_hls2.default.Events.ERROR, (_evt, data) => {
999
1850
  if (data?.fatal) {
1000
1851
  switch (data.type) {
1001
- case import_hls.default.ErrorTypes.NETWORK_ERROR:
1852
+ case import_hls2.default.ErrorTypes.NETWORK_ERROR:
1002
1853
  this.hls?.startLoad();
1003
1854
  break;
1004
- case import_hls.default.ErrorTypes.MEDIA_ERROR:
1855
+ case import_hls2.default.ErrorTypes.MEDIA_ERROR:
1005
1856
  this.hls?.recoverMediaError();
1006
1857
  break;
1007
1858
  default:
@@ -1512,6 +2363,42 @@ var StormcloudVideoPlayer = class {
1512
2363
  }
1513
2364
  }
1514
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
+ }
1515
2402
  const apiUrl = "https://adstorm.co/api-adstorm-dev/adstorm/ads/web";
1516
2403
  if (this.config.debugAdTiming) {
1517
2404
  console.log(
@@ -1525,7 +2412,12 @@ var StormcloudVideoPlayer = class {
1525
2412
  }
1526
2413
  const response = await fetch(apiUrl, { headers });
1527
2414
  if (!response.ok) {
1528
- 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;
1529
2421
  }
1530
2422
  const data = await response.json();
1531
2423
  const imaPayload = data.response?.ima?.["publisherdesk.ima"]?.payload;
@@ -1533,17 +2425,16 @@ var StormcloudVideoPlayer = class {
1533
2425
  this.apiVastTagUrl = decodeURIComponent(imaPayload);
1534
2426
  if (this.config.debugAdTiming) {
1535
2427
  console.log(
1536
- "[StormcloudVideoPlayer] Extracted VAST tag URL:",
2428
+ "[StormcloudVideoPlayer] Extracted VAST tag URL from /ads/web:",
1537
2429
  this.apiVastTagUrl
1538
2430
  );
1539
2431
  }
1540
- }
1541
- this.vastConfig = data.response?.options?.vast;
1542
- if (this.config.debugAdTiming) {
1543
- console.log("[StormcloudVideoPlayer] Ad configuration loaded:", {
1544
- vastTagUrl: this.apiVastTagUrl,
1545
- vastConfig: this.vastConfig
1546
- });
2432
+ } else {
2433
+ if (this.config.debugAdTiming) {
2434
+ console.warn(
2435
+ "[StormcloudVideoPlayer] No VAST tag URL found in /ads/web response"
2436
+ );
2437
+ }
1547
2438
  }
1548
2439
  }
1549
2440
  getCurrentAdIndex() {
@@ -1587,23 +2478,14 @@ var StormcloudVideoPlayer = class {
1587
2478
  );
1588
2479
  const tags = this.selectVastTagsForBreak(scheduled);
1589
2480
  let vastTagUrl;
1590
- let adsNumber = 1;
1591
2481
  if (this.apiVastTagUrl) {
1592
2482
  vastTagUrl = this.apiVastTagUrl;
1593
- if (this.vastConfig) {
1594
- const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1595
- if (isHls && this.vastConfig.cue_tones?.number_ads) {
1596
- adsNumber = this.vastConfig.cue_tones.number_ads;
1597
- } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1598
- adsNumber = this.vastConfig.timer_vod.number_ads;
1599
- }
1600
- }
1601
- this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
2483
+ this.adPodQueue = [];
1602
2484
  this.currentAdIndex = 0;
1603
- this.totalAdsInBreak = adsNumber;
2485
+ this.totalAdsInBreak = 1;
1604
2486
  if (this.config.debugAdTiming) {
1605
2487
  console.log(
1606
- `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
2488
+ "[StormcloudVideoPlayer] Using VAST endpoint:",
1607
2489
  vastTagUrl
1608
2490
  );
1609
2491
  }