Introduction
Some years ago, back in 2008, I posted my take on on premium video pay per view (with flashvideo). Now, after some years, the environment has changed. First, the outcome of mobile devices has obligued developers to think in a multiplatform way. Also Flash has been dropped from mobile devices and so RTMP has become outdated. Also, the new HTML5 standard has modified the video concept.
In my case, I had to find another way of serving secure video within drupal. After a long process of research I found that the current streaming state of the art was named HLS. There is also a name that sounds as a possible future, MPEG-DASH, but it is still very inmature. The difference between both, HLS and MPEG-DASH. is that HLS is a proprietary protocol (from Apple) and MPEG-DASH is being defined as an open international standard. It will probably become the standard after HLS epoch. But right now, HLS is the way to go.
Both HLS and MPEG-DASH are multibitrate protocols, designed for the multiplatform world we are now in. The underlying idea is to have one source encoded into multiple different bitrates (that is, on original file encoded into several different files, one for each bitrate), and segmented in such a way that a player downloads segments for playin them, and the player can pick one segment or another depending on the bitrate happening at a given moment.
This post tries to explain the whole process from video transcoding up to website secure playback in case someone is interested. I mostly write it for my own interest, so that if I need to repeat it, I can come back here and consult the process. The explanation will be centered in HLS for CloudFront.
Why CloudFront
The only CDN I could find that offers cheap secure streaming options for HLS is ClodFront. There are several CDNs that offer token authentication, but this is a method that only works for one file. As an HLS file is segmented into multiple files, the token looses its value for the rest of the files. In CloudFront they have implemented what they called Signed Cookies, that is, a cookie storing a code that will be used for all segments. If someone reads this post and knows of other cheap CDN offering alternative methods, please, reply with a comment about the method used in this CDN.
Security via https
I have tried like mad adding https into the equation, but I coudln't manage to be sucessfull, so right now this document is going to explain how to do it all without SSL. If someone can help and add some coment on how to manage to add SSL in between please reply!
Transconding
The first step is to have the video transcoded from its original source into an HLS format. The transcoding is going to include one several security layers: encryption and key obfuscation.
The first thing to be chosen is the bitrates to be served. For this, one should consult the apple page on Recommended Encoding Settings for HTTP Live Streaming Media and pick preferred options.
Once the options are chosen, the video source must be transcoded into HLS. I use ffmpeg for this.
But before, let's talk about HLS structure. An HLS video is composed by a set of files:
video.m3u8 - it's the manifest of the video, containing references to each bitrate file
video-bitrateN.m3u8 - it's a manifest of all the segments that compose the video in a concrete bitrate N
video-bitrateN-M.ts - segment M of file N for bitrate N
When a video is transcoded with ffmpeg, video.m3u8 is not being created, only the different video-bitrateN.m3u8 and video-bitrateN-M.ts files. So, before or after transcoding, video.m3u8 must be created. It's a plain text file. For the whole task I have a batch file:
#!/bin/bash
VIDSOURCE="$1"
name=${VIDSOURCE%.*}
name=${name// /-}
echo """$name"""
#generate encoding key
keyFile="$name.key"
#save encoding key to file
openssl rand 16 > $keyFile
#storing keyfile reference in a file
#if we are successfully adding SSL, then http should be changed into https
echo 'http://mysite.mydomain.com/video-key.php\n$keyFile' > key_info
#Parameters corresponding to https://developer.apple.com/library/ios/technotes/tn2224/_index.html#//apple_ref/doc/uid/DTS40009745-CH1-SETTINGSFILES
RESOLUTION3="640x360"
RESOLUTION4="640x360"
RESOLUTION5="960x540"
RESOLUTION6="1280x720"
RESOLUTION7="1280x720"
RESOLUTION8="1920x1080"
FRAMERATE3="29.97"
FRAMERATE4="29.97"
FRAMERATE5="29.97"
FRAMERATE6="29.97"
FRAMERATE7="29.97"
FRAMERATE8="29.97"
TOTALBITRATE3="664k"
TOTALBITRATE4="1296k"
TOTALBITRATE5="3596k"
TOTALBITRATE6="5128k"
TOTALBITRATE7="6628k"
TOTALBITRATE8="8628k"
VIDEOBITRATE3="600k"
VIDEOBITRATE4="1200k"
VIDEOBITRATE5="3500k"
VIDEOBITRATE6="5000k"
VIDEOBITRATE7="6500k"
VIDEOBITRATE8="8500k"
AUDIOBITRATE3="64k"
AUDIOBITRATE4="96k"
AUDIOBITRATE5="96k"
AUDIOBITRATE6="128k"
AUDIOBITRATE7="128k"
AUDIOBITRATE8="128k"
AUDIOSAMPLERATE="48k"
KEYFRAME3="90"
KEYFRAME4="90"
KEYFRAME5="90"
KEYFRAME6="90"
KEYFRAME7="90"
KEYFRAME8="90"
VPROFILE3="baseline"
VPROFILE4="baseline"
VPROFILE5="main"
VPROFILE6="main"
VPROFILE7="main"
VPROFILE8="high"
LEVEL3="3.0"
LEVEL4="3.1"
LEVEL5="3.1"
LEVEL6="3.1"
LEVEL7="3.1"
LEVEL8="4.0"
SEGMENTSIZE="9"
PRESET="ultrafast"
# the order is relevant, as players will start playing the first stream usually before checking bandwidth
# you should choose which bitrates you want to offer, comment out the rest
echo '#EXTM3U' > $name.m3u8
echo '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH='$TOTALBITRATE8',CODECS="avc1.640028,mp4a.40.2"' >> $name.m3u8
echo $name'-stream-high.m3u8' >> $name.m3u8
echo '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH='$TOTALBITRATE7',CODECS="avc1.4d001f,mp4a.40.2"' >> $name.m3u8
echo $name'-stream-wifi4.m3u8' >> $name.m3u8
echo '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH='$TOTALBITRATE6',CODECS="avc1.4d001f,mp4a.40.2"' >> $name.m3u8
echo $name'-stream-wifi3.m3u8' >> $name.m3u8
echo '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH='$TOTALBITRATE5',CODECS="avc1.4d001f,mp4a.40.2"' >> $name.m3u8
echo $name'-stream-med.m3u8' >> $name.m3u8
echo '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH='$TOTALBITRATE4',CODECS="avc1.42001f,mp4a.40.2"' >> $name.m3u8
echo $name'-stream-wifi1.m3u8' >> $name.m3u8
echo '#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH='$TOTALBITRATE3',CODECS="avc1.42001e,avc1.66.30,mp4a.40.2"' >> $name.m3u8
echo $name'-stream-low.m3u8' >> $name.m3u8
AUDIO_OPTS3="-c:a aac -strict -2 -b:a $AUDIOBITRATE3 -ac 2"
AUDIO_OPTS4="-c:a aac -strict -2 -b:a $AUDIOBITRATE4 -ac 2"
AUDIO_OPTS5="-c:a aac -strict -2 -b:a $AUDIOBITRATE5 -ac 2"
AUDIO_OPTS6="-c:a aac -strict -2 -b:a $AUDIOBITRATE6 -ac 2"
AUDIO_OPTS7="-c:a aac -strict -2 -b:a $AUDIOBITRATE7 -ac 2"
AUDIO_OPTS8="-c:a aac -strict -2 -b:a $AUDIOBITRATE8 -ac 2"
VIDEO_OPTS3="-c:v libx264 -x264opts keyint=90:min-keyint=90 -s $RESOLUTION3 -b:v $VIDEOBITRATE3 -force_key_frames "expr:gte\(t,n_forced*$KEYFRAME3\)" -profile:v $VPROFILE3 -level $LEVEL3"
VIDEO_OPTS4="-c:v libx264 -x264opts keyint=90:min-keyint=90 -s $RESOLUTION4 -b:v $VIDEOBITRATE4 -force_key_frames "expr:gte\(t,n_forced*$KEYFRAME4\)" -profile:v $VPROFILE4 -level $LEVEL4"
VIDEO_OPTS5="-c:v libx264 -x264opts keyint=90:min-keyint=90 -s $RESOLUTION5 -b:v $VIDEOBITRATE5 -force_key_frames "expr:gte\(t,n_forced*$KEYFRAME5\)" -profile:v $VPROFILE5 -level $LEVEL5"
VIDEO_OPTS6="-c:v libx264 -x264opts keyint=90:min-keyint=90 -s $RESOLUTION6 -b:v $VIDEOBITRATE6 -force_key_frames "expr:gte\(t,n_forced*$KEYFRAME6\)" -profile:v $VPROFILE6 -level $LEVEL6"
VIDEO_OPTS7="-c:v libx264 -x264opts keyint=90:min-keyint=90 -s $RESOLUTION7 -b:v $VIDEOBITRATE7 -force_key_frames "expr:gte\(t,n_forced*$KEYFRAME7\)" -profile:v $VPROFILE7 -level $LEVEL7"
VIDEO_OPTS8="-c:v libx264 -x264opts keyint=90:min-keyint=90 -s $RESOLUTION8 -b:v $VIDEOBITRATE8 -force_key_frames "expr:gte\(t,n_forced*$KEYFRAME8\)" -profile:v $VPROFILE8 -level $LEVEL8"
#if you want no segments and just one big file, choose one line or the other
#OUTPUT_HLS="-hls_time 10 -hls_list_size 0 -hls_wrap 0 -hls_allow_cache 0 -start_number 1 -segment_format mpegts -copyts -hls_flags single_file" #one big file
OUTPUT_HLS="-hls_time 10 -hls_key_info_file key_info -hls_list_size 0 -hls_wrap 0 -hls_allow_cache 0 -start_number 1 -segment_format mpegts -copyts" #multiple segments
ffmpeg -re -i "$VIDSOURCE" -y -threads 4 -preset $PRESET \
$AUDIO_OPTS3 $VIDEO_OPTS3 $OUTPUT_HLS $name-stream-low.m3u8 \
$AUDIO_OPTS5 $VIDEO_OPTS5 $OUTPUT_HLS $name-stream-med.m3u8 \
$AUDIO_OPTS8 $VIDEO_OPTS8 $OUTPUT_HLS $name-stream-high.m3u8 \
$AUDIO_OPTS4 $VIDEO_OPTS4 $OUTPUT_HLS $name-stream-wifi1.m3u8 \
$AUDIO_OPTS6 $VIDEO_OPTS6 $OUTPUT_HLS $name-stream-wifi3.m3u8 \
$AUDIO_OPTS7 $VIDEO_OPTS7 $OUTPUT_HLS $name-stream-wifi4.m3u8 \
rm key_info
About the batch file above, as the title explains, this approach is for secure streaming. So, a series of security layers will be implemented along the guide. In this bash file the first two security layers take place. Segments are encoded with AES-128. And the key is not stored openly in the m3u8 manifest, but it obfuscated and it must be obtained from a URL in the server: http://mysite.mydomain.com/video-key.php. This way, if someone happens to successfully download a segment, the key will still be needed from the server. It's two security layers: first, segments encrypted, second, decrypting key must be obtained from the server. There will be still more security layers when we finish.
There could be another layer of security, having different keys for different segments. I don't know how this is done and I don't know how much security it would add in comparison to bandwidth overhead.
Storing files in the server
In order to be able to stream the videos, files need to be accessible from the outside. But access to the the files should be restricted.
A directory must be created. I chose to put it into drupal files in a folder named private. Inside the private directory this .htaccess must be used:
SetHandler None
SetEnvIf User-Agent ^Amazon Cloudfront$ cdn
#This could also be a cloudfront.net host
SetEnvIf Host ^cdn.mydomain.com$ cdn
Order Deny,Allow
Deny from all
Allow from your-server-IP #maybe not needed
Allow from env=cdn
# Set CORS headers so CloudFront will forward them
# with AJAX withCredentials=false (cookies NOT sent)
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, PUT, OPTIONS, PATCH, DELETE"
Header always set Access-Control-Allow-Headers "X-Accept-Charset,X-Accept,Content-Type"
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L,E=HTTP_ORIGIN:%{HTTP:ORIGIN}]]
# with AJAX withCredentials=true (cookies sent, SSL allowed...)
SetEnvIfNoCase ORIGIN (.*) ORIGIN=$1
Header always set Access-Control-Allow-Methods "POST, GET, PUT, OPTIONS, PATCH, DELETE"
Header always set Access-Control-Allow-Origin "%{ORIGIN}e" env=ORIGIN
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Allow-Headers "X-Accept-Charset,X-Accept,Content-Type"
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L,E=HTTP_ORIGIN:%{HTTP:ORIGIN}]
# restrict referencing the key reader to this domain
# and the domain from where flashls (flowplayerhls.swf) is loaded
RewriteEngine on
RewriteBase /files/private/
RewriteCond %{HTTP_REFERER} !(mysite\.mydomain\.net|releases\.flowplayer\.org|d1bakp2kr5uhp8\.cloudfront\.net|cdn\.mydomain\.net)
RewriteRule ^drive-key\.php$ - [F]
The .htaccess has several aspects that will be commented later. Right now, the first part with
SetHandler None
SetEnvIf User-Agent ^Amazon Cloudfront$ cdn
#This could also be a cloudfront.net host
SetEnvIf Host ^cdn.mydomain.net$ cdn
Order Deny,Allow
Deny from all
Allow from your-server-IP #maybe not needed
Allow from env=cdn
is the one limiting access to files from selected domains. And the last part
# restrict referencing the key reader to this domain
RewriteEngine on
RewriteBase /files/private/
RewriteCond %{HTTP_REFERER} !(mysubdomain\.mydomain\.com|releases\.flowplayer\.org|cdn\.mydomain\.com)
RewriteRule ^key\.php$ - [F]
limits access to the key to some domains. The section in the middle will be explained later.
All m3u8 and ts files should be moved into this private directory, one folder for each video, because every video will be generating a lot of segments and it's easier to control storing each video in one subdirectory.
Also, all key files generated by ffmpeg should be stored in a directory out of reach of apache. In my case the directory is calle hls-keys and it is in the root of the account, out of the public_html directory.
PHP files in the server related to key obtention
In order to get a key for decrypting videos for playback, the m3u8 manifests refer to http://mydomain.com/video-key.php. This php file must be placed in drupal root and is one of two files that provide another layer of protection.
File video-key.php:
<?php
#activate drupal sessions
define('DRUPAL_ROOT', $_SERVER['DOCUMENT_ROOT']);
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
if (isset($_SESSION['secret']) && $_SESSION['secret'] === '{mysecret}' &&
isset($_SESSION['allow']) && $_SESSION['allow'] === 'on') {
header('Content-Type: binary/octet-stream');
header('Pragma: no-cache');
readfile('../hls-keys/'.$_SESSION["video"].'.key');
} else {
header('HTTP/1.0 403 Forbidden');
}
$_SESSION['allow'] = 'off';
session_write_close();
video-key.php is a file that, by itself, wouldn't do anything. It depends on some session variables being set, and if someone tries to call this file directly, they will get a 403 Forbidden reply. The engine will work when everything is finished.
CloudFront distribution setup
In my case, I am only using CloudFront as a pull CDN. I am not using S3. I am sure this can easily be done with S3, but I prefer not to pay extra for storing my videos.
In CloudFront a distribution must be created with CNAME set to cdn.mydomain.com
An origin must be created with:
Origin Domain Name: mysite.mydomain.com
Origin Path: /files/private
A behavious must be created with:
Origin: set to the origin created before
Forwarded headers: Whitelist
1 header(s) whitelisted: Origin
Restrict Viewer Access (Use Signed URLs or Signed Cookies): yes
Trusted signers: self
With this configuration we are telling CloudFront that this distribution can only be used if the browser has a cookie with a key access for our account.
Next a private/public key pair for CloudFront must be created in account->security credentials. In the IAM Management Console go to CloudFront Key Pairs, request Create New Key Pair and download private and public keys. In your server a directory must be created in the root, out of the reach of PHP. In my case, the directory is caled hls-keys and it is in the domain root, outside of public-html. Move into this directory the private and public keys from AWS IAM and also put there the video.key files that were generated by the transcoding batch file for all your videos. As it is supposed that several videos will be uploaded, every video has a different .key file, all of them should be moved here. Script video-key.php is going to look for them here.
Video template
In the .tpl.php template where we want the video to display, the engine will be complete.
For playing the videos I use FlowPlayer. There is a plugin for having pure HTML5 HLS in desktop platforms named flowplayer-hls.js that is based in dailymotion's hls.js. In my case, I use moneysuite module for converting the site in a subscription site, and this is the reason for the if in the code, but it can be removed if you use another solution for another security layer. The code is depending on a variable, $videoname. In my case this is a drupal field. You should adapt to your solution.
<?php
$streamingfile='http://cdn.mydomain.net/'.$videoname.'.m3u8';
$_SESSION['video'] = $videoname
$_SESSION['secret'] = '{mysecret}';
include_once "getsignedcookie.inc";
// Flowplayer skin
$attributes=array(
'rel'=>'stylesheet',
'href'=>'http://releases.flowplayer.org/6.0.5/skin/functional.css');
drupal_add_html_head_link($attributes);
// Flowplayer library
drupal_add_js('http://releases.flowplayer.org/6.0.5/flowplayer.min.js','external');
// Flowplayer hlsjs engine, use this in production
//drupal_add_js('http://releases.flowplayer.org/hlsjs/flowplayer.hlsjs.min.js','external');
//unminified hls.js library for testing purposes, remove for deploying
drupal_add_js("http://releases.flowplayer.org/hlsjs/hls.js",'external');
//separate hlsjs plugin component for testing purposes, remove for deploying
drupal_add_js("http://releases.flowplayer.org/hlsjs/flowplayer.hlsjs.js",'external');
// Additional safety measures for hlsjs and Flash HLS are taken globally for all players.
// Then players are installed as usual.
$javascript='// very simple global setup for all players
// to prevent key retrieval via header spoofing
// assumes splash setups throughout, no playlists
flowplayer(function (api, root) {
// access 2 groups of Flowplayers advanced public api methods
var bean = flowplayer.bean,
common = flowplayer.common;
bean.on(root, "click", function () {
if (api.splash) {
// allow one key retrieval; see php code
common.xhrGet("http://mysite.mydomain.com/hlsengine.php", common.noop, common.noop);
}
});
});
window.onload = function (e) {
flowplayer("#player", {
ratio: 9/16,
splash: true,
bgcolor: "#333333",
// only iframe embedding would work
// but we cannot force embedders to deploy via HTTPS only
embed: false,
clip: {
sources: [
{ type: "application/x-mpegurl",
src: "'.$streamingfile.'" }
]
},
hlsjs: {
xhrSetup: function(xhr, url) {
xhr.withCredentials = true; // do send cookies
}
},
startLevel: "auto"
})};';
drupal_add_js($javascript,'inline');
?>
<script>
// turn on hlsjs debugging, remove for deploying
flowplayer.conf.hlsjs = {
debug: true
};
</script>
<div id="player" class="is-closeable"></div>
Several comments to this code segment.
At the beginning there is as an example the url that will be used for streaming: $streamingfile='http://cdn.mydomain.com/'.$videoname.'.m3u8'. cdn.mydomain.com is a CNAME for the cloudfront distribution.
I have put my present configuration, which activates debug. It can be removed when everything works smoothly and just keep flowplayer.hlsjs.min.js
The line common.xhrGet("http://mysite.mydomain.com/hlsengine.php", common.noop, common.noop); is part of the protection layers. It is related to another file that must be uploaded to drupal root.
File hlsengine.php:
<?php
#activate drupal sessions
define('DRUPAL_ROOT', $_SERVER['DOCUMENT_ROOT']);
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
if (isset($_SESSION['secret']) && $_SESSION['secret'] === '{mysecret}') {
$_SESSION['allow'] = 'on';
}
hlsengine.php only detects if there is a session variable 'allow' that allows one key download. This variable is set to off in video-key.php.
Going back to the template, there is an important segment:
hlsjs: {
xhrSetup: function(xhr, url) {
xhr.withCredentials = true; // do send cookies
}
},
In this part we are specifying FlowPlayer that with the HTTP request some credentials are going to be sent. This has to be with CORS. CORS is one of the complex parts of this engine and one that caused me a lot of headaches. When the CloudFront distribution is created, the Forwarded headers: Whitelist and 1 header(s) whitelisted: Origin are also related to CORS. In .htaccess there are some lines related to headers, they are also caused by CORS. In fact, HLS playback is highly affected by CORS.
We still need another file that is included from the template. I have separated it from the template because it's the part responsible for setting up the cookies. For this to work you'll need to install version 3 of AWS SDK for PHP.
Instaling AWS SDK for PHP version 3
It's easy. First, install composer:
curl -sS https://getcomposer.org/installer | php
Then edit or create a file composer.json with contents:
{
"require": {
"aws/aws-sdk-php": "3.*"
}
}
Then install SDK
php composer.phar require aws/aws-sdk-php
And finally, to call this from your template used
<?php
require 'vendor/autoload.php';
Setting the cookies for CludFront viewer restriction
I have created a secondary file for setting up the cookies. That way if I want to remove viewer restriction I can just not include this file into the template code.
File getsignedcookie.inc:
<?php
require_once 'AWS_SDK/vendor/autoload.php';
//Setup Region
$region = 'your-region';
$cloudFront = new Aws\CloudFront\CloudFrontClient([
'region' => $region,
'version' => '2016-01-28'
]);
// Setup parameter values for the custom policy
$expires = time() + 60*60;
$private_key = '/path/to/hls-keys/pk-KEY-PAIR-ID.pem';
$key_pair_id = 'KEY-PAIR-ID';
$customPolicy = <<<POLICY
{
"Statement": [
{
"Resource": "http://*",
"Condition": {
"IpAddress": {"AWS:SourceIp": "{$_SERVER['REMOTE_ADDR']}/32"},
"DateLessThan": {"AWS:EpochTime": {$expires}}
}
}
]
}
POLICY;
// Create a signed cookie for the resource using a custom policy
$signedCookieCustomPolicy = $cloudFront->getSignedCookie([
'policy' => $customPolicy,
'private_key' => $private_key,
'key_pair_id' => $key_pair_id
]);
// headers specific for explorer
drupal_add_http_header('Access-Control-Origin', '*'); //maybe not needed
drupal_add_http_header('P3P:CP',"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT");
drupal_add_http_header('Access-Control-Allow-Credentials', 'true');
foreach ($signedCookieCustomPolicy as $name => $value) {
setcookie($name, $value, strtotime('+1 hour'), "/", ".mydomain.com", false, true);
}
I hope I didn't forget anything important. Your secure multibitrate multiplatform streaming should be ok and displaying. Inclusion of HTTPS is on work. Any help on this would be gratefully thanked.
References:
To do all this job I have consulted the pages below:
For transcoding:
https://developer.apple.com/library/ios/technotes/tn2224/_index.html#//a...
For installing AWS SDK for PHP:
http://docs.aws.amazon.com/aws-sdk-php/v2/guide/installation.html
https://docs.aws.amazon.com/aws-sdk-php/v3/guide/getting-started/install...
For setting up signed cookies:
http://www.spacevatican.org/2015/5/1/using-cloudfront-signed-cookies/
https://mnm.at/markus/2015/04/05/serving-private-content-through-cloudfr...
http://www.strehle.de/tim/weblog/archives/2015/11/06/1575
For installing ssl certificates in cloudfront:
https://www.savjee.be/2015/11/Uploading-your-own-ssl-certificate-to-Amaz...
For securing encrypted hls content:
https://blacktrash-org.prossl.de/fpssl/hls-crypt.php
Comments
Adding ssl to the formula
About adding https it is quite easy. You just need to install a proper certificate in your site, and using the certificate service in amazon install in the distribution a certificate for *.yourdomain.com or just yourdomain.com. That's all.
Hi Farreres, I have the same
Hi Farreres, I have the same suppose to server pre-encode hls video over Cloudfront but it still not work, My server running Nginx, I can play the m3u8 file in my origin, but not on the cloudfront, now they change the UI and many features, I try to play the url but it show error like "CORS" issue. Can you update the guide with the current Cloudfront web console?
sorry
I am sorry, I am not anymore working on this. Maybe you should contact some help center in cloudfront to help you.