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