Web video streaming server in Docker on Linux
Good evening
Today we are going to make our own video streaming server on a Linux machine. We will create it in a Docker container, so I assume you already have it installed.
Before I start, I’d like to say thanks to the author of this and this tutorials, it was very helpful.
So let’s dive right in.
Setting up the container
It is as simple as
docker run -it --name streaming4 -p 21935:1935 -p 21936:1936 -p 21937:80 ubuntu:18.04 bashYou may replace streaming4 with any name you want for the container. You may even omit --name streaming4, and Docker will generate a name for you.
We have opened up three ports here. You may replace them with whichever you like, but keep in mind that you may need to change them in the settings that follow. The ports serve the following purposes:
-
21935- the entry point for streaming. You will send your video stream here. -
21936- the web interface will read the video stream on this port -
21937- you, as a user, will connect here via your browser to access the web player
Installing nginx
Now we need to install nginx web server with the essential plugin for RTMP protocol support. Since we are in a Docker container, we don’t need sudo, we are already root user.
apt update
apt install -y nginx libnginx-mod-rtmpWe can run it now by simply running nginx to make sure that it has installed properly. It will out put nothing and that is okay.
Creating the stream directory
Now we need to create a directory that will be a root for stream connection, and set the ownership so nginx would have an access to it.
cd /mnt
mkdir hls
chown www-data:www-data hlswww-data is the user of nginx. In some versions it may be www, maybe something else. I simply ran top to see the user of nginx process to check it out.
Setting up nginx for streaming
Open /etc/nginx/nginx.conf with your preferred text editor. You will see a lot of default configuration blocks, ignore them for now.
RTMP streaming
Put the following block at the very end of the file.
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
#allow publish 11.22.33.44;
deny publish all;
# Turn on HLS
hls on;
hls_path /mnt/hls/;
hls_fragment 3;
hls_playlist_length 60;
# disable consuming the stream from nginx as rtmp
deny play all;
}
}
}
1935 is the internal port for streaming input. Even though the streaming software on our computer will be connecting to 21935, it will be redirected to the internal 1935 port (as we have set when we created the container) and it is the one we need to set here.
At this point streaming will not work because we have blocked it with deny publish all;. To allow streaming from your computer, uncomment the allow publish line and replace 11.22.33.44 with your external IP address.
Connection point for the player
Now we need to create an point for our web player to connect to. It has a variety of necessary options.
Now, attention, this block should go at the end of the http block, which most likely exists by default in the config. Not at the end of the whole file!
server {
listen 1936;
location / {
# Disable cache
add_header 'Cache-Control' 'no-cache';
# CORS setup
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length';
# allow CORS preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
types {
application/dash+xml mpd;
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /mnt/;
}
}
Again the port 1936 is the internal port we have set for these services. Our web player will be connecting to 21936 though.
Finishing nginx configuration
Now save the file and exit the editor. Run the following to apply the settings.
nginx -s reloadIf nothing is printed, then everything is okay.
Let’s try streaming
At this point you can actually start streaming video. I’ll show you how to do it using OBS Studio. I assume that you already have it installed and know how to actually stream (setting up the scene, video quality settings, etc.). I’ll just show the configuration you need to connect to your newly made server.
In File menu choose Settings. Go to Stream tab. All relevant configuration will be done here.
- Select “Custom” from Service dropdown menu.
- In Server, set
rtmp://33.33.33.33:21935/live; replace33.33.33.33with the IP address of your server. Notice that we are using the external port we have set for streaming on the Docker container. - In Stream Key, you can write pretty much anything. Just remember what it is, we’ll need it soon.
Click OK, and now you may try streaming by clicking the Start Streaming button. You will see a green square and a non-zero streaming speed at the bottom-right of the window, if it works fine.
The web player
But now we want to check whether it actually plays or not. Create a file called vid.html (or whichever name you prefer, just remember it) with the following contents
<html>
<head>
<link href="https://vjs.zencdn.net/7.8.2/video-js.css" rel="stylesheet" />
<!-- If you'd like to support IE8 (for Video.js versions prior to v7) -->
<script src="https://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js"></script>
<style>
body {
margin-top: 0px;
margin-left: 0px;
margin-bottom: 0px;
margin-right: 0px;
}
</style>
</head>
<body>
<video
id="my-video"
class="video-js vjs-fill"
controls
preload="auto"
poster="MY_VIDEO_POSTER.jpg"
data-setup="{}"
>
<source src="http://33.33.33.33:21936/hls/MYSTREAMKEY.m3u8" type="application/x-mpegURL" />
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a
web browser that
<a href="https://videojs.com/html5-video-support/" target="_blank"
>supports HTML5 video</a
>
</p>
</video>
<script src="https://vjs.zencdn.net/7.8.2/video.js"></script>
</body>
</html>Replace 33.33.33.33 with the IP address of your server, and MYSTREAMKEY with the stream key you have set in OBS settings. Notice again, we are using the external port 21936 for our player’s connection.
This creates a video player that occupies the whole screen.
Now you can simply open this file locally in your web browser, and it should play your stream. Success so far!
Setting up the player on the server
But we are not going to give out this file manually to every interested person, now are we? How about we put it on our server, so a user could access it by simply going to an address in their web browser.
Let’s go back to our container. Navigate to /var/www/html, delete the default files in there and put the vid.html file there. I cheated, I simply opened text editor, copypasted the contents and saved it; didn’t feel like copying it to the server and then docker cp-ing or whatever.
Now we need to edit /etc/nginx/sites-enabled/default (the actual file is located in sites-available, and sites-enabled must contain the symlink to it). Somewhere within the server block should be a line starting with index. It contains the default index files which we have just deleted. Replace this line with index vid.html;. Also make sure that root /var/www/html; is present, since it is the directory where we have put our file. Save and don’t forget to nginx -s reload.
I myself encountered a strange error here, so I’ll describe it so you could fix it if it happens to you. When I tried to nginx -s reload, it showed nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/default~:22. The workaround is to change the 80 in listen 80 default_server; and listen [::]:80 default_server; to something else, for example 81; then saving, then nginx -s reload, then going back into the file, changing back to 80, saving and nginx -s reload. It probably conflicts with itself at some point or something.
Finally, go to your web browser enter http://33.33.33.33:21937/, where (you’re probably tired of this already) 33.33.33.33:21937 is the IP address of your server and 21937 is the external port we have opened up in the beginning. And it should show your player with your stream. We’re done here.
Playing videos from your server
As a little bonus, I’m going to add the way to stream videos that are stored on your server in a certain folder in a random order indefinitely.
I usually just cd to the folder with my videos, and from there I create a container with latest ubuntu in it with docker run -it --rm -v $(pwd):/vids --net bridge ubuntu:20.04 bash. Why did I specify that I wanted --net bridge? Simply because I didn’t feel like streaming via the open port of the streaming server container (21935 in the example above); instead I wanted to access the container via its local IP address (which can be seen in the output of docker network inspect bridge). Yes, of course, I could set the --host parameter for that container, but I was too lazy to recreate it.
Inside the container I’ve just created, I install ffmpeg.
apt update
apt install ffmpegAnd then I go to the shared folder with videos via cd /vids and run the following to actually start streaming.
OIFS="$IFS"
IFS=$'\n'
VIDEO_BITRATE=2000k
while true; do for filename in `ls | shuf`; do ffmpeg -re -i "$filename" -vsync 2 -b:v $VIDEO_BITRATE -bufsize $VIDEO_BITRATE -vcodec libx264 -vf scale=1280:960 -x264-params keyint=60:scenecut=0 -preset ultrafast -maxrate $VIDEO_BITRATE -pix_fmt yuv420p -c:a aac -b:a 160k -ar 44100 -f flv rtmp://172.17.0.99:1935/live/MYSTREAMKEY; done; done;
IFS="$OIFS";The magic with OIFS and IFS is needed only if the file names of your video files contain whitespaces, because this allows the shell to treat them as a whole. You can play around with bitrate and scale of the videos, this is just an example that worked well for me.
In conclusion
We have created a video streaming server, and we didn’t have to contaminate the native settings on our server. Moreover, we can docker commit the container for safekeeping or so we could transfer all the work we’ve done here to another server. Quite convenient.
I haven’t touched upon encryption or any kinds of authorization for viewers. I haven’t figured it out myself yet. Bending my mind over this task was a challenge already.
Thank you for dropping by!