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

JW Player Real Time Data Provider #5537

Merged
merged 51 commits into from
Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1b19f5f
[AD-469] Add player vendor.
vseventer Apr 7, 2020
e4d5cf1
ssets up targeting module
Jul 6, 2020
88ec998
implements getTargeting
Jul 6, 2020
7fbdbee
implements getPlayer
Jul 7, 2020
041c537
blocks bids until all targeting requests complete
Jul 7, 2020
daaa0fd
makes getTarget more resilient
Jul 8, 2020
c04cb3a
enables mdule hook
Jul 8, 2020
ce8b2bc
replaces triple dot notation
Jul 9, 2020
2a72c04
Revert "replaces triple dot notation"
Jul 9, 2020
35c870a
Revert "Revert "replaces triple dot notation""
Jul 9, 2020
3fb4cee
checks current item only if mediaid is missing
Jul 9, 2020
c94d05a
adds unit tests
Jul 10, 2020
e954470
completes test cases
Jul 10, 2020
aef48b9
stores segments for current item
Jul 10, 2020
38f2c50
renames jwp targeting
Jul 10, 2020
3956cc6
refactors fetch tests
Jul 10, 2020
708a34c
refactors get targeting tests
Jul 10, 2020
724b03d
refactors blocking tests
Jul 10, 2020
a34a5df
renames module
Jul 10, 2020
9331726
cleans changes made to app nexus
Jul 10, 2020
3606651
removes setup and player utilities
Jul 13, 2020
41290a1
renames onFetchCompletion
Jul 13, 2020
e2d55fb
renames onFetchCOmpletion in unti tests
Jul 13, 2020
1ee3fdd
throws instead of early return
Jul 13, 2020
e343642
reduces timeout and introduces override
Jul 13, 2020
d3571c8
targeting timeout supersedes
Jul 13, 2020
e3a3d88
renames feed fetch timeout
Jul 14, 2020
570b4e7
adds inline doc
Jul 15, 2020
eca4eb6
uses find util
Jul 15, 2020
8cd1a13
adds jwplayer rtd provider
Jul 15, 2020
34c66eb
implements targeting retrieval
Jul 16, 2020
1d626f5
ensures provider is found
Jul 20, 2020
e393980
implements init
Jul 21, 2020
5d6556a
jwTargeting is object
Jul 22, 2020
2c66117
commits test file
Jul 22, 2020
e091b3d
adds file extension
Jul 22, 2020
cc5ccfc
adds tests
Jul 22, 2020
8bf45fd
fixes test for proper structure
Jul 22, 2020
ba21385
uses default clock mock
Jul 22, 2020
cf51266
ends reqs before rtd module timeout
Jul 22, 2020
c31ca79
removes obsolete export
Jul 23, 2020
36d47c0
request counts updates in aggregate
Jul 23, 2020
18a0bf3
cleans server mock after each test
Jul 23, 2020
c77f1f7
deletes jwplayer targeting
Jul 23, 2020
eab3a6b
includes content id
Jul 23, 2020
b60dcf9
adds test for missing segment
Jul 23, 2020
369ee70
getSegments is nullable
Jul 23, 2020
e770799
replaces condition with guard
Jul 23, 2020
97a6a9b
updates doc
Jul 23, 2020
b62958f
adds md file
Jul 25, 2020
5bcb160
adds example page
Jul 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patmmccann this is the line where we register as an RTD submodule

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ty! how do you like this format?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean how do I like having the jwplayer module be a submodule of an existing module ? I think it's great for reusability and consistency across all rtd providers. Thank you for suggesting this approach.


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