stormcloud-video-player 0.2.16 → 0.2.18

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/lib/index.cjs CHANGED
@@ -267,6 +267,13 @@ function createImaController(video, options) {
267
267
  let adPlaying = false;
268
268
  let originalMutedState = false;
269
269
  const listeners = /* @__PURE__ */ new Map();
270
+ function setAdPlayingFlag(isPlaying) {
271
+ if (isPlaying) {
272
+ video.dataset.stormcloudAdPlaying = "true";
273
+ } else {
274
+ delete video.dataset.stormcloudAdPlaying;
275
+ }
276
+ }
270
277
  function emit(event, payload) {
271
278
  const set = listeners.get(event);
272
279
  if (!set) return;
@@ -351,6 +358,14 @@ function createImaController(video, options) {
351
358
  function makeAdsRequest(google, vastTagUrl) {
352
359
  const adsRequest = new google.ima.AdsRequest();
353
360
  adsRequest.adTagUrl = vastTagUrl;
361
+ const videoWidth = video.offsetWidth || video.clientWidth || 640;
362
+ const videoHeight = video.offsetHeight || video.clientHeight || 360;
363
+ adsRequest.linearAdSlotWidth = videoWidth;
364
+ adsRequest.linearAdSlotHeight = videoHeight;
365
+ adsRequest.nonLinearAdSlotWidth = videoWidth;
366
+ adsRequest.nonLinearAdSlotHeight = videoHeight;
367
+ adsRequest.vastLoadTimeout = 5e3;
368
+ console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
354
369
  adsLoader.requestAds(adsRequest);
355
370
  }
356
371
  function destroyAdsManager() {
@@ -397,6 +412,18 @@ function createImaController(video, options) {
397
412
  },
398
413
  async requestAds(vastTagUrl) {
399
414
  console.log("[IMA] Requesting ads:", vastTagUrl);
415
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
416
+ const error = new Error("VAST tag URL is empty or undefined");
417
+ console.warn("[IMA]", error.message);
418
+ return Promise.reject(error);
419
+ }
420
+ try {
421
+ new URL(vastTagUrl);
422
+ } catch (e) {
423
+ const error = new Error(`Invalid VAST tag URL format: ${vastTagUrl}`);
424
+ console.warn("[IMA]", error.message);
425
+ return Promise.reject(error);
426
+ }
400
427
  if (adPlaying) {
401
428
  console.warn(
402
429
  "[IMA] Cannot request new ads while an ad is playing. Call stop() first."
@@ -458,6 +485,18 @@ function createImaController(video, options) {
458
485
  );
459
486
  }
460
487
  }
488
+ const videoWidth = video.offsetWidth || video.clientWidth;
489
+ const videoHeight = video.offsetHeight || video.clientHeight;
490
+ if (!videoWidth || !videoHeight || videoWidth === 0 || videoHeight === 0) {
491
+ const error = new Error(
492
+ `Invalid video dimensions: ${videoWidth}x${videoHeight}. Cannot initialize ads.`
493
+ );
494
+ console.warn("[IMA]", error.message);
495
+ currentReject == null ? void 0 : currentReject(error);
496
+ adsLoadedReject = void 0;
497
+ adsLoadedResolve = void 0;
498
+ return Promise.reject(error);
499
+ }
461
500
  if (!adsLoader) {
462
501
  console.log("[IMA] Creating ads loader");
463
502
  const adsLoaderCls = new google.ima.AdsLoader(adDisplayContainer);
@@ -477,7 +516,12 @@ function createImaController(video, options) {
477
516
  console.error("[IMA] Ad error:", errorEvent.getError());
478
517
  destroyAdsManager();
479
518
  adPlaying = false;
519
+ const previousMutedState = video.muted;
480
520
  video.muted = originalMutedState;
521
+ setAdPlayingFlag(false);
522
+ console.log(
523
+ `[IMA] Restored mute state after ad error: ${previousMutedState} -> ${originalMutedState}`
524
+ );
481
525
  if (adContainerEl) {
482
526
  adContainerEl.style.pointerEvents = "none";
483
527
  adContainerEl.style.display = "none";
@@ -506,7 +550,9 @@ function createImaController(video, options) {
506
550
  emit("ad_error");
507
551
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
508
552
  if (video.paused) {
509
- console.log("[IMA] Resuming paused video after ad error");
553
+ console.log(
554
+ "[IMA] Resuming paused video after ad error"
555
+ );
510
556
  (_a = video.play()) == null ? void 0 : _a.catch(() => {
511
557
  });
512
558
  }
@@ -528,11 +574,13 @@ function createImaController(video, options) {
528
574
  }
529
575
  video.muted = true;
530
576
  adPlaying = true;
577
+ setAdPlayingFlag(true);
531
578
  emit("content_pause");
532
579
  }
533
580
  );
534
581
  adsManager.addEventListener(AdEvent.STARTED, () => {
535
582
  console.log("[IMA] Ad started playing");
583
+ setAdPlayingFlag(true);
536
584
  if (adContainerEl) {
537
585
  adContainerEl.style.pointerEvents = "auto";
538
586
  adContainerEl.style.display = "flex";
@@ -548,6 +596,7 @@ function createImaController(video, options) {
548
596
  console.log("[IMA] Content resume requested");
549
597
  adPlaying = false;
550
598
  video.muted = originalMutedState;
599
+ setAdPlayingFlag(false);
551
600
  if (adContainerEl) {
552
601
  adContainerEl.style.pointerEvents = "none";
553
602
  adContainerEl.style.display = "none";
@@ -571,6 +620,7 @@ function createImaController(video, options) {
571
620
  console.log("[IMA] All ads completed");
572
621
  adPlaying = false;
573
622
  video.muted = originalMutedState;
623
+ setAdPlayingFlag(false);
574
624
  if (adContainerEl) {
575
625
  adContainerEl.style.pointerEvents = "none";
576
626
  adContainerEl.style.display = "none";
@@ -601,6 +651,7 @@ function createImaController(video, options) {
601
651
  console.error("[IMA] Error setting up ads manager:", e);
602
652
  adPlaying = false;
603
653
  video.muted = originalMutedState;
654
+ setAdPlayingFlag(false);
604
655
  if (adContainerEl) {
605
656
  adContainerEl.style.pointerEvents = "none";
606
657
  adContainerEl.style.display = "none";
@@ -608,7 +659,9 @@ function createImaController(video, options) {
608
659
  }
609
660
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
610
661
  if (video.paused) {
611
- console.log("[IMA] Resuming paused video after setup error");
662
+ console.log(
663
+ "[IMA] Resuming paused video after setup error"
664
+ );
612
665
  video.play().catch(() => {
613
666
  });
614
667
  }
@@ -628,7 +681,12 @@ function createImaController(video, options) {
628
681
  (adErrorEvent) => {
629
682
  console.error("[IMA] Ads loader error:", adErrorEvent.getError());
630
683
  adPlaying = false;
684
+ const previousMutedState = video.muted;
631
685
  video.muted = originalMutedState;
686
+ setAdPlayingFlag(false);
687
+ console.log(
688
+ `[IMA] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
689
+ );
632
690
  if (adContainerEl) {
633
691
  adContainerEl.style.pointerEvents = "none";
634
692
  adContainerEl.style.display = "none";
@@ -680,12 +738,20 @@ function createImaController(video, options) {
680
738
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
681
739
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
682
740
  adPlaying = true;
741
+ const adVolume = originalMutedState ? 0 : video.volume;
742
+ try {
743
+ adsManager.setVolume(adVolume);
744
+ console.log(`[IMA] Set ad volume to ${adVolume}`);
745
+ } catch (error) {
746
+ console.warn("[IMA] Failed to set ad volume:", error);
747
+ }
683
748
  console.log("[IMA] Starting ad playback");
684
749
  adsManager.start();
685
750
  return Promise.resolve();
686
751
  } catch (error) {
687
752
  console.error("[IMA] Error starting ad playback:", error);
688
753
  adPlaying = false;
754
+ setAdPlayingFlag(false);
689
755
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
690
756
  (_b = video.play()) == null ? void 0 : _b.catch(() => {
691
757
  });
@@ -697,6 +763,7 @@ function createImaController(video, options) {
697
763
  var _a;
698
764
  adPlaying = false;
699
765
  video.muted = originalMutedState;
766
+ setAdPlayingFlag(false);
700
767
  if (adContainerEl) {
701
768
  adContainerEl.style.pointerEvents = "none";
702
769
  adContainerEl.style.display = "none";
@@ -720,6 +787,7 @@ function createImaController(video, options) {
720
787
  destroyAdsManager();
721
788
  adPlaying = false;
722
789
  video.muted = originalMutedState;
790
+ setAdPlayingFlag(false);
723
791
  if (adContainerEl) {
724
792
  adContainerEl.style.pointerEvents = "none";
725
793
  adContainerEl.style.display = "none";
@@ -762,6 +830,9 @@ function createImaController(video, options) {
762
830
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
763
831
  },
764
832
  updateOriginalMutedState(muted) {
833
+ console.log(
834
+ `[IMA] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
835
+ );
765
836
  originalMutedState = muted;
766
837
  },
767
838
  getOriginalMutedState() {
@@ -818,7 +889,10 @@ function createHlsAdPlayer(contentVideo, options) {
818
889
  try {
819
890
  fn(payload);
820
891
  } catch (error) {
821
- console.warn(`[HlsAdPlayer] Error in event listener for ${event}:`, error);
892
+ console.warn(
893
+ `[HlsAdPlayer] Error in event listener for ${event}:`,
894
+ error
895
+ );
822
896
  }
823
897
  }
824
898
  }
@@ -881,7 +955,9 @@ function createHlsAdPlayer(contentVideo, options) {
881
955
  }
882
956
  const mainQuality = getMainStreamQuality();
883
957
  if (!mainQuality) {
884
- console.log("[HlsAdPlayer] No main stream quality info, using first media file");
958
+ console.log(
959
+ "[HlsAdPlayer] No main stream quality info, using first media file"
960
+ );
885
961
  return firstFile;
886
962
  }
887
963
  console.log("[HlsAdPlayer] Main stream quality:", mainQuality);
@@ -917,7 +993,10 @@ function createHlsAdPlayer(contentVideo, options) {
917
993
  const xmlDoc = parser.parseFromString(xmlString, "text/xml");
918
994
  const parserError = xmlDoc.querySelector("parsererror");
919
995
  if (parserError) {
920
- console.error("[HlsAdPlayer] XML parsing error (malformed VAST XML):", parserError.textContent);
996
+ console.error(
997
+ "[HlsAdPlayer] XML parsing error (malformed VAST XML):",
998
+ parserError.textContent
999
+ );
921
1000
  return null;
922
1001
  }
923
1002
  const adElement = xmlDoc.querySelector("Ad");
@@ -933,17 +1012,23 @@ function createHlsAdPlayer(contentVideo, options) {
933
1012
  const duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
934
1013
  const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
935
1014
  const mediaFiles = [];
936
- console.log(`[HlsAdPlayer] Found ${mediaFileElements.length} MediaFile element(s) in VAST XML`);
1015
+ console.log(
1016
+ `[HlsAdPlayer] Found ${mediaFileElements.length} MediaFile element(s) in VAST XML`
1017
+ );
937
1018
  mediaFileElements.forEach((mf, index) => {
938
1019
  var _a2;
939
1020
  const type = mf.getAttribute("type") || "";
940
1021
  const url = ((_a2 = mf.textContent) == null ? void 0 : _a2.trim()) || "";
941
1022
  const width = mf.getAttribute("width") || "";
942
1023
  const height = mf.getAttribute("height") || "";
943
- console.log(`[HlsAdPlayer] MediaFile ${index}: type="${type}", url="${url}", width="${width}", height="${height}"`);
1024
+ console.log(
1025
+ `[HlsAdPlayer] MediaFile ${index}: type="${type}", url="${url}", width="${width}", height="${height}"`
1026
+ );
944
1027
  if (type === "application/x-mpegURL" || type.includes("m3u8")) {
945
1028
  if (!url) {
946
- console.warn(`[HlsAdPlayer] MediaFile ${index} has HLS type but empty URL`);
1029
+ console.warn(
1030
+ `[HlsAdPlayer] MediaFile ${index} has HLS type but empty URL`
1031
+ );
947
1032
  return;
948
1033
  }
949
1034
  const bitrateAttr = mf.getAttribute("bitrate");
@@ -957,12 +1042,16 @@ function createHlsAdPlayer(contentVideo, options) {
957
1042
  });
958
1043
  console.log(`[HlsAdPlayer] Added HLS MediaFile: ${url}`);
959
1044
  } else {
960
- console.log(`[HlsAdPlayer] MediaFile ${index} ignored (type="${type}" is not HLS)`);
1045
+ console.log(
1046
+ `[HlsAdPlayer] MediaFile ${index} ignored (type="${type}" is not HLS)`
1047
+ );
961
1048
  }
962
1049
  });
963
1050
  if (mediaFiles.length === 0) {
964
1051
  if (isNoAdAvailable) {
965
- console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
1052
+ console.warn(
1053
+ "[HlsAdPlayer] No ads available (VAST response indicates no ads)"
1054
+ );
966
1055
  } else {
967
1056
  console.warn("[HlsAdPlayer] No HLS media files found in VAST XML");
968
1057
  }
@@ -1025,6 +1114,10 @@ function createHlsAdPlayer(contentVideo, options) {
1025
1114
  video.style.backgroundColor = "#000";
1026
1115
  video.playsInline = true;
1027
1116
  video.muted = false;
1117
+ video.volume = 1;
1118
+ console.log(
1119
+ `[HlsAdPlayer] Created ad video element with volume ${video.volume}`
1120
+ );
1028
1121
  return video;
1029
1122
  }
1030
1123
  function setupAdEventListeners() {
@@ -1084,10 +1177,22 @@ function createHlsAdPlayer(contentVideo, options) {
1084
1177
  }
1085
1178
  });
1086
1179
  }
1180
+ function setAdPlayingFlag(isPlaying) {
1181
+ if (isPlaying) {
1182
+ contentVideo.dataset.stormcloudAdPlaying = "true";
1183
+ } else {
1184
+ delete contentVideo.dataset.stormcloudAdPlaying;
1185
+ }
1186
+ }
1087
1187
  function handleAdComplete() {
1088
1188
  console.log("[HlsAdPlayer] Handling ad completion");
1089
1189
  adPlaying = false;
1190
+ setAdPlayingFlag(false);
1191
+ const previousMutedState = contentVideo.muted;
1090
1192
  contentVideo.muted = originalMutedState;
1193
+ console.log(
1194
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1195
+ );
1091
1196
  if (adContainerEl) {
1092
1197
  adContainerEl.style.display = "none";
1093
1198
  adContainerEl.style.pointerEvents = "none";
@@ -1105,7 +1210,12 @@ function createHlsAdPlayer(contentVideo, options) {
1105
1210
  function handleAdError() {
1106
1211
  console.log("[HlsAdPlayer] Handling ad error");
1107
1212
  adPlaying = false;
1213
+ setAdPlayingFlag(false);
1214
+ const previousMutedState = contentVideo.muted;
1108
1215
  contentVideo.muted = originalMutedState;
1216
+ console.log(
1217
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1218
+ );
1109
1219
  if (adContainerEl) {
1110
1220
  adContainerEl.style.display = "none";
1111
1221
  adContainerEl.style.pointerEvents = "none";
@@ -1142,7 +1252,9 @@ function createHlsAdPlayer(contentVideo, options) {
1142
1252
  async requestAds(vastTagUrl) {
1143
1253
  console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
1144
1254
  if (adPlaying) {
1145
- console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
1255
+ console.warn(
1256
+ "[HlsAdPlayer] Cannot request new ads while an ad is playing"
1257
+ );
1146
1258
  return Promise.reject(new Error("Ad already playing"));
1147
1259
  }
1148
1260
  try {
@@ -1153,14 +1265,20 @@ function createHlsAdPlayer(contentVideo, options) {
1153
1265
  }
1154
1266
  const vastXml = await response.text();
1155
1267
  console.log("[HlsAdPlayer] VAST XML received");
1156
- console.log("[HlsAdPlayer] VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
1268
+ console.log(
1269
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1270
+ vastXml.substring(0, 2e3)
1271
+ );
1157
1272
  const ad = parseVastXml(vastXml);
1158
1273
  if (!ad) {
1159
1274
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1275
+ emit("ad_error");
1160
1276
  return Promise.resolve();
1161
1277
  }
1162
1278
  currentAd = ad;
1163
- console.log(`[HlsAdPlayer] Ad parsed: ${ad.title}, duration: ${ad.duration}s`);
1279
+ console.log(
1280
+ `[HlsAdPlayer] Ad parsed: ${ad.title}, duration: ${ad.duration}s`
1281
+ );
1164
1282
  fireTrackingPixels(ad.trackingUrls.impression);
1165
1283
  trackingFired.impression = true;
1166
1284
  return Promise.resolve();
@@ -1172,7 +1290,9 @@ function createHlsAdPlayer(contentVideo, options) {
1172
1290
  },
1173
1291
  async play() {
1174
1292
  if (!currentAd) {
1175
- console.warn("[HlsAdPlayer] Cannot play: No ad loaded (no ads available)");
1293
+ console.warn(
1294
+ "[HlsAdPlayer] Cannot play: No ad loaded (no ads available)"
1295
+ );
1176
1296
  return Promise.reject(new Error("No ad loaded"));
1177
1297
  }
1178
1298
  console.log("[HlsAdPlayer] Starting ad playback");
@@ -1190,6 +1310,7 @@ function createHlsAdPlayer(contentVideo, options) {
1190
1310
  thirdQuartile: false,
1191
1311
  complete: false
1192
1312
  };
1313
+ const contentVolume = contentVideo.volume;
1193
1314
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
1194
1315
  contentVideo.pause();
1195
1316
  console.log("[HlsAdPlayer] Content paused (VOD mode)");
@@ -1198,6 +1319,15 @@ function createHlsAdPlayer(contentVideo, options) {
1198
1319
  }
1199
1320
  contentVideo.muted = true;
1200
1321
  adPlaying = true;
1322
+ setAdPlayingFlag(true);
1323
+ if (adVideoElement) {
1324
+ const adVolume = originalMutedState ? 0 : contentVolume;
1325
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1326
+ adVideoElement.muted = false;
1327
+ console.log(
1328
+ `[HlsAdPlayer] Set ad video volume to ${adVideoElement.volume}, muted: ${adVideoElement.muted}, originalMutedState: ${originalMutedState}, contentVolume: ${contentVolume}`
1329
+ );
1330
+ }
1201
1331
  if (adContainerEl) {
1202
1332
  adContainerEl.style.display = "flex";
1203
1333
  adContainerEl.style.pointerEvents = "auto";
@@ -1250,6 +1380,7 @@ function createHlsAdPlayer(contentVideo, options) {
1250
1380
  async stop() {
1251
1381
  console.log("[HlsAdPlayer] Stopping ad");
1252
1382
  adPlaying = false;
1383
+ setAdPlayingFlag(false);
1253
1384
  contentVideo.muted = originalMutedState;
1254
1385
  if (adContainerEl) {
1255
1386
  adContainerEl.style.display = "none";
@@ -1272,6 +1403,7 @@ function createHlsAdPlayer(contentVideo, options) {
1272
1403
  destroy() {
1273
1404
  console.log("[HlsAdPlayer] Destroying");
1274
1405
  adPlaying = false;
1406
+ setAdPlayingFlag(false);
1275
1407
  contentVideo.muted = originalMutedState;
1276
1408
  if (adHls) {
1277
1409
  adHls.destroy();
@@ -1313,6 +1445,9 @@ function createHlsAdPlayer(contentVideo, options) {
1313
1445
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
1314
1446
  },
1315
1447
  updateOriginalMutedState(muted) {
1448
+ console.log(
1449
+ `[HlsAdPlayer] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
1450
+ );
1316
1451
  originalMutedState = muted;
1317
1452
  },
1318
1453
  getOriginalMutedState() {
@@ -1805,6 +1940,9 @@ var StormcloudVideoPlayer = class {
1805
1940
  this.isLiveStream = false;
1806
1941
  this.nativeHlsMode = false;
1807
1942
  this.videoSrcProtection = null;
1943
+ this.bufferedSegmentsCount = 0;
1944
+ this.shouldAutoplayAfterBuffering = false;
1945
+ this.hasInitialBufferCompleted = false;
1808
1946
  initializePolyfills();
1809
1947
  const browserOverrides = getBrowserConfigOverrides();
1810
1948
  this.config = { ...config, ...browserOverrides };
@@ -1891,14 +2029,22 @@ var StormcloudVideoPlayer = class {
1891
2029
  liveDurationInfinity: true,
1892
2030
  lowLatencyMode: !!this.config.lowLatencyMode,
1893
2031
  maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
1894
- ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
2032
+ ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {},
2033
+ maxBufferLength: 30,
2034
+ maxMaxBufferLength: 600,
2035
+ maxBufferSize: 60 * 1e3 * 1e3,
2036
+ maxBufferHole: 0.5,
2037
+ highBufferWatchdogPeriod: 2,
2038
+ nudgeOffset: 0.1,
2039
+ nudgeMaxRetry: 3,
2040
+ startPosition: -1
1895
2041
  });
1896
2042
  this.hls.on(import_hls2.default.Events.MEDIA_ATTACHED, () => {
1897
2043
  var _a2;
1898
2044
  (_a2 = this.hls) == null ? void 0 : _a2.loadSource(this.config.src);
1899
2045
  });
1900
2046
  this.hls.on(import_hls2.default.Events.MANIFEST_PARSED, async (_, data) => {
1901
- var _a2, _b2, _c, _d;
2047
+ var _a2, _b2, _c, _d, _e;
1902
2048
  this.isLiveStream = (_c = (_b2 = (_a2 = this.hls) == null ? void 0 : _a2.levels) == null ? void 0 : _b2.some(
1903
2049
  (level) => {
1904
2050
  var _a3, _b3;
@@ -1916,9 +2062,51 @@ var StormcloudVideoPlayer = class {
1916
2062
  this.ima.destroy();
1917
2063
  this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
1918
2064
  this.ima.initialize();
1919
- if (this.config.autoplay) {
1920
- await ((_d = this.video.play()) == null ? void 0 : _d.catch(() => {
1921
- }));
2065
+ this.bufferedSegmentsCount = 0;
2066
+ this.hasInitialBufferCompleted = false;
2067
+ this.shouldAutoplayAfterBuffering = !!this.config.autoplay;
2068
+ const minSegments = (_d = this.config.minSegmentsBeforePlay) != null ? _d : 2;
2069
+ if (this.config.debugAdTiming) {
2070
+ console.log(
2071
+ "[StormcloudVideoPlayer] Waiting for",
2072
+ minSegments,
2073
+ "segments to buffer before playback"
2074
+ );
2075
+ }
2076
+ if (minSegments === 0 || !this.config.autoplay) {
2077
+ this.hasInitialBufferCompleted = true;
2078
+ if (this.config.autoplay) {
2079
+ await ((_e = this.video.play()) == null ? void 0 : _e.catch(() => {
2080
+ }));
2081
+ }
2082
+ }
2083
+ });
2084
+ this.hls.on(import_hls2.default.Events.FRAG_BUFFERED, async (_evt, data) => {
2085
+ var _a2, _b2;
2086
+ if (this.hasInitialBufferCompleted) {
2087
+ return;
2088
+ }
2089
+ this.bufferedSegmentsCount++;
2090
+ const minSegments = (_a2 = this.config.minSegmentsBeforePlay) != null ? _a2 : 2;
2091
+ if (this.config.debugAdTiming) {
2092
+ console.log(
2093
+ `[StormcloudVideoPlayer] Buffered segment ${this.bufferedSegmentsCount}/${minSegments}`
2094
+ );
2095
+ }
2096
+ if (this.bufferedSegmentsCount >= minSegments) {
2097
+ this.hasInitialBufferCompleted = true;
2098
+ if (this.shouldAutoplayAfterBuffering) {
2099
+ if (this.config.debugAdTiming) {
2100
+ console.log(
2101
+ `[StormcloudVideoPlayer] Initial buffer complete (${this.bufferedSegmentsCount} segments). Starting playback.`
2102
+ );
2103
+ }
2104
+ await ((_b2 = this.video.play()) == null ? void 0 : _b2.catch((err) => {
2105
+ if (this.config.debugAdTiming) {
2106
+ console.warn("[StormcloudVideoPlayer] Autoplay failed:", err);
2107
+ }
2108
+ }));
2109
+ }
1922
2110
  }
1923
2111
  });
1924
2112
  this.hls.on(import_hls2.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
@@ -2014,6 +2202,7 @@ var StormcloudVideoPlayer = class {
2014
2202
  this.video.autoplay = !!this.config.autoplay;
2015
2203
  this.video.muted = !!this.config.muted;
2016
2204
  this.ima.initialize();
2205
+ this.ima.updateOriginalMutedState(this.video.muted);
2017
2206
  this.ima.on("all_ads_completed", () => {
2018
2207
  if (this.config.debugAdTiming) {
2019
2208
  console.log(
@@ -2798,7 +2987,23 @@ var StormcloudVideoPlayer = class {
2798
2987
  }
2799
2988
  return;
2800
2989
  }
2801
- this.ima.updateOriginalMutedState(this.video.muted);
2990
+ if (!this.showAds) {
2991
+ if (this.config.debugAdTiming) {
2992
+ console.log(
2993
+ `[StormcloudVideoPlayer] Capturing original state before ad request:`,
2994
+ {
2995
+ videoMuted: this.video.muted,
2996
+ videoVolume: this.video.volume,
2997
+ showAds: this.showAds
2998
+ }
2999
+ );
3000
+ }
3001
+ this.ima.updateOriginalMutedState(this.video.muted);
3002
+ } else if (this.config.debugAdTiming) {
3003
+ console.log(
3004
+ `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
3005
+ );
3006
+ }
2802
3007
  this.startAdFailsafeTimer();
2803
3008
  try {
2804
3009
  await this.ima.requestAds(vastTagUrl);
@@ -2853,11 +3058,12 @@ var StormcloudVideoPlayer = class {
2853
3058
  this.showAds = false;
2854
3059
  this.currentAdIndex = 0;
2855
3060
  this.totalAdsInBreak = 0;
3061
+ const currentMutedState = this.video.muted;
2856
3062
  const originalMutedState = this.ima.getOriginalMutedState();
2857
3063
  this.video.muted = originalMutedState;
2858
3064
  if (this.config.debugAdTiming) {
2859
3065
  console.log(
2860
- `[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
3066
+ `[StormcloudVideoPlayer] Restored mute state: ${currentMutedState} -> ${originalMutedState}`
2861
3067
  );
2862
3068
  }
2863
3069
  if (this.video.paused) {
@@ -2949,6 +3155,7 @@ var StormcloudVideoPlayer = class {
2949
3155
  }
2950
3156
  } else {
2951
3157
  this.video.muted = !this.video.muted;
3158
+ this.ima.updateOriginalMutedState(this.video.muted);
2952
3159
  if (this.config.debugAdTiming) {
2953
3160
  console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
2954
3161
  }
@@ -2993,11 +3200,47 @@ var StormcloudVideoPlayer = class {
2993
3200
  }
2994
3201
  isMuted() {
2995
3202
  if (this.ima.isAdPlaying()) {
2996
- const adVolume = this.ima.getAdVolume();
2997
- return adVolume === 0;
3203
+ if (this.config.debugAdTiming) {
3204
+ console.log(
3205
+ "[StormcloudVideoPlayer] isMuted() override during ad playback -> false"
3206
+ );
3207
+ }
3208
+ return false;
3209
+ }
3210
+ if (this.config.debugAdTiming) {
3211
+ console.log(
3212
+ `[StormcloudVideoPlayer] isMuted() no ad playing: video.muted=${this.video.muted}`
3213
+ );
2998
3214
  }
2999
3215
  return this.video.muted;
3000
3216
  }
3217
+ setMuted(muted) {
3218
+ const adPlaying = this.ima.isAdPlaying();
3219
+ if (adPlaying && muted === this.video.muted) {
3220
+ if (this.config.debugAdTiming) {
3221
+ console.log(
3222
+ "[StormcloudVideoPlayer] setMuted reflective update during ad ignored",
3223
+ { muted }
3224
+ );
3225
+ }
3226
+ return;
3227
+ }
3228
+ this.video.muted = muted;
3229
+ if (adPlaying) {
3230
+ this.ima.updateOriginalMutedState(muted);
3231
+ this.ima.setAdVolume(muted ? 0 : 1);
3232
+ if (this.config.debugAdTiming) {
3233
+ console.log("[StormcloudVideoPlayer] setMuted applied during ad", {
3234
+ muted
3235
+ });
3236
+ }
3237
+ return;
3238
+ }
3239
+ this.ima.updateOriginalMutedState(muted);
3240
+ if (this.config.debugAdTiming) {
3241
+ console.log("[StormcloudVideoPlayer] setMuted called:", muted);
3242
+ }
3243
+ }
3001
3244
  isFullscreen() {
3002
3245
  return !!document.fullscreenElement;
3003
3246
  }
@@ -3076,10 +3319,12 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3076
3319
  vastMode,
3077
3320
  vastTagUrl,
3078
3321
  adPlayerType,
3322
+ minSegmentsBeforePlay,
3079
3323
  ...restVideoAttrs
3080
3324
  } = props;
3081
3325
  const videoRef = (0, import_react.useRef)(null);
3082
3326
  const playerRef = (0, import_react.useRef)(null);
3327
+ const bufferingTimeoutRef = (0, import_react.useRef)(null);
3083
3328
  const [adStatus, setAdStatus] = import_react.default.useState({ showAds: false, currentIndex: 0, totalAds: 0 });
3084
3329
  const [shouldShowNativeControls, setShouldShowNativeControls] = import_react.default.useState(true);
3085
3330
  const [isMuted, setIsMuted] = import_react.default.useState(false);
@@ -3201,6 +3446,9 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3201
3446
  return;
3202
3447
  }
3203
3448
  setShowLicenseWarning(false);
3449
+ if (debugAdTiming) {
3450
+ console.log("[StormcloudUI] Initializing player, isLoading=true");
3451
+ }
3204
3452
  if (playerRef.current) {
3205
3453
  try {
3206
3454
  playerRef.current.destroy();
@@ -3231,17 +3479,25 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3231
3479
  if (vastMode !== void 0) cfg.vastMode = vastMode;
3232
3480
  if (vastTagUrl !== void 0) cfg.vastTagUrl = vastTagUrl;
3233
3481
  if (adPlayerType !== void 0) cfg.adPlayerType = adPlayerType;
3482
+ if (minSegmentsBeforePlay !== void 0)
3483
+ cfg.minSegmentsBeforePlay = minSegmentsBeforePlay;
3234
3484
  const player = new StormcloudVideoPlayer(cfg);
3235
3485
  playerRef.current = player;
3236
3486
  player.load().then(() => {
3237
3487
  const showNative = player.shouldShowNativeControls();
3238
3488
  setShouldShowNativeControls(showNative);
3489
+ if (debugAdTiming) {
3490
+ console.log(
3491
+ "[StormcloudUI] Player loaded successfully, waiting for video ready"
3492
+ );
3493
+ }
3239
3494
  onReady == null ? void 0 : onReady(player);
3240
3495
  }).catch((error) => {
3241
3496
  console.error(
3242
3497
  "StormcloudVideoPlayer: Failed to load player:",
3243
3498
  error
3244
3499
  );
3500
+ setIsLoading(false);
3245
3501
  onReady == null ? void 0 : onReady(player);
3246
3502
  });
3247
3503
  return () => {
@@ -3258,8 +3514,8 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3258
3514
  if (autoplay !== void 0 && playerRef.current.videoElement) {
3259
3515
  playerRef.current.videoElement.autoplay = autoplay;
3260
3516
  }
3261
- if (muted !== void 0 && playerRef.current.videoElement) {
3262
- playerRef.current.videoElement.muted = muted;
3517
+ if (muted !== void 0) {
3518
+ playerRef.current.setMuted(muted);
3263
3519
  }
3264
3520
  } catch (error) {
3265
3521
  console.warn("Failed to update player properties:", error);
@@ -3340,26 +3596,108 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3340
3596
  (0, import_react.useEffect)(() => {
3341
3597
  if (!videoRef.current) return;
3342
3598
  const handleLoadedMetadata = () => {
3599
+ var _a;
3343
3600
  if (videoRef.current) {
3344
3601
  const video2 = videoRef.current;
3345
3602
  void video2.offsetHeight;
3346
3603
  }
3604
+ if (debugAdTiming) {
3605
+ console.log(
3606
+ "[StormcloudUI] Video event: loadedmetadata, readyState:",
3607
+ (_a = videoRef.current) == null ? void 0 : _a.readyState
3608
+ );
3609
+ }
3610
+ };
3611
+ const handleLoadedData = () => {
3612
+ var _a;
3613
+ if (debugAdTiming) {
3614
+ console.log(
3615
+ "[StormcloudUI] Video event: loadeddata, readyState:",
3616
+ (_a = videoRef.current) == null ? void 0 : _a.readyState
3617
+ );
3618
+ }
3347
3619
  };
3348
3620
  const handleLoadStart = () => {
3349
- setIsLoading(true);
3350
- setIsBuffering(false);
3621
+ var _a;
3622
+ if (debugAdTiming) {
3623
+ console.log(
3624
+ "[StormcloudUI] Video event: loadstart, readyState:",
3625
+ (_a = videoRef.current) == null ? void 0 : _a.readyState
3626
+ );
3627
+ }
3351
3628
  };
3352
3629
  const handleCanPlay = () => {
3630
+ var _a;
3631
+ setIsLoading(false);
3632
+ if (bufferingTimeoutRef.current) {
3633
+ clearTimeout(bufferingTimeoutRef.current);
3634
+ bufferingTimeoutRef.current = null;
3635
+ }
3636
+ setIsBuffering(false);
3637
+ if (debugAdTiming) {
3638
+ console.log(
3639
+ "[StormcloudUI] Video event: canplay, readyState:",
3640
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3641
+ "- clearing loading state, isLoading=false"
3642
+ );
3643
+ }
3644
+ };
3645
+ const handleCanPlayThrough = () => {
3646
+ var _a;
3353
3647
  setIsLoading(false);
3648
+ if (bufferingTimeoutRef.current) {
3649
+ clearTimeout(bufferingTimeoutRef.current);
3650
+ bufferingTimeoutRef.current = null;
3651
+ }
3354
3652
  setIsBuffering(false);
3653
+ if (debugAdTiming) {
3654
+ console.log(
3655
+ "[StormcloudUI] Video event: canplaythrough, readyState:",
3656
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3657
+ "- clearing loading state, isLoading=false"
3658
+ );
3659
+ }
3355
3660
  };
3356
3661
  const handleWaiting = () => {
3357
- setIsBuffering(true);
3662
+ var _a;
3663
+ if (bufferingTimeoutRef.current) {
3664
+ clearTimeout(bufferingTimeoutRef.current);
3665
+ }
3666
+ bufferingTimeoutRef.current = window.setTimeout(() => {
3667
+ var _a2;
3668
+ setIsBuffering(true);
3669
+ if (debugAdTiming) {
3670
+ console.log(
3671
+ "[StormcloudUI] Video buffering detected (after 300ms delay), readyState:",
3672
+ (_a2 = videoRef.current) == null ? void 0 : _a2.readyState,
3673
+ "- showing spinner, isBuffering=true"
3674
+ );
3675
+ }
3676
+ }, 300);
3677
+ if (debugAdTiming) {
3678
+ console.log(
3679
+ "[StormcloudUI] Video event: waiting, readyState:",
3680
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3681
+ "- buffering delay started (300ms)"
3682
+ );
3683
+ }
3358
3684
  };
3359
3685
  const handlePlaying = () => {
3686
+ var _a;
3360
3687
  setIsLoading(false);
3688
+ if (bufferingTimeoutRef.current) {
3689
+ clearTimeout(bufferingTimeoutRef.current);
3690
+ bufferingTimeoutRef.current = null;
3691
+ }
3361
3692
  setIsBuffering(false);
3362
3693
  setShowCenterPlay(false);
3694
+ if (debugAdTiming) {
3695
+ console.log(
3696
+ "[StormcloudUI] Video event: playing, readyState:",
3697
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3698
+ "- playback started, isLoading=false, isBuffering=false"
3699
+ );
3700
+ }
3363
3701
  };
3364
3702
  const handlePause = () => {
3365
3703
  if (playerRef.current && !playerRef.current.isShowingAds()) {
@@ -3374,8 +3712,9 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3374
3712
  const video = videoRef.current;
3375
3713
  video.addEventListener("loadstart", handleLoadStart);
3376
3714
  video.addEventListener("loadedmetadata", handleLoadedMetadata);
3377
- video.addEventListener("loadeddata", handleLoadedMetadata);
3715
+ video.addEventListener("loadeddata", handleLoadedData);
3378
3716
  video.addEventListener("canplay", handleCanPlay);
3717
+ video.addEventListener("canplaythrough", handleCanPlayThrough);
3379
3718
  video.addEventListener("waiting", handleWaiting);
3380
3719
  video.addEventListener("playing", handlePlaying);
3381
3720
  video.addEventListener("pause", handlePause);
@@ -3384,16 +3723,21 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3384
3723
  setShowCenterPlay(true);
3385
3724
  }
3386
3725
  return () => {
3726
+ if (bufferingTimeoutRef.current) {
3727
+ clearTimeout(bufferingTimeoutRef.current);
3728
+ bufferingTimeoutRef.current = null;
3729
+ }
3387
3730
  video.removeEventListener("loadstart", handleLoadStart);
3388
3731
  video.removeEventListener("loadedmetadata", handleLoadedMetadata);
3389
- video.removeEventListener("loadeddata", handleLoadedMetadata);
3732
+ video.removeEventListener("loadeddata", handleLoadedData);
3390
3733
  video.removeEventListener("canplay", handleCanPlay);
3734
+ video.removeEventListener("canplaythrough", handleCanPlayThrough);
3391
3735
  video.removeEventListener("waiting", handleWaiting);
3392
3736
  video.removeEventListener("playing", handlePlaying);
3393
3737
  video.removeEventListener("pause", handlePause);
3394
3738
  video.removeEventListener("ended", handleEnded);
3395
3739
  };
3396
- }, []);
3740
+ }, [debugAdTiming]);
3397
3741
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3398
3742
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
3399
3743
  @keyframes spin {
@@ -3500,35 +3844,18 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3500
3844
  }
3501
3845
  ),
3502
3846
  (isLoading || isBuffering) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3503
- "div",
3847
+ import_fa.FaSpinner,
3504
3848
  {
3849
+ size: 42,
3850
+ color: "white",
3505
3851
  style: {
3506
3852
  position: "absolute",
3507
- top: "50%",
3508
- left: "50%",
3509
- transform: "translate(-50%, -50%)",
3853
+ top: "calc(50% - 21px)",
3854
+ left: "calc(50% - 21px)",
3510
3855
  zIndex: 20,
3511
- display: "flex",
3512
- alignItems: "center",
3513
- justifyContent: "center",
3514
- background: "linear-gradient(135deg, rgba(0, 0, 0, 0.8) 0%, rgba(20, 20, 20, 0.6) 100%)",
3515
- width: "80px",
3516
- height: "80px",
3517
- borderRadius: "50%",
3518
- backdropFilter: "blur(20px)",
3519
- boxShadow: "0 12px 40px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 255, 255, 0.1)"
3520
- },
3521
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3522
- import_fa.FaSpinner,
3523
- {
3524
- size: 28,
3525
- color: "white",
3526
- style: {
3527
- animation: "spin 1s linear infinite",
3528
- filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3529
- }
3530
- }
3531
- )
3856
+ animation: "spin 1s linear infinite",
3857
+ filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3858
+ }
3532
3859
  }
3533
3860
  ),
3534
3861
  showLicenseWarning && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -4641,6 +4968,7 @@ var defaultProps = {
4641
4968
  showCustomControls: false,
4642
4969
  licenseKey: "",
4643
4970
  adFailsafeTimeoutMs: 1e4,
4971
+ minSegmentsBeforePlay: 2,
4644
4972
  onStart: noop,
4645
4973
  onPlay: noop,
4646
4974
  onPause: noop,
@@ -4814,6 +5142,8 @@ var HlsPlayer = class extends import_react3.Component {
4814
5142
  config.licenseKey = this.props.licenseKey;
4815
5143
  if (this.props.adFailsafeTimeoutMs !== void 0)
4816
5144
  config.adFailsafeTimeoutMs = this.props.adFailsafeTimeoutMs;
5145
+ if (this.props.minSegmentsBeforePlay !== void 0)
5146
+ config.minSegmentsBeforePlay = this.props.minSegmentsBeforePlay;
4817
5147
  this.player = new StormcloudVideoPlayer(config);
4818
5148
  (_b = (_a = this.props).onMount) == null ? void 0 : _b.call(_a, this);
4819
5149
  await this.player.load();
@@ -5420,6 +5750,7 @@ var SUPPORTED_PROPS = [
5420
5750
  "showCustomControls",
5421
5751
  "licenseKey",
5422
5752
  "adFailsafeTimeoutMs",
5753
+ "minSegmentsBeforePlay",
5423
5754
  "onReady",
5424
5755
  "onStart",
5425
5756
  "onPlay",