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

Yieldmo Bid Adapter: read video parameters from the ad unit #6873

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 55 additions & 35 deletions modules/yieldmoBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const NET_REVENUE = true;
const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid';
const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo';
const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js';
const OPENRTB_VIDEO_BIDPARAMS = ['placement', 'startdelay', 'skipafter',
'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos'];
const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api',
'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable'];
const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords'];
const LOCAL_WINDOW = utils.getWindowTop();
const DEFAULT_PLAYBACK_METHOD = 2;
Expand Down Expand Up @@ -335,7 +335,6 @@ function openRtbRequest(bidRequests, bidderRequest) {
* @return Object OpenRTB's 'imp' (impression) object
*/
function openRtbImpression(bidRequest) {
const videoReq = utils.deepAccess(bidRequest, 'mediaTypes.video');
const size = extractPlayerSize(bidRequest);
const imp = {
id: bidRequest.bidId,
Expand All @@ -347,23 +346,27 @@ function openRtbImpression(bidRequest) {
video: {
w: size[0],
h: size[1],
mimes: videoReq.mimes,
linearity: 1
}
};

const mediaTypesParams = utils.deepAccess(bidRequest, 'mediaTypes.video');
Object.keys(mediaTypesParams)
.filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param))
.forEach(param => imp.video[param] = mediaTypesParams[param]);

const videoParams = utils.deepAccess(bidRequest, 'params.video');
Object.keys(videoParams)
.filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param))
.forEach(param => imp.video[param] = videoParams[param]);

