Skip to content

Commit

Permalink
Support multiple media formats within a single ad unit (prebid#1991)
Browse files Browse the repository at this point in the history
* Populate ad_types array on all requests

* Remove descriptionUrl

* Allow a bidder to participate on an adunit if it supports at least one of the mediaTypes

* Add hb_mediatype to standard targeting

* Determine when to trigger callback based on bid response

* Change key to hb_format

* Print banner as default when mediaTypes not defined

* Update spportedMediaTypes to include banner for adapters that were implicitly supporting banner
  • Loading branch information
matthewlane authored and dluxemburg committed Jul 17, 2018
1 parent 4d2f857 commit 0849da4
Show file tree
Hide file tree
Showing 21 changed files with 160 additions and 158 deletions.
2 changes: 1 addition & 1 deletion modules/adkernelAdnBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const spec = {

code: 'adkernelAdn',

supportedMediaTypes: [VIDEO],
supportedMediaTypes: [BANNER, VIDEO],

isBidRequestValid: function(bidRequest) {
return 'params' in bidRequest && (typeof bidRequest.params.host === 'undefined' || typeof bidRequest.params.host === 'string') &&
Expand Down
2 changes: 1 addition & 1 deletion modules/adkernelBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const spec = {

code: 'adkernel',
aliases: ['headbidding'],
supportedMediaTypes: [VIDEO],
supportedMediaTypes: [BANNER, VIDEO],
isBidRequestValid: function(bidRequest) {
return 'params' in bidRequest && typeof bidRequest.params.host !== 'undefined' &&
'zoneId' in bidRequest.params && !isNaN(Number(bidRequest.params.zoneId));
Expand Down
4 changes: 2 additions & 2 deletions modules/adxcgBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as utils from 'src/utils';
import * as url from 'src/url';
import {registerBidder} from 'src/adapters/bidderFactory';
import {NATIVE, VIDEO} from 'src/mediaTypes';
import {BANNER, NATIVE, VIDEO} from 'src/mediaTypes';

/**
* Adapter for requesting bids from adxcg.net
* updated to latest prebid repo on 2017.10.20
*/

const BIDDER_CODE = 'adxcg';
const SUPPORTED_AD_TYPES = [VIDEO, NATIVE];
const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE];
const SOURCE = 'pbjs10';
export const spec = {
code: BIDDER_CODE,
Expand Down
47 changes: 29 additions & 18 deletions modules/appnexusBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Renderer } from 'src/Renderer';
import * as utils from 'src/utils';
import { registerBidder } from 'src/adapters/bidderFactory';
import { NATIVE, VIDEO } from 'src/mediaTypes';
import { BANNER, NATIVE, VIDEO } from 'src/mediaTypes';
import find from 'core-js/library/fn/array/find';
import includes from 'core-js/library/fn/array/includes';

const BIDDER_CODE = 'appnexus';
const URL = '//ib.adnxs.com/ut/v3/prebid';
const SUPPORTED_AD_TYPES = ['banner', 'video', 'native'];
const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration',
'startdelay', 'skippable', 'playback_method', 'frameworks'];
const USER_PARAMS = ['age', 'external_uid', 'segments', 'gender', 'dnt', 'language'];
Expand All @@ -31,7 +30,7 @@ const SOURCE = 'pbjs';
export const spec = {
code: BIDDER_CODE,
aliases: ['appnexusAst', 'brealtime', 'pagescience', 'defymedia', 'gourmetads', 'matomy', 'featureforward', 'oftmedia', 'districtm'],
supportedMediaTypes: [VIDEO, NATIVE],
supportedMediaTypes: [BANNER, VIDEO, NATIVE],

/**
* Determines whether or not the given bid request is valid.
Expand Down Expand Up @@ -103,7 +102,7 @@ export const spec = {
serverResponse.tags.forEach(serverBid => {
const rtbBid = getRtbBid(serverBid);
if (rtbBid) {
if (rtbBid.cpm !== 0 && includes(SUPPORTED_AD_TYPES, rtbBid.ad_type)) {
if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) {
const bid = newBid(serverBid, rtbBid);
bid.mediaType = parseMediaType(rtbBid);
bids.push(bid);
Expand Down Expand Up @@ -197,7 +196,6 @@ function newBid(serverBid, rtbBid) {
width: rtbBid.rtb.video.player_width,
height: rtbBid.rtb.video.player_height,
vastUrl: rtbBid.rtb.video.asset_url,
descriptionUrl: rtbBid.rtb.video.asset_url,
ttl: 3600
});
// This supports Outstream Video
Expand All @@ -209,9 +207,9 @@ function newBid(serverBid, rtbBid) {
bid.adResponse.ad = bid.adResponse.ads[0];
bid.adResponse.ad.video = bid.adResponse.ad.rtb.video;
}
} else if (rtbBid.rtb['native']) {
const nativeAd = rtbBid.rtb['native'];
bid['native'] = {
} else if (rtbBid.rtb[NATIVE]) {
const nativeAd = rtbBid.rtb[NATIVE];
bid[NATIVE] = {
title: nativeAd.title,
body: nativeAd.desc,
cta: nativeAd.ctatext,
Expand Down Expand Up @@ -256,6 +254,7 @@ function bidToTag(bid) {
const tag = {};
tag.sizes = transformSizes(bid.sizes);
tag.primary_size = tag.sizes[0];
tag.ad_types = [];
tag.uuid = bid.bidId;
if (bid.params.placementId) {
tag.id = parseInt(bid.params.placementId, 10);
Expand Down Expand Up @@ -294,19 +293,24 @@ function bidToTag(bid) {
tag.keywords = getKeywords(bid.params.keywords);
}

if (bid.mediaType === 'native' || utils.deepAccess(bid, 'mediaTypes.native')) {
tag.ad_types = ['native'];
if (bid.mediaType === NATIVE || utils.deepAccess(bid, `mediaTypes.${NATIVE}`)) {
tag.ad_types.push(NATIVE);

if (bid.nativeParams) {
const nativeRequest = buildNativeRequest(bid.nativeParams);
tag['native'] = {layouts: [nativeRequest]};
tag[NATIVE] = {layouts: [nativeRequest]};
}
}

const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video');
const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`);
const context = utils.deepAccess(bid, 'mediaTypes.video.context');

if (bid.mediaType === 'video' || (videoMediaType && context !== 'outstream')) {
if (bid.mediaType === VIDEO || videoMediaType) {
tag.ad_types.push(VIDEO);
}

// instream gets vastUrl, outstream gets vastXml
if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) {
tag.require_asset_url = true;
}

Expand All @@ -318,6 +322,13 @@ function bidToTag(bid) {
.forEach(param => tag.video[param] = bid.params.video[param]);
}

if (
(utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) ||
(bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER]))
) {
tag.ad_types.push(BANNER);
}

return tag;
}

Expand Down Expand Up @@ -414,12 +425,12 @@ function handleOutstreamRendererEvents(bid, id, eventName) {

function parseMediaType(rtbBid) {
const adType = rtbBid.ad_type;
if (adType === 'video') {
return 'video';
} else if (adType === 'native') {
return 'native';
if (adType === VIDEO) {
return VIDEO;
} else if (adType === NATIVE) {
return NATIVE;
} else {
return 'banner';
return BANNER;
}
}

Expand Down
2 changes: 1 addition & 1 deletion modules/audienceNetworkBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const code = 'audienceNetwork';
const currency = 'USD';
const method = 'GET';
const url = 'https://an.facebook.com/v2/placementbid.json';
const supportedMediaTypes = ['video'];
const supportedMediaTypes = ['banner', 'video'];
const netRevenue = true;
const hb_bidder = 'fan';

Expand Down
4 changes: 2 additions & 2 deletions modules/conversantBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as utils from 'src/utils';
import {registerBidder} from 'src/adapters/bidderFactory';
import { VIDEO } from 'src/mediaTypes';
import { BANNER, VIDEO } from 'src/mediaTypes';

const BIDDER_CODE = 'conversant';
const URL = '//media.msg.dotomi.com/s2s/header/24';
Expand All @@ -10,7 +10,7 @@ const VERSION = '2.2.1';
export const spec = {
code: BIDDER_CODE,
aliases: ['cnvr'], // short code
supportedMediaTypes: [VIDEO],
supportedMediaTypes: [BANNER, VIDEO],

/**
* Determines whether or not the given bid request is valid.
Expand Down
2 changes: 1 addition & 1 deletion modules/freewheelSSPBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ var getOutstreamScript = function(bid) {

export const spec = {
code: BIDDER_CODE,
supportedMediaTypes: ['video'],
supportedMediaTypes: ['banner', 'video'],
aliases: ['stickyadstv'], // former name for freewheel-ssp
/**
* Determines whether or not the given bid request is valid.
Expand Down
2 changes: 1 addition & 1 deletion modules/pulsepointBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const spec = {

aliases: ['pulseLite', 'pulsepointLite'],

supportedMediaTypes: ['native'],
supportedMediaTypes: ['banner', 'native'],

isBidRequestValid: bid => (
!!(bid && bid.params && bid.params.cp && bid.params.ct)
Expand Down
2 changes: 1 addition & 1 deletion modules/rubiconBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ utils._each(sizeMap, (item, key) => sizeMap[item] = key);
export const spec = {
code: 'rubicon',
aliases: ['rubiconLite'],
supportedMediaTypes: ['video'],
supportedMediaTypes: ['banner', 'video'],
/**
* @param {object} bid
* @return boolean
Expand Down
2 changes: 1 addition & 1 deletion modules/sekindoUMBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as utils from 'src/utils';
import {registerBidder} from 'src/adapters/bidderFactory';
export const spec = {
code: 'sekindoUM',
supportedMediaTypes: ['video'],
supportedMediaTypes: ['banner', 'video'],
/**
* Determines whether or not the given bid request is valid.
*
Expand Down
12 changes: 9 additions & 3 deletions src/adapters/bidderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,14 @@ export function newBidder(spec) {
// After all the responses have come back, call done() and
// register any required usersync pixels.
const responses = [];
function afterAllResponses() {
done();
function afterAllResponses(bids) {
const videoBid = bids && bids[0] && bids[0].mediaType && bids[0].mediaType === 'video';
const cacheEnabled = config.getConfig('cache.url');

// video bids with cache enabled need to be cached first before they are considered done
if (!(videoBid && cacheEnabled)) {
done();
}
registerSyncs(responses);
}

Expand Down Expand Up @@ -281,7 +287,7 @@ export function newBidder(spec) {
addBidUsingRequestMap(bids);
}
}
onResponse();
onResponse(bids);

function addBidUsingRequestMap(bid) {
const bidRequest = bidRequestMap[bid.requestId];
Expand Down
26 changes: 10 additions & 16 deletions src/auction.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ import { Renderer } from 'src/Renderer';
import { config } from 'src/config';
import { userSync } from 'src/userSync';
import { createHook } from 'src/hook';
import { videoAdUnit } from 'src/video';
import find from 'core-js/library/fn/array/find';
import includes from 'core-js/library/fn/array/includes';

Expand Down Expand Up @@ -153,20 +152,9 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels})
return innerBidRequestId === bidRequest.bidderRequestId;
});

const nonVideoBid = request.bids.filter(videoAdUnit).length === 0;
const videoBid = request.bids.filter(videoAdUnit).length > 0;
const videoBidNoCache = videoBid && !config.getConfig('cache.url');
const videoBidWithCache = videoBid && config.getConfig('cache.url');

// video bids with cache enabled need to be cached first before saying they are done
if (!videoBidWithCache) {
request.doneCbCallCount += 1;
}

// in case of mediaType video and prebidCache enabled, call bidsBackHandler after cache is stored.
if (nonVideoBid || videoBidNoCache) {
bidsBackAll()
}
// this is done for cache-enabled video bids in tryAddVideoBids, after the cache is stored
request.doneCbCallCount += 1;
bidsBackAll();
}, 1);
}

Expand Down Expand Up @@ -379,7 +367,13 @@ export function getStandardBidderSettings() {
val: function (bidResponse) {
return bidResponse.source;
}
}
},
{
key: 'hb_format',
val: function (bidResponse) {
return bidResponse.mediaType;
}
},
]
}
return bidder_settings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD];
Expand Down
3 changes: 2 additions & 1 deletion src/constants.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"hb_pb",
"hb_size",
"hb_deal",
"hb_source"
"hb_source",
"hb_format"
],
"S2S" : {
"SRC" : "s2s",
Expand Down
48 changes: 28 additions & 20 deletions src/prebid.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import { getGlobal } from './prebidGlobal';
import { flatten, uniques, isGptPubadsDefined, adUnitsFilter, removeRequestId } from './utils';
import { videoAdUnit, videoBidder, hasNonVideoBidder } from './video';
import { nativeAdUnit, nativeBidder, hasNonNativeBidder } from './native';
import { listenMessagesFromCreative } from './secureCreatives';
import { userSync } from 'src/userSync.js';
import { loadScript } from './adloader';
Expand Down Expand Up @@ -299,24 +297,34 @@ $$PREBID_GLOBAL$$.requestBids = createHook('asyncSeries', function ({ bidsBackHa
adUnitCodes = adUnits && adUnits.map(unit => unit.code);
}

// for video-enabled adUnits, only request bids for bidders that support video
adUnits.filter(videoAdUnit).filter(hasNonVideoBidder).forEach(adUnit => {
const nonVideoBidders = adUnit.bids
.filter(bid => !videoBidder(bid))
.map(bid => bid.bidder);

utils.logWarn(utils.unsupportedBidderMessage(adUnit, nonVideoBidders));
adUnit.bids = adUnit.bids.filter(videoBidder);
});

// for native-enabled adUnits, only request bids for bidders that support native
adUnits.filter(nativeAdUnit).filter(hasNonNativeBidder).forEach(adUnit => {
const nonNativeBidders = adUnit.bids
.filter(bid => !nativeBidder(bid))
.map(bid => bid.bidder);

utils.logWarn(utils.unsupportedBidderMessage(adUnit, nonNativeBidders));
adUnit.bids = adUnit.bids.filter(nativeBidder);
/*
* for a given adunit which supports a set of mediaTypes
* and a given bidder which supports a set of mediaTypes
* a bidder is eligible to participate on the adunit
* if it supports at least one of the mediaTypes on the adunit
*/
adUnits.forEach(adUnit => {
// get the adunit's mediaTypes, defaulting to banner if mediaTypes isn't present
const adUnitMediaTypes = Object.keys(adUnit.mediaTypes || {'banner': 'banner'});

// get the bidder's mediaTypes
const bidders = adUnit.bids.map(bid => bid.bidder);
const bidderRegistry = adaptermanager.bidderRegistry;

bidders.forEach(bidder => {
const adapter = bidderRegistry[bidder];
const spec = adapter && adapter.getSpec && adapter.getSpec()
// banner is default if not specified in spec
const bidderMediaTypes = (spec && spec.supportedMediaTypes) || ['banner'];

// check if the bidder's mediaTypes are not in the adUnit's mediaTypes
const bidderEligible = adUnitMediaTypes.some(type => bidderMediaTypes.includes(type));
if (!bidderEligible) {
// drop the bidder from the ad unit if it's not compatible
utils.logWarn(utils.unsupportedBidderMessage(adUnit, bidder));
adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder);
}
});
});

if (!adUnits || adUnits.length === 0) {
Expand Down
11 changes: 5 additions & 6 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -832,17 +832,16 @@ export function isSlotMatchingAdUnitCode(adUnitCode) {
/**
* Constructs warning message for when unsupported bidders are dropped from an adunit
* @param {Object} adUnit ad unit from which the bidder is being dropped
* @param {Array} unSupportedBidders arrary of bidder codes that are not compatible with the adUnit
* @param {string} bidder bidder code that is not compatible with the adUnit
* @return {string} warning message to display when condition is met
*/
export function unsupportedBidderMessage(adUnit, unSupportedBidders) {
const mediaType = adUnit.mediaType || Object.keys(adUnit.mediaTypes).join(', ');
const plural = unSupportedBidders.length === 1 ? 'This bidder' : 'These bidders';
export function unsupportedBidderMessage(adUnit, bidder) {
const mediaType = Object.keys(adUnit.mediaTypes || {'banner': 'banner'}).join(', ');

return `
${adUnit.code} is a ${mediaType} ad unit
containing bidders that don't support ${mediaType}: ${unSupportedBidders.join(', ')}.
${plural} won't fetch demand.
containing bidders that don't support ${mediaType}: ${bidder}.
This bidder won't fetch demand.
`;
}

Expand Down
Loading

0 comments on commit 0849da4

Please sign in to comment.