stormcloud-video-player 0.8.47 → 0.8.49

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
@@ -615,9 +615,11 @@ function createHlsAdPlayer(contentVideo, options) {
615
615
  var pendingTimeouts = [];
616
616
  var STALL_TIMEOUT_MS = 4e3;
617
617
  var STALL_CHECK_INTERVAL_MS = 1e3;
618
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
618
619
  var stallWatchdogId;
619
620
  var lastAdProgressTime = 0;
620
621
  var lastAdProgressPosition = 0;
622
+ var sawDecodedVideoFrame = false;
621
623
  var trackingFired = {
622
624
  impression: false,
623
625
  start: false,
@@ -759,6 +761,61 @@ function createHlsAdPlayer(contentVideo, options) {
759
761
  var url = mediaFile.url.toLowerCase();
760
762
  return type.startsWith("video/") || url.endsWith(".mp4") || url.includes(".mp4?");
761
763
  }
764
+ function isAudioMediaFile(mediaFile) {
765
+ var type = mediaFile.type.toLowerCase();
766
+ var url = (mediaFile.url.toLowerCase().split("?")[0] || "").trim();
767
+ return type.startsWith("audio/") || url.endsWith(".mp3") || url.endsWith(".aac") || url.endsWith(".m4a") || url.endsWith(".ogg") || url.endsWith(".oga") || url.endsWith(".opus") || url.endsWith(".wav") || url.endsWith(".weba");
768
+ }
769
+ function collectVideoMediaFiles(scope, verbose) {
770
+ var mediaFileElements = scope.querySelectorAll("MediaFile");
771
+ var mediaFiles = [];
772
+ if (verbose) {
773
+ console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
774
+ }
775
+ mediaFileElements.forEach(function(mf, index) {
776
+ var _mf_textContent;
777
+ var type = mf.getAttribute("type") || "";
778
+ var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
779
+ var width = mf.getAttribute("width") || "";
780
+ var height = mf.getAttribute("height") || "";
781
+ if (verbose) {
782
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
783
+ }
784
+ if (!url) {
785
+ if (verbose) {
786
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
787
+ }
788
+ return;
789
+ }
790
+ var bitrateAttr = mf.getAttribute("bitrate");
791
+ var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
792
+ var mediaFile = {
793
+ url: url,
794
+ type: type,
795
+ width: parseInt(width || "1920", 10),
796
+ height: parseInt(height || "1080", 10),
797
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
798
+ };
799
+ if (isAudioMediaFile(mediaFile)) {
800
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
801
+ return;
802
+ }
803
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
804
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
805
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, " skipped (degenerate dimensions ").concat(mediaFile.width, "x").concat(mediaFile.height, ', type="').concat(type, '") — likely an audio-only placeholder rendition'));
806
+ return;
807
+ }
808
+ if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
809
+ mediaFiles.push(mediaFile);
810
+ if (verbose) {
811
+ console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
812
+ }
813
+ } else if (verbose) {
814
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
815
+ }
816
+ });
817
+ return mediaFiles;
818
+ }
762
819
  function createEmptyTrackingUrls() {
763
820
  return {
764
821
  impression: [],
@@ -846,37 +903,7 @@ function createHlsAdPlayer(contentVideo, options) {
846
903
  var durationText = ((_xmlDoc_querySelector3 = xmlDoc.querySelector("Duration")) === null || _xmlDoc_querySelector3 === void 0 ? void 0 : _xmlDoc_querySelector3.textContent) || "00:00:30";
847
904
  var durationParts = durationText.split(":");
848
905
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
849
- var mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
850
- var mediaFiles = [];
851
- console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
852
- mediaFileElements.forEach(function(mf, index) {
853
- var _mf_textContent;
854
- var type = mf.getAttribute("type") || "";
855
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
856
- var width = mf.getAttribute("width") || "";
857
- var height = mf.getAttribute("height") || "";
858
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
859
- var mediaFile = {
860
- url: url,
861
- type: type,
862
- width: parseInt(width || "1920", 10),
863
- height: parseInt(height || "1080", 10),
864
- bitrate: void 0
865
- };
866
- if (!url) {
867
- console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
868
- return;
869
- }
870
- var bitrateAttr = mf.getAttribute("bitrate");
871
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
872
- mediaFile.bitrate = bitrateValue && bitrateValue > 0 ? bitrateValue : void 0;
873
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
874
- mediaFiles.push(mediaFile);
875
- console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
876
- } else {
877
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
878
- }
879
- });
906
+ var mediaFiles = collectVideoMediaFiles(xmlDoc, true);
880
907
  if (mediaFiles.length === 0) {
881
908
  if (isNoAdAvailable) {
882
909
  console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
@@ -924,28 +951,7 @@ function createHlsAdPlayer(contentVideo, options) {
924
951
  var durationText = ((_adElement_querySelector3 = adElement.querySelector("Duration")) === null || _adElement_querySelector3 === void 0 ? void 0 : _adElement_querySelector3.textContent) || "00:00:30";
925
952
  var durationParts = durationText.split(":");
926
953
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
927
- var mediaFileElements = adElement.querySelectorAll("MediaFile");
928
- var mediaFiles = [];
929
- mediaFileElements.forEach(function(mf) {
930
- var _mf_textContent;
931
- var type = mf.getAttribute("type") || "";
932
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
933
- var width = mf.getAttribute("width") || "";
934
- var height = mf.getAttribute("height") || "";
935
- if (!url) return;
936
- var bitrateAttr = mf.getAttribute("bitrate");
937
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
938
- var mediaFile = {
939
- url: url,
940
- type: type,
941
- width: parseInt(width || "1920", 10),
942
- height: parseInt(height || "1080", 10),
943
- bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
944
- };
945
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
946
- mediaFiles.push(mediaFile);
947
- }
948
- });
954
+ var mediaFiles = collectVideoMediaFiles(adElement, false);
949
955
  if (mediaFiles.length === 0) {
950
956
  if (isNoAdAvailable) {
951
957
  console.warn("[HlsAdPlayer] Pod <Ad> indicates no ad available");
@@ -1266,6 +1272,14 @@ function createHlsAdPlayer(contentVideo, options) {
1266
1272
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1267
1273
  }
1268
1274
  };
1275
+ var onLoadedMetadata = function onLoadedMetadata() {
1276
+ if (!adPlaying || !adVideoElement) return;
1277
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1278
+ if (videoWidth <= 1 || videoHeight <= 1) {
1279
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1280
+ handleVideoDecodeFailure();
1281
+ }
1282
+ };
1269
1283
  var onPlaying = function onPlaying() {
1270
1284
  startStallWatchdog();
1271
1285
  if (!currentAd || trackingFired.start) return;
@@ -1317,6 +1331,10 @@ function createHlsAdPlayer(contentVideo, options) {
1317
1331
  type: "timeupdate",
1318
1332
  handler: onTimeUpdate
1319
1333
  },
1334
+ {
1335
+ type: "loadedmetadata",
1336
+ handler: onLoadedMetadata
1337
+ },
1320
1338
  {
1321
1339
  type: "playing",
1322
1340
  handler: onPlaying
@@ -1506,13 +1524,51 @@ function createHlsAdPlayer(contentVideo, options) {
1506
1524
  }
1507
1525
  handleAdError();
1508
1526
  }
1527
+ function getDecodedVideoFrameCount(video) {
1528
+ try {
1529
+ if (typeof video.getVideoPlaybackQuality === "function") {
1530
+ var quality = video.getVideoPlaybackQuality();
1531
+ if (quality && typeof quality.totalVideoFrames === "number") {
1532
+ return quality.totalVideoFrames;
1533
+ }
1534
+ }
1535
+ var legacyCount = video.webkitDecodedFrameCount;
1536
+ if (typeof legacyCount === "number") {
1537
+ return legacyCount;
1538
+ }
1539
+ } catch (error) {
1540
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1541
+ }
1542
+ return void 0;
1543
+ }
1544
+ function handleVideoDecodeFailure() {
1545
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1546
+ clearStallWatchdog();
1547
+ if (currentAd) {
1548
+ fireTrackingPixels(currentAd.trackingUrls.error);
1549
+ }
1550
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1551
+ return;
1552
+ }
1553
+ handleAdError();
1554
+ }
1509
1555
  function startStallWatchdog() {
1510
1556
  clearStallWatchdog();
1511
1557
  noteAdProgress();
1558
+ sawDecodedVideoFrame = false;
1512
1559
  stallWatchdogId = window.setInterval(function() {
1513
1560
  if (!adPlaying || !adVideoElement) {
1514
1561
  return;
1515
1562
  }
1563
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1564
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1565
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1566
+ sawDecodedVideoFrame = true;
1567
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1568
+ handleVideoDecodeFailure();
1569
+ return;
1570
+ }
1571
+ }
1516
1572
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1517
1573
  noteAdProgress();
1518
1574
  return;