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,4 +1,4 @@
1
- import { a as StormcloudVideoPlayerConfig } from '../types-cTqIKw_D.cjs';
1
+ import { S as StormcloudVideoPlayerConfig } from '../types-iDjS8f_7.cjs';
2
2
 
3
3
  declare class StormcloudVideoPlayer {
4
4
  private readonly video;
@@ -1,4 +1,4 @@
1
- import { S as Scte35Marker } from '../types-cTqIKw_D.cjs';
1
+ import { a as Scte35Marker } from '../types-iDjS8f_7.cjs';
2
2
 
3
3
  type Scte35CueSource = "manifest" | "id3" | "ts";
4
4
  type Scte35CueReadiness = "early" | "playback";
@@ -425,9 +425,11 @@ function createHlsAdPlayer(contentVideo, options) {
425
425
  var pendingTimeouts = [];
426
426
  var STALL_TIMEOUT_MS = 4e3;
427
427
  var STALL_CHECK_INTERVAL_MS = 1e3;
428
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
428
429
  var stallWatchdogId;
429
430
  var lastAdProgressTime = 0;
430
431
  var lastAdProgressPosition = 0;
432
+ var sawDecodedVideoFrame = false;
431
433
  var trackingFired = {
432
434
  impression: false,
433
435
  start: false,
@@ -569,6 +571,61 @@ function createHlsAdPlayer(contentVideo, options) {
569
571
  var url = mediaFile.url.toLowerCase();
570
572
  return type.startsWith("video/") || url.endsWith(".mp4") || url.includes(".mp4?");
571
573
  }
574
+ function isAudioMediaFile(mediaFile) {
575
+ var type = mediaFile.type.toLowerCase();
576
+ var url = (mediaFile.url.toLowerCase().split("?")[0] || "").trim();
577
+ 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");
578
+ }
579
+ function collectVideoMediaFiles(scope, verbose) {
580
+ var mediaFileElements = scope.querySelectorAll("MediaFile");
581
+ var mediaFiles = [];
582
+ if (verbose) {
583
+ console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
584
+ }
585
+ mediaFileElements.forEach(function(mf, index) {
586
+ var _mf_textContent;
587
+ var type = mf.getAttribute("type") || "";
588
+ var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
589
+ var width = mf.getAttribute("width") || "";
590
+ var height = mf.getAttribute("height") || "";
591
+ if (verbose) {
592
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
593
+ }
594
+ if (!url) {
595
+ if (verbose) {
596
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
597
+ }
598
+ return;
599
+ }
600
+ var bitrateAttr = mf.getAttribute("bitrate");
601
+ var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
602
+ var mediaFile = {
603
+ url: url,
604
+ type: type,
605
+ width: parseInt(width || "1920", 10),
606
+ height: parseInt(height || "1080", 10),
607
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
608
+ };
609
+ if (isAudioMediaFile(mediaFile)) {
610
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
611
+ return;
612
+ }
613
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
614
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
615
+ 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'));
616
+ return;
617
+ }
618
+ if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
619
+ mediaFiles.push(mediaFile);
620
+ if (verbose) {
621
+ console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
622
+ }
623
+ } else if (verbose) {
624
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
625
+ }
626
+ });
627
+ return mediaFiles;
628
+ }
572
629
  function createEmptyTrackingUrls() {
573
630
  return {
574
631
  impression: [],
@@ -656,37 +713,7 @@ function createHlsAdPlayer(contentVideo, options) {
656
713
  var durationText = ((_xmlDoc_querySelector3 = xmlDoc.querySelector("Duration")) === null || _xmlDoc_querySelector3 === void 0 ? void 0 : _xmlDoc_querySelector3.textContent) || "00:00:30";
657
714
  var durationParts = durationText.split(":");
658
715
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
659
- var mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
660
- var mediaFiles = [];
661
- console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
662
- mediaFileElements.forEach(function(mf, index) {
663
- var _mf_textContent;
664
- var type = mf.getAttribute("type") || "";
665
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
666
- var width = mf.getAttribute("width") || "";
667
- var height = mf.getAttribute("height") || "";
668
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
669
- var mediaFile = {
670
- url: url,
671
- type: type,
672
- width: parseInt(width || "1920", 10),
673
- height: parseInt(height || "1080", 10),
674
- bitrate: void 0
675
- };
676
- if (!url) {
677
- console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
678
- return;
679
- }
680
- var bitrateAttr = mf.getAttribute("bitrate");
681
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
682
- mediaFile.bitrate = bitrateValue && bitrateValue > 0 ? bitrateValue : void 0;
683
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
684
- mediaFiles.push(mediaFile);
685
- console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
686
- } else {
687
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
688
- }
689
- });
716
+ var mediaFiles = collectVideoMediaFiles(xmlDoc, true);
690
717
  if (mediaFiles.length === 0) {
691
718
  if (isNoAdAvailable) {
692
719
  console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
@@ -734,28 +761,7 @@ function createHlsAdPlayer(contentVideo, options) {
734
761
  var durationText = ((_adElement_querySelector3 = adElement.querySelector("Duration")) === null || _adElement_querySelector3 === void 0 ? void 0 : _adElement_querySelector3.textContent) || "00:00:30";
735
762
  var durationParts = durationText.split(":");
736
763
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
737
- var mediaFileElements = adElement.querySelectorAll("MediaFile");
738
- var mediaFiles = [];
739
- mediaFileElements.forEach(function(mf) {
740
- var _mf_textContent;
741
- var type = mf.getAttribute("type") || "";
742
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
743
- var width = mf.getAttribute("width") || "";
744
- var height = mf.getAttribute("height") || "";
745
- if (!url) return;
746
- var bitrateAttr = mf.getAttribute("bitrate");
747
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
748
- var mediaFile = {
749
- url: url,
750
- type: type,
751
- width: parseInt(width || "1920", 10),
752
- height: parseInt(height || "1080", 10),
753
- bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
754
- };
755
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
756
- mediaFiles.push(mediaFile);
757
- }
758
- });
764
+ var mediaFiles = collectVideoMediaFiles(adElement, false);
759
765
  if (mediaFiles.length === 0) {
760
766
  if (isNoAdAvailable) {
761
767
  console.warn("[HlsAdPlayer] Pod <Ad> indicates no ad available");
@@ -1076,6 +1082,14 @@ function createHlsAdPlayer(contentVideo, options) {
1076
1082
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1077
1083
  }
1078
1084
  };
1085
+ var onLoadedMetadata = function onLoadedMetadata() {
1086
+ if (!adPlaying || !adVideoElement) return;
1087
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1088
+ if (videoWidth <= 1 || videoHeight <= 1) {
1089
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1090
+ handleVideoDecodeFailure();
1091
+ }
1092
+ };
1079
1093
  var onPlaying = function onPlaying() {
1080
1094
  startStallWatchdog();
1081
1095
  if (!currentAd || trackingFired.start) return;
@@ -1127,6 +1141,10 @@ function createHlsAdPlayer(contentVideo, options) {
1127
1141
  type: "timeupdate",
1128
1142
  handler: onTimeUpdate
1129
1143
  },
1144
+ {
1145
+ type: "loadedmetadata",
1146
+ handler: onLoadedMetadata
1147
+ },
1130
1148
  {
1131
1149
  type: "playing",
1132
1150
  handler: onPlaying
@@ -1316,13 +1334,51 @@ function createHlsAdPlayer(contentVideo, options) {
1316
1334
  }
1317
1335
  handleAdError();
1318
1336
  }
1337
+ function getDecodedVideoFrameCount(video) {
1338
+ try {
1339
+ if (typeof video.getVideoPlaybackQuality === "function") {
1340
+ var quality = video.getVideoPlaybackQuality();
1341
+ if (quality && typeof quality.totalVideoFrames === "number") {
1342
+ return quality.totalVideoFrames;
1343
+ }
1344
+ }
1345
+ var legacyCount = video.webkitDecodedFrameCount;
1346
+ if (typeof legacyCount === "number") {
1347
+ return legacyCount;
1348
+ }
1349
+ } catch (error) {
1350
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1351
+ }
1352
+ return void 0;
1353
+ }
1354
+ function handleVideoDecodeFailure() {
1355
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1356
+ clearStallWatchdog();
1357
+ if (currentAd) {
1358
+ fireTrackingPixels(currentAd.trackingUrls.error);
1359
+ }
1360
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1361
+ return;
1362
+ }
1363
+ handleAdError();
1364
+ }
1319
1365
  function startStallWatchdog() {
1320
1366
  clearStallWatchdog();
1321
1367
  noteAdProgress();
1368
+ sawDecodedVideoFrame = false;
1322
1369
  stallWatchdogId = window.setInterval(function() {
1323
1370
  if (!adPlaying || !adVideoElement) {
1324
1371
  return;
1325
1372
  }
1373
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1374
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1375
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1376
+ sawDecodedVideoFrame = true;
1377
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1378
+ handleVideoDecodeFailure();
1379
+ return;
1380
+ }
1381
+ }
1326
1382
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1327
1383
  noteAdProgress();
1328
1384
  return;