Category Archives: Uncategorized

Streaming HLS with Nginx’s RTMP Module

In this post I’m going to show you how to use Nginx’s RTMP module to stream a live video (over RTMP) from your desktop and then play it back with HLS. I’ll also cover how to encrypt the video.

RTMP was initially a proprietary protocol by Adobe for streaming video and audio between a Flash player and a server but is still widely used today for broadcasting video despite the demise of Flash.

Continue reading

Generating HLS Playlists with Bento4

In this post we’re going to look at how to use Bento4 to generate HLS playlists from MP4 files. In previous posts I’ve used ffmpeg to encode the videos and generate the playlist. However, sometimes it’s better to separate the encoding from the packaging. For example, you may want to support both DASH and HLS from the same encodings.

Bento4 also has support for MPEG DASH and many other formats, but in this post I’ll focus specifically on using the command line tools mp4hls and mp42hls to create HLS playlists. You can download the latest version from the website. Once you’ve downloaded it, make sure the bin directory is on your PATH. I used version 1.5.1.0 in the examples.

If you are familiar with Apple’s HLS tools, mp4hls and mp42hls can be used as a replacement for variantplaylistcreator and mediafilesegmenter respectively. Or put another way, if you want to create a master playlist for more than one video, use mp4hls. If you only want to create a playlist for a single video, use mp42hls instead.

For the examples, I’m going to assume there are 3 videos available: sample-1080p.mp4, sample-720p.mp4 and sample-360p.mp4.

Let’s begin.

Continue reading

HLS and Fragmented MP4

At WWDC 2016, Apple announced support for fragmented MP4 (fMP4) as an alternative to MPEG-TS, which prior to their announcement was the only supported format.

So why use fragmented MP4 files? Well, according to Apple’s video encoding requirements in their HLS Authoring Specification, if you want to use HEVC/H.265, you have to use it (1.5). Fragmented MP4 files are also compatible with MPEG-DASH – an alternative to HLS – so you can use the same files; only the manifest file (playlist) is different. This means less encoding and less storage requirements, which should reduce costs.

In this post, I’ll demonstrate how to generate fMP4 files using ffmpeg. You’ll need a fairly recent version of ffmpeg that supports fMP4 – I used version 4.1.1 to create the files in this post.

Continue reading

Playing HLS Video in the Browser – Part 2

I often get email from people asking if there any examples in the book about how to play HLS video in the browser and my answer is always the same: no. In this short post, I’ll try to explain why not with an example of how to play encrypted HLS video in the browser, which is also something I get asked about a lot.

The main reason there aren’t any specific examples in the book that show how to play HLS video in the browser is because most of the time, all you need to do is tell the player where the playlist is and hit play; it takes care of the rest. Plus, there are a lot of players out there that support HLS video.

Most of the effort involved in playing HLS video goes into encoding the videos, segmenting them, preparing the playlist, and so on, not configuring the front-end. And this is what the book focuses on.

Let me highlight this with an example. I’m going to use Video.js, but it should work in just about every other HLS capable player. To play HLS video using Video.js you’ll need to use the Video.js HLS source handler. I’ve already written something about how to encrypt video for HLS so I won’t go over the details here – I did say this was going to be a short post.

Here’s some HTML that plays the video:

<head>
<link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet">
</head>
<body>
<video id="example-video" class="video-js vjs-default-skin" controls="controls" width="640" height="360">
   <source src="https://s3.eu-west-1.amazonaws.com/net.hlsbook.hlsvideo/prog_index.m3u8" type="application/x-mpegURL" />
</video>
<script src="https://vjs.zencdn.net/7.4.1/video.min.js"></script>
<script>
var player = videojs('example-video');
</script>
</body>

As you can see (highlighted) all you have to do to play the video is just tell the player where the playlist is. Even though this particular video is encrypted, there is nothing more you have to do on the front-end.

In this instance, the playlist and the encrypted video are stored in an S3 bucket. (If you are interested, I wrote a post recently about how to serve HLS video from an S3 bucket that goes into more detail.)

And finally, here’s an example:



How to Serve HLS Video from an S3 Bucket

This post will describe how to configure an S3 bucket to serve video using HLS.

I’ve also written some example code that creates a bucket and configures it as outlined in this post.

Amazon S3 is a storage solution that allows you to store large amounts of data for relatively little cost, perfect for something like video files, which tend to be quite large. Files (or objects as they are often referred to) are accessed over HTTP, which makes it a great solution for storing (and serving) your HLS videos.

You’ll need an Amazon Web Services (AWS) account to use S3. If you haven’t got one, you can sign up here. Sign in to your account and navigate to the S3 console. Create a bucket. Next, upload your video segments and playlist etc. to the bucket.

