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

[AD-963] - Update JW Player RTD Provider for compliance with RTD Module Phase 3 #5844

Merged
merged 11 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion integrationExamples/gpt/jwplayerRtdProvider_example.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
var adUnits = [{
code: 'div-gpt-ad-1460505748561-0',
jwTargeting: {
// Note: the following Ids are placeholders and should be replaced with your Ids.
playerID: '123',
mediaID: 'abc'
},
Expand All @@ -32,7 +33,6 @@

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

</script>

<script>
Expand All @@ -45,9 +45,12 @@
pbjs.que.push(function() {
pbjs.setConfig({
realTimeData: {
auctionDelay: 5000,
dataProviders: [{
name: "jwplayer",
waitForIt: true,
params: {
// Note: the following media Ids are placeholders and should be replaced with your Ids.
mediaIDs: ['abc', 'def', 'ghi', 'jkl']
}
}]
Expand Down
12 changes: 6 additions & 6 deletions modules/gridBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,17 +344,17 @@ function buildNewRequest(validBidRequests, bidderRequest) {
if (!userId) {
userId = bid.userId;
}
const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, realTimeData} = bid;
const {params: {uid, keywords}, mediaTypes, bidId, adUnitCode, jwTargeting} = bid;
bidsMap[bidId] = bid;
if (!pageKeywords && !utils.isEmpty(keywords)) {
pageKeywords = utils.transformBidderParamKeywords(keywords);
}
if (realTimeData && realTimeData.jwTargeting) {
if (!jwpseg && realTimeData.jwTargeting.segments) {
jwpseg = realTimeData.jwTargeting.segments;
if (jwTargeting) {
if (!jwpseg && jwTargeting.segments) {
jwpseg = jwTargeting.segments;
}
if (!content && realTimeData.jwTargeting.content) {
content = realTimeData.jwTargeting.content;
if (!content && jwTargeting.content) {
content = jwTargeting.content;
}
}
let impObj = {
Expand Down
217 changes: 133 additions & 84 deletions modules/jwplayerRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ 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';
import { getGlobal } from '../src/prebidGlobal.js';

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

/** @type {RtdSubmodule} */
Expand All @@ -29,12 +30,12 @@ export const jwplayerSubmodule = {
*/
name: SUBMODULE_NAME,
/**
* get data and send back to realTimeData module
* add targeting data to bids and signal completion to realTimeData module
* @function
* @param {adUnit[]} adUnits
* @param {Obj} bidReqConfig
* @param {function} onDone
*/
getData: getSegments,
getBidRequestData: enrichBidRequest,
init
};

Expand All @@ -45,14 +46,12 @@ config.getConfig('realTimeData', ({realTimeData}) => {
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) {
function init(provider, userConsent) {
return true;
}

Expand All @@ -67,40 +66,57 @@ export function fetchTargetingInformation(jwTargeting) {
}

export function fetchTargetingForMediaId(mediaId) {
const ajax = ajaxBuilder(requestTimeout);
requestCount++;
const ajax = ajaxBuilder();
// TODO: Avoid checking undefined vs null by setting a callback to pendingRequests.
pendingRequests[mediaId] = null;
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();
const segment = parseSegment(response);
cacheSegments(segment, mediaId);
onRequestCompleted(mediaId, !!segment);
},
error: function () {
logError('failed to retrieve targeting information');
onRequestCompleted();
onRequestCompleted(mediaId, false);
}
});
}

function onRequestCompleted() {
requestCount--;
if (requestCount > 0) {
function parseSegment(response) {
let segment;
try {
const data = JSON.parse(response);
if (!data) {
throw ('Empty response');
}

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

segment = playlist[0].jwpseg;
} catch (err) {
logError(err);
}
return segment;
}

function cacheSegments(jwpseg, mediaId) {
if (jwpseg && mediaId) {
segCache[mediaId] = jwpseg;
}
}

function onRequestCompleted(mediaID, success) {
const callback = pendingRequests[mediaID];
if (callback) {
callback(success ? getVatFromCache(mediaID) : { mediaID });
activeRequestCount--;
}
delete pendingRequests[mediaID];

if (activeRequestCount > 0) {
return;
}

Expand All @@ -110,67 +126,76 @@ function onRequestCompleted() {
}
}

function getSegments(adUnits, onDone) {
executeAfterPrefetch(() => {
const realTimeData = adUnits.reduce((data, adUnit) => {
const code = adUnit.code;
const vat = code && getTargetingForBid(adUnit);
function enrichBidRequest(bidReqConfig, onDone) {
activeRequestCount = 0;
const adUnits = bidReqConfig.adUnits || getGlobal().adUnits;
enrichAdUnits(adUnits);
if (activeRequestCount <= 0) {
onDone();
} else {
resumeBidRequest = onDone;
}
}

/**
* get targeting data and write to bids
* @function
* @param {adUnit[]} adUnits
* @param {function} onDone
*/
export function enrichAdUnits(adUnits) {
adUnits.forEach(adUnit => {
const onVatResponse = function (vat) {
if (!vat) {
return data;
return;
}
const targeting = formatTargetingResponse(vat);
addTargetingToBids(adUnit.bids, targeting);
};

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

if (mediaID) {
const id = 'jw_' + mediaID;
jwTargeting.content = {
id
}
}
function loadVat(params, onCompletion) {
if (!params) {
return;
}

data[code] = {
jwTargeting
};
return data;
}, {});
onDone(realTimeData);
});
const { playerID, mediaID } = params;
if (pendingRequests[mediaID] !== undefined) {
loadVatForPendingRequest(playerID, mediaID, onCompletion);
return;
}

const vat = getVatFromCache(mediaID) || getVatFromPlayer(playerID, mediaID) || { mediaID };
onCompletion(vat);
}

function executeAfterPrefetch(callback) {
if (requestCount > 0) {
resumeBidRequest = callback;
function loadVatForPendingRequest(playerID, mediaID, callback) {
const vat = getVatFromPlayer(playerID, mediaID);
if (vat) {
callback(vat);
} else {
callback();
activeRequestCount++;
pendingRequests[mediaID] = 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) {
export function getVatFromCache(mediaID) {
const segments = segCache[mediaID];

if (!segments) {
return null;
}
const playerID = jwTargeting.playerID;
let mediaID = jwTargeting.mediaID;
let segments = segCache[mediaID];
if (segments) {
return {
segments,
mediaID
};
}

return {
segments,
mediaID
};
}

export function getVatFromPlayer(playerID, mediaID) {
const player = getPlayer(playerID);
if (!player) {
return null;
Expand All @@ -182,17 +207,41 @@ export function getTargetingForBid(bidRequest) {
}

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

return {
segments,
mediaID
};
}

export function formatTargetingResponse(vat) {
const { segments, mediaID } = vat;
const targeting = {};
if (segments && segments.length) {
targeting.segments = segments;
}

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

function addTargetingToBids(bids, targeting) {
if (!bids || !targeting) {
return;
}

bids.forEach(bid => {
bid.jwTargeting = targeting;
});
}

function getPlayer(playerID) {
const jwplayer = window.jwplayer;
if (!jwplayer) {
Expand Down
Loading