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