AWS for M&E Blog
Playing your ads-featured content with AWS Elemental MediaTailor and VideoJS
In the previous installments of this blog series, we showed how to monetize your content with a custom workflow with FFmpeg on Amazon Elastic Container Service at Fargate and with Amazon Rekognition.
In this post, we show you how to display and interact with an HLS stream in a simple webpage similar to this.
Prerequisites
To get the most out of this post, you need:
- A basic understanding of HTML and Javascript
- An AWS account to deploy your player on an Amazon Simple Storage Service (S3) hosted website.
Overview
AWS Elemental MediaTailor campaigns provide you a URL to an HLS manifest for your video content, featured with server-side inserted ads. This URL can be used by an HLS compatible player to display your ads-featured content.
In this blog post, you will
- learn about the VMAP specifications that AWS Elemental MediaTailor uses to insert ads
- create a player in a webpage able to display ads-featured content
- fetch the position of the ad breaks from the VMAP file
- generate clickable elements in the UI to skip to the ads position, so you can verify the ad insertion
Anatomy of a VMAP File
AWS Elemental MediaTailor uses VMAP files to include ads from your Ads Decision Server in your media content.
Here’s an example VMAP file that describes a simple ads inventory.
<?xml version="1.0" encoding="UTF-8"?>
<vmap:VMAP xmlns:vmap="http://www.iab.net/videosuite/vmap"version="1.0">
<vmap:AdBreak timeOffset="00:05:04.125"breakType="linear" breakId="1">
<vmap:AdSource id="ad-source-1"followRedirects="true">
<vmap:AdTagURI templateType="vast3">
<![CDATA[ ${ADS_URL} ]]>
</vmap:AdTagURI>
</vmap:AdSource>
</vmap:AdBreak>
</vmap:VMAP>
The vmap:VMAP
tag is the root of each VMAP document and represents the playlist of ads available for the media content. It may contain zero or more vmap:AdBreak
tags, which represent a single ad break, but may include more than one ad.
The vmap:AdBreak
tag lets you specify when an ad break should appear with the timeOffset
parameter. In this example, we represent timeOffset
in SMPTE format (hh:mm:ss.mmm
), but other format options are available, such as percentage, start, or end position.
The breakType
parameter can assume a combination of the following values, ordered by display preference:
- linear – for video ads inserted in the media content
- nonlinear – used to describe non-linear ads, overlays for example
- display – used to describe companion ads outside of the player
breakId
is an optional string parameter that can be used as unique identifier for the ad break.
Each vmap:AdBreak
may contain up to one vmap:AdSource
element. It provides the player with either an inline ad response or a reference to an ad response. In this example, we use the vmap:AdTagURI
to provide a URI that references an ad response from another source, your Ads Decision Server for example. The URI must be contained within a CDATA block.
The VMAP specification offers many more configuration options for your ads inventory, but for the scope of this post we only consider the elements discussed so far. You can learn more about VMAP here.
Building the Player Web Page
In this section, we build a basic HTML page to display your content. We also download a VMAP file to gather the position of the ad-breaks and create clickable elements to seek to their position, so you can verify where the ads have been inserted.
Basic HTML
Begin by creating a basic HTML page and include VideoJS, an HTML5 web video player that supports HTML5 video and modern streaming formats. Create a file called index.html
and paste the following code.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>AWS Elemental MediaTailor</title>
<link href="//vjs.zencdn.net/7.3.0/video-js.min.css"rel="stylesheet">
<script src="//vjs.zencdn.net/7.3.0/video.min.js"></script>
</head>
<body>
<h1>My Ads Featured Video</h1>
</body>
</html>
The link
tag includes the CSS styles required for VideoJS, while the script
tag imports the necessary scripts.
Add the Player
Next, add and configure VideoJS player to the page with the tag <video>
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>AWS Elemental MediaTailor</title>
<link href="//vjs.zencdn.net/7.3.0/video-js.min.css"
rel="stylesheet">
<script src="//vjs.zencdn.net/7.3.0/video.min.js"></script>
</head>
<body>
<h1>My Ads Featured Video</h1>
<video
id="my_video" class="video-js"
controls preload="auto"
width="960" height="400"
withCredentials="true"
>
<source src="${STREAM_URL}" >
</video>
<ul id="adsUl" />
</body>
</html>
The property id
is a handler we use to control the player programmatically via Javascript, while class
is set to “video-js” to style the element with the default VideoJS appearance. The controls
toggle enables visual controls on the player. height
and width
set the respective display properties of the player. preload
indicates what should be downloaded and displayed before the media is ready to be interacted with. Finally, the property withCredentials
set to true
enables storing and passing cookies to the server where manifests and segments are hosted on.
You can find more info on how to personalize your player experience here.
The source
tag specifies what content the player should reproduce. The URL of the content can be specified with the property src
. For example, try to play this stream by replacing ${STREAM_URL}
with
We also add a placeholder for the list of ad-breaks, the <ul />
tag. We populate this list programmatically when the webpage loads. The end user can click on each item to seek to the position of the ad. The positions of the ads are loaded from the VMAP file. You can download a sample VMAP file here.
Add functionality
Now it’s time to add functionality to the page with a script that will
- download and parse the VMAP file
- extract the SMPTE timestamps of the ad breaks in the
vmap:AdBreak
tags - convert the ads timestamps to seconds
- for each ad, create a list element that when clicked skips to the position of the ad
Add the following snippet between the <ul />
tag and </body>
.
<script>
// 1. VMAP File URL
const vmapUrl = '${VMAP_URL}';
// 2. [hours, minutes, seconds] expressed in seconds
const smpteConversion = [3600, 60, 1];
// 3. get hold of the player
const player = videojs('my_video');
// 4. get handle of the UL element
const adsUl = document.querySelector('#adsUl');
// 5. utility functions
const map = fn => list => list.map(fn);
const dot = x => y => x.reduce((acc, next, i) => acc + next * y[i], 0);
const int10 = x => parseInt(x, 10);
// 6. workflow steps
const getVMAP = response => response.text();
const parseVMAP = text => new DOMParser().parseFromString(text, 'text/xml');
const getAdBreaks = xmlDoc => xmlDoc.getElementsByTagName('vmap:AdBreak');
const getTimeOffsets = adBreak => adBreak.getAttribute('timeOffset');
const splitSMPTEString = timeOffset => timeOffset.split(':');
const SMPTEToSec = dot(smpteConversion);
const minus5Sec = x => x > 5 ? x-5 : 0;
// 7. create <li> for a given ad-break timestamp
const createAdLi = ts => {
const li = document.createElement('li');
li.setAttribute('data-ts', ts)
li.appendChild(document.createTextNode(`ad-break at ${ts} sec`));
li.addEventListener('click', liCallback);
return li;
};
// 8. callback invoked when an <li> is clicked on
const liCallback = ({target}) => {
const ts = parseInt(target.getAttribute('data-ts'),10);
player.currentTime(ts);
player.play();
};
// 9. append to <ul>
const append = li => adsUl.appendChild(li);
// 10. handles errors and returns an empty list
const errorHandler = error => console.error(`error with VMAP file ${error}`) || [];
// 11. the whole workflow
fetch(vmapUrl)
.then(getVMAP)
.then(parseVMAP)
.then(getAdBreaks)
.then(Array.from)
.then(map(getTimeOffsets))
.then(map(splitSMPTEString))
.then(map(map(int10)))
.then(map(SMPTEToSec))
.then(map(minus5Sec))
.then(map(createAdLi))
.then(map(append))
.catch(errorHandler);
</script>
In step 1, we define the URL of the VMAP file: update the placeholder ${VMAP_URL}
with your VMAP file or the one provided in the previous section.
Alternatively, you can use the following URL:
https://dev-automads-solution-ads-bucket.s3-eu-west-1.amazonaws.com/episode3/ad_breaks.vmap
In step 2, we define an array with the values used to convert SMPTE timestamps to seconds.
With steps 3 and 4, we create a reference to the elements of the page we want to interact with programmatically: player
is a reference to the VideoJS player element, and adsUl
references the list placeholder.
In step 5, we define three utility functions
map
– wraps the built-inArray.prototype.map
method, so we can use it conveniently in a promise chain.dot
– defines the scalar product of two arrays. We use this function convert SMPTE timestamps to seconds.int10
– wraps the built-in functionparseInt
to use it conveniently with map
For step 6, we define the core steps of the workflow
getVMAP
– extracts the plain text from an HTTP Response Object BodyparseVMAP
– parses the text as an XML DocumentgetAdBreaks
– gets all thevmap:AdBreaks
tags in anHTMLCollection
getTimeOffsets
– gets all of the timeOffsets values from theAdBreaks
tags- composed,
splitSMPTEString
andSMPTEToSec
convert a timestamp expressed in SMPTE format into seconds minus5Sec
decreases by 5 seconds a timestamp expressed in seconds.
In step 7, 8 and 9, we define the following functions to manipulate the UI
createAdLi
is a function that populatesadsUl
with clickable<li>
elements.- The function accepts two parameters
ts
and text.ts
is the timestamp in seconds of an ad.text
is the text we want to display in the<li>
element. liCallback
is the callback function each<li>
element invokes when clicked on. The goal of this function is to seek to the provided position in the video and start the playback. This function is bound to each element by thecreateAdLi
function.append
– adds the newly created<li>
elements to the list
For step 10, we define a simple error handler function that logs to the console in case of error.
Finally, step 11, is where we put together the workflow in a promise chain. We begin with fetching the VMAP file from the remote server, we parse it into an XML document. Then we extract the timestamps, convert them to integers and generate clickable elements on the UI.
Test the player locally
You are now ready to test the player locally. To do so, start up a simple HTTP server on your local machine. The following are instructions for starting it up with Node.js and Python3.
Node.js
If you have Node.js installed, run the following command in a terminal in your working directory.
$ npm install -g http-server
$ http-server .
You should see a response similar to the following:
Open up a browser window and route to http://localhost:8080 to test your player.
Python3
If you have Python3 installed, open up a terminal in the current working directory and run the following:
$ python3 -m http.server
You should see a response similar to the following:
Open up a browser window and route to http://localhost:8000 to test your player.
Deploy the Player to an Amazon S3 Website
You can use Amazon S3 to host your webpage. Make sure that your AWS CLI is configured with a profile that can manipulate S3 resources. Then open up a terminal in the current folder and issue the following commands:
DEPLOY_BUCKET=<insert-a-bucket-name-here>
AWS_REGION=<insert-a-region-here>
aws s3 mb s3://$DEPLOY_BUCKET
aws s3 website s3://$DEPLOY_BUCKET --index-document index.html --error-document index.html
aws s3 cp index.html s3://$DEPLOY_BUCKET --acl public-read
echo "website endpoint: http://$DEPLOY_BUCKET.s3-website-$AWS_REGION.amazonaws.com"
Open up a browser window and route to the URL displayed in your terminal: you can now share this URL and start streaming and monetizing your content.
Next Steps
Now that you have a quick-start for displaying and interacting with an HLS stream, consider the following applications:
- provide markers on the player track to tell the user when ads are inserted
- provide a countdown to the beginning and the end of the inserted ads
- gather analytics about the performance of an ad and its placement
- disable fast-forward for the duration of the ad or provide a button to skip the ad after a fixed amount of time
- include UI elements at the end of an ad to encourage user interaction or a call to action
Conclusion
In this blog post we built, tested, and deployed a simple webpage using VideoJS to display and interact with an HLS stream served by AWS Elemental MediaTailor.
Make sure you check out the other posts in this series:
- Monetize media content with AWS Elemental MediaTailor and Computer Vision
- Inserting Ad Breaks into Video Content using Amazon Rekognition, AWS Elemental MediaConvert and AWS Elemental MediaTailor
Watch the AWS Media Blog for the next post in this series. Let us know via the comment box if you have any questions, feedback or suggestions.