Adaptive Streaming with AWS

Introduction

Let’s build a system for scalable adaptive streaming of video and audio. I hope this guide saves you the time that I invested in figuring this out. To help you understand how this works, and so this guide isn’t just a set of steps you blindly follow let’s talk about how adaptive streaming works.

When you go to a page with streaming video or audio, a player loads. This player accesses a playlist that is located on a CDN or content distribution network. CDN’s are networks of fast servers spread all over the world. When you access a file on a CDN it serves you the content from a server that is both physically close to you and has low latency, or speed in responding. CDN’s make your content fast to everyone all over the world! This is really important for streaming, and the consistency of a CDN will make sure that your viewer has a stable quality experience. We don’t have control over how fast your viewer’s internet connection is, so that’s why we use adaptive bitrate streaming. Adaptive streaming adjusts the quality of the video and even audio at specified segments. If you’ve ever been watching a streaming video and it starts out a little blurry and then gets sharp, you hit a segment where it switched to a higher quality. In order to have multiple qualities of video and even audio, we have to transcode your full resolution video file into many files of varying qualities. The audio is a completely separate file. The video player you loaded accesses a playlist on your CDN that lists all of these video and audio files and automatically switches at segment markers.

Here’s what you need:

– [An Amazon Web Services (AWS) Account](https://aws.amazon.com/)
– [Video.js](https://github.com/videojs/video.js/releases)
– [videojs-contrib-hls.min.js](https://github.com/videojs/videojs-contrib-hls/releases/)
– [dash.all.min.js](https://github.com/Dash-Industry-Forum/dash.js/releases)
– [videojs-dash.min.js](https://github.com/videojs/videojs-contrib-dash/releases/)
– [Mobile_Detect.php](https://packagist.org/packages/mobiledetect/mobiledetectlib)

Here’s everything ziped up as of the date of this post (April 25, 2018). You might want to put together your own copy with the latest releases.

Transcoding & File Storage

To transcode or convert your video file into all of the formats we need, we’ll be using Amazon Web Services (AWS) Elastic Transcoder to create multiple files for HLS (iOS Video Format) and DASH. The transcoder will also create the playlist that will list all of the video files and audio files. We’re also going to use Amazon Web Services (AWS) CloudFront for our Content Delivery Network (CDN) to deliver a scalable stream to a web player on your site. The web player I’ve chosen is Video.js for the HTML5 player as well as javascript support libraries to make Video.js work with HLS and DASH. Finally, the site will use Mobile_Detect to identify if the device is iOS so we can deliver an HLS stream or fallback to DASH for normal web browsers. I know there is some argument on falling back to HLS or falling back to DASH. I choose fallback to DASH worked out best in my testing.

Set up three AWS S3 buckets to hold files
In AWS go to their S3 service and create 3 buckets. You can name them whatever you want. I recommend appending -in, -out, and -thumbs to each for easy organization. Since I have many customers who will be using this service, I organize my buckets with clientname-in, clientname-out, clientname-thumbs.

Upload your video
Upload your full quality video to your -in bucket. Then in your -out bucket create 2 folders named: dash and hls. If you’re going to have multiple videos, you may want to consider organizing your folders with projectname-dash and projectname-hls.

Transcoding DASH with AWS Elastic Transcoder

Under AWS Services navigate to the Elastic Transcoder.

Let’s create a new pipeline. Think of the pipeline as a queue that connects your -in and -out buckets. If you are doing this for multiple customers, you will end up creating a pipeline for each customer. Consider using clientname as the name of your pipeline.

DASH Transcoding Input

Now that we have a pipeline let’s create a Job that will transcode our video for MPEG-DASH. If we created our output folders and uploaded our video correctly we should see them as options when we select the empty fields here. So start your job as so:

DASH Transcoding Output

We’re now ready specify how many different video quality segments we want for our streaming. This is where we define the bitrate for each file that allows our video to switch to different bitrate qualities based on a user’s internet connection speed. First, select the DASH preset for the highest quality. A good segment duration is 10 seconds, although Apple recommends 6 seconds. The segment duration is the amount of time that will elapse before the video or audio shifts to a better or lesser quality as it adapts to the devices available bandwidth. For the output key, I like to use the naming convention you see in the screenshot. Repeat the process for the different DASH quality presets as well as for the audio preset. I transcode at each of the available DASH presets. You may also have the job create thumbnails here and output them to our -thumbs bucket. I’m going to ignore the thumbnail creations step and upload my own thumbs manually later.

DASH Transcoding Playlist

Once we have all our pre-sets we need to add them to a playlist (Media Playlist Description or .mpd file) which we are going to name `index`. This file is the file that we point our player at that has the full list of all the media files. I add my video files from the highest bitrate to the lowest and then the sound files from highest bitrate to the lowest.

Now run your job. Once it’s complete you should see the output of your dash files and index.mpd file in the dash folder in your -out S3 bucket.

Transcoding HLS with AWS Elastic Transcoder

We’re basically reproducing the same steps we followed in creating dash transcoding, but this time for HLS:

  1. HLS Transcoding Input
  2. HLS Transcoding Output
  3. HLS Transcoding Playlist

S3 Files Permission

You will need to go to your -out bucket in AWS S3. Once there, goo into the HLS and DASH folders and set the files you just created through transcoding and set them to have Public access. Don’t worry, When you set up the CloudFont Content Distribution Network’s access to the files, you will restrict direct access to the files.

In your S3 buckets, highlight your -out bucket and under its properties select the *Add CORS Configuration* button and add the xml below to it. This allows your player to access the files through the CDN with cross-origin permissions. Without it, the player will throw an error.

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Setup the CloudFront (CDN)

CloudFront is Amazon’s Content Distribution Network (CDN) for delivering content to the web player. Content Distribution Networks or CDN’s are servers scattered all over the world that have copies of your files and deliver from a server that is near (or has low latency to) your viewer. The server that is closest to your viewer is called an Edge Server and it makes sure your video is delivered quickly and consistently! Under AWS Services navigate to CloudFront and Create a new distribution. Then select “Get Started” for a “Web Distribution”.

For Origin Domain Name select your -out folder to be the origin. What’s next is important. For Cache Behaviors be sure to select allowed HTTP Methods for GET, HEAD, OPTIONS and then check the OPTIONS box for Cached HTTP Methods. Then Whitelist Forward Headers as seen in the screenshot below. For everything else, leave the default settings and create the distribution. It will take a little while (20-30 min) for CloudFront to process the distribution, although it takes up to 24 hours for complete distribution to all edge servers. Once the initial process is finished you can access your files from your CloudFront domain name which will look something like: `https://d106r1lp4z33yd.cloudfront.net`

Example link to DASH manifest:
index.mpd

Example link to HLS Manifest:
index.m3u8

To test everything works you can try loading the AWS CloudFront URL to your manifest [Here]

Client Implementation


<?php 
require_once "libs/Mobile_Detect.php"; 
	$detect = new Mobile_Detect;
	if( $detect->isiOS() ){
	$isIOS = TRUE;
}
?>

<html>
<head>
    <link href="/video/js/video-js/video-js.css" rel="stylesheet">
    <!-- If you'd like to support IE8 -->
    <script src="/video/js/video-js/ie8/videojs-ie8.min.js"></script>
</head>
<body>
	test
    <video id="story_video" class="video-js vjs-default-skin vjs-16-9 vjs-big-play-centered" controls width="500" height="281" poster="">
        <p class="vjs-no-js">
            To view this video please enable JavaScript, and consider upgrading to a web browser that
            <a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a>
        </p>
    </video>
     
</body>
 
<script src="/video/js/video-js/video.js"></script>
<script src="/video/js/video-js-hls/videojs-contrib-hls.min.js"></script>
<script src="/video/js/video-js-dash/dash.all.min.js"></script>
<script src="/video/js/video-js-dash/videojs-dash.min.js"></script>
 
<script>
//Optional Callback to disable debugging logging
var dashjsCallback = function(player, mediaPlayer) {
    // Log MediaPlayer messages through video.js
    if (videojs && videojs.log) {
        mediaPlayer.getDebug().setLogToBrowserConsole(false);
    }
};
 
//Add the Callback hook from above
videojs.Html5DashJS.hook('beforeinitialize', dashjsCallback);
 
var videoElement = document.getElementsByTagName("video")[0];
 
//check the browser headers to determine if coming from iOS or not
//and store result in isIOS var.
var isIOS = "<?php echo $isIOS ?>";
if(isIOS) {
      manifest = "hls/index.m3u8";
      mimeType = "application/x-mpegURL";
  } else {
      manifest = "dash/index.mpd";
      mimeType = "application/dash+xml";
 }
   
var myPlayer = videojs(videoElement, { "controls": true, "autoplay": true, "preload": "auto", "fluid" : "true" });
myPlayer.src({
    src: 'https://00000.cloudfront.net/' + manifest,
    type: mimeType
});
 
myPlayer.play();
</script>
</html>

If you want to add a poster graphic for your video, upload a graphic to your *-thumbs* S3 bucket and link like so:

<video ... poster="link_here">

You may also consider setting up a second CloudFront distribution to point to the -thumbs bucket. Make sure to make the thumb image permission public.

Special thanks for getting me started down the right path: https://gist.github.com/askilondz/d618fb2e39f481fd19bf3f8b0476a3b6

© 2018 Jeffrey Milton