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.js CHANGED
@@ -395,9 +395,11 @@ function createHlsAdPlayer(contentVideo, options) {
395
395
  var pendingTimeouts = [];
396
396
  var STALL_TIMEOUT_MS = 4e3;
397
397
  var STALL_CHECK_INTERVAL_MS = 1e3;
398
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
398
399
  var stallWatchdogId;
399
400
  var lastAdProgressTime = 0;
400
401
  var lastAdProgressPosition = 0;
402
+ var sawDecodedVideoFrame = false;
401
403
  var trackingFired = {
402
404
  impression: false,
403
405
  start: false,
@@ -539,6 +541,61 @@ function createHlsAdPlayer(contentVideo, options) {
539
541
  var url = mediaFile.url.toLowerCase();
540
542
  return type.startsWith("video/") || url.endsWith(".mp4") || url.includes(".mp4?");
541
543
  }
544
+ function isAudioMediaFile(mediaFile) {
545
+ var type = mediaFile.type.toLowerCase();
546
+ var url = (mediaFile.url.toLowerCase().split("?")[0] || "").trim();
547
+ 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");
548
+ }
549
+ function collectVideoMediaFiles(scope, verbose) {
550
+ var mediaFileElements = scope.querySelectorAll("MediaFile");
551
+ var mediaFiles = [];
552
+ if (verbose) {
553
+ console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
554
+ }
555
+ mediaFileElements.forEach(function(mf, index) {
556
+ var _mf_textContent;
557
+ var type = mf.getAttribute("type") || "";
558
+ var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
559
+ var width = mf.getAttribute("width") || "";
560
+ var height = mf.getAttribute("height") || "";
561
+ if (verbose) {
562
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
563
+ }
564
+ if (!url) {
565
+ if (verbose) {
566
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
567
+ }
568
+ return;
569
+ }
570
+ var bitrateAttr = mf.getAttribute("bitrate");
571
+ var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
572
+ var mediaFile = {
573
+ url: url,
574
+ type: type,
575
+ width: parseInt(width || "1920", 10),
576
+ height: parseInt(height || "1080", 10),
577
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
578
+ };
579
+ if (isAudioMediaFile(mediaFile)) {
580
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
581
+ return;
582
+ }
583
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
584
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
585
+ 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'));
586
+ return;
587
+ }
588
+ if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
589
+ mediaFiles.push(mediaFile);
590
+ if (verbose) {
591
+ console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
592
+ }
593
+ } else if (verbose) {
594
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
595
+ }
596
+ });
597
+ return mediaFiles;
598
+ }
542
599
  function createEmptyTrackingUrls() {
543
600
  return {
544
601
  impression: [],
@@ -626,37 +683,7 @@ function createHlsAdPlayer(contentVideo, options) {
626
683
  var durationText = ((_xmlDoc_querySelector3 = xmlDoc.querySelector("Duration")) === null || _xmlDoc_querySelector3 === void 0 ? void 0 : _xmlDoc_querySelector3.textContent) || "00:00:30";
627
684
  var durationParts = durationText.split(":");
628
685
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
629
- var mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
630
- var mediaFiles = [];
631
- console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
632
- mediaFileElements.forEach(function(mf, index) {
633
- var _mf_textContent;
634
- var type = mf.getAttribute("type") || "";
635
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
636
- var width = mf.getAttribute("width") || "";
637
- var height = mf.getAttribute("height") || "";
638
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
639
- var mediaFile = {
640
- url: url,
641
- type: type,
642
- width: parseInt(width || "1920", 10),
643
- height: parseInt(height || "1080", 10),
644
- bitrate: void 0
645
- };
646
- if (!url) {
647
- console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
648
- return;
649
- }
650
- var bitrateAttr = mf.getAttribute("bitrate");
651
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
652
- mediaFile.bitrate = bitrateValue && bitrateValue > 0 ? bitrateValue : void 0;
653
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
654
- mediaFiles.push(mediaFile);
655
- console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
656
- } else {
657
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
658
- }
659
- });
686
+ var mediaFiles = collectVideoMediaFiles(xmlDoc, true);
660
687
  if (mediaFiles.length === 0) {
661
688
  if (isNoAdAvailable) {
662
689
  console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
@@ -704,28 +731,7 @@ function createHlsAdPlayer(contentVideo, options) {
704
731
  var durationText = ((_adElement_querySelector3 = adElement.querySelector("Duration")) === null || _adElement_querySelector3 === void 0 ? void 0 : _adElement_querySelector3.textContent) || "00:00:30";
705
732
  var durationParts = durationText.split(":");
706
733
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
707
- var mediaFileElements = adElement.querySelectorAll("MediaFile");
708
- var mediaFiles = [];
709
- mediaFileElements.forEach(function(mf) {
710
- var _mf_textContent;
711
- var type = mf.getAttribute("type") || "";
712
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
713
- var width = mf.getAttribute("width") || "";
714
- var height = mf.getAttribute("height") || "";
715
- if (!url) return;
716
- var bitrateAttr = mf.getAttribute("bitrate");
717
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
718
- var mediaFile = {
719
- url: url,
720
- type: type,
721
- width: parseInt(width || "1920", 10),
722
- height: parseInt(height || "1080", 10),
723
- bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
724
- };
725
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
726
- mediaFiles.push(mediaFile);
727
- }
728
- });
734
+ var mediaFiles = collectVideoMediaFiles(adElement, false);
729
735
  if (mediaFiles.length === 0) {
730
736
  if (isNoAdAvailable) {
731
737
  console.warn("[HlsAdPlayer] Pod <Ad> indicates no ad available");
@@ -1046,6 +1052,14 @@ function createHlsAdPlayer(contentVideo, options) {
1046
1052
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1047
1053
  }
1048
1054
  };
1055
+ var onLoadedMetadata = function onLoadedMetadata() {
1056
+ if (!adPlaying || !adVideoElement) return;
1057
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1058
+ if (videoWidth <= 1 || videoHeight <= 1) {
1059
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1060
+ handleVideoDecodeFailure();
1061
+ }
1062
+ };
1049
1063
  var onPlaying = function onPlaying() {
1050
1064
  startStallWatchdog();
1051
1065
  if (!currentAd || trackingFired.start) return;
@@ -1097,6 +1111,10 @@ function createHlsAdPlayer(contentVideo, options) {
1097
1111
  type: "timeupdate",
1098
1112
  handler: onTimeUpdate
1099
1113
  },
1114
+ {
1115
+ type: "loadedmetadata",
1116
+ handler: onLoadedMetadata
1117
+ },
1100
1118
  {
1101
1119
  type: "playing",
1102
1120
  handler: onPlaying
@@ -1286,13 +1304,51 @@ function createHlsAdPlayer(contentVideo, options) {
1286
1304
  }
1287
1305
  handleAdError();
1288
1306
  }
1307
+ function getDecodedVideoFrameCount(video) {
1308
+ try {
1309
+ if (typeof video.getVideoPlaybackQuality === "function") {
1310
+ var quality = video.getVideoPlaybackQuality();
1311
+ if (quality && typeof quality.totalVideoFrames === "number") {
1312
+ return quality.totalVideoFrames;
1313
+ }
1314
+ }
1315
+ var legacyCount = video.webkitDecodedFrameCount;
1316
+ if (typeof legacyCount === "number") {
1317
+ return legacyCount;
1318
+ }
1319
+ } catch (error) {
1320
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1321
+ }
1322
+ return void 0;
1323
+ }
1324
+ function handleVideoDecodeFailure() {
1325
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1326
+ clearStallWatchdog();
1327
+ if (currentAd) {
1328
+ fireTrackingPixels(currentAd.trackingUrls.error);
1329
+ }
1330
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1331
+ return;
1332
+ }
1333
+ handleAdError();
1334
+ }
1289
1335
  function startStallWatchdog() {
1290
1336
  clearStallWatchdog();
1291
1337
  noteAdProgress();
1338
+ sawDecodedVideoFrame = false;
1292
1339
  stallWatchdogId = window.setInterval(function() {
1293
1340
  if (!adPlaying || !adVideoElement) {
1294
1341
  return;
1295
1342
  }
1343
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1344
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1345
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1346
+ sawDecodedVideoFrame = true;
1347
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1348
+ handleVideoDecodeFailure();
1349
+ return;
1350
+ }
1351
+ }
1296
1352
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1297
1353
  noteAdProgress();
1298
1354
  return;