stormcloud-video-player 0.2.8 → 0.2.10
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/dist/stormcloud-vp.min.js +1 -1
- package/lib/index.cjs +413 -100
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +413 -100
- package/lib/index.js.map +1 -1
- package/lib/player/StormcloudVideoPlayer.cjs +150 -48
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/players/HlsPlayer.cjs +150 -48
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/index.cjs +150 -48
- package/lib/players/index.cjs.map +1 -1
- package/lib/sdk/ima.cjs +94 -32
- package/lib/sdk/ima.cjs.map +1 -1
- package/lib/ui/StormcloudVideoPlayer.cjs +413 -100
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/utils/tracking.cjs +7 -1
- package/lib/utils/tracking.cjs.map +1 -1
- package/package.json +1 -1
|
@@ -76,9 +76,22 @@ function createImaController(video, options) {
|
|
|
76
76
|
'script[data-ima="true"]'
|
|
77
77
|
);
|
|
78
78
|
if (existing) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if (window.google?.ima) {
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
}
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const timeout = setTimeout(() => {
|
|
84
|
+
reject(new Error("IMA SDK load timeout"));
|
|
85
|
+
}, 1e4);
|
|
86
|
+
existing.addEventListener("load", () => {
|
|
87
|
+
clearTimeout(timeout);
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
existing.addEventListener("error", () => {
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
reject(new Error("IMA SDK load failed"));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
82
95
|
}
|
|
83
96
|
return new Promise((resolve, reject) => {
|
|
84
97
|
const script = document.createElement("script");
|
|
@@ -107,6 +120,17 @@ function createImaController(video, options) {
|
|
|
107
120
|
adsRequest.adTagUrl = vastTagUrl;
|
|
108
121
|
adsLoader.requestAds(adsRequest);
|
|
109
122
|
}
|
|
123
|
+
function destroyAdsManager() {
|
|
124
|
+
if (adsManager) {
|
|
125
|
+
try {
|
|
126
|
+
console.log("[IMA] Destroying existing ads manager");
|
|
127
|
+
adsManager.destroy();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.warn("[IMA] Error destroying ads manager:", error);
|
|
130
|
+
}
|
|
131
|
+
adsManager = void 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
110
134
|
return {
|
|
111
135
|
initialize() {
|
|
112
136
|
ensureImaLoaded().then(() => {
|
|
@@ -139,9 +163,22 @@ function createImaController(video, options) {
|
|
|
139
163
|
},
|
|
140
164
|
async requestAds(vastTagUrl) {
|
|
141
165
|
console.log("[IMA] Requesting ads:", vastTagUrl);
|
|
166
|
+
if (adPlaying) {
|
|
167
|
+
console.warn(
|
|
168
|
+
"[IMA] Cannot request new ads while an ad is playing. Call stop() first."
|
|
169
|
+
);
|
|
170
|
+
return Promise.reject(
|
|
171
|
+
new Error("Ad already playing - cannot request new ads")
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
destroyAdsManager();
|
|
175
|
+
adsLoadedReject = void 0;
|
|
176
|
+
adsLoadedResolve = void 0;
|
|
177
|
+
let currentReject;
|
|
142
178
|
adsLoadedPromise = new Promise((resolve, reject) => {
|
|
143
179
|
adsLoadedResolve = resolve;
|
|
144
180
|
adsLoadedReject = reject;
|
|
181
|
+
currentReject = reject;
|
|
145
182
|
setTimeout(() => {
|
|
146
183
|
if (adsLoadedReject) {
|
|
147
184
|
adsLoadedReject(new Error("Ad request timeout"));
|
|
@@ -163,7 +200,7 @@ function createImaController(video, options) {
|
|
|
163
200
|
container.style.top = "0";
|
|
164
201
|
container.style.right = "0";
|
|
165
202
|
container.style.bottom = "0";
|
|
166
|
-
container.style.display = "
|
|
203
|
+
container.style.display = "none";
|
|
167
204
|
container.style.alignItems = "center";
|
|
168
205
|
container.style.justifyContent = "center";
|
|
169
206
|
container.style.pointerEvents = "none";
|
|
@@ -203,14 +240,14 @@ function createImaController(video, options) {
|
|
|
203
240
|
AdErrorEvent.AD_ERROR,
|
|
204
241
|
(errorEvent) => {
|
|
205
242
|
console.error("[IMA] Ad error:", errorEvent.getError());
|
|
206
|
-
|
|
207
|
-
adsManager?.destroy?.();
|
|
208
|
-
} catch {
|
|
209
|
-
}
|
|
243
|
+
destroyAdsManager();
|
|
210
244
|
adPlaying = false;
|
|
211
245
|
video.muted = originalMutedState;
|
|
212
|
-
if (adContainerEl)
|
|
246
|
+
if (adContainerEl) {
|
|
213
247
|
adContainerEl.style.pointerEvents = "none";
|
|
248
|
+
adContainerEl.style.display = "none";
|
|
249
|
+
console.log("[IMA] Ad container hidden after error");
|
|
250
|
+
}
|
|
214
251
|
if (adsLoadedReject) {
|
|
215
252
|
adsLoadedReject(new Error("Ad playback error"));
|
|
216
253
|
adsLoadedReject = void 0;
|
|
@@ -243,8 +280,6 @@ function createImaController(video, options) {
|
|
|
243
280
|
AdEvent.CONTENT_PAUSE_REQUESTED,
|
|
244
281
|
() => {
|
|
245
282
|
console.log("[IMA] Content pause requested");
|
|
246
|
-
originalMutedState = video.muted;
|
|
247
|
-
video.muted = true;
|
|
248
283
|
if (!options?.continueLiveStreamDuringAds) {
|
|
249
284
|
video.pause();
|
|
250
285
|
console.log("[IMA] Video paused (VOD mode)");
|
|
@@ -253,20 +288,34 @@ function createImaController(video, options) {
|
|
|
253
288
|
"[IMA] Video continues playing but muted (Live mode)"
|
|
254
289
|
);
|
|
255
290
|
}
|
|
291
|
+
video.muted = true;
|
|
256
292
|
adPlaying = true;
|
|
257
|
-
if (adContainerEl)
|
|
258
|
-
adContainerEl.style.pointerEvents = "auto";
|
|
259
293
|
emit("content_pause");
|
|
260
294
|
}
|
|
261
295
|
);
|
|
296
|
+
adsManager.addEventListener(AdEvent.STARTED, () => {
|
|
297
|
+
console.log("[IMA] Ad started playing");
|
|
298
|
+
if (adContainerEl) {
|
|
299
|
+
adContainerEl.style.pointerEvents = "auto";
|
|
300
|
+
adContainerEl.style.display = "flex";
|
|
301
|
+
console.log(
|
|
302
|
+
"[IMA] Ad container visibility set to flex with pointer events enabled"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
262
306
|
adsManager.addEventListener(
|
|
263
307
|
AdEvent.CONTENT_RESUME_REQUESTED,
|
|
264
308
|
() => {
|
|
265
309
|
console.log("[IMA] Content resume requested");
|
|
266
310
|
adPlaying = false;
|
|
267
311
|
video.muted = originalMutedState;
|
|
268
|
-
if (adContainerEl)
|
|
312
|
+
if (adContainerEl) {
|
|
269
313
|
adContainerEl.style.pointerEvents = "none";
|
|
314
|
+
adContainerEl.style.display = "none";
|
|
315
|
+
console.log(
|
|
316
|
+
"[IMA] Ad container hidden - pointer events disabled"
|
|
317
|
+
);
|
|
318
|
+
}
|
|
270
319
|
if (!options?.continueLiveStreamDuringAds) {
|
|
271
320
|
video.play()?.catch(() => {
|
|
272
321
|
});
|
|
@@ -283,7 +332,13 @@ function createImaController(video, options) {
|
|
|
283
332
|
console.log("[IMA] All ads completed");
|
|
284
333
|
adPlaying = false;
|
|
285
334
|
video.muted = originalMutedState;
|
|
286
|
-
if (adContainerEl)
|
|
335
|
+
if (adContainerEl) {
|
|
336
|
+
adContainerEl.style.pointerEvents = "none";
|
|
337
|
+
adContainerEl.style.display = "none";
|
|
338
|
+
console.log(
|
|
339
|
+
"[IMA] Ad container hidden after all ads completed"
|
|
340
|
+
);
|
|
341
|
+
}
|
|
287
342
|
if (!options?.continueLiveStreamDuringAds) {
|
|
288
343
|
video.play().catch(() => {
|
|
289
344
|
});
|
|
@@ -326,6 +381,13 @@ function createImaController(video, options) {
|
|
|
326
381
|
google.ima.AdErrorEvent.Type.AD_ERROR,
|
|
327
382
|
(adErrorEvent) => {
|
|
328
383
|
console.error("[IMA] Ads loader error:", adErrorEvent.getError());
|
|
384
|
+
adPlaying = false;
|
|
385
|
+
video.muted = originalMutedState;
|
|
386
|
+
if (adContainerEl) adContainerEl.style.pointerEvents = "none";
|
|
387
|
+
if (!options?.continueLiveStreamDuringAds) {
|
|
388
|
+
video.play().catch(() => {
|
|
389
|
+
});
|
|
390
|
+
}
|
|
329
391
|
if (adsLoadedReject) {
|
|
330
392
|
adsLoadedReject(new Error("Ads loader error"));
|
|
331
393
|
adsLoadedReject = void 0;
|
|
@@ -341,11 +403,9 @@ function createImaController(video, options) {
|
|
|
341
403
|
return adsLoadedPromise;
|
|
342
404
|
} catch (error) {
|
|
343
405
|
console.error("[IMA] Failed to request ads:", error);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
adsLoadedResolve = void 0;
|
|
348
|
-
}
|
|
406
|
+
currentReject?.(error);
|
|
407
|
+
adsLoadedReject = void 0;
|
|
408
|
+
adsLoadedResolve = void 0;
|
|
349
409
|
return Promise.reject(error);
|
|
350
410
|
}
|
|
351
411
|
},
|
|
@@ -365,14 +425,6 @@ function createImaController(video, options) {
|
|
|
365
425
|
const height = video.clientHeight || 360;
|
|
366
426
|
console.log(`[IMA] Initializing ads manager (${width}x${height})`);
|
|
367
427
|
adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
|
|
368
|
-
if (!options?.continueLiveStreamDuringAds) {
|
|
369
|
-
console.log("[IMA] Pausing video for ad playback (VOD mode)");
|
|
370
|
-
video.pause();
|
|
371
|
-
} else {
|
|
372
|
-
console.log(
|
|
373
|
-
"[IMA] Keeping video playing but muted for ad playback (Live mode)"
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
428
|
adPlaying = true;
|
|
377
429
|
console.log("[IMA] Starting ad playback");
|
|
378
430
|
adsManager.start();
|
|
@@ -390,10 +442,16 @@ function createImaController(video, options) {
|
|
|
390
442
|
async stop() {
|
|
391
443
|
adPlaying = false;
|
|
392
444
|
video.muted = originalMutedState;
|
|
445
|
+
if (adContainerEl) {
|
|
446
|
+
adContainerEl.style.pointerEvents = "none";
|
|
447
|
+
adContainerEl.style.display = "none";
|
|
448
|
+
console.log("[IMA] Ad container hidden after stop");
|
|
449
|
+
}
|
|
393
450
|
try {
|
|
394
451
|
adsManager?.stop?.();
|
|
395
452
|
} catch {
|
|
396
453
|
}
|
|
454
|
+
destroyAdsManager();
|
|
397
455
|
if (!options?.continueLiveStreamDuringAds) {
|
|
398
456
|
video.play().catch(() => {
|
|
399
457
|
});
|
|
@@ -403,12 +461,13 @@ function createImaController(video, options) {
|
|
|
403
461
|
}
|
|
404
462
|
},
|
|
405
463
|
destroy() {
|
|
406
|
-
|
|
407
|
-
adsManager?.destroy?.();
|
|
408
|
-
} catch {
|
|
409
|
-
}
|
|
464
|
+
destroyAdsManager();
|
|
410
465
|
adPlaying = false;
|
|
411
466
|
video.muted = originalMutedState;
|
|
467
|
+
if (adContainerEl) {
|
|
468
|
+
adContainerEl.style.pointerEvents = "none";
|
|
469
|
+
adContainerEl.style.display = "none";
|
|
470
|
+
}
|
|
412
471
|
try {
|
|
413
472
|
adsLoader?.destroy?.();
|
|
414
473
|
} catch {
|
|
@@ -416,6 +475,9 @@ function createImaController(video, options) {
|
|
|
416
475
|
if (adContainerEl?.parentElement) {
|
|
417
476
|
adContainerEl.parentElement.removeChild(adContainerEl);
|
|
418
477
|
}
|
|
478
|
+
adContainerEl = void 0;
|
|
479
|
+
adDisplayContainer = void 0;
|
|
480
|
+
adsLoader = void 0;
|
|
419
481
|
},
|
|
420
482
|
isAdPlaying() {
|
|
421
483
|
return adPlaying;
|
|
@@ -471,6 +533,7 @@ function createImaController(video, options) {
|
|
|
471
533
|
}
|
|
472
534
|
|
|
473
535
|
// src/utils/tracking.ts
|
|
536
|
+
var cachedBrowserId = null;
|
|
474
537
|
function getClientInfo() {
|
|
475
538
|
const ua = navigator.userAgent;
|
|
476
539
|
const platform = navigator.platform;
|
|
@@ -613,6 +676,9 @@ function getClientInfo() {
|
|
|
613
676
|
};
|
|
614
677
|
}
|
|
615
678
|
async function getBrowserID(clientInfo) {
|
|
679
|
+
if (cachedBrowserId) {
|
|
680
|
+
return cachedBrowserId;
|
|
681
|
+
}
|
|
616
682
|
const fingerprintString = JSON.stringify(clientInfo);
|
|
617
683
|
if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
|
|
618
684
|
try {
|
|
@@ -623,6 +689,7 @@ async function getBrowserID(clientInfo) {
|
|
|
623
689
|
);
|
|
624
690
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
625
691
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
692
|
+
cachedBrowserId = hashHex;
|
|
626
693
|
return hashHex;
|
|
627
694
|
} catch (error) {
|
|
628
695
|
console.warn(
|
|
@@ -639,7 +706,8 @@ async function getBrowserID(clientInfo) {
|
|
|
639
706
|
const fallbackHash = Math.abs(hash).toString(16).padStart(8, "0");
|
|
640
707
|
const timestamp = Date.now().toString(16).padStart(12, "0");
|
|
641
708
|
const random = Math.random().toString(16).substring(2, 14).padStart(12, "0");
|
|
642
|
-
|
|
709
|
+
cachedBrowserId = (fallbackHash + timestamp + random).padEnd(64, "0");
|
|
710
|
+
return cachedBrowserId;
|
|
643
711
|
}
|
|
644
712
|
async function sendInitialTracking(licenseKey) {
|
|
645
713
|
try {
|
|
@@ -887,17 +955,8 @@ var StormcloudVideoPlayer = class {
|
|
|
887
955
|
this.video.muted = !!this.config.muted;
|
|
888
956
|
this.ima.initialize();
|
|
889
957
|
this.ima.on("all_ads_completed", () => {
|
|
890
|
-
if (
|
|
891
|
-
|
|
892
|
-
if (remaining > 500 && this.adPodQueue.length > 0) {
|
|
893
|
-
const next = this.adPodQueue.shift();
|
|
894
|
-
this.currentAdIndex++;
|
|
895
|
-
this.playSingleAd(next).catch(() => {
|
|
896
|
-
});
|
|
897
|
-
} else {
|
|
898
|
-
this.currentAdIndex = 0;
|
|
899
|
-
this.totalAdsInBreak = 0;
|
|
900
|
-
this.showAds = false;
|
|
958
|
+
if (this.config.debugAdTiming) {
|
|
959
|
+
console.log("[StormcloudVideoPlayer] IMA all_ads_completed event received");
|
|
901
960
|
}
|
|
902
961
|
});
|
|
903
962
|
this.ima.on("ad_error", () => {
|
|
@@ -928,6 +987,26 @@ var StormcloudVideoPlayer = class {
|
|
|
928
987
|
);
|
|
929
988
|
}
|
|
930
989
|
this.clearAdFailsafeTimer();
|
|
990
|
+
if (!this.inAdBreak) return;
|
|
991
|
+
const remaining = this.getRemainingAdMs();
|
|
992
|
+
if (remaining > 500 && this.adPodQueue.length > 0) {
|
|
993
|
+
const next = this.adPodQueue.shift();
|
|
994
|
+
this.currentAdIndex++;
|
|
995
|
+
if (this.config.debugAdTiming) {
|
|
996
|
+
console.log(
|
|
997
|
+
`[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
this.playSingleAd(next).catch(() => {
|
|
1001
|
+
});
|
|
1002
|
+
} else {
|
|
1003
|
+
if (this.config.debugAdTiming) {
|
|
1004
|
+
console.log("[StormcloudVideoPlayer] Ad pod completed");
|
|
1005
|
+
}
|
|
1006
|
+
this.currentAdIndex = 0;
|
|
1007
|
+
this.totalAdsInBreak = 0;
|
|
1008
|
+
this.showAds = false;
|
|
1009
|
+
}
|
|
931
1010
|
});
|
|
932
1011
|
this.video.addEventListener("timeupdate", () => {
|
|
933
1012
|
this.onTimeUpdate(this.video.currentTime);
|
|
@@ -1328,17 +1407,21 @@ var StormcloudVideoPlayer = class {
|
|
|
1328
1407
|
return void 0;
|
|
1329
1408
|
}
|
|
1330
1409
|
initializeTracking() {
|
|
1331
|
-
sendInitialTracking(this.config.licenseKey).
|
|
1410
|
+
sendInitialTracking(this.config.licenseKey).then(() => {
|
|
1411
|
+
this.heartbeatInterval = window.setInterval(() => {
|
|
1412
|
+
this.sendHeartbeatIfNeeded();
|
|
1413
|
+
}, 5e3);
|
|
1414
|
+
}).catch((error) => {
|
|
1332
1415
|
if (this.config.debugAdTiming) {
|
|
1333
1416
|
console.warn(
|
|
1334
1417
|
"[StormcloudVideoPlayer] Failed to send initial tracking:",
|
|
1335
1418
|
error
|
|
1336
1419
|
);
|
|
1337
1420
|
}
|
|
1421
|
+
this.heartbeatInterval = window.setInterval(() => {
|
|
1422
|
+
this.sendHeartbeatIfNeeded();
|
|
1423
|
+
}, 5e3);
|
|
1338
1424
|
});
|
|
1339
|
-
this.heartbeatInterval = window.setInterval(() => {
|
|
1340
|
-
this.sendHeartbeatIfNeeded();
|
|
1341
|
-
}, 5e3);
|
|
1342
1425
|
}
|
|
1343
1426
|
sendHeartbeatIfNeeded() {
|
|
1344
1427
|
const now = Date.now();
|
|
@@ -1573,9 +1656,21 @@ var StormcloudVideoPlayer = class {
|
|
|
1573
1656
|
if (this.config.debugAdTiming) {
|
|
1574
1657
|
console.log("[StormcloudVideoPlayer] Attempting to play ad:", vastTagUrl);
|
|
1575
1658
|
}
|
|
1659
|
+
if (this.ima.isAdPlaying()) {
|
|
1660
|
+
if (this.config.debugAdTiming) {
|
|
1661
|
+
console.warn(
|
|
1662
|
+
"[StormcloudVideoPlayer] Ad already playing - skipping new ad request"
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
this.ima.updateOriginalMutedState(this.video.muted);
|
|
1576
1668
|
this.startAdFailsafeTimer();
|
|
1577
1669
|
try {
|
|
1578
1670
|
await this.ima.requestAds(vastTagUrl);
|
|
1671
|
+
if (this.config.debugAdTiming) {
|
|
1672
|
+
console.log("[StormcloudVideoPlayer] Ad request successful, starting playback");
|
|
1673
|
+
}
|
|
1579
1674
|
await this.ima.play();
|
|
1580
1675
|
if (this.config.debugAdTiming) {
|
|
1581
1676
|
console.log("[StormcloudVideoPlayer] Ad playback started successfully");
|
|
@@ -1603,6 +1698,13 @@ var StormcloudVideoPlayer = class {
|
|
|
1603
1698
|
this.showAds = false;
|
|
1604
1699
|
this.currentAdIndex = 0;
|
|
1605
1700
|
this.totalAdsInBreak = 0;
|
|
1701
|
+
const originalMutedState = this.ima.getOriginalMutedState();
|
|
1702
|
+
this.video.muted = originalMutedState;
|
|
1703
|
+
if (this.config.debugAdTiming) {
|
|
1704
|
+
console.log(
|
|
1705
|
+
`[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1606
1708
|
if (this.video.paused) {
|
|
1607
1709
|
this.video.play()?.catch(() => {
|
|
1608
1710
|
if (this.config.debugAdTiming) {
|