summaryrefslogtreecommitdiffstats
path: root/_posts/2018-08-26-Self-hosted-livestreaming.md
blob: 81d924a54737bde53cfb6ea330aeeb1503911f87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
---
layout: post
title: How to make a self-hosted video livestream
tags: [instructive, ffmpeg]
---

I have seen some articles in the past which explain how to build the ecosystem
*around* your video streaming, such as live chat and forums, but which leave the
actual video streaming to Twitch.tv. I made a note the last time I saw one of
these articles to write one of my own explaining the video bit. As is often the
case with video, we'll be using the excellent [ffmpeg](http://ffmpeg.org/) tool
for this. If it's A/V-related, ffmpeg can probably do it.

<script src="/js/dash.all.min.js"></script>
<video
    data-dashjs-player autoplay muted controls
    src="/dash/live.mpd"
    poster="https://sr.ht/JGOY.png"
    style="width: 100%"
></video>
<div style="text-align: center; font-size: 0.8rem; width: 80%; margin: 0 auto 1rem auto;">
    This is the recordings from the
    <a href="https://www.indiegogo.com/projects/sway-hackathon-software#/">
        Sway hackathon
    </a>
    we put on earlier this year, plus the current UTC time to prove that it's
    live. Click unmute if you want to hear the audio stream.
</div>

ffmpeg has a built-in [DASH](https://dashif.org/) output format, which is the
current industry standard for live streaming video to web browsers. It works by
splitting the output up into discrete files and using an [XML
file](/dash/live.mpd) (an MPD playlist) to tell the player where they are. Few
browsers support DASH natively, but
[dash.js](https://github.com/Dash-Industry-Forum/dash.js/wiki) can polyfill it
by periodically downloading the latest manifest and driving the video element
itself.

Getting the source video into ffmpeg is a little bit beyond the scope of this
article, but I know some readers won't be familiar with ffmpeg so I'll have
mercy. Let's say you want to play some static video files like I'm doing above:

```sh
ffmpeg \
	-re \
	-stream_loop -1 \
	-i my-video.mkv \
```

This will tell ffmpeg to read the input (-i) in real time (-re), and loop it
indefinitely. If instead you want to, for example, use x11grab instead to
capture your screen and pulse to capture desktop audio, try this:

```sh
    -f x11grab \
    -r 30 \
    -video_size 1920x1080 \
    -i $DISPLAY \
    -f pulse \
    -i alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_REV8-00.analog-stereo
```

This sets the framerate to 30 FPS and the video resolution to 1080p, then reads
from the X11 display `$DISPLAY` (usually :0). Then we add pulseaudio and use my
microphone source name, which I obtained with `pactl list sources`.

Let's add some arguments describing the output format. Your typical web browser
is a finicky bitch and has some very specific demands from your output format if
you want maximum compatability:

```sh
    -codec:v libx264 \
    -profile:v baseline \
    -level 4 \
    -pix_fmt yuv420p \
    -preset veryfast \
    -codec:a aac \
```

This specifices the libx264 video encoder with the baseline level 4 profile, the
most broadly compatible x264 profile, with the yuv420p pixel format, the most
broadly compatible pixel format, the veryfast preset to make sure we can encode
it in realtime, the aac audio codec. Now that we've specified the parameters for
the output, let's configure the output format: DASH.

```sh
	-f dash \
	-window_size 5 \
	-remove_at_exit 1 \
	/tmp/dash/live.mpd
```

The window_size specifies the maximum number of A/V segments to keep in the
manifest at any time, and remove_at_exit will clean up all of the files when
ffmpeg exits. The output file is the path to the playlist to write to disk, and
the segments will be written next to it. The last step is to serve this with
nginx:

```nginx
location /dash {
        types {
                application/dash+xml mpd;
                video/mp4 m4v;
                audio/mp4 m4a;
        }
        add_header Access-Control-Allow-Origin *;
        root /tmp;
}
```

You can now point the [DASH reference
player](http://reference.dashif.org/dash.js/nightly/samples/dash-if-reference-player/index.html)
at `http://your-server.org/dash/live.mpd` and see your video streaming there.
Neato! You can add dash.js to your website and you know have a fully self-hosted
video live streaming setup ready to rock.

Perhaps the ffmpeg swiss army knife isn't your cup of tea. If you want to, for
example, use [OBS Studio](https://obsproject.com/), you might want to take a
somewhat different approach. The
[nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) provides an RTMP
(real-time media protocol) server that integrates with nginx. After adding
the DASH output, you'll end up with something like this:

```sh
rtmp {
    server {
        listen 1935;

        application live {
            dash on;
            dash_path /tmp/dash;
            dash_fragment 15s;
        }
    }
}
```

Then you can stream to `rtmp://your-server.org/live` and your dash segments
will show up in `/tmp/dash`. There's no password protection here, so put it in
the stream URL (e.g. `application R9AyTRfguLK8`) or use an IP whitelist:

```
application live {
    allow publish your-ip;
    deny publish all;
}
```

If you want to get creative with it you can use
[`on_publish`](https://github.com/arut/nginx-rtmp-module/wiki/Directives#on_publish)
to hit an web service with some details and return a non-2xx code to forbid
streaming. Have fun!

I learned all of this stuff by making a bot which livestreamed Google hangouts
over the LAN to get around the participant limit at work. I'll do a full writeup
about that one later!

---

Here's the full script I'm using to generate the live stream on this
page:

```sh
#!/bin/sh
rm -f /tmp/playlist
mkdir -p /tmp/dash
for file in /var/www/mirror.sr.ht/hacksway-2018/*
do
	echo "file '$file'" >> /tmp/playlist
done

ffmpeg \
	-re \
	-loglevel error \
	-stream_loop -1 \
	-f concat \
	-safe 0 \
	-i /tmp/playlist \
	-vf "drawtext=\
			fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:\
			text='%{gmtime\:%Y-%m-%d %T} UTC':\
			fontcolor=white:\
			x=(w-text_w)/2:y=128:\
			box=1:boxcolor=black:\
			fontsize=72,
		drawtext=\
			fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:\
			text='REBROADCAST':\
			fontcolor=white:\
			x=(w-text_w)/2:y=16:\
			box=1:boxcolor=black:\
			fontsize=48" \
	-codec:v libx264 \
	-profile:v baseline \
	-pix_fmt yuv420p \
	-level 4 \
	-preset veryfast \
	-codec:a aac \
	-f dash \
	-window_size 5 \
	-remove_at_exit 1 \
	/tmp/dash/live.mpd
```