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.
@@ -36,7 +36,166 @@ module.exports = __toCommonJS(HlsPlayer_exports);
36
36
  var import_react = require("react");
37
37
 
38
38
  // src/player/StormcloudVideoPlayer.ts
39
- var import_hls = __toESM(require("hls.js"), 1);
39
+ var import_hls2 = __toESM(require("hls.js"), 1);
40
+
41
+ // src/utils/browserCompat.ts
42
+ function getChromeVersion(ua) {
43
+ const match = ua.match(/Chrome\/(\d+)/);
44
+ return match && match[1] ? parseInt(match[1], 10) : 0;
45
+ }
46
+ function getWebKitVersion(ua) {
47
+ const match = ua.match(/AppleWebKit\/(\d+)/);
48
+ return match && match[1] ? parseInt(match[1], 10) : 0;
49
+ }
50
+ function getPlatform() {
51
+ if ("userAgentData" in navigator && navigator.userAgentData?.platform) {
52
+ return navigator.userAgentData.platform;
53
+ }
54
+ const ua = navigator.userAgent;
55
+ if (/Mac|iPhone|iPad|iPod/i.test(ua)) {
56
+ return /iPhone|iPad|iPod/i.test(ua) ? "iPhone" : "MacIntel";
57
+ }
58
+ if (/Win/i.test(ua)) {
59
+ return "Win32";
60
+ }
61
+ if (/Linux/i.test(ua)) {
62
+ return /Android/i.test(ua) ? "Linux armv8l" : "Linux x86_64";
63
+ }
64
+ if (/CrOS/i.test(ua)) {
65
+ return "CrOS";
66
+ }
67
+ return navigator.platform || "Unknown";
68
+ }
69
+ function detectBrowser() {
70
+ const ua = navigator.userAgent;
71
+ const platform = getPlatform();
72
+ let name = "Unknown";
73
+ let version = "0";
74
+ let majorVersion = 0;
75
+ let isSmartTV = false;
76
+ let isLegacyTV = false;
77
+ let supportsIMA = true;
78
+ let supportsModernJS = true;
79
+ let recommendedAdPlayer = "ima";
80
+ if (/Web0S|webOS/i.test(ua)) {
81
+ name = "LG WebOS";
82
+ isSmartTV = true;
83
+ const match = ua.match(/Web0S[/\s]*([\d.]+)/i);
84
+ version = match && match[1] ? match[1] : "Unknown";
85
+ if (version !== "Unknown") {
86
+ const parts = version.split(".");
87
+ majorVersion = parts[0] ? parseInt(parts[0], 10) : 0;
88
+ }
89
+ } else if (/Tizen/i.test(ua)) {
90
+ name = "Samsung Tizen";
91
+ isSmartTV = true;
92
+ const match = ua.match(/Tizen[/\s]*([\d.]+)/i);
93
+ version = match && match[1] ? match[1] : "Unknown";
94
+ if (version !== "Unknown") {
95
+ const parts = version.split(".");
96
+ majorVersion = parts[0] ? parseInt(parts[0], 10) : 0;
97
+ }
98
+ } else if (/SMART-TV|SmartTV/i.test(ua)) {
99
+ name = "Smart TV";
100
+ isSmartTV = true;
101
+ } else if (/NetCast/i.test(ua)) {
102
+ name = "LG NetCast";
103
+ isSmartTV = true;
104
+ isLegacyTV = true;
105
+ } else if (/BRAVIA/i.test(ua)) {
106
+ name = "Sony BRAVIA";
107
+ isSmartTV = true;
108
+ }
109
+ const chromeVersion = getChromeVersion(ua);
110
+ const webkitVersion = getWebKitVersion(ua);
111
+ if (chromeVersion > 0) {
112
+ if (!isSmartTV) {
113
+ name = "Chrome";
114
+ version = chromeVersion.toString();
115
+ majorVersion = chromeVersion;
116
+ }
117
+ if (chromeVersion < 50) {
118
+ supportsIMA = false;
119
+ supportsModernJS = false;
120
+ isLegacyTV = true;
121
+ recommendedAdPlayer = "hls";
122
+ }
123
+ }
124
+ if (webkitVersion > 0 && webkitVersion < 600) {
125
+ supportsModernJS = false;
126
+ if (isSmartTV) {
127
+ isLegacyTV = true;
128
+ supportsIMA = false;
129
+ recommendedAdPlayer = "hls";
130
+ }
131
+ }
132
+ if (typeof Promise === "undefined" || typeof Map === "undefined" || typeof Set === "undefined") {
133
+ supportsModernJS = false;
134
+ supportsIMA = false;
135
+ recommendedAdPlayer = "hls";
136
+ }
137
+ if (typeof URLSearchParams === "undefined") {
138
+ supportsModernJS = false;
139
+ }
140
+ return {
141
+ name,
142
+ version,
143
+ majorVersion,
144
+ isSmartTV,
145
+ isLegacyTV,
146
+ platform,
147
+ supportsIMA,
148
+ supportsModernJS,
149
+ recommendedAdPlayer
150
+ };
151
+ }
152
+ function supportsGoogleIMA() {
153
+ const browser = detectBrowser();
154
+ if (browser.isLegacyTV) {
155
+ return false;
156
+ }
157
+ if (typeof document === "undefined" || typeof document.createElement !== "function") {
158
+ return false;
159
+ }
160
+ try {
161
+ const video = document.createElement("video");
162
+ if (!video) {
163
+ return false;
164
+ }
165
+ } catch (e) {
166
+ return false;
167
+ }
168
+ if (typeof Promise === "undefined") {
169
+ return false;
170
+ }
171
+ return browser.supportsIMA;
172
+ }
173
+ function logBrowserInfo(debug = false) {
174
+ if (!debug) return;
175
+ const browser = detectBrowser();
176
+ const imaSupport = supportsGoogleIMA();
177
+ console.log("[StormcloudVideoPlayer] Browser Compatibility Info:", {
178
+ browser: `${browser.name} ${browser.version}`,
179
+ platform: browser.platform,
180
+ isSmartTV: browser.isSmartTV,
181
+ isLegacyTV: browser.isLegacyTV,
182
+ supportsIMA: imaSupport,
183
+ supportsModernJS: browser.supportsModernJS,
184
+ recommendedAdPlayer: browser.recommendedAdPlayer,
185
+ userAgent: navigator.userAgent
186
+ });
187
+ }
188
+ function getBrowserConfigOverrides() {
189
+ const browser = detectBrowser();
190
+ const overrides = {};
191
+ if (browser.isLegacyTV || !browser.supportsIMA) {
192
+ overrides.adPlayerType = "hls";
193
+ }
194
+ if (browser.isSmartTV) {
195
+ overrides.allowNativeHls = true;
196
+ }
197
+ return overrides;
198
+ }
40
199
 