Make sure the content type of the playlist and the video segments (.ts) is set to “application/x-mpegURL” and “video/MP2T” respectively. You can do this by selecting the Properties tab for each file and then clicking on Metadata.

Before you can start serving your videos, you need to grant read access to the files in the bucket; files are private by default. Select the Permissions tab and set the bucket policy to the following:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetBucketObjects",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<your bucket name>/*"
        }
    ]
}

Most browsers don’t support HLS natively, so HLS video playback is typically achieved by using some sort of Javascript-based video player like Video.js, for example. To play an HLS video all you typically need to do is configure the player with the playlist URL and it takes care of the rest. If you serve your videos from the same domain as the video player, there are no additional steps for you to do. However, for the purposes of this post I’m going to assume the video player references the playlist stored in the S3 bucket.

This presents a bit of a problem. If you request a resource (a file) from a different domain, you need permission to do so; it violates the same-origin policy that browsers enforce. Cross-origin resource sharing (CORS) is a mechanism that allows user agents to request permission to access resources that reside on a different server. In this instance, you need to grant permission to the player to allow it to access to the video(s) in the S3 bucket.

Thankfully, Amazon S3 supports CORS so you can selectively allow cross-origin access to the files in your S3 bucket. To enable CORS on your bucket, select the Permissons tab then click on CORS configuration. By default, the configuration file allows access from anywhere. If I wanted to restrict access to the videos to this domain, I would modify the CORS configuration to look like this:

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

Once you have granted public access to files in the S3 bucket and configured CORS appropriately, you should now be able to serve your HLS videos from an S3 bucket.

Restricting Access

Setting the allowed origin(s) in the CORS policy will prevent somebody from embedding your video on their website if they use a player like VideoJS, but it won’t prevent it if the browser supports HLS natively because in this instance, the same-origin policy doesn’t apply. One thing you can do is check for the presence of the Referer header and if the request hasn’t come from your domain, you block it. You can do this by modifying the bucket policy. Here’s the policy for my bucket:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetBucketObjects",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::net.hlsbook/*"
        },
        {
            "Sid": "Explicit deny to ensure requests are allowed only from a specific domain.",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::net.hlsbook/*",
            "Condition": {
                "StringNotLike": {
                    "aws:Referer": [
                        "http://hlsbook.net/*"
                    ]
                }
            }
        }
    ]
}

This policy will check the referrer header and if it doesn’t match or it’s missing, access to the requested resource will be blocked. (You could include the check in the allowed action but I prefer to use an explicit deny because it overrides any allows.) However, bear in mind that the check can be circumvented because the referrer header can be spoofed.

Unfortunately, Chrome Mobile doesn’t include the referrer header so the above policy will block access to your videos on that particular browser. Fortunately there is a workaround (or “hack” if you prefer).

The solution is to tell the player to use the Media Source Extensions (if the platform supports it) for HLS playback instead of playing it natively. You can do this with VideoJS by setting the overrideNative property to true. If you view the source for this page, you’ll see this:

videojs.options.hls.overrideNative = true;
videojs.options.html5.nativeAudioTracks = false;
videojs.options.html5.nativeTextTracks = false;

var player = videojs('example-video');
player.src('https://s3.eu-west-2.amazonaws.com/net.hlsbook/prog_index.m3u8');

The referrer header will now be included in the requests so access to the video will no longer be blocked on Chrome Mobile.

One More Thing

HLS playlists support the EXT-X-BYTERANGE tag. This indicates to the player that each video segment is part of a (larger) resource. (There’s more about this in the book if you are interested.) Here’s an example from a playlist that uses the tag:

#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:10.00000,
#EXT-X-BYTERANGE:808400@0
main.ts
#EXTINF:10.00000,
#EXT-X-BYTERANGE:848068@808400
main.ts
#EXTINF:10.00000,
#EXT-X-BYTERANGE:811784@1656468
main.ts

With the current configuration, any requests for the video file (main.ts) will result in a 403 Access Denied response from S3. To grant access to the file, you need to allow the use of the HTTP Range header. Modify the CORS configuration file so it looks like this:

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

Now your video player will be able to make range requests for parts of the video file.

Example

Finally, here’s an example of playing an HLS video from an S3 bucket:



Conclusion

Amazon S3 is a cost effective solution for storing videos as video files can take up a lot of space. Because access to files in S3 is over HTTP and with support for CORS, it also makes it a viable solution for using it to serve your HLS videos to your viewers.

S3 also has other features that I haven’t mentioned here, such as versioning and archiving, that could be useful for managing your video assets. Check out the S3 documentation for more information.