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

Video ad #135

Merged
merged 7 commits into from
Sep 14, 2023
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
6 changes: 4 additions & 2 deletions services/dsp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,17 @@ app.get("/join-ad-interest-group.html", async (req: Request, res: Response) => {
})

app.get("/interest-group.json", async (req: Request, res: Response) => {
const { advertiser, id } = req.query
const { advertiser, id, adType } = req.query
if (advertiser === undefined || id === undefined) {
return res.sendStatus(400)
}

// FIXME: Render URL in interest group should be from DSP_HOST.
const ssp = new URL(`https://${SSP_HOST}:${EXTERNAL_PORT}/ads`)
ssp.searchParams.append("advertiser", advertiser as string)
ssp.searchParams.append("id", id as string)
const renderUrl = ssp.toString()
const videoCreative = new URL(`https://${DSP_HOST}:${EXTERNAL_PORT}/html/video-ad-creative.html`)
const renderUrl = 'video' === adType ? videoCreative : ssp.toString()

const owner = new URL(`https://${DSP_HOST}:${EXTERNAL_PORT}`)
const biddingLogicUrl = new URL(`https://${DSP_HOST}:${EXTERNAL_PORT}/js/bidding_logic.js`)
Expand Down
4 changes: 4 additions & 0 deletions services/dsp/src/public/dsp-tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
src.pathname = "join-ad-interest-group.html"
src.searchParams.append("advertiser", advertiser)
src.searchParams.append("id", id)
const currentUrl = new URL(location.href)
for (const searchParam of currentUrl.searchParams) {
src.searchParams.append(searchParam[0], searchParam[1])
}

const $iframe = document.createElement("iframe")
$iframe.width = 1
Expand Down
9 changes: 9 additions & 0 deletions services/dsp/src/public/html/video-ad-creative.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8" />
<script src="/js/video-ad-creative.js"></script>
</head>

</html>
31 changes: 14 additions & 17 deletions services/dsp/src/public/js/join-ad-interest-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,25 @@
*/

// Protected Audience API
async function getInterestGroup(advertiser, id) {
const url = new URL(location.origin)
url.pathname = "/interest-group.json"
url.searchParams.append("id", id)
url.searchParams.append("advertiser", advertiser)
const res = await fetch(url)
return res.json()
async function getInterestGroupFromServer() {
const currentUrl = new URL(location.href)
const interestGroupUrl = new URL(location.origin)
interestGroupUrl.pathname = "/interest-group.json"
for (const searchParam of currentUrl.searchParams) {
interestGroupUrl.searchParams.append(searchParam[0], searchParam[1])
}
const res = await fetch(interestGroupUrl)
if (res.ok) {
return res.json()
}
}

document.addEventListener("DOMContentLoaded", async (e) => {
if (navigator.joinAdInterestGroup === undefined) {
return console.log("Protected Audience API is not supported")
return console.log("[DEMO] Protected Audience API is not supported")
}

// Protected Audience API
const url = new URL(location.href)
const advertiser = url.searchParams.get("advertiser")
const id = url.searchParams.get("id")

const interestGroup = await getInterestGroup(advertiser, id)
console.log({ interestGroup })

const interestGroup = await getInterestGroupFromServer()
console.log(`[DEMO] ${{interestGroup}}`)
const kSecsPerDay = 3600 * 24 * 30
console.log(await navigator.joinAdInterestGroup(interestGroup, kSecsPerDay))

Expand Down
10 changes: 10 additions & 0 deletions services/dsp/src/public/js/video-ad-creative.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
(async () => {
const data = {
adVastUrl: 'https://pubads.g.doubleclick.net/gampad/ads?' +
'iu=/21775744923/external/single_ad_samples&sz=640x480&' +
'cust_params=sample_ct%3Dlinear&ciu_szs=300x250%2C728x90&' +
'gdfp_req=1&output=vast&unviewed_position_start=1&env=vp&' +
'impl=s&correlator=',
}
window.top.postMessage(JSON.stringify(data), '*')
})()
2 changes: 2 additions & 0 deletions services/news/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
**/*.js
!src/public/**/*.js
**/*.css
!src/public/css/video-ad-style.css
!**/*.tailwind.css
29 changes: 19 additions & 10 deletions services/news/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const { EXTERNAL_PORT, PORT, HOME_HOST, SSP_HOST, NEWS_TOKEN, NEWS_DETAIL } = pr

const app: Application = express()

const TITLE = NEWS_DETAIL
const LOREM =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

app.use((req, res, next) => {
res.setHeader("Origin-Trial", NEWS_TOKEN as string)
next()
Expand All @@ -29,20 +33,25 @@ app.set("view engine", "ejs")
app.set("views", "src/views")

app.get("/", async (req: Request, res: Response) => {
const title = NEWS_DETAIL
const lorem =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

const params = {
title,
lorem,
res.render("index", {
title: TITLE,
lorem: LOREM,
EXTERNAL_PORT,
HOME_HOST,
NEWS_TOKEN,
SSP_HOST
}
res.render("index", params)
SSP_HOST,
})
})
app.get("/video-ad", async (req: Request, res: Response) => {
res.render("video-ad", {
title: TITLE,
lorem: LOREM,
EXTERNAL_PORT,
HOME_HOST,
NEWS_TOKEN,
SSP_HOST,
});
});

app.listen(PORT, async () => {
console.log(`Listening on port ${PORT}`)
Expand Down
38 changes: 38 additions & 0 deletions services/news/src/public/css/video-ad-style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

#mainContainer {
position: relative;
width: 640px;
height: 360px;
}

#content, #adContainer {
position: absolute;
top: 0px;
left: 0px;
width: 640px;
height: 360px;
}

#contentElement {
width: 640px;
height: 360px;
overflow: hidden;
}

#playButton {
margin-top:10px;
vertical-align: top;
width: 350px;
height: 60px;
padding: 0;
font-size: 22px;
color: white;
text-align: center;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
background: #2c3e50;
border: 0;
border-bottom: 2px solid #22303f;
cursor: pointer;
-webkit-box-shadow: inset 0 -2px #22303f;
box-shadow: inset 0 -2px #22303f;
}
209 changes: 209 additions & 0 deletions services/news/src/public/js/video-ad-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// You may study, modify, and use this example for any purpose.
// Note that this example is provided "as is", WITHOUT WARRANTY
// of any kind either expressed or implied.

let adsManager;
let adsLoader;
let adDisplayContainer;
let intervalTimer;
let isAdPlaying;
let isContentFinished;
let playButton;
let videoContent;

/**
* Initializes IMA setup.
*/
function init() {
videoContent = document.getElementById('contentElement');
playButton = document.getElementById('playButton');
playButton.addEventListener('click', playAds);
console.log('[DEMO] Organic video initialized.')
}

/**
* Sets up IMA ad display container, ads loader, and makes an ad request.
*/
function setUpIMA(adTagUrl) {
if (!adTagUrl) {
return console.log('[DEMO] No VAST XML URL provided.')
}
// Create the ad display container.
createAdDisplayContainer();
// Create ads loader.
adsLoader = new google.ima.AdsLoader(adDisplayContainer);
// Listen and respond to ads loaded and error events.
adsLoader.addEventListener(
google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
onAdsManagerLoaded, false);
adsLoader.addEventListener(
google.ima.AdErrorEvent.Type.AD_ERROR, onAdError, false);

// An event listener to tell the SDK that our content video
// is completed so the SDK can play any post-roll ads.
const contentEndedListener = function() {
// An ad might have been playing in the content element, in which case the
// content has not actually ended.
if (isAdPlaying) return;
isContentFinished = true;
adsLoader.contentComplete();
};
videoContent.onended = contentEndedListener;

// Request video ads.
const adsRequest = new google.ima.AdsRequest();
adsRequest.adTagUrl = adTagUrl;

// Specify the linear and nonlinear slot sizes. This helps the SDK to
// select the correct creative if multiple are returned.
adsRequest.linearAdSlotWidth = 640;
adsRequest.linearAdSlotHeight = 400;

adsRequest.nonLinearAdSlotWidth = 640;
adsRequest.nonLinearAdSlotHeight = 150;

adsLoader.requestAds(adsRequest);
}

/**
* Sets the 'adContainer' div as the IMA ad display container.
*/
function createAdDisplayContainer() {
// We assume the adContainer is the DOM id of the element that will house
// the ads.
adDisplayContainer = new google.ima.AdDisplayContainer(
document.getElementById('adContainer'), videoContent);
}

/**
* Loads the video content and initializes IMA ad playback.
*/
function playAds() {
// Initialize the container. Must be done through a user action on mobile
// devices.
videoContent.load();
if (adDisplayContainer) {
adDisplayContainer.initialize();
}

try {
// Initialize the ads manager. Ad rules playlist will start at this time.
adsManager.init(640, 360, google.ima.ViewMode.NORMAL);
// Call play to start showing the ad. Single video and overlay ads will
// start at this time; the call will be ignored for ad rules.
adsManager.start();
} catch (adError) {
// An error may be thrown if there was a problem with the VAST response.
videoContent.play();
}
}

/**
* Handles the ad manager loading and sets ad event listeners.
* @param {!google.ima.AdsManagerLoadedEvent} adsManagerLoadedEvent
*/
function onAdsManagerLoaded(adsManagerLoadedEvent) {
// Get the ads manager.
const adsRenderingSettings = new google.ima.AdsRenderingSettings();
adsRenderingSettings.restoreCustomPlaybackStateOnAdBreakComplete = true;
// videoContent should be set to the content video element.
adsManager =
adsManagerLoadedEvent.getAdsManager(videoContent, adsRenderingSettings);

// Add listeners to the required events.
adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdError);
adsManager.addEventListener(
google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, onContentPauseRequested);
adsManager.addEventListener(
google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED,
onContentResumeRequested);
adsManager.addEventListener(
google.ima.AdEvent.Type.ALL_ADS_COMPLETED, onAdEvent);

// Listen to any additional events, if necessary.
adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, onAdEvent);
adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, onAdEvent);
adsManager.addEventListener(google.ima.AdEvent.Type.COMPLETE, onAdEvent);
console.log('Ads Manager loaded.')
}

/**
* Handles actions taken in response to ad events.
* @param {!google.ima.AdEvent} adEvent
*/
function onAdEvent(adEvent) {
// Retrieve the ad from the event. Some events (for example,
// ALL_ADS_COMPLETED) don't have ad object associated.
const ad = adEvent.getAd();
switch (adEvent.type) {
case google.ima.AdEvent.Type.LOADED:
// This is the first event sent for an ad - it is possible to
// determine whether the ad is a video ad or an overlay.
if (!ad.isLinear()) {
// Position AdDisplayContainer correctly for overlay.
// Use ad.width and ad.height.
videoContent.play();
}
break;
case google.ima.AdEvent.Type.STARTED:
// This event indicates the ad has started - the video player
// can adjust the UI, for example display a pause button and
// remaining time.
if (ad.isLinear()) {
// For a linear ad, a timer can be started to poll for
// the remaining time.
intervalTimer = setInterval(
function() {
// Example: const remainingTime = adsManager.getRemainingTime();
},
300); // every 300ms
}
break;
case google.ima.AdEvent.Type.COMPLETE:
// This event indicates the ad has finished - the video player
// can perform appropriate UI actions, such as removing the timer for
// remaining time detection.
if (ad.isLinear()) {
clearInterval(intervalTimer);
}
break;
}
}

/**
* Handles ad errors.
* @param {!google.ima.AdErrorEvent} adErrorEvent
*/
function onAdError(adErrorEvent) {
// Handle the error logging.
console.log(adErrorEvent.getError());
adsManager.destroy();
}

/**
* Pauses video content and sets up ad UI.
*/
function onContentPauseRequested() {
isAdPlaying = true;
videoContent.pause();
// This function is where you should setup UI for showing ads (for example,
// display ad timer countdown, disable seeking and more.)
// setupUIForAds();
}

/**
* Resumes video content and removes ad UI.
*/
function onContentResumeRequested() {
isAdPlaying = false;
if (!isContentFinished) {
videoContent.play();
}
// This function is where you should ensure that your UI is ready
// to play content. It is the responsibility of the Publisher to
// implement this function when necessary.
// setupUIForContent();
}

init();
Loading