├── Dockerfile ├── LICENSE ├── README.md ├── sbin ├── entrypoint.sh ├── render-templates.sh └── substitute-env-vars.sh └── templates └── nginx.conf.tmpl /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | MAINTAINER Reid Burke 3 | 4 | RUN apt-get -q -y update \ 5 | && apt-get -q -y install cron logrotate make build-essential libssl-dev \ 6 | zlib1g-dev libpcre3 libpcre3-dev curl pgp yasm \ 7 | && apt-get -q -y build-dep nginx \ 8 | && apt-get -q -y clean && rm -rf /var/cache/apt/archives/* /var/lib/apt/lists/* 9 | 10 | RUN cd /root \ 11 | && curl -L https://downloads.sourceforge.net/project/opencore-amr/fdk-aac/fdk-aac-0.1.4.tar.gz > fdk-aac.tgz \ 12 | && mkdir fdk-aac && tar xzf fdk-aac.tgz -C fdk-aac --strip 1 && cd fdk-aac \ 13 | && ./configure && make install 14 | 15 | RUN cd /root \ 16 | && curl -L ftp://ftp.videolan.org/pub/x264/snapshots/x264-snapshot-20160225-2245.tar.bz2 > x264.tar.bz2 \ 17 | && mkdir x264 && tar xjf x264.tar.bz2 -C x264 --strip 1 && cd x264 \ 18 | && ./configure --enable-static && make install 19 | 20 | RUN cd /root \ 21 | && curl -L https://libav.org/releases/libav-11.4.tar.gz > libav.tgz \ 22 | && mkdir libav && tar xzf libav.tgz -C libav --strip 1 && cd libav \ 23 | && ./configure --enable-gpl --enable-nonfree \ 24 | --enable-libfdk-aac --enable-libx264 \ 25 | && make install 26 | 27 | RUN groupadd nginx 28 | RUN useradd -m -g nginx nginx 29 | RUN mkdir -p /var/log/nginx /var/cache/nginx 30 | 31 | RUN cd /root && curl -L https://github.com/arut/nginx-rtmp-module/archive/v1.1.7.tar.gz > nginx-rtmp.tgz \ 32 | && mkdir nginx-rtmp && tar xzf nginx-rtmp.tgz -C nginx-rtmp --strip 1 33 | 34 | RUN mkdir /www && cp /root/nginx-rtmp/stat.xsl /www/info.xsl && chown -R nginx:nginx /www 35 | 36 | RUN cd /root \ 37 | && curl -L -O http://nginx.org/download/nginx-1.8.1.tar.gz \ 38 | && curl -L -O http://nginx.org/download/nginx-1.8.1.tar.gz.asc \ 39 | && gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-key A1C052F8 \ 40 | && gpg nginx-1.8.1.tar.gz.asc \ 41 | && tar xzf nginx-1.8.1.tar.gz && cd nginx-1.8.1 \ 42 | && ./configure \ 43 | --prefix=/etc/nginx \ 44 | --sbin-path=/usr/sbin/nginx \ 45 | --conf-path=/etc/nginx/nginx.conf \ 46 | --error-log-path=/var/log/nginx/error.log \ 47 | --http-log-path=/var/log/nginx/access.log \ 48 | --pid-path=/var/run/nginx.pid \ 49 | --lock-path=/var/run/nginx.lock \ 50 | --http-client-body-temp-path=/var/cache/nginx/client_temp \ 51 | --http-proxy-temp-path=/var/cache/nginx/proxy_temp \ 52 | --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \ 53 | --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \ 54 | --http-scgi-temp-path=/var/cache/nginx/scgi_temp \ 55 | --user=nginx \ 56 | --group=nginx \ 57 | --with-http_ssl_module \ 58 | --with-http_realip_module \ 59 | --with-http_addition_module \ 60 | --with-http_sub_module \ 61 | --with-http_dav_module \ 62 | --with-http_flv_module \ 63 | --with-http_mp4_module \ 64 | --with-http_gunzip_module \ 65 | --with-http_gzip_static_module \ 66 | --with-http_random_index_module \ 67 | --with-http_secure_link_module \ 68 | --with-http_stub_status_module \ 69 | --with-http_auth_request_module \ 70 | --with-mail \ 71 | --with-mail_ssl_module \ 72 | --with-file-aio \ 73 | --add-module=/root/nginx-rtmp \ 74 | --with-ipv6 \ 75 | && make install 76 | 77 | RUN ldconfig 78 | 79 | EXPOSE 80 80 | EXPOSE 1935 81 | 82 | RUN mkdir -p /etc/nginx/templates 83 | 84 | ADD sbin/substitute-env-vars.sh /usr/sbin/substitute-env-vars.sh 85 | ADD sbin/render-templates.sh /usr/sbin/render-templates.sh 86 | ADD sbin/entrypoint.sh /usr/sbin/entrypoint.sh 87 | 88 | ADD templates/nginx.conf.tmpl /etc/nginx/templates/nginx.conf.tmpl 89 | 90 | ENTRYPOINT ["/usr/sbin/entrypoint.sh"] 91 | CMD ["/usr/sbin/nginx", "-c", "/etc/nginx/nginx.conf"] 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2014 Reid Burke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | Portions of the Software are adapted from the nginx-template-image project. 24 | Copyright 2014 Jake Goulding. Used under the MIT license. 25 | https://github.com/shepmaster/nginx-template-image 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awakening-nginx-rtmp 2 | 3 | Live streaming video server for Flash, iOS and Android 4 | 5 | ## Usage 6 | 7 | Publishing is restricted to clients who supply the pre-shared `PUBLISH_SECRET`, 8 | passed to the Docker container as an environment variable. 9 | 10 | You must set the following environment variables: 11 | 12 | - `PUBLISH_SECRET`: Secret token for publishing and statistics. 13 | - `CORS_HTTP_ORIGIN`: HTTP origin regex to allow CORS on the /hls location. 14 | 15 | This image exposes ports `80` for HTTP and `1935` for RTMP. 16 | 17 | ### Example 18 | 19 | docker run -e PUBLISH_SECRET=VERY_SECRET_KEY 20 | -e CORS_HTTP_ORIGIN='(https?://[^/]*\.awakeningchurch\.com(:[0-9]+)?)' 21 | -p 80:80 -p 1935:1935 awakening/awakening-nginx-rtmp 22 | 23 | 24 | ## Publish URL 25 | 26 | Set your RTMP encoder to publish to `rtmp://{your-server}/pub_{PUBLISH_SECRET}/{your-stream-name}`. 27 | 28 | ## Player URL 29 | 30 | The stream can be viewed at `rtmp://{your-server}/player/{your-stream-name}`. 31 | 32 | ## HLS 33 | 34 | HLS playlists are available at `http://{{your-server}/hls/{your-stream-name}.m3u8`. 35 | 36 | ## Statistics 37 | 38 | The following resources are available: 39 | 40 | - `info`: General information 41 | - `stats`: XML of general information 42 | 43 | Statistic URLs contain references to the `PUBLISH_SECRET`, so they are protected. 44 | You can visit these protected resources by visiting `/p/{token}/{resource-name}`, where 45 | `{token}` is set the the result of: 46 | 47 | ``` 48 | echo -n '{resource-name}{PUBLISH_SECRET}' | openssl md5 -hex 49 | ``` 50 | 51 | ## License 52 | 53 | MIT, see LICENSE file. 54 | -------------------------------------------------------------------------------- /sbin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | render-templates.sh /etc/nginx/templates/ /etc/nginx/ 6 | exec $@ 7 | -------------------------------------------------------------------------------- /sbin/render-templates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | indir="${1}" 6 | outdir="${2}" 7 | 8 | function template_files() { 9 | find "${indir}" \ 10 | -mindepth 1 \ 11 | -maxdepth 1 \ 12 | -name '*.tmpl' \ 13 | -print0 14 | } 15 | 16 | function non_template_files() { 17 | find "${indir}" \ 18 | -mindepth 1 \ 19 | -maxdepth 1 \ 20 | -not \ 21 | -name '*.tmpl' \ 22 | -print0 23 | } 24 | 25 | template_files | xargs -0 substitute-env-vars.sh "${outdir}" 26 | non_template_files | xargs -0 -I{} ln -s {} "${outdir}" 27 | -------------------------------------------------------------------------------- /sbin/substitute-env-vars.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | output_dir=$1 4 | shift 5 | files=$@ 6 | 7 | function fill_in() { 8 | perl -p -e 's/\$\{([^}]+)\}/defined $ENV{$1} ? $ENV{$1} : "\${$1}"/eg' "${1}" 9 | } 10 | 11 | function output_filename { 12 | local destname=$(basename "${1}" '.tmpl') 13 | echo "${output_dir}/${destname}" 14 | } 15 | 16 | for file in $files; do 17 | fill_in "${file}" > $(output_filename "${file}") 18 | done 19 | -------------------------------------------------------------------------------- /templates/nginx.conf.tmpl: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | 6 | daemon off; 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | http { 13 | include /etc/nginx/mime.types; 14 | default_type application/octet-stream; 15 | 16 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 17 | '$status $body_bytes_sent "$http_referer" ' 18 | '"$http_user_agent" "$http_x_forwarded_for"'; 19 | 20 | access_log /var/log/nginx/access.log main; 21 | 22 | sendfile on; 23 | 24 | keepalive_timeout 65; 25 | 26 | server { 27 | listen 80; 28 | 29 | location / { 30 | root /www; 31 | } 32 | 33 | location /hls { 34 | types { 35 | application/vnd.apple.mpegurl m3u8; 36 | } 37 | 38 | root /tmp; 39 | add_header Cache-Control no-cache; 40 | 41 | # CORS 42 | # based on: https://gist.github.com/algal/5480916 43 | # based on: https://gist.github.com/alexjs/4165271 44 | 45 | # if the request included an Origin: header with an origin on the whitelist, 46 | # then it is some kind of CORS request. 47 | 48 | if ($http_origin ~* ${CORS_HTTP_ORIGIN}) { 49 | set $cors "true"; 50 | } 51 | 52 | # Nginx doesn't support nested If statements, so we use string 53 | # concatenation to create a flag for compound conditions 54 | 55 | # OPTIONS indicates a CORS pre-flight request 56 | if ($request_method = 'OPTIONS') { 57 | set $cors "${cors}options"; 58 | } 59 | 60 | # non-OPTIONS indicates a normal CORS request 61 | if ($request_method = 'GET') { 62 | set $cors "${cors}get"; 63 | } 64 | if ($request_method = 'POST') { 65 | set $cors "${cors}post"; 66 | } 67 | 68 | # if it's a GET or POST, set the standard CORS responses header 69 | if ($cors = "trueget") { 70 | # Tells the browser this origin may make cross-origin requests 71 | # (Here, we echo the requesting origin, which matched the whitelist.) 72 | add_header 'Access-Control-Allow-Origin' "$http_origin"; 73 | # Tells the browser it may show the response, when XmlHttpRequest.withCredentials=true. 74 | add_header 'Access-Control-Allow-Credentials' 'true'; 75 | } 76 | 77 | if ($cors = "truepost") { 78 | # Tells the browser this origin may make cross-origin requests 79 | # (Here, we echo the requesting origin, which matched the whitelist.) 80 | add_header 'Access-Control-Allow-Origin' "$http_origin"; 81 | # Tells the browser it may show the response, when XmlHttpRequest.withCredentials=true. 82 | add_header 'Access-Control-Allow-Credentials' 'true'; 83 | } 84 | 85 | # if it's OPTIONS, then it's a CORS preflight request so respond immediately with no response body 86 | if ($cors = "trueoptions") { 87 | # Tells the browser this origin may make cross-origin requests 88 | # (Here, we echo the requesting origin, which matched the whitelist.) 89 | add_header 'Access-Control-Allow-Origin' "$http_origin"; 90 | # in a preflight response, tells browser the subsequent actual request can include user credentials (e.g., cookies) 91 | add_header 'Access-Control-Allow-Credentials' 'true'; 92 | 93 | # Tell browser to cache this pre-flight info for 20 days 94 | add_header 'Access-Control-Max-Age' 1728000; 95 | 96 | add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 97 | 98 | add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since'; 99 | 100 | # no body in this response 101 | add_header 'Content-Length' 0; 102 | # (should not be necessary, but included for non-conforming browsers) 103 | add_header 'Content-Type' 'text/plain charset=UTF-8'; 104 | # indicate successful return with no content 105 | return 204; 106 | } 107 | } 108 | 109 | location /p/ { 110 | secure_link_secret "${PUBLISH_SECRET}"; 111 | 112 | if ($secure_link = "") { 113 | return 403; 114 | } 115 | 116 | rewrite ^ /secure/$secure_link; 117 | } 118 | 119 | location /secure/stat { 120 | internal; 121 | rtmp_stat all; 122 | } 123 | 124 | location /secure/info { 125 | internal; 126 | rtmp_stat all; 127 | rtmp_stat_stylesheet /info.xsl; 128 | } 129 | 130 | location /info.xsl { 131 | root /www; 132 | } 133 | 134 | } 135 | 136 | } 137 | 138 | rtmp_auto_push on; 139 | 140 | rtmp { 141 | 142 | server { 143 | listen 1935; 144 | notify_method get; 145 | 146 | chunk_size 131072; 147 | max_message 12M; 148 | buflen 2s; 149 | 150 | application pub_${PUBLISH_SECRET} { 151 | live on; 152 | drop_idle_publisher 5s; 153 | allow play 127.0.0.1; 154 | deny play all; 155 | 156 | exec_push /usr/local/bin/avconv -i rtmp://localhost/pub_${PUBLISH_SECRET}/$name 157 | -filter:v scale=-1:460 158 | -c:a libfdk_aac -b:a 32k -c:v libx264 -b:v 128k -f flv rtmp://localhost/hls/$name_128 159 | -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512k -f flv rtmp://localhost/hls/$name_512; 160 | } 161 | 162 | application player { 163 | live on; 164 | 165 | allow publish 127.0.0.1; 166 | deny publish all; 167 | 168 | pull rtmp://localhost/pub_${PUBLISH_SECRET} live=1; 169 | 170 | wait_key on; 171 | wait_video on; 172 | } 173 | 174 | application hls { 175 | live on; 176 | allow publish 127.0.0.1; 177 | deny publish all; 178 | 179 | hls on; 180 | hls_path /tmp/hls; 181 | hls_fragment 15s; 182 | hls_nested on; 183 | 184 | hls_variant _128 BANDWIDTH=160000; 185 | hls_variant _512 BANDWIDTH=640000; 186 | } 187 | 188 | } 189 | } 190 | --------------------------------------------------------------------------------