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
|
@@ -73,9 +73,22 @@ function createImaController(video, options) {
|
|
|
73
73
|
'script[data-ima="true"]'
|
|
74
74
|
);
|
|
75
75
|
if (existing) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
if (window.google?.ima) {
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
reject(new Error("IMA SDK load timeout"));
|
|
82
|
+
}, 1e4);
|
|
83
|
+
existing.addEventListener("load", () => {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
existing.addEventListener("error", () => {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
reject(new Error("IMA SDK load failed"));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
79
92
|
}
|
|
80
93
|
return new Promise((resolve, reject) => {
|
|
81
94
|
const script = document.createElement("script");
|
|
@@ -104,6 +117,17 @@ function createImaController(video, options) {
|
|
|
104
117
|
adsRequest.adTagUrl = vastTagUrl;
|
|
105
118
|
adsLoader.requestAds(adsRequest);
|
|
106
119
|
}
|
|
120
|
+
function destroyAdsManager() {
|
|
121
|
+
if (adsManager) {
|
|
122
|
+
try {
|
|
123
|
+
console.log("[IMA] Destroying existing ads manager");
|
|
124
|
+
adsManager.destroy();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.warn("[IMA] Error destroying ads manager:", error);
|
|
127
|
+
}
|
|
128
|
+
adsManager = void 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
107
131
|
return {
|
|
108
132
|
initialize() {
|
|
109
133
|
ensureImaLoaded().then(() => {
|
|
@@ -136,9 +160,22 @@ function createImaController(video, options) {
|
|
|
136
160
|
},
|
|
137
161
|
async requestAds(vastTagUrl) {
|
|
138
162
|
console.log("[IMA] Requesting ads:", vastTagUrl);
|
|
163
|
+
if (adPlaying) {
|
|
164
|
+
console.warn(
|
|
165
|
+
"[IMA] Cannot request new ads while an ad is playing. Call stop() first."
|
|
166
|
+
);
|
|
167
|
+
return Promise.reject(
|
|
168
|
+
new Error("Ad already playing - cannot request new ads")
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
destroyAdsManager();
|
|
172
|
+
adsLoadedReject = void 0;
|
|
173
|
+
adsLoadedResolve = void 0;
|
|
174
|
+
let currentReject;
|
|
139
175
|
adsLoadedPromise = new Promise((resolve, reject) => {
|
|
140
176
|
adsLoadedResolve = resolve;
|
|
141
177
|
adsLoadedReject = reject;
|
|
178
|
+
currentReject = reject;
|
|
142
179
|
setTimeout(() => {
|
|
143
180
|
if (adsLoadedReject) {
|
|
144
181
|
adsLoadedReject(new Error("Ad request timeout"));
|
|
@@ -160,7 +197,7 @@ function createImaController(video, options) {
|
|
|
160
197
|
container.style.top = "0";
|
|
161
198
|
container.style.right = "0";
|
|
162
199
|
container.style.bottom = "0";
|
|
163
|
-
container.style.display = "
|
|
200
|
+
container.style.display = "none";
|
|
164
201
|
container.style.alignItems = "center";
|
|
165
202
|
container.style.justifyContent = "center";
|
|
166
203
|
container.style.pointerEvents = "none";
|
|
@@ -200,14 +237,14 @@ function createImaController(video, options) {
|
|
|
200
237
|
AdErrorEvent.AD_ERROR,
|
|
201
238
|
(errorEvent) => {
|
|
202
239
|
console.error("[IMA] Ad error:", errorEvent.getError());
|
|
203
|
-
|
|
204
|
-
adsManager?.destroy?.();
|
|
205
|
-
} catch {
|
|
206
|
-
}
|
|
240
|
+
destroyAdsManager();
|
|
207
241
|
adPlaying = false;
|
|
208
242
|
video.muted = originalMutedState;
|
|
209
|
-
if (adContainerEl)
|
|
243
|
+
if (adContainerEl) {
|
|
210
244
|
adContainerEl.style.pointerEvents = "none";
|
|
245
|
+
adContainerEl.style.display = "none";
|
|
246
|
+
console.log("[IMA] Ad container hidden after error");
|
|
247
|
+
}
|
|
211
248
|
if (adsLoadedReject) {
|
|
212
249
|
adsLoadedReject(new Error("Ad playback error"));
|
|
213
250
|
adsLoadedReject = void 0;
|
|
@@ -240,8 +277,6 @@ function createImaController(video, options) {
|
|
|
240
277
|
AdEvent.CONTENT_PAUSE_REQUESTED,
|
|
241
278
|
() => {
|
|
242
279
|
console.log("[IMA] Content pause requested");
|
|
243
|
-
originalMutedState = video.muted;
|
|
244
|
-
video.muted = true;
|
|
245
280
|
if (!options?.continueLiveStreamDuringAds) {
|
|
246
281
|
video.pause();
|
|
247
282
|
console.log("[IMA] Video paused (VOD mode)");
|
|
@@ -250,20 +285,34 @@ function createImaController(video, options) {
|
|
|
250
285
|
"[IMA] Video continues playing but muted (Live mode)"
|
|
251
286
|
);
|
|
252
287
|
}
|
|
288
|
+
video.muted = true;
|
|
253
289
|
adPlaying = true;
|
|
254
|
-
if (adContainerEl)
|
|
255
|
-
adContainerEl.style.pointerEvents = "auto";
|
|
256
290
|
emit("content_pause");
|
|
257
291
|
}
|
|
258
292
|
);
|
|
293
|
+
adsManager.addEventListener(AdEvent.STARTED, () => {
|
|
294
|
+
console.log("[IMA] Ad started playing");
|
|
295
|
+
if (adContainerEl) {
|
|
296
|
+
adContainerEl.style.pointerEvents = "auto";
|
|
297
|
+
adContainerEl.style.display = "flex";
|
|
298
|
+
console.log(
|
|
299
|
+
"[IMA] Ad container visibility set to flex with pointer events enabled"
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
259
303
|
adsManager.addEventListener(
|
|
260
304
|
AdEvent.CONTENT_RESUME_REQUESTED,
|
|
261
305
|
() => {
|
|
262
306
|
console.log("[IMA] Content resume requested");
|
|
263
307
|
adPlaying = false;
|
|
264
308
|
video.muted = originalMutedState;
|
|
265
|
-
if (adContainerEl)
|
|
309
|
+
if (adContainerEl) {
|
|
266
310
|
adContainerEl.style.pointerEvents = "none";
|
|
311
|
+
adContainerEl.style.display = "none";
|
|
312
|
+
console.log(
|
|
313
|
+
"[IMA] Ad container hidden - pointer events disabled"
|
|
314
|
+
);
|
|
315
|
+
}
|
|
267
316
|
if (!options?.continueLiveStreamDuringAds) {
|
|
268
317
|
video.play()?.catch(() => {
|
|
269
318
|
});
|
|
@@ -280,7 +329,13 @@ function createImaController(video, options) {
|
|
|
280
329
|
console.log("[IMA] All ads completed");
|
|
281
330
|
adPlaying = false;
|
|
282
331
|
video.muted = originalMutedState;
|
|
283
|
-
if (adContainerEl)
|
|
332
|
+
if (adContainerEl) {
|
|
333
|
+
adContainerEl.style.pointerEvents = "none";
|
|
334
|
+
adContainerEl.style.display = "none";
|
|
335
|
+
console.log(
|
|
336
|
+
"[IMA] Ad container hidden after all ads completed"
|
|
337
|
+
);
|
|
338
|
+
}
|
|
284
339
|
if (!options?.continueLiveStreamDuringAds) {
|
|
285
340
|
video.play().catch(() => {
|
|
286
341
|
});
|
|
@@ -323,6 +378,13 @@ function createImaController(video, options) {
|
|
|
323
378
|
google.ima.AdErrorEvent.Type.AD_ERROR,
|
|
324
379
|
(adErrorEvent) => {
|
|
325
380
|
console.error("[IMA] Ads loader error:", adErrorEvent.getError());
|
|
381
|
+
adPlaying = false;
|
|
382
|
+
video.muted = originalMutedState;
|
|
383
|
+
if (adContainerEl) adContainerEl.style.pointerEvents = "none";
|
|
384
|
+
if (!options?.continueLiveStreamDuringAds) {
|
|
385
|
+
video.play().catch(() => {
|
|
386
|
+
});
|
|
387
|
+
}
|
|
326
388
|
if (adsLoadedReject) {
|
|
327
389
|
adsLoadedReject(new Error("Ads loader error"));
|
|
328
390
|
adsLoadedReject = void 0;
|
|
@@ -338,11 +400,9 @@ function createImaController(video, options) {
|
|
|
338
400
|
return adsLoadedPromise;
|
|
339
401
|
} catch (error) {
|
|
340
402
|
console.error("[IMA] Failed to request ads:", error);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
adsLoadedResolve = void 0;
|
|
345
|
-
}
|
|
403
|
+
currentReject?.(error);
|
|
404
|
+
adsLoadedReject = void 0;
|
|
405
|
+
adsLoadedResolve = void 0;
|
|
346
406
|
return Promise.reject(error);
|
|
347
407
|
}
|
|
348
408
|
},
|
|
@@ -362,14 +422,6 @@ function createImaController(video, options) {
|
|
|
362
422
|
const height = video.clientHeight || 360;
|
|
363
423
|
console.log(`[IMA] Initializing ads manager (${width}x${height})`);
|
|
364
424
|
adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
|
|
365
|
-
if (!options?.continueLiveStreamDuringAds) {
|
|
366
|
-
console.log("[IMA] Pausing video for ad playback (VOD mode)");
|
|
367
|
-
video.pause();
|
|
368
|
-
} else {
|
|
369
|
-
console.log(
|
|
370
|
-
"[IMA] Keeping video playing but muted for ad playback (Live mode)"
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
425
|
adPlaying = true;
|
|
374
426
|
console.log("[IMA] Starting ad playback");
|
|
375
427
|
adsManager.start();
|
|
@@ -387,10 +439,16 @@ function createImaController(video, options) {
|
|
|
387
439
|
async stop() {
|
|
388
440
|
adPlaying = false;
|
|
389
441
|
video.muted = originalMutedState;
|
|
442
|
+
if (adContainerEl) {
|
|
443
|
+
adContainerEl.style.pointerEvents = "none";
|
|
444
|
+
adContainerEl.style.display = "none";
|
|
445
|
+
console.log("[IMA] Ad container hidden after stop");
|
|
446
|
+
}
|
|
390
447
|
try {
|
|
391
448
|
adsManager?.stop?.();
|
|
392
449
|
} catch {
|
|
393
450
|
}
|
|
451
|
+
destroyAdsManager();
|
|
394
452
|
if (!options?.continueLiveStreamDuringAds) {
|
|
395
453
|
video.play().catch(() => {
|
|
396
454
|
});
|
|
@@ -400,12 +458,13 @@ function createImaController(video, options) {
|
|
|
400
458
|
}
|
|
401
459
|
},
|
|
402
460
|
destroy() {
|
|
403
|
-
|
|
404
|
-
adsManager?.destroy?.();
|
|
405
|
-
} catch {
|
|
406
|
-
}
|
|
461
|
+
destroyAdsManager();
|
|
407
462
|
adPlaying = false;
|
|
408
463
|
video.muted = originalMutedState;
|
|
464
|
+
if (adContainerEl) {
|
|
465
|
+
adContainerEl.style.pointerEvents = "none";
|
|
466
|
+
adContainerEl.style.display = "none";
|
|
467
|
+
}
|
|
409
468
|
try {
|
|
410
469
|
adsLoader?.destroy?.();
|
|
411
470
|
} catch {
|
|
@@ -413,6 +472,9 @@ function createImaController(video, options) {
|
|
|
413
472
|
if (adContainerEl?.parentElement) {
|
|
414
473
|
adContainerEl.parentElement.removeChild(adContainerEl);
|
|
415
474
|
}
|
|
475
|
+
adContainerEl = void 0;
|
|
476
|
+
adDisplayContainer = void 0;
|
|
477
|
+
adsLoader = void 0;
|
|
416
478
|
},
|
|
417
479
|
isAdPlaying() {
|
|
418
480
|
return adPlaying;
|
|
@@ -468,6 +530,7 @@ function createImaController(video, options) {
|
|
|
468
530
|
}
|
|
469
531
|
|
|
470
532
|
// src/utils/tracking.ts
|
|
533
|
+
var cachedBrowserId = null;
|
|
471
534
|
function getClientInfo() {
|
|
472
535
|
const ua = navigator.userAgent;
|
|
473
536
|
const platform = navigator.platform;
|
|
@@ -610,6 +673,9 @@ function getClientInfo() {
|
|
|
610
673
|
};
|
|
611
674
|
}
|
|
612
675
|
async function getBrowserID(clientInfo) {
|
|
676
|
+
if (cachedBrowserId) {
|
|
677
|
+
return cachedBrowserId;
|
|
678
|
+
}
|
|
613
679
|
const fingerprintString = JSON.stringify(clientInfo);
|
|
614
680
|
if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
|
|
615
681
|
try {
|
|
@@ -620,6 +686,7 @@ async function getBrowserID(clientInfo) {
|
|
|
620
686
|
);
|
|
621
687
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
622
688
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
689
|
+
cachedBrowserId = hashHex;
|
|
623
690
|
return hashHex;
|
|
624
691
|
} catch (error) {
|
|
625
692
|
console.warn(
|
|
@@ -636,7 +703,8 @@ async function getBrowserID(clientInfo) {
|
|
|
636
703
|
const fallbackHash = Math.abs(hash).toString(16).padStart(8, "0");
|
|
637
704
|
const timestamp = Date.now().toString(16).padStart(12, "0");
|
|
638
705
|
const random = Math.random().toString(16).substring(2, 14).padStart(12, "0");
|
|
639
|
-
|
|
706
|
+
cachedBrowserId = (fallbackHash + timestamp + random).padEnd(64, "0");
|
|
707
|
+
return cachedBrowserId;
|
|
640
708
|
}
|
|
641
709
|
async function sendInitialTracking(licenseKey) {
|
|
642
710
|
try {
|
|
@@ -884,17 +952,8 @@ var StormcloudVideoPlayer = class {
|
|
|
884
952
|
this.video.muted = !!this.config.muted;
|
|
885
953
|
this.ima.initialize();
|
|
886
954
|
this.ima.on("all_ads_completed", () => {
|
|
887
|
-
if (
|
|
888
|
-
|
|
889
|
-
if (remaining > 500 && this.adPodQueue.length > 0) {
|
|
890
|
-
const next = this.adPodQueue.shift();
|
|
891
|
-
this.currentAdIndex++;
|
|
892
|
-
this.playSingleAd(next).catch(() => {
|
|
893
|
-
});
|
|
894
|
-
} else {
|
|
895
|
-
this.currentAdIndex = 0;
|
|
896
|
-
this.totalAdsInBreak = 0;
|
|
897
|
-
this.showAds = false;
|
|
955
|
+
if (this.config.debugAdTiming) {
|
|
956
|
+
console.log("[StormcloudVideoPlayer] IMA all_ads_completed event received");
|
|
898
957
|
}
|
|
899
958
|
});
|
|
900
959
|
this.ima.on("ad_error", () => {
|
|
@@ -925,6 +984,26 @@ var StormcloudVideoPlayer = class {
|
|
|
925
984
|
);
|
|
926
985
|
}
|
|
927
986
|
this.clearAdFailsafeTimer();
|
|
987
|
+
if (!this.inAdBreak) return;
|
|
988
|
+
const remaining = this.getRemainingAdMs();
|
|
989
|
+
if (remaining > 500 && this.adPodQueue.length > 0) {
|
|
990
|
+
const next = this.adPodQueue.shift();
|
|
991
|
+
this.currentAdIndex++;
|
|
992
|
+
if (this.config.debugAdTiming) {
|
|
993
|
+
console.log(
|
|
994
|
+
`[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
this.playSingleAd(next).catch(() => {
|
|
998
|
+
});
|
|
999
|
+
} else {
|
|
1000
|
+
if (this.config.debugAdTiming) {
|
|
1001
|
+
console.log("[StormcloudVideoPlayer] Ad pod completed");
|
|
1002
|
+
}
|
|
1003
|
+
this.currentAdIndex = 0;
|
|
1004
|
+
this.totalAdsInBreak = 0;
|
|
1005
|
+
this.showAds = false;
|
|
1006
|
+
}
|
|
928
1007
|
});
|
|
929
1008
|
this.video.addEventListener("timeupdate", () => {
|
|
930
1009
|
this.onTimeUpdate(this.video.currentTime);
|
|
@@ -1325,17 +1404,21 @@ var StormcloudVideoPlayer = class {
|
|
|
1325
1404
|
return void 0;
|
|
1326
1405
|
}
|
|
1327
1406
|
initializeTracking() {
|
|
1328
|
-
sendInitialTracking(this.config.licenseKey).
|
|
1407
|
+
sendInitialTracking(this.config.licenseKey).then(() => {
|
|
1408
|
+
this.heartbeatInterval = window.setInterval(() => {
|
|
1409
|
+
this.sendHeartbeatIfNeeded();
|
|
1410
|
+
}, 5e3);
|
|
1411
|
+
}).catch((error) => {
|
|
1329
1412
|
if (this.config.debugAdTiming) {
|
|
1330
1413
|
console.warn(
|
|
1331
1414
|
"[StormcloudVideoPlayer] Failed to send initial tracking:",
|
|
1332
1415
|
error
|
|
1333
1416
|
);
|
|
1334
1417
|
}
|
|
1418
|
+
this.heartbeatInterval = window.setInterval(() => {
|
|
1419
|
+
this.sendHeartbeatIfNeeded();
|
|
1420
|
+
}, 5e3);
|
|
1335
1421
|
});
|
|
1336
|
-
this.heartbeatInterval = window.setInterval(() => {
|
|
1337
|
-
this.sendHeartbeatIfNeeded();
|
|
1338
|
-
}, 5e3);
|
|
1339
1422
|
}
|
|
1340
1423
|
sendHeartbeatIfNeeded() {
|
|
1341
1424
|
const now = Date.now();
|
|
@@ -1570,9 +1653,21 @@ var StormcloudVideoPlayer = class {
|
|
|
1570
1653
|
if (this.config.debugAdTiming) {
|
|
1571
1654
|
console.log("[StormcloudVideoPlayer] Attempting to play ad:", vastTagUrl);
|
|
1572
1655
|
}
|
|
1656
|
+
if (this.ima.isAdPlaying()) {
|
|
1657
|
+
if (this.config.debugAdTiming) {
|
|
1658
|
+
console.warn(
|
|
1659
|
+
"[StormcloudVideoPlayer] Ad already playing - skipping new ad request"
|
|
1660
|
+
);
|
|
1661
|
+
}
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
this.ima.updateOriginalMutedState(this.video.muted);
|
|
1573
1665
|
this.startAdFailsafeTimer();
|
|
1574
1666
|
try {
|
|
1575
1667
|
await this.ima.requestAds(vastTagUrl);
|
|
1668
|
+
if (this.config.debugAdTiming) {
|
|
1669
|
+
console.log("[StormcloudVideoPlayer] Ad request successful, starting playback");
|
|
1670
|
+
}
|
|
1576
1671
|
await this.ima.play();
|
|
1577
1672
|
if (this.config.debugAdTiming) {
|
|
1578
1673
|
console.log("[StormcloudVideoPlayer] Ad playback started successfully");
|
|
@@ -1600,6 +1695,13 @@ var StormcloudVideoPlayer = class {
|
|
|
1600
1695
|
this.showAds = false;
|
|
1601
1696
|
this.currentAdIndex = 0;
|
|
1602
1697
|
this.totalAdsInBreak = 0;
|
|
1698
|
+
const originalMutedState = this.ima.getOriginalMutedState();
|
|
1699
|
+
this.video.muted = originalMutedState;
|
|
1700
|
+
if (this.config.debugAdTiming) {
|
|
1701
|
+
console.log(
|
|
1702
|
+
`[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1603
1705
|
if (this.video.paused) {
|
|
1604
1706
|
this.video.play()?.catch(() => {
|
|
1605
1707
|
if (this.config.debugAdTiming) {
|