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.
@@ -115,9 +115,22 @@ function createImaController(video, options) {
115
115
  'script[data-ima="true"]'
116
116
  );
117
117
  if (existing) {
118
- return new Promise(
119
- (resolve) => existing.addEventListener("load", () => resolve())
120
- );
118
+ if (window.google?.ima) {
119
+ return Promise.resolve();
120
+ }
121
+ return new Promise((resolve, reject) => {
122
+ const timeout = setTimeout(() => {
123
+ reject(new Error("IMA SDK load timeout"));
124
+ }, 1e4);
125
+ existing.addEventListener("load", () => {
126
+ clearTimeout(timeout);
127
+ resolve();
128
+ });
129
+ existing.addEventListener("error", () => {
130
+ clearTimeout(timeout);
131
+ reject(new Error("IMA SDK load failed"));
132
+ });
133
+ });
121
134
  }
122
135
  return new Promise((resolve, reject) => {
123
136
  const script = document.createElement("script");
@@ -146,6 +159,17 @@ function createImaController(video, options) {
146
159
  adsRequest.adTagUrl = vastTagUrl;
147
160
  adsLoader.requestAds(adsRequest);
148
161
  }
162
+ function destroyAdsManager() {
163
+ if (adsManager) {
164
+ try {
165
+ console.log("[IMA] Destroying existing ads manager");
166
+ adsManager.destroy();
167
+ } catch (error) {
168
+ console.warn("[IMA] Error destroying ads manager:", error);
169
+ }
170
+ adsManager = void 0;
171
+ }
172
+ }
149
173
  return {
150
174
  initialize() {
151
175
  ensureImaLoaded().then(() => {
@@ -178,9 +202,22 @@ function createImaController(video, options) {
178
202
  },
179
203
  async requestAds(vastTagUrl) {
180
204
  console.log("[IMA] Requesting ads:", vastTagUrl);
205
+ if (adPlaying) {
206
+ console.warn(
207
+ "[IMA] Cannot request new ads while an ad is playing. Call stop() first."
208
+ );
209
+ return Promise.reject(
210
+ new Error("Ad already playing - cannot request new ads")
211
+ );
212
+ }
213
+ destroyAdsManager();
214
+ adsLoadedReject = void 0;
215
+ adsLoadedResolve = void 0;
216
+ let currentReject;
181
217
  adsLoadedPromise = new Promise((resolve, reject) => {
182
218
  adsLoadedResolve = resolve;
183
219
  adsLoadedReject = reject;
220
+ currentReject = reject;
184
221
  setTimeout(() => {
185
222
  if (adsLoadedReject) {
186
223
  adsLoadedReject(new Error("Ad request timeout"));
@@ -202,7 +239,7 @@ function createImaController(video, options) {
202
239
  container.style.top = "0";
203
240
  container.style.right = "0";
204
241
  container.style.bottom = "0";
205
- container.style.display = "flex";
242
+ container.style.display = "none";
206
243
  container.style.alignItems = "center";
207
244
  container.style.justifyContent = "center";
208
245
  container.style.pointerEvents = "none";
@@ -242,14 +279,14 @@ function createImaController(video, options) {
242
279
  AdErrorEvent.AD_ERROR,
243
280
  (errorEvent) => {
244
281
  console.error("[IMA] Ad error:", errorEvent.getError());
245
- try {
246
- adsManager?.destroy?.();
247
- } catch {
248
- }
282
+ destroyAdsManager();
249
283
  adPlaying = false;
250
284
  video.muted = originalMutedState;
251
- if (adContainerEl)
285
+ if (adContainerEl) {
252
286
  adContainerEl.style.pointerEvents = "none";
287
+ adContainerEl.style.display = "none";
288
+ console.log("[IMA] Ad container hidden after error");
289
+ }
253
290
  if (adsLoadedReject) {
254
291
  adsLoadedReject(new Error("Ad playback error"));
255
292
  adsLoadedReject = void 0;
@@ -272,8 +309,11 @@ function createImaController(video, options) {
272
309
  );
273
310
  emit("ad_error");
274
311
  if (!options?.continueLiveStreamDuringAds) {
275
- video.play()?.catch(() => {
276
- });
312
+ if (video.paused) {
313
+ console.log("[IMA] Resuming paused video after ad error");
314
+ video.play()?.catch(() => {
315
+ });
316
+ }
277
317
  }
278
318
  }
279
319
  }
@@ -282,10 +322,6 @@ function createImaController(video, options) {
282
322
  AdEvent.CONTENT_PAUSE_REQUESTED,
283
323
  () => {
284
324
  console.log("[IMA] Content pause requested");
285
- if (!adPlaying) {
286
- originalMutedState = video.muted;
287
- }
288
- video.muted = true;
289
325
  if (!options?.continueLiveStreamDuringAds) {
290
326
  video.pause();
291
327
  console.log("[IMA] Video paused (VOD mode)");
@@ -294,20 +330,34 @@ function createImaController(video, options) {
294
330
  "[IMA] Video continues playing but muted (Live mode)"
295
331
  );
296
332
  }
333
+ video.muted = true;
297
334
  adPlaying = true;
298
- if (adContainerEl)
299
- adContainerEl.style.pointerEvents = "auto";
300
335
  emit("content_pause");
301
336
  }
302
337
  );
338
+ adsManager.addEventListener(AdEvent.STARTED, () => {
339
+ console.log("[IMA] Ad started playing");
340
+ if (adContainerEl) {
341
+ adContainerEl.style.pointerEvents = "auto";
342
+ adContainerEl.style.display = "flex";
343
+ console.log(
344
+ "[IMA] Ad container visibility set to flex with pointer events enabled"
345
+ );
346
+ }
347
+ });
303
348
  adsManager.addEventListener(
304
349
  AdEvent.CONTENT_RESUME_REQUESTED,
305
350
  () => {
306
351
  console.log("[IMA] Content resume requested");
307
352
  adPlaying = false;
308
353
  video.muted = originalMutedState;
309
- if (adContainerEl)
354
+ if (adContainerEl) {
310
355
  adContainerEl.style.pointerEvents = "none";
356
+ adContainerEl.style.display = "none";
357
+ console.log(
358
+ "[IMA] Ad container hidden - pointer events disabled"
359
+ );
360
+ }
311
361
  if (!options?.continueLiveStreamDuringAds) {
312
362
  video.play()?.catch(() => {
313
363
  });
@@ -324,7 +374,13 @@ function createImaController(video, options) {
324
374
  console.log("[IMA] All ads completed");
325
375
  adPlaying = false;
326
376
  video.muted = originalMutedState;
327
- if (adContainerEl) adContainerEl.style.pointerEvents = "none";
377
+ if (adContainerEl) {
378
+ adContainerEl.style.pointerEvents = "none";
379
+ adContainerEl.style.display = "none";
380
+ console.log(
381
+ "[IMA] Ad container hidden after all ads completed"
382
+ );
383
+ }
328
384
  if (!options?.continueLiveStreamDuringAds) {
329
385
  video.play().catch(() => {
330
386
  });
@@ -348,10 +404,17 @@ function createImaController(video, options) {
348
404
  console.error("[IMA] Error setting up ads manager:", e);
349
405
  adPlaying = false;
350
406
  video.muted = originalMutedState;
351
- if (adContainerEl) adContainerEl.style.pointerEvents = "none";
407
+ if (adContainerEl) {
408
+ adContainerEl.style.pointerEvents = "none";
409
+ adContainerEl.style.display = "none";
410
+ console.log("[IMA] Ad container hidden after setup error");
411
+ }
352
412
  if (!options?.continueLiveStreamDuringAds) {
353
- video.play().catch(() => {
354
- });
413
+ if (video.paused) {
414
+ console.log("[IMA] Resuming paused video after setup error");
415
+ video.play().catch(() => {
416
+ });
417
+ }
355
418
  }
356
419
  if (adsLoadedReject) {
357
420
  adsLoadedReject(new Error("Failed to setup ads manager"));
@@ -367,6 +430,20 @@ function createImaController(video, options) {
367
430
  google.ima.AdErrorEvent.Type.AD_ERROR,
368
431
  (adErrorEvent) => {
369
432
  console.error("[IMA] Ads loader error:", adErrorEvent.getError());
433
+ adPlaying = false;
434
+ video.muted = originalMutedState;
435
+ if (adContainerEl) {
436
+ adContainerEl.style.pointerEvents = "none";
437
+ adContainerEl.style.display = "none";
438
+ console.log("[IMA] Ad container hidden after loader error");
439
+ }
440
+ if (!options?.continueLiveStreamDuringAds) {
441
+ if (video.paused) {
442
+ console.log("[IMA] Resuming paused video after loader error");
443
+ video.play().catch(() => {
444
+ });
445
+ }
446
+ }
370
447
  if (adsLoadedReject) {
371
448
  adsLoadedReject(new Error("Ads loader error"));
372
449
  adsLoadedReject = void 0;
@@ -382,11 +459,9 @@ function createImaController(video, options) {
382
459
  return adsLoadedPromise;
383
460
  } catch (error) {
384
461
  console.error("[IMA] Failed to request ads:", error);
385
- if (adsLoadedReject) {
386
- adsLoadedReject(error);
387
- adsLoadedReject = void 0;
388
- adsLoadedResolve = void 0;
389
- }
462
+ currentReject?.(error);
463
+ adsLoadedReject = void 0;
464
+ adsLoadedResolve = void 0;
390
465
  return Promise.reject(error);
391
466
  }
392
467
  },
@@ -423,10 +498,16 @@ function createImaController(video, options) {
423
498
  async stop() {
424
499
  adPlaying = false;
425
500
  video.muted = originalMutedState;
501
+ if (adContainerEl) {
502
+ adContainerEl.style.pointerEvents = "none";
503
+ adContainerEl.style.display = "none";
504
+ console.log("[IMA] Ad container hidden after stop");
505
+ }
426
506
  try {
427
507
  adsManager?.stop?.();
428
508
  } catch {
429
509
  }
510
+ destroyAdsManager();
430
511
  if (!options?.continueLiveStreamDuringAds) {
431
512
  video.play().catch(() => {
432
513
  });
@@ -436,12 +517,13 @@ function createImaController(video, options) {
436
517
  }
437
518
  },
438
519
  destroy() {
439
- try {
440
- adsManager?.destroy?.();
441
- } catch {
442
- }
520
+ destroyAdsManager();
443
521
  adPlaying = false;
444
522
  video.muted = originalMutedState;
523
+ if (adContainerEl) {
524
+ adContainerEl.style.pointerEvents = "none";
525
+ adContainerEl.style.display = "none";
526
+ }
445
527
  try {
446
528
  adsLoader?.destroy?.();
447
529
  } catch {
@@ -449,6 +531,9 @@ function createImaController(video, options) {
449
531
  if (adContainerEl?.parentElement) {
450
532
  adContainerEl.parentElement.removeChild(adContainerEl);
451
533
  }
534
+ adContainerEl = void 0;
535
+ adDisplayContainer = void 0;
536
+ adsLoader = void 0;
452
537
  },
453
538
  isAdPlaying() {
454
539
  return adPlaying;
@@ -926,32 +1011,33 @@ var StormcloudVideoPlayer = class {
926
1011
  this.video.muted = !!this.config.muted;
927
1012
  this.ima.initialize();
928
1013
  this.ima.on("all_ads_completed", () => {
929
- if (!this.inAdBreak) return;
930
- const remaining = this.getRemainingAdMs();
931
- if (remaining > 500 && this.adPodQueue.length > 0) {
932
- const next = this.adPodQueue.shift();
933
- this.currentAdIndex++;
934
- this.playSingleAd(next).catch(() => {
935
- });
936
- } else {
937
- this.currentAdIndex = 0;
938
- this.totalAdsInBreak = 0;
939
- this.showAds = false;
1014
+ if (this.config.debugAdTiming) {
1015
+ console.log("[StormcloudVideoPlayer] IMA all_ads_completed event received");
940
1016
  }
941
1017
  });
942
1018
  this.ima.on("ad_error", () => {
943
1019
  if (this.config.debugAdTiming) {
944
1020
  console.log("[StormcloudVideoPlayer] IMA ad_error event received");
945
1021
  }
946
- if (!this.inAdBreak) return;
947
- const remaining = this.getRemainingAdMs();
948
- if (remaining > 500 && this.adPodQueue.length > 0) {
949
- const next = this.adPodQueue.shift();
950
- this.currentAdIndex++;
951
- this.playSingleAd(next).catch(() => {
952
- });
953
- } else {
954
- this.handleAdFailure();
1022
+ if (this.showAds) {
1023
+ if (this.inAdBreak) {
1024
+ const remaining = this.getRemainingAdMs();
1025
+ if (remaining > 500 && this.adPodQueue.length > 0) {
1026
+ const next = this.adPodQueue.shift();
1027
+ this.currentAdIndex++;
1028
+ this.playSingleAd(next).catch(() => {
1029
+ });
1030
+ } else {
1031
+ this.handleAdFailure();
1032
+ }
1033
+ } else {
1034
+ if (this.config.debugAdTiming) {
1035
+ console.log(
1036
+ "[StormcloudVideoPlayer] Ad error before ad break established - cleaning up"
1037
+ );
1038
+ }
1039
+ this.handleAdFailure();
1040
+ }
955
1041
  }
956
1042
  });
957
1043
  this.ima.on("content_pause", () => {
@@ -967,6 +1053,26 @@ var StormcloudVideoPlayer = class {
967
1053
  );
968
1054
  }
969
1055
  this.clearAdFailsafeTimer();
1056
+ if (!this.inAdBreak) return;
1057
+ const remaining = this.getRemainingAdMs();
1058
+ if (remaining > 500 && this.adPodQueue.length > 0) {
1059
+ const next = this.adPodQueue.shift();
1060
+ this.currentAdIndex++;
1061
+ if (this.config.debugAdTiming) {
1062
+ console.log(
1063
+ `[StormcloudVideoPlayer] Playing next ad in pod (${this.currentAdIndex}/${this.totalAdsInBreak})`
1064
+ );
1065
+ }
1066
+ this.playSingleAd(next).catch(() => {
1067
+ });
1068
+ } else {
1069
+ if (this.config.debugAdTiming) {
1070
+ console.log("[StormcloudVideoPlayer] Ad pod completed");
1071
+ }
1072
+ this.currentAdIndex = 0;
1073
+ this.totalAdsInBreak = 0;
1074
+ this.showAds = false;
1075
+ }
970
1076
  });
971
1077
  this.video.addEventListener("timeupdate", () => {
972
1078
  this.onTimeUpdate(this.video.currentTime);
@@ -1512,9 +1618,20 @@ var StormcloudVideoPlayer = class {
1512
1618
  return;
1513
1619
  }
1514
1620
  if (vastTagUrl) {
1621
+ this.inAdBreak = true;
1515
1622
  this.showAds = true;
1516
1623
  this.currentAdIndex++;
1517
- await this.playSingleAd(vastTagUrl);
1624
+ try {
1625
+ await this.playSingleAd(vastTagUrl);
1626
+ } catch (error) {
1627
+ if (this.config.debugAdTiming) {
1628
+ console.error(
1629
+ "[StormcloudVideoPlayer] Ad playback failed in handleAdStart:",
1630
+ error
1631
+ );
1632
+ }
1633
+ this.handleAdFailure();
1634
+ }
1518
1635
  }
1519
1636
  if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1520
1637
  this.expectedAdBreakDurationMs = scheduled.durationMs;
@@ -1616,21 +1733,21 @@ var StormcloudVideoPlayer = class {
1616
1733
  if (this.config.debugAdTiming) {
1617
1734
  console.log("[StormcloudVideoPlayer] Attempting to play ad:", vastTagUrl);
1618
1735
  }
1619
- this.ima.updateOriginalMutedState(this.video.muted);
1620
- if (!this.shouldContinueLiveStreamDuringAds()) {
1621
- if (this.config.debugAdTiming) {
1622
- console.log("[StormcloudVideoPlayer] Pausing video immediately for ad (VOD mode)");
1623
- }
1624
- this.video.pause();
1625
- } else {
1736
+ if (this.ima.isAdPlaying()) {
1626
1737
  if (this.config.debugAdTiming) {
1627
- console.log("[StormcloudVideoPlayer] Muting video for ad (Live mode)");
1738
+ console.warn(
1739
+ "[StormcloudVideoPlayer] Ad already playing - skipping new ad request"
1740
+ );
1628
1741
  }
1629
- this.video.muted = true;
1742
+ return;
1630
1743
  }
1744
+ this.ima.updateOriginalMutedState(this.video.muted);
1631
1745
  this.startAdFailsafeTimer();
1632
1746
  try {
1633
1747
  await this.ima.requestAds(vastTagUrl);
1748
+ if (this.config.debugAdTiming) {
1749
+ console.log("[StormcloudVideoPlayer] Ad request successful, starting playback");
1750
+ }
1634
1751
  await this.ima.play();
1635
1752
  if (this.config.debugAdTiming) {
1636
1753
  console.log("[StormcloudVideoPlayer] Ad playback started successfully");
@@ -1645,7 +1762,13 @@ var StormcloudVideoPlayer = class {
1645
1762
  handleAdFailure() {
1646
1763
  if (this.config.debugAdTiming) {
1647
1764
  console.log(
1648
- "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1765
+ "[StormcloudVideoPlayer] Handling ad failure - resuming content",
1766
+ {
1767
+ inAdBreak: this.inAdBreak,
1768
+ showAds: this.showAds,
1769
+ videoPaused: this.video.paused,
1770
+ adPlaying: this.ima.isAdPlaying()
1771
+ }
1649
1772
  );
1650
1773
  }
1651
1774
  this.inAdBreak = false;
@@ -1658,14 +1781,29 @@ var StormcloudVideoPlayer = class {
1658
1781
  this.showAds = false;
1659
1782
  this.currentAdIndex = 0;
1660
1783
  this.totalAdsInBreak = 0;
1784
+ const originalMutedState = this.ima.getOriginalMutedState();
1785
+ this.video.muted = originalMutedState;
1786
+ if (this.config.debugAdTiming) {
1787
+ console.log(
1788
+ `[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
1789
+ );
1790
+ }
1661
1791
  if (this.video.paused) {
1662
- this.video.play()?.catch(() => {
1792
+ if (this.config.debugAdTiming) {
1793
+ console.log("[StormcloudVideoPlayer] Resuming paused video");
1794
+ }
1795
+ this.video.play()?.catch((error) => {
1663
1796
  if (this.config.debugAdTiming) {
1664
1797
  console.error(
1665
- "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1798
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure:",
1799
+ error
1666
1800
  );
1667
1801
  }
1668
1802
  });
1803
+ } else {
1804
+ if (this.config.debugAdTiming) {
1805
+ console.log("[StormcloudVideoPlayer] Video is already playing, no resume needed");
1806
+ }
1669
1807
  }
1670
1808
  }
1671
1809
  startAdFailsafeTimer() {
@@ -1677,10 +1815,12 @@ var StormcloudVideoPlayer = class {
1677
1815
  );
1678
1816
  }
1679
1817
  this.adFailsafeTimerId = window.setTimeout(() => {
1680
- if (this.video.paused) {
1818
+ const shouldTrigger = this.video.paused || this.showAds && !this.ima.isAdPlaying();
1819
+ if (shouldTrigger) {
1681
1820
  if (this.config.debugAdTiming) {
1682
1821
  console.warn(
1683
- "[StormcloudVideoPlayer] Failsafe timer triggered - forcing video resume"
1822
+ "[StormcloudVideoPlayer] Failsafe timer triggered - forcing video resume",
1823
+ { paused: this.video.paused, showAds: this.showAds, adPlaying: this.ima.isAdPlaying() }
1684
1824
  );
1685
1825
  }
1686
1826
  this.handleAdFailure();