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.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;
@@ -509,7 +516,12 @@ function createImaController(video, options) {
509
516
  console.error("[IMA] Ad error:", errorEvent.getError());
510
517
  destroyAdsManager();
511
518
  adPlaying = false;
519
+ const previousMutedState = video.muted;
512
520
  video.muted = originalMutedState;
521
+ setAdPlayingFlag(false);
522
+ console.log(
523
+ `[IMA] Restored mute state after ad error: ${previousMutedState} -> ${originalMutedState}`
524
+ );
513
525
  if (adContainerEl) {
514
526
  adContainerEl.style.pointerEvents = "none";
515
527
  adContainerEl.style.display = "none";
@@ -562,11 +574,13 @@ function createImaController(video, options) {
562
574
  }
563
575
  video.muted = true;
564
576
  adPlaying = true;
577
+ setAdPlayingFlag(true);
565
578
  emit("content_pause");
566
579
  }
567
580
  );
568
581
  adsManager.addEventListener(AdEvent.STARTED, () => {
569
582
  console.log("[IMA] Ad started playing");
583
+ setAdPlayingFlag(true);
570
584
  if (adContainerEl) {
571
585
  adContainerEl.style.pointerEvents = "auto";
572
586
  adContainerEl.style.display = "flex";
@@ -582,6 +596,7 @@ function createImaController(video, options) {
582
596
  console.log("[IMA] Content resume requested");
583
597
  adPlaying = false;
584
598
  video.muted = originalMutedState;
599
+ setAdPlayingFlag(false);
585
600
  if (adContainerEl) {
586
601
  adContainerEl.style.pointerEvents = "none";
587
602
  adContainerEl.style.display = "none";
@@ -605,6 +620,7 @@ function createImaController(video, options) {
605
620
  console.log("[IMA] All ads completed");
606
621
  adPlaying = false;
607
622
  video.muted = originalMutedState;
623
+ setAdPlayingFlag(false);
608
624
  if (adContainerEl) {
609
625
  adContainerEl.style.pointerEvents = "none";
610
626
  adContainerEl.style.display = "none";
@@ -635,6 +651,7 @@ function createImaController(video, options) {
635
651
  console.error("[IMA] Error setting up ads manager:", e);
636
652
  adPlaying = false;
637
653
  video.muted = originalMutedState;
654
+ setAdPlayingFlag(false);
638
655
  if (adContainerEl) {
639
656
  adContainerEl.style.pointerEvents = "none";
640
657
  adContainerEl.style.display = "none";
@@ -664,7 +681,12 @@ function createImaController(video, options) {
664
681
  (adErrorEvent) => {
665
682
  console.error("[IMA] Ads loader error:", adErrorEvent.getError());
666
683
  adPlaying = false;
684
+ const previousMutedState = video.muted;
667
685
  video.muted = originalMutedState;
686
+ setAdPlayingFlag(false);
687
+ console.log(
688
+ `[IMA] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
689
+ );
668
690
  if (adContainerEl) {
669
691
  adContainerEl.style.pointerEvents = "none";
670
692
  adContainerEl.style.display = "none";
@@ -716,12 +738,20 @@ function createImaController(video, options) {
716
738
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
717
739
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
718
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
+ }
719
748
  console.log("[IMA] Starting ad playback");
720
749
  adsManager.start();
721
750
  return Promise.resolve();
722
751
  } catch (error) {
723
752
  console.error("[IMA] Error starting ad playback:", error);
724
753
  adPlaying = false;
754
+ setAdPlayingFlag(false);
725
755
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
726
756
  (_b = video.play()) == null ? void 0 : _b.catch(() => {
727
757
  });
@@ -733,6 +763,7 @@ function createImaController(video, options) {
733
763
  var _a;
734
764
  adPlaying = false;
735
765
  video.muted = originalMutedState;
766
+ setAdPlayingFlag(false);
736
767
  if (adContainerEl) {
737
768
  adContainerEl.style.pointerEvents = "none";
738
769
  adContainerEl.style.display = "none";
@@ -756,6 +787,7 @@ function createImaController(video, options) {
756
787
  destroyAdsManager();
757
788
  adPlaying = false;
758
789
  video.muted = originalMutedState;
790
+ setAdPlayingFlag(false);
759
791
  if (adContainerEl) {
760
792
  adContainerEl.style.pointerEvents = "none";
761
793
  adContainerEl.style.display = "none";
@@ -798,6 +830,9 @@ function createImaController(video, options) {
798
830
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
799
831
  },
800
832
  updateOriginalMutedState(muted) {
833
+ console.log(
834
+ `[IMA] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
835
+ );
801
836
  originalMutedState = muted;
802
837
  },
803
838
  getOriginalMutedState() {
@@ -854,7 +889,10 @@ function createHlsAdPlayer(contentVideo, options) {
854
889
  try {
855
890
  fn(payload);
856
891
  } catch (error) {
857
- console.warn(`[HlsAdPlayer] Error in event listener for ${event}:`, error);
892
+ console.warn(
893
+ `[HlsAdPlayer] Error in event listener for ${event}:`,
894
+ error
895
+ );
858
896
  }
859
897
  }
860
898
  }
@@ -917,7 +955,9 @@ function createHlsAdPlayer(contentVideo, options) {
917
955
  }
918
956
  const mainQuality = getMainStreamQuality();
919
957
  if (!mainQuality) {
920
- 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
+ );
921
961
  return firstFile;
922
962
  }
923
963
  console.log("[HlsAdPlayer] Main stream quality:", mainQuality);
@@ -953,7 +993,10 @@ function createHlsAdPlayer(contentVideo, options) {
953
993
  const xmlDoc = parser.parseFromString(xmlString, "text/xml");
954
994
  const parserError = xmlDoc.querySelector("parsererror");
955
995
  if (parserError) {
956
- 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
+ );
957
1000
  return null;
958
1001
  }
959
1002
  const adElement = xmlDoc.querySelector("Ad");
@@ -969,17 +1012,23 @@ function createHlsAdPlayer(contentVideo, options) {
969
1012
  const duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
970
1013
  const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
971
1014
  const mediaFiles = [];
972
- 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
+ );
973
1018
  mediaFileElements.forEach((mf, index) => {
974
1019
  var _a2;
975
1020
  const type = mf.getAttribute("type") || "";
976
1021
  const url = ((_a2 = mf.textContent) == null ? void 0 : _a2.trim()) || "";
977
1022
  const width = mf.getAttribute("width") || "";
978
1023
  const height = mf.getAttribute("height") || "";
979
- 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
+ );
980
1027
  if (type === "application/x-mpegURL" || type.includes("m3u8")) {
981
1028
  if (!url) {
982
- 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
+ );
983
1032
  return;
984
1033
  }
985
1034
  const bitrateAttr = mf.getAttribute("bitrate");
@@ -993,12 +1042,16 @@ function createHlsAdPlayer(contentVideo, options) {
993
1042
  });
994
1043
  console.log(`[HlsAdPlayer] Added HLS MediaFile: ${url}`);
995
1044
  } else {
996
- 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
+ );
997
1048
  }
998
1049
  });
999
1050
  if (mediaFiles.length === 0) {
1000
1051
  if (isNoAdAvailable) {
1001
- 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
+ );
1002
1055
  } else {
1003
1056
  console.warn("[HlsAdPlayer] No HLS media files found in VAST XML");
1004
1057
  }
@@ -1061,6 +1114,10 @@ function createHlsAdPlayer(contentVideo, options) {
1061
1114
  video.style.backgroundColor = "#000";
1062
1115
  video.playsInline = true;
1063
1116
  video.muted = false;
1117
+ video.volume = 1;
1118
+ console.log(
1119
+ `[HlsAdPlayer] Created ad video element with volume ${video.volume}`
1120
+ );
1064
1121
  return video;
1065
1122
  }
1066
1123
  function setupAdEventListeners() {
@@ -1120,10 +1177,22 @@ function createHlsAdPlayer(contentVideo, options) {
1120
1177
  }
1121
1178
  });
1122
1179
  }
1180
+ function setAdPlayingFlag(isPlaying) {
1181
+ if (isPlaying) {
1182
+ contentVideo.dataset.stormcloudAdPlaying = "true";
1183
+ } else {
1184
+ delete contentVideo.dataset.stormcloudAdPlaying;
1185
+ }
1186
+ }
1123
1187
  function handleAdComplete() {
1124
1188
  console.log("[HlsAdPlayer] Handling ad completion");
1125
1189
  adPlaying = false;
1190
+ setAdPlayingFlag(false);
1191
+ const previousMutedState = contentVideo.muted;
1126
1192
  contentVideo.muted = originalMutedState;
1193
+ console.log(
1194
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1195
+ );
1127
1196
  if (adContainerEl) {
1128
1197
  adContainerEl.style.display = "none";
1129
1198
  adContainerEl.style.pointerEvents = "none";
@@ -1141,7 +1210,12 @@ function createHlsAdPlayer(contentVideo, options) {
1141
1210
  function handleAdError() {
1142
1211
  console.log("[HlsAdPlayer] Handling ad error");
1143
1212
  adPlaying = false;
1213
+ setAdPlayingFlag(false);
1214
+ const previousMutedState = contentVideo.muted;
1144
1215
  contentVideo.muted = originalMutedState;
1216
+ console.log(
1217
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1218
+ );
1145
1219
  if (adContainerEl) {
1146
1220
  adContainerEl.style.display = "none";
1147
1221
  adContainerEl.style.pointerEvents = "none";
@@ -1178,7 +1252,9 @@ function createHlsAdPlayer(contentVideo, options) {
1178
1252
  async requestAds(vastTagUrl) {
1179
1253
  console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
1180
1254
  if (adPlaying) {
1181
- 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
+ );
1182
1258
  return Promise.reject(new Error("Ad already playing"));
1183
1259
  }
1184
1260
  try {
@@ -1189,14 +1265,20 @@ function createHlsAdPlayer(contentVideo, options) {
1189
1265
  }
1190
1266
  const vastXml = await response.text();
1191
1267
  console.log("[HlsAdPlayer] VAST XML received");
1192
- 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
+ );
1193
1272
  const ad = parseVastXml(vastXml);
1194
1273
  if (!ad) {
1195
1274
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1275
+ emit("ad_error");
1196
1276
  return Promise.resolve();
1197
1277
  }
1198
1278
  currentAd = ad;
1199
- 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
+ );
1200
1282
  fireTrackingPixels(ad.trackingUrls.impression);
1201
1283
  trackingFired.impression = true;
1202
1284
  return Promise.resolve();
@@ -1208,7 +1290,9 @@ function createHlsAdPlayer(contentVideo, options) {
1208
1290
  },
1209
1291
  async play() {
1210
1292
  if (!currentAd) {
1211
- 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
+ );
1212
1296
  return Promise.reject(new Error("No ad loaded"));
1213
1297
  }
1214
1298
  console.log("[HlsAdPlayer] Starting ad playback");
@@ -1226,6 +1310,7 @@ function createHlsAdPlayer(contentVideo, options) {
1226
1310
  thirdQuartile: false,
1227
1311
  complete: false
1228
1312
  };
1313
+ const contentVolume = contentVideo.volume;
1229
1314
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
1230
1315
  contentVideo.pause();
1231
1316
  console.log("[HlsAdPlayer] Content paused (VOD mode)");
@@ -1234,6 +1319,15 @@ function createHlsAdPlayer(contentVideo, options) {
1234
1319
  }
1235
1320
  contentVideo.muted = true;
1236
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
+ }
1237
1331
  if (adContainerEl) {
1238
1332
  adContainerEl.style.display = "flex";
1239
1333
  adContainerEl.style.pointerEvents = "auto";
@@ -1286,6 +1380,7 @@ function createHlsAdPlayer(contentVideo, options) {
1286
1380
  async stop() {
1287
1381
  console.log("[HlsAdPlayer] Stopping ad");
1288
1382
  adPlaying = false;
1383
+ setAdPlayingFlag(false);
1289
1384
  contentVideo.muted = originalMutedState;
1290
1385
  if (adContainerEl) {
1291
1386
  adContainerEl.style.display = "none";
@@ -1308,6 +1403,7 @@ function createHlsAdPlayer(contentVideo, options) {
1308
1403
  destroy() {
1309
1404
  console.log("[HlsAdPlayer] Destroying");
1310
1405
  adPlaying = false;
1406
+ setAdPlayingFlag(false);
1311
1407
  contentVideo.muted = originalMutedState;
1312
1408
  if (adHls) {
1313
1409
  adHls.destroy();
@@ -1349,6 +1445,9 @@ function createHlsAdPlayer(contentVideo, options) {
1349
1445
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
1350
1446
  },
1351
1447
  updateOriginalMutedState(muted) {
1448
+ console.log(
1449
+ `[HlsAdPlayer] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
1450
+ );
1352
1451
  originalMutedState = muted;
1353
1452
  },
1354
1453
  getOriginalMutedState() {
@@ -1841,6 +1940,9 @@ var StormcloudVideoPlayer = class {
1841
1940
  this.isLiveStream = false;
1842
1941
  this.nativeHlsMode = false;
1843
1942
  this.videoSrcProtection = null;
1943
+ this.bufferedSegmentsCount = 0;
1944
+ this.shouldAutoplayAfterBuffering = false;
1945
+ this.hasInitialBufferCompleted = false;
1844
1946
  initializePolyfills();
1845
1947
  const browserOverrides = getBrowserConfigOverrides();
1846
1948
  this.config = { ...config, ...browserOverrides };
@@ -1927,14 +2029,22 @@ var StormcloudVideoPlayer = class {
1927
2029
  liveDurationInfinity: true,
1928
2030
  lowLatencyMode: !!this.config.lowLatencyMode,
1929
2031
  maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
1930
- ...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
1931
2041
  });
1932
2042
  this.hls.on(import_hls2.default.Events.MEDIA_ATTACHED, () => {
1933
2043
  var _a2;
1934
2044
  (_a2 = this.hls) == null ? void 0 : _a2.loadSource(this.config.src);
1935
2045
  });
1936
2046
  this.hls.on(import_hls2.default.Events.MANIFEST_PARSED, async (_, data) => {
1937
- var _a2, _b2, _c, _d;
2047
+ var _a2, _b2, _c, _d, _e;
1938
2048
  this.isLiveStream = (_c = (_b2 = (_a2 = this.hls) == null ? void 0 : _a2.levels) == null ? void 0 : _b2.some(
1939
2049
  (level) => {
1940
2050
  var _a3, _b3;
@@ -1952,9 +2062,51 @@ var StormcloudVideoPlayer = class {
1952
2062
  this.ima.destroy();
1953
2063
  this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
1954
2064
  this.ima.initialize();
1955
- if (this.config.autoplay) {
1956
- await ((_d = this.video.play()) == null ? void 0 : _d.catch(() => {
1957
- }));
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
+ }
1958
2110
  }
1959
2111
  });
1960
2112
  this.hls.on(import_hls2.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
@@ -2050,6 +2202,7 @@ var StormcloudVideoPlayer = class {
2050
2202
  this.video.autoplay = !!this.config.autoplay;
2051
2203
  this.video.muted = !!this.config.muted;
2052
2204
  this.ima.initialize();
2205
+ this.ima.updateOriginalMutedState(this.video.muted);
2053
2206
  this.ima.on("all_ads_completed", () => {
2054
2207
  if (this.config.debugAdTiming) {
2055
2208
  console.log(
@@ -2834,7 +2987,23 @@ var StormcloudVideoPlayer = class {
2834
2987
  }
2835
2988
  return;
2836
2989
  }
2837
- 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
+ }
2838
3007
  this.startAdFailsafeTimer();
2839
3008
  try {
2840
3009
  await this.ima.requestAds(vastTagUrl);
@@ -2889,11 +3058,12 @@ var StormcloudVideoPlayer = class {
2889
3058
  this.showAds = false;
2890
3059
  this.currentAdIndex = 0;
2891
3060
  this.totalAdsInBreak = 0;
3061
+ const currentMutedState = this.video.muted;
2892
3062
  const originalMutedState = this.ima.getOriginalMutedState();
2893
3063
  this.video.muted = originalMutedState;
2894
3064
  if (this.config.debugAdTiming) {
2895
3065
  console.log(
2896
- `[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
3066
+ `[StormcloudVideoPlayer] Restored mute state: ${currentMutedState} -> ${originalMutedState}`
2897
3067
  );
2898
3068
  }
2899
3069
  if (this.video.paused) {
@@ -2985,6 +3155,7 @@ var StormcloudVideoPlayer = class {
2985
3155
  }
2986
3156
  } else {
2987
3157
  this.video.muted = !this.video.muted;
3158
+ this.ima.updateOriginalMutedState(this.video.muted);
2988
3159
  if (this.config.debugAdTiming) {
2989
3160
  console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
2990
3161
  }
@@ -3029,11 +3200,47 @@ var StormcloudVideoPlayer = class {
3029
3200
  }
3030
3201
  isMuted() {
3031
3202
  if (this.ima.isAdPlaying()) {
3032
- const adVolume = this.ima.getAdVolume();
3033
- 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
+ );
3034
3214
  }
3035
3215
  return this.video.muted;
3036
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
+ }
3037
3244
  isFullscreen() {
3038
3245
  return !!document.fullscreenElement;
3039
3246
  }
@@ -3112,10 +3319,12 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3112
3319
  vastMode,
3113
3320
  vastTagUrl,
3114
3321
  adPlayerType,
3322
+ minSegmentsBeforePlay,
3115
3323
  ...restVideoAttrs
3116
3324
  } = props;
3117
3325
  const videoRef = (0, import_react.useRef)(null);
3118
3326
  const playerRef = (0, import_react.useRef)(null);
3327
+ const bufferingTimeoutRef = (0, import_react.useRef)(null);
3119
3328
  const [adStatus, setAdStatus] = import_react.default.useState({ showAds: false, currentIndex: 0, totalAds: 0 });
3120
3329
  const [shouldShowNativeControls, setShouldShowNativeControls] = import_react.default.useState(true);
3121
3330
  const [isMuted, setIsMuted] = import_react.default.useState(false);
@@ -3237,6 +3446,9 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3237
3446
  return;
3238
3447
  }
3239
3448
  setShowLicenseWarning(false);
3449
+ if (debugAdTiming) {
3450
+ console.log("[StormcloudUI] Initializing player, isLoading=true");
3451
+ }
3240
3452
  if (playerRef.current) {
3241
3453
  try {
3242
3454
  playerRef.current.destroy();
@@ -3267,17 +3479,25 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3267
3479
  if (vastMode !== void 0) cfg.vastMode = vastMode;
3268
3480
  if (vastTagUrl !== void 0) cfg.vastTagUrl = vastTagUrl;
3269
3481
  if (adPlayerType !== void 0) cfg.adPlayerType = adPlayerType;
3482
+ if (minSegmentsBeforePlay !== void 0)
3483
+ cfg.minSegmentsBeforePlay = minSegmentsBeforePlay;
3270
3484
  const player = new StormcloudVideoPlayer(cfg);
3271
3485
  playerRef.current = player;
3272
3486
  player.load().then(() => {
3273
3487
  const showNative = player.shouldShowNativeControls();
3274
3488
  setShouldShowNativeControls(showNative);
3489
+ if (debugAdTiming) {
3490
+ console.log(
3491
+ "[StormcloudUI] Player loaded successfully, waiting for video ready"
3492
+ );
3493
+ }
3275
3494
  onReady == null ? void 0 : onReady(player);
3276
3495
  }).catch((error) => {
3277
3496
  console.error(
3278
3497
  "StormcloudVideoPlayer: Failed to load player:",
3279
3498
  error
3280
3499
  );
3500
+ setIsLoading(false);
3281
3501
  onReady == null ? void 0 : onReady(player);
3282
3502
  });
3283
3503
  return () => {
@@ -3294,8 +3514,8 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3294
3514
  if (autoplay !== void 0 && playerRef.current.videoElement) {
3295
3515
  playerRef.current.videoElement.autoplay = autoplay;
3296
3516
  }
3297
- if (muted !== void 0 && playerRef.current.videoElement) {
3298
- playerRef.current.videoElement.muted = muted;
3517
+ if (muted !== void 0) {
3518
+ playerRef.current.setMuted(muted);
3299
3519
  }
3300
3520
  } catch (error) {
3301
3521
  console.warn("Failed to update player properties:", error);
@@ -3376,26 +3596,108 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3376
3596
  (0, import_react.useEffect)(() => {
3377
3597
  if (!videoRef.current) return;
3378
3598
  const handleLoadedMetadata = () => {
3599
+ var _a;
3379
3600
  if (videoRef.current) {
3380
3601
  const video2 = videoRef.current;
3381
3602
  void video2.offsetHeight;
3382
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
+ }
3383
3619
  };
3384
3620
  const handleLoadStart = () => {
3385
- setIsLoading(true);
3386
- 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
+ }
3387
3628
  };
3388
3629
  const handleCanPlay = () => {
3630
+ var _a;
3389
3631
  setIsLoading(false);
3632
+ if (bufferingTimeoutRef.current) {
3633
+ clearTimeout(bufferingTimeoutRef.current);
3634
+ bufferingTimeoutRef.current = null;
3635
+ }
3390
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;
3647
+ setIsLoading(false);
3648
+ if (bufferingTimeoutRef.current) {
3649
+ clearTimeout(bufferingTimeoutRef.current);
3650
+ bufferingTimeoutRef.current = null;
3651
+ }
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
+ }
3391
3660
  };
3392
3661
  const handleWaiting = () => {
3393
- 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
+ }
3394
3684
  };
3395
3685
  const handlePlaying = () => {
3686
+ var _a;
3396
3687
  setIsLoading(false);
3688
+ if (bufferingTimeoutRef.current) {
3689
+ clearTimeout(bufferingTimeoutRef.current);
3690
+ bufferingTimeoutRef.current = null;
3691
+ }
3397
3692
  setIsBuffering(false);
3398
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
+ }
3399
3701
  };
3400
3702
  const handlePause = () => {
3401
3703
  if (playerRef.current && !playerRef.current.isShowingAds()) {
@@ -3410,8 +3712,9 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3410
3712
  const video = videoRef.current;
3411
3713
  video.addEventListener("loadstart", handleLoadStart);
3412
3714
  video.addEventListener("loadedmetadata", handleLoadedMetadata);
3413
- video.addEventListener("loadeddata", handleLoadedMetadata);
3715
+ video.addEventListener("loadeddata", handleLoadedData);
3414
3716
  video.addEventListener("canplay", handleCanPlay);
3717
+ video.addEventListener("canplaythrough", handleCanPlayThrough);
3415
3718
  video.addEventListener("waiting", handleWaiting);
3416
3719
  video.addEventListener("playing", handlePlaying);
3417
3720
  video.addEventListener("pause", handlePause);
@@ -3420,16 +3723,21 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3420
3723
  setShowCenterPlay(true);
3421
3724
  }
3422
3725
  return () => {
3726
+ if (bufferingTimeoutRef.current) {
3727
+ clearTimeout(bufferingTimeoutRef.current);
3728
+ bufferingTimeoutRef.current = null;
3729
+ }
3423
3730
  video.removeEventListener("loadstart", handleLoadStart);
3424
3731
  video.removeEventListener("loadedmetadata", handleLoadedMetadata);
3425
- video.removeEventListener("loadeddata", handleLoadedMetadata);
3732
+ video.removeEventListener("loadeddata", handleLoadedData);
3426
3733
  video.removeEventListener("canplay", handleCanPlay);
3734
+ video.removeEventListener("canplaythrough", handleCanPlayThrough);
3427
3735
  video.removeEventListener("waiting", handleWaiting);
3428
3736
  video.removeEventListener("playing", handlePlaying);
3429
3737
  video.removeEventListener("pause", handlePause);
3430
3738
  video.removeEventListener("ended", handleEnded);
3431
3739
  };
3432
- }, []);
3740
+ }, [debugAdTiming]);
3433
3741
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
3434
3742
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
3435
3743
  @keyframes spin {
@@ -3536,35 +3844,18 @@ var StormcloudVideoPlayerComponent = import_react.default.memo(
3536
3844
  }
3537
3845
  ),
3538
3846
  (isLoading || isBuffering) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3539
- "div",
3847
+ import_fa.FaSpinner,
3540
3848
  {
3849
+ size: 42,
3850
+ color: "white",
3541
3851
  style: {
3542
3852
  position: "absolute",
3543
- top: "50%",
3544
- left: "50%",
3545
- transform: "translate(-50%, -50%)",
3853
+ top: "calc(50% - 21px)",
3854
+ left: "calc(50% - 21px)",
3546
3855
  zIndex: 20,
3547
- display: "flex",
3548
- alignItems: "center",
3549
- justifyContent: "center",
3550
- background: "linear-gradient(135deg, rgba(0, 0, 0, 0.8) 0%, rgba(20, 20, 20, 0.6) 100%)",
3551
- width: "80px",
3552
- height: "80px",
3553
- borderRadius: "50%",
3554
- backdropFilter: "blur(20px)",
3555
- boxShadow: "0 12px 40px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 255, 255, 0.1)"
3556
- },
3557
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3558
- import_fa.FaSpinner,
3559
- {
3560
- size: 28,
3561
- color: "white",
3562
- style: {
3563
- animation: "spin 1s linear infinite",
3564
- filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3565
- }
3566
- }
3567
- )
3856
+ animation: "spin 1s linear infinite",
3857
+ filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3858
+ }
3568
3859
  }
3569
3860
  ),
3570
3861
  showLicenseWarning && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -4677,6 +4968,7 @@ var defaultProps = {
4677
4968
  showCustomControls: false,
4678
4969
  licenseKey: "",
4679
4970
  adFailsafeTimeoutMs: 1e4,
4971
+ minSegmentsBeforePlay: 2,
4680
4972
  onStart: noop,
4681
4973
  onPlay: noop,
4682
4974
  onPause: noop,
@@ -4850,6 +5142,8 @@ var HlsPlayer = class extends import_react3.Component {
4850
5142
  config.licenseKey = this.props.licenseKey;
4851
5143
  if (this.props.adFailsafeTimeoutMs !== void 0)
4852
5144
  config.adFailsafeTimeoutMs = this.props.adFailsafeTimeoutMs;
5145
+ if (this.props.minSegmentsBeforePlay !== void 0)
5146
+ config.minSegmentsBeforePlay = this.props.minSegmentsBeforePlay;
4853
5147
  this.player = new StormcloudVideoPlayer(config);
4854
5148
  (_b = (_a = this.props).onMount) == null ? void 0 : _b.call(_a, this);
4855
5149
  await this.player.load();
@@ -5456,6 +5750,7 @@ var SUPPORTED_PROPS = [
5456
5750
  "showCustomControls",
5457
5751
  "licenseKey",
5458
5752
  "adFailsafeTimeoutMs",
5753
+ "minSegmentsBeforePlay",
5459
5754
  "onReady",
5460
5755
  "onStart",
5461
5756
  "onPlay",