Skip to content

Commit

Permalink
JW Player Real Time Data Provider (#5537)
Browse files Browse the repository at this point in the history
* [AD-469] Add player vendor.

* ssets up targeting module

* implements getTargeting

* implements getPlayer

* blocks bids until all targeting requests complete

* makes getTarget more resilient

* enables mdule hook

* replaces triple dot notation

* Revert "replaces triple dot notation"

This reverts commit 7a76ea6.

* Revert "Revert "replaces triple dot notation""

This reverts commit 130aa2a.

* checks current item only if mediaid is missing

* adds unit tests

* completes test cases

* stores segments for current item

* renames jwp targeting

* refactors fetch tests

* refactors get targeting tests

* refactors blocking tests

* renames module

* cleans changes made to app nexus

* removes setup and player utilities

* renames onFetchCompletion

* renames onFetchCOmpletion in unti tests

* throws instead of early return

* reduces timeout and introduces override

* targeting timeout supersedes

* renames feed fetch timeout

* adds inline doc

* uses find util

* adds jwplayer rtd provider

* implements targeting retrieval

* ensures provider is found

* implements init

* jwTargeting is object

* commits test file

* adds file extension

* adds tests

* fixes test for proper structure

* uses default clock mock

* ends reqs before rtd module timeout

* removes obsolete export

* request counts updates in aggregate

* cleans server mock after each test

* deletes jwplayer targeting

* includes content id

* adds test for missing segment

* getSegments is nullable

* replaces condition with guard

* updates doc

* adds md file

* adds example page

Co-authored-by: vseventer <mark@vseventer.com>
Co-authored-by: karimJWP <karimJWP@github.com>
  • Loading branch information
3 people authored Aug 20, 2020
1 parent 67d184a commit 494015f
Show file tree
Hide file tree
Showing 5 changed files with 804 additions and 1 deletion.
98 changes: 98 additions & 0 deletions integrationExamples/gpt/jwplayerRtdProvider_example.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script async src="../../build/dev/prebid.js"></script>
<script async src="https://www.googletagservices.com/tag/js/gpt.js"></script>
<meta charset="UTF-8">
<title>JW Player RTD Provider Example</title>
<script>
var FAILSAFE_TIMEOUT = 3300;
var PREBID_TIMEOUT = 1000;

var adUnits = [{
code: 'div-gpt-ad-1460505748561-0',
jwTargeting: {
playerID: '123',
mediaID: 'abc'
},
mediaTypes: {
banner: {
sizes: [[300, 250], [300,600]],
}
},
// Replace this object to test a new Adapter!
bids: [{
bidder: 'appnexus',
params: {
placementId: 13144370
}
}]

}];

var pbjs = pbjs || {};
pbjs.que = pbjs.que || [];

</script>

<script>
var googletag = googletag || {};
googletag.cmd = googletag.cmd || [];
googletag.cmd.push(function() {
googletag.pubads().disableInitialLoad();
});

pbjs.que.push(function() {
pbjs.setConfig({
realTimeData: {
dataProviders: [{
name: "jwplayer",
params: {
mediaIDs: ['abc', 'def', 'ghi', 'jkl']
}
}]
}
});
pbjs.addAdUnits(adUnits);
pbjs.requestBids({
bidsBackHandler: sendAdserverRequest,
timeout: PREBID_TIMEOUT
});
});

function sendAdserverRequest() {
if (pbjs.adserverRequestSent) return;
pbjs.adserverRequestSent = true;
googletag.cmd.push(function() {
pbjs.que.push(function() {
pbjs.setTargetingForGPTAsync();
googletag.pubads().refresh();
});
});
}

setTimeout(function() {
sendAdserverRequest();
}, FAILSAFE_TIMEOUT);

</script>

<script>
googletag.cmd.push(function () {
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-gpt-ad-1460505748561-0').addService(googletag.pubads());

googletag.pubads().enableSingleRequest();
googletag.enableServices();
});
</script>
</head>

<body>
<h5>Div-1</h5>
<div id='div-gpt-ad-1460505748561-0'>
<script type='text/javascript'>
googletag.cmd.push(function() { googletag.display('div-gpt-ad-1460505748561-0'); });
</script>
</div>
</body>
</html>
3 changes: 2 additions & 1 deletion modules/.submodules.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dfpAdServerVideo"
],
"rtdModule": [
"browsiRtdProvider"
"browsiRtdProvider",
"jwplayerRtdProvider"
]
}
209 changes: 209 additions & 0 deletions modules/jwplayerRtdProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/**
* This module adds the jwplayer provider to the Real Time Data module (rtdModule)
* The {@link module:modules/realTimeData} module is required
* The module will allow Ad Bidders to obtain JW Player's Video Ad Targeting information
* The module will fetch segments for the media ids present in the prebid config when the module loads. If any bid
* requests are made while the segments are being fetched, they will be blocked until all requests complete, or the
* timeout expires.
* @module modules/jwplayerRtdProvider
* @requires module:modules/realTimeData
*/

