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/README.md +100 -1
- package/dist/stormcloud-vp.min.js +2 -2
- package/lib/index.cjs +986 -52
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +75 -2
- package/lib/index.d.ts +75 -2
- package/lib/index.js +976 -52
- package/lib/index.js.map +1 -1
- package/lib/player/StormcloudVideoPlayer.cjs +922 -40
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/player/StormcloudVideoPlayer.d.cts +2 -2
- package/lib/players/HlsPlayer.cjs +922 -40
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/HlsPlayer.d.cts +1 -1
- package/lib/players/index.cjs +922 -40
- package/lib/players/index.cjs.map +1 -1
- package/lib/sdk/hlsAdPlayer.cjs +488 -0
- package/lib/sdk/hlsAdPlayer.cjs.map +1 -0
- package/lib/sdk/hlsAdPlayer.d.cts +8 -0
- package/lib/sdk/ima.cjs +143 -0
- package/lib/sdk/ima.cjs.map +1 -1
- package/lib/sdk/ima.d.cts +1 -1
- package/lib/{types-GpA_hKek.d.cts → types-mVgmKmzM.d.cts} +3 -0
- package/lib/ui/StormcloudVideoPlayer.cjs +922 -40
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
- package/lib/utils/browserCompat.cjs +228 -0
- package/lib/utils/browserCompat.cjs.map +1 -0
- package/lib/utils/browserCompat.d.cts +36 -0
- package/lib/utils/polyfills.cjs +253 -0
- package/lib/utils/polyfills.cjs.map +1 -0
- package/lib/utils/polyfills.d.cts +11 -0
- package/lib/utils/tracking.d.cts +1 -1
- package/package.json +1 -1
|
@@ -36,7 +36,166 @@ module.exports = __toCommonJS(StormcloudVideoPlayer_exports);
|
|
|
36
36
|
var import_react = __toESM(require("react"), 1);
|
|
37
37
|
|
|
38
38
|
// src/player/StormcloudVideoPlayer.ts
|
|
39
|
-
var
|
|
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() {
|
|
@@ -798,6 +1415,215 @@ async function sendHeartbeat(licenseKey) {
|
|
|
798
1415
|
}
|
|
799
1416
|
}
|
|
800
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
|
+
|
|
801
1627
|
// src/player/StormcloudVideoPlayer.ts
|
|
802
1628
|
var StormcloudVideoPlayer = class {
|
|
803
1629
|
constructor(config) {
|
|
@@ -810,11 +1636,40 @@ var StormcloudVideoPlayer = class {
|
|
|
810
1636
|
this.totalAdsInBreak = 0;
|
|
811
1637
|
this.showAds = false;
|
|
812
1638
|
this.isLiveStream = false;
|
|
813
|
-
|
|
1639
|
+
initializePolyfills();
|
|
1640
|
+
const browserOverrides = getBrowserConfigOverrides();
|
|
1641
|
+
this.config = { ...config, ...browserOverrides };
|
|
814
1642
|
this.video = config.videoElement;
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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
|
+
}
|
|
818
1673
|
}
|
|
819
1674
|
async load() {
|
|
820
1675
|
if (!this.attached) {
|
|
@@ -845,9 +1700,7 @@ var StormcloudVideoPlayer = class {
|
|
|
845
1700
|
);
|
|
846
1701
|
}
|
|
847
1702
|
this.ima.destroy();
|
|
848
|
-
this.ima =
|
|
849
|
-
continueLiveStreamDuringAds: false
|
|
850
|
-
});
|
|
1703
|
+
this.ima = this.createAdPlayer(false);
|
|
851
1704
|
this.ima.initialize();
|
|
852
1705
|
if (this.config.autoplay) {
|
|
853
1706
|
await this.video.play()?.catch(() => {
|
|
@@ -855,7 +1708,7 @@ var StormcloudVideoPlayer = class {
|
|
|
855
1708
|
}
|
|
856
1709
|
return;
|
|
857
1710
|
}
|
|
858
|
-
this.hls = new
|
|
1711
|
+
this.hls = new import_hls2.default({
|
|
859
1712
|
enableWorker: true,
|
|
860
1713
|
backBufferLength: 30,
|
|
861
1714
|
liveDurationInfinity: true,
|
|
@@ -863,10 +1716,10 @@ var StormcloudVideoPlayer = class {
|
|
|
863
1716
|
maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
|
|
864
1717
|
...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
|
|
865
1718
|
});
|
|
866
|
-
this.hls.on(
|
|
1719
|
+
this.hls.on(import_hls2.default.Events.MEDIA_ATTACHED, () => {
|
|
867
1720
|
this.hls?.loadSource(this.config.src);
|
|
868
1721
|
});
|
|
869
|
-
this.hls.on(
|
|
1722
|
+
this.hls.on(import_hls2.default.Events.MANIFEST_PARSED, async (_, data) => {
|
|
870
1723
|
this.isLiveStream = this.hls?.levels?.some(
|
|
871
1724
|
(level) => level?.details?.live === true || level?.details?.type === "LIVE"
|
|
872
1725
|
) ?? false;
|
|
@@ -879,16 +1732,14 @@ var StormcloudVideoPlayer = class {
|
|
|
879
1732
|
});
|
|
880
1733
|
}
|
|
881
1734
|
this.ima.destroy();
|
|
882
|
-
this.ima =
|
|
883
|
-
continueLiveStreamDuringAds: this.shouldContinueLiveStreamDuringAds()
|
|
884
|
-
});
|
|
1735
|
+
this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
|
|
885
1736
|
this.ima.initialize();
|
|
886
1737
|
if (this.config.autoplay) {
|
|
887
1738
|
await this.video.play()?.catch(() => {
|
|
888
1739
|
});
|
|
889
1740
|
}
|
|
890
1741
|
});
|
|
891
|
-
this.hls.on(
|
|
1742
|
+
this.hls.on(import_hls2.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
|
|
892
1743
|
const id3Tags = (data?.samples || []).map((s) => ({
|
|
893
1744
|
key: "ID3",
|
|
894
1745
|
value: s?.data,
|
|
@@ -896,7 +1747,7 @@ var StormcloudVideoPlayer = class {
|
|
|
896
1747
|
}));
|
|
897
1748
|
id3Tags.forEach((tag) => this.onId3Tag(tag));
|
|
898
1749
|
});
|
|
899
|
-
this.hls.on(
|
|
1750
|
+
this.hls.on(import_hls2.default.Events.FRAG_CHANGED, (_evt, data) => {
|
|
900
1751
|
const frag = data?.frag;
|
|
901
1752
|
const tagList = frag?.tagList;
|
|
902
1753
|
if (!Array.isArray(tagList)) return;
|
|
@@ -956,13 +1807,13 @@ var StormcloudVideoPlayer = class {
|
|
|
956
1807
|
}
|
|
957
1808
|
}
|
|
958
1809
|
});
|
|
959
|
-
this.hls.on(
|
|
1810
|
+
this.hls.on(import_hls2.default.Events.ERROR, (_evt, data) => {
|
|
960
1811
|
if (data?.fatal) {
|
|
961
1812
|
switch (data.type) {
|
|
962
|
-
case
|
|
1813
|
+
case import_hls2.default.ErrorTypes.NETWORK_ERROR:
|
|
963
1814
|
this.hls?.startLoad();
|
|
964
1815
|
break;
|
|
965
|
-
case
|
|
1816
|
+
case import_hls2.default.ErrorTypes.MEDIA_ERROR:
|
|
966
1817
|
this.hls?.recoverMediaError();
|
|
967
1818
|
break;
|
|
968
1819
|
default:
|
|
@@ -1473,6 +2324,42 @@ var StormcloudVideoPlayer = class {
|
|
|
1473
2324
|
}
|
|
1474
2325
|
}
|
|
1475
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
|
+
}
|
|
1476
2363
|
const apiUrl = "https://adstorm.co/api-adstorm-dev/adstorm/ads/web";
|
|
1477
2364
|
if (this.config.debugAdTiming) {
|
|
1478
2365
|
console.log(
|
|
@@ -1486,7 +2373,12 @@ var StormcloudVideoPlayer = class {
|
|
|
1486
2373
|
}
|
|
1487
2374
|
const response = await fetch(apiUrl, { headers });
|
|
1488
2375
|
if (!response.ok) {
|
|
1489
|
-
|
|
2376
|
+
if (this.config.debugAdTiming) {
|
|
2377
|
+
console.warn(
|
|
2378
|
+
`[StormcloudVideoPlayer] Failed to fetch ad configuration: ${response.status}`
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2381
|
+
return;
|
|
1490
2382
|
}
|
|
1491
2383
|
const data = await response.json();
|
|
1492
2384
|
const imaPayload = data.response?.ima?.["publisherdesk.ima"]?.payload;
|
|
@@ -1494,17 +2386,16 @@ var StormcloudVideoPlayer = class {
|
|
|
1494
2386
|
this.apiVastTagUrl = decodeURIComponent(imaPayload);
|
|
1495
2387
|
if (this.config.debugAdTiming) {
|
|
1496
2388
|
console.log(
|
|
1497
|
-
"[StormcloudVideoPlayer] Extracted VAST tag URL:",
|
|
2389
|
+
"[StormcloudVideoPlayer] Extracted VAST tag URL from /ads/web:",
|
|
1498
2390
|
this.apiVastTagUrl
|
|
1499
2391
|
);
|
|
1500
2392
|
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
});
|
|
2393
|
+
} else {
|
|
2394
|
+
if (this.config.debugAdTiming) {
|
|
2395
|
+
console.warn(
|
|
2396
|
+
"[StormcloudVideoPlayer] No VAST tag URL found in /ads/web response"
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
1508
2399
|
}
|
|
1509
2400
|
}
|
|
1510
2401
|
getCurrentAdIndex() {
|
|
@@ -1548,23 +2439,14 @@ var StormcloudVideoPlayer = class {
|
|
|
1548
2439
|
);
|
|
1549
2440
|
const tags = this.selectVastTagsForBreak(scheduled);
|
|
1550
2441
|
let vastTagUrl;
|
|
1551
|
-
let adsNumber = 1;
|
|
1552
2442
|
if (this.apiVastTagUrl) {
|
|
1553
2443
|
vastTagUrl = this.apiVastTagUrl;
|
|
1554
|
-
|
|
1555
|
-
const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
|
|
1556
|
-
if (isHls && this.vastConfig.cue_tones?.number_ads) {
|
|
1557
|
-
adsNumber = this.vastConfig.cue_tones.number_ads;
|
|
1558
|
-
} else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
|
|
1559
|
-
adsNumber = this.vastConfig.timer_vod.number_ads;
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
|
|
2444
|
+
this.adPodQueue = [];
|
|
1563
2445
|
this.currentAdIndex = 0;
|
|
1564
|
-
this.totalAdsInBreak =
|
|
2446
|
+
this.totalAdsInBreak = 1;
|
|
1565
2447
|
if (this.config.debugAdTiming) {
|
|
1566
2448
|
console.log(
|
|
1567
|
-
|
|
2449
|
+
"[StormcloudVideoPlayer] Using VAST endpoint:",
|
|
1568
2450
|
vastTagUrl
|
|
1569
2451
|
);
|
|
1570
2452
|
}
|