stormcloud-video-player 0.2.17 → 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.js CHANGED
@@ -198,6 +198,13 @@ function createImaController(video, options) {
198
198
  let adPlaying = false;
199
199
  let originalMutedState = false;
200
200
  const listeners = /* @__PURE__ */ new Map();
201
+ function setAdPlayingFlag(isPlaying) {
202
+ if (isPlaying) {
203
+ video.dataset.stormcloudAdPlaying = "true";
204
+ } else {
205
+ delete video.dataset.stormcloudAdPlaying;
206
+ }
207
+ }
201
208
  function emit(event, payload) {
202
209
  const set = listeners.get(event);
203
210
  if (!set) return;
@@ -440,7 +447,12 @@ function createImaController(video, options) {
440
447
  console.error("[IMA] Ad error:", errorEvent.getError());
441
448
  destroyAdsManager();
442
449
  adPlaying = false;
450
+ const previousMutedState = video.muted;
443
451
  video.muted = originalMutedState;
452
+ setAdPlayingFlag(false);
453
+ console.log(
454
+ `[IMA] Restored mute state after ad error: ${previousMutedState} -> ${originalMutedState}`
455
+ );
444
456
  if (adContainerEl) {
445
457
  adContainerEl.style.pointerEvents = "none";
446
458
  adContainerEl.style.display = "none";
@@ -493,11 +505,13 @@ function createImaController(video, options) {
493
505
  }
494
506
  video.muted = true;
495
507
  adPlaying = true;
508
+ setAdPlayingFlag(true);
496
509
  emit("content_pause");
497
510
  }
498
511
  );
499
512
  adsManager.addEventListener(AdEvent.STARTED, () => {
500
513
  console.log("[IMA] Ad started playing");
514
+ setAdPlayingFlag(true);
501
515
  if (adContainerEl) {
502
516
  adContainerEl.style.pointerEvents = "auto";
503
517
  adContainerEl.style.display = "flex";
@@ -513,6 +527,7 @@ function createImaController(video, options) {
513
527
  console.log("[IMA] Content resume requested");
514
528
  adPlaying = false;
515
529
  video.muted = originalMutedState;
530
+ setAdPlayingFlag(false);
516
531
  if (adContainerEl) {
517
532
  adContainerEl.style.pointerEvents = "none";
518
533
  adContainerEl.style.display = "none";
@@ -536,6 +551,7 @@ function createImaController(video, options) {
536
551
  console.log("[IMA] All ads completed");
537
552
  adPlaying = false;
538
553
  video.muted = originalMutedState;
554
+ setAdPlayingFlag(false);
539
555
  if (adContainerEl) {
540
556
  adContainerEl.style.pointerEvents = "none";
541
557
  adContainerEl.style.display = "none";
@@ -566,6 +582,7 @@ function createImaController(video, options) {
566
582
  console.error("[IMA] Error setting up ads manager:", e);
567
583
  adPlaying = false;
568
584
  video.muted = originalMutedState;
585
+ setAdPlayingFlag(false);
569
586
  if (adContainerEl) {
570
587
  adContainerEl.style.pointerEvents = "none";
571
588
  adContainerEl.style.display = "none";
@@ -595,7 +612,12 @@ function createImaController(video, options) {
595
612
  (adErrorEvent) => {
596
613
  console.error("[IMA] Ads loader error:", adErrorEvent.getError());
597
614
  adPlaying = false;
615
+ const previousMutedState = video.muted;
598
616
  video.muted = originalMutedState;
617
+ setAdPlayingFlag(false);
618
+ console.log(
619
+ `[IMA] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
620
+ );
599
621
  if (adContainerEl) {
600
622
  adContainerEl.style.pointerEvents = "none";
601
623
  adContainerEl.style.display = "none";
@@ -647,12 +669,20 @@ function createImaController(video, options) {
647
669
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
648
670
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
649
671
  adPlaying = true;
672
+ const adVolume = originalMutedState ? 0 : video.volume;
673
+ try {
674
+ adsManager.setVolume(adVolume);
675
+ console.log(`[IMA] Set ad volume to ${adVolume}`);
676
+ } catch (error) {
677
+ console.warn("[IMA] Failed to set ad volume:", error);
678
+ }
650
679
  console.log("[IMA] Starting ad playback");
651
680
  adsManager.start();
652
681
  return Promise.resolve();
653
682
  } catch (error) {
654
683
  console.error("[IMA] Error starting ad playback:", error);
655
684
  adPlaying = false;
685
+ setAdPlayingFlag(false);
656
686
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
657
687
  (_b = video.play()) == null ? void 0 : _b.catch(() => {
658
688
  });
@@ -664,6 +694,7 @@ function createImaController(video, options) {
664
694
  var _a;
665
695
  adPlaying = false;
666
696
  video.muted = originalMutedState;
697
+ setAdPlayingFlag(false);
667
698
  if (adContainerEl) {
668
699
  adContainerEl.style.pointerEvents = "none";
669
700
  adContainerEl.style.display = "none";
@@ -687,6 +718,7 @@ function createImaController(video, options) {
687
718
  destroyAdsManager();
688
719
  adPlaying = false;
689
720
  video.muted = originalMutedState;
721
+ setAdPlayingFlag(false);
690
722
  if (adContainerEl) {
691
723
  adContainerEl.style.pointerEvents = "none";
692
724
  adContainerEl.style.display = "none";
@@ -729,6 +761,9 @@ function createImaController(video, options) {
729
761
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
730
762
  },
731
763
  updateOriginalMutedState(muted) {
764
+ console.log(
765
+ `[IMA] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
766
+ );
732
767
  originalMutedState = muted;
733
768
  },
734
769
  getOriginalMutedState() {
@@ -785,7 +820,10 @@ function createHlsAdPlayer(contentVideo, options) {
785
820
  try {
786
821
  fn(payload);
787
822
  } catch (error) {
788
- console.warn(`[HlsAdPlayer] Error in event listener for ${event}:`, error);
823
+ console.warn(
824
+ `[HlsAdPlayer] Error in event listener for ${event}:`,
825
+ error
826
+ );
789
827
  }
790
828
  }
791
829
  }
@@ -848,7 +886,9 @@ function createHlsAdPlayer(contentVideo, options) {
848
886
  }
849
887
  const mainQuality = getMainStreamQuality();
850
888
  if (!mainQuality) {
851
- console.log("[HlsAdPlayer] No main stream quality info, using first media file");
889
+ console.log(
890
+ "[HlsAdPlayer] No main stream quality info, using first media file"
891
+ );
852
892
  return firstFile;
853
893
  }
854
894
  console.log("[HlsAdPlayer] Main stream quality:", mainQuality);
@@ -884,7 +924,10 @@ function createHlsAdPlayer(contentVideo, options) {
884
924
  const xmlDoc = parser.parseFromString(xmlString, "text/xml");
885
925
  const parserError = xmlDoc.querySelector("parsererror");
886
926
  if (parserError) {
887
- console.error("[HlsAdPlayer] XML parsing error (malformed VAST XML):", parserError.textContent);
927
+ console.error(
928
+ "[HlsAdPlayer] XML parsing error (malformed VAST XML):",
929
+ parserError.textContent
930
+ );
888
931
  return null;
889
932
  }
890
933
  const adElement = xmlDoc.querySelector("Ad");
@@ -900,17 +943,23 @@ function createHlsAdPlayer(contentVideo, options) {
900
943
  const duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
901
944
  const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
902
945
  const mediaFiles = [];
903
- console.log(`[HlsAdPlayer] Found ${mediaFileElements.length} MediaFile element(s) in VAST XML`);
946
+ console.log(
947
+ `[HlsAdPlayer] Found ${mediaFileElements.length} MediaFile element(s) in VAST XML`
948
+ );
904
949
  mediaFileElements.forEach((mf, index) => {
905
950
  var _a2;
906
951
  const type = mf.getAttribute("type") || "";
907
952
  const url = ((_a2 = mf.textContent) == null ? void 0 : _a2.trim()) || "";
908
953
  const width = mf.getAttribute("width") || "";
909
954
  const height = mf.getAttribute("height") || "";
910
- console.log(`[HlsAdPlayer] MediaFile ${index}: type="${type}", url="${url}", width="${width}", height="${height}"`);
955
+ console.log(
956
+ `[HlsAdPlayer] MediaFile ${index}: type="${type}", url="${url}", width="${width}", height="${height}"`
957
+ );
911
958
  if (type === "application/x-mpegURL" || type.includes("m3u8")) {
912
959
  if (!url) {
913
- console.warn(`[HlsAdPlayer] MediaFile ${index} has HLS type but empty URL`);
960
+ console.warn(
961
+ `[HlsAdPlayer] MediaFile ${index} has HLS type but empty URL`
962
+ );
914
963
  return;
915
964
  }
916
965
  const bitrateAttr = mf.getAttribute("bitrate");
@@ -924,12 +973,16 @@ function createHlsAdPlayer(contentVideo, options) {
924
973
  });
925
974
  console.log(`[HlsAdPlayer] Added HLS MediaFile: ${url}`);
926
975
  } else {
927
- console.log(`[HlsAdPlayer] MediaFile ${index} ignored (type="${type}" is not HLS)`);
976
+ console.log(
977
+ `[HlsAdPlayer] MediaFile ${index} ignored (type="${type}" is not HLS)`
978
+ );
928
979
  }
929
980
  });
930
981
  if (mediaFiles.length === 0) {
931
982
  if (isNoAdAvailable) {
932
- console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
983
+ console.warn(
984
+ "[HlsAdPlayer] No ads available (VAST response indicates no ads)"
985
+ );
933
986
  } else {
934
987
  console.warn("[HlsAdPlayer] No HLS media files found in VAST XML");
935
988
  }
@@ -992,6 +1045,10 @@ function createHlsAdPlayer(contentVideo, options) {
992
1045
  video.style.backgroundColor = "#000";
993
1046
  video.playsInline = true;
994
1047
  video.muted = false;
1048
+ video.volume = 1;
1049
+ console.log(
1050
+ `[HlsAdPlayer] Created ad video element with volume ${video.volume}`
1051
+ );
995
1052
  return video;
996
1053
  }
997
1054
  function setupAdEventListeners() {
@@ -1051,10 +1108,22 @@ function createHlsAdPlayer(contentVideo, options) {
1051
1108
  }
1052
1109
  });
1053
1110
  }
1111
+ function setAdPlayingFlag(isPlaying) {
1112
+ if (isPlaying) {
1113
+ contentVideo.dataset.stormcloudAdPlaying = "true";
1114
+ } else {
1115
+ delete contentVideo.dataset.stormcloudAdPlaying;
1116
+ }
1117
+ }
1054
1118
  function handleAdComplete() {
1055
1119
  console.log("[HlsAdPlayer] Handling ad completion");
1056
1120
  adPlaying = false;
1121
+ setAdPlayingFlag(false);
1122
+ const previousMutedState = contentVideo.muted;
1057
1123
  contentVideo.muted = originalMutedState;
1124
+ console.log(
1125
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1126
+ );
1058
1127
  if (adContainerEl) {
1059
1128
  adContainerEl.style.display = "none";
1060
1129
  adContainerEl.style.pointerEvents = "none";
@@ -1072,7 +1141,12 @@ function createHlsAdPlayer(contentVideo, options) {
1072
1141
  function handleAdError() {
1073
1142
  console.log("[HlsAdPlayer] Handling ad error");
1074
1143
  adPlaying = false;
1144
+ setAdPlayingFlag(false);
1145
+ const previousMutedState = contentVideo.muted;
1075
1146
  contentVideo.muted = originalMutedState;
1147
+ console.log(
1148
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1149
+ );
1076
1150
  if (adContainerEl) {
1077
1151
  adContainerEl.style.display = "none";
1078
1152
  adContainerEl.style.pointerEvents = "none";
@@ -1109,7 +1183,9 @@ function createHlsAdPlayer(contentVideo, options) {
1109
1183
  async requestAds(vastTagUrl) {
1110
1184
  console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
1111
1185
  if (adPlaying) {
1112
- console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
1186
+ console.warn(
1187
+ "[HlsAdPlayer] Cannot request new ads while an ad is playing"
1188
+ );
1113
1189
  return Promise.reject(new Error("Ad already playing"));
1114
1190
  }
1115
1191
  try {
@@ -1120,14 +1196,20 @@ function createHlsAdPlayer(contentVideo, options) {
1120
1196
  }
1121
1197
  const vastXml = await response.text();
1122
1198
  console.log("[HlsAdPlayer] VAST XML received");
1123
- console.log("[HlsAdPlayer] VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
1199
+ console.log(
1200
+ "[HlsAdPlayer] VAST XML content (first 2000 chars):",
1201
+ vastXml.substring(0, 2e3)
1202
+ );
1124
1203
  const ad = parseVastXml(vastXml);
1125
1204
  if (!ad) {
1126
1205
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1206
+ emit("ad_error");
1127
1207
  return Promise.resolve();
1128
1208
  }
1129
1209
  currentAd = ad;
1130
- console.log(`[HlsAdPlayer] Ad parsed: ${ad.title}, duration: ${ad.duration}s`);
1210
+ console.log(
1211
+ `[HlsAdPlayer] Ad parsed: ${ad.title}, duration: ${ad.duration}s`
1212
+ );
1131
1213
  fireTrackingPixels(ad.trackingUrls.impression);
1132
1214
  trackingFired.impression = true;
1133
1215
  return Promise.resolve();
@@ -1139,7 +1221,9 @@ function createHlsAdPlayer(contentVideo, options) {
1139
1221
  },
1140
1222
  async play() {
1141
1223
  if (!currentAd) {
1142
- console.warn("[HlsAdPlayer] Cannot play: No ad loaded (no ads available)");
1224
+ console.warn(
1225
+ "[HlsAdPlayer] Cannot play: No ad loaded (no ads available)"
1226
+ );
1143
1227
  return Promise.reject(new Error("No ad loaded"));
1144
1228
  }
1145
1229
  console.log("[HlsAdPlayer] Starting ad playback");
@@ -1157,6 +1241,7 @@ function createHlsAdPlayer(contentVideo, options) {
1157
1241
  thirdQuartile: false,
1158
1242
  complete: false
1159
1243
  };
1244
+ const contentVolume = contentVideo.volume;
1160
1245
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
1161
1246
  contentVideo.pause();
1162
1247
  console.log("[HlsAdPlayer] Content paused (VOD mode)");
@@ -1165,6 +1250,15 @@ function createHlsAdPlayer(contentVideo, options) {
1165
1250
  }
1166
1251
  contentVideo.muted = true;
1167
1252
  adPlaying = true;
1253
+ setAdPlayingFlag(true);
1254
+ if (adVideoElement) {
1255
+ const adVolume = originalMutedState ? 0 : contentVolume;
1256
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1257
+ adVideoElement.muted = false;
1258
+ console.log(
1259
+ `[HlsAdPlayer] Set ad video volume to ${adVideoElement.volume}, muted: ${adVideoElement.muted}, originalMutedState: ${originalMutedState}, contentVolume: ${contentVolume}`
1260
+ );
1261
+ }
1168
1262
  if (adContainerEl) {
1169
1263
  adContainerEl.style.display = "flex";
1170
1264
  adContainerEl.style.pointerEvents = "auto";
@@ -1217,6 +1311,7 @@ function createHlsAdPlayer(contentVideo, options) {
1217
1311
  async stop() {
1218
1312
  console.log("[HlsAdPlayer] Stopping ad");
1219
1313
  adPlaying = false;
1314
+ setAdPlayingFlag(false);
1220
1315
  contentVideo.muted = originalMutedState;
1221
1316
  if (adContainerEl) {
1222
1317
  adContainerEl.style.display = "none";
@@ -1239,6 +1334,7 @@ function createHlsAdPlayer(contentVideo, options) {
1239
1334
  destroy() {
1240
1335
  console.log("[HlsAdPlayer] Destroying");
1241
1336
  adPlaying = false;
1337
+ setAdPlayingFlag(false);
1242
1338
  contentVideo.muted = originalMutedState;
1243
1339
  if (adHls) {
1244
1340
  adHls.destroy();
@@ -1280,6 +1376,9 @@ function createHlsAdPlayer(contentVideo, options) {
1280
1376
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
1281
1377
  },
1282
1378
  updateOriginalMutedState(muted) {
1379
+ console.log(
1380
+ `[HlsAdPlayer] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
1381
+ );
1283
1382
  originalMutedState = muted;
1284
1383
  },
1285
1384
  getOriginalMutedState() {
@@ -1772,6 +1871,9 @@ var StormcloudVideoPlayer = class {
1772
1871
  this.isLiveStream = false;
1773
1872
  this.nativeHlsMode = false;
1774
1873
  this.videoSrcProtection = null;
1874
+ this.bufferedSegmentsCount = 0;
1875
+ this.shouldAutoplayAfterBuffering = false;
1876
+ this.hasInitialBufferCompleted = false;
1775
1877
  initializePolyfills();
1776
1878
  const browserOverrides = getBrowserConfigOverrides();
1777
1879
  this.config = { ...config, ...browserOverrides };
@@ -1858,14 +1960,22 @@ var StormcloudVideoPlayer = class {
1858
1960
  liveDurationInfinity: true,
1859
1961
  lowLatencyMode: !!this.config.lowLatencyMode,
1860
1962
  maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
1861
- ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
1963
+ ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {},
1964
+ maxBufferLength: 30,
1965
+ maxMaxBufferLength: 600,
1966
+ maxBufferSize: 60 * 1e3 * 1e3,
1967
+ maxBufferHole: 0.5,
1968
+ highBufferWatchdogPeriod: 2,
1969
+ nudgeOffset: 0.1,
1970
+ nudgeMaxRetry: 3,
1971
+ startPosition: -1
1862
1972
  });
1863
1973
  this.hls.on(Hls2.Events.MEDIA_ATTACHED, () => {
1864
1974
  var _a2;
1865
1975
  (_a2 = this.hls) == null ? void 0 : _a2.loadSource(this.config.src);
1866
1976
  });
1867
1977
  this.hls.on(Hls2.Events.MANIFEST_PARSED, async (_, data) => {
1868
- var _a2, _b2, _c, _d;
1978
+ var _a2, _b2, _c, _d, _e;
1869
1979
  this.isLiveStream = (_c = (_b2 = (_a2 = this.hls) == null ? void 0 : _a2.levels) == null ? void 0 : _b2.some(
1870
1980
  (level) => {
1871
1981
  var _a3, _b3;
@@ -1883,9 +1993,51 @@ var StormcloudVideoPlayer = class {
1883
1993
  this.ima.destroy();
1884
1994
  this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
1885
1995
  this.ima.initialize();
1886
- if (this.config.autoplay) {
1887
- await ((_d = this.video.play()) == null ? void 0 : _d.catch(() => {
1888
- }));
1996
+ this.bufferedSegmentsCount = 0;
1997
+ this.hasInitialBufferCompleted = false;
1998
+ this.shouldAutoplayAfterBuffering = !!this.config.autoplay;
1999
+ const minSegments = (_d = this.config.minSegmentsBeforePlay) != null ? _d : 2;
2000
+ if (this.config.debugAdTiming) {
2001
+ console.log(
2002
+ "[StormcloudVideoPlayer] Waiting for",
2003
+ minSegments,
2004
+ "segments to buffer before playback"
2005
+ );
2006
+ }
2007
+ if (minSegments === 0 || !this.config.autoplay) {
2008
+ this.hasInitialBufferCompleted = true;
2009
+ if (this.config.autoplay) {
2010
+ await ((_e = this.video.play()) == null ? void 0 : _e.catch(() => {
2011
+ }));
2012
+ }
2013
+ }
2014
+ });
2015
+ this.hls.on(Hls2.Events.FRAG_BUFFERED, async (_evt, data) => {
2016
+ var _a2, _b2;
2017
+ if (this.hasInitialBufferCompleted) {
2018
+ return;
2019
+ }
2020
+ this.bufferedSegmentsCount++;
2021
+ const minSegments = (_a2 = this.config.minSegmentsBeforePlay) != null ? _a2 : 2;
2022
+ if (this.config.debugAdTiming) {
2023
+ console.log(
2024
+ `[StormcloudVideoPlayer] Buffered segment ${this.bufferedSegmentsCount}/${minSegments}`
2025
+ );
2026
+ }
2027
+ if (this.bufferedSegmentsCount >= minSegments) {
2028
+ this.hasInitialBufferCompleted = true;
2029
+ if (this.shouldAutoplayAfterBuffering) {
2030
+ if (this.config.debugAdTiming) {
2031
+ console.log(
2032
+ `[StormcloudVideoPlayer] Initial buffer complete (${this.bufferedSegmentsCount} segments). Starting playback.`
2033
+ );
2034
+ }
2035
+ await ((_b2 = this.video.play()) == null ? void 0 : _b2.catch((err) => {
2036
+ if (this.config.debugAdTiming) {
2037
+ console.warn("[StormcloudVideoPlayer] Autoplay failed:", err);
2038
+ }
2039
+ }));
2040
+ }
1889
2041
  }
1890
2042
  });
1891
2043
  this.hls.on(Hls2.Events.FRAG_PARSING_METADATA, (_evt, data) => {
@@ -1981,6 +2133,7 @@ var StormcloudVideoPlayer = class {
1981
2133
  this.video.autoplay = !!this.config.autoplay;
1982
2134
  this.video.muted = !!this.config.muted;
1983
2135
  this.ima.initialize();
2136
+ this.ima.updateOriginalMutedState(this.video.muted);
1984
2137
  this.ima.on("all_ads_completed", () => {
1985
2138
  if (this.config.debugAdTiming) {
1986
2139
  console.log(
@@ -2765,7 +2918,23 @@ var StormcloudVideoPlayer = class {
2765
2918
  }
2766
2919
  return;
2767
2920
  }
2768
- this.ima.updateOriginalMutedState(this.video.muted);
2921
+ if (!this.showAds) {
2922
+ if (this.config.debugAdTiming) {
2923
+ console.log(
2924
+ `[StormcloudVideoPlayer] Capturing original state before ad request:`,
2925
+ {
2926
+ videoMuted: this.video.muted,
2927
+ videoVolume: this.video.volume,
2928
+ showAds: this.showAds
2929
+ }
2930
+ );
2931
+ }
2932
+ this.ima.updateOriginalMutedState(this.video.muted);
2933
+ } else if (this.config.debugAdTiming) {
2934
+ console.log(
2935
+ `[StormcloudVideoPlayer] Keeping existing original mute state (currently showing ads)`
2936
+ );
2937
+ }
2769
2938
  this.startAdFailsafeTimer();
2770
2939
  try {
2771
2940
  await this.ima.requestAds(vastTagUrl);
@@ -2820,11 +2989,12 @@ var StormcloudVideoPlayer = class {
2820
2989
  this.showAds = false;
2821
2990
  this.currentAdIndex = 0;
2822
2991
  this.totalAdsInBreak = 0;
2992
+ const currentMutedState = this.video.muted;
2823
2993
  const originalMutedState = this.ima.getOriginalMutedState();
2824
2994
  this.video.muted = originalMutedState;
2825
2995
  if (this.config.debugAdTiming) {
2826
2996
  console.log(
2827
- `[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
2997
+ `[StormcloudVideoPlayer] Restored mute state: ${currentMutedState} -> ${originalMutedState}`
2828
2998
  );
2829
2999
  }
2830
3000
  if (this.video.paused) {
@@ -2916,6 +3086,7 @@ var StormcloudVideoPlayer = class {
2916
3086
  }
2917
3087
  } else {
2918
3088
  this.video.muted = !this.video.muted;
3089
+ this.ima.updateOriginalMutedState(this.video.muted);
2919
3090
  if (this.config.debugAdTiming) {
2920
3091
  console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
2921
3092
  }
@@ -2960,11 +3131,47 @@ var StormcloudVideoPlayer = class {
2960
3131
  }
2961
3132
  isMuted() {
2962
3133
  if (this.ima.isAdPlaying()) {
2963
- const adVolume = this.ima.getAdVolume();
2964
- return adVolume === 0;
3134
+ if (this.config.debugAdTiming) {
3135
+ console.log(
3136
+ "[StormcloudVideoPlayer] isMuted() override during ad playback -> false"
3137
+ );
3138
+ }
3139
+ return false;
3140
+ }
3141
+ if (this.config.debugAdTiming) {
3142
+ console.log(
3143
+ `[StormcloudVideoPlayer] isMuted() no ad playing: video.muted=${this.video.muted}`
3144
+ );
2965
3145
  }
2966
3146
  return this.video.muted;
2967
3147
  }
3148
+ setMuted(muted) {
3149
+ const adPlaying = this.ima.isAdPlaying();
3150
+ if (adPlaying && muted === this.video.muted) {
3151
+ if (this.config.debugAdTiming) {
3152
+ console.log(
3153
+ "[StormcloudVideoPlayer] setMuted reflective update during ad ignored",
3154
+ { muted }
3155
+ );
3156
+ }
3157
+ return;
3158
+ }
3159
+ this.video.muted = muted;
3160
+ if (adPlaying) {
3161
+ this.ima.updateOriginalMutedState(muted);
3162
+ this.ima.setAdVolume(muted ? 0 : 1);
3163
+ if (this.config.debugAdTiming) {
3164
+ console.log("[StormcloudVideoPlayer] setMuted applied during ad", {
3165
+ muted
3166
+ });
3167
+ }
3168
+ return;
3169
+ }
3170
+ this.ima.updateOriginalMutedState(muted);
3171
+ if (this.config.debugAdTiming) {
3172
+ console.log("[StormcloudVideoPlayer] setMuted called:", muted);
3173
+ }
3174
+ }
2968
3175
  isFullscreen() {
2969
3176
  return !!document.fullscreenElement;
2970
3177
  }
@@ -3052,10 +3259,12 @@ var StormcloudVideoPlayerComponent = React.memo(
3052
3259
  vastMode,
3053
3260
  vastTagUrl,
3054
3261
  adPlayerType,
3262
+ minSegmentsBeforePlay,
3055
3263
  ...restVideoAttrs
3056
3264
  } = props;
3057
3265
  const videoRef = useRef(null);
3058
3266
  const playerRef = useRef(null);
3267
+ const bufferingTimeoutRef = useRef(null);
3059
3268
  const [adStatus, setAdStatus] = React.useState({ showAds: false, currentIndex: 0, totalAds: 0 });
3060
3269
  const [shouldShowNativeControls, setShouldShowNativeControls] = React.useState(true);
3061
3270
  const [isMuted, setIsMuted] = React.useState(false);
@@ -3177,6 +3386,9 @@ var StormcloudVideoPlayerComponent = React.memo(
3177
3386
  return;
3178
3387
  }
3179
3388
  setShowLicenseWarning(false);
3389
+ if (debugAdTiming) {
3390
+ console.log("[StormcloudUI] Initializing player, isLoading=true");
3391
+ }
3180
3392
  if (playerRef.current) {
3181
3393
  try {
3182
3394
  playerRef.current.destroy();
@@ -3207,17 +3419,25 @@ var StormcloudVideoPlayerComponent = React.memo(
3207
3419
  if (vastMode !== void 0) cfg.vastMode = vastMode;
3208
3420
  if (vastTagUrl !== void 0) cfg.vastTagUrl = vastTagUrl;
3209
3421
  if (adPlayerType !== void 0) cfg.adPlayerType = adPlayerType;
3422
+ if (minSegmentsBeforePlay !== void 0)
3423
+ cfg.minSegmentsBeforePlay = minSegmentsBeforePlay;
3210
3424
  const player = new StormcloudVideoPlayer(cfg);
3211
3425
  playerRef.current = player;
3212
3426
  player.load().then(() => {
3213
3427
  const showNative = player.shouldShowNativeControls();
3214
3428
  setShouldShowNativeControls(showNative);
3429
+ if (debugAdTiming) {
3430
+ console.log(
3431
+ "[StormcloudUI] Player loaded successfully, waiting for video ready"
3432
+ );
3433
+ }
3215
3434
  onReady == null ? void 0 : onReady(player);
3216
3435
  }).catch((error) => {
3217
3436
  console.error(
3218
3437
  "StormcloudVideoPlayer: Failed to load player:",
3219
3438
  error
3220
3439
  );
3440
+ setIsLoading(false);
3221
3441
  onReady == null ? void 0 : onReady(player);
3222
3442
  });
3223
3443
  return () => {
@@ -3234,8 +3454,8 @@ var StormcloudVideoPlayerComponent = React.memo(
3234
3454
  if (autoplay !== void 0 && playerRef.current.videoElement) {
3235
3455
  playerRef.current.videoElement.autoplay = autoplay;
3236
3456
  }
3237
- if (muted !== void 0 && playerRef.current.videoElement) {
3238
- playerRef.current.videoElement.muted = muted;
3457
+ if (muted !== void 0) {
3458
+ playerRef.current.setMuted(muted);
3239
3459
  }
3240
3460
  } catch (error) {
3241
3461
  console.warn("Failed to update player properties:", error);
@@ -3316,26 +3536,108 @@ var StormcloudVideoPlayerComponent = React.memo(
3316
3536
  useEffect(() => {
3317
3537
  if (!videoRef.current) return;
3318
3538
  const handleLoadedMetadata = () => {
3539
+ var _a;
3319
3540
  if (videoRef.current) {
3320
3541
  const video2 = videoRef.current;
3321
3542
  void video2.offsetHeight;
3322
3543
  }
3544
+ if (debugAdTiming) {
3545
+ console.log(
3546
+ "[StormcloudUI] Video event: loadedmetadata, readyState:",
3547
+ (_a = videoRef.current) == null ? void 0 : _a.readyState
3548
+ );
3549
+ }
3550
+ };
3551
+ const handleLoadedData = () => {
3552
+ var _a;
3553
+ if (debugAdTiming) {
3554
+ console.log(
3555
+ "[StormcloudUI] Video event: loadeddata, readyState:",
3556
+ (_a = videoRef.current) == null ? void 0 : _a.readyState
3557
+ );
3558
+ }
3323
3559
  };
3324
3560
  const handleLoadStart = () => {
3325
- setIsLoading(true);
3326
- setIsBuffering(false);
3561
+ var _a;
3562
+ if (debugAdTiming) {
3563
+ console.log(
3564
+ "[StormcloudUI] Video event: loadstart, readyState:",
3565
+ (_a = videoRef.current) == null ? void 0 : _a.readyState
3566
+ );
3567
+ }
3327
3568
  };
3328
3569
  const handleCanPlay = () => {
3570
+ var _a;
3329
3571
  setIsLoading(false);
3572
+ if (bufferingTimeoutRef.current) {
3573
+ clearTimeout(bufferingTimeoutRef.current);
3574
+ bufferingTimeoutRef.current = null;
3575
+ }
3330
3576
  setIsBuffering(false);
3577
+ if (debugAdTiming) {
3578
+ console.log(
3579
+ "[StormcloudUI] Video event: canplay, readyState:",
3580
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3581
+ "- clearing loading state, isLoading=false"
3582
+ );
3583
+ }
3584
+ };
3585
+ const handleCanPlayThrough = () => {
3586
+ var _a;
3587
+ setIsLoading(false);
3588
+ if (bufferingTimeoutRef.current) {
3589
+ clearTimeout(bufferingTimeoutRef.current);
3590
+ bufferingTimeoutRef.current = null;
3591
+ }
3592
+ setIsBuffering(false);
3593
+ if (debugAdTiming) {
3594
+ console.log(
3595
+ "[StormcloudUI] Video event: canplaythrough, readyState:",
3596
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3597
+ "- clearing loading state, isLoading=false"
3598
+ );
3599
+ }
3331
3600
  };
3332
3601
  const handleWaiting = () => {
3333
- setIsBuffering(true);
3602
+ var _a;
3603
+ if (bufferingTimeoutRef.current) {
3604
+ clearTimeout(bufferingTimeoutRef.current);
3605
+ }
3606
+ bufferingTimeoutRef.current = window.setTimeout(() => {
3607
+ var _a2;
3608
+ setIsBuffering(true);
3609
+ if (debugAdTiming) {
3610
+ console.log(
3611
+ "[StormcloudUI] Video buffering detected (after 300ms delay), readyState:",
3612
+ (_a2 = videoRef.current) == null ? void 0 : _a2.readyState,
3613
+ "- showing spinner, isBuffering=true"
3614
+ );
3615
+ }
3616
+ }, 300);
3617
+ if (debugAdTiming) {
3618
+ console.log(
3619
+ "[StormcloudUI] Video event: waiting, readyState:",
3620
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3621
+ "- buffering delay started (300ms)"
3622
+ );
3623
+ }
3334
3624
  };
3335
3625
  const handlePlaying = () => {
3626
+ var _a;
3336
3627
  setIsLoading(false);
3628
+ if (bufferingTimeoutRef.current) {
3629
+ clearTimeout(bufferingTimeoutRef.current);
3630
+ bufferingTimeoutRef.current = null;
3631
+ }
3337
3632
  setIsBuffering(false);
3338
3633
  setShowCenterPlay(false);
3634
+ if (debugAdTiming) {
3635
+ console.log(
3636
+ "[StormcloudUI] Video event: playing, readyState:",
3637
+ (_a = videoRef.current) == null ? void 0 : _a.readyState,
3638
+ "- playback started, isLoading=false, isBuffering=false"
3639
+ );
3640
+ }
3339
3641
  };
3340
3642
  const handlePause = () => {
3341
3643
  if (playerRef.current && !playerRef.current.isShowingAds()) {
@@ -3350,8 +3652,9 @@ var StormcloudVideoPlayerComponent = React.memo(
3350
3652
  const video = videoRef.current;
3351
3653
  video.addEventListener("loadstart", handleLoadStart);
3352
3654
  video.addEventListener("loadedmetadata", handleLoadedMetadata);
3353
- video.addEventListener("loadeddata", handleLoadedMetadata);
3655
+ video.addEventListener("loadeddata", handleLoadedData);
3354
3656
  video.addEventListener("canplay", handleCanPlay);
3657
+ video.addEventListener("canplaythrough", handleCanPlayThrough);
3355
3658
  video.addEventListener("waiting", handleWaiting);
3356
3659
  video.addEventListener("playing", handlePlaying);
3357
3660
  video.addEventListener("pause", handlePause);
@@ -3360,16 +3663,21 @@ var StormcloudVideoPlayerComponent = React.memo(
3360
3663
  setShowCenterPlay(true);
3361
3664
  }
3362
3665
  return () => {
3666
+ if (bufferingTimeoutRef.current) {
3667
+ clearTimeout(bufferingTimeoutRef.current);
3668
+ bufferingTimeoutRef.current = null;
3669
+ }
3363
3670
  video.removeEventListener("loadstart", handleLoadStart);
3364
3671
  video.removeEventListener("loadedmetadata", handleLoadedMetadata);
3365
- video.removeEventListener("loadeddata", handleLoadedMetadata);
3672
+ video.removeEventListener("loadeddata", handleLoadedData);
3366
3673
  video.removeEventListener("canplay", handleCanPlay);
3674
+ video.removeEventListener("canplaythrough", handleCanPlayThrough);
3367
3675
  video.removeEventListener("waiting", handleWaiting);
3368
3676
  video.removeEventListener("playing", handlePlaying);
3369
3677
  video.removeEventListener("pause", handlePause);
3370
3678
  video.removeEventListener("ended", handleEnded);
3371
3679
  };
3372
- }, []);
3680
+ }, [debugAdTiming]);
3373
3681
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3374
3682
  /* @__PURE__ */ jsx("style", { children: `
3375
3683
  @keyframes spin {
@@ -3476,35 +3784,18 @@ var StormcloudVideoPlayerComponent = React.memo(
3476
3784
  }
3477
3785
  ),
3478
3786
  (isLoading || isBuffering) && /* @__PURE__ */ jsx(
3479
- "div",
3787
+ FaSpinner,
3480
3788
  {
3789
+ size: 42,
3790
+ color: "white",
3481
3791
  style: {
3482
3792
  position: "absolute",
3483
- top: "50%",
3484
- left: "50%",
3485
- transform: "translate(-50%, -50%)",
3793
+ top: "calc(50% - 21px)",
3794
+ left: "calc(50% - 21px)",
3486
3795
  zIndex: 20,
3487
- display: "flex",
3488
- alignItems: "center",
3489
- justifyContent: "center",
3490
- background: "linear-gradient(135deg, rgba(0, 0, 0, 0.8) 0%, rgba(20, 20, 20, 0.6) 100%)",
3491
- width: "80px",
3492
- height: "80px",
3493
- borderRadius: "50%",
3494
- backdropFilter: "blur(20px)",
3495
- boxShadow: "0 12px 40px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 255, 255, 0.1)"
3496
- },
3497
- children: /* @__PURE__ */ jsx(
3498
- FaSpinner,
3499
- {
3500
- size: 28,
3501
- color: "white",
3502
- style: {
3503
- animation: "spin 1s linear infinite",
3504
- filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3505
- }
3506
- }
3507
- )
3796
+ animation: "spin 1s linear infinite",
3797
+ filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3798
+ }
3508
3799
  }
3509
3800
  ),
3510
3801
  showLicenseWarning && /* @__PURE__ */ jsxs(
@@ -4617,6 +4908,7 @@ var defaultProps = {
4617
4908
  showCustomControls: false,
4618
4909
  licenseKey: "",
4619
4910
  adFailsafeTimeoutMs: 1e4,
4911
+ minSegmentsBeforePlay: 2,
4620
4912
  onStart: noop,
4621
4913
  onPlay: noop,
4622
4914
  onPause: noop,
@@ -4790,6 +5082,8 @@ var HlsPlayer = class extends Component {
4790
5082
  config.licenseKey = this.props.licenseKey;
4791
5083
  if (this.props.adFailsafeTimeoutMs !== void 0)
4792
5084
  config.adFailsafeTimeoutMs = this.props.adFailsafeTimeoutMs;
5085
+ if (this.props.minSegmentsBeforePlay !== void 0)
5086
+ config.minSegmentsBeforePlay = this.props.minSegmentsBeforePlay;
4793
5087
  this.player = new StormcloudVideoPlayer(config);
4794
5088
  (_b = (_a = this.props).onMount) == null ? void 0 : _b.call(_a, this);
4795
5089
  await this.player.load();
@@ -5396,6 +5690,7 @@ var SUPPORTED_PROPS = [
5396
5690
  "showCustomControls",
5397
5691
  "licenseKey",
5398
5692
  "adFailsafeTimeoutMs",
5693
+ "minSegmentsBeforePlay",
5399
5694
  "onReady",
5400
5695
  "onStart",
5401
5696
  "onPlay",