Skip to content

Commit

Permalink
Merge pull request #6 from jwplayer/feat/AD-1498
Browse files Browse the repository at this point in the history
Connect coreVideo to Prebid
  • Loading branch information
karimMourra authored Sep 22, 2021
2 parents ce34f3e + 396158c commit f474b41
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 9 deletions.
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) {
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

0 comments on commit f474b41

Please sign in to comment.