stormcloud-video-player 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -1
- package/dist/stormcloud-vp.min.js +2 -2
- package/lib/index.cjs +1010 -48
- 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 +1000 -48
- package/lib/index.js.map +1 -1
- package/lib/player/StormcloudVideoPlayer.cjs +934 -44
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/player/StormcloudVideoPlayer.d.cts +2 -2
- package/lib/players/HlsPlayer.cjs +934 -44
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/HlsPlayer.d.cts +1 -1
- package/lib/players/index.cjs +934 -44
- 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 +934 -44
- 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.cjs +12 -4
- package/lib/utils/tracking.cjs.map +1 -1
- package/lib/utils/tracking.d.cts +1 -1
- package/package.json +1 -1
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
|
|
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() {
|
|
@@ -666,10 +1312,18 @@ async function getBrowserID(clientInfo) {
|
|
|
666
1312
|
if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
|
|
667
1313
|
try {
|
|
668
1314
|
await crypto.subtle.digest("SHA-256", new Uint8Array([1, 2, 3]));
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
new TextEncoder().encode(fingerprintString)
|
|
672
|
-
|
|
1315
|
+
let encodedData;
|
|
1316
|
+
if (typeof TextEncoder !== "undefined") {
|
|
1317
|
+
encodedData = new TextEncoder().encode(fingerprintString);
|
|
1318
|
+
} else {
|
|
1319
|
+
const utf8 = unescape(encodeURIComponent(fingerprintString));
|
|
1320
|
+
const buffer = new Uint8Array(utf8.length);
|
|
1321
|
+
for (let i = 0; i < utf8.length; i++) {
|
|
1322
|
+
buffer[i] = utf8.charCodeAt(i);
|
|
1323
|
+
}
|
|
1324
|
+
encodedData = buffer;
|
|
1325
|
+
}
|
|
1326
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", encodedData);
|
|
673
1327
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
674
1328
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
675
1329
|
cachedBrowserId = hashHex;
|
|
@@ -756,6 +1410,215 @@ async function sendHeartbeat(licenseKey) {
|
|
|
756
1410
|
}
|
|
757
1411
|
}
|
|
758
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
|
+
|
|
759
1622
|
// src/player/StormcloudVideoPlayer.ts
|
|
760
1623
|
var StormcloudVideoPlayer = class {
|
|
761
1624
|
constructor(config) {
|
|
@@ -768,11 +1631,40 @@ var StormcloudVideoPlayer = class {
|
|
|
768
1631
|
this.totalAdsInBreak = 0;
|
|
769
1632
|
this.showAds = false;
|
|
770
1633
|
this.isLiveStream = false;
|
|
771
|
-
|
|
1634
|
+
initializePolyfills();
|
|
1635
|
+
const browserOverrides = getBrowserConfigOverrides();
|
|
1636
|
+
this.config = { ...config, ...browserOverrides };
|
|
772
1637
|
this.video = config.videoElement;
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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
|
+
}
|
|
776
1668
|
}
|
|
777
1669
|
async load() {
|
|
778
1670
|
if (!this.attached) {
|
|
@@ -803,9 +1695,7 @@ var StormcloudVideoPlayer = class {
|
|
|
803
1695
|
);
|
|
804
1696
|
}
|
|
805
1697
|
this.ima.destroy();
|
|
806
|
-
this.ima =
|
|
807
|
-
continueLiveStreamDuringAds: false
|
|
808
|
-
});
|
|
1698
|
+
this.ima = this.createAdPlayer(false);
|
|
809
1699
|
this.ima.initialize();
|
|
810
1700
|
if (this.config.autoplay) {
|
|
811
1701
|
await this.video.play()?.catch(() => {
|
|
@@ -813,7 +1703,7 @@ var StormcloudVideoPlayer = class {
|
|
|
813
1703
|
}
|
|
814
1704
|
return;
|
|
815
1705
|
}
|
|
816
|
-
this.hls = new
|
|
1706
|
+
this.hls = new Hls2({
|
|
817
1707
|
enableWorker: true,
|
|
818
1708
|
backBufferLength: 30,
|
|
819
1709
|
liveDurationInfinity: true,
|
|
@@ -821,10 +1711,10 @@ var StormcloudVideoPlayer = class {
|
|
|
821
1711
|
maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
|
|
822
1712
|
...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
|
|
823
1713
|
});
|
|
824
|
-
this.hls.on(
|
|
1714
|
+
this.hls.on(Hls2.Events.MEDIA_ATTACHED, () => {
|
|
825
1715
|
this.hls?.loadSource(this.config.src);
|
|
826
1716
|
});
|
|
827
|
-
this.hls.on(
|
|
1717
|
+
this.hls.on(Hls2.Events.MANIFEST_PARSED, async (_, data) => {
|
|
828
1718
|
this.isLiveStream = this.hls?.levels?.some(
|
|
829
1719
|
(level) => level?.details?.live === true || level?.details?.type === "LIVE"
|
|
830
1720
|
) ?? false;
|
|
@@ -837,16 +1727,14 @@ var StormcloudVideoPlayer = class {
|
|
|
837
1727
|
});
|
|
838
1728
|
}
|
|
839
1729
|
this.ima.destroy();
|
|
840
|
-
this.ima =
|
|
841
|
-
continueLiveStreamDuringAds: this.shouldContinueLiveStreamDuringAds()
|
|
842
|
-
});
|
|
1730
|
+
this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
|
|
843
1731
|
this.ima.initialize();
|
|
844
1732
|
if (this.config.autoplay) {
|
|
845
1733
|
await this.video.play()?.catch(() => {
|
|
846
1734
|
});
|
|
847
1735
|
}
|
|
848
1736
|
});
|
|
849
|
-
this.hls.on(
|
|
1737
|
+
this.hls.on(Hls2.Events.FRAG_PARSING_METADATA, (_evt, data) => {
|
|
850
1738
|
const id3Tags = (data?.samples || []).map((s) => ({
|
|
851
1739
|
key: "ID3",
|
|
852
1740
|
value: s?.data,
|
|
@@ -854,7 +1742,7 @@ var StormcloudVideoPlayer = class {
|
|
|
854
1742
|
}));
|
|
855
1743
|
id3Tags.forEach((tag) => this.onId3Tag(tag));
|
|
856
1744
|
});
|
|
857
|
-
this.hls.on(
|
|
1745
|
+
this.hls.on(Hls2.Events.FRAG_CHANGED, (_evt, data) => {
|
|
858
1746
|
const frag = data?.frag;
|
|
859
1747
|
const tagList = frag?.tagList;
|
|
860
1748
|
if (!Array.isArray(tagList)) return;
|
|
@@ -914,13 +1802,13 @@ var StormcloudVideoPlayer = class {
|
|
|
914
1802
|
}
|
|
915
1803
|
}
|
|
916
1804
|
});
|
|
917
|
-
this.hls.on(
|
|
1805
|
+
this.hls.on(Hls2.Events.ERROR, (_evt, data) => {
|
|
918
1806
|
if (data?.fatal) {
|
|
919
1807
|
switch (data.type) {
|
|
920
|
-
case
|
|
1808
|
+
case Hls2.ErrorTypes.NETWORK_ERROR:
|
|
921
1809
|
this.hls?.startLoad();
|
|
922
1810
|
break;
|
|
923
|
-
case
|
|
1811
|
+
case Hls2.ErrorTypes.MEDIA_ERROR:
|
|
924
1812
|
this.hls?.recoverMediaError();
|
|
925
1813
|
break;
|
|
926
1814
|
default:
|
|
@@ -1431,6 +2319,42 @@ var StormcloudVideoPlayer = class {
|
|
|
1431
2319
|
}
|
|
1432
2320
|
}
|
|
1433
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
|
+
}
|
|
1434
2358
|
const apiUrl = "https://adstorm.co/api-adstorm-dev/adstorm/ads/web";
|
|
1435
2359
|
if (this.config.debugAdTiming) {
|
|
1436
2360
|
console.log(
|
|
@@ -1444,7 +2368,12 @@ var StormcloudVideoPlayer = class {
|
|
|
1444
2368
|
}
|
|
1445
2369
|
const response = await fetch(apiUrl, { headers });
|
|
1446
2370
|
if (!response.ok) {
|
|
1447
|
-
|
|
2371
|
+
if (this.config.debugAdTiming) {
|
|
2372
|
+
console.warn(
|
|
2373
|
+
`[StormcloudVideoPlayer] Failed to fetch ad configuration: ${response.status}`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
return;
|
|
1448
2377
|
}
|
|
1449
2378
|
const data = await response.json();
|
|
1450
2379
|
const imaPayload = data.response?.ima?.["publisherdesk.ima"]?.payload;
|
|
@@ -1452,17 +2381,16 @@ var StormcloudVideoPlayer = class {
|
|
|
1452
2381
|
this.apiVastTagUrl = decodeURIComponent(imaPayload);
|
|
1453
2382
|
if (this.config.debugAdTiming) {
|
|
1454
2383
|
console.log(
|
|
1455
|
-
"[StormcloudVideoPlayer] Extracted VAST tag URL:",
|
|
2384
|
+
"[StormcloudVideoPlayer] Extracted VAST tag URL from /ads/web:",
|
|
1456
2385
|
this.apiVastTagUrl
|
|
1457
2386
|
);
|
|
1458
2387
|
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
});
|
|
2388
|
+
} else {
|
|
2389
|
+
if (this.config.debugAdTiming) {
|
|
2390
|
+
console.warn(
|
|
2391
|
+
"[StormcloudVideoPlayer] No VAST tag URL found in /ads/web response"
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
1466
2394
|
}
|
|
1467
2395
|
}
|
|
1468
2396
|
getCurrentAdIndex() {
|
|
@@ -1506,23 +2434,14 @@ var StormcloudVideoPlayer = class {
|
|
|
1506
2434
|
);
|
|
1507
2435
|
const tags = this.selectVastTagsForBreak(scheduled);
|
|
1508
2436
|
let vastTagUrl;
|
|
1509
|
-
let adsNumber = 1;
|
|
1510
2437
|
if (this.apiVastTagUrl) {
|
|
1511
2438
|
vastTagUrl = this.apiVastTagUrl;
|
|
1512
|
-
|
|
1513
|
-
const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
|
|
1514
|
-
if (isHls && this.vastConfig.cue_tones?.number_ads) {
|
|
1515
|
-
adsNumber = this.vastConfig.cue_tones.number_ads;
|
|
1516
|
-
} else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
|
|
1517
|
-
adsNumber = this.vastConfig.timer_vod.number_ads;
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
|
|
2439
|
+
this.adPodQueue = [];
|
|
1521
2440
|
this.currentAdIndex = 0;
|
|
1522
|
-
this.totalAdsInBreak =
|
|
2441
|
+
this.totalAdsInBreak = 1;
|
|
1523
2442
|
if (this.config.debugAdTiming) {
|
|
1524
2443
|
console.log(
|
|
1525
|
-
|
|
2444
|
+
"[StormcloudVideoPlayer] Using VAST endpoint:",
|
|
1526
2445
|
vastTagUrl
|
|
1527
2446
|
);
|
|
1528
2447
|
}
|
|
@@ -3474,10 +4393,33 @@ var randomString = () => {
|
|
|
3474
4393
|
};
|
|
3475
4394
|
var parseQuery = (url) => {
|
|
3476
4395
|
const query = {};
|
|
3477
|
-
const
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
4396
|
+
const queryString = url.split("?")[1] || "";
|
|
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
|
+
};
|
|
4410
|
+
if (typeof URLSearchParams !== "undefined") {
|
|
4411
|
+
try {
|
|
4412
|
+
const params = new URLSearchParams(queryString);
|
|
4413
|
+
params.forEach((value, key) => {
|
|
4414
|
+
query[key] = value;
|
|
4415
|
+
});
|
|
4416
|
+
return query;
|
|
4417
|
+
} catch (e) {
|
|
4418
|
+
manualParse(queryString);
|
|
4419
|
+
}
|
|
4420
|
+
} else {
|
|
4421
|
+
manualParse(queryString);
|
|
4422
|
+
}
|
|
3481
4423
|
return query;
|
|
3482
4424
|
};
|
|
3483
4425
|
var merge = (target, ...sources) => {
|
|
@@ -4305,12 +5247,19 @@ export {
|
|
|
4305
5247
|
StormcloudVideoPlayer,
|
|
4306
5248
|
StormcloudVideoPlayerComponent,
|
|
4307
5249
|
canPlay,
|
|
5250
|
+
createHlsAdPlayer,
|
|
5251
|
+
createImaController,
|
|
4308
5252
|
createStormcloudPlayer,
|
|
4309
5253
|
StormcloudVideoPlayerComponent as default,
|
|
5254
|
+
detectBrowser,
|
|
5255
|
+
getBrowserConfigOverrides,
|
|
4310
5256
|
getBrowserID,
|
|
4311
5257
|
getClientInfo,
|
|
5258
|
+
getRecommendedAdPlayer,
|
|
5259
|
+
initializePolyfills,
|
|
4312
5260
|
isMediaStream,
|
|
4313
5261
|
lazy,
|
|
5262
|
+
logBrowserInfo,
|
|
4314
5263
|
merge,
|
|
4315
5264
|
omit,
|
|
4316
5265
|
parseQuery,
|
|
@@ -4318,6 +5267,9 @@ export {
|
|
|
4318
5267
|
randomString,
|
|
4319
5268
|
sendHeartbeat,
|
|
4320
5269
|
sendInitialTracking,
|
|
5270
|
+
supportsFeature,
|
|
5271
|
+
supportsGoogleIMA,
|
|
5272
|
+
supportsModernJS,
|
|
4321
5273
|
supportsWebKitPresentationMode
|
|
4322
5274
|
};
|
|
4323
5275
|
//# sourceMappingURL=index.js.map
|