Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XCH-1994: Construct Video ORTB #33

Merged
merged 13 commits into from
Oct 20, 2020
134 changes: 126 additions & 8 deletions modules/33acrossBidAdapter.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { registerBidder } from '../src/adapters/bidderFactory.js';
import { config } from '../src/config.js';
import * as utils from '../src/utils.js';
import {BANNER, VIDEO} from '../src/mediaTypes.js';
terryc33x marked this conversation as resolved.
Show resolved Hide resolved

const BIDDER_CODE = '33across';
const END_POINT = 'https://ssc.33across.com/api/v1/hb';
const SYNC_ENDPOINT = 'https://ssc-cms.33across.com/ps/?m=xch&rt=html&ru=deb';
const MEDIA_TYPE = {
BANNER: 'banner',
VIDEO: 'video'
};

const CURRENCY = 'USD';
const GUID_PATTERN = /^[a-zA-Z0-9_-]{22}$/;
Expand All @@ -19,6 +16,23 @@ const PRODUCT = {
INSTREAM: 'instream'
};

const VIDEO_ORTB_PARAMS = [
'mimes',
'minduration',
'maxduration',
'placement',
'protocols',
'startdelay',
'skip',
'skipafter',
'minbitrate',
'maxbitrate',
'delivery',
'playbackmethod',
'api',
'linearity'
];

