diff --git a/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html new file mode 100644 index 00000000000..7c73312c5c3 --- /dev/null +++ b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html @@ -0,0 +1,143 @@ + + + + + + + AdPlayer.Pro bid request scheduling + + + + + + + + +

AdPlayer.Pro Event Listeners

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/adPlayerPro/eventListeners.html b/integrationExamples/videoModule/adPlayerPro/eventListeners.html new file mode 100644 index 00000000000..3c26ef42bee --- /dev/null +++ b/integrationExamples/videoModule/adPlayerPro/eventListeners.html @@ -0,0 +1,154 @@ + + + + + + + AdPlayer.Pro Event Listeners + + + + + + + + +

AdPlayer.Pro Event Listeners

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/libraries/video/constants/vendorCodes.js b/libraries/video/constants/vendorCodes.js index 370c151b997..4e3550ce431 100644 --- a/libraries/video/constants/vendorCodes.js +++ b/libraries/video/constants/vendorCodes.js @@ -1,6 +1,7 @@ // Video Vendors export const JWPLAYER_VENDOR = 1; export const VIDEO_JS_VENDOR = 2; +export const AD_PLAYER_PRO_VENDOR = 3; // Ad Server Vendors export const GAM_VENDOR = 'gam'; diff --git a/modules/.submodules.json b/modules/.submodules.json index 97017237909..3ac541ce4ea 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -110,7 +110,8 @@ ], "videoModule": [ "jwplayerVideoProvider", - "videojsVideoProvider" + "videojsVideoProvider", + "adplayerproVideoProvider" ], "paapi": [ "paapiForGpt", diff --git a/modules/adplayerproVideoProvider.js b/modules/adplayerproVideoProvider.js new file mode 100644 index 00000000000..826aee257ec --- /dev/null +++ b/modules/adplayerproVideoProvider.js @@ -0,0 +1,439 @@ +import { + API_FRAMEWORKS, + PLACEMENT, + PLAYBACK_METHODS, + PROTOCOLS, + VIDEO_MIME_TYPE, + VPAID_MIME_TYPE +} from '../libraries/video/constants/ortb.js'; +import { + AD_CLICK, + AD_COMPLETE, + AD_ERROR, + AD_IMPRESSION, + AD_LOADED, + AD_PAUSE, + AD_PLAY, + AD_REQUEST, + AD_SKIPPED, + AD_STARTED, + DESTROYED, + ERROR, + MUTE, + PLAYER_RESIZE, + SETUP_COMPLETE, + SETUP_FAILED, + VOLUME +} from '../libraries/video/constants/events.js'; +import {AD_PLAYER_PRO_VENDOR} from '../libraries/video/constants/vendorCodes.js'; +import {getEventHandler} from '../libraries/video/shared/eventHandler.js'; +import {submodule} from '../src/hook.js'; + +const setupFailMessage = 'Failed to instantiate the player'; + +/** + * @constructor + * @param {Object} config - videoProviderConfig + * @param {function} adPlayerPro_ + * @param {CallbackStorage} callbackStorage_ + * @param {Object} utils + * @returns {Object} - VideoProvider + */ +export function AdPlayerProProvider(config, adPlayerPro_, callbackStorage_, utils) { + const adPlayerPro = adPlayerPro_; + let player = null; + let playerVersion = null; + const playerConfig = config.playerConfig; + const divId = config.divId; + let callbackStorage = callbackStorage_; + let supportedMediaTypes = null; + let setupCompleteCallbacks = []; + let setupFailedCallbacks = []; + const MEDIA_TYPES = [ + VIDEO_MIME_TYPE.MP4, + VIDEO_MIME_TYPE.OGG, + VIDEO_MIME_TYPE.WEBM, + VIDEO_MIME_TYPE.AAC, + VIDEO_MIME_TYPE.HLS + ]; + + function init() { + if (!adPlayerPro) { + triggerSetupFailure(-1, setupFailMessage + ': player not present'); + return; + } + + // if (playerVersion < minimumSupportedPlayerVersion) { + // triggerSetupFailure(-2, setupFailMessage + ': player version not supported'); + // return; + // } + + if (!document.getElementById(divId)) { + triggerSetupFailure(-3, setupFailMessage + ': No div found with id ' + divId); + return; + } + + if (!playerConfig || !playerConfig.placementId) { + triggerSetupFailure(-4, setupFailMessage + ': placementId is required in playerConfig'); + return; + } + + triggerSetupComplete(); + } + + function getId() { + return divId; + } + + function getOrtbVideo() { + supportedMediaTypes = supportedMediaTypes || utils.getSupportedMediaTypes(MEDIA_TYPES); + + const video = { + mimes: supportedMediaTypes, + protocols: [ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ], + // h: player.getHeight(), + // w: player.getWidth(), + placement: utils.getPlacement(playerConfig.advertising), + maxextended: -1, // extension is allowed, and there is no time limit imposed. + boxingallowed: 1, + playbackmethod: [utils.getPlaybackMethod(config)], + playbackend: 1, + api: [ + API_FRAMEWORKS.VPAID_2_0, + API_FRAMEWORKS.OMID_1_0 + ], + }; + + return video; + } + + function getOrtbContent() { + } + + function setAdTagUrl(adTagUrl, options) { + setupPlayer(playerConfig, adTagUrl || options.adXml) + } + + function onEvent(externalEventName, callback, basePayload) { + if (externalEventName === SETUP_COMPLETE) { + setupCompleteCallbacks.push(callback); + return; + } + + if (externalEventName === SETUP_FAILED) { + setupFailedCallbacks.push(callback); + return; + } + + let getEventPayload; + + switch (externalEventName) { + case AD_REQUEST: + case AD_PLAY: + case AD_PAUSE: + case AD_LOADED: + case AD_STARTED: + case AD_IMPRESSION: + case AD_CLICK: + case AD_SKIPPED: + case AD_ERROR: + case AD_COMPLETE: + case MUTE: + case VOLUME: + case ERROR: + case PLAYER_RESIZE: + getEventPayload = e => ({ + height: player.getAdHeight(), + width: player.getAdWidth(), + }); + break; + default: + return; + } + + // eslint-disable-next-line no-unreachable + const playerEventName = utils.getPlayerEvent(externalEventName); + const eventHandler = getEventHandler(externalEventName, callback, basePayload, getEventPayload) + player && player.on(playerEventName, eventHandler); + callbackStorage.storeCallback(playerEventName, eventHandler, callback); + } + + function offEvent(event, callback) { + const playerEventName = utils.getPlayerEvent(event); + const eventHandler = callbackStorage.getCallback(playerEventName, callback); + if (eventHandler) { + player && player.off(playerEventName, eventHandler); + } else { + player && player.off(playerEventName); + } + callbackStorage.clearCallback(playerEventName, callback); + } + + function destroy() { + if (!player) { + return; + } + player.remove(); + player = null; + } + + return { + init, + getId, + getOrtbVideo, + getOrtbContent, + setAdTagUrl, + onEvent, + offEvent, + destroy + }; + + function setupPlayer(config, urlOrXml) { + if (!config || player) { + return; + } + const playerConfig = utils.getConfig(config, urlOrXml); + + if (!playerConfig) { + return; + } + + player = adPlayerPro(divId); + callbackStorage.addAllCallbacks(player.on); + player.on('AdStopped', () => player = null); + player.setup(playerConfig); + } + + function triggerSetupComplete() { + if (!setupCompleteCallbacks.length) { + return; + } + + const payload = getSetupCompletePayload(); + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; + } + + function getSetupCompletePayload() { + return { + divId, + playerVersion, + type: SETUP_COMPLETE + }; + } + + function triggerSetupFailure(errorCode, msg, sourceError) { + if (!setupFailedCallbacks.length) { + return; + } + + const payload = { + divId, + playerVersion, + type: SETUP_FAILED, + errorCode: errorCode, + errorMessage: msg, + sourceError: sourceError + }; + + setupFailedCallbacks.forEach(callback => callback(SETUP_FAILED, payload)); + setupFailedCallbacks = []; + } +} + +/** + * @param {Object} config - videoProviderConfig + * @param {sharedUtils} sharedUtils + * @returns {Object} - VideoProvider + */ +const adPlayerProSubmoduleFactory = function (config, sharedUtils) { + const callbackStorage = callbackStorageFactory(); + return AdPlayerProProvider(config, window.playerPro, callbackStorage, utils); +} + +adPlayerProSubmoduleFactory.vendorCode = AD_PLAYER_PRO_VENDOR; +submodule('video', adPlayerProSubmoduleFactory); +export default adPlayerProSubmoduleFactory; + +// HELPERS + +export const utils = { + getConfig: function (config, urlOrXml) { + if (!config || !urlOrXml) { + return; + } + + const params = config.params || {}; + params.placementId = config.placementId; + params.advertising = params.advertising || {}; + params.advertising.tag = params.advertising.tag || {}; + + params._pType = 'pbjs'; + params.advertising.tag.url = urlOrXml; + return params; + }, + + getPlayerEvent: function (eventName) { + switch (eventName) { + case DESTROYED: + return 'AdStopped'; + + case AD_REQUEST: + return 'AdRequest'; + case AD_LOADED: + return 'AdLoaded'; + case AD_STARTED: + return 'AdStarted'; + case AD_IMPRESSION: + return 'AdImpression'; + case AD_PLAY: + return 'AdPlaying'; + case AD_PAUSE: + return 'AdPaused'; + case AD_CLICK: + return 'AdClickThru'; + case AD_SKIPPED: + return 'AdSkipped'; + case AD_ERROR: + return 'AdError'; + case AD_COMPLETE: + return 'AdCompleted'; + case VOLUME: + return 'AdVolumeChange'; + case PLAYER_RESIZE: + return 'AdSizeChange'; + // case FULLSCREEN: + // return FULLSCREEN; + default: + return eventName; + } + }, + + getSupportedMediaTypes: function (mediaTypes = []) { + const el = document.createElement('video'); + return mediaTypes + .filter(mediaType => el.canPlayType(mediaType)) + .concat(VPAID_MIME_TYPE); // Always allow VPAIDs. + }, + + /** + * Determine the ad placement + * @param {Object} adConfig + * @return {PLACEMENT|undefined} + */ + getPlacement: function (adConfig) { + adConfig = adConfig || {}; + + switch (adConfig.type) { + case 'inPage': + return PLACEMENT.ARTICLE; + case 'rewarded': + case 'inView': + return PLACEMENT.INTERSTITIAL_SLIDER_FLOATING; + default: + return PLACEMENT.BANNER; + } + }, + + getPlaybackMethod: function ({autoplay, mute}) { + if (autoplay) { + return mute ? PLAYBACK_METHODS.AUTOPLAY_MUTED : PLAYBACK_METHODS.AUTOPLAY; + } + return PLAYBACK_METHODS.CLICK_TO_PLAY; + } +} + +/** + * Tracks which functions are attached to events + * @typedef CallbackStorage + * @function storeCallback + * @function getCallback + * @function clearCallback + * @function addAllCallbacks + * @function clearStorage + */ + +/** + * @returns {CallbackStorage} + */ +export function callbackStorageFactory() { + let storage = {}; + let storageHandlers = {}; + + function storeCallback(eventType, eventHandler, callback) { + let eventHandlers = storage[eventType]; + if (!eventHandlers) { + eventHandlers = storage[eventType] = {}; + } + + eventHandlers[callback] = eventHandler; + addHandler(eventType, eventHandler); + } + + function getCallback(eventType, callback) { + let eventHandlers = storage[eventType]; + if (eventHandlers) { + return eventHandlers[callback]; + } + } + + function clearCallback(eventType, callback) { + if (!callback) { + delete storage[eventType]; + delete storageHandlers[eventType]; + return; + } + let eventHandlers = storage[eventType]; + if (eventHandlers) { + const eventHandler = eventHandlers[callback]; + if (eventHandler) { + delete eventHandlers[callback]; + clearHandler(eventType, eventHandler); + } + } + } + + function clearStorage() { + storage = {}; + storageHandlers = {}; + } + + function addHandler(eventType, eventHandler) { + let eventHandlers = storageHandlers[eventType]; + if (!eventHandlers) { + eventHandlers = storageHandlers[eventType] = []; + } + eventHandlers.push(eventHandler); + } + + function clearHandler(eventType, eventHandler) { + let eventHandlers = storageHandlers[eventType]; + eventHandlers = eventHandlers.filter(handler => handler !== eventHandler); + if (eventHandlers.length) { + storageHandlers[eventType] = eventHandlers; + } else { + delete storageHandlers[eventType]; + } + } + + function addAllCallbacks(functionOnPlayer) { + for (let eventType in storageHandlers) { + storageHandlers[eventType].forEach(handler => functionOnPlayer(eventType, handler)); + } + } + + return { + storeCallback, + getCallback, + clearCallback, + addAllCallbacks, + clearStorage, + } +} diff --git a/modules/adplayerproVideoProvider.md b/modules/adplayerproVideoProvider.md new file mode 100644 index 00000000000..d0b0601afeb --- /dev/null +++ b/modules/adplayerproVideoProvider.md @@ -0,0 +1,54 @@ +# Overview + +Module Name: AdPlayer.Pro Video Provider +Module Type: Video Submodule +Video Player: AdPlayer.Pro +Player website: https://adplayer.pro +Maintainer: support@adplayer.pro + +# Description + +Video provider to connect the Prebid Video Module to AdPlayer.Pro. + +# Requirements + +Your page must embed a build of AdPlayer.Pro. +i.e. +```html + + + +``` + +# Configuration + +The AdPlayer.Pro Video Provider requires the following configuration: + +```javascript +pbjs.setConfig({ + video: { + providers: [{ + divId: 'player', // required, this is the id of the div element where the player will be placed + vendorCode: 3, // AdPlayer.Pro vendorCode + playerConfig: { + placementId: 'c9gebfehcqjE', // required, this placementId is only for demo purposes + params: { + 'type': 'inView', + 'muted': true, + 'autoStart': true, + 'advertising': { + 'controls': true, + 'closeButton': true, + }, + 'width': '600', + 'height': '300' + } + }, + }] + } +}); +``` + +[Additional embed instructions](https://docs.adplayer.pro) + +[Obtaining a license](https://adplayer.pro/contacts) diff --git a/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js new file mode 100644 index 00000000000..1a792411497 --- /dev/null +++ b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js @@ -0,0 +1,521 @@ +// Using require style imports for fine grained control of import time +import { + AD_CLICK, + AD_COMPLETE, + AD_ERROR, + AD_IMPRESSION, + AD_LOADED, + AD_PAUSE, + AD_PLAY, + AD_REQUEST, + AD_SKIPPED, + AD_STARTED, + DESTROYED, + PLAYER_RESIZE, + SETUP_COMPLETE, + SETUP_FAILED, + VOLUME +} from 'libraries/video/constants/events.js'; +import adPlayerProSubmoduleFactory, {callbackStorageFactory} from '../../../../../modules/adplayerproVideoProvider.js'; +import {PLACEMENT} from '../../../../../libraries/video/constants/ortb'; +import sinon from 'sinon'; + +const {AdPlayerProProvider, utils} = require('modules/adplayerproVideoProvider.js'); + +const { + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, VPAID_MIME_TYPE +} = require('libraries/video/constants/ortb.js'); + +function getPlayerMock() { + return { + setup: function () { + return this; + }, + load: function () { + }, + resize: function () { + }, + remove: function () { + }, + on: function () { + return this; + }, + off: function () { + return this; + }, + getAdWidth: function () { + return 600 + }, + getAdHeight: function () { + return 400; + } + }; +} + +function makePlayerFactoryMock(playerMock_) { + return () => playerMock_; +} + +function getUtilsMock() { + return { + getConfig: function () { + }, + getPlayerEvent: event => event, + getSupportedMediaTypes: function () { + }, + getPlacement: function () { + }, + getPlaybackMethod: function () { + } + }; +} + +function addDiv() { + const div = document.createElement('div'); + div.setAttribute('id', 'test'); + document.body.appendChild(div); +} + +function removeDiv() { + const div = document.getElementById('test'); + if (div) { + div.remove(); + } +} + +describe('AdPlayerProProvider', function () { + let config; + let callbackStorage; + let utilsMock; + let player; + + beforeEach(() => { + addDiv(); + config = {divId: 'test', playerConfig: {placementId: 'testId'}}; + callbackStorage = callbackStorageFactory(); + utilsMock = getUtilsMock(); + player = getPlayerMock(); + }); + + afterEach(() => { + removeDiv(); + }); + + describe('init', function () { + it('should trigger failure when Adplayer.Pro is missing', function () { + const provider = AdPlayerProProvider(config, null, callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-1); + }); + + it('should trigger failure when the div is not found', function () { + config.divId = 'fake-div' + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-3); + }); + + it('should trigger failure when the placementId is not found', function () { + config.playerConfig.placementId = ''; + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-4); + }); + + it('should instantiate the player after setAdTagUrl', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + }); + + it('should instantiate the player after setAdTagUrl for adPlayerProSubmoduleFactory', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + window.playerPro = makePlayerFactoryMock(player); + const provider = adPlayerProSubmoduleFactory(config, {}); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + }); + + it('should trigger setup complete when player is already instantiated', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + const setupComplete = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupSpy.called).to.be.false; + }); + + it('should support multiple setup complete event handlers', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + const setupComplete = sinon.spy(); + const setupComplete2 = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.onEvent(SETUP_COMPLETE, setupComplete2, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupComplete2.calledOnce).to.be.true; + expect(setupSpy.called).to.be.false; + }); + + it('should not reinstantiate player', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const onSpy = player.on = sinon.spy(player.on); + + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + + // test that the player is not reinitialized + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + + // get and call AdStopped event + const args = onSpy.args[0]; + expect(args[0]).to.be.equal('AdStopped'); + args[1](); + + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledTwice).to.be.true; + }); + }); + + describe('getId', function () { + it('should return configured div id', function () { + const provider = AdPlayerProProvider(config, undefined, undefined, utils); + expect(provider.getId()).to.be.equal('test'); + }); + }); + + describe('getOrtbVideo', function () { + it('should populate oRTB Video params', function () { + const test_media_type = VIDEO_MIME_TYPE.MP4; + const test_placement = PLACEMENT.ARTICLE; + const test_playback_method = PLAYBACK_METHODS.CLICK_TO_PLAY; + + utilsMock.getSupportedMediaTypes = () => [test_media_type]; + utilsMock.getPlacement = () => test_placement; + utilsMock.getPlaybackMethod = () => test_playback_method; + + const provider = AdPlayerProProvider(config, null, null, utilsMock); + provider.init(); + let video = provider.getOrtbVideo(); + + expect(video.mimes).to.include(VIDEO_MIME_TYPE.MP4); + expect(video.protocols).to.include.members([ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ]); + expect(video.placement).to.equal(test_placement); + expect(video.maxextended).to.equal(-1); + expect(video.boxingallowed).to.equal(1); + expect(video.playbackmethod).to.include(test_playback_method); + expect(video.playbackend).to.equal(1); + expect(video.api).to.have.length(2); + expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); // + }); + }); + + describe('getOrtbContent', function () { + it('should populate oRTB Content params', function () { + const provider = AdPlayerProProvider(config, null, null, utils); + provider.init(); + expect(provider.getOrtbContent()).to.be.undefined; + }); + }); + + describe('setAdTagUrl', function () { + it('should call setup', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('', {adXml: 'https://test.com'}); + expect(setupSpy.calledOnce).to.be.true; + }); + + it('should not call setup', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('', {}); + expect(setupSpy.calledOnce).to.be.false; + }); + }); + + describe('events', function () { + it('should register event listener on player', function () { + const onSpy = player.on = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_REQUEST, callback, {}); + provider.onEvent('test', callback, {}); + expect(onSpy.calledTwice).to.be.true; + expect(onSpy.args[0][0]).to.be.equal('AdStopped'); + expect(onSpy.args[1][0]).to.be.equal('AdRequest'); + }); + + it('should remove event listener on player', function () { + const offSpy = player.off = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_IMPRESSION, callback, {}); + provider.offEvent(AD_IMPRESSION, callback); + expect(offSpy.calledOnce).to.be.true; + const eventName = offSpy.args[0][0]; + const eventCallback = offSpy.args[0][1]; + expect(eventName).to.be.equal('AdImpression'); + expect(eventCallback).to.be.exist; + }); + + it('should remove event listener on player by eventName', function () { + const offSpy = player.off = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_IMPRESSION, callback, {}); + provider.offEvent(AD_IMPRESSION); + expect(offSpy.calledOnce).to.be.true; + const eventName = offSpy.args[0][0]; + expect(eventName).to.be.equal('AdImpression'); + }); + + it('should call event player resize', function () { + const onSpy = player.on = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callbackSpy = sinon.spy(); + provider.onEvent(PLAYER_RESIZE, callbackSpy, {}); + expect(onSpy.calledTwice).to.be.true; + expect(onSpy.args[1][0]).to.be.equal('AdSizeChange'); + expect(callbackSpy.notCalled).to.be.true; + onSpy.args[1][1](); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.args[0][1].width).to.be.equal(600); + expect(callbackSpy.args[0][1].height).to.be.equal(400); + }); + }); + + describe('destroy', function () { + it('should remove and null the player', function () { + const removeSpy = player.remove = sinon.spy(); + player.remove = removeSpy; + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + provider.destroy(); + provider.destroy(); + expect(removeSpy.calledOnce).to.be.true; + }); + }); +}); + +describe('AdPlayerProProvider utils', function () { + it('getConfig', function () { + expect(utils.getConfig()).to.be.undefined; + expect(utils.getConfig({})).to.be.undefined; + const config = utils.getConfig({}, 'https://test.com'); + expect(config.advertising.tag.url).to.be.equal('https://test.com'); + expect(config._pType).to.be.equal('pbjs'); + }); + + it('getPlayerEvent', function () { + function test(event, expected) { + expect(utils.getPlayerEvent(event)).to.be.equal(expected); + } + + test(DESTROYED, 'AdStopped'); + test(AD_REQUEST, 'AdRequest'); + test(AD_LOADED, 'AdLoaded'); + test(AD_STARTED, 'AdStarted'); + test(AD_IMPRESSION, 'AdImpression'); + test(AD_PLAY, 'AdPlaying'); + test(AD_PAUSE, 'AdPaused'); + test(AD_CLICK, 'AdClickThru'); + test(AD_SKIPPED, 'AdSkipped'); + test(AD_ERROR, 'AdError'); + test(AD_COMPLETE, 'AdCompleted'); + test(VOLUME, 'AdVolumeChange'); + test(PLAYER_RESIZE, 'AdSizeChange'); + test('test', 'test'); + }); + + it('getSupportedMediaTypes', function () { + let supportedMediaTypes = utils.getSupportedMediaTypes([]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + + supportedMediaTypes = utils.getSupportedMediaTypes([VIDEO_MIME_TYPE.MP4]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + }); + + it('getPlacement', function () { + function test(config, expected) { + expect(utils.getPlacement(config)).to.be.equal(expected); + } + + test(false, PLACEMENT.BANNER); + test({}, PLACEMENT.BANNER); + test({type: 'test'}, PLACEMENT.BANNER); + test({type: 'inPage'}, PLACEMENT.ARTICLE); + test({type: 'rewarded'}, PLACEMENT.INTERSTITIAL_SLIDER_FLOATING); + test({type: 'inView'}, PLACEMENT.INTERSTITIAL_SLIDER_FLOATING); + }); + + it('getPlaybackMethod', function () { + function test(autoplay, mute, expected) { + expect(utils.getPlaybackMethod({autoplay, mute})).to.be.equal(expected); + } + + test(false, false, PLAYBACK_METHODS.CLICK_TO_PLAY); + test(false, true, PLAYBACK_METHODS.CLICK_TO_PLAY); + test(true, false, PLAYBACK_METHODS.AUTOPLAY); + test(true, true, PLAYBACK_METHODS.AUTOPLAY_MUTED); + }); +}); + +describe('AdPlayerProProvider callbackStorageFactory', function () { + let player; + let callbackStorage; + + beforeEach(() => { + player = getPlayerMock(); + callbackStorage = callbackStorageFactory(); + }); + + it('storeCallback and getCallback', function () { + const eventType = 'test'; + const callback = () => 11; + const eventHandler = () => 12; + callbackStorage.storeCallback(eventType, eventHandler, callback); + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + + const callback2 = () => 21; + const eventHandler2 = () => 22; + callbackStorage.storeCallback(eventType, eventHandler2, callback2); + expect(callbackStorage.getCallback(eventType, callback2)).to.be.equal(eventHandler2); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + }); + + it('clearCallback', function () { + const eventType = 'test'; + const callback = () => 11; + const callback2 = () => 21; + callbackStorage.storeCallback(eventType, () => 12, callback); + callbackStorage.storeCallback(eventType, () => 22, callback2); + + expect(callbackStorage.getCallback(eventType, callback)()).to.be.equal(12); + expect(callbackStorage.getCallback(eventType, callback2)()).to.be.equal(22); + + callbackStorage.clearCallback(eventType); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + expect(callbackStorage.getCallback(eventType, callback2)).to.be.undefined; + + callbackStorage.storeCallback(eventType, () => 12, callback); + callbackStorage.storeCallback(eventType, () => 22, callback2); + + callbackStorage.clearCallback(eventType, callback); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + expect(callbackStorage.getCallback(eventType, callback2)()).to.be.equal(22); + }); + + it('addAllCallbacks', function () { + const eventType = 'test'; + const eventType2 = 'test2'; + const callback = () => 11; + const callback2 = () => 21; + const eventHandler = () => 12; + const eventHandler2 = () => 22; + callbackStorage.storeCallback(eventType, eventHandler, callback); + callbackStorage.storeCallback(eventType, eventHandler2, callback2); + callbackStorage.storeCallback(eventType2, eventHandler, callback); + callbackStorage.storeCallback(eventType2, eventHandler2, callback2); + + let spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(4); + expect(spy.calledWith(eventType, eventHandler)).to.be.true; + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler2)).to.be.true; + + callbackStorage.clearCallback(eventType, callback); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(3); + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler2)).to.be.true; + + callbackStorage.clearCallback(eventType2); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(1); + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + + callbackStorage.storeCallback(eventType, eventHandler, callback); + callbackStorage.storeCallback(eventType2, eventHandler, callback); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(3); + expect(spy.calledWith(eventType, eventHandler)).to.be.true; + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + + callbackStorage.clearStorage(); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(0); + }); + + it('clearStorage', function () { + const eventType = 'test'; + const callback = () => 11; + const eventHandler = () => 12; + callbackStorage.storeCallback(eventType, eventHandler, callback); + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + + callbackStorage.clearStorage(); + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + }); +});