-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
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 e4d5cf1
ssets up targeting module
88ec998
implements getTargeting
7fbdbee
implements getPlayer
041c537
blocks bids until all targeting requests complete
daaa0fd
makes getTarget more resilient
c04cb3a
enables mdule hook
ce8b2bc
replaces triple dot notation
2a72c04
Revert "replaces triple dot notation"
35c870a
Revert "Revert "replaces triple dot notation""
3fb4cee
checks current item only if mediaid is missing
c94d05a
adds unit tests
e954470
completes test cases
aef48b9
stores segments for current item
38f2c50
renames jwp targeting
3956cc6
refactors fetch tests
708a34c
refactors get targeting tests
724b03d
refactors blocking tests
a34a5df
renames module
9331726
cleans changes made to app nexus
3606651
removes setup and player utilities
41290a1
renames onFetchCompletion
e2d55fb
renames onFetchCOmpletion in unti tests
1ee3fdd
throws instead of early return
e343642
reduces timeout and introduces override
d3571c8
targeting timeout supersedes
e3a3d88
renames feed fetch timeout
570b4e7
adds inline doc
eca4eb6
uses find util
8cd1a13
adds jwplayer rtd provider
34c66eb
implements targeting retrieval
1d626f5
ensures provider is found
e393980
implements init
5d6556a
jwTargeting is object
2c66117
commits test file
e091b3d
adds file extension
cc5ccfc
adds tests
8bf45fd
fixes test for proper structure
ba21385
uses default clock mock
cf51266
ends reqs before rtd module timeout
c31ca79
removes obsolete export
36d47c0
request counts updates in aggregate
18a0bf3
cleans server mock after each test
c77f1f7
deletes jwplayer targeting
eab3a6b
includes content id
b60dcf9
adds test for missing segment
369ee70
getSegments is nullable
e770799
replaces condition with guard
97a6a9b
updates doc
b62958f
adds md file
5bcb160
adds example page
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.