Skip to content

Commit 88c7795

Browse files
committed
Yieldmo Bid Adapter: read video parameters from the ad unit
1 parent bcd1ebe commit 88c7795

3 files changed

Lines changed: 130 additions & 36 deletions

File tree

modules/yieldmoBidAdapter.js

Lines changed: 55 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ const NET_REVENUE = true;
1212
const BANNER_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebid';
1313
const VIDEO_SERVER_ENDPOINT = 'https://ads.yieldmo.com/exchange/prebidvideo';
1414
const OUTSTREAM_VIDEO_PLAYER_URL = 'https://prebid-outstream.yieldmo.com/bundle.js';
15-
const OPENRTB_VIDEO_BIDPARAMS = ['placement', 'startdelay', 'skipafter',
16-
'protocols', 'api', 'playbackmethod', 'maxduration', 'minduration', 'pos'];
15+
const OPENRTB_VIDEO_BIDPARAMS = ['mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api',
16+
'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable'];
1717
const OPENRTB_VIDEO_SITEPARAMS = ['name', 'domain', 'cat', 'keywords'];
1818
const LOCAL_WINDOW = utils.getWindowTop();
1919
const DEFAULT_PLAYBACK_METHOD = 2;
@@ -335,7 +335,6 @@ function openRtbRequest(bidRequests, bidderRequest) {
335335
* @return Object OpenRTB's 'imp' (impression) object
336336
*/
337337
function openRtbImpression(bidRequest) {
338-
const videoReq = utils.deepAccess(bidRequest, 'mediaTypes.video');
339338
const size = extractPlayerSize(bidRequest);
340339
const imp = {
341340
id: bidRequest.bidId,
@@ -347,23 +346,27 @@ function openRtbImpression(bidRequest) {
347346
video: {
348347
w: size[0],
349348
h: size[1],
350-
mimes: videoReq.mimes,
351349
linearity: 1
352350
}
353351
};
354352

353+
const mediaTypesParams = utils.deepAccess(bidRequest, 'mediaTypes.video');
354+
Object.keys(mediaTypesParams)
355+
.filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param))
356+
.forEach(param => imp.video[param] = mediaTypesParams[param]);
357+
355358
const videoParams = utils.deepAccess(bidRequest, 'params.video');
356359
Object.keys(videoParams)
357360
.filter(param => includes(OPENRTB_VIDEO_BIDPARAMS, param))
358361
.forEach(param => imp.video[param] = videoParams[param]);
359362

360-
if (videoParams.skippable) imp.video.skip = 1;
361-
if (videoParams.placement !== 1) {
362-
imp.video = {
363-
...imp.video,
364-
startdelay: DEFAULT_START_DELAY,
365-
playbackmethod: [ DEFAULT_PLAYBACK_METHOD ]
366-
}
363+
if (imp.video.skippable) {
364+
imp.video.skip = 1;
365+
delete imp.video.skippable;
366+
}
367+
if (imp.video.placement !== 1) {
368+
imp.video.startdelay = DEFAULT_START_DELAY;
369+
imp.video.playbackmethod = [ DEFAULT_PLAYBACK_METHOD ];
367370
}
368371
return imp;
369372
}
@@ -476,51 +479,68 @@ function validateVideoParams(bid) {
476479

477480
const isDefined = val => typeof val !== 'undefined';
478481
const validate = (fieldPath, validateCb, errorCb, errorCbParam) => {
479-
const value = utils.deepAccess(bid, fieldPath);
480-
if (!validateCb(value)) {
481-
errorCb(fieldPath, value, errorCbParam);
482+
if (fieldPath.includes('video')) {
483+
const valueFieldPath = 'params.' + fieldPath;
484+
const mediaFieldPath = 'mediaTypes.' + fieldPath;
485+
const valueParams = utils.deepAccess(bid, valueFieldPath);
486+
const mediaTypesParams = utils.deepAccess(bid, mediaFieldPath);
487+
const hasValidValueParams = validateCb(valueParams);
488+
const hasValidMediaTypesParams = validateCb(mediaTypesParams);
489+
490+
if (hasValidValueParams) return valueParams;
491+
else if (hasValidMediaTypesParams) return hasValidMediaTypesParams;
492+
else {
493+
if (!hasValidValueParams) errorCb(valueFieldPath, valueParams, errorCbParam);
494+
else if (!hasValidMediaTypesParams) errorCb(mediaFieldPath, mediaTypesParams, errorCbParam);
495+
}
496+
return valueParams || mediaTypesParams;
497+
} else {
498+
const value = utils.deepAccess(bid, fieldPath);
499+
if (!validateCb(value)) {
500+
errorCb(fieldPath, value, errorCbParam);
501+
}
502+
return value;
482503
}
483-
return value;
484504
}
485505

486506
try {
507+
validate('video.context', val => !utils.isEmpty(val), paramRequired);
508+
487509
validate('params.placementId', val => !utils.isEmpty(val), paramRequired);
488510

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

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

497-
validate('params.video', val => !utils.isEmpty(val), paramRequired);
498-
499-
const placement = validate('params.video.placement', val => isDefined(val), paramRequired);
500-
validate('params.video.placement', val => val >= 1 && val <= 5, paramInvalid);
519+
const placement = validate('video.placement', val => isDefined(val), paramRequired);
520+
validate('video.placement', val => val >= 1 && val <= 5, paramInvalid);
501521
if (placement === 1) {
502-
validate('params.video.startdelay', val => isDefined(val),
522+
validate('video.startdelay', val => isDefined(val),
503523
(field, v) => paramRequired(field, v, 'placement == 1'));
504-
validate('params.video.startdelay', val => utils.isNumber(val), paramInvalid, 'number, ex: 5');
524+
validate('video.startdelay', val => utils.isNumber(val), paramInvalid, 'number, ex: 5');
505525
}
506526

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

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

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

518-
validate('params.video.maxduration', val => isDefined(val), paramRequired);
519-
validate('params.video.maxduration', val => utils.isInteger(val), paramInvalid);
520-
validate('params.video.minduration', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
521-
validate('params.video.skippable', val => !isDefined(val) || utils.isBoolean(val), paramInvalid);
522-
validate('params.video.skipafter', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
523-
validate('params.video.pos', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
538+
validate('video.maxduration', val => isDefined(val), paramRequired);
539+
validate('video.maxduration', val => utils.isInteger(val), paramInvalid);
540+
validate('video.minduration', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
541+
validate('video.skippable', val => !isDefined(val) || utils.isBoolean(val), paramInvalid);
542+
validate('video.skipafter', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
543+
validate('video.pos', val => !isDefined(val) || utils.isNumber(val), paramInvalid);
524544
validate('params.badv', val => !isDefined(val) || utils.isArray(val), paramInvalid,
525545
'array of strings, ex: ["ford.com","pepsi.com"]');
526546
validate('params.bcat', val => !isDefined(val) || utils.isArray(val), paramInvalid,

modules/yieldmoBidAdapter.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,8 @@ var videoAdUnit = [{
9696
}]
9797
}];
9898
```
99+
100+
Please also note, that we support the following OpenRTB params:
101+
'mimes', 'startdelay', 'placement', 'startdelay', 'skipafter', 'protocols', 'api',
102+
'playbackmethod', 'maxduration', 'minduration', 'pos', 'skip', 'skippable'.
103+
They can be specified in `mediaTypes.video` or in `bids[].params.video`.

test/spec/modules/yieldmoBidAdapter_spec.js

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,13 +331,82 @@ describe('YieldmoAdapter', function () {
331331
});
332332

333333
describe('Instream video:', function () {
334+
let videoBid;
335+
const buildVideoBidAndGetVideoParam = () => build([videoBid])[0].data.imp[0].video;
336+
337+
beforeEach(() => {
338+
videoBid = mockVideoBid();
339+
});
340+
334341
it('should attempt to send video bid requests to the endpoint via POST', function () {
335-
const requests = build([mockVideoBid()]);
342+
const requests = build([videoBid]);
336343
expect(requests.length).to.equal(1);
337344
expect(requests[0].method).to.equal('POST');
338345
expect(requests[0].url).to.be.equal(VIDEO_ENDPOINT);
339346
});
340347

348+
it('should add mediaTypes.video prop to the imp.video prop', function () {
349+
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = 40;
350+
expect(buildVideoBidAndGetVideoParam().minduration).to.equal(40);
351+
});
352+
353+
it('should override mediaTypes.video prop if params.video prop is present', function () {
354+
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = 50;
355+
utils.deepAccess(videoBid, 'params.video')['minduration'] = 40;
356+
expect(buildVideoBidAndGetVideoParam().minduration).to.equal(40);
357+
});
358+
359+
it('should add mediaTypes.video.mimes prop to the imp.video', function () {
360+
utils.deepAccess(videoBid, 'mediaTypes.video')['minduration'] = ['video/mp4'];
361+
expect(buildVideoBidAndGetVideoParam().minduration).to.deep.equal(['video/mp4']);
362+
});
363+
364+
it('should override mediaTypes.video.mimes prop if params.video.mimes is present', function () {
365+
utils.deepAccess(videoBid, 'mediaTypes.video')['mimes'] = ['video/mp4'];
366+
utils.deepAccess(videoBid, 'params.video')['mimes'] = ['video/mkv'];
367+
expect(buildVideoBidAndGetVideoParam().mimes).to.deep.equal(['video/mkv']);
368+
});
369+
370+
describe('video.skip state check', () => {
371+
it('should not set video.skip if neither *.video.skip nor *.video.skippable is present', function () {
372+
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = false;
373+
utils.deepAccess(videoBid, 'params.video')['skippable'] = false;
374+
expect(buildVideoBidAndGetVideoParam().skip).to.undefined;
375+
});
376+
377+
it('should set video.skip=1 if mediaTypes.video.skip is present', function () {
378+
utils.deepAccess(videoBid, 'mediaTypes.video')['skip'] = 1;
379+
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
380+
});
381+
382+
it('should set video.skip=1 if params.video.skip is present', function () {
383+
utils.deepAccess(videoBid, 'params.video')['skip'] = 1;
384+
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
385+
});
386+
387+
it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
388+
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = true;
389+
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
390+
});
391+
392+
it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
393+
utils.deepAccess(videoBid, 'params.video')['skippable'] = true;
394+
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
395+
});
396+
397+
it('should set video.skip=1 if mediaTypes.video.skippable is present', function () {
398+
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = false;
399+
utils.deepAccess(videoBid, 'params.video')['skippable'] = true;
400+
expect(buildVideoBidAndGetVideoParam().skip).to.equal(1);
401+
});
402+
403+
it('should not set video.skip if params.video.skippable is false', function () {
404+
utils.deepAccess(videoBid, 'mediaTypes.video')['skippable'] = true;
405+
utils.deepAccess(videoBid, 'params.video')['skippable'] = false;
406+
expect(buildVideoBidAndGetVideoParam().skip).to.undefined;
407+
});
408+
});
409+
341410
it('should process floors module if available', function () {
342411
const requests = build([
343412
mockVideoBid({...mockGetFloor(3.99)}),

0 commit comments

Comments
 (0)