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.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;
@@ -282,6 +289,14 @@ function createImaController(video, options) {
282
289
  function makeAdsRequest(google, vastTagUrl) {
283
290
  const adsRequest = new google.ima.AdsRequest();
284
291
  adsRequest.adTagUrl = vastTagUrl;
292
+ const videoWidth = video.offsetWidth || video.clientWidth || 640;
293
+ const videoHeight = video.offsetHeight || video.clientHeight || 360;
294
+ adsRequest.linearAdSlotWidth = videoWidth;
295
+ adsRequest.linearAdSlotHeight = videoHeight;
296
+ adsRequest.nonLinearAdSlotWidth = videoWidth;
297
+ adsRequest.nonLinearAdSlotHeight = videoHeight;
298
+ adsRequest.vastLoadTimeout = 5e3;
299
+ console.log(`[IMA] Ads request dimensions: ${videoWidth}x${videoHeight}`);
285
300
  adsLoader.requestAds(adsRequest);
286
301
  }
287
302
  function destroyAdsManager() {
@@ -328,6 +343,18 @@ function createImaController(video, options) {
328
343
  },
329
344
  async requestAds(vastTagUrl) {
330
345
  console.log("[IMA] Requesting ads:", vastTagUrl);
346
+ if (!vastTagUrl || vastTagUrl.trim() === "") {
347
+ const error = new Error("VAST tag URL is empty or undefined");
348
+ console.warn("[IMA]", error.message);
349
+ return Promise.reject(error);
350
+ }
351
+ try {
352
+ new URL(vastTagUrl);
353
+ } catch (e) {
354
+ const error = new Error(`Invalid VAST tag URL format: ${vastTagUrl}`);
355
+ console.warn("[IMA]", error.message);
356
+ return Promise.reject(error);
357
+ }
331
358
  if (adPlaying) {
332
359
  console.warn(
333
360
  "[IMA] Cannot request new ads while an ad is playing. Call stop() first."
@@ -389,6 +416,18 @@ function createImaController(video, options) {
389
416
  );
390
417
  }
391
418
  }
419
+ const videoWidth = video.offsetWidth || video.clientWidth;
420
+ const videoHeight = video.offsetHeight || video.clientHeight;
421
+ if (!videoWidth || !videoHeight || videoWidth === 0 || videoHeight === 0) {
422
+ const error = new Error(
423
+ `Invalid video dimensions: ${videoWidth}x${videoHeight}. Cannot initialize ads.`
424
+ );
425
+ console.warn("[IMA]", error.message);
426
+ currentReject == null ? void 0 : currentReject(error);
427
+ adsLoadedReject = void 0;
428
+ adsLoadedResolve = void 0;
429
+ return Promise.reject(error);
430
+ }
392
431
  if (!adsLoader) {
393
432
  console.log("[IMA] Creating ads loader");
394
433
  const adsLoaderCls = new google.ima.AdsLoader(adDisplayContainer);
@@ -408,7 +447,12 @@ function createImaController(video, options) {
408
447
  console.error("[IMA] Ad error:", errorEvent.getError());
409
448
  destroyAdsManager();
410
449
  adPlaying = false;
450
+ const previousMutedState = video.muted;
411
451
  video.muted = originalMutedState;
452
+ setAdPlayingFlag(false);
453
+ console.log(
454
+ `[IMA] Restored mute state after ad error: ${previousMutedState} -> ${originalMutedState}`
455
+ );
412
456
  if (adContainerEl) {
413
457
  adContainerEl.style.pointerEvents = "none";
414
458
  adContainerEl.style.display = "none";
@@ -437,7 +481,9 @@ function createImaController(video, options) {
437
481
  emit("ad_error");
438
482
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
439
483
  if (video.paused) {
440
- console.log("[IMA] Resuming paused video after ad error");
484
+ console.log(
485
+ "[IMA] Resuming paused video after ad error"
486
+ );
441
487
  (_a = video.play()) == null ? void 0 : _a.catch(() => {
442
488
  });
443
489
  }
@@ -459,11 +505,13 @@ function createImaController(video, options) {
459
505
  }
460
506
  video.muted = true;
461
507
  adPlaying = true;
508
+ setAdPlayingFlag(true);
462
509
  emit("content_pause");
463
510
  }
464
511
  );
465
512
  adsManager.addEventListener(AdEvent.STARTED, () => {
466
513
  console.log("[IMA] Ad started playing");
514
+ setAdPlayingFlag(true);
467
515
  if (adContainerEl) {
468
516
  adContainerEl.style.pointerEvents = "auto";
469
517
  adContainerEl.style.display = "flex";
@@ -479,6 +527,7 @@ function createImaController(video, options) {
479
527
  console.log("[IMA] Content resume requested");
480
528
  adPlaying = false;
481
529
  video.muted = originalMutedState;
530
+ setAdPlayingFlag(false);
482
531
  if (adContainerEl) {
483
532
  adContainerEl.style.pointerEvents = "none";
484
533
  adContainerEl.style.display = "none";
@@ -502,6 +551,7 @@ function createImaController(video, options) {
502
551
  console.log("[IMA] All ads completed");
503
552
  adPlaying = false;
504
553
  video.muted = originalMutedState;
554
+ setAdPlayingFlag(false);
505
555
  if (adContainerEl) {
506
556
  adContainerEl.style.pointerEvents = "none";
507
557
  adContainerEl.style.display = "none";
@@ -532,6 +582,7 @@ function createImaController(video, options) {
532
582
  console.error("[IMA] Error setting up ads manager:", e);
533
583
  adPlaying = false;
534
584
  video.muted = originalMutedState;
585
+ setAdPlayingFlag(false);
535
586
  if (adContainerEl) {
536
587
  adContainerEl.style.pointerEvents = "none";
537
588
  adContainerEl.style.display = "none";
@@ -539,7 +590,9 @@ function createImaController(video, options) {
539
590
  }
540
591
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
541
592
  if (video.paused) {
542
- console.log("[IMA] Resuming paused video after setup error");
593
+ console.log(
594
+ "[IMA] Resuming paused video after setup error"
595
+ );
543
596
  video.play().catch(() => {
544
597
  });
545
598
  }
@@ -559,7 +612,12 @@ function createImaController(video, options) {
559
612
  (adErrorEvent) => {
560
613
  console.error("[IMA] Ads loader error:", adErrorEvent.getError());
561
614
  adPlaying = false;
615
+ const previousMutedState = video.muted;
562
616
  video.muted = originalMutedState;
617
+ setAdPlayingFlag(false);
618
+ console.log(
619
+ `[IMA] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
620
+ );
563
621
  if (adContainerEl) {
564
622
  adContainerEl.style.pointerEvents = "none";
565
623
  adContainerEl.style.display = "none";
@@ -611,12 +669,20 @@ function createImaController(video, options) {
611
669
  console.log(`[IMA] Initializing ads manager (${width}x${height})`);
612
670
  adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
613
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
+ }
614
679
  console.log("[IMA] Starting ad playback");
615
680
  adsManager.start();
616
681
  return Promise.resolve();
617
682
  } catch (error) {
618
683
  console.error("[IMA] Error starting ad playback:", error);
619
684
  adPlaying = false;
685
+ setAdPlayingFlag(false);
620
686
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
621
687
  (_b = video.play()) == null ? void 0 : _b.catch(() => {
622
688
  });
@@ -628,6 +694,7 @@ function createImaController(video, options) {
628
694
  var _a;
629
695
  adPlaying = false;
630
696
  video.muted = originalMutedState;
697
+ setAdPlayingFlag(false);
631
698
  if (adContainerEl) {
632
699
  adContainerEl.style.pointerEvents = "none";
633
700
  adContainerEl.style.display = "none";
@@ -651,6 +718,7 @@ function createImaController(video, options) {
651
718
  destroyAdsManager();
652
719
  adPlaying = false;
653
720
  video.muted = originalMutedState;
721
+ setAdPlayingFlag(false);
654
722
  if (adContainerEl) {
655
723
  adContainerEl.style.pointerEvents = "none";
656
724
  adContainerEl.style.display = "none";
@@ -693,6 +761,9 @@ function createImaController(video, options) {
693
761
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
694
762
  },
695
763
  updateOriginalMutedState(muted) {
764
+ console.log(
765
+ `[IMA] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
766
+ );
696
767
  originalMutedState = muted;
697
768
  },
698
769
  getOriginalMutedState() {
@@ -749,7 +820,10 @@ function createHlsAdPlayer(contentVideo, options) {
749
820
  try {
750
821
  fn(payload);
751
822
  } catch (error) {
752
- console.warn(`[HlsAdPlayer] Error in event listener for ${event}:`, error);
823
+ console.warn(
824
+ `[HlsAdPlayer] Error in event listener for ${event}:`,
825
+ error
826
+ );
753
827
  }
754
828
  }
755
829
  }
@@ -812,7 +886,9 @@ function createHlsAdPlayer(contentVideo, options) {
812
886
  }
813
887
  const mainQuality = getMainStreamQuality();
814
888
  if (!mainQuality) {
815
- 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
+ );
816
892
  return firstFile;
817
893
  }
818
894
  console.log("[HlsAdPlayer] Main stream quality:", mainQuality);
@@ -848,7 +924,10 @@ function createHlsAdPlayer(contentVideo, options) {
848
924
  const xmlDoc = parser.parseFromString(xmlString, "text/xml");
849
925
  const parserError = xmlDoc.querySelector("parsererror");
850
926
  if (parserError) {
851
- 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
+ );
852
931
  return null;
853
932
  }
854
933
  const adElement = xmlDoc.querySelector("Ad");
@@ -864,17 +943,23 @@ function createHlsAdPlayer(contentVideo, options) {
864
943
  const duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
865
944
  const mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
866
945
  const mediaFiles = [];
867
- 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
+ );
868
949
  mediaFileElements.forEach((mf, index) => {
869
950
  var _a2;
870
951
  const type = mf.getAttribute("type") || "";
871
952
  const url = ((_a2 = mf.textContent) == null ? void 0 : _a2.trim()) || "";
872
953
  const width = mf.getAttribute("width") || "";
873
954
  const height = mf.getAttribute("height") || "";
874
- 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
+ );
875
958
  if (type === "application/x-mpegURL" || type.includes("m3u8")) {
876
959
  if (!url) {
877
- 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
+ );
878
963
  return;
879
964
  }
880
965
  const bitrateAttr = mf.getAttribute("bitrate");
@@ -888,12 +973,16 @@ function createHlsAdPlayer(contentVideo, options) {
888
973
  });
889
974
  console.log(`[HlsAdPlayer] Added HLS MediaFile: ${url}`);
890
975
  } else {
891
- 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
+ );
892
979
  }
893
980
  });
894
981
  if (mediaFiles.length === 0) {
895
982
  if (isNoAdAvailable) {
896
- 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
+ );
897
986
  } else {
898
987
  console.warn("[HlsAdPlayer] No HLS media files found in VAST XML");
899
988
  }
@@ -956,6 +1045,10 @@ function createHlsAdPlayer(contentVideo, options) {
956
1045
  video.style.backgroundColor = "#000";
957
1046
  video.playsInline = true;
958
1047
  video.muted = false;
1048
+ video.volume = 1;
1049
+ console.log(
1050
+ `[HlsAdPlayer] Created ad video element with volume ${video.volume}`
1051
+ );
959
1052
  return video;
960
1053
  }
961
1054
  function setupAdEventListeners() {
@@ -1015,10 +1108,22 @@ function createHlsAdPlayer(contentVideo, options) {
1015
1108
  }
1016
1109
  });
1017
1110
  }
1111
+ function setAdPlayingFlag(isPlaying) {
1112
+ if (isPlaying) {
1113
+ contentVideo.dataset.stormcloudAdPlaying = "true";
1114
+ } else {
1115
+ delete contentVideo.dataset.stormcloudAdPlaying;
1116
+ }
1117
+ }
1018
1118
  function handleAdComplete() {
1019
1119
  console.log("[HlsAdPlayer] Handling ad completion");
1020
1120
  adPlaying = false;
1121
+ setAdPlayingFlag(false);
1122
+ const previousMutedState = contentVideo.muted;
1021
1123
  contentVideo.muted = originalMutedState;
1124
+ console.log(
1125
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1126
+ );
1022
1127
  if (adContainerEl) {
1023
1128
  adContainerEl.style.display = "none";
1024
1129
  adContainerEl.style.pointerEvents = "none";
@@ -1036,7 +1141,12 @@ function createHlsAdPlayer(contentVideo, options) {
1036
1141
  function handleAdError() {
1037
1142
  console.log("[HlsAdPlayer] Handling ad error");
1038
1143
  adPlaying = false;
1144
+ setAdPlayingFlag(false);
1145
+ const previousMutedState = contentVideo.muted;
1039
1146
  contentVideo.muted = originalMutedState;
1147
+ console.log(
1148
+ `[HlsAdPlayer] Restored mute state: ${previousMutedState} -> ${originalMutedState}`
1149
+ );
1040
1150
  if (adContainerEl) {
1041
1151
  adContainerEl.style.display = "none";
1042
1152
  adContainerEl.style.pointerEvents = "none";
@@ -1073,7 +1183,9 @@ function createHlsAdPlayer(contentVideo, options) {
1073
1183
  async requestAds(vastTagUrl) {
1074
1184
  console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
1075
1185
  if (adPlaying) {
1076
- 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
+ );
1077
1189
  return Promise.reject(new Error("Ad already playing"));
1078
1190
  }
1079
1191
  try {
@@ -1084,14 +1196,20 @@ function createHlsAdPlayer(contentVideo, options) {
1084
1196
  }
1085
1197
  const vastXml = await response.text();
1086
1198
  console.log("[HlsAdPlayer] VAST XML received");
1087
- 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
+ );
1088
1203
  const ad = parseVastXml(vastXml);
1089
1204
  if (!ad) {
1090
1205
  console.warn("[HlsAdPlayer] No ads available from VAST response");
1206
+ emit("ad_error");
1091
1207
  return Promise.resolve();
1092
1208
  }
1093
1209
  currentAd = ad;
1094
- 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
+ );
1095
1213
  fireTrackingPixels(ad.trackingUrls.impression);
1096
1214
  trackingFired.impression = true;
1097
1215
  return Promise.resolve();
@@ -1103,7 +1221,9 @@ function createHlsAdPlayer(contentVideo, options) {
1103
1221
  },
1104
1222
  async play() {
1105
1223
  if (!currentAd) {
1106
- 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
+ );
1107
1227
  return Promise.reject(new Error("No ad loaded"));
1108
1228
  }
1109
1229
  console.log("[HlsAdPlayer] Starting ad playback");
@@ -1121,6 +1241,7 @@ function createHlsAdPlayer(contentVideo, options) {
1121
1241
  thirdQuartile: false,
1122
1242
  complete: false
1123
1243
  };
1244
+ const contentVolume = contentVideo.volume;
1124
1245
  if (!(options == null ? void 0 : options.continueLiveStreamDuringAds)) {
1125
1246
  contentVideo.pause();
1126
1247
  console.log("[HlsAdPlayer] Content paused (VOD mode)");
@@ -1129,6 +1250,15 @@ function createHlsAdPlayer(contentVideo, options) {
1129
1250
  }
1130
1251
  contentVideo.muted = true;
1131
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
+ }
1132
1262
  if (adContainerEl) {
1133
1263
  adContainerEl.style.display = "flex";
1134
1264
  adContainerEl.style.pointerEvents = "auto";
@@ -1181,6 +1311,7 @@ function createHlsAdPlayer(contentVideo, options) {
1181
1311
  async stop() {
1182
1312
  console.log("[HlsAdPlayer] Stopping ad");
1183
1313
  adPlaying = false;
1314
+ setAdPlayingFlag(false);
1184
1315
  contentVideo.muted = originalMutedState;
1185
1316
  if (adContainerEl) {
1186
1317
  adContainerEl.style.display = "none";
@@ -1203,6 +1334,7 @@ function createHlsAdPlayer(contentVideo, options) {
1203
1334
  destroy() {
1204
1335
  console.log("[HlsAdPlayer] Destroying");
1205
1336
  adPlaying = false;
1337
+ setAdPlayingFlag(false);
1206
1338
  contentVideo.muted = originalMutedState;
1207
1339
  if (adHls) {
1208
1340
  adHls.destroy();
@@ -1244,6 +1376,9 @@ function createHlsAdPlayer(contentVideo, options) {
1244
1376
  (_a = listeners.get(event)) == null ? void 0 : _a.delete(listener);
1245
1377
  },
1246
1378
  updateOriginalMutedState(muted) {
1379
+ console.log(
1380
+ `[HlsAdPlayer] updateOriginalMutedState called: ${originalMutedState} -> ${muted}`
1381
+ );
1247
1382
  originalMutedState = muted;
1248
1383
  },
1249
1384
  getOriginalMutedState() {
@@ -1736,6 +1871,9 @@ var StormcloudVideoPlayer = class {
1736
1871
  this.isLiveStream = false;
1737
1872
  this.nativeHlsMode = false;
1738
1873
  this.videoSrcProtection = null;
1874
+ this.bufferedSegmentsCount = 0;
1875
+ this.shouldAutoplayAfterBuffering = false;
1876
+ this.hasInitialBufferCompleted = false;
1739
1877
  initializePolyfills();
1740
1878
  const browserOverrides = getBrowserConfigOverrides();
1741
1879
  this.config = { ...config, ...browserOverrides };
@@ -1822,14 +1960,22 @@ var StormcloudVideoPlayer = class {
1822
1960
  liveDurationInfinity: true,
1823
1961
  lowLatencyMode: !!this.config.lowLatencyMode,
1824
1962
  maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
1825
- ...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
1826
1972
  });
1827
1973
  this.hls.on(Hls2.Events.MEDIA_ATTACHED, () => {
1828
1974
  var _a2;
1829
1975
  (_a2 = this.hls) == null ? void 0 : _a2.loadSource(this.config.src);
1830
1976
  });
1831
1977
  this.hls.on(Hls2.Events.MANIFEST_PARSED, async (_, data) => {
1832
- var _a2, _b2, _c, _d;
1978
+ var _a2, _b2, _c, _d, _e;
1833
1979
  this.isLiveStream = (_c = (_b2 = (_a2 = this.hls) == null ? void 0 : _a2.levels) == null ? void 0 : _b2.some(
1834
1980
  (level) => {
1835
1981
  var _a3, _b3;
@@ -1847,9 +1993,51 @@ var StormcloudVideoPlayer = class {
1847
1993
  this.ima.destroy();
1848
1994
  this.ima = this.createAdPlayer(this.shouldContinueLiveStreamDuringAds());
1849
1995
  this.ima.initialize();
1850
- if (this.config.autoplay) {
1851
- await ((_d = this.video.play()) == null ? void 0 : _d.catch(() => {
1852
- }));
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
+ }
1853
2041
  }
1854
2042
  });
1855
2043
  this.hls.on(Hls2.Events.FRAG_PARSING_METADATA, (_evt, data) => {
@@ -1945,6 +2133,7 @@ var StormcloudVideoPlayer = class {
1945
2133
  this.video.autoplay = !!this.config.autoplay;
1946
2134
  this.video.muted = !!this.config.muted;
1947
2135
  this.ima.initialize();
2136
+ this.ima.updateOriginalMutedState(this.video.muted);
1948
2137
  this.ima.on("all_ads_completed", () => {
1949
2138
  if (this.config.debugAdTiming) {
1950
2139
  console.log(
@@ -2729,7 +2918,23 @@ var StormcloudVideoPlayer = class {
2729
2918
  }
2730
2919
  return;
2731
2920
  }
2732
- 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
+ }
2733
2938
  this.startAdFailsafeTimer();
2734
2939
  try {
2735
2940
  await this.ima.requestAds(vastTagUrl);
@@ -2784,11 +2989,12 @@ var StormcloudVideoPlayer = class {
2784
2989
  this.showAds = false;
2785
2990
  this.currentAdIndex = 0;
2786
2991
  this.totalAdsInBreak = 0;
2992
+ const currentMutedState = this.video.muted;
2787
2993
  const originalMutedState = this.ima.getOriginalMutedState();
2788
2994
  this.video.muted = originalMutedState;
2789
2995
  if (this.config.debugAdTiming) {
2790
2996
  console.log(
2791
- `[StormcloudVideoPlayer] Restored mute state to: ${originalMutedState}`
2997
+ `[StormcloudVideoPlayer] Restored mute state: ${currentMutedState} -> ${originalMutedState}`
2792
2998
  );
2793
2999
  }
2794
3000
  if (this.video.paused) {
@@ -2880,6 +3086,7 @@ var StormcloudVideoPlayer = class {
2880
3086
  }
2881
3087
  } else {
2882
3088
  this.video.muted = !this.video.muted;
3089
+ this.ima.updateOriginalMutedState(this.video.muted);
2883
3090
  if (this.config.debugAdTiming) {
2884
3091
  console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
2885
3092
  }
@@ -2924,11 +3131,47 @@ var StormcloudVideoPlayer = class {
2924
3131
  }
2925
3132
  isMuted() {
2926
3133
  if (this.ima.isAdPlaying()) {
2927
- const adVolume = this.ima.getAdVolume();
2928
- 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
+ );
2929
3145
  }
2930
3146
  return this.video.muted;
2931
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
+ }
2932
3175
  isFullscreen() {
2933
3176
  return !!document.fullscreenElement;
2934
3177
  }
@@ -3016,10 +3259,12 @@ var StormcloudVideoPlayerComponent = React.memo(
3016
3259
  vastMode,
3017
3260
  vastTagUrl,
3018
3261
  adPlayerType,
3262
+ minSegmentsBeforePlay,
3019
3263
  ...restVideoAttrs
3020
3264
  } = props;
3021
3265
  const videoRef = useRef(null);
3022
3266
  const playerRef = useRef(null);
3267
+ const bufferingTimeoutRef = useRef(null);
3023
3268
  const [adStatus, setAdStatus] = React.useState({ showAds: false, currentIndex: 0, totalAds: 0 });
3024
3269
  const [shouldShowNativeControls, setShouldShowNativeControls] = React.useState(true);
3025
3270
  const [isMuted, setIsMuted] = React.useState(false);
@@ -3141,6 +3386,9 @@ var StormcloudVideoPlayerComponent = React.memo(
3141
3386
  return;
3142
3387
  }
3143
3388
  setShowLicenseWarning(false);
3389
+ if (debugAdTiming) {
3390
+ console.log("[StormcloudUI] Initializing player, isLoading=true");
3391
+ }
3144
3392
  if (playerRef.current) {
3145
3393
  try {
3146
3394
  playerRef.current.destroy();
@@ -3171,17 +3419,25 @@ var StormcloudVideoPlayerComponent = React.memo(
3171
3419
  if (vastMode !== void 0) cfg.vastMode = vastMode;
3172
3420
  if (vastTagUrl !== void 0) cfg.vastTagUrl = vastTagUrl;
3173
3421
  if (adPlayerType !== void 0) cfg.adPlayerType = adPlayerType;
3422
+ if (minSegmentsBeforePlay !== void 0)
3423
+ cfg.minSegmentsBeforePlay = minSegmentsBeforePlay;
3174
3424
  const player = new StormcloudVideoPlayer(cfg);
3175
3425
  playerRef.current = player;
3176
3426
  player.load().then(() => {
3177
3427
  const showNative = player.shouldShowNativeControls();
3178
3428
  setShouldShowNativeControls(showNative);
3429
+ if (debugAdTiming) {
3430
+ console.log(
3431
+ "[StormcloudUI] Player loaded successfully, waiting for video ready"
3432
+ );
3433
+ }
3179
3434
  onReady == null ? void 0 : onReady(player);
3180
3435
  }).catch((error) => {
3181
3436
  console.error(
3182
3437
  "StormcloudVideoPlayer: Failed to load player:",
3183
3438
  error
3184
3439
  );
3440
+ setIsLoading(false);
3185
3441
  onReady == null ? void 0 : onReady(player);
3186
3442
  });
3187
3443
  return () => {
@@ -3198,8 +3454,8 @@ var StormcloudVideoPlayerComponent = React.memo(
3198
3454
  if (autoplay !== void 0 && playerRef.current.videoElement) {
3199
3455
  playerRef.current.videoElement.autoplay = autoplay;
3200
3456
  }
3201
- if (muted !== void 0 && playerRef.current.videoElement) {
3202
- playerRef.current.videoElement.muted = muted;
3457
+ if (muted !== void 0) {
3458
+ playerRef.current.setMuted(muted);
3203
3459
  }
3204
3460
  } catch (error) {
3205
3461
  console.warn("Failed to update player properties:", error);
@@ -3280,26 +3536,108 @@ var StormcloudVideoPlayerComponent = React.memo(
3280
3536
  useEffect(() => {
3281
3537
  if (!videoRef.current) return;
3282
3538
  const handleLoadedMetadata = () => {
3539
+ var _a;
3283
3540
  if (videoRef.current) {
3284
3541
  const video2 = videoRef.current;
3285
3542
  void video2.offsetHeight;
3286
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
+ }
3287
3559
  };
3288
3560
  const handleLoadStart = () => {
3289
- setIsLoading(true);
3290
- 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
+ }
3291
3568
  };
3292
3569
  const handleCanPlay = () => {
3570
+ var _a;
3571
+ setIsLoading(false);
3572
+ if (bufferingTimeoutRef.current) {
3573
+ clearTimeout(bufferingTimeoutRef.current);
3574
+ bufferingTimeoutRef.current = null;
3575
+ }
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;
3293
3587
  setIsLoading(false);
3588
+ if (bufferingTimeoutRef.current) {
3589
+ clearTimeout(bufferingTimeoutRef.current);
3590
+ bufferingTimeoutRef.current = null;
3591
+ }
3294
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
+ }
3295
3600
  };
3296
3601
  const handleWaiting = () => {
3297
- 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
+ }
3298
3624
  };
3299
3625
  const handlePlaying = () => {
3626
+ var _a;
3300
3627
  setIsLoading(false);
3628
+ if (bufferingTimeoutRef.current) {
3629
+ clearTimeout(bufferingTimeoutRef.current);
3630
+ bufferingTimeoutRef.current = null;
3631
+ }
3301
3632
  setIsBuffering(false);
3302
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
+ }
3303
3641
  };
3304
3642
  const handlePause = () => {
3305
3643
  if (playerRef.current && !playerRef.current.isShowingAds()) {
@@ -3314,8 +3652,9 @@ var StormcloudVideoPlayerComponent = React.memo(
3314
3652
  const video = videoRef.current;
3315
3653
  video.addEventListener("loadstart", handleLoadStart);
3316
3654
  video.addEventListener("loadedmetadata", handleLoadedMetadata);
3317
- video.addEventListener("loadeddata", handleLoadedMetadata);
3655
+ video.addEventListener("loadeddata", handleLoadedData);
3318
3656
  video.addEventListener("canplay", handleCanPlay);
3657
+ video.addEventListener("canplaythrough", handleCanPlayThrough);
3319
3658
  video.addEventListener("waiting", handleWaiting);
3320
3659
  video.addEventListener("playing", handlePlaying);
3321
3660
  video.addEventListener("pause", handlePause);
@@ -3324,16 +3663,21 @@ var StormcloudVideoPlayerComponent = React.memo(
3324
3663
  setShowCenterPlay(true);
3325
3664
  }
3326
3665
  return () => {
3666
+ if (bufferingTimeoutRef.current) {
3667
+ clearTimeout(bufferingTimeoutRef.current);
3668
+ bufferingTimeoutRef.current = null;
3669
+ }
3327
3670
  video.removeEventListener("loadstart", handleLoadStart);
3328
3671
  video.removeEventListener("loadedmetadata", handleLoadedMetadata);
3329
- video.removeEventListener("loadeddata", handleLoadedMetadata);
3672
+ video.removeEventListener("loadeddata", handleLoadedData);
3330
3673
  video.removeEventListener("canplay", handleCanPlay);
3674
+ video.removeEventListener("canplaythrough", handleCanPlayThrough);
3331
3675
  video.removeEventListener("waiting", handleWaiting);
3332
3676
  video.removeEventListener("playing", handlePlaying);
3333
3677
  video.removeEventListener("pause", handlePause);
3334
3678
  video.removeEventListener("ended", handleEnded);
3335
3679
  };
3336
- }, []);
3680
+ }, [debugAdTiming]);
3337
3681
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3338
3682
  /* @__PURE__ */ jsx("style", { children: `
3339
3683
  @keyframes spin {
@@ -3440,35 +3784,18 @@ var StormcloudVideoPlayerComponent = React.memo(
3440
3784
  }
3441
3785
  ),
3442
3786
  (isLoading || isBuffering) && /* @__PURE__ */ jsx(
3443
- "div",
3787
+ FaSpinner,
3444
3788
  {
3789
+ size: 42,
3790
+ color: "white",
3445
3791
  style: {
3446
3792
  position: "absolute",
3447
- top: "50%",
3448
- left: "50%",
3449
- transform: "translate(-50%, -50%)",
3793
+ top: "calc(50% - 21px)",
3794
+ left: "calc(50% - 21px)",
3450
3795
  zIndex: 20,
3451
- display: "flex",
3452
- alignItems: "center",
3453
- justifyContent: "center",
3454
- background: "linear-gradient(135deg, rgba(0, 0, 0, 0.8) 0%, rgba(20, 20, 20, 0.6) 100%)",
3455
- width: "80px",
3456
- height: "80px",
3457
- borderRadius: "50%",
3458
- backdropFilter: "blur(20px)",
3459
- boxShadow: "0 12px 40px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 255, 255, 0.1)"
3460
- },
3461
- children: /* @__PURE__ */ jsx(
3462
- FaSpinner,
3463
- {
3464
- size: 28,
3465
- color: "white",
3466
- style: {
3467
- animation: "spin 1s linear infinite",
3468
- filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3469
- }
3470
- }
3471
- )
3796
+ animation: "spin 1s linear infinite",
3797
+ filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
3798
+ }
3472
3799
  }
3473
3800
  ),
3474
3801
  showLicenseWarning && /* @__PURE__ */ jsxs(
@@ -4581,6 +4908,7 @@ var defaultProps = {
4581
4908
  showCustomControls: false,
4582
4909
  licenseKey: "",
4583
4910
  adFailsafeTimeoutMs: 1e4,
4911
+ minSegmentsBeforePlay: 2,
4584
4912
  onStart: noop,
4585
4913
  onPlay: noop,
4586
4914
  onPause: noop,
@@ -4754,6 +5082,8 @@ var HlsPlayer = class extends Component {
4754
5082
  config.licenseKey = this.props.licenseKey;
4755
5083
  if (this.props.adFailsafeTimeoutMs !== void 0)
4756
5084
  config.adFailsafeTimeoutMs = this.props.adFailsafeTimeoutMs;
5085
+ if (this.props.minSegmentsBeforePlay !== void 0)
5086
+ config.minSegmentsBeforePlay = this.props.minSegmentsBeforePlay;
4757
5087
  this.player = new StormcloudVideoPlayer(config);
4758
5088
  (_b = (_a = this.props).onMount) == null ? void 0 : _b.call(_a, this);
4759
5089
  await this.player.load();
@@ -5360,6 +5690,7 @@ var SUPPORTED_PROPS = [
5360
5690
  "showCustomControls",
5361
5691
  "licenseKey",
5362
5692
  "adFailsafeTimeoutMs",
5693
+ "minSegmentsBeforePlay",
5363
5694
  "onReady",
5364
5695
  "onStart",
5365
5696
  "onPlay",