const adapterState = {
uniqueSiteIds: []
};
Expand Down Expand Up @@ -71,6 +85,7 @@ function _validateBanner(bid) {

function _validateVideo(bid) {
const videoAdUnit = utils.deepAccess(bid, 'mediaTypes.video');
const videoBidderParams = utils.deepAccess(bid, 'params.video', {});

// If there's no video no need to validate against video rules
if (videoAdUnit === undefined) {
Expand All @@ -85,6 +100,36 @@ function _validateVideo(bid) {
return false;
}

const videoParams = {
...videoAdUnit,
...videoBidderParams
};

if (!Array.isArray(videoParams.mimes) || videoParams.mimes.length === 0) {
return false;
}

if (!Array.isArray(videoParams.protocols) || videoParams.protocols.length === 0) {
return false;
}

// If placement if defined, it must be a number
if (
typeof videoParams.placement !== 'undefined' &&
typeof videoParams.placement !== 'number'
) {
return false;
}

// If startdelay is defined it must be a number
if (
curlyblueeagle marked this conversation as resolved.
Show resolved Hide resolved
videoAdUnit.context === 'instream' &&
typeof videoParams.startdelay !== 'undefined' &&
typeof videoParams.startdelay !== 'number'
) {
return false;
}

return true;
}

Expand Down Expand Up @@ -129,6 +174,10 @@ function _createServerRequest({bidRequest, gdprConsent = {}, uspConsent, pageUrl
}
}

if (utils.deepAccess(bidRequest, 'mediaTypes.video')) {
ttxRequest.imp[0].video = _buildVideoORTB(bidRequest);
}

ttxRequest.imp[0].ext = {
ttx: {
prod: _getProduct(bidRequest)
Expand Down Expand Up @@ -242,7 +291,7 @@ function _buildBannerORTB(bidRequest) {
// We support size based bidfloors so obtain one if there's a rule associated
if (typeof bidRequest.getFloor === 'function') {
format = sizes.map((size) => {
const bidfloors = _getBidFloors(bidRequest, size, MEDIA_TYPE.BANNER);
const bidfloors = _getBidFloors(bidRequest, size, BANNER);

let formatExt;
if (bidfloors) {
Expand All @@ -267,10 +316,66 @@ function _buildBannerORTB(bidRequest) {
? _getViewability(element, utils.getWindowTop(), minSize)
: NON_MEASURABLE;

const ext = contributeViewability(viewabilityAmount);

return {
format,
ext: contributeViewability(viewabilityAmount)
ext
}
}

// BUILD REQUESTS: VIDEO
// eslint-disable-next-line no-unused-vars
function _buildVideoORTB(bidRequest) {
const videoAdUnit = utils.deepAccess(bidRequest, 'mediaTypes.video', {});
const videoBidderParams = utils.deepAccess(bidRequest, 'params.video', {});

const videoParams = {
...videoAdUnit,
...videoBidderParams // Bidder Specific overrides
};

const video = {}

const sizes = videoAdUnit.playerSize || [];
const {w, h} = _getSize(sizes);
video.w = w;
video.h = h;

// Obtain all ORTB params related video from Ad Unit
VIDEO_ORTB_PARAMS.forEach((param) => {
if (videoParams.hasOwnProperty(param)) {
video[param] = videoParams[param];
}
});

const product = _getProduct(bidRequest);

// Placement Inference Rules:
// - If no placement is defined then default to 2 (In Banner)
// - If product is instream (for instream context) then override placement to 1
video.placement = video.placement || 2;

if (product === PRODUCT.INSTREAM) {
video.startdelay = video.startdelay || 0;
terryc33x marked this conversation as resolved.
Show resolved Hide resolved
video.placement = 1;
};

// bidfloors
if (typeof bidRequest.getFloor === 'function') {
terryc33x marked this conversation as resolved.
Show resolved Hide resolved
const bidfloors = _getBidFloors(bidRequest, {w: video.w, h: video.h}, VIDEO);

if (bidfloors) {
Object.assign(video, {
ext: {
ttx: {
bidfloors: [ bidfloors ]
}
}
});
}
}
return video;
terryc33x marked this conversation as resolved.
Show resolved Hide resolved
}

// BUILD REQUESTS: BIDFLOORS
Expand Down Expand Up @@ -437,7 +542,7 @@ function interpretResponse(serverResponse, bidRequest) {

// All this assumes that only one bid is ever returned by ttx
function _createBidResponse(response) {
return {
const bid = {
requestId: response.id,
bidderCode: BIDDER_CODE,
cpm: response.seatbid[0].bid[0].price,
Expand All @@ -446,9 +551,22 @@ function _createBidResponse(response) {
ad: response.seatbid[0].bid[0].adm,
ttl: response.seatbid[0].bid[0].ttl || 60,
creativeId: response.seatbid[0].bid[0].crid,
mediaType: utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.mediaType', BANNER),
currency: response.cur,
netRevenue: true
}

if (bid.mediaType === VIDEO) {
const vastType = utils.deepAccess(response.seatbid[0].bid[0], 'ext.ttx.vastType', 'xml');

if (vastType === 'xml') {
bid.vastXml = bid.ad;
} else {
bid.vastUrl = bid.ad;
}
}

return bid;
}

// **************************** USER SYNC *************************** //
Expand Down Expand Up @@ -492,7 +610,7 @@ export const spec = {
NON_MEASURABLE,

code: BIDDER_CODE,

supportedMediaTypes: [ BANNER, VIDEO ],
isBidRequestValid,
buildRequests,
interpretResponse,
Expand Down
146 changes: 134 additions & 12 deletions modules/33acrossBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,145 @@ Maintainer: headerbidding@33across.com

Connects to 33Across's exchange for bids.

33Across bid adapter supports only Banner at present and follows MRA
33Across bid adapter supports Banner and Video at present and follows MRA

# Sample Ad Unit: For Publishers
## Sample Banner only Ad Unit
```
var adUnits = [
{
code: '33across-hb-ad-123456-1', // ad slot HTML element ID
sizes: [
[300, 250],
[728, 90]
],
bids: [{
bidder: '33across',
params: {
siteId: 'cxBE0qjUir6iopaKkGJozW',
productId: 'siab'
code: '33across-hb-ad-123456-1', // ad slot HTML element ID
mediaTypes: {
banner: {
sizes: [
[300, 250],
[728, 90]
]
}
}
bids: [{
bidder: '33across',
params: {
siteId: 'sampleGUID123',
productId: 'siab|inview'
}
}]
}
```

## Sample Video only Ad Unit: Outstream
```
var adUnits = [
{
code: '33across-hb-ad-123456-1', // ad slot HTML element ID
mediaTypes: {
video: {
playerSize: [300, 250],
context: 'outstream',
placement: 2
... // Aditional ORTB video params

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since mimes and protocols are required, better to put it in the doc

}
},
renderer: {
url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js',
render: function (bid) {
adResponse = {
ad: {
video: {
content: bid.vastXml,
player_height: bid.playerHeight,
player_width: bid.playerWidth
}
}
}
}]
// push to render queue because ANOutstreamVideo may not be loaded yet.
bid.renderer.push(() => {
ANOutstreamVideo.renderAd({
targetId: bid.adUnitCode, // target div id to render video.
adResponse: adResponse
});
});
}
},
bids: [{
bidder: '33across',
params: {
siteId: 'sampleGUID123',
productId: 'siab'
}
}]
}
```

## Sample Multi-Format Ad Unit: Outstream
```
var adUnits = [
{
code: '33across-hb-ad-123456-1', // ad slot HTML element ID
mediaTypes: {
banner: {
sizes: [
[300, 250],
[728, 90]
]
},
video: {
playerSize: [300, 250],
context: 'outstream',
placement: 2
... // Aditional ORTB video params
}
},
renderer: {
url: 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js',
render: function (bid) {
adResponse = {
ad: {
video: {
content: bid.vastXml,
player_height: bid.playerHeight,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure the bid.playerHeight|Width is standard, that may exist in APNX adapter only, please double check. if it does not exists, then just suggest with w & h

player_width: bid.playerWidth
}
}
}
// push to render queue because ANOutstreamVideo may not be loaded yet.
bid.renderer.push(() => {
ANOutstreamVideo.renderAd({
targetId: bid.adUnitCode, // target div id to render video.
adResponse: adResponse
});
});
}
},
bids: [{
bidder: '33across',
params: {
siteId: 'sampleGUID123',
productId: 'siab'
}
}]
}
```

## Sample Video only Ad Unit: Instream
```
var adUnits = [
{
code: '33across-hb-ad-123456-1', // ad slot HTML element ID
mediaTypes: {
video: {
playerSize: [300, 250],
context: 'intstream',
placement: 1
... // Aditional ORTB video params
}
}
bids: [{
bidder: '33across',
params: {
siteId: 'sampleGUID123',
productId: 'instream'
}
}]
}
```
Loading