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 { S as Scte35Marker, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-cTqIKw_D.cjs';
1
+ import { a as Scte35Marker, S as StormcloudVideoPlayerConfig, A as AdController } from '../types-iDjS8f_7.cjs';
2
2
  import { AdTimingService } from './AdTimingService.cjs';
3
3
  import { AdConfigManager } from './AdConfigManager.cjs';
4
4
  import { Scte35CueManager } from './Scte35CueManager.cjs';
@@ -1,4 +1,4 @@
1
- import { b as AdBreak, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-cTqIKw_D.cjs';
1
+ import { b as AdBreak, S as StormcloudVideoPlayerConfig, A as AdController } from '../types-iDjS8f_7.cjs';
2
2
  import { PalNonceManager } from '../sdk/pal.cjs';
3
3
  import { VastConsentSignals } from '../utils/vastMacros.cjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { a as StormcloudVideoPlayerConfig, S as Scte35Marker, A as AdController } from '../types-cTqIKw_D.cjs';
1
+ import { S as StormcloudVideoPlayerConfig, a as Scte35Marker, A as AdController } from '../types-iDjS8f_7.cjs';
2
2
 
3
3
  interface AdTimingCallbacks {
4
4
  onAdStopTimerFired(): void;
@@ -1,5 +1,5 @@
1
1
  import Hls from 'hls.js';
2
- import { a as StormcloudVideoPlayerConfig, S as Scte35Marker } from '../types-cTqIKw_D.cjs';
2
+ import { S as StormcloudVideoPlayerConfig, a as Scte35Marker } from '../types-iDjS8f_7.cjs';
3
3
  import { Scte35CueContext } from './playerTypes.cjs';
4
4
  import { Scte35CueManager } from './Scte35CueManager.cjs';
5
5
  import { AdTimingService } from './AdTimingService.cjs';
@@ -1,4 +1,4 @@
1
- import { A as AdController } from '../types-cTqIKw_D.cjs';
1
+ import { A as AdController } from '../types-iDjS8f_7.cjs';
2
2
 
3
3
  declare function getAdAudioVolume(adPlayer: AdController): number;
4
4
  declare function toggleMute(video: HTMLVideoElement, adPlayer: AdController, debug: boolean): void;
@@ -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
  import { Scte35CueSource, Scte35CueReadiness, Scte35CueContext } from './playerTypes.cjs';
3
3
 
4
4
  interface Scte35CueManagerCallbacks {
@@ -1,4 +1,4 @@
1
- import { S as Scte35Marker, I as Id3TagInfo } from '../types-cTqIKw_D.cjs';
1
+ import { a as Scte35Marker, I as Id3TagInfo } from '../types-iDjS8f_7.cjs';
2
2
 
3
3
  declare function parseCueOutDuration(value: string): number | undefined;
4
4
  declare function parseCueOutCont(value: string): {
@@ -374,9 +374,11 @@ function createHlsAdPlayer(contentVideo, options) {
374
374
  var pendingTimeouts = [];
375
375
  var STALL_TIMEOUT_MS = 4e3;
376
376
  var STALL_CHECK_INTERVAL_MS = 1e3;
377
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
377
378
  var stallWatchdogId;
378
379
  var lastAdProgressTime = 0;
379
380
  var lastAdProgressPosition = 0;
381
+ var sawDecodedVideoFrame = false;
380
382
  var trackingFired = {
381
383
  impression: false,
382
384
  start: false,
@@ -518,6 +520,61 @@ function createHlsAdPlayer(contentVideo, options) {
518
520
  var url = mediaFile.url.toLowerCase();
519
521
  return type.startsWith("video/") || url.endsWith(".mp4") || url.includes(".mp4?");
520
522
  }
523
+ function isAudioMediaFile(mediaFile) {
524
+ var type = mediaFile.type.toLowerCase();
525
+ var url = (mediaFile.url.toLowerCase().split("?")[0] || "").trim();
526
+ 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");
527
+ }
528
+ function collectVideoMediaFiles(scope, verbose) {
529
+ var mediaFileElements = scope.querySelectorAll("MediaFile");
530
+ var mediaFiles = [];
531
+ if (verbose) {
532
+ console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
533
+ }
534
+ mediaFileElements.forEach(function(mf, index) {
535
+ var _mf_textContent;
536
+ var type = mf.getAttribute("type") || "";
537
+ var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
538
+ var width = mf.getAttribute("width") || "";
539
+ var height = mf.getAttribute("height") || "";
540
+ if (verbose) {
541
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
542
+ }
543
+ if (!url) {
544
+ if (verbose) {
545
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
546
+ }
547
+ return;
548
+ }
549
+ var bitrateAttr = mf.getAttribute("bitrate");
550
+ var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
551
+ var mediaFile = {
552
+ url: url,
553
+ type: type,
554
+ width: parseInt(width || "1920", 10),
555
+ height: parseInt(height || "1080", 10),
556
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
557
+ };
558
+ if (isAudioMediaFile(mediaFile)) {
559
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
560
+ return;
561
+ }
562
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
563
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
564
+ 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'));
565
+ return;
566
+ }
567
+ if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
568
+ mediaFiles.push(mediaFile);
569
+ if (verbose) {
570
+ console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
571
+ }
572
+ } else if (verbose) {
573
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
574
+ }
575
+ });
576
+ return mediaFiles;
577
+ }
521
578
  function createEmptyTrackingUrls() {
522
579
  return {
523
580
  impression: [],
@@ -605,37 +662,7 @@ function createHlsAdPlayer(contentVideo, options) {
605
662
  var durationText = ((_xmlDoc_querySelector3 = xmlDoc.querySelector("Duration")) === null || _xmlDoc_querySelector3 === void 0 ? void 0 : _xmlDoc_querySelector3.textContent) || "00:00:30";
606
663
  var durationParts = durationText.split(":");
607
664
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
608
- var mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
609
- var mediaFiles = [];
610
- console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
611
- mediaFileElements.forEach(function(mf, index) {
612
- var _mf_textContent;
613
- var type = mf.getAttribute("type") || "";
614
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
615
- var width = mf.getAttribute("width") || "";
616
- var height = mf.getAttribute("height") || "";
617
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
618
- var mediaFile = {
619
- url: url,
620
- type: type,
621
- width: parseInt(width || "1920", 10),
622
- height: parseInt(height || "1080", 10),
623
- bitrate: void 0
624
- };
625
- if (!url) {
626
- console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
627
- return;
628
- }
629
- var bitrateAttr = mf.getAttribute("bitrate");
630
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
631
- mediaFile.bitrate = bitrateValue && bitrateValue > 0 ? bitrateValue : void 0;
632
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
633
- mediaFiles.push(mediaFile);
634
- console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
635
- } else {
636
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
637
- }
638
- });
665
+ var mediaFiles = collectVideoMediaFiles(xmlDoc, true);
639
666
  if (mediaFiles.length === 0) {
640
667
  if (isNoAdAvailable) {
641
668
  console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
@@ -683,28 +710,7 @@ function createHlsAdPlayer(contentVideo, options) {
683
710
  var durationText = ((_adElement_querySelector3 = adElement.querySelector("Duration")) === null || _adElement_querySelector3 === void 0 ? void 0 : _adElement_querySelector3.textContent) || "00:00:30";
684
711
  var durationParts = durationText.split(":");
685
712
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
686
- var mediaFileElements = adElement.querySelectorAll("MediaFile");
687
- var mediaFiles = [];
688
- mediaFileElements.forEach(function(mf) {
689
- var _mf_textContent;
690
- var type = mf.getAttribute("type") || "";
691
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
692
- var width = mf.getAttribute("width") || "";
693
- var height = mf.getAttribute("height") || "";
694
- if (!url) return;
695
- var bitrateAttr = mf.getAttribute("bitrate");
696
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
697
- var mediaFile = {
698
- url: url,
699
- type: type,
700
- width: parseInt(width || "1920", 10),
701
- height: parseInt(height || "1080", 10),
702
- bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
703
- };
704
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
705
- mediaFiles.push(mediaFile);
706
- }
707
- });
713
+ var mediaFiles = collectVideoMediaFiles(adElement, false);
708
714
  if (mediaFiles.length === 0) {
709
715
  if (isNoAdAvailable) {
710
716
  console.warn("[HlsAdPlayer] Pod <Ad> indicates no ad available");
@@ -1025,6 +1031,14 @@ function createHlsAdPlayer(contentVideo, options) {
1025
1031
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1026
1032
  }
1027
1033
  };
1034
+ var onLoadedMetadata = function onLoadedMetadata() {
1035
+ if (!adPlaying || !adVideoElement) return;
1036
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1037
+ if (videoWidth <= 1 || videoHeight <= 1) {
1038
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1039
+ handleVideoDecodeFailure();
1040
+ }
1041
+ };
1028
1042
  var onPlaying = function onPlaying() {
1029
1043
  startStallWatchdog();
1030
1044
  if (!currentAd || trackingFired.start) return;
@@ -1076,6 +1090,10 @@ function createHlsAdPlayer(contentVideo, options) {
1076
1090
  type: "timeupdate",
1077
1091
  handler: onTimeUpdate
1078
1092
  },
1093
+ {
1094
+ type: "loadedmetadata",
1095
+ handler: onLoadedMetadata
1096
+ },
1079
1097
  {
1080
1098
  type: "playing",
1081
1099
  handler: onPlaying
@@ -1265,13 +1283,51 @@ function createHlsAdPlayer(contentVideo, options) {
1265
1283
  }
1266
1284
  handleAdError();
1267
1285
  }
1286
+ function getDecodedVideoFrameCount(video) {
1287
+ try {
1288
+ if (typeof video.getVideoPlaybackQuality === "function") {
1289
+ var quality = video.getVideoPlaybackQuality();
1290
+ if (quality && typeof quality.totalVideoFrames === "number") {
1291
+ return quality.totalVideoFrames;
1292
+ }
1293
+ }
1294
+ var legacyCount = video.webkitDecodedFrameCount;
1295
+ if (typeof legacyCount === "number") {
1296
+ return legacyCount;
1297
+ }
1298
+ } catch (error) {
1299
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1300
+ }
1301
+ return void 0;
1302
+ }
1303
+ function handleVideoDecodeFailure() {
1304
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1305
+ clearStallWatchdog();
1306
+ if (currentAd) {
1307
+ fireTrackingPixels(currentAd.trackingUrls.error);
1308
+ }
1309
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1310
+ return;
1311
+ }
1312
+ handleAdError();
1313
+ }
1268
1314
  function startStallWatchdog() {
1269
1315
  clearStallWatchdog();
1270
1316
  noteAdProgress();
1317
+ sawDecodedVideoFrame = false;
1271
1318
  stallWatchdogId = window.setInterval(function() {
1272
1319
  if (!adPlaying || !adVideoElement) {
1273
1320
  return;
1274
1321
  }
1322
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1323
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1324
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1325
+ sawDecodedVideoFrame = true;
1326
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1327
+ handleVideoDecodeFailure();
1328
+ return;
1329
+ }
1330
+ }
1275
1331
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1276
1332
  noteAdProgress();
1277
1333
  return;