• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

prebid / Prebid.js / #300

04 Jun 2025 03:18PM UTC coverage: 90.157% (+7.7%) from 82.459%
#300

push

travis-ci

prebidjs-release
Prebid 9.47.0 release

43113 of 54311 branches covered (79.38%)

63868 of 70841 relevant lines covered (90.16%)

174.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.97
/modules/videoModule/index.js
1
import { config } from '../../src/config.js';
1✔
2
import * as events from '../../src/events.js';
3
import {mergeDeep, logWarn, logError} from '../../src/utils.js';
4
import { getGlobal } from '../../src/prebidGlobal.js';
5
import { EVENTS } from '../../src/constants.js';
6
import {
7
  videoEvents,
8
  AUCTION_AD_LOAD_ATTEMPT,
9
  AD_IMPRESSION,
10
  AD_ERROR,
11
  BID_IMPRESSION,
12
  BID_ERROR,
13
  AUCTION_AD_LOAD_ABORT,
14
  AUCTION_AD_LOAD_QUEUED
15
} from '../../libraries/video/constants/events.js'
16
import { PLACEMENT } from '../../libraries/video/constants/ortb.js';
17
import { videoKey } from '../../libraries/video/constants/constants.js'
18
import { videoCoreFactory } from './coreVideo.js';
19
import { gamSubmoduleFactory } from './gamAdServerSubmodule.js';
20
import { videoImpressionVerifierFactory } from './videoImpressionVerifier.js';
21
import { AdQueueCoordinator } from './adQueue.js';
22
import { getExternalVideoEventName, getExternalVideoEventPayload } from '../../libraries/video/shared/helpers.js'
23
import {VIDEO} from '../../src/mediaTypes.js';
24
import {auctionManager} from '../../src/auctionManager.js';
25
import {doRender} from '../../src/adRendering.js';
26

27
const allVideoEvents = Object.keys(videoEvents).map(eventKey => videoEvents[eventKey]);
37✔
28
events.addEvents(allVideoEvents.concat([AUCTION_AD_LOAD_ATTEMPT, AUCTION_AD_LOAD_QUEUED, AUCTION_AD_LOAD_ABORT, BID_IMPRESSION, BID_ERROR]).map(getExternalVideoEventName));
1✔
29

30
/**
31
 * This module adds User Video support to prebid.js
32
 * @module modules/videoModule
33
 */