import { submodule } from '../src/hook.js';
import { config } from '../src/config.js';
import { ajaxBuilder } from '../src/ajax.js';
import { logError } from '../src/utils.js';
import find from 'core-js-pure/features/array/find.js';

const SUBMODULE_NAME = 'jwplayer';
let requestCount = 0;
let requestTimeout = 150;
const segCache = {};
let resumeBidRequest;

/** @type {RtdSubmodule} */
export const jwplayerSubmodule = {
/**
* used to link submodule with realTimeData
* @type {string}
*/
name: SUBMODULE_NAME,
/**
* get data and send back to realTimeData module
* @function
* @param {adUnit[]} adUnits
* @param {function} onDone
*/
getData: getSegments,
init
};

config.getConfig('realTimeData', ({realTimeData}) => {
const providers = realTimeData.dataProviders;
const jwplayerProvider = providers && find(providers, pr => pr.name && pr.name.toLowerCase() === SUBMODULE_NAME);
const params = jwplayerProvider && jwplayerProvider.params;
if (!params) {
return;
}
const rtdModuleTimeout = params.auctionDelay || params.timeout;
requestTimeout = rtdModuleTimeout === undefined ? requestTimeout : Math.max(rtdModuleTimeout - 1, 0);
fetchTargetingInformation(params);
});

submodule('realTimeData', jwplayerSubmodule);

function init(config, gdpr, usp) {
return true;
}

export function fetchTargetingInformation(jwTargeting) {
const mediaIDs = jwTargeting.mediaIDs;
if (!mediaIDs) {
return;
}
mediaIDs.forEach(mediaID => {
fetchTargetingForMediaId(mediaID);
});
}

export function fetchTargetingForMediaId(mediaId) {
const ajax = ajaxBuilder(requestTimeout);
requestCount++;
ajax(`https://cdn.jwplayer.com/v2/media/${mediaId}`, {
success: function (response) {
try {
const data = JSON.parse(response);
if (!data) {
throw ('Empty response');
}

const playlist = data.playlist;
if (!playlist || !playlist.length) {
throw ('Empty playlist');
}

const jwpseg = playlist[0].jwpseg;
if (jwpseg) {
segCache[mediaId] = jwpseg;
}
} catch (err) {
logError(err);
}
onRequestCompleted();
},
error: function () {
logError('failed to retrieve targeting information');
onRequestCompleted();
}
});
}

function onRequestCompleted() {
requestCount--;
if (requestCount > 0) {
return;
}

if (resumeBidRequest) {
resumeBidRequest();
resumeBidRequest = null;
}
}

function getSegments(adUnits, onDone) {
executeAfterPrefetch(() => {
const realTimeData = adUnits.reduce((data, adUnit) => {
const code = adUnit.code;
const vat = code && getTargetingForBid(adUnit);
if (!vat) {
return data;
}

const { segments, mediaID } = vat;
const jwTargeting = {};
if (segments && segments.length) {
jwTargeting.segments = segments;
}

if (mediaID) {
const id = 'jw_' + mediaID;
jwTargeting.content = {
id
}
}

data[code] = {
jwTargeting
};
return data;
}, {});
onDone(realTimeData);
});
}

function executeAfterPrefetch(callback) {
if (requestCount > 0) {
resumeBidRequest = callback;
} else {
callback();
}
}

/**
* Retrieves the targeting information pertaining to a bid request.
* @param bidRequest {object} - the bid which is passed to a prebid adapter for use in `buildRequests`. It must contain
* a jwTargeting property.
* @returns targetingInformation {object} nullable - contains the media ID as well as the jwpseg targeting segments
* found for the given bidRequest information
*/
export function getTargetingForBid(bidRequest) {
const jwTargeting = bidRequest.jwTargeting;
if (!jwTargeting) {
return null;
}
const playerID = jwTargeting.playerID;
let mediaID = jwTargeting.mediaID;
let segments = segCache[mediaID];
if (segments) {
return {
segments,
mediaID
};
}

const player = getPlayer(playerID);
if (!player) {
return null;
}

const item = mediaID ? find(player.getPlaylist(), item => item.mediaid === mediaID) : player.getPlaylistItem();
if (!item) {
return null;
}

mediaID = mediaID || item.mediaid;
segments = item.jwpseg;
if (segments && mediaID) {
segCache[mediaID] = segments;
}

return {
segments,
mediaID
};
}

function getPlayer(playerID) {
const jwplayer = window.jwplayer;
if (!jwplayer) {
logError('jwplayer.js was not found on page');
return;
}

const player = jwplayer(playerID);
if (!player || !player.getPlaylist) {
logError('player ID did not match any players');
return;
}
return player;
}
Loading

0 comments on commit 494015f

Please sign in to comment.