if (videoParams.skippable) imp.video.skip = 1;
if (videoParams.placement !== 1) {
imp.video = {
...imp.video,
startdelay: DEFAULT_START_DELAY,
playbackmethod: [ DEFAULT_PLAYBACK_METHOD ]
}
if (imp.video.skippable) {
imp.video.skip = 1;
delete imp.video.skippable;
}
if (imp.video.placement !== 1) {
imp.video.startdelay = DEFAULT_START_DELAY;
imp.video.playbackmethod = [ DEFAULT_PLAYBACK_METHOD ];
}
return imp;
}
Expand Down Expand Up @@ -476,51 +479,68 @@ function validateVideoParams(bid) {

const isDefined = val => typeof val !== 'undefined';
const validate = (fieldPath, validateCb, errorCb, errorCbParam) => {
const value = utils.deepAccess(bid, fieldPath);
if (!validateCb(value)) {
errorCb(fieldPath, value, errorCbParam);
if (fieldPath.indexOf('video') === 0) {
const valueFieldPath = 'params.' + fieldPath;
const mediaFieldPath = 'mediaTypes.' + fieldPath;
const valueParams = utils.deepAccess(bid, valueFieldPath);
const mediaTypesParams = utils.deepAccess(bid, mediaFieldPath);
const hasValidValueParams = validateCb(valueParams);
const hasValidMediaTypesParams = validateCb(mediaTypesParams);

if (hasValidValueParams) return valueParams;
else if (hasValidMediaTypesParams) return hasValidMediaTypesParams;
else {
if (!hasValidValueParams) errorCb(valueFieldPath, valueParams, errorCbParam);
else if (!hasValidMediaTypesParams) errorCb(mediaFieldPath, mediaTypesParams, errorCbParam);
}
return valueParams || mediaTypesParams;
} else {
const value = utils.deepAccess(bid, fieldPath);
if (!validateCb(value)) {
errorCb(fieldPath, value, errorCbParam);
}
return value;
}
return value;
}

try {
validate('video.context', val => !utils.isEmpty(val), paramRequired);

validate('params.placementId', val => !utils.isEmpty(val), paramRequired);

validate('mediaTypes.video.playerSize', val => utils.isArrayOfNums(val, 2) ||
validate('video.playerSize', val => utils.isArrayOfNums(val, 2) ||
(utils.isArray(val) && val.every(v => utils.isArrayOfNums(v, 2))),
paramInvalid, 'array of 2 integers, ex: [640,480] or [[640,480]]');

validate('mediaTypes.video.mimes', val => isDefined(val), paramRequired);
validate('mediaTypes.video.mimes', val => utils.isArray(val) && val.every(v => utils.isStr(v)), paramInvalid,
validate('video.mimes', val => isDefined(val), paramRequired);
validate('video.mimes', val => utils.isArray(val) && val.every(v => utils.isStr(v)), paramInvalid,
'array of strings, ex: ["video/mp4"]');

validate('params.video', val => !utils.isEmpty(val), paramRequired);

const placement = validate('params.video.placement', val => isDefined(val), paramRequired);
validate('params.video.placement', val => val >= 1 && val <= 5, paramInvalid);
const placement = validate('video.placement', val => isDefined(val), paramRequired);
validate('video.placement', val => val >= 1 && val <= 5, paramInvalid);
if (placement === 1) {
validate('params.video.startdelay', val => isDefined(val),
validate('video.startdelay', val => isDefined(val),
(field, v) => paramRequired(field, v, 'placement == 1'));
validate('params.video.startdelay', val => utils.isNumber(val), paramInvalid, 'number, ex: 5');
validate('video.startdelay', val => utils.isNumber(val), paramInvalid, 'number, ex: 5');
}

validate('params.video.protocols', val => isDefined(val), paramRequired);
validate('params.video.protocols', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)),
validate('video.protocols', val => isDefined(val), paramRequired);
validate('video.protocols', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)),
paramInvalid, 'array of numbers, ex: [2,3]');

validate('params.video.api', val => isDefined(val), paramRequired);
validate('params.video.api', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)),
validate('video.api', val => isDefined(val), paramRequired);
validate('video.api', val => utils.isArrayOfNums(val) && val.every(v => (v >= 1 && v <= 6)),
paramInvalid, 'array of numbers, ex: [2,3]');

validate('params.video.playbackmethod', val => !isDefined(val) || utils.isArrayOfNums(val), paramInvalid,
validate('video.playbackmethod', val => !isDefined(val) || utils.isArrayOfNums(val), paramInvalid,
'array of integers, ex: [2,6]');

validate('params.video.maxduration', val => isDefined(val), paramRequired);
validate('params.video.maxduration', val => utils.isInteger(val), paramInvalid);
validate('params.video.minduration', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
validate('params.video.skippable', val => !isDefined(val) || utils.isBoolean(val), paramInvalid);
validate('params.video.skipafter', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
validate('params.video.pos', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
validate('video.maxduration', val => isDefined(val), paramRequired);
validate('video.maxduration', val => utils.isInteger(val), paramInvalid);
validate('video.minduration', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
validate('video.skippable', val => !isDefined(val) || utils.isBoolean(val), paramInvalid);
validate('video.skipafter', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
validate('video.pos', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
validate('params.badv', val => !isDefined(val) || utils.isArray(val), paramInvalid,
'array of strings, ex: ["ford.com","pepsi.com"]');
validate('params.bcat', val => !isDefined(val) || utils.isArray(val), paramInvalid,
Expand Down
5 changes: 5 additions & 0 deletions modules/yieldmoBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,8 @@ var videoAdUnit = [{
}]
}];
```

Please also note, that we support the following OpenRTB params:
'mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api',
'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable'.
They can be specified in `mediaTypes.video` or in `bids[].params.video`.
71 changes: 70 additions & 1 deletion test/spec/modules/yieldmoBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,13 +331,82 @@ describe('YieldmoAdapter', function () {
});

describe('Instream video:', function () {
let videoBid;
const buildVideoBidAndGetVideoParam = () => build([videoBid])[0].data.imp[0].video;

beforeEach(() => {
videoBid = mockVideoBid();
});

it('should attempt to send video bid requests to the endpoint via POST', function () {
const requests = build([mockVideoBid()]);
const requests = build([videoBid]);
expect(requests.length).to.equal(1);
expect(requests[0].method).to.equal('POST');
expect(requests[0].url).to.be.equal(VIDEO_ENDPOINT);
});

it('should add mediaTypes.video prop to the imp.video prop', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = 40;
expect(buildVideoBidAndGetVideoParam().minduration).to.equal(40);
});

it('should override mediaTypes.video prop if params.video prop is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = 50;
utils.deepAccess(videoBid, 'params.video')['minduration'] = 40;
expect(buildVideoBidAndGetVideoParam().minduration).to.equal(40);
});

it('should add mediaTypes.video.mimes prop to the imp.video', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = ['video/mp4'];
expect(buildVideoBidAndGetVideoParam().minduration).to.deep.equal(['video/mp4']);
});

it('should override mediaTypes.video.mimes prop if params.video.mimes is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['mimes'] = ['video/mp4'];
utils.deepAccess(videoBid, 'params.video')['mimes'] = ['video/mkv'];
expect(buildVideoBidAndGetVideoParam().mimes).to.deep.equal(['video/mkv']);
});

describe('video.skip state check', () => {
it('should not set video.skip if neither *.video.skip nor *.video.skippable is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = false;
utils.deepAccess(videoBid, 'params.video')['skippable'] = false;
expect(buildVideoBidAndGetVideoParam().skip).to.undefined;
});

it('should set video.skip=1 if mediaTypes.video.skip is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skip'] = 1;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});

it('should set video.skip=1 if params.video.skip is present', function () {
utils.deepAccess(videoBid, 'params.video')['skip'] = 1;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});

it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = true;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});

it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
utils.deepAccess(videoBid, 'params.video')['skippable'] = true;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});

it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = false;
utils.deepAccess(videoBid, 'params.video')['skippable'] = true;
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
});

it('should not set video.skip if params.video.skippable is false', function () {
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = true;
utils.deepAccess(videoBid, 'params.video')['skippable'] = false;
expect(buildVideoBidAndGetVideoParam().skip).to.undefined;
});
});

it('should process floors module if available', function () {
const requests = build([
mockVideoBid({...mockGetFloor(3.99)}),
Expand Down