34
export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvents_, gamAdServerFactory_, videoImpressionVerifierFactory_, adQueueCoordinator_) {
35
  const videoCore = videoCore_;
17✔
36
  const getConfig = getConfig_;
17✔
37
  const pbGlobal = pbGlobal_;
17✔
38
  const requestBids = pbGlobal.requestBids;
17✔
39
  const pbEvents = pbEvents_;
17✔
40
  const videoEvents = videoEvents_;
17✔
41
  const gamAdServerFactory = gamAdServerFactory_;
17✔
42
  const adQueueCoordinator = adQueueCoordinator_;
17✔
43
  let gamSubmodule;
44
  let mainContentDivId;
45
  let contentEnrichmentEnabled = true;
17✔
46
  const videoImpressionVerifierFactory = videoImpressionVerifierFactory_;
17✔
47
  let videoImpressionVerifier;
48

49
  function init() {
50
    const cache = getConfig('cache');
17✔
51
    videoImpressionVerifier = videoImpressionVerifierFactory(!!cache);
17✔
52
    getConfig(videoKey, ({ video }) => {
17✔
53
      video.providers.forEach(provider => {
7✔
54
        const divId = provider.divId;
14✔
55
        videoCore.registerProvider(provider);
14✔
56
        adQueueCoordinator.registerProvider(divId);
14✔
57
        videoCore.initProvider(divId);
14✔
58
        videoCore.onEvents(videoEvents, (type, payload) => {
14✔
59
          pbEvents.emit(getExternalVideoEventName(type), getExternalVideoEventPayload(type, payload));
1✔
60
        }, divId);
61

62
        const adServerConfig = provider.adServer;
14✔
63
        if (!gamSubmodule && adServerConfig) {
14✔
64
          gamSubmodule = gamAdServerFactory();
7✔
65
        }
66
      });
67
      contentEnrichmentEnabled = video.contentEnrichmentEnabled !== false;
7✔
68
      mainContentDivId = contentEnrichmentEnabled ? video.mainContentDivId : null;
7!
69
    });
70

71
    requestBids.before(beforeBidsRequested, 40);
17✔
72

73
    pbEvents.on(EVENTS.BID_ADJUSTMENT, function (bid) {
17✔
74
      videoImpressionVerifier.trackBid(bid);
86✔
75
    });
76

77
    pbEvents.on(getExternalVideoEventName(AD_IMPRESSION), function (payload) {
17✔
78
      triggerVideoBidEvent(BID_IMPRESSION, payload);
2✔
79
    });
80

81
    pbEvents.on(getExternalVideoEventName(AD_ERROR), function (payload) {
17✔
82
      triggerVideoBidEvent(BID_ERROR, payload);
2✔
83
    });
84
  }
85

86
  function renderBid(divId, bid, options = {}) {
×
87
    const adUrl = bid.vastUrl;
1✔
88
    options.adXml = bid.vastXml;
1✔
89
    options.winner = bid.bidder;
1✔
90

91
    loadAd(adUrl, divId, options);
1✔
92
  }
93

94
  function getOrtbVideo(divId) {
95
    return videoCore.getOrtbVideo(divId);
2✔
96
  }
97

98
  function getOrtbContent(divId) {
99
    return videoCore.getOrtbContent(divId);
2✔
100
  }
101

102
  return { init, renderBid, getOrtbVideo, getOrtbContent };
17✔
103

104
  function beforeBidsRequested(nextFn, bidderRequest) {
105
    logErrorForInvalidDivIds(bidderRequest);
5✔
106
    enrichAuction(bidderRequest);
5✔
107

108
    const bidsBackHandler = bidderRequest.bidsBackHandler;
5✔
109
    if (!bidsBackHandler || typeof bidsBackHandler !== 'function') {
5!
110
      pbEvents.on(EVENTS.AUCTION_END, auctionEnd);
5✔
111
    }
112

113
    return nextFn.call(this, bidderRequest);
5✔
114
  }
115

116
  function logErrorForInvalidDivIds(bidderRequest) {
117
    const adUnits = bidderRequest.adUnits || pbGlobal.adUnits || [];
5✔
118
    adUnits.forEach(adUnit => {
5✔
119
      const video = adUnit.video;
2✔
120
      if (!video) {
2!
121
        return;
×
122
      }
123
      if (!video.divId) {
2!
124
        logError(`Missing Video player div ID for ad unit '${adUnit.code}'`);
×
125
      }
126
      if (!videoCore.hasProviderFor(video.divId)) {
2!
127
        logError(`Video player div ID '${video.divId}' for ad unit '${adUnit.code}' does not match any registered player`);
2✔
128
      }
129
    });
130
  }
131

132
  function enrichAuction(bidderRequest) {
133
    if (mainContentDivId) {
5!
134
      enrichOrtb2(mainContentDivId, bidderRequest);
×
135
    }
136

137
    const adUnits = bidderRequest.adUnits || pbGlobal.adUnits || [];
5✔
138
    adUnits.forEach(adUnit => {
5✔
139
      const divId = getDivId(adUnit);
2✔
140
      enrichAdUnit(adUnit, divId);
2✔
141
      if (contentEnrichmentEnabled && !mainContentDivId) {
2!
142
        enrichOrtb2(divId, bidderRequest);
2✔
143
      }
144
    });
145
  }
146

147
  function getDivId(adUnit) {
148
    const videoConfig = adUnit.video;
2✔
149
    if (!adUnit.mediaTypes.video || !videoConfig) {
2!
150
      return;
×
151
    }
152

153
    return videoConfig.divId;
2✔
154
  }
155

156
  function enrichAdUnit(adUnit, videoDivId) {
157
    const ortbVideo = getOrtbVideo(videoDivId);
2✔
158
    if (!ortbVideo) {
2!
159
      return;
×
160
    }
161

162
    const video = Object.assign({}, ortbVideo, adUnit.mediaTypes.video);
2✔
163

164
    if (!video.context) {
2!
165
      video.context = ortbVideo.placement === PLACEMENT.INSTREAM ? 'instream' : 'outstream';
2!
166
    }
167

168
    if (!video.plcmt) {
2!
169
      logWarn('Video.plcmt has not been set. Failure to set a value may result in loss of bids');
2✔
170
    }
171

172
    const width = ortbVideo.w;
2✔
173
    const height = ortbVideo.h;
2✔
174
    if (!video.playerSize && width && height) {
2!
175
      video.playerSize = [width, height];
×
176
    }
177

178
    adUnit.mediaTypes.video = video;
2✔
179
  }
180

181
  function enrichOrtb2(divId, bidderRequest) {
182
    const ortbContent = getOrtbContent(divId);
2✔
183
    if (!ortbContent) {
2!
184
      return;
×
185
    }
186
    bidderRequest.ortb2 = mergeDeep({}, bidderRequest.ortb2, { site: { content: ortbContent } });
2✔
187
  }
188

189
  function auctionEnd(auctionResult) {
190
    auctionResult.adUnits.forEach(adUnit => {
3✔
191
      if (adUnit.video) {
6✔
192
        renderWinningBid(adUnit);
3✔
193
      }
194
    });
195
    pbEvents.off(EVENTS.AUCTION_END, auctionEnd);
3✔
196
  }
197

198
  function getAdServerConfig(adUnitVideoConfig) {
199
    const globalVideoConfig = getConfig(videoKey);
3✔
200
    const globalProviderConfig = globalVideoConfig.providers.find(provider => provider.divId === adUnitVideoConfig.divId) || {};
4✔
201
    if (!globalVideoConfig.adServer && !globalProviderConfig.adServer && !adUnitVideoConfig.adServer) {
3✔
202
      return;
1✔
203
    }
204
    return mergeDeep({}, globalVideoConfig.adServer, globalProviderConfig.adServer, adUnitVideoConfig.adServer);
2✔
205
  }
206

207
  async function renderWinningBid(adUnit) {
208
    const adUnitCode = adUnit.code;
3✔
209

210
    const videoConfig = adUnit.video;
3✔
211
    const divId = videoConfig.divId;
3✔
212

213
    const adServerConfig = getAdServerConfig(videoConfig);
3✔
214
    const winningBid = getWinningBid(adUnitCode);
3✔
215

216
    const options = { adUnitCode };
3✔
217

218
    async function prefetchVast() {
219
      const gamVastWrapper = await gamSubmodule.getVastXml(
×
220
        adUnit, adServerConfig.baseAdTagUrl, adServerConfig.params, winningBid
221
      );
222
      options.prefetchedVastXml = gamVastWrapper;
×
223
    }
224

225
    if (adServerConfig) {
3✔
226
      if (config.getConfig('cache.useLocal')) {
2!
227
        await prefetchVast();
×
228
      } else {
229
        const adTagUrl = gamSubmodule.getAdTagUrl(
2✔
230
          adUnit, adServerConfig.baseAdTagUrl, adServerConfig.params
231
        );
232
        loadAd(adTagUrl, divId, options);
2✔
233
        return;
2✔
234
      }
235
    }
236

237
    renderBid(divId, winningBid, options);
1✔
238
  }
239

240
  function getWinningBid(adUnitCode) {
241
    const highestCpmBids = pbGlobal.getHighestCpmBids(adUnitCode);
3✔
242
    if (!highestCpmBids.length) {
3!
243
      pbEvents.emit(getExternalVideoEventName(AUCTION_AD_LOAD_ABORT), getExternalVideoEventPayload(AUCTION_AD_LOAD_ABORT, {adUnitCode}));
×
244
      return;
×
245
    }
246
    return highestCpmBids.shift();
3✔
247
  }
248

249
  function loadAd(adTagUrl, divId, options) {
250
    adQueueCoordinator.queueAd(adTagUrl, divId, options);
3✔
251
  }
252

253
  function triggerVideoBidEvent(eventName, adEventPayload) {
254
    const bid = getBid(adEventPayload);
4✔
255
    if (!bid) {
4✔
256
      return;
2✔
257
    }
258

259
    pbGlobal.markWinningBidAsUsed(bid);
2✔
260
    pbEvents.emit(getExternalVideoEventName(eventName), getExternalVideoEventPayload(eventName, { bid, adEvent: adEventPayload }));
2✔
261
  }
262

263
  function getBid(adPayload) {
264
    const { adId, adTagUrl, wrapperAdIds } = adPayload;
4✔
265
    const bidIdentifiers = videoImpressionVerifier.getBidIdentifiers(adId, adTagUrl, wrapperAdIds);
4✔
266
    if (!bidIdentifiers) {
4!
267
      return;
×
268
    }
269

270
    const { adUnitCode, requestId, auctionId } = bidIdentifiers;
4✔
271
    const bidAdId = bidIdentifiers.adId;
4✔
272
    const { bids } = pbGlobal.getBidResponsesForAdUnitCode(adUnitCode);
4✔
273
    return ((bids) || []).find(bid => bid.adId === bidAdId && bid.requestId === requestId && bid.auctionId === auctionId);
4!
274
  }
275
}
276

277
function videoRenderHook(next, args) {
278
  if (args.bidResponse.mediaType === VIDEO) {
21✔
279
    const adUnit = auctionManager.index.getAdUnit(args.bidResponse);
1✔
280
    if (adUnit?.video) {
1!
281
      getGlobal().videoModule.renderBid(adUnit.video.divId, args.bidResponse);
×
282
      next.bail();
×
283
      return;
×
284
    }
285
  }
286
  next(args);
21✔
287
}
288

289
export function pbVideoFactory() {
290
  const videoCore = videoCoreFactory();
1✔
291
  const adQueueCoordinator = AdQueueCoordinator(videoCore, events);
1✔
292
  const pbGlobal = getGlobal();
1✔
293
  const pbVideo = PbVideo(videoCore, config.getConfig, pbGlobal, events, allVideoEvents, gamSubmoduleFactory, videoImpressionVerifierFactory, adQueueCoordinator);
1✔
294
  pbVideo.init();
1✔
295
  pbGlobal.videoModule = pbVideo;
1✔
296
  doRender.before(videoRenderHook);
1✔
297
  return pbVideo;
1✔
298
}
299

300
pbVideoFactory();
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc