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

Connect coreVideo to Prebid #6

Merged
merged 13 commits into from
Sep 22, 2021
113 changes: 113 additions & 0 deletions integrationExamples/videoModule/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<!--

-->

<html>

<head>
<script async src="../../build/dev/prebid.js"></script>
<script>
var adUnits = [{
code: 'div-gpt-ad-51545-0',
sizes: [[600, 500]],
mediaTypes: {
video: {
sizes: [[600, 500]]
}
},
video: {
divId: 'vid-div-1',
adServer: {
vendorCode: "gam",
params: {
iu: '/19968336/prebid_cache_video_adunit',
cust_params: {
section: 'blog',
anotherKey: 'anotherValue'
},
output: 'vast'
}
},
},

// Replace this object to test a new Adapter!
bids: [{
bidder: 'ix',
params: {
siteId: '300',
size: [600, 500]
}
}]
}];

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];
</script>
<script>
pbjs.que.push(function () {
pbjs.onEvent('adImpression', event => {
console.log('adImpression: ', event);
});
pbjs.addAdUnits(adUnits);
pbjs.setConfig({
video: {
providers: [{
divId: 'vid-div-1',
vendorCode: 1,
playerConfig: {
licenseKey: "INSERT LICENSE KEY HERE",
params: {
vendorConfig: {
"file": "http://content.bitsontherun.com/videos/3XnJSIm4-52qL9xLP.mp4",
mediaid: 'd9J2zcaA',
"advertising": {
"adscheduleid": "00000000",
client: 'vast',
"offset": "10",
"tag": [
"http://playertest.longtailvideo.com/pre-bad.xml",
"http://playertest.longtailvideo.com/mid.xml"
],
}
}
}
}
}, {
divId: 'vid-div-2',
vendorCode: 1,
playerConfig: {
licenseKey: "INSERT LICENSE KEY HERE",
params: {
vendorConfig: {
"file": "http://content.bitsontherun.com/videos/3XnJSIm4-52qL9xLP.mp4",
mediaid: 'd9J2zcaA',
"advertising": {
"adscheduleid": "00000000",
client: 'vast',
"offset": "10",
"tag": [
"http://playertest.longtailvideo.com/pre-bad.xml",
"http://playertest.longtailvideo.com/mid.xml"
],
}
}
}
}
}]
}
});
pbjs.requestBids();
});
</script>
</head>

<body>
<h2>Prebid.js Test</h2>
<h5>Div-1</h5>
<div id='vid-div-1'></div>
<h5>Div-2</h5>
<div id='vid-div-2'></div>
</body>

</html>

4 changes: 4 additions & 0 deletions modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,9 @@
"fpdModule": [
"enrichmentFpdModule",
"validationFpdModule"
],
"videoModule": [
"coreVideo",
"jwplayerVideoProvider"
]
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE
} from '../constants/ortb.js';
} from './videoModule/constants/ortb.js';
import {
SETUP_COMPLETE, SETUP_FAILED, DESTROYED, AD_REQUEST, AD_BREAK_START, AD_LOADED, AD_STARTED, AD_IMPRESSION, AD_PLAY,
AD_TIME, AD_PAUSE, AD_CLICK, AD_SKIPPED, AD_ERROR, AD_COMPLETE, AD_BREAK_END, PLAYLIST, PLAYBACK_REQUEST,
AUTOSTART_BLOCKED, PLAY_ATTEMPT_FAILED, CONTENT_LOADED, PLAY, PAUSE, BUFFER, TIME, SEEK_START, SEEK_END, MUTE, VOLUME,
RENDITION_UPDATE, ERROR, COMPLETE, PLAYLIST_COMPLETE, FULLSCREEN, PLAYER_RESIZE, VIEWABLE, CAST, PLAYBACK_MODE
} from '../constants/events.js';
import stateFactory from '../shared/state.js';
import { JWPLAYER_VENDOR } from '../constants/vendorCodes.js';
import { vendorDirectory } from '../vendorDirectory';
} from './videoModule/constants/events.js';
import stateFactory from './videoModule/shared/state.js';
import { JWPLAYER_VENDOR } from './videoModule/constants/vendorCodes.js';
import { vendorDirectory } from './videoModule/vendorDirectory.js';

export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callbackStorage_, utils) {
const jwplayer = jwplayer_;
Expand Down
8 changes: 8 additions & 0 deletions modules/videoModule/constants/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export const PLAYER_RESIZE = 'playerResize';
export const VIEWABLE = 'viewable';
export const CAST = 'cast';

export const allVideoEvents = [
SETUP_COMPLETE, SETUP_FAILED, DESTROYED, AD_REQUEST, AD_BREAK_START, AD_LOADED, AD_STARTED,
AD_IMPRESSION, AD_PLAY, AD_TIME, AD_PAUSE, AD_CLICK, AD_SKIPPED, AD_ERROR, AD_COMPLETE, AD_BREAK_END, PLAYLIST,
PLAYBACK_REQUEST, AUTOSTART_BLOCKED, PLAY_ATTEMPT_FAILED, CONTENT_LOADED, PLAY, PAUSE, BUFFER, TIME, SEEK_START,
SEEK_END, MUTE, VOLUME, RENDITION_UPDATE, ERROR, COMPLETE, PLAYLIST_COMPLETE, FULLSCREEN, PLAYER_RESIZE, VIEWABLE,
CAST
];

// Param options
export const PLAYBACK_MODE = {
VOD: 0,
Expand Down
11 changes: 8 additions & 3 deletions modules/videoModule/coreVideo.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { vendorDirectory } from './vendorDirectory';
import { vendorDirectory } from './vendorDirectory.js';

export function VideoCore(submoduleBuilder_) {
const submodules = {};
const submoduleBuilder = submoduleBuilder_;

function registerProvider(providerConfig) {
const divId = providerConfig.divId;
if (submodules[divId]) {
return;
}

let submodule;
try {
submodule = submoduleBuilder.build(providerConfig);
} catch (e) {
throw e;
}
submodules[providerConfig.divId] = submodule;
submodules[divId] = submodule;
}

function getOrtbParams(divId) {
Expand Down Expand Up @@ -58,7 +63,7 @@ export function VideoSubmoduleBuilder(vendorDirectory_) {
}

const submodule = submoduleFactory(providerConfig);
submodule.init && submodule.init();
submodule && submodule.init && submodule.init();
return submodule;
}

Expand Down
57 changes: 57 additions & 0 deletions modules/videoModule/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {config} from '../../src/config.js';
import events from '../../src/events.js';
import { allVideoEvents } from './constants/events.js';
import CONSTANTS from '../../src/constants.json';
import { videoCoreFactory } from './coreVideo.js';

events.addEvents(allVideoEvents);

export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvents_) {
const videoCore = videoCore_;
const getConfig = getConfig_;
const pbGlobal = pbGlobal_;
const requestBids = pbGlobal.requestBids;
const pbEvents = pbEvents_;
const videoEvents = videoEvents_;

function init() {
getConfig('video', ({ video }) => {
video.providers.forEach(provider => {
try {
videoCore.registerProvider(provider);
videoCore.onEvents(videoEvents, (type, payload) => {
pbEvents.emit(type, payload);
}, provider.divId);
} catch (e) {}
});
});

requestBids.before(enrichAdUnits, 40);

pbEvents.on(CONSTANTS.EVENTS.AUCTION_END, function(auctionResult) {
// TODO: requires AdServer Module.
// get ad tag from adServer - auctionResult.winningBids
// coreVideo.setAdTagUrl(adTag, divId);
});
}

function enrichAdUnits(nextFn, bidRequest) {
const adUnits = bidRequest.adUnits || pbGlobal.adUnits || [];
adUnits.forEach(adUnit => {
const oRtbParams = videoCore.getOrtbParams(adUnit.video.divId);
adUnit.mediaTypes.video = Object.assign({}, adUnit.mediaTypes.video, oRtbParams);
});
return nextFn.call(this, bidRequest);
}

return { init };
}

function pbVideoFactory() {
const videoCore = videoCoreFactory();
const pbVideo = PbVideo(videoCore, config.getConfig, $$PREBID_GLOBAL$$, events, allVideoEvents);
pbVideo.init();
return pbVideo;
}

pbVideoFactory();
4 changes: 4 additions & 0 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ module.exports = (function () {
return _handlers;
};

_public.addEvents = function (events) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@jsnellbaker how do you feel about events.addEvents ?

allEvents.concat(events);
}

/**
* This method can return a copy of all the events fired
* @return {Array} array of events fired
Expand Down
127 changes: 127 additions & 0 deletions test/spec/modules/videoModule/pbVideo_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { expect } from 'chai';
import { PbVideo } from 'modules/videoModule/index.js';

let ortbParamsMock;
let videoCoreMock;
let getConfigMock;
let requestBidsMock;
let pbGlobalMock;
let pbEventsMock;
let videoEventsMock;

function resetTestVars() {
ortbParamsMock = {
'video': {},
'content': {}
}
videoCoreMock = {
registerProvider: sinon.spy(),
onEvents: sinon.spy(),
getOrtbParams: () => ortbParamsMock
};
getConfigMock = () => {};
requestBidsMock = {
before: sinon.spy()
};
pbGlobalMock = {
requestBids: requestBidsMock
};
pbEventsMock = {
emit: sinon.spy(),
on: sinon.spy()
};
videoEventsMock = [];
}

let pbVideoFactory = (videoCore, getConfig, pbGlobal, pbEvents, videoEvents) => {
const pbVideo = PbVideo(
videoCore || videoCoreMock,
getConfig || getConfigMock,
pbGlobal || pbGlobalMock,
pbEvents || pbEventsMock,
videoEvents || videoEventsMock
);
pbVideo.init();
return pbVideo;
}

describe('Prebid Video', function () {
beforeEach(() => resetTestVars());

describe('Setting video to config', function () {
let providers = [{ divId: 'div1' }, { divId: 'div2' }];
let getConfigCallback;
let getConfig = (video, callback) => {
getConfigCallback = callback;
};

beforeEach(() => {
pbVideoFactory(null, getConfig);
getConfigCallback({ video: { providers } });
});

it('Should register providers', function () {
expect(videoCoreMock.registerProvider.calledTwice).to.be.true;
});

it('Should register events', function () {
expect(videoCoreMock.onEvents.calledTwice).to.be.true;
const onEventsSpy = videoCoreMock.onEvents;
expect(onEventsSpy.getCall(0).args[2]).to.be.equal('div1');
expect(onEventsSpy.getCall(1).args[2]).to.be.equal('div2');
});

describe('Event triggering', function () {
it('Should emit events off of Prebid\'s Events', function () {
let eventHandler;
const videoCore = Object.assign({}, videoCoreMock, {
onEvents: (events, eventHandler_) => eventHandler = eventHandler_
});
pbVideoFactory(videoCore, getConfig);
getConfigCallback({ video: { providers } });
const expectedType = 'test_event';
const expectedPayload = {'test': 'data'};
eventHandler(expectedType, expectedPayload);
expect(pbEventsMock.emit.calledOnce).to.be.true;
expect(pbEventsMock.emit.getCall(0).args[0]).to.be.equal(expectedType);
expect(pbEventsMock.emit.getCall(0).args[1]).to.be.equal(expectedPayload);
});
});
});

describe('Ad unit Enrichment', function () {
it('registers before:bidRequest hook', function () {
const pbVideo = pbVideoFactory();
expect(requestBidsMock.before.calledOnce).to.be.true;
});

it('requests oRtb params and writes them to ad unit', function() {
const getOrtbParamsSpy = sinon.spy(videoCoreMock, 'getOrtbParams');
let beforeBidRequestCallback;
const requestBids = {
before: callback_ => beforeBidRequestCallback = callback_
};

const pbVideo = pbVideoFactory(null, null, { requestBids });
expect(beforeBidRequestCallback).to.not.be.undefined;
const nextFn = sinon.spy();
const adUnits = [{
code: 'ad1',
mediaTypes: {
video: {}
},
video: { divId: 'divId' }
}];
beforeBidRequestCallback(nextFn, { adUnits });
expect(getOrtbParamsSpy.calledOnce).to.be.true;
const adUnit = adUnits[0];
expect(adUnit.mediaTypes.video).to.have.property('video');
expect(adUnit.mediaTypes.video).to.have.property('content');
expect(nextFn.calledOnce).to.be.true;
});
});

describe('Ad tag injection', function () {
// TODO: requires adServer to be implemented
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
timeStateFactory,
callbackStorageFactory,
utils
} from 'modules/videoModule/submodules/jwplayerVideoProvider';
} from 'modules/jwplayerVideoProvider';

import {
PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE
Expand Down