stormcloud-video-player 0.2.9 → 0.2.11

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.
@@ -73,9 +73,22 @@ function createImaController(video, options) {
73
73
  'script[data-ima="true"]'
74
74
  );
75
75
  if (existing) {
76
- return new Promise(
77
- (resolve) => existing.addEventListener("load", () => resolve())
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 = "flex";
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
- try {
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;
@@ -230,8 +267,11 @@ function createImaController(video, options) {
230
267
  );
231
268
  emit("ad_error");
232
269
  if (!options?.continueLiveStreamDuringAds) {
233
- video.play()?.catch(() => {
234
- });
270
+ if (video.paused) {
271
+ console.log("[IMA] Resuming paused video after ad error");
272
+ video.play()?.catch(() => {
273
+ });
274
+ }
235
275
  }
236
276
  }
237
277
  }
@@ -240,10 +280,6 @@ function createImaController(video, options) {
240
280
  AdEvent.CONTENT_PAUSE_REQUESTED,
241
281
  () => {
242
282
  console.log("[IMA] Content pause requested");
243
- if (!adPlaying) {
244
- originalMutedState = video.muted;
245
- }
246
- video.muted = true;
247
283
  if (!options?.continueLiveStreamDuringAds) {
248
284
  video.pause();
249
285
  console.log("[IMA] Video paused (VOD mode)");
@@ -252,20 +288,34 @@ function createImaController(video, options) {
252
288
  "[IMA] Video continues playing but muted (Live mode)"
253
289
  );
254
290
  }
291
+ video.muted = true;
255
292
  adPlaying = true;
256
- if (adContainerEl)
257
- adContainerEl.style.pointerEvents = "auto";
258
293
  emit("content_pause");
259
294
  }
260
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
+ });
261
306
  adsManager.addEventListener(
262
307
  AdEvent.CONTENT_RESUME_REQUESTED,
263
308
  () => {
264
309
  console.log("[IMA] Content resume requested");
265
310
  adPlaying = false;
266
311
  video.muted = originalMutedState;
267
- if (adContainerEl)
312
+ if (adContainerEl) {
268
313
  adContainerEl.style.pointerEvents = "none";
314
+ adContainerEl.style.display = "none";
315
+ console.log(
316
+ "[IMA] Ad container hidden - pointer events disabled"
317
+ );
318
+ }
269
319
  if (!options?.continueLiveStreamDuringAds) {
270
320
  video.play()?.catch(() => {
271
321
  });
@@ -282,7 +332,13 @@ function createImaController(video, options) {
282
332
  console.log("[IMA] All ads completed");
283
333
  adPlaying = false;
284
334
  video.muted = originalMutedState;
285
- if (adContainerEl) adContainerEl.style.pointerEvents = "none";
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
+ }
286
342
  if (!options?.continueLiveStreamDuringAds) {
287
343
  video.play().catch(() => {
288
344
  });
@@ -306,10 +362,17 @@ function createImaController(video, options) {
306
362
  console.error("[IMA] Error setting up ads manager:", e);
307
363
  adPlaying = false;
308
364
  video.muted = originalMutedState;
309
- if (adContainerEl) adContainerEl.style.pointerEvents = "none";
365
+ if (adContainerEl) {
366
+ adContainerEl.style.pointerEvents = "none";
367
+ adContainerEl.style.display = "none";
368
+ console.log("[IMA] Ad container hidden after setup error");
369
+ }
310
370
  if (!options?.continueLiveStreamDuringAds) {
311
- video.play().catch(() => {
312
- });
371
+ if (video.paused) {
372
+ console.log("[IMA] Resuming paused video after setup error");
373
+ video.play().catch(() => {
374
+ });
375
+ }
313
376
  }
314
377
  if (adsLoadedReject) {
315
378
  adsLoadedReject(new Error("Failed to setup ads manager"));
@@ -325,6 +388,20 @@ function createImaController(video, options) {
325
388
  google.ima.AdErrorEvent.Type.AD_ERROR,
326
389
  (adErrorEvent) => {
327
390
  console.error("[IMA] Ads loader error:", adErrorEvent.getError());
391
+ adPlaying = false;
392
+ video.muted = originalMutedState;
393
+ if (adContainerEl) {
394
+ adContainerEl.style.pointerEvents = "none";
395
+ adContainerEl.style.display = "none";
396
+ console.log("[IMA] Ad container hidden after loader error");
397
+ }
398
+ if (!options?.continueLiveStreamDuringAds) {
399
+ if (video.paused) {
400
+ console.log("[IMA] Resuming paused video after loader error");
401
+ video.play().catch(() => {
402
+ });
403
+ }
404
+ }
328
405
  if (adsLoadedReject) {
329
406
  adsLoadedReject(new Error("Ads loader error"));
330
407
  adsLoadedReject = void 0;
@@ -340,11 +417,9 @@ function createImaController(video, options) {
340
417
  return adsLoadedPromise;
341
418
  } catch (error) {
342
419
  console.error("[IMA] Failed to request ads:", error);
343
- if (adsLoadedReject) {
344
- adsLoadedReject(error);
345
- adsLoadedReject = void 0;
346
- adsLoadedResolve = void 0;
347
- }
420
+ currentReject?.(error);
421
+ adsLoadedReject = void 0;
422
+ adsLoadedResolve = void 0;
348
423
  return Promise.reject(error);
349
424
  }
350
425
  },
@@ -381,10 +456,16 @@ function createImaController(video, options) {
381
456
  async stop() {
382
457
  adPlaying = false;
383
458
  video.muted = originalMutedState;
459
+ if (adContainerEl) {
460
+ adContainerEl.style.pointerEvents = "none";
461
+ adContainerEl.style.display = "none";
462
+ console.log("[IMA] Ad container hidden after stop");
463
+ }
384
464
  try {
385
465
  adsManager?.stop?.();
386
466
  } catch {
387
467
  }
468
+ destroyAdsManager();
388
469
  if (!options?.continueLiveStreamDuringAds) {
389
470
  video.play().catch(() => {
390
471
  });
@@ -394,12 +475,13 @@ function createImaController(video, options) {
394
475
  }
395
476
  },
396
477
  destroy() {
397
- try {
398
- adsManager?.destroy?.();
399
- } catch {
400
- }
478
+ destroyAdsManager();
401
479
  adPlaying = false;
402
480
  video.muted = originalMutedState;
481
+ if (adContainerEl) {
482
+ adContainerEl.style.pointerEvents = "none";
483
+ adContainerEl.style.display = "none";
484
+ }
403
485
  try {
404
486
  adsLoader?.destroy?.();
405
487
  } catch {
@@ -407,6 +489,9 @@ function createImaController(video, options) {
407
489
  if (adContainerEl?.parentElement) {
408
490
  adContainerEl.parentElement.removeChild(adContainerEl);
409
491
  }
492
+ adContainerEl = void 0;
493
+ adDisplayContainer = void 0;
494
+ adsLoader = void 0;
410
495
  },
411
496
  isAdPlaying() {
412
497
  return adPlaying;
@@ -884,32 +969,33 @@ var StormcloudVideoPlayer = class {
884
969
  this.video.muted = !!this.config.muted;
885
970
  this.ima.initialize();
886
971
  this.ima.on("all_ads_completed", () => {
887
- if (!this.inAdBreak) return;
888
- const remaining = this.getRemainingAdMs();
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;
972
+ if (this.config.debugAdTiming) {
973
+ console.log("[StormcloudVideoPlayer] IMA all_ads_completed event received");
898
974
  }
899
975
  });
900
976
  this.ima.on("ad_error", () => {
901
977
  if (this.config.debugAdTiming) {
902
978
  console.log("[StormcloudVideoPlayer] IMA ad_error event received");
903
979
  }
904
- if (!this.inAdBreak) return;
905
- const remaining = this.getRemainingAdMs();
906
- if (remaining > 500 && this.adPodQueue.length > 0) {
907
- const next = this.adPodQueue.shift();
908
- this.currentAdIndex++;
909
- this.playSingleAd(next).catch(() => {
910
- });
911
- } else {
912
- this.handleAdFailure();
980
+ if (this.showAds) {
981
+ if (this.inAdBreak) {
982
+ const remaining = this.getRemainingAdMs();
983
+ if (remaining > 500 && this.adPodQueue.length > 0) {
984
+ const next = this.adPodQueue.shift();
985
+ this.currentAdIndex++;
986
+ this.playSingleAd(next).catch(() => {
987
+ });
988
+ } else {
989
+ this.handleAdFailure();
990
+ }
991
+ } else {
992
+ if (this.config.debugAdTiming) {
993
+ console.log(
994
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
995
+ );
996
+ }
997
+ this.handleAdFailure();
998
+ }
913
999
  }
914
1000
  });
915
1001
  this.ima.on("content_pause", () => {
@@ -925,6 +1011,26 @@ var StormcloudVideoPlayer = class {
925
1011
  );
926
1012
  }
927
1013
  this.clearAdFailsafeTimer();
1014
+ if (!this.inAdBreak) return;
1015
+ const remaining = this.getRemainingAdMs();
1016
+ if (remaining > 500 && this.adPodQueue.length > 0) {
1017
+ const next = this.adPodQueue.shift();
1018
+ this.currentAdIndex++;
1019
+ if (this.config.debugAdTiming) {
1020
+ console.log(
1021
+ `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
1022
+ );
1023
+ }
1024
+ this.playSingleAd(next).catch(() => {
1025
+ });
1026
+ } else {
1027
+ if (this.config.debugAdTiming) {
1028
+ console.log("[StormcloudVideoPlayer] Ad pod completed");
1029
+ }
1030
+ this.currentAdIndex = 0;
1031
+ this.totalAdsInBreak = 0;
1032
+ this.showAds = false;
1033
+ }
928
1034
  });
929
1035
  this.video.addEventListener("timeupdate", () => {
930
1036
  this.onTimeUpdate(this.video.currentTime);
@@ -1470,9 +1576,20 @@ var StormcloudVideoPlayer = class {
1470
1576
  return;
1471
1577
  }
1472
1578
  if (vastTagUrl) {
1579
+ this.inAdBreak = true;
1473
1580
  this.showAds = true;
1474
1581
  this.currentAdIndex++;
1475
- await this.playSingleAd(vastTagUrl);
1582
+ try {
1583
+ await this.playSingleAd(vastTagUrl);
1584
+ } catch (error) {
1585
+ if (this.config.debugAdTiming) {
1586
+ console.error(
1587
+ "[StormcloudVideoPlayer] Ad playback failed in handleAdStart:",
1588
+ error
1589
+ );
1590
+ }
1591
+ this.handleAdFailure();
1592
+ }
1476
1593
  }
1477
1594
  if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1478
1595
  this.expectedAdBreakDurationMs = scheduled.durationMs;
@@ -1574,21 +1691,21 @@ var StormcloudVideoPlayer = class {
1574
1691
  if (this.config.debugAdTiming) {
1575
1692
  console.log("[StormcloudVideoPlayer] Attempting to play ad:", vastTagUrl);
1576
1693
  }
1577
- this.ima.updateOriginalMutedState(this.video.muted);
1578
- if (!this.shouldContinueLiveStreamDuringAds()) {
1579
- if (this.config.debugAdTiming) {
1580
- console.log("[StormcloudVideoPlayer] Pausing video immediately for ad (VOD mode)");
1581
- }
1582
- this.video.pause();
1583
- } else {
1694
+ if (this.ima.isAdPlaying()) {
1584
1695
  if (this.config.debugAdTiming) {
1585
- console.log("[StormcloudVideoPlayer] Muting video for ad (Live mode)");
1696
+ console.warn(
1697
+ "[StormcloudVideoPlayer] Ad already playing - skipping new ad request"
1698
+ );
1586
1699
  }
1587
- this.video.muted = true;
1700
+ return;
1588
1701
  }
1702
+ this.ima.updateOriginalMutedState(this.video.muted);
1589
1703
  this.startAdFailsafeTimer();
1590
1704
  try {
1591
1705
  await this.ima.requestAds(vastTagUrl);
1706
+ if (this.config.debugAdTiming) {
1707
+ console.log("[StormcloudVideoPlayer] Ad request successful, starting playback");
1708
+ }
1592
1709
  await this.ima.play();
1593
1710
  if (this.config.debugAdTiming) {
1594
1711
  console.log("[StormcloudVideoPlayer] Ad playback started successfully");
@@ -1603,7 +1720,13 @@ var StormcloudVideoPlayer = class {
1603
1720
  handleAdFailure() {
1604
1721
  if (this.config.debugAdTiming) {
1605
1722
  console.log(
1606
- "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1723
+ "[StormcloudVideoPlayer] Handling ad failure - resuming content",
1724
+ {
1725
+ inAdBreak: this.inAdBreak,
1726
+ showAds: this.showAds,
1727
+ videoPaused: this.video.paused,
1728
+ adPlaying: this.ima.isAdPlaying()
1729
+ }
1607
1730
  );
1608
1731
  }
1609
1732
  this.inAdBreak = false;
@@ -1616,14 +1739,29 @@ var StormcloudVideoPlayer = class {
1616
1739
  this.showAds = false;
1617
1740
  this.currentAdIndex = 0;
1618
1741
  this.totalAdsInBreak = 0;
1742
+ const originalMutedState = this.ima.getOriginalMutedState();
1743
+ this.video.muted = originalMutedState;
1744
+ if (this.config.debugAdTiming) {
1745
+ console.log(
1746
+ `[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
1747
+ );
1748
+ }
1619
1749
  if (this.video.paused) {
1620
- this.video.play()?.catch(() => {
1750
+ if (this.config.debugAdTiming) {
1751
+ console.log("[StormcloudVideoPlayer] Resuming paused video");
1752
+ }
1753
+ this.video.play()?.catch((error) => {
1621
1754
  if (this.config.debugAdTiming) {
1622
1755
  console.error(
1623
- "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1756
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure:",
1757
+ error
1624
1758
  );
1625
1759
  }
1626
1760
  });
1761
+ } else {
1762
+ if (this.config.debugAdTiming) {
1763
+ console.log("[StormcloudVideoPlayer] Video is already playing, no resume needed");
1764
+ }
1627
1765
  }
1628
1766
  }
1629
1767
  startAdFailsafeTimer() {
@@ -1635,10 +1773,12 @@ var StormcloudVideoPlayer = class {
1635
1773
  );
1636
1774
  }
1637
1775
  this.adFailsafeTimerId = window.setTimeout(() => {
1638
- if (this.video.paused) {
1776
+ const shouldTrigger = this.video.paused || this.showAds && !this.ima.isAdPlaying();
1777
+ if (shouldTrigger) {
1639
1778
  if (this.config.debugAdTiming) {
1640
1779
  console.warn(
1641
- "[StormcloudVideoPlayer] Failsafe timer triggered - forcing video resume"
1780
+ "[StormcloudVideoPlayer] Failsafe timer triggered - forcing video resume",
1781
+ { paused: this.video.paused, showAds: this.showAds, adPlaying: this.ima.isAdPlaying() }
1642
1782
  );
1643
1783
  }
1644
1784
  this.handleAdFailure();