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.
@@ -1,5 +1,5 @@
1
1
  import { Component } from 'react';
2
- import { a as StormcloudVideoPlayerConfig } from '../types-cTqIKw_D.cjs';
2
+ import { S as StormcloudVideoPlayerConfig } from '../types-iDjS8f_7.cjs';
3
3
 
4
4
  interface HlsPlayerProps extends StormcloudVideoPlayerConfig {
5
5
  onMount?: (player: any) => void;
@@ -461,9 +461,11 @@ function createHlsAdPlayer(contentVideo, options) {
461
461
  var pendingTimeouts = [];
462
462
  var STALL_TIMEOUT_MS = 4e3;
463
463
  var STALL_CHECK_INTERVAL_MS = 1e3;
464
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
464
465
  var stallWatchdogId;
465
466
  var lastAdProgressTime = 0;
466
467
  var lastAdProgressPosition = 0;
468
+ var sawDecodedVideoFrame = false;
467
469
  var trackingFired = {
468
470
  impression: false,
469
471
  start: false,
@@ -605,6 +607,61 @@ function createHlsAdPlayer(contentVideo, options) {
605
607
  var url = mediaFile.url.toLowerCase();
606
608
  return type.startsWith("video/") || url.endsWith(".mp4") || url.includes(".mp4?");
607
609
  }
610
+ function isAudioMediaFile(mediaFile) {
611
+ var type = mediaFile.type.toLowerCase();
612
+ var url = (mediaFile.url.toLowerCase().split("?")[0] || "").trim();
613
+ 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");
614
+ }
615
+ function collectVideoMediaFiles(scope, verbose) {
616
+ var mediaFileElements = scope.querySelectorAll("MediaFile");
617
+ var mediaFiles = [];
618
+ if (verbose) {
619
+ console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
620
+ }
621
+ mediaFileElements.forEach(function(mf, index) {
622
+ var _mf_textContent;
623
+ var type = mf.getAttribute("type") || "";
624
+ var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
625
+ var width = mf.getAttribute("width") || "";
626
+ var height = mf.getAttribute("height") || "";
627
+ if (verbose) {
628
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
629
+ }
630
+ if (!url) {
631
+ if (verbose) {
632
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
633
+ }
634
+ return;
635
+ }
636
+ var bitrateAttr = mf.getAttribute("bitrate");
637
+ var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
638
+ var mediaFile = {
639
+ url: url,
640
+ type: type,
641
+ width: parseInt(width || "1920", 10),
642
+ height: parseInt(height || "1080", 10),
643
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
644
+ };
645
+ if (isAudioMediaFile(mediaFile)) {
646
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
647
+ return;
648
+ }
649
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
650
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
651
+ 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'));
652
+ return;
653
+ }
654
+ if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
655
+ mediaFiles.push(mediaFile);
656
+ if (verbose) {
657
+ console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
658
+ }
659
+ } else if (verbose) {
660
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
661
+ }
662
+ });
663
+ return mediaFiles;
664
+ }
608
665
  function createEmptyTrackingUrls() {
609
666
  return {
610
667
  impression: [],
@@ -692,37 +749,7 @@ function createHlsAdPlayer(contentVideo, options) {
692
749
  var durationText = ((_xmlDoc_querySelector3 = xmlDoc.querySelector("Duration")) === null || _xmlDoc_querySelector3 === void 0 ? void 0 : _xmlDoc_querySelector3.textContent) || "00:00:30";
693
750
  var durationParts = durationText.split(":");
694
751
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
695
- var mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
696
- var mediaFiles = [];
697
- console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
698
- mediaFileElements.forEach(function(mf, index) {
699
- var _mf_textContent;
700
- var type = mf.getAttribute("type") || "";
701
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
702
- var width = mf.getAttribute("width") || "";
703
- var height = mf.getAttribute("height") || "";
704
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
705
- var mediaFile = {
706
- url: url,
707
- type: type,
708
- width: parseInt(width || "1920", 10),
709
- height: parseInt(height || "1080", 10),
710
- bitrate: void 0
711
- };
712
- if (!url) {
713
- console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
714
- return;
715
- }
716
- var bitrateAttr = mf.getAttribute("bitrate");
717
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
718
- mediaFile.bitrate = bitrateValue && bitrateValue > 0 ? bitrateValue : void 0;
719
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
720
- mediaFiles.push(mediaFile);
721
- console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
722
- } else {
723
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
724
- }
725
- });
752
+ var mediaFiles = collectVideoMediaFiles(xmlDoc, true);
726
753
  if (mediaFiles.length === 0) {
727
754
  if (isNoAdAvailable) {
728
755
  console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
@@ -770,28 +797,7 @@ function createHlsAdPlayer(contentVideo, options) {
770
797
  var durationText = ((_adElement_querySelector3 = adElement.querySelector("Duration")) === null || _adElement_querySelector3 === void 0 ? void 0 : _adElement_querySelector3.textContent) || "00:00:30";
771
798
  var durationParts = durationText.split(":");
772
799
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
773
- var mediaFileElements = adElement.querySelectorAll("MediaFile");
774
- var mediaFiles = [];
775
- mediaFileElements.forEach(function(mf) {
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 (!url) return;
782
- var bitrateAttr = mf.getAttribute("bitrate");
783
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
784
- var mediaFile = {
785
- url: url,
786
- type: type,
787
- width: parseInt(width || "1920", 10),
788
- height: parseInt(height || "1080", 10),
789
- bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
790
- };
791
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
792
- mediaFiles.push(mediaFile);
793
- }
794
- });
800
+ var mediaFiles = collectVideoMediaFiles(adElement, false);
795
801
  if (mediaFiles.length === 0) {
796
802
  if (isNoAdAvailable) {
797
803
  console.warn("[HlsAdPlayer] Pod <Ad> indicates no ad available");
@@ -1112,6 +1118,14 @@ function createHlsAdPlayer(contentVideo, options) {
1112
1118
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1113
1119
  }
1114
1120
  };
1121
+ var onLoadedMetadata = function onLoadedMetadata() {
1122
+ if (!adPlaying || !adVideoElement) return;
1123
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1124
+ if (videoWidth <= 1 || videoHeight <= 1) {
1125
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1126
+ handleVideoDecodeFailure();
1127
+ }
1128
+ };
1115
1129
  var onPlaying = function onPlaying() {
1116
1130
  startStallWatchdog();
1117
1131
  if (!currentAd || trackingFired.start) return;
@@ -1163,6 +1177,10 @@ function createHlsAdPlayer(contentVideo, options) {
1163
1177
  type: "timeupdate",
1164
1178
  handler: onTimeUpdate
1165
1179
  },
1180
+ {
1181
+ type: "loadedmetadata",
1182
+ handler: onLoadedMetadata
1183
+ },
1166
1184
  {
1167
1185
  type: "playing",
1168
1186
  handler: onPlaying
@@ -1352,13 +1370,51 @@ function createHlsAdPlayer(contentVideo, options) {
1352
1370
  }
1353
1371
  handleAdError();
1354
1372
  }
1373
+ function getDecodedVideoFrameCount(video) {
1374
+ try {
1375
+ if (typeof video.getVideoPlaybackQuality === "function") {
1376
+ var quality = video.getVideoPlaybackQuality();
1377
+ if (quality && typeof quality.totalVideoFrames === "number") {
1378
+ return quality.totalVideoFrames;
1379
+ }
1380
+ }
1381
+ var legacyCount = video.webkitDecodedFrameCount;
1382
+ if (typeof legacyCount === "number") {
1383
+ return legacyCount;
1384
+ }
1385
+ } catch (error) {
1386
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1387
+ }
1388
+ return void 0;
1389
+ }
1390
+ function handleVideoDecodeFailure() {
1391
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1392
+ clearStallWatchdog();
1393
+ if (currentAd) {
1394
+ fireTrackingPixels(currentAd.trackingUrls.error);
1395
+ }
1396
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1397
+ return;
1398
+ }
1399
+ handleAdError();
1400
+ }
1355
1401
  function startStallWatchdog() {
1356
1402
  clearStallWatchdog();
1357
1403
  noteAdProgress();
1404
+ sawDecodedVideoFrame = false;
1358
1405
  stallWatchdogId = window.setInterval(function() {
1359
1406
  if (!adPlaying || !adVideoElement) {
1360
1407
  return;
1361
1408
  }
1409
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1410
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1411
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1412
+ sawDecodedVideoFrame = true;
1413
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1414
+ handleVideoDecodeFailure();
1415
+ return;
1416
+ }
1417
+ }
1362
1418
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1363
1419
  noteAdProgress();
1364
1420
  return;