Skip to content

Commit

Permalink
JW Player RTD Module : populate content url, title and description (p…
Browse files Browse the repository at this point in the history
…rebid#11178)

* caches items

* updates fetch tests

* verifies against entire object

* updates docs

* appens to ext

* renames tests

* parses source from playlist

* Update modules/jwplayerRtdProvider.js

Co-authored-by: Demetrio Girardi <demetrio.girardi@proton.me>

* Update modules/jwplayerRtdProvider.js

Co-authored-by: Demetrio Girardi <demetrio.girardi@proton.me>

* adds indentation

* allow override configuration

* adds tests

* uses labels

* populates tests

* updates tests

* adds config docs

* updates demo

* uses ix

* adds todos

---------

Co-authored-by: Demetrio Girardi <demetrio.girardi@proton.me>
  • Loading branch information
karimMourra and dgirardi authored Apr 11, 2024
1 parent 7dbcb47 commit 458036a
Show file tree
Hide file tree
Showing 4 changed files with 816 additions and 99 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>

/* Paste JW Player script tag here */

<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',
code: 'video-ad-unit',
ortb2Imp: {
ext: {
data: {
Expand All @@ -23,16 +25,21 @@
}
},
mediaTypes: {
banner: {
sizes: [[300, 250], [300,600]],
video: {
sizes: [[320, 460]],
mimes : ["video/mp4"],
minduration : 3,
maxduration: 30,
protocols : [1,2]
}
},
// Replace this object to test a new Adapter!
bids: [{
bidder: 'appnexus',
params: {
placementId: 13144370
}
bidder: 'ix',
params: {
siteId: '300',
video: {}
}
}]
}];

Expand All @@ -41,11 +48,6 @@
</script>

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

pbjs.que.push(function() {
pbjs.setConfig({
Expand All @@ -63,44 +65,28 @@
});
pbjs.addAdUnits(adUnits);
pbjs.requestBids({
bidsBackHandler: sendAdserverRequest,
bidsBackHandler: renderHighestBid,
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>
function renderHighestBid() {
const highestBids = pbjs.getHighestCpmBids('video-ad-unit');
const highestBid = highestBids && highestBids.length && highestBids[0];
if (!highestBid) {
return;
}

<script>
googletag.cmd.push(function () {
googletag.defineSlot('/19968336/header-bid-tag-0', [[300, 250], [300, 600]], 'div-gpt-ad-1460505748561-0').addService(googletag.pubads());
if (highestBid.vastXml) {
jwplayer().loadAdXml(highestBid.vastXml);
} else if (highestBid.vastUrl) {
jwplayer().loadAdTag(highestBid.vastUrl);
}
}

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>
119 changes: 93 additions & 26 deletions modules/jwplayerRtdProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,20 @@ import {getGlobal} from '../src/prebidGlobal.js';

const SUBMODULE_NAME = 'jwplayer';
const JWPLAYER_DOMAIN = SUBMODULE_NAME + '.com';
const segCache = {};
const ENRICH_ALWAYS = 'always';
const ENRICH_WHEN_EMPTY = 'whenEmpty';
const ENRICH_NEVER = 'never';
const overrideValidationRegex = /^(always|never|whenEmpty)$/;
const playlistItemCache = {};
const pendingRequests = {};
let activeRequestCount = 0;
let resumeBidRequest;
// defaults to 'always' for backwards compatibility
// TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY
let overrideContentId = ENRICH_ALWAYS;
let overrideContentUrl = ENRICH_WHEN_EMPTY;
let overrideContentTitle = ENRICH_WHEN_EMPTY;
let overrideContentDescription = ENRICH_WHEN_EMPTY;

/** @type {RtdSubmodule} */
export const jwplayerSubmodule = {
Expand All @@ -38,7 +48,7 @@ export const jwplayerSubmodule = {
/**
* add targeting data to bids and signal completion to realTimeData module
* @function
* @param {Obj} bidReqConfig
* @param {object} bidReqConfig
* @param {function} onDone
*/
getBidRequestData: enrichBidRequest,
Expand All @@ -53,6 +63,7 @@ config.getConfig('realTimeData', ({realTimeData}) => {
return;
}
fetchTargetingInformation(params);
setOverrides(params);
});

submodule('realTimeData', jwplayerSubmodule);
Expand All @@ -71,15 +82,32 @@ export function fetchTargetingInformation(jwTargeting) {
});
}

export function setOverrides(params) {
// For backwards compatibility, default to always unless overridden by Publisher.
// TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY
overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_ALWAYS);
overrideContentUrl = sanitizeOverrideParam(params.overrideContentUrl, ENRICH_WHEN_EMPTY);
overrideContentTitle = sanitizeOverrideParam(params.overrideContentTitle, ENRICH_WHEN_EMPTY);
overrideContentDescription = sanitizeOverrideParam(params.overrideContentDescription, ENRICH_WHEN_EMPTY);
}

function sanitizeOverrideParam(overrideParam, defaultValue) {
if (overrideValidationRegex.test(overrideParam)) {
return overrideParam;
}

return defaultValue;
}

export function fetchTargetingForMediaId(mediaId) {
const ajax = ajaxBuilder();
// TODO: Avoid checking undefined vs null by setting a callback to pendingRequests.
pendingRequests[mediaId] = null;
ajax(`https://cdn.${JWPLAYER_DOMAIN}/v2/media/${mediaId}`, {
success: function (response) {
const segment = parseSegment(response);
cacheSegments(segment, mediaId);
onRequestCompleted(mediaId, !!segment);
const item = parsePlaylistItem(response);
cachePlaylistItem(item, mediaId);
onRequestCompleted(mediaId, !!item);
},
error: function () {
logError('failed to retrieve targeting information');
Expand All @@ -88,8 +116,8 @@ export function fetchTargetingForMediaId(mediaId) {
});
}

function parseSegment(response) {
let segment;
function parsePlaylistItem(response) {
let item;
try {
const data = JSON.parse(response);
if (!data) {
Expand All @@ -101,16 +129,16 @@ function parseSegment(response) {
throw ('Empty playlist');
}

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

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

Expand Down Expand Up @@ -167,7 +195,7 @@ export function enrichAdUnits(adUnits, ortb2Fragments = {}) {
const contentData = getContentData(mediaId, contentSegments);
const targeting = formatTargetingResponse(vat);
enrichBids(adUnit.bids, targeting, contentId, contentData);
addOrtbSiteContent(ortb2Fragments.global, contentId, contentData);
addOrtbSiteContent(ortb2Fragments.global, contentId, contentData, vat.title, vat.description, vat.mediaUrl);
};
loadVat(jwTargeting, onVatResponse);
});
Expand Down Expand Up @@ -217,18 +245,27 @@ function loadVatForPendingRequest(playerDivId, mediaID, callback) {
}

export function getVatFromCache(mediaID) {
const segments = segCache[mediaID];
const item = playlistItemCache[mediaID];

if (!segments) {
if (!item) {
return null;
}

const mediaUrl = item.file ?? getFileFromSources(item);

return {
segments,
segments: item.jwpseg,
title: item.title,
description: item.description,
mediaUrl,
mediaID
};
}

function getFileFromSources(playlistItem) {
return playlistItem.sources?.find?.(source => !!source.file)?.file;
}

export function getVatFromPlayer(playerDivId, mediaID) {
const player = getPlayer(playerDivId);
if (!player) {
Expand All @@ -241,12 +278,18 @@ export function getVatFromPlayer(playerDivId, mediaID) {
}

mediaID = mediaID || item.mediaid;
const title = item.title;
const description = item.description;
const mediaUrl = item.file;
const segments = item.jwpseg;
cacheSegments(segments, mediaID)
cachePlaylistItem(item, mediaID)

return {
segments,
mediaID
mediaID,
title,
mediaUrl,
description
};
}

Expand Down Expand Up @@ -313,35 +356,59 @@ export function getContentData(mediaId, segments) {
return contentData;
}

export function addOrtbSiteContent(ortb2, contentId, contentData) {
if (!contentId && !contentData) {
return;
}

export function addOrtbSiteContent(ortb2, contentId, contentData, contentTitle, contentDescription, contentUrl) {
if (ortb2 == null) {
ortb2 = {};
}

let site = ortb2.site = ortb2.site || {};
let content = site.content = site.content || {};

if (contentId) {
if (shouldOverride(content.id, contentId, overrideContentId)) {
content.id = contentId;
}

const currentData = content.data = content.data || [];
if (shouldOverride(content.url, contentUrl, overrideContentUrl)) {
content.url = contentUrl;
}

if (shouldOverride(content.title, contentTitle, overrideContentTitle)) {
content.title = contentTitle;
}

if (shouldOverride(content.ext && content.ext.description, contentDescription, overrideContentDescription)) {
content.ext = content.ext || {};
content.ext.description = contentDescription;
}

const currentData = content.data || [];
// remove old jwplayer data
const data = currentData.filter(datum => datum.name !== JWPLAYER_DOMAIN);

if (contentData) {
data.push(contentData);
}

content.data = data;
if (data.length) {
content.data = data;
}

return ortb2;
}

function shouldOverride(currentValue, newValue, configValue) {
switch (configValue) {
case ENRICH_ALWAYS:
return !!newValue;
case ENRICH_NEVER:
return false;
case ENRICH_WHEN_EMPTY:
return !!newValue && currentValue === undefined;
default:
return false;
}
}

function enrichBids(bids, targeting, contentId, contentData) {
if (!bids) {
return;
Expand Down
Loading

0 comments on commit 458036a

Please sign in to comment.