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

[AD-1499] Integrate Video Module with Ad Server #7

Merged
merged 12 commits into from
Oct 14, 2021
22 changes: 22 additions & 0 deletions modules/gamAdServerSubmodule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { GAM_VENDOR } from './videoModule/constants/vendorCodes.js';
import { adServerDirectory } from './videoModule/vendorDirectory.js';

function GamAdServerProvider(dfpModule_) {
const dfp = dfpModule_;

function getAdTagUrl(adUnit, baseAdTag) {
return dfp.buildVideoUrl({ adUnit: adUnit, url: baseAdTag });
}

return {
getAdTagUrl
}
}
function gamSubmoduleFactory() {
const dfp = $$PREBID_GLOBAL$$.adServers.dfp;
const gamProvider = GamAdServerProvider(dfp);
return gamProvider;
}

gamSubmoduleFactory.vendorCode = GAM_VENDOR;
adServerDirectory[GAM_VENDOR] = gamSubmoduleFactory;
8 changes: 4 additions & 4 deletions modules/jwplayerVideoProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} 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';
import { videoVendorDirectory } from './videoModule/vendorDirectory.js';

export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callbackStorage_, utils) {
const jwplayer = jwplayer_;
Expand Down Expand Up @@ -124,11 +124,11 @@ export function JWPlayerProvider(config, jwplayer_, adState_, timeState_, callba
}
}

function setAdTagUrl(adTagUrl) {
function setAdTagUrl(adTagUrl, options) {
if (!player) {
return;
}
player.playAd(adTagUrl);
player.playAd(adTagUrl || options.adXml, options);
}

function onEvents(events, callback) {
Expand Down Expand Up @@ -632,7 +632,7 @@ const jwplayerSubmoduleFactory = function (config) {
}

jwplayerSubmoduleFactory.vendorCode = JWPLAYER_VENDOR;
vendorDirectory[JWPLAYER_VENDOR] = jwplayerSubmoduleFactory;
videoVendorDirectory[JWPLAYER_VENDOR] = jwplayerSubmoduleFactory;
export default jwplayerSubmoduleFactory;

// HELPERS
Expand Down
28 changes: 28 additions & 0 deletions modules/videoModule/adServer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { adServerDirectory } from './vendorDirectory.js';
import { ParentModule, SubmoduleBuilder } from './shared/parentModule.js';

export function AdServerCore(parentModule_) {
const parentModule = parentModule_;

function registerAdServer(config) {
const vendorCode = config.vendorCode;
parentModule.registerSubmodule(vendorCode, vendorCode, config);
}

function getAdTagUrl(vendorCode, adUnit, baseAdTagUrl) {
const submodule = parentModule.getSubmodule(vendorCode);
return submodule && submodule.getAdTagUrl(adUnit, baseAdTagUrl);
}

return {
registerAdServer,
getAdTagUrl
}
}

export function coreAdServerFactory() {
const adServerSubmoduleBuilder = SubmoduleBuilder(adServerDirectory);
const parentModule = ParentModule(adServerSubmoduleBuilder);
const adServerCore = AdServerCore(parentModule);
return adServerCore;
}
4 changes: 4 additions & 0 deletions modules/videoModule/constants/vendorCodes.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
// Video Vendors
export const JWPLAYER_VENDOR = 1;
export const VIDEO_JS_VENDOR = 2;

// Ad Server Vendors
export const GAM_VENDOR = 'gam';
57 changes: 14 additions & 43 deletions modules/videoModule/coreVideo.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,30 @@
import { vendorDirectory } from './vendorDirectory.js';
import { videoVendorDirectory } from './vendorDirectory.js';
import { ParentModule, SubmoduleBuilder } from './shared/parentModule.js';

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

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

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

function getOrtbParams(divId) {
const submodule = submodules[divId];
const submodule = parentModule.getSubmodule(divId);
return submodule && submodule.getOrtbParams();
}

function setAdTagUrl(adTagUrl, divId) {
const submodule = submodules[divId];
return submodule && submodule.setAdTagUrl(adTagUrl);
function setAdTagUrl(adTagUrl, divId, options) {
const submodule = parentModule.getSubmodule(divId);
return submodule && submodule.setAdTagUrl(adTagUrl, options);
}

function onEvents(events, callback, divId) {
const submodule = submodules[divId];
const submodule = parentModule.getSubmodule(divId);
return submodule && submodule.onEvents(events, callback);
}

function offEvents(events, callback, divId) {
const submodule = submodules[divId];
const submodule = parentModule.getSubmodule(divId);
return submodule && submodule.offEvents(events, callback);
}

Expand All @@ -49,25 +38,7 @@ export function VideoCore(submoduleBuilder_) {
}

export function videoCoreFactory() {
const submoduleBuilder = VideoSubmoduleBuilder(vendorDirectory);
return VideoCore(submoduleBuilder);
}

export function VideoSubmoduleBuilder(vendorDirectory_) {
const vendorDirectory = vendorDirectory_;

function build(providerConfig) {
const submoduleFactory = vendorDirectory[providerConfig.vendorCode];
if (!submoduleFactory) {
throw new Error('Unrecognized vendor code');
}

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

return {
build
};
const videoSubmoduleBuilder = SubmoduleBuilder(videoVendorDirectory);
const parentModule = ParentModule(videoSubmoduleBuilder);
return VideoCore(parentModule);
}
55 changes: 45 additions & 10 deletions modules/videoModule/index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import {config} from '../../src/config.js';
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';
import { coreAdServerFactory } from './adServer.js';

events.addEvents(allVideoEvents);

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

function init() {
getConfig('video', ({ video }) => {
Expand All @@ -23,18 +25,27 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent
pbEvents.emit(type, payload);
}, provider.divId);
} catch (e) {}

const adServerConfig = provider.adServer;
if (adServerConfig) {
adServerCore.registerAdServer(adServerConfig.vendorCode, adServerConfig.params);
}
});
});

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);
auctionResult.adUnits.forEach(adUnit => {
if (adUnit.video) {
renderWinningBid(adUnit);
}
});
});
}

