This example is part of this article.
This is an example for an HLS delivery with basic security. Nginx compiled with nginx-rtmp-module & secure-link is used as media server. Features:
- Domain filtering
- Referrer filtering
- Embed buster
- Session token for playlist, segments and AES keys
- AES encryption
- HTTPS only
Throughout this example the host is assumed to be example.com
.
if you want to use this configurations, be sure to replace all instances of example.com
with your domain.
# install deps (Ubuntu)
sudo apt-get install -y build-essential libpcre3 libpcre3-dev libssl-dev
wget http://nginx.org/download/nginx-1.10.1.tar.gz
tar -xf nginx-1.10.1.tar.gz
cd nginx-1.10.1
./configure --with-http_ssl_module --add-module=../nginx-rtmp-module --with-http_secure_link_module
make -j
sudo make install
# nginx is now installed in /usr/local/nginx
In order to push video to nginx i'm going to use ffmpeg which well supports RTMP as its output.
I'm going to create an I frame
roughly every 2 seconds which will allow nginx to achieve the 4s segment target.
For simplicity i'll be using a static mp4 file and ingest it in infinite loop.
ffmpeg -hide_banner \
-stream_loop -1 \
-re -i test-video.mp4 \
-c:a aac -c:v h264 -g 48 \
-f flv rtmp://localhost:1935/show/live
I'm using live
as the stream name, the output hls will carry that same name - e.g. live.m3u8
.
The session token is based on this format (note the spaces):
MD5("EXPIREY_DATE_IN_SECONDS CLIENT_IP_ADDRESS SECRET")
here are several examples of generating the token:
BASH
get_customer_url() {
local IP=${1:-127.0.0.1}
local SECRET=${2:-VERY_COOL_SECRET}
local EXPIRES="$(date -d "today + 30 minutes" +%s)";
local token="$(echo -n "${EXPIRES} ${IP} ${SECRET}" | openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =)"
echo "https://example.com/video/hls/${token}/${EXPIRES}/live.m3u8"
}
get_customer_url 10.20.1.55 "uigfp(@#tfpIUDGPFiouGDF"
Node.JS (Javascript)
var crypto = require('crypto');
function generateSecurePathHash(expires, client_ip, secret) {
if (!expires || !client_ip || !secret) throw new Error('Must provide all token components');
var input = expires + ' ' + client_ip + ' ' + secret;
var binaryHash = crypto.createHash('md5').update(input).digest();
var base64Value = new Buffer(binaryHash).toString('base64');
return base64Value.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
}
function getStreamUrl(ip, secret) {
const expiresTimestamp = new Date(Date.now() + (1000 * 60 * 30)).getTime();
const expires = String(Math.round(expiresTimestamp / 1000));
const token = generateSecurePathHash(expires, ip, secret);
return `https://example.com/video/hls/${token}/${expires}/live.m3u8`;
}
getStreamUrl('127.0.0.1', 'uigfp(@#tfpIUDGPFiouGDF');
// https://example.com/video/hls/LdS-kcC-JGVHGNTFlX-6Sw/1526373776/live.m3u8
Why did you choose to have the HLS URL point to
/video
then rewrite it to/hls
with the token and expire appended to the query string? Why not directly write the token and expire timestamp directly such ashttps://example.com/hls/live.m3u8?token=foo&expire=1234
.I noticed the
/hls
location isinternal
so perhaps that has a part to do with it or were you just following the pattern as described in the secure link documentation?