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.
@@ -223,9 +223,11 @@ function createHlsAdPlayer(contentVideo, options) {
223
223
  var pendingTimeouts = [];
224
224
  var STALL_TIMEOUT_MS = 4e3;
225
225
  var STALL_CHECK_INTERVAL_MS = 1e3;
226
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
226
227
  var stallWatchdogId;
227
228
  var lastAdProgressTime = 0;
228
229
  var lastAdProgressPosition = 0;
230
+ var sawDecodedVideoFrame = false;
229
231
  var trackingFired = {
230
232
  impression: false,
231
233
  start: false,
@@ -367,6 +369,61 @@ function createHlsAdPlayer(contentVideo, options) {
367
369
  var url = mediaFile.url.toLowerCase();
368
370
  return type.startsWith("video/") || url.endsWith(".mp4") || url.includes(".mp4?");
369
371
  }
372
+ function isAudioMediaFile(mediaFile) {
373
+ var type = mediaFile.type.toLowerCase();
374
+ var url = (mediaFile.url.toLowerCase().split("?")[0] || "").trim();
375
+ 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");
376
+ }
377
+ function collectVideoMediaFiles(scope, verbose) {
378
+ var mediaFileElements = scope.querySelectorAll("MediaFile");
379
+ var mediaFiles = [];
380
+ if (verbose) {
381
+ console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
382
+ }
383
+ mediaFileElements.forEach(function(mf, index) {
384
+ var _mf_textContent;
385
+ var type = mf.getAttribute("type") || "";
386
+ var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
387
+ var width = mf.getAttribute("width") || "";
388
+ var height = mf.getAttribute("height") || "";
389
+ if (verbose) {
390
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
391
+ }
392
+ if (!url) {
393
+ if (verbose) {
394
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
395
+ }
396
+ return;
397
+ }
398
+ var bitrateAttr = mf.getAttribute("bitrate");
399
+ var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
400
+ var mediaFile = {
401
+ url: url,
402
+ type: type,
403
+ width: parseInt(width || "1920", 10),
404
+ height: parseInt(height || "1080", 10),
405
+ bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
406
+ };
407
+ if (isAudioMediaFile(mediaFile)) {
408
+ console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
409
+ return;
410
+ }
411
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
412
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
413
+ 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'));
414
+ return;
415
+ }
416
+ if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
417
+ mediaFiles.push(mediaFile);
418
+ if (verbose) {
419
+ console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
420
+ }
421
+ } else if (verbose) {
422
+ console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
423
+ }
424
+ });
425
+ return mediaFiles;
426
+ }
370
427
  function createEmptyTrackingUrls() {
371
428
  return {
372
429
  impression: [],
@@ -454,37 +511,7 @@ function createHlsAdPlayer(contentVideo, options) {
454
511
  var durationText = ((_xmlDoc_querySelector3 = xmlDoc.querySelector("Duration")) === null || _xmlDoc_querySelector3 === void 0 ? void 0 : _xmlDoc_querySelector3.textContent) || "00:00:30";
455
512
  var durationParts = durationText.split(":");
456
513
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
457
- var mediaFileElements = xmlDoc.querySelectorAll("MediaFile");
458
- var mediaFiles = [];
459
- console.log("[HlsAdPlayer] Found ".concat(mediaFileElements.length, " MediaFile element(s) in VAST XML"));
460
- mediaFileElements.forEach(function(mf, index) {
461
- var _mf_textContent;
462
- var type = mf.getAttribute("type") || "";
463
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
464
- var width = mf.getAttribute("width") || "";
465
- var height = mf.getAttribute("height") || "";
466
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ': type="').concat(type, '", url="').concat(url, '", width="').concat(width, '", height="').concat(height, '"'));
467
- var mediaFile = {
468
- url: url,
469
- type: type,
470
- width: parseInt(width || "1920", 10),
471
- height: parseInt(height || "1080", 10),
472
- bitrate: void 0
473
- };
474
- if (!url) {
475
- console.warn("[HlsAdPlayer] MediaFile ".concat(index, " has empty URL"));
476
- return;
477
- }
478
- var bitrateAttr = mf.getAttribute("bitrate");
479
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
480
- mediaFile.bitrate = bitrateValue && bitrateValue > 0 ? bitrateValue : void 0;
481
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
482
- mediaFiles.push(mediaFile);
483
- console.log("[HlsAdPlayer] Added ".concat(isHlsMediaFile(mediaFile) ? "HLS" : "progressive", " MediaFile: ").concat(url));
484
- } else {
485
- console.log("[HlsAdPlayer] MediaFile ".concat(index, ' ignored (type="').concat(type, '" is not supported)'));
486
- }
487
- });
514
+ var mediaFiles = collectVideoMediaFiles(xmlDoc, true);
488
515
  if (mediaFiles.length === 0) {
489
516
  if (isNoAdAvailable) {
490
517
  console.warn("[HlsAdPlayer] No ads available (VAST response indicates no ads)");
@@ -532,28 +559,7 @@ function createHlsAdPlayer(contentVideo, options) {
532
559
  var durationText = ((_adElement_querySelector3 = adElement.querySelector("Duration")) === null || _adElement_querySelector3 === void 0 ? void 0 : _adElement_querySelector3.textContent) || "00:00:30";
533
560
  var durationParts = durationText.split(":");
534
561
  var duration = parseInt(durationParts[0] || "0", 10) * 3600 + parseInt(durationParts[1] || "0", 10) * 60 + parseInt(durationParts[2] || "0", 10);
535
- var mediaFileElements = adElement.querySelectorAll("MediaFile");
536
- var mediaFiles = [];
537
- mediaFileElements.forEach(function(mf) {
538
- var _mf_textContent;
539
- var type = mf.getAttribute("type") || "";
540
- var url = ((_mf_textContent = mf.textContent) === null || _mf_textContent === void 0 ? void 0 : _mf_textContent.trim()) || "";
541
- var width = mf.getAttribute("width") || "";
542
- var height = mf.getAttribute("height") || "";
543
- if (!url) return;
544
- var bitrateAttr = mf.getAttribute("bitrate");
545
- var bitrateValue = bitrateAttr ? parseInt(bitrateAttr, 10) : void 0;
546
- var mediaFile = {
547
- url: url,
548
- type: type,
549
- width: parseInt(width || "1920", 10),
550
- height: parseInt(height || "1080", 10),
551
- bitrate: bitrateValue && bitrateValue > 0 ? bitrateValue : void 0
552
- };
553
- if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
554
- mediaFiles.push(mediaFile);
555
- }
556
- });
562
+ var mediaFiles = collectVideoMediaFiles(adElement, false);
557
563
  if (mediaFiles.length === 0) {
558
564
  if (isNoAdAvailable) {
559
565
  console.warn("[HlsAdPlayer] Pod <Ad> indicates no ad available");
@@ -874,6 +880,14 @@ function createHlsAdPlayer(contentVideo, options) {
874
880
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
875
881
  }
876
882
  };
883
+ var onLoadedMetadata = function onLoadedMetadata() {
884
+ if (!adPlaying || !adVideoElement) return;
885
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
886
+ if (videoWidth <= 1 || videoHeight <= 1) {
887
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
888
+ handleVideoDecodeFailure();
889
+ }
890
+ };
877
891
  var onPlaying = function onPlaying() {
878
892
  startStallWatchdog();
879
893
  if (!currentAd || trackingFired.start) return;
@@ -925,6 +939,10 @@ function createHlsAdPlayer(contentVideo, options) {
925
939
  type: "timeupdate",
926
940
  handler: onTimeUpdate
927
941
  },
942
+ {
943
+ type: "loadedmetadata",
944
+ handler: onLoadedMetadata
945
+ },
928
946
  {
929
947
  type: "playing",
930
948
  handler: onPlaying
@@ -1114,13 +1132,51 @@ function createHlsAdPlayer(contentVideo, options) {
1114
1132
  }
1115
1133
  handleAdError();
1116
1134
  }
1135
+ function getDecodedVideoFrameCount(video) {
1136
+ try {
1137
+ if (typeof video.getVideoPlaybackQuality === "function") {
1138
+ var quality = video.getVideoPlaybackQuality();
1139
+ if (quality && typeof quality.totalVideoFrames === "number") {
1140
+ return quality.totalVideoFrames;
1141
+ }
1142
+ }
1143
+ var legacyCount = video.webkitDecodedFrameCount;
1144
+ if (typeof legacyCount === "number") {
1145
+ return legacyCount;
1146
+ }
1147
+ } catch (error) {
1148
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1149
+ }
1150
+ return void 0;
1151
+ }
1152
+ function handleVideoDecodeFailure() {
1153
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1154
+ clearStallWatchdog();
1155
+ if (currentAd) {
1156
+ fireTrackingPixels(currentAd.trackingUrls.error);
1157
+ }
1158
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1159
+ return;
1160
+ }
1161
+ handleAdError();
1162
+ }
1117
1163
  function startStallWatchdog() {
1118
1164
  clearStallWatchdog();
1119
1165
  noteAdProgress();
1166
+ sawDecodedVideoFrame = false;
1120
1167
  stallWatchdogId = window.setInterval(function() {
1121
1168
  if (!adPlaying || !adVideoElement) {
1122
1169
  return;
1123
1170
  }
1171
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1172
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1173
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1174
+ sawDecodedVideoFrame = true;
1175
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1176
+ handleVideoDecodeFailure();
1177
+ return;
1178
+ }
1179
+ }
1124
1180
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1125
1181
  noteAdProgress();
1126
1182
  return;