return { init };

function enrichAdUnits(nextFn, bidRequest) {
const adUnits = bidRequest.adUnits || pbGlobal.adUnits || [];
adUnits.forEach(adUnit => {
Expand All @@ -44,14 +55,38 @@ export function PbVideo(videoCore_, getConfig_, pbGlobal_, pbEvents_, videoEvent
return nextFn.call(this, bidRequest);
}

return { init };
function renderWinningBid(adUnit) {
const videoConfig = adUnit.video;
const divId = videoConfig.divId;
const adServerConfig = videoConfig.adServer;
let adTagUrl;
if (adServerConfig) {
adTagUrl = adServerCore.getAdTagUrl(adServerConfig.vendorCode, adUnit, adServerConfig.baseAdTagUrl);
}

const adUnitCode = adUnit.code;
const options = { adUnitCode };
if (adTagUrl) {
videoCore.setAdTagUrl(adTagUrl, divId, options);
return;
}

const highestCpmBids = pbGlobal.getHighestCpmBids(adUnit.code);
const highestBid = highestCpmBids && highestCpmBids.shift();
if (!highestBid) {
return;
}

adTagUrl = highestBid.vastUrl;
options.adXml = highestBid.vastXml;
videoCore.setAdTagUrl(adTagUrl, divId, options);
}
}

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

pbVideoFactory();
55 changes: 55 additions & 0 deletions modules/videoModule/shared/parentModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

/*
Used by any module to manage the relationship with its submodules.
*/
export function ParentModule(submoduleBuilder_) {
const submoduleBuilder = submoduleBuilder_;
const submodules = {};

/*
id: identifies the submodule instance
vendorCode: identifies the submodule type that must be built
config: additional information necessary to instantiate the instance
*/
function registerSubmodule(id, vendorCode, config) {
if (submodules[id]) {
return;
}

let submodule;
try {
submodule = submoduleBuilder.build(vendorCode, config);
} catch (e) {
throw e;
}
submodules[id] = submodule;
}

function getSubmodule(id) {
return submodules[id];
}

return {
registerSubmodule,
getSubmodule
}
}

export function SubmoduleBuilder(submoduleDirectory_) {
const submoduleDirectory = submoduleDirectory_;

function build(vendorCode, config) {
const submoduleFactory = submoduleDirectory[vendorCode];
if (!submoduleFactory) {
throw new Error('Unrecognized submodule vendor code: ' + vendorCode);
}

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

return {
build
};
}
3 changes: 2 additions & 1 deletion modules/videoModule/vendorDirectory.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const vendorDirectory = {};
export const videoVendorDirectory = {};
export const adServerDirectory = {};
28 changes: 28 additions & 0 deletions test/spec/modules/videoModule/adServer_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { expect } from 'chai';
import { AdServerCore } from 'modules/videoModule/adServer.js';

describe('Ad Server Core', function () {
const parentModuleMock = {
registerSubmodule: sinon.spy(),
getSubmodule: sinon.spy()
};
const testVendorCode = 'test';
const adServerCore = AdServerCore(parentModuleMock);
adServerCore.registerAdServer({ vendorCode: testVendorCode });

describe('Register Ad Server', function () {
it('should use the vendor code as an id as well as a vendor code', function () {
expect(parentModuleMock.registerSubmodule.calledOnce).to.be.true;
expect(parentModuleMock.registerSubmodule.args[0][0]).to.be.equal(testVendorCode);
expect(parentModuleMock.registerSubmodule.args[0][1]).to.be.equal(testVendorCode);
});
});

describe('Get Ad Tag Url', function () {
it('should request the right submodule', function () {
adServerCore.getAdTagUrl(testVendorCode);
expect(parentModuleMock.getSubmodule.calledOnce).to.be.true;
expect(parentModuleMock.getSubmodule.args[0][0]).to.be.equal(testVendorCode);
});
});
});
Loading