stormcloud-video-player 0.8.48 → 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.
@@ -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 };
@@ -411,9 +411,11 @@ function createHlsAdPlayer(contentVideo, options) {
411
411
  var pendingTimeouts = [];
412
412
  var STALL_TIMEOUT_MS = 4e3;
413
413
  var STALL_CHECK_INTERVAL_MS = 1e3;
414
+ var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
414
415
  var stallWatchdogId;
415
416
  var lastAdProgressTime = 0;
416
417
  var lastAdProgressPosition = 0;
418
+ var sawDecodedVideoFrame = false;
417
419
  var trackingFired = {
418
420
  impression: false,
419
421
  start: false,
@@ -594,6 +596,11 @@ function createHlsAdPlayer(contentVideo, options) {
594
596
  console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
595
597
  return;
596
598
  }
599
+ var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
600
+ if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
601
+ 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'));
602
+ return;
603
+ }
597
604
  if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
598
605
  mediaFiles.push(mediaFile);
599
606
  if (verbose) {
@@ -1061,6 +1068,14 @@ function createHlsAdPlayer(contentVideo, options) {
1061
1068
  fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
1062
1069
  }
1063
1070
  };
1071
+ var onLoadedMetadata = function onLoadedMetadata() {
1072
+ if (!adPlaying || !adVideoElement) return;
1073
+ var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
1074
+ if (videoWidth <= 1 || videoHeight <= 1) {
1075
+ console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
1076
+ handleVideoDecodeFailure();
1077
+ }
1078
+ };
1064
1079
  var onPlaying = function onPlaying() {
1065
1080
  startStallWatchdog();
1066
1081
  if (!currentAd || trackingFired.start) return;
@@ -1112,6 +1127,10 @@ function createHlsAdPlayer(contentVideo, options) {
1112
1127
  type: "timeupdate",
1113
1128
  handler: onTimeUpdate
1114
1129
  },
1130
+ {
1131
+ type: "loadedmetadata",
1132
+ handler: onLoadedMetadata
1133
+ },
1115
1134
  {
1116
1135
  type: "playing",
1117
1136
  handler: onPlaying
@@ -1301,13 +1320,51 @@ function createHlsAdPlayer(contentVideo, options) {
1301
1320
  }
1302
1321
  handleAdError();
1303
1322
  }
1323
+ function getDecodedVideoFrameCount(video) {
1324
+ try {
1325
+ if (typeof video.getVideoPlaybackQuality === "function") {
1326
+ var quality = video.getVideoPlaybackQuality();
1327
+ if (quality && typeof quality.totalVideoFrames === "number") {
1328
+ return quality.totalVideoFrames;
1329
+ }
1330
+ }
1331
+ var legacyCount = video.webkitDecodedFrameCount;
1332
+ if (typeof legacyCount === "number") {
1333
+ return legacyCount;
1334
+ }
1335
+ } catch (error) {
1336
+ console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
1337
+ }
1338
+ return void 0;
1339
+ }
1340
+ function handleVideoDecodeFailure() {
1341
+ console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1342
+ clearStallWatchdog();
1343
+ if (currentAd) {
1344
+ fireTrackingPixels(currentAd.trackingUrls.error);
1345
+ }
1346
+ if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1347
+ return;
1348
+ }
1349
+ handleAdError();
1350
+ }
1304
1351
  function startStallWatchdog() {
1305
1352
  clearStallWatchdog();
1306
1353
  noteAdProgress();
1354
+ sawDecodedVideoFrame = false;
1307
1355
  stallWatchdogId = window.setInterval(function() {
1308
1356
  if (!adPlaying || !adVideoElement) {
1309
1357
  return;
1310
1358
  }
1359
+ if (!sawDecodedVideoFrame && !adVideoElement.paused) {
1360
+ var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1361
+ if (decodedFrames !== void 0 && decodedFrames > 0) {
1362
+ sawDecodedVideoFrame = true;
1363
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1364
+ handleVideoDecodeFailure();
1365
+ return;
1366
+ }
1367
+ }
1311
1368
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1312
1369
  noteAdProgress();
1313
1370
  return;