diff --git a/src/prebid.js b/src/prebid.js index 94ade8e5f83..5c5f66d6028 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -52,6 +52,7 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; +import {fillVideoDefaults} from './video.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -269,6 +270,12 @@ export const checkAdUnitSetup = hook('sync', function (adUnits) { return validatedAdUnits; }, 'checkAdUnitSetup'); +function fillAdUnitDefaults(adUnits) { + if (FEATURES.VIDEO) { + adUnits.forEach(au => fillVideoDefaults(au)) + } +} + /// /////////////////////////////// // // // Start Public APIs // @@ -658,6 +665,7 @@ pbjsInstance.requestBids = (function() { export const startAuction = hook('async', function ({ bidsBackHandler, timeout: cbTimeout, adUnits, ttlBuffer, adUnitCodes, labels, auctionId, ortb2Fragments, metrics, defer } = {}) { const s2sBidders = getS2SBidderSet(config.getConfig('s2sConfig') || []); + fillAdUnitDefaults(adUnits); adUnits = useMetrics(metrics).measureTime('requestBids.validate', () => checkAdUnitSetup(adUnits)); function auctionDone(bids, timedOut, auctionId) { diff --git a/src/video.js b/src/video.js index 7930e318874..90539306011 100644 --- a/src/video.js +++ b/src/video.js @@ -1,25 +1,21 @@ -import adapterManager from './adapterManager.js'; -import { deepAccess, logError } from './utils.js'; -import { config } from '../src/config.js'; -import {includes} from './polyfill.js'; -import { hook } from './hook.js'; +import {deepAccess, logError} from './utils.js'; +import {config} from '../src/config.js'; +import {hook} from './hook.js'; import {auctionManager} from './auctionManager.js'; -const VIDEO_MEDIA_TYPE = 'video'; export const OUTSTREAM = 'outstream'; export const INSTREAM = 'instream'; -/** - * Helper functions for working with video-enabled adUnits - */ -export const videoAdUnit = adUnit => { - const mediaType = adUnit.mediaType === VIDEO_MEDIA_TYPE; - const mediaTypes = deepAccess(adUnit, 'mediaTypes.video'); - return mediaType || mediaTypes; -}; -export const videoBidder = bid => includes(adapterManager.videoAdapters, bid.bidder); -export const hasNonVideoBidder = adUnit => - adUnit.bids.filter(bid => !videoBidder(bid)).length; +export function fillVideoDefaults(adUnit) { + const video = adUnit?.mediaTypes?.video; + if (video != null && video.plcmt == null) { + if (video.context === OUTSTREAM || [2, 3, 4].includes(video.placement)) { + video.plcmt = 4; + } else if (video.context !== OUTSTREAM && [2, 6].includes(video.playmethod)) { + video.plcmt = 2; + } + } +} /** * @typedef {object} VideoBid diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 61621c7ec42..0911f087587 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,4 +1,4 @@ -import { isValidVideoBid } from 'src/video.js'; +import {fillVideoDefaults, isValidVideoBid} from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; @@ -7,97 +7,169 @@ describe('video.js', function () { hook.ready(); }); - it('validates valid instream bids', function () { - const bid = { - adId: '456xyz', - vastUrl: 'http://www.example.com/vastUrl', - transactionId: 'au' - }; - const adUnits = [{ - transactionId: 'au', - mediaTypes: { - video: {context: 'instream'} - } - }]; - const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); - expect(valid).to.equal(true); - }); + describe('fillVideoDefaults', () => { + function fillDefaults(videoMediaType = {}) { + const adUnit = {mediaTypes: {video: videoMediaType}}; + fillVideoDefaults(adUnit); + return adUnit.mediaTypes.video; + } - it('catches invalid instream bids', function () { - const bid = { - transactionId: 'au' - }; - const adUnits = [{ - transactionId: 'au', - mediaTypes: { - video: {context: 'instream'} - } - }]; - const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); - expect(valid).to.equal(false); - }); + describe('should set plcmt = 4 when', () => { + it('context is "outstream"', () => { + expect(fillDefaults({context: 'outstream'})).to.eql({ + context: 'outstream', + plcmt: 4 + }) + }); + [2, 3, 4].forEach(placement => { + it(`placemement is "${placement}"`, () => { + expect(fillDefaults({placement})).to.eql({ + placement, + plcmt: 4 + }); + }) + }); + }); + describe('should set plcmt = 2 when', () => { + [2, 6].forEach(playmethod => { + it(`playmethod is "${playmethod}"`, () => { + expect(fillDefaults({playmethod})).to.eql({ + playmethod, + plcmt: 2, + }); + }); + }); + }); + describe('should not set plcmt when', () => { + Object.entries({ + 'it was set by pub (context=outstream)': { + expected: 1, + video: { + context: 'outstream', + plcmt: 1 + } + }, + 'it was set by pub (placement=2)': { + expected: 1, + video: { + placement: 2, + plcmt: 1 + } + }, + 'placement not in 2, 3, 4': { + expected: undefined, + video: { + placement: 1 + } + }, + 'it was set by pub (playmethod=2)': { + expected: 1, + video: { + plcmt: 1, + playmethod: 2 + } + } + }).forEach(([t, {expected, video}]) => { + it(t, () => { + expect(fillDefaults(video).plcmt).to.eql(expected); + }) + }) + }) + }) - it('catches invalid bids when prebid-cache is disabled', function () { - const adUnits = [{ - transactionId: 'au', - bidder: 'vastOnlyVideoBidder', - mediaTypes: {video: {}}, - }]; + describe('isValidVideoBid', () => { + it('validates valid instream bids', function () { + const bid = { + adId: '456xyz', + vastUrl: 'http://www.example.com/vastUrl', + transactionId: 'au' + }; + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'instream'} + } + }]; + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); + expect(valid).to.equal(true); + }); - const valid = isValidVideoBid({ transactionId: 'au', vastXml: 'vast' }, {index: stubAuctionIndex({adUnits})}); + it('catches invalid instream bids', function () { + const bid = { + transactionId: 'au' + }; + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'instream'} + } + }]; + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); + expect(valid).to.equal(false); + }); - expect(valid).to.equal(false); - }); + it('catches invalid bids when prebid-cache is disabled', function () { + const adUnits = [{ + transactionId: 'au', + bidder: 'vastOnlyVideoBidder', + mediaTypes: {video: {}}, + }]; - it('validates valid outstream bids', function () { - const bid = { - transactionId: 'au', - renderer: { - url: 'render.url', - render: () => true, - } - }; - const adUnits = [{ - transactionId: 'au', - mediaTypes: { - video: {context: 'outstream'} - } - }]; - const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); - expect(valid).to.equal(true); - }); + const valid = isValidVideoBid({ transactionId: 'au', vastXml: 'vast' }, {index: stubAuctionIndex({adUnits})}); - it('validates valid outstream bids with a publisher defined renderer', function () { - const bid = { - transactionId: 'au', - }; - const adUnits = [{ - transactionId: 'au', - mediaTypes: { - video: { - context: 'outstream', + expect(valid).to.equal(false); + }); + + it('validates valid outstream bids', function () { + const bid = { + transactionId: 'au', + renderer: { + url: 'render.url', + render: () => true, } - }, - renderer: { - url: 'render.url', - render: () => true, - } - }]; - const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); - expect(valid).to.equal(true); - }); + }; + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'outstream'} + } + }]; + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); + expect(valid).to.equal(true); + }); - it('catches invalid outstream bids', function () { - const bid = { - transactionId: 'au', - }; - const adUnits = [{ - transactionId: 'au', - mediaTypes: { - video: {context: 'outstream'} - } - }]; - const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); - expect(valid).to.equal(false); - }); + it('validates valid outstream bids with a publisher defined renderer', function () { + const bid = { + transactionId: 'au', + }; + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: { + context: 'outstream', + } + }, + renderer: { + url: 'render.url', + render: () => true, + } + }]; + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); + expect(valid).to.equal(true); + }); + + it('catches invalid outstream bids', function () { + const bid = { + transactionId: 'au', + }; + const adUnits = [{ + transactionId: 'au', + mediaTypes: { + video: {context: 'outstream'} + } + }]; + const valid = isValidVideoBid(bid, {index: stubAuctionIndex({adUnits})}); + expect(valid).to.equal(false); + }); + }) });