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
|
@@ -33,7 +33,166 @@ __export(StormcloudVideoPlayer_exports, {
|
|
|
33
33
|
StormcloudVideoPlayer: () => StormcloudVideoPlayer
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(StormcloudVideoPlayer_exports);
|
|
36
|
-
var
|
|
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
|
-
|
|
1636
|
+
initializePolyfills();
|
|
1637
|
+
const browserOverrides = getBrowserConfigOverrides();
|
|
1638
|
+
this.config = { ...config, ...browserOverrides };
|
|
811
1639
|
this.video = config.videoElement;
|
|
812
|
-
|
|
813
|
-
|
|
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 =
|
|
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
|
|
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(
|
|
1716
|
+
this.hls.on(import_hls2.default.Events.MEDIA_ATTACHED, () => {
|
|
864
1717
|
this.hls?.loadSource(this.config.src);
|
|
865
1718
|
});
|
|
866
|
-
this.hls.on(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
1807
|
+
this.hls.on(import_hls2.default.Events.ERROR, (_evt, data) => {
|
|
957
1808
|
if (data?.fatal) {
|
|
958
1809
|
switch (data.type) {
|
|
959
|
-
case
|
|
1810
|
+
case import_hls2.default.ErrorTypes.NETWORK_ERROR:
|
|
960
1811
|
this.hls?.startLoad();
|
|
961
1812
|
break;
|
|
962
|
-
case
|
|
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
|
-
|
|
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
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
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
|
-
|
|
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 =
|
|
2443
|
+
this.totalAdsInBreak = 1;
|
|
1562
2444
|
if (this.config.debugAdTiming) {
|
|
1563
2445
|
console.log(
|
|
1564
|
-
|
|
2446
|
+
"[StormcloudVideoPlayer] Using VAST endpoint:",
|
|
1565
2447
|
vastTagUrl
|
|
1566
2448
|
);
|
|
1567
2449
|
}
|