stormcloud-video-player 0.8.48 → 0.8.50

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.cjs CHANGED
@@ -601,6 +601,7 @@ function createHlsAdPlayer(contentVideo, options) {
601
601
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
602
602
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
603
603
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
604
+ var FRAME_COUNTER_RELIABLE_PROP = "__stormcloudFrameCounterReliable";
604
605
  var adVideoElement = (_contentVideo_AD_VIDEO_PROP = contentVideo[AD_VIDEO_PROP]) !== null && _contentVideo_AD_VIDEO_PROP !== void 0 ? _contentVideo_AD_VIDEO_PROP : void 0;
605
606
  var adHls;
606
607
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
@@ -615,9 +616,12 @@ function createHlsAdPlayer(contentVideo, options) {
615
616
  var pendingTimeouts = [];
616
617
  var STALL_TIMEOUT_MS = 4e3;
617
618
  var STALL_CHECK_INTERVAL_MS = 1e3;
619
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
618
620
  var stallWatchdogId;
619
621
  var lastAdProgressTime = 0;
620
622
  var lastAdProgressPosition = 0;
623
+ var sawDecodedVideoFrame = false;
624
+ var videoFrameCallbackHandle;
621
625
  var trackingFired = {
622
626
  impression: false,
623
627
  start: false,
@@ -798,6 +802,11 @@ function createHlsAdPlayer(contentVideo, options) {
798
802
  console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
799
803
  return;
800
804
  }
805
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
806
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
807
+ 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'));
808
+ return;
809
+ }
801
810
  if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
802
811
  mediaFiles.push(mediaFile);
803
812
  if (verbose) {
@@ -1265,6 +1274,14 @@ function createHlsAdPlayer(contentVideo, options) {
1265
1274
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1266
1275
  }
1267
1276
  };
1277
+ var onLoadedMetadata = function onLoadedMetadata() {
1278
+ if (!adPlaying || !adVideoElement) return;
1279
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1280
+ if (videoWidth <= 1 || videoHeight <= 1) {
1281
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1282
+ handleVideoDecodeFailure();
1283
+ }
1284
+ };
1268
1285
  var onPlaying = function onPlaying() {
1269
1286
  startStallWatchdog();
1270
1287
  if (!currentAd || trackingFired.start) return;
@@ -1316,6 +1333,10 @@ function createHlsAdPlayer(contentVideo, options) {
1316
1333
  type: "timeupdate",
1317
1334
  handler: onTimeUpdate
1318
1335
  },
1336
+ {
1337
+ type: "loadedmetadata",
1338
+ handler: onLoadedMetadata
1339
+ },
1319
1340
  {
1320
1341
  type: "playing",
1321
1342
  handler: onPlaying
@@ -1487,6 +1508,7 @@ function createHlsAdPlayer(contentVideo, options) {
1487
1508
  clearInterval(stallWatchdogId);
1488
1509
  stallWatchdogId = void 0;
1489
1510
  }
1511
+ cancelVideoFrameProbe();
1490
1512
  }
1491
1513
  function noteAdProgress() {
1492
1514
  lastAdProgressTime = Date.now();
@@ -1505,13 +1527,86 @@ function createHlsAdPlayer(contentVideo, options) {
1505
1527
  }
1506
1528
  handleAdError();
1507
1529
  }
1530
+ function getDecodedVideoFrameCount(video) {
1531
+ try {
1532
+ if (typeof video.getVideoPlaybackQuality === "function") {
1533
+ var quality = video.getVideoPlaybackQuality();
1534
+ if (quality && typeof quality.totalVideoFrames === "number") {
1535
+ return quality.totalVideoFrames;
1536
+ }
1537
+ }
1538
+ var legacyCount = video.webkitDecodedFrameCount;
1539
+ if (typeof legacyCount === "number") {
1540
+ return legacyCount;
1541
+ }
1542
+ } catch (error) {
1543
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1544
+ }
1545
+ return void 0;
1546
+ }
1547
+ function isFrameCounterReliable() {
1548
+ return contentVideo[FRAME_COUNTER_RELIABLE_PROP] === true;
1549
+ }
1550
+ function markFrameCounterReliable() {
1551
+ contentVideo[FRAME_COUNTER_RELIABLE_PROP] = true;
1552
+ }
1553
+ function cancelVideoFrameProbe() {
1554
+ if (videoFrameCallbackHandle != null && adVideoElement && typeof adVideoElement.cancelVideoFrameCallback === "function") {
1555
+ try {
1556
+ adVideoElement.cancelVideoFrameCallback(videoFrameCallbackHandle);
1557
+ } catch (error) {
1558
+ console.warn("[HlsAdPlayer] Error cancelling video frame callback:", error);
1559
+ }
1560
+ }
1561
+ videoFrameCallbackHandle = void 0;
1562
+ }
1563
+ function startVideoFrameProbe() {
1564
+ cancelVideoFrameProbe();
1565
+ if (!adVideoElement) return;
1566
+ var rvfc = adVideoElement.requestVideoFrameCallback;
1567
+ if (typeof rvfc !== "function") return;
1568
+ var onPresentedFrame = function onPresentedFrame() {
1569
+ videoFrameCallbackHandle = void 0;
1570
+ if (!adPlaying) return;
1571
+ sawDecodedVideoFrame = true;
1572
+ };
1573
+ try {
1574
+ videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
1575
+ } catch (error) {
1576
+ console.warn("[HlsAdPlayer] Error requesting video frame callback:", error);
1577
+ videoFrameCallbackHandle = void 0;
1578
+ }
1579
+ }
1580
+ function handleVideoDecodeFailure() {
1581
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1582
+ clearStallWatchdog();
1583
+ if (currentAd) {
1584
+ fireTrackingPixels(currentAd.trackingUrls.error);
1585
+ }
1586
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1587
+ return;
1588
+ }
1589
+ handleAdError();
1590
+ }
1508
1591
  function startStallWatchdog() {
1509
1592
  clearStallWatchdog();
1510
1593
  noteAdProgress();
1594
+ sawDecodedVideoFrame = false;
1595
+ startVideoFrameProbe();
1511
1596
  stallWatchdogId = window.setInterval(function() {
1512
1597
  if (!adPlaying || !adVideoElement) {
1513
1598
  return;
1514
1599
  }
1600
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1601
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1602
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1603
+ sawDecodedVideoFrame = true;
1604
+ markFrameCounterReliable();
1605
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && isFrameCounterReliable()) {
1606
+ handleVideoDecodeFailure();
1607
+ return;
1608
+ }
1609
+ }
1515
1610
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1516
1611
  noteAdProgress();
1517
1612
  return;
@@ -6396,17 +6491,17 @@ var AdTimingService = /*#__PURE__*/ function() {
6396
6491
  {
6397
6492
  key: "logAdState",
6398
6493
  value: function logAdState(event) {
6399
- var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, adPlayer = arguments.length > 2 ? arguments[2] : void 0, inAdBreak = arguments.length > 3 ? arguments[3] : void 0, showAds = arguments.length > 4 ? arguments[4] : void 0;
6400
- var _ref;
6494
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
6401
6495
  if (!this.debug) {
6402
6496
  return;
6403
6497
  }
6498
+ var _this_callbacks_getAdState = this.callbacks.getAdState(), inAdBreak = _this_callbacks_getAdState.inAdBreak, showAds = _this_callbacks_getAdState.showAds, adPlaying = _this_callbacks_getAdState.adPlaying;
6404
6499
  console.log("[StormcloudVideoPlayer][AdState]", _object_spread({
6405
6500
  event: event,
6406
6501
  timestamp: /* @__PURE__ */ new Date().toISOString(),
6407
- showAds: showAds !== null && showAds !== void 0 ? showAds : false,
6408
- adPlaying: (_ref = adPlayer === null || adPlayer === void 0 ? void 0 : adPlayer.isAdPlaying()) !== null && _ref !== void 0 ? _ref : false,
6409
- inAdBreak: inAdBreak !== null && inAdBreak !== void 0 ? inAdBreak : false,
6502
+ showAds: showAds,
6503
+ adPlaying: adPlaying,
6504
+ inAdBreak: inAdBreak,
6410
6505
  activeAdRequestToken: this.activeAdRequestToken
6411
6506
  }, extra));
6412
6507
  }
@@ -8756,6 +8851,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8756
8851
  },
8757
8852
  onFailsafeTimeout: function onFailsafeTimeout() {
8758
8853
  return _this.adBreak.handleAdFailure();
8854
+ },
8855
+ getAdState: function getAdState() {
8856
+ return {
8857
+ inAdBreak: _this.adBreak.inAdBreak,
8858
+ showAds: _this.adBreak.showAds,
8859
+ adPlaying: _this.adPlayer.isAdPlaying()
8860
+ };
8759
8861
  }
8760
8862
  });
8761
8863
  this.placeholder = new PlaceholderLayer(this.video, !!this.config.debugAdTiming);