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.
@@ -0,0 +1,133 @@
1
+ interface AdBreak {
2
+ id?: string;
3
+ startTimeMs: number;
4
+ durationMs?: number;
5
+ vastTagUrl?: string;
6
+ }
7
+ interface StormcloudVideoPlayerConfig {
8
+ videoElement: HTMLVideoElement;
9
+ src: string;
10
+ autoplay?: boolean;
11
+ muted?: boolean;
12
+ allowNativeHls?: boolean;
13
+ lowLatencyMode?: boolean;
14
+ isLiveStream?: boolean;
15
+ pauseContentDuringAds?: boolean;
16
+ driftToleranceMs?: number;
17
+ immediateManifestAds?: boolean;
18
+ debugAdTiming?: boolean;
19
+ adFailsafeTimeoutMs?: number;
20
+ adBreakCheckIntervalMs?: number;
21
+ maxAdBreakExtensionMs?: number;
22
+ minRemainingMsToStartAd?: number;
23
+ showCustomControls?: boolean;
24
+ hideLoadingIndicator?: boolean;
25
+ onVolumeToggle?: () => void;
26
+ onFullscreenToggle?: () => void;
27
+ onControlClick?: () => void;
28
+ licenseKey?: string;
29
+ vastTagUrl?: string;
30
+ isVmap?: boolean;
31
+ vmapUrl?: string;
32
+ vastMode?: 'adstorm' | 'default';
33
+ minSegmentsBeforePlay?: number;
34
+ ctvAdRequest?: boolean;
35
+ adTest?: boolean;
36
+ podMaxAds?: number;
37
+ podMaxDurationMs?: number;
38
+ podExactDuration?: boolean;
39
+ }
40
+ interface Scte35Marker {
41
+ type: "start" | "end" | "progress";
42
+ ptsSeconds?: number;
43
+ durationSeconds?: number;
44
+ raw?: unknown;
45
+ }
46
+ interface Id3TagInfo {
47
+ key: string;
48
+ value: string | Uint8Array;
49
+ ptsSeconds?: number;
50
+ }
51
+ interface AdController {
52
+ initialize: () => void;
53
+ requestAds: (vastTagUrl: string) => Promise<void>;
54
+ play: () => Promise<void>;
55
+ pause: () => void;
56
+ resume: () => void;
57
+ stop: () => Promise<void>;
58
+ destroy: () => void;
59
+ isAdPlaying: () => boolean;
60
+ resize: (width: number, height: number) => void;
61
+ on: (event: string, listener: (payload?: any) => void) => void;
62
+ off: (event: string, listener: (payload?: any) => void) => void;
63
+ updateOriginalMutedState: (muted: boolean, volume?: number) => void;
64
+ getOriginalMutedState: () => boolean;
65
+ getOriginalVolume: () => number;
66
+ setAdVolume: (volume: number) => void;
67
+ getAdVolume: () => number;
68
+ showPlaceholder: () => void;
69
+ hidePlaceholder: () => void;
70
+ getLoadedAdDuration?: () => number | undefined;
71
+ getLoadedAdId?: () => string | undefined;
72
+ getPodAdCount?: () => number;
73
+ getPodTotalDurationMs?: () => number;
74
+ }
75
+ interface ClientInfo {
76
+ brand: string;
77
+ os: string;
78
+ model: string;
79
+ deviceType: "tv" | "mobile" | "tablet" | "desktop";
80
+ isSmartTV: boolean;
81
+ isAndroid: boolean;
82
+ isWebView: boolean;
83
+ isWebApp: boolean;
84
+ domain: string;
85
+ origin: string;
86
+ path: string;
87
+ userAgent: string;
88
+ vendor: string;
89
+ platform: string;
90
+ screen: {
91
+ width?: number;
92
+ height?: number;
93
+ availWidth?: number;
94
+ availHeight?: number;
95
+ orientation?: string;
96
+ pixelDepth?: number;
97
+ };
98
+ hardwareConcurrency: number;
99
+ deviceMemory: number | null;
100
+ maxTouchPoints: number;
101
+ language: string;
102
+ languages: string;
103
+ cookieEnabled: boolean;
104
+ doNotTrack: string;
105
+ referrer: string;
106
+ visibilityState: string;
107
+ }
108
+ interface PlayerAnalyticsContext {
109
+ inputStreamType?: string;
110
+ debugAdTiming?: boolean;
111
+ }
112
+ interface AdDetectInfo {
113
+ source: "scte35";
114
+ durationSeconds?: number;
115
+ ptsSeconds?: number;
116
+ detectedAtFragmentSn?: number;
117
+ timestamp: string;
118
+ }
119
+ interface AdLoadedInfo {
120
+ source: "vast" | "hls";
121
+ vastUrl?: string;
122
+ durationSeconds?: number;
123
+ timestamp: string;
124
+ }
125
+ interface AdImpressionInfo {
126
+ source: "vast" | "hls";
127
+ adIndex: number;
128
+ adUrl?: string;
129
+ durationSeconds?: number;
130
+ timestamp: string;
131
+ }
132
+
133
+ export type { AdController as A, ClientInfo as C, Id3TagInfo as I, PlayerAnalyticsContext as P, StormcloudVideoPlayerConfig as S, Scte35Marker as a, AdBreak as b, AdDetectInfo as c, AdImpressionInfo as d, AdLoadedInfo as e };
@@ -397,6 +397,7 @@ function createHlsAdPlayer(contentVideo, options) {
397
397
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
398
398
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
399
399
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
400
+ var FRAME_COUNTER_RELIABLE_PROP = "__stormcloudFrameCounterReliable";
400
401
  var adVideoElement = (_contentVideo_AD_VIDEO_PROP = contentVideo[AD_VIDEO_PROP]) !== null && _contentVideo_AD_VIDEO_PROP !== void 0 ? _contentVideo_AD_VIDEO_PROP : void 0;
401
402
  var adHls;
402
403
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
@@ -411,9 +412,12 @@ function createHlsAdPlayer(contentVideo, options) {
411
412
  var pendingTimeouts = [];
412
413
  var STALL_TIMEOUT_MS = 4e3;
413
414
  var STALL_CHECK_INTERVAL_MS = 1e3;
415
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
414
416
  var stallWatchdogId;
415
417
  var lastAdProgressTime = 0;
416
418
  var lastAdProgressPosition = 0;
419
+ var sawDecodedVideoFrame = false;
420
+ var videoFrameCallbackHandle;
417
421
  var trackingFired = {
418
422
  impression: false,
419
423
  start: false,
@@ -594,6 +598,11 @@ function createHlsAdPlayer(contentVideo, options) {
594
598
  console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
595
599
  return;
596
600
  }
601
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
602
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
603
+ 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'));
604
+ return;
605
+ }
597
606
  if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
598
607
  mediaFiles.push(mediaFile);
599
608
  if (verbose) {
@@ -1061,6 +1070,14 @@ function createHlsAdPlayer(contentVideo, options) {
1061
1070
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1062
1071
  }
1063
1072
  };
1073
+ var onLoadedMetadata = function onLoadedMetadata() {
1074
+ if (!adPlaying || !adVideoElement) return;
1075
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1076
+ if (videoWidth <= 1 || videoHeight <= 1) {
1077
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1078
+ handleVideoDecodeFailure();
1079
+ }
1080
+ };
1064
1081
  var onPlaying = function onPlaying() {
1065
1082
  startStallWatchdog();
1066
1083
  if (!currentAd || trackingFired.start) return;
@@ -1112,6 +1129,10 @@ function createHlsAdPlayer(contentVideo, options) {
1112
1129
  type: "timeupdate",
1113
1130
  handler: onTimeUpdate
1114
1131
  },
1132
+ {
1133
+ type: "loadedmetadata",
1134
+ handler: onLoadedMetadata
1135
+ },
1115
1136
  {
1116
1137
  type: "playing",
1117
1138
  handler: onPlaying
@@ -1283,6 +1304,7 @@ function createHlsAdPlayer(contentVideo, options) {
1283
1304
  clearInterval(stallWatchdogId);
1284
1305
  stallWatchdogId = void 0;
1285
1306
  }
1307
+ cancelVideoFrameProbe();
1286
1308
  }
1287
1309
  function noteAdProgress() {
1288
1310
  lastAdProgressTime = Date.now();
@@ -1301,13 +1323,86 @@ function createHlsAdPlayer(contentVideo, options) {
1301
1323
  }
1302
1324
  handleAdError();
1303
1325
  }
1326
+ function getDecodedVideoFrameCount(video) {
1327
+ try {
1328
+ if (typeof video.getVideoPlaybackQuality === "function") {
1329
+ var quality = video.getVideoPlaybackQuality();
1330
+ if (quality && typeof quality.totalVideoFrames === "number") {
1331
+ return quality.totalVideoFrames;
1332
+ }
1333
+ }
1334
+ var legacyCount = video.webkitDecodedFrameCount;
1335
+ if (typeof legacyCount === "number") {
1336
+ return legacyCount;
1337
+ }
1338
+ } catch (error) {
1339
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1340
+ }
1341
+ return void 0;
1342
+ }
1343
+ function isFrameCounterReliable() {
1344
+ return contentVideo[FRAME_COUNTER_RELIABLE_PROP] === true;
1345
+ }
1346
+ function markFrameCounterReliable() {
1347
+ contentVideo[FRAME_COUNTER_RELIABLE_PROP] = true;
1348
+ }
1349
+ function cancelVideoFrameProbe() {
1350
+ if (videoFrameCallbackHandle != null && adVideoElement && typeof adVideoElement.cancelVideoFrameCallback === "function") {
1351
+ try {
1352
+ adVideoElement.cancelVideoFrameCallback(videoFrameCallbackHandle);
1353
+ } catch (error) {
1354
+ console.warn("[HlsAdPlayer] Error cancelling video frame callback:", error);
1355
+ }
1356
+ }
1357
+ videoFrameCallbackHandle = void 0;
1358
+ }
1359
+ function startVideoFrameProbe() {
1360
+ cancelVideoFrameProbe();
1361
+ if (!adVideoElement) return;
1362
+ var rvfc = adVideoElement.requestVideoFrameCallback;
1363
+ if (typeof rvfc !== "function") return;
1364
+ var onPresentedFrame = function onPresentedFrame() {
1365
+ videoFrameCallbackHandle = void 0;
1366
+ if (!adPlaying) return;
1367
+ sawDecodedVideoFrame = true;
1368
+ };
1369
+ try {
1370
+ videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
1371
+ } catch (error) {
1372
+ console.warn("[HlsAdPlayer] Error requesting video frame callback:", error);
1373
+ videoFrameCallbackHandle = void 0;
1374
+ }
1375
+ }
1376
+ function handleVideoDecodeFailure() {
1377
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1378
+ clearStallWatchdog();
1379
+ if (currentAd) {
1380
+ fireTrackingPixels(currentAd.trackingUrls.error);
1381
+ }
1382
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1383
+ return;
1384
+ }
1385
+ handleAdError();
1386
+ }
1304
1387
  function startStallWatchdog() {
1305
1388
  clearStallWatchdog();
1306
1389
  noteAdProgress();
1390
+ sawDecodedVideoFrame = false;
1391
+ startVideoFrameProbe();
1307
1392
  stallWatchdogId = window.setInterval(function() {
1308
1393
  if (!adPlaying || !adVideoElement) {
1309
1394
  return;
1310
1395
  }
1396
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1397
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1398
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1399
+ sawDecodedVideoFrame = true;
1400
+ markFrameCounterReliable();
1401
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && isFrameCounterReliable()) {
1402
+ handleVideoDecodeFailure();
1403
+ return;
1404
+ }
1405
+ }
1311
1406
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1312
1407
  noteAdProgress();
1313
1408
  return;
@@ -6148,17 +6243,17 @@ var AdTimingService = /*#__PURE__*/ function() {
6148
6243
  {
6149
6244
  key: "logAdState",
6150
6245
  value: function logAdState(event) {
6151
- 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;
6152
- var _ref;
6246
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
6153
6247
  if (!this.debug) {
6154
6248
  return;
6155
6249
  }
6250
+ var _this_callbacks_getAdState = this.callbacks.getAdState(), inAdBreak = _this_callbacks_getAdState.inAdBreak, showAds = _this_callbacks_getAdState.showAds, adPlaying = _this_callbacks_getAdState.adPlaying;
6156
6251
  console.log("[StormcloudVideoPlayer][AdState]", _object_spread({
6157
6252
  event: event,
6158
6253
  timestamp: /* @__PURE__ */ new Date().toISOString(),
6159
- showAds: showAds !== null && showAds !== void 0 ? showAds : false,
6160
- adPlaying: (_ref = adPlayer === null || adPlayer === void 0 ? void 0 : adPlayer.isAdPlaying()) !== null && _ref !== void 0 ? _ref : false,
6161
- inAdBreak: inAdBreak !== null && inAdBreak !== void 0 ? inAdBreak : false,
6254
+ showAds: showAds,
6255
+ adPlaying: adPlaying,
6256
+ inAdBreak: inAdBreak,
6162
6257
  activeAdRequestToken: this.activeAdRequestToken
6163
6258
  }, extra));
6164
6259
  }
@@ -8508,6 +8603,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8508
8603
  },
8509
8604
  onFailsafeTimeout: function onFailsafeTimeout() {
8510
8605
  return _this.adBreak.handleAdFailure();
8606
+ },
8607
+ getAdState: function getAdState() {
8608
+ return {
8609
+ inAdBreak: _this.adBreak.inAdBreak,
8610
+ showAds: _this.adBreak.showAds,
8611
+ adPlaying: _this.adPlayer.isAdPlaying()
8612
+ };
8511
8613
  }
8512
8614
  });
8513
8615
  this.placeholder = new PlaceholderLayer(this.video, !!this.config.debugAdTiming);