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.js CHANGED
@@ -381,6 +381,7 @@ function createHlsAdPlayer(contentVideo, options) {
381
381
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
382
382
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
383
383
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
384
+ var FRAME_COUNTER_RELIABLE_PROP = "__stormcloudFrameCounterReliable";
384
385
  var adVideoElement = (_contentVideo_AD_VIDEO_PROP = contentVideo[AD_VIDEO_PROP]) !== null && _contentVideo_AD_VIDEO_PROP !== void 0 ? _contentVideo_AD_VIDEO_PROP : void 0;
385
386
  var adHls;
386
387
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
@@ -395,9 +396,12 @@ function createHlsAdPlayer(contentVideo, options) {
395
396
  var pendingTimeouts = [];
396
397
  var STALL_TIMEOUT_MS = 4e3;
397
398
  var STALL_CHECK_INTERVAL_MS = 1e3;
399
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
398
400
  var stallWatchdogId;
399
401
  var lastAdProgressTime = 0;
400
402
  var lastAdProgressPosition = 0;
403
+ var sawDecodedVideoFrame = false;
404
+ var videoFrameCallbackHandle;
401
405
  var trackingFired = {
402
406
  impression: false,
403
407
  start: false,
@@ -578,6 +582,11 @@ function createHlsAdPlayer(contentVideo, options) {
578
582
  console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
579
583
  return;
580
584
  }
585
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
586
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
587
+ 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'));
588
+ return;
589
+ }
581
590
  if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
582
591
  mediaFiles.push(mediaFile);
583
592
  if (verbose) {
@@ -1045,6 +1054,14 @@ function createHlsAdPlayer(contentVideo, options) {
1045
1054
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1046
1055
  }
1047
1056
  };
1057
+ var onLoadedMetadata = function onLoadedMetadata() {
1058
+ if (!adPlaying || !adVideoElement) return;
1059
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1060
+ if (videoWidth <= 1 || videoHeight <= 1) {
1061
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1062
+ handleVideoDecodeFailure();
1063
+ }
1064
+ };
1048
1065
  var onPlaying = function onPlaying() {
1049
1066
  startStallWatchdog();
1050
1067
  if (!currentAd || trackingFired.start) return;
@@ -1096,6 +1113,10 @@ function createHlsAdPlayer(contentVideo, options) {
1096
1113
  type: "timeupdate",
1097
1114
  handler: onTimeUpdate
1098
1115
  },
1116
+ {
1117
+ type: "loadedmetadata",
1118
+ handler: onLoadedMetadata
1119
+ },
1099
1120
  {
1100
1121
  type: "playing",
1101
1122
  handler: onPlaying
@@ -1267,6 +1288,7 @@ function createHlsAdPlayer(contentVideo, options) {
1267
1288
  clearInterval(stallWatchdogId);
1268
1289
  stallWatchdogId = void 0;
1269
1290
  }
1291
+ cancelVideoFrameProbe();
1270
1292
  }
1271
1293
  function noteAdProgress() {
1272
1294
  lastAdProgressTime = Date.now();
@@ -1285,13 +1307,86 @@ function createHlsAdPlayer(contentVideo, options) {
1285
1307
  }
1286
1308
  handleAdError();
1287
1309
  }
1310
+ function getDecodedVideoFrameCount(video) {
1311
+ try {
1312
+ if (typeof video.getVideoPlaybackQuality === "function") {
1313
+ var quality = video.getVideoPlaybackQuality();
1314
+ if (quality && typeof quality.totalVideoFrames === "number") {
1315
+ return quality.totalVideoFrames;
1316
+ }
1317
+ }
1318
+ var legacyCount = video.webkitDecodedFrameCount;
1319
+ if (typeof legacyCount === "number") {
1320
+ return legacyCount;
1321
+ }
1322
+ } catch (error) {
1323
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1324
+ }
1325
+ return void 0;
1326
+ }
1327
+ function isFrameCounterReliable() {
1328
+ return contentVideo[FRAME_COUNTER_RELIABLE_PROP] === true;
1329
+ }
1330
+ function markFrameCounterReliable() {
1331
+ contentVideo[FRAME_COUNTER_RELIABLE_PROP] = true;
1332
+ }
1333
+ function cancelVideoFrameProbe() {
1334
+ if (videoFrameCallbackHandle != null && adVideoElement && typeof adVideoElement.cancelVideoFrameCallback === "function") {
1335
+ try {
1336
+ adVideoElement.cancelVideoFrameCallback(videoFrameCallbackHandle);
1337
+ } catch (error) {
1338
+ console.warn("[HlsAdPlayer] Error cancelling video frame callback:", error);
1339
+ }
1340
+ }
1341
+ videoFrameCallbackHandle = void 0;
1342
+ }
1343
+ function startVideoFrameProbe() {
1344
+ cancelVideoFrameProbe();
1345
+ if (!adVideoElement) return;
1346
+ var rvfc = adVideoElement.requestVideoFrameCallback;
1347
+ if (typeof rvfc !== "function") return;
1348
+ var onPresentedFrame = function onPresentedFrame() {
1349
+ videoFrameCallbackHandle = void 0;
1350
+ if (!adPlaying) return;
1351
+ sawDecodedVideoFrame = true;
1352
+ };
1353
+ try {
1354
+ videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
1355
+ } catch (error) {
1356
+ console.warn("[HlsAdPlayer] Error requesting video frame callback:", error);
1357
+ videoFrameCallbackHandle = void 0;
1358
+ }
1359
+ }
1360
+ function handleVideoDecodeFailure() {
1361
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1362
+ clearStallWatchdog();
1363
+ if (currentAd) {
1364
+ fireTrackingPixels(currentAd.trackingUrls.error);
1365
+ }
1366
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1367
+ return;
1368
+ }
1369
+ handleAdError();
1370
+ }
1288
1371
  function startStallWatchdog() {
1289
1372
  clearStallWatchdog();
1290
1373
  noteAdProgress();
1374
+ sawDecodedVideoFrame = false;
1375
+ startVideoFrameProbe();
1291
1376
  stallWatchdogId = window.setInterval(function() {
1292
1377
  if (!adPlaying || !adVideoElement) {
1293
1378
  return;
1294
1379
  }
1380
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1381
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1382
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1383
+ sawDecodedVideoFrame = true;
1384
+ markFrameCounterReliable();
1385
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && isFrameCounterReliable()) {
1386
+ handleVideoDecodeFailure();
1387
+ return;
1388
+ }
1389
+ }
1295
1390
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1296
1391
  noteAdProgress();
1297
1392
  return;
@@ -6182,17 +6277,17 @@ var AdTimingService = /*#__PURE__*/ function() {
6182
6277
  {
6183
6278
  key: "logAdState",
6184
6279
  value: function logAdState(event) {
6185
- 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;
6186
- var _ref;
6280
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
6187
6281
  if (!this.debug) {
6188
6282
  return;
6189
6283
  }
6284
+ var _this_callbacks_getAdState = this.callbacks.getAdState(), inAdBreak = _this_callbacks_getAdState.inAdBreak, showAds = _this_callbacks_getAdState.showAds, adPlaying = _this_callbacks_getAdState.adPlaying;
6190
6285
  console.log("[StormcloudVideoPlayer][AdState]", _object_spread({
6191
6286
  event: event,
6192
6287
  timestamp: /* @__PURE__ */ new Date().toISOString(),
6193
- showAds: showAds !== null && showAds !== void 0 ? showAds : false,
6194
- adPlaying: (_ref = adPlayer === null || adPlayer === void 0 ? void 0 : adPlayer.isAdPlaying()) !== null && _ref !== void 0 ? _ref : false,
6195
- inAdBreak: inAdBreak !== null && inAdBreak !== void 0 ? inAdBreak : false,
6288
+ showAds: showAds,
6289
+ adPlaying: adPlaying,
6290
+ inAdBreak: inAdBreak,
6196
6291
  activeAdRequestToken: this.activeAdRequestToken
6197
6292
  }, extra));
6198
6293
  }
@@ -8546,6 +8641,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8546
8641
  },
8547
8642
  onFailsafeTimeout: function onFailsafeTimeout() {
8548
8643
  return _this.adBreak.handleAdFailure();
8644
+ },
8645
+ getAdState: function getAdState() {
8646
+ return {
8647
+ inAdBreak: _this.adBreak.inAdBreak,
8648
+ showAds: _this.adBreak.showAds,
8649
+ adPlaying: _this.adPlayer.isAdPlaying()
8650
+ };
8549
8651
  }
8550
8652
  });
8551
8653
  this.placeholder = new PlaceholderLayer(this.video, !!this.config.debugAdTiming);