-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
JW Player Real Time Data Provider (#5537)
* [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
1 parent
67d184a
commit 494015f
Showing
5 changed files
with
804 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
"dfpAdServerVideo" | ||
], | ||
"rtdModule": [ | ||
"browsiRtdProvider" | ||
"browsiRtdProvider", | ||
"jwplayerRtdProvider" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.