41
200
  // src/sdk/ima.ts
42
201
  function createImaController(video, options) {
@@ -54,6 +213,14 @@ function createImaController(video, options) {
54
213
  }
55
214
  }
56
215
  function ensureImaLoaded() {
216
+ if (!supportsGoogleIMA()) {
217
+ console.warn(
218
+ "[IMA] Google IMA SDK is not supported on this browser. Please use HLS ad player instead."
219
+ );
220
+ return Promise.reject(
221
+ new Error("Google IMA SDK not supported on this browser")
222
+ );
223
+ }
57
224
  try {
58
225
  const frameEl = window.frameElement;
59
226
  const sandboxAttr = frameEl?.getAttribute?.("sandbox") || "";
@@ -549,6 +716,456 @@ function createImaController(video, options) {
549
716
  };
550
717
  }
551
718
 
719
+ // src/sdk/hlsAdPlayer.ts
720
+ var import_hls = __toESM(require("hls.js"), 1);
721
+ function createHlsAdPlayer(contentVideo, options) {
722
+ let adPlaying = false;
723
+ let originalMutedState = false;
724
+ const listeners = /* @__PURE__ */ new Map();
725
+ const licenseKey = options?.licenseKey;
726
+ let adVideoElement;
727
+ let adHls;
728
+ let adContainerEl;
729
+ let currentAd;
730
+ let sessionId;
731
+ let trackingFired = {
732
+ impression: false,
733
+ start: false,
734
+ firstQuartile: false,
735
+ midpoint: false,
736
+ thirdQuartile: false,
737
+ complete: false
738
+ };
739
+ function emit(event, payload) {
740
+ const set = listeners.get(event);
741
+ if (!set) return;
742
+ for (const fn of Array.from(set)) {
743
+ try {
744
+ fn(payload);
745
+ } catch (error) {
746
+ console.warn(`[HlsAdPlayer] Error in event listener for ${event}:`, error);
747
+ }
748
+ }
749
+ }
750
+ function generateSessionId() {
751
+ return `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
752
+ }
753
+ function fireTrackingPixels(urls) {
754
+ if (!urls || urls.length === 0) return;
755
+ urls.forEach((url) => {
756
+ try {
757
+ let trackingUrl = url;
758
+ if (sessionId) {
759
+ trackingUrl = `${trackingUrl}${trackingUrl.includes("?") ? "&" : "?"}session_id=${sessionId}`;
760
+ }
761
+ if (licenseKey) {
762
+ trackingUrl = `${trackingUrl}${trackingUrl.includes("?") ? "&" : "?"}license_key=${licenseKey}`;
763
+ }
764
+ const img = new Image(1, 1);
765
+ img.src = trackingUrl;
766
+ console.log(`[HlsAdPlayer] Fired tracking pixel: ${trackingUrl}`);
767
+ } catch (error) {
768
+ console.warn(`[HlsAdPlayer] Error firing tracking pixel:`, error);
769
+ }
770
+ });
771
+ }
772
+ function parseVastXml(xmlString) {
773
+ try {
774
+ const parser = new DOMParser();
775
+ const xmlDoc = parser.parseFromString(xmlString, "text/xml");
776
+ const parserError = xmlDoc.querySelector("parsererror");
777
+ if (parserError) {
778
+ console.error("[HlsAdPlayer] XML parsing error:", parserError.textContent);
779
+ return null;
780
+ }
781
+ const adElement = xmlDoc.querySelector("Ad");
782
+ if (!adElement) {
783
+ console.warn("[HlsAdPlayer] No Ad element found in VAST XML");
784
+ return null;
785
+ }
786
+ const adId = adElement.getAttribute("id") || "unknown";
787
+ const title = xmlDoc.querySelector("AdTitle")?.textContent || "Ad";
788
+ const durationText = xmlDoc.querySelector("Duration")?.textContent || "00:00:30";
789
+ const durationParts = durationText.split(":");
790
+ const duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
791
+ const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
792
+ const mediaFiles = [];
793
+ mediaFileElements.forEach((mf) => {
794
+ const type = mf.getAttribute("type") || "";
795
+ if (type === "application/x-mpegURL" || type.includes("m3u8")) {
796
+ const bitrateAttr = mf.getAttribute("bitrate");
797
+ const bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
798
+ mediaFiles.push({
799
+ url: mf.textContent?.trim() || "",
800
+ type,
801
+ width: parseInt(mf.getAttribute("width") || "1920", 10),
802
+ height: parseInt(mf.getAttribute("height") || "1080", 10),
803
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
804
+ });
805
+ }
806
+ });
807
+ if (mediaFiles.length === 0) {
808
+ console.warn("[HlsAdPlayer] No HLS media files found in VAST XML");
809
+ return null;
810
+ }
811
+ const trackingUrls = {
812
+ impression: [],
813
+ start: [],
814
+ firstQuartile: [],
815
+ midpoint: [],
816
+ thirdQuartile: [],
817
+ complete: [],
818
+ mute: [],
819
+ unmute: [],
820
+ pause: [],
821
+ resume: [],
822
+ fullscreen: [],
823
+ exitFullscreen: [],
824
+ skip: [],
825
+ error: []
826
+ };
827
+ xmlDoc.querySelectorAll("Impression").forEach((el) => {
828
+ const url = el.textContent?.trim();
829
+ if (url) trackingUrls.impression.push(url);
830
+ });
831
+ xmlDoc.querySelectorAll("Tracking").forEach((el) => {
832
+ const event = el.getAttribute("event");
833
+ const url = el.textContent?.trim();
834
+ if (event && url) {
835
+ const eventKey = event;
836
+ if (trackingUrls[eventKey]) {
837
+ trackingUrls[eventKey].push(url);
838
+ }
839
+ }
840
+ });
841
+ const clickThrough = xmlDoc.querySelector("ClickThrough")?.textContent?.trim();
842
+ return {
843
+ id: adId,
844
+ title,
845
+ duration,
846
+ mediaFiles,
847
+ trackingUrls,
848
+ clickThrough
849
+ };
850
+ } catch (error) {
851
+ console.error("[HlsAdPlayer] Error parsing VAST XML:", error);
852
+ return null;
853
+ }
854
+ }
855
+ function createAdVideoElement() {
856
+ const video = document.createElement("video");
857
+ video.style.position = "absolute";
858
+ video.style.left = "0";
859
+ video.style.top = "0";
860
+ video.style.width = "100%";
861
+ video.style.height = "100%";
862
+ video.style.objectFit = "contain";
863
+ video.style.backgroundColor = "#000";
864
+ video.playsInline = true;
865
+ video.muted = false;
866
+ return video;
867
+ }
868
+ function setupAdEventListeners() {
869
+ if (!adVideoElement || !currentAd) return;
870
+ adVideoElement.addEventListener("timeupdate", () => {
871
+ if (!currentAd || !adVideoElement) return;
872
+ const progress = adVideoElement.currentTime / currentAd.duration;
873
+ if (progress >= 0.25 && !trackingFired.firstQuartile) {
874
+ trackingFired.firstQuartile = true;
875
+ fireTrackingPixels(currentAd.trackingUrls.firstQuartile);
876
+ }
877
+ if (progress >= 0.5 && !trackingFired.midpoint) {
878
+ trackingFired.midpoint = true;
879
+ fireTrackingPixels(currentAd.trackingUrls.midpoint);
880
+ }
881
+ if (progress >= 0.75 && !trackingFired.thirdQuartile) {
882
+ trackingFired.thirdQuartile = true;
883
+ fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
884
+ }
885
+ });
886
+ adVideoElement.addEventListener("playing", () => {
887
+ if (!currentAd || trackingFired.start) return;
888
+ trackingFired.start = true;
889
+ fireTrackingPixels(currentAd.trackingUrls.start);
890
+ console.log("[HlsAdPlayer] Ad started playing");
891
+ });
892
+ adVideoElement.addEventListener("ended", () => {
893
+ if (!currentAd || trackingFired.complete) return;
894
+ trackingFired.complete = true;
895
+ fireTrackingPixels(currentAd.trackingUrls.complete);
896
+ console.log("[HlsAdPlayer] Ad completed");
897
+ handleAdComplete();
898
+ });
899
+ adVideoElement.addEventListener("error", (e) => {
900
+ console.error("[HlsAdPlayer] Ad video error:", e);
901
+ if (currentAd) {
902
+ fireTrackingPixels(currentAd.trackingUrls.error);
903
+ }
904
+ handleAdError();
905
+ });
906
+ adVideoElement.addEventListener("volumechange", () => {
907
+ if (!currentAd) return;
908
+ if (adVideoElement.muted) {
909
+ fireTrackingPixels(currentAd.trackingUrls.mute);
910
+ } else {
911
+ fireTrackingPixels(currentAd.trackingUrls.unmute);
912
+ }
913
+ });
914
+ adVideoElement.addEventListener("pause", () => {
915
+ if (currentAd && !adVideoElement.ended) {
916
+ fireTrackingPixels(currentAd.trackingUrls.pause);
917
+ }
918
+ });
919
+ adVideoElement.addEventListener("play", () => {
920
+ if (currentAd && adVideoElement.currentTime > 0) {
921
+ fireTrackingPixels(currentAd.trackingUrls.resume);
922
+ }
923
+ });
924
+ }
925
+ function handleAdComplete() {
926
+ console.log("[HlsAdPlayer] Handling ad completion");
927
+ adPlaying = false;
928
+ contentVideo.muted = originalMutedState;
929
+ if (adContainerEl) {
930
+ adContainerEl.style.display = "none";
931
+ adContainerEl.style.pointerEvents = "none";
932
+ }
933
+ if (!options?.continueLiveStreamDuringAds) {
934
+ contentVideo.play().catch(() => {
935
+ });
936
+ console.log("[HlsAdPlayer] Content resumed (VOD mode)");
937
+ } else {
938
+ console.log("[HlsAdPlayer] Content unmuted (Live mode)");
939
+ }
940
+ emit("content_resume");
941
+ emit("all_ads_completed");
942
+ }
943
+ function handleAdError() {
944
+ console.log("[HlsAdPlayer] Handling ad error");
945
+ adPlaying = false;
946
+ contentVideo.muted = originalMutedState;
947
+ if (adContainerEl) {
948
+ adContainerEl.style.display = "none";
949
+ adContainerEl.style.pointerEvents = "none";
950
+ }
951
+ if (!options?.continueLiveStreamDuringAds) {
952
+ if (contentVideo.paused) {
953
+ contentVideo.play().catch(() => {
954
+ });
955
+ }
956
+ }
957
+ emit("ad_error");
958
+ }
959
+ return {
960
+ initialize() {
961
+ console.log("[HlsAdPlayer] Initializing");
962
+ if (!adContainerEl) {
963
+ const container = document.createElement("div");
964
+ container.style.position = "absolute";
965
+ container.style.left = "0";
966
+ container.style.top = "0";
967
+ container.style.right = "0";
968
+ container.style.bottom = "0";
969
+ container.style.display = "none";
970
+ container.style.alignItems = "center";
971
+ container.style.justifyContent = "center";
972
+ container.style.pointerEvents = "none";
973
+ container.style.zIndex = "2";
974
+ container.style.backgroundColor = "#000";
975
+ contentVideo.parentElement?.appendChild(container);
976
+ adContainerEl = container;
977
+ }
978
+ },
979
+ async requestAds(vastTagUrl) {
980
+ console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
981
+ if (adPlaying) {
982
+ console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
983
+ return Promise.reject(new Error("Ad already playing"));
984
+ }
985
+ try {
986
+ sessionId = generateSessionId();
987
+ const response = await fetch(vastTagUrl);
988
+ if (!response.ok) {
989
+ throw new Error(`Failed to fetch VAST: ${response.statusText}`);
990
+ }
991
+ const vastXml = await response.text();
992
+ console.log("[HlsAdPlayer] VAST XML received");
993
+ const ad = parseVastXml(vastXml);
994
+ if (!ad) {
995
+ throw new Error("Failed to parse VAST XML or no ads available");
996
+ }
997
+ currentAd = ad;
998
+ console.log(`[HlsAdPlayer] Ad parsed: ${ad.title}, duration: ${ad.duration}s`);
999
+ fireTrackingPixels(ad.trackingUrls.impression);
1000
+ trackingFired.impression = true;
1001
+ return Promise.resolve();
1002
+ } catch (error) {
1003
+ console.error("[HlsAdPlayer] Error requesting ads:", error);
1004
+ emit("ad_error");
1005
+ return Promise.reject(error);
1006
+ }
1007
+ },
1008
+ async play() {
1009
+ if (!currentAd) {
1010
+ console.warn("[HlsAdPlayer] Cannot play: No ad loaded");
1011
+ return Promise.reject(new Error("No ad loaded"));
1012
+ }
1013
+ console.log("[HlsAdPlayer] Starting ad playback");
1014
+ try {
1015
+ if (!adVideoElement) {
1016
+ adVideoElement = createAdVideoElement();
1017
+ adContainerEl?.appendChild(adVideoElement);
1018
+ setupAdEventListeners();
1019
+ }
1020
+ trackingFired = {
1021
+ impression: trackingFired.impression,
1022
+ start: false,
1023
+ firstQuartile: false,
1024
+ midpoint: false,
1025
+ thirdQuartile: false,
1026
+ complete: false
1027
+ };
1028
+ if (!options?.continueLiveStreamDuringAds) {
1029
+ contentVideo.pause();
1030
+ console.log("[HlsAdPlayer] Content paused (VOD mode)");
1031
+ } else {
1032
+ console.log("[HlsAdPlayer] Content continues (Live mode)");
1033
+ }
1034
+ contentVideo.muted = true;
1035
+ adPlaying = true;
1036
+ if (adContainerEl) {
1037
+ adContainerEl.style.display = "flex";
1038
+ adContainerEl.style.pointerEvents = "auto";
1039
+ }
1040
+ emit("content_pause");
1041
+ const mediaFile = currentAd.mediaFiles[0];
1042
+ if (!mediaFile) {
1043
+ throw new Error("No media file available for ad");
1044
+ }
1045
+ console.log(`[HlsAdPlayer] Loading ad from: ${mediaFile.url}`);
1046
+ if (import_hls.default.isSupported()) {
1047
+ if (adHls) {
1048
+ adHls.destroy();
1049
+ }
1050
+ adHls = new import_hls.default({
1051
+ enableWorker: true,
1052
+ lowLatencyMode: false
1053
+ });
1054
+ adHls.loadSource(mediaFile.url);
1055
+ adHls.attachMedia(adVideoElement);
1056
+ adHls.on(import_hls.default.Events.MANIFEST_PARSED, () => {
1057
+ console.log("[HlsAdPlayer] HLS manifest parsed, starting playback");
1058
+ adVideoElement.play().catch((error) => {
1059
+ console.error("[HlsAdPlayer] Error starting ad playback:", error);
1060
+ handleAdError();
1061
+ });
1062
+ });
1063
+ adHls.on(import_hls.default.Events.ERROR, (event, data) => {
1064
+ console.error("[HlsAdPlayer] HLS error:", data);
1065
+ if (data.fatal) {
1066
+ handleAdError();
1067
+ }
1068
+ });
1069
+ } else if (adVideoElement.canPlayType("application/vnd.apple.mpegurl")) {
1070
+ adVideoElement.src = mediaFile.url;
1071
+ adVideoElement.play().catch((error) => {
1072
+ console.error("[HlsAdPlayer] Error starting ad playback:", error);
1073
+ handleAdError();
1074
+ });
1075
+ } else {
1076
+ throw new Error("HLS not supported");
1077
+ }
1078
+ return Promise.resolve();
1079
+ } catch (error) {
1080
+ console.error("[HlsAdPlayer] Error playing ad:", error);
1081
+ handleAdError();
1082
+ return Promise.reject(error);
1083
+ }
1084
+ },
1085
+ async stop() {
1086
+ console.log("[HlsAdPlayer] Stopping ad");
1087
+ adPlaying = false;
1088
+ contentVideo.muted = originalMutedState;
1089
+ if (adContainerEl) {
1090
+ adContainerEl.style.display = "none";
1091
+ adContainerEl.style.pointerEvents = "none";
1092
+ }
1093
+ if (adHls) {
1094
+ adHls.destroy();
1095
+ adHls = void 0;
1096
+ }
1097
+ if (adVideoElement) {
1098
+ adVideoElement.pause();
1099
+ adVideoElement.src = "";
1100
+ }
1101
+ if (!options?.continueLiveStreamDuringAds) {
1102
+ contentVideo.play().catch(() => {
1103
+ });
1104
+ }
1105
+ currentAd = void 0;
1106
+ },
1107
+ destroy() {
1108
+ console.log("[HlsAdPlayer] Destroying");
1109
+ adPlaying = false;
1110
+ contentVideo.muted = originalMutedState;
1111
+ if (adHls) {
1112
+ adHls.destroy();
1113
+ adHls = void 0;
1114
+ }
1115
+ if (adVideoElement) {
1116
+ adVideoElement.pause();
1117
+ adVideoElement.src = "";
1118
+ adVideoElement.remove();
1119
+ adVideoElement = void 0;
1120
+ }
1121
+ if (adContainerEl?.parentElement) {
1122
+ adContainerEl.parentElement.removeChild(adContainerEl);
1123
+ }
1124
+ adContainerEl = void 0;
1125
+ currentAd = void 0;
1126
+ listeners.clear();
1127
+ },
1128
+ isAdPlaying() {
1129
+ return adPlaying;
1130
+ },
1131
+ resize(width, height) {
1132
+ console.log(`[HlsAdPlayer] Resizing to ${width}x${height}`);
1133
+ if (adContainerEl) {
1134
+ adContainerEl.style.width = `${width}px`;
1135
+ adContainerEl.style.height = `${height}px`;
1136
+ }
1137
+ if (adVideoElement) {
1138
+ adVideoElement.style.width = `${width}px`;
1139
+ adVideoElement.style.height = `${height}px`;
1140
+ }
1141
+ },
1142
+ on(event, listener) {
1143
+ if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
1144
+ listeners.get(event).add(listener);
1145
+ },
1146
+ off(event, listener) {
1147
+ listeners.get(event)?.delete(listener);
1148
+ },
1149
+ updateOriginalMutedState(muted) {
1150
+ originalMutedState = muted;
1151
+ },
1152
+ getOriginalMutedState() {
1153
+ return originalMutedState;
1154
+ },
1155
+ setAdVolume(volume) {
1156
+ if (adVideoElement && adPlaying) {
1157
+ adVideoElement.volume = Math.max(0, Math.min(1, volume));
1158
+ }
1159
+ },
1160
+ getAdVolume() {
1161
+ if (adVideoElement && adPlaying) {
1162
+ return adVideoElement.volume;
1163
+ }
1164
+ return 1;
1165
+ }
1166
+ };
1167
+ }
1168
+
552
1169
  // src/utils/tracking.ts
553
1170
  var cachedBrowserId = null;
554
1171
  function getClientInfo() {
@@ -700,10 +1317,18 @@ async function getBrowserID(clientInfo) {
700
1317
  if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
701
1318
  try {
702
1319
  await crypto.subtle.digest("SHA-256", new Uint8Array([1, 2, 3]));
703
- const hashBuffer = await crypto.subtle.digest(
704
- "SHA-256",
705
- new TextEncoder().encode(fingerprintString)
706
- );
1320
+ let encodedData;
1321
+ if (typeof TextEncoder !== "undefined") {
1322
+ encodedData = new TextEncoder().encode(fingerprintString);
1323
+ } else {
1324
+ const utf8 = unescape(encodeURIComponent(fingerprintString));
1325
+ const buffer = new Uint8Array(utf8.length);
1326
+ for (let i = 0; i < utf8.length; i++) {
1327
+ buffer[i] = utf8.charCodeAt(i);
1328
+ }
1329
+ encodedData = buffer;
1330
+ }
1331
+ const hashBuffer = await crypto.subtle.digest("SHA-256", encodedData);
707
1332
  const hashArray = Array.from(new Uint8Array(hashBuffer));
708
1333
  const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
709
1334
  cachedBrowserId = hashHex;
@@ -790,6 +1415,215 @@ async function sendHeartbeat(licenseKey) {
790
1415
  }
791
1416
  }
792
1417
 
1418
+ // src/utils/polyfills.ts
1419
+ function polyfillURLSearchParams() {
1420
+ if (typeof URLSearchParams !== "undefined") {
1421
+ return;
1422
+ }
1423
+ class URLSearchParamsPolyfill {
1424
+ constructor(init) {
1425
+ this.params = /* @__PURE__ */ new Map();
1426
+ if (typeof init === "string") {
1427
+ this.parseQueryString(init);
1428
+ } else if (init instanceof URLSearchParamsPolyfill) {
1429
+ init.forEach((value, key) => {
1430
+ this.append(key, value);
1431
+ });
1432
+ }
1433
+ }
1434
+ parseQueryString(query) {
1435
+ const cleanQuery = query.startsWith("?") ? query.slice(1) : query;
1436
+ if (!cleanQuery) return;
1437
+ cleanQuery.split("&").forEach((param) => {
1438
+ const [key, value] = param.split("=");
1439
+ if (key) {
1440
+ const decodedKey = this.safeDecodeURIComponent(key);
1441
+ const decodedValue = value ? this.safeDecodeURIComponent(value) : "";
1442
+ this.append(decodedKey, decodedValue);
1443
+ }
1444
+ });
1445
+ }
1446
+ safeDecodeURIComponent(str) {
1447
+ try {
1448
+ return decodeURIComponent(str.replace(/\+/g, " "));
1449
+ } catch (e) {
1450
+ return str;
1451
+ }
1452
+ }
1453
+ append(name, value) {
1454
+ const values = this.params.get(name) || [];
1455
+ values.push(String(value));
1456
+ this.params.set(name, values);
1457
+ }
1458
+ delete(name) {
1459
+ this.params.delete(name);
1460
+ }
1461
+ get(name) {
1462
+ const values = this.params.get(name);
1463
+ return values && values.length > 0 && values[0] !== void 0 ? values[0] : null;
1464
+ }
1465
+ getAll(name) {
1466
+ return this.params.get(name) || [];
1467
+ }
1468
+ has(name) {
1469
+ return this.params.has(name);
1470
+ }
1471
+ set(name, value) {
1472
+ this.params.set(name, [String(value)]);
1473
+ }
1474
+ forEach(callback) {
1475
+ this.params.forEach((values, key) => {
1476
+ values.forEach((value) => {
1477
+ callback(value, key, this);
1478
+ });
1479
+ });
1480
+ }
1481
+ toString() {
1482
+ const parts = [];
1483
+ this.params.forEach((values, key) => {
1484
+ values.forEach((value) => {
1485
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
1486
+ });
1487
+ });
1488
+ return parts.join("&");
1489
+ }
1490
+ }
1491
+ window.URLSearchParams = URLSearchParamsPolyfill;
1492
+ }
1493
+ function polyfillTextEncoder() {
1494
+ if (typeof TextEncoder !== "undefined") {
1495
+ return;
1496
+ }
1497
+ class TextEncoderPolyfill {
1498
+ constructor() {
1499
+ this.encoding = "utf-8";
1500
+ }
1501
+ encode(str) {
1502
+ const utf8 = [];
1503
+ for (let i = 0; i < str.length; i++) {
1504
+ let charcode = str.charCodeAt(i);
1505
+ if (charcode < 128) {
1506
+ utf8.push(charcode);
1507
+ } else if (charcode < 2048) {
1508
+ utf8.push(192 | charcode >> 6, 128 | charcode & 63);
1509
+ } else if (charcode < 55296 || charcode >= 57344) {
1510
+ utf8.push(
1511
+ 224 | charcode >> 12,
1512
+ 128 | charcode >> 6 & 63,
1513
+ 128 | charcode & 63
1514
+ );
1515
+ } else {
1516
+ i++;
1517
+ charcode = 65536 + ((charcode & 1023) << 10 | str.charCodeAt(i) & 1023);
1518
+ utf8.push(
1519
+ 240 | charcode >> 18,
1520
+ 128 | charcode >> 12 & 63,
1521
+ 128 | charcode >> 6 & 63,
1522
+ 128 | charcode & 63
1523
+ );
1524
+ }
1525
+ }
1526
+ return new Uint8Array(utf8);
1527
+ }
1528
+ }
1529
+ window.TextEncoder = TextEncoderPolyfill;
1530
+ }
1531
+ function polyfillPromiseFinally() {
1532
+ if (typeof Promise !== "undefined" && !Promise.prototype.finally) {
1533
+ Promise.prototype.finally = function(callback) {
1534
+ const constructor = this.constructor;
1535
+ return this.then(
1536
+ (value) => constructor.resolve(callback()).then(() => value),
1537
+ (reason) => constructor.resolve(callback()).then(() => {
1538
+ throw reason;
1539
+ })
1540
+ );
1541
+ };
1542
+ }
1543
+ }
1544
+ function polyfillObjectAssign() {
1545
+ if (typeof Object.assign !== "function") {
1546
+ Object.assign = function(target, ...sources) {
1547
+ if (target == null) {
1548
+ throw new TypeError("Cannot convert undefined or null to object");
1549
+ }
1550
+ const to = Object(target);
1551
+ for (let i = 0; i < sources.length; i++) {
1552
+ const nextSource = sources[i];
1553
+ if (nextSource != null) {
1554
+ for (const nextKey in nextSource) {
1555
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
1556
+ to[nextKey] = nextSource[nextKey];
1557
+ }
1558
+ }
1559
+ }
1560
+ }
1561
+ return to;
1562
+ };
1563
+ }
1564
+ }
1565
+ function polyfillArrayFrom() {
1566
+ if (!Array.from) {
1567
+ Array.from = function(arrayLike, mapFn, thisArg) {
1568
+ const items = Object(arrayLike);
1569
+ if (arrayLike == null) {
1570
+ throw new TypeError("Array.from requires an array-like object");
1571
+ }
1572
+ const len = items.length >>> 0;
1573
+ const result = new Array(len);
1574
+ for (let i = 0; i < len; i++) {
1575
+ if (mapFn) {
1576
+ result[i] = mapFn.call(thisArg, items[i], i);
1577
+ } else {
1578
+ result[i] = items[i];
1579
+ }
1580
+ }
1581
+ return result;
1582
+ };
1583
+ }
1584
+ }
1585
+ function polyfillStringStartsWith() {
1586
+ if (!String.prototype.startsWith) {
1587
+ String.prototype.startsWith = function(search, pos) {
1588
+ pos = !pos || pos < 0 ? 0 : +pos;
1589
+ return this.substring(pos, pos + search.length) === search;
1590
+ };
1591
+ }
1592
+ }
1593
+ function polyfillStringEndsWith() {
1594
+ if (!String.prototype.endsWith) {
1595
+ String.prototype.endsWith = function(search, length) {
1596
+ if (length === void 0 || length > this.length) {
1597
+ length = this.length;
1598
+ }
1599
+ return this.substring(length - search.length, length) === search;
1600
+ };
1601
+ }
1602
+ }
1603
+ function polyfillStringIncludes() {
1604
+ if (!String.prototype.includes) {
1605
+ String.prototype.includes = function(search, start) {
1606
+ if (typeof start !== "number") {
1607
+ start = 0;
1608
+ }
1609
+ if (start + search.length > this.length) {
1610
+ return false;
1611
+ }
1612
+ return this.indexOf(search, start) !== -1;
1613
+ };
1614
+ }
1615
+ }
1616
+ function initializePolyfills() {
1617
+ polyfillObjectAssign();
1618
+ polyfillArrayFrom();
1619
+ polyfillStringStartsWith();
1620
+ polyfillStringEndsWith();
1621
+ polyfillStringIncludes();
1622
+ polyfillURLSearchParams();
1623
+ polyfillTextEncoder();
1624
+ polyfillPromiseFinally();
1625
+ }
1626
+
793
1627
  // src/player/StormcloudVideoPlayer.ts
794
1628
  var StormcloudVideoPlayer = class {
795
1629
  constructor(config) {
@@ -802,11 +1636,40 @@ var StormcloudVideoPlayer = class {
802
1636
  this.totalAdsInBreak = 0;
803
1637
  this.showAds = false;
804
1638
  this.isLiveStream = false;
805
- this.config = config;
1639
+ initializePolyfills();
1640
+ const browserOverrides = getBrowserConfigOverrides();
1641
+ this.config = { ...config, ...browserOverrides };
806
1642
  this.video = config.videoElement;
807
- this.ima = createImaController(this.video, {
808
- continueLiveStreamDuringAds: false
809
- });
1643
+ logBrowserInfo(config.debugAdTiming);
1644
+ this.ima = this.createAdPlayer(false);
1645
+ }
1646
+ createAdPlayer(continueLiveStreamDuringAds) {
1647
+ const vastMode = this.config.vastMode || "default";
1648
+ let adPlayerType = this.config.adPlayerType || (vastMode === "adstorm" ? "hls" : "ima");
1649
+ if (adPlayerType === "ima" && !supportsGoogleIMA()) {
1650
+ if (this.config.debugAdTiming) {
1651
+ console.warn(
1652
+ "[StormcloudVideoPlayer] Google IMA SDK not supported on this browser, falling back to HLS ad player"
1653
+ );
1654
+ }
1655
+ adPlayerType = "hls";
1656
+ }
1657
+ if (adPlayerType === "hls") {
1658
+ if (this.config.debugAdTiming) {
1659
+ console.log("[StormcloudVideoPlayer] Creating HLS ad player (AdStorm mode)");
1660
+ }
1661
+ return createHlsAdPlayer(this.video, {
1662
+ continueLiveStreamDuringAds,
1663
+ ...this.config.licenseKey ? { licenseKey: this.config.licenseKey } : {}
1664
+ });
1665
+ } else {
1666
+ if (this.config.debugAdTiming) {
1667
+ console.log("[StormcloudVideoPlayer] Creating Google IMA ad player (Default mode)");
1668
+ }
1669
+ return createImaController(this.video, {
1670
+ continueLiveStreamDuringAds
1671
+ });
1672
+ }
810
1673
  }
811
1674
  async load() {
812
1675
  if (!this.attached) {
@@ -837,9 +1700,7 @@ var StormcloudVideoPlayer = class {
837
1700
  );
838
1701
  }
839
1702
  this.ima.destroy();
840
- this.ima = createImaController(this.video, {
841
- continueLiveStreamDuringAds: false
842
- });
1703
+ this.ima = this.createAdPlayer(false);
843
1704
  this.ima.initialize();
844
1705
  if (this.config.autoplay) {
845
1706
  await this.video.play()?.catch(() => {
@@ -847,7 +1708,7 @@ var StormcloudVideoPlayer = class {
847
1708
  }
848
1709
  return;
849
1710
  }
850
- this.hls = new import_hls.default({
1711
+ this.hls = new import_hls2.default({
851
1712
  enableWorker: true,
852
1713
  backBufferLength: 30,
853
1714
  liveDurationInfinity: true,
@@ -855,10 +1716,10 @@ var StormcloudVideoPlayer = class {
855
1716
  maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
856
1717
  ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
857
1718
  });
858
- this.hls.on(import_hls.default.Events.MEDIA_ATTACHED, () => {
1719
+ this.hls.on(import_hls2.default.Events.MEDIA_ATTACHED, () => {
859
1720
  this.hls?.loadSource(this.config.src);
860
1721
  });
861
- this.hls.on(import_hls.default.Events.MANIFEST_PARSED, async (_, data) => {
1722
+ this.hls.on(import_hls2.default.Events.MANIFEST_PARSED, async (_, data) => {
862
1723
  this.isLiveStream = this.hls?.levels?.some(
863
1724
  (level) => level?.details?.live === true || level?.details?.type === "LIVE"
864
1725
  ) ?? false;
@@ -871,16 +1732,14 @@ var StormcloudVideoPlayer = class {
871
1732
  });
872
1733
  }
873
1734
  this.ima.destroy();
874
- this.ima = createImaController(this.video, {
875
- continueLiveStreamDuringAds: this.shouldContinueLiveStreamDuringAds()
876
- });
1735
+ this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
877
1736
  this.ima.initialize();
878
1737
  if (this.config.autoplay) {
879
1738
  await this.video.play()?.catch(() => {
880
1739
  });
881
1740
  }
882
1741
  });
883
- this.hls.on(import_hls.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
1742
+ this.hls.on(import_hls2.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
884
1743
  const id3Tags = (data?.samples || []).map((s) => ({
885
1744
  key: "ID3",
886
1745
  value: s?.data,
@@ -888,7 +1747,7 @@ var StormcloudVideoPlayer = class {
888
1747
  }));
889
1748
  id3Tags.forEach((tag) => this.onId3Tag(tag));
890
1749
  });
891
- this.hls.on(import_hls.default.Events.FRAG_CHANGED, (_evt, data) => {
1750
+ this.hls.on(import_hls2.default.Events.FRAG_CHANGED, (_evt, data) => {
892
1751
  const frag = data?.frag;
893
1752
  const tagList = frag?.tagList;
894
1753
  if (!Array.isArray(tagList)) return;
@@ -948,13 +1807,13 @@ var StormcloudVideoPlayer = class {
948
1807
  }
949
1808
  }
950
1809
  });
951
- this.hls.on(import_hls.default.Events.ERROR, (_evt, data) => {
1810
+ this.hls.on(import_hls2.default.Events.ERROR, (_evt, data) => {
952
1811
  if (data?.fatal) {
953
1812
  switch (data.type) {
954
- case import_hls.default.ErrorTypes.NETWORK_ERROR:
1813
+ case import_hls2.default.ErrorTypes.NETWORK_ERROR:
955
1814
  this.hls?.startLoad();
956
1815
  break;
957
- case import_hls.default.ErrorTypes.MEDIA_ERROR:
1816
+ case import_hls2.default.ErrorTypes.MEDIA_ERROR:
958
1817
  this.hls?.recoverMediaError();
959
1818
  break;
960
1819
  default:
@@ -1465,6 +2324,42 @@ var StormcloudVideoPlayer = class {
1465
2324
  }
1466
2325
  }
1467
2326
  async fetchAdConfiguration() {
2327
+ const vastMode = this.config.vastMode || "default";
2328
+ if (this.config.debugAdTiming) {
2329
+ console.log(
2330
+ "[StormcloudVideoPlayer] VAST mode:",
2331
+ vastMode
2332
+ );
2333
+ }
2334
+ if (vastMode === "adstorm") {
2335
+ if (!this.config.licenseKey) {
2336
+ if (this.config.debugAdTiming) {
2337
+ console.warn(
2338
+ "[StormcloudVideoPlayer] AdStorm mode requires a license key"
2339
+ );
2340
+ }
2341
+ return;
2342
+ }
2343
+ const vastEndpoint = `https://adstorm.co/api-adstorm-dev/adstorm/vast/${this.config.licenseKey}`;
2344
+ this.apiVastTagUrl = vastEndpoint;
2345
+ if (this.config.debugAdTiming) {
2346
+ console.log(
2347
+ "[StormcloudVideoPlayer] Using AdStorm VAST endpoint:",
2348
+ vastEndpoint
2349
+ );
2350
+ }
2351
+ return;
2352
+ }
2353
+ if (this.config.vastTagUrl) {
2354
+ this.apiVastTagUrl = this.config.vastTagUrl;
2355
+ if (this.config.debugAdTiming) {
2356
+ console.log(
2357
+ "[StormcloudVideoPlayer] Using custom VAST tag URL:",
2358
+ this.apiVastTagUrl
2359
+ );
2360
+ }
2361
+ return;
2362
+ }
1468
2363
  const apiUrl = "https://adstorm.co/api-adstorm-dev/adstorm/ads/web";
1469
2364
  if (this.config.debugAdTiming) {
1470
2365
  console.log(
@@ -1478,7 +2373,12 @@ var StormcloudVideoPlayer = class {
1478
2373
  }
1479
2374
  const response = await fetch(apiUrl, { headers });
1480
2375
  if (!response.ok) {
1481
- throw new Error(`Failed to fetch ad configuration: ${response.status}`);
2376
+ if (this.config.debugAdTiming) {
2377
+ console.warn(
2378
+ `[StormcloudVideoPlayer] Failed to fetch ad configuration: ${response.status}`
2379
+ );
2380
+ }
2381
+ return;
1482
2382
  }
1483
2383
  const data = await response.json();
1484
2384
  const imaPayload = data.response?.ima?.["publisherdesk.ima"]?.payload;
@@ -1486,17 +2386,16 @@ var StormcloudVideoPlayer = class {
1486
2386
  this.apiVastTagUrl = decodeURIComponent(imaPayload);
1487
2387
  if (this.config.debugAdTiming) {
1488
2388
  console.log(
1489
- "[StormcloudVideoPlayer] Extracted VAST tag URL:",
2389
+ "[StormcloudVideoPlayer] Extracted VAST tag URL from /ads/web:",
1490
2390
  this.apiVastTagUrl
1491
2391
  );
1492
2392
  }
1493
- }
1494
- this.vastConfig = data.response?.options?.vast;
1495
- if (this.config.debugAdTiming) {
1496
- console.log("[StormcloudVideoPlayer] Ad configuration loaded:", {
1497
- vastTagUrl: this.apiVastTagUrl,
1498
- vastConfig: this.vastConfig
1499
- });
2393
+ } else {
2394
+ if (this.config.debugAdTiming) {
2395
+ console.warn(
2396
+ "[StormcloudVideoPlayer] No VAST tag URL found in /ads/web response"
2397
+ );
2398
+ }
1500
2399
  }
1501
2400
  }
1502
2401
  getCurrentAdIndex() {
@@ -1540,23 +2439,14 @@ var StormcloudVideoPlayer = class {
1540
2439
  );
1541
2440
  const tags = this.selectVastTagsForBreak(scheduled);
1542
2441
  let vastTagUrl;
1543
- let adsNumber = 1;
1544
2442
  if (this.apiVastTagUrl) {
1545
2443
  vastTagUrl = this.apiVastTagUrl;
1546
- if (this.vastConfig) {
1547
- const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1548
- if (isHls && this.vastConfig.cue_tones?.number_ads) {
1549
- adsNumber = this.vastConfig.cue_tones.number_ads;
1550
- } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1551
- adsNumber = this.vastConfig.timer_vod.number_ads;
1552
- }
1553
- }
1554
- this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
2444
+ this.adPodQueue = [];
1555
2445
  this.currentAdIndex = 0;
1556
- this.totalAdsInBreak = adsNumber;
2446
+ this.totalAdsInBreak = 1;
1557
2447
  if (this.config.debugAdTiming) {
1558
2448
  console.log(
1559
- `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
2449
+ "[StormcloudVideoPlayer] Using VAST endpoint:",
1560
2450
  vastTagUrl
1561
2451
  );
1562
2452
  }