diff --git a/integrationExamples/videoModule/index.html b/integrationExamples/videoModule/index.html
new file mode 100644
index 00000000000..3deb6689263
--- /dev/null
+++ b/integrationExamples/videoModule/index.html
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+ Prebid.js Test
+ Div-1
+
+ Div-2
+
+
+
+
+
diff --git a/modules/.submodules.json b/modules/.submodules.json
index 555e8adaefa..c4d279e771a 100644
--- a/modules/.submodules.json
+++ b/modules/.submodules.json
@@ -52,5 +52,9 @@
"fpdModule": [
"enrichmentFpdModule",
"validationFpdModule"
+ ],
+ "videoModule": [
+ "coreVideo",
+ "jwplayerVideoProvider"
]
}
diff --git a/modules/videoModule/submodules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js
similarity index 98%
rename from modules/videoModule/submodules/jwplayerVideoProvider.js
rename to modules/jwplayerVideoProvider.js
index 8350739d895..3bd47060107 100644
--- a/modules/videoModule/submodules/jwplayerVideoProvider.js
+++ b/modules/jwplayerVideoProvider.js
@@ -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_;
diff --git a/modules/videoModule/constants/events.js b/modules/videoModule/constants/events.js
index fbf8444f4a9..c1a4e2ca7d5 100644
--- a/modules/videoModule/constants/events.js
+++ b/modules/videoModule/constants/events.js
@@ -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,
diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js
index 5288dfd9f52..36b0a90f4ff 100644
--- a/modules/videoModule/coreVideo.js
+++ b/modules/videoModule/coreVideo.js
@@ -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) {
@@ -58,7 +63,7 @@ export function VideoSubmoduleBuilder(vendorDirectory_) {
}
const submodule = submoduleFactory(providerConfig);
- submodule.init && submodule.init();
+ submodule && submodule.init && submodule.init();
return submodule;
}
diff --git a/modules/videoModule/index.js b/modules/videoModule/index.js
new file mode 100644
index 00000000000..774704a4c32
--- /dev/null
+++ b/modules/videoModule/index.js
@@ -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();
diff --git a/src/events.js b/src/events.js
index 8749ddf206b..e52ac18b383 100644
--- a/src/events.js
+++ b/src/events.js
@@ -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
diff --git a/test/spec/modules/videoModule/pbVideo_spec.js b/test/spec/modules/videoModule/pbVideo_spec.js
new file mode 100644
index 00000000000..10d4c5e494e
--- /dev/null
+++ b/test/spec/modules/videoModule/pbVideo_spec.js
@@ -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
+ });
+});
diff --git a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js
index 5d6f4eeb18d..16b143cf706 100644
--- a/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js
+++ b/test/spec/modules/videoModule/submodules/jwplayerVideoProvider_spec.js
@@ -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