1---
2title: "Custom Streaming Setup"
3description: "My second post of 100 Days To Offload details my custom streaming setup"
4author: Amolith
5date: 2020-04-26T20:24:38-04:00
6cover: /assets/pngs/stream.png
7categories:
8 - Technology
9tags:
10 - Gaming
11 - Streaming
12 - NGINX
13 - OBS
14 - 100 Days To Offload
15toc: true
16---
17
18The other day, I decided that I wanted to start streaming. I'll
19definitely be playing some games but I might also stream some other
20things like me playing music. We'll see where that goes. In any case, I
21don't like relying on third parties for things and didn't want to use
22Twitch so I started figuring out how to build my own open source and
23privacy-friendly "platform" (which is really just a [page.](/live))
24
25## The search for a platform
26Before settling on my own custom thing, I did some digging into
27ready-made platforms I could just throw on one of my servers and run.
28Two of the ones I found were
29[OpenStreamingPlatform](https://openstreamingplatform.com/) and
30[Restreamer.](https://datarhei.github.io/restreamer/) The latter isn't
31exactly what I was looking for but it could have worked quite well. The
32former, at first glance, was absolutely *perfect*. On a functional
33level, it still is. However, take a look at [the installation
34guide.](https://wiki.openstreamingplatform.com/Install/Manual)
35
36`<rant>`
37
38Steps 3 and 7 are unnecessary unless you feel like manually compiling
39your web server; it's already available in the [Debian
40repos](https://packages.debian.org/buster/libnginx-mod-rtmp) and, by
41extension, Ubuntu's. It's even been backported to Stretch. In step 4, he
42has `sed -i 's/appendfsync everysec/appendfsync no/'`. Like so many
43application developers, he's assuming that this is the only project that
44will be installed on the system. If someone is already using redis in
45production and they have a different value there, that command will
46fail. In step 9, the commands are copying the SystemD service files to
47`/lib/systemd/` but this is where the package manager, `apt`, stores its
48services. When you have your own that you're writing or copying from
49somewhere else, best practise is to put them in `/etc/systemd/system`.
50In addition, all of this is scripted for the "standard" install. Yes,
51you're always supposed to review scripts before running them but who
52really does that? When I see a project whose only supported installation
53method is a script, I nope right on out of there for exactly this
54reason. I know how my system *is* set up and I know how I *want* it set
55up. I can't stand it when they assume they know what's best. Just tell
56me what you *recommend* and I'll make decisions from there.
57
58`</rant>`
59
60## NGINX & RTMP
61RTMP stands for [Real-Time Messaging
62Protocol](https://wikipedia.org/wiki/Real-Time_Messaging_Protocol) and
63facilitates streaming audio, video, and other data over the internet in
64real-time. The NGINX module mentioned above adds functionality to NGINX
65that allows it to handle RTMP streams and turn them into something a
66browser or media streaming client can use. Connecting directly via
67`rtmp://example.com/live/stream` is not very widely supported so
68protocols such as
69[MPEG-DASH](https://wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP)
70and [HLS](https://wikipedia.org/wiki/HTTP_Live_Streaming) are used
71instead.
72
73On Debian-based systems, adding RTMP functionality to NGINX is as simple
74as `apt install libnginx-mod-rtmp`. After that, you'll need to add some
75things to your `nginx.conf` and whatever host file you're using for your
76website.
77
78``` c
79rtmp {
80 server {
81 listen 1935;
82 application live {
83 deny publish all;
84 allow publish 127.0.0.1;
85 live on;
86 interleave on;
87 hls on;
88 hls_path /tmp/hls;
89 hls_fragment 15s;
90 dash on;
91 dash_path /tmp/dash;
92 dash_fragment 15s;
93 }
94 }
95}
96```
97
98`1935` is the default RTMP port. `deny publish all` means you are
99denying *anyone* from publishing a stream (that includes you. `allow
100publish 127.0.0.1` allows *local* connections to publish content. I'm
101using this as a form of authentication---before streaming anything, I
102have to tunnel my connection to my server via SSH or a VPN. At the
103moment, I'm using SSH:
104
105``` text
106ssh -L 1935:localhost:1935 user@example.com
107```
108
109The other options are just the basics needed to get DASH and HLS to
110work. The only other thing to do is use NGINX as a reverse proxy (sort
111of) to serve the streams. Add this to your site's virtual host.
112
113``` c
114location /dash {
115 root /tmp;
116}
117location /hls {
118 root /tmp;
119}
120```
121
122That's it! Now you'll need to test your stream and verify that it
123actually works.
124
125``` bash
126ffmpeg -re -i video.mp4 -vcodec copy -loop -1 -c:a aac -b:a 160k -ar 44100 -strict -2 -f flv rtmp://example.com/live/stream
127```
128
129This command has FFmpeg play the video and stream it to the server. You
130should then be able to open the stream in something like
131[VLC](https://www.videolan.org/) or [MPV](https://mpv.io/) and watch it
132from anywhere.
133
134``` bash
135mpv https://example.com/dash/stream.mpd
136```
137
138However, I also wanted to embed it in a website and this is where it
139gets a little unstable.
140
141## Browser playback
142`dash.js` is currently one of the best ways to play a live stream in a
143browser plus it's pretty easy to work with. The code can be found [on
144GitHub.](https://github.com/Dash-Industry-Forum/dash.js) Using the setup
145with NGINX I detailed above, this should work perfectly fine out of the
146box.
147
148``` js
149<div>
150 <video id="videoPlayer" poster="/assets/jpgs/stream.jpg" controls></video>
151</div>
152<script src="/assets/js/dash.all.min.js"></script>
153<script>
154(function(){
155 var url = "/dash/stream.mpd";
156 var player = dashjs.MediaPlayer().create();
157 player.initialize(document.querySelector("#videoPlayer"), url, true);
158})();
159</script>
160```
161
162## Web chat
163The last thing every stream needs is something for web chat. I tried a
164few different solutions and had mixed results. The first was
165[KiwiIRC](https://kiwiirc.com/) but the iframe wouldn't even finish
166loading because it connected to so many third parties with a lot of
167tracking. It functions very well and I might set it up on my own site
168eventually but it was a bit much to go through at the time. As an
169intermediate solution, I embedded [my
170instance](https://irc.nixnet.services) of [The
171Lounge,](https://thelounge.chat) a fully-functional web-based IRC
172client. This loaded perfectly right out of the box but it wasn't quite
173what I wanted; there were *too* many options and the friends of mine who
174tested it got frustrated because some of the essential UI elements were
175hidden due to the small viewport. It's just not quite suitable for
176embedded webchat.
177
178Finally, I landed on [qwebirc](https://qwebirc.org/) and it was pretty
179much *exactly* what I wanted. When the iframe loads, you're prompted to
180enter a nick, you click connect, wait a minute, and done! My one
181complaint is that the theme is very bright but I'll work on that later
182on. It's good enough for now :wink:
183
184**EDIT:** Since the time of writing, I have switched to hosting
185[KiwiIRC](https://kiwiirc.com/) on
186[Secluded.Site](https://chat.secluded.site) so all of the trackers and
187third parties aren't in use. My configs are below but I recommend going
188through [the
189wiki](https://github.com/kiwiirc/kiwiirc/wiki/Configuration-Options) and
190making your own decisions.
191
192`/etc/kiwiirc/config.conf`
193
194``` ini
195logLevel = 3
196identd = false
197gateway_name = "webircgateway"
198secret = "changeme"
199
200[verify]
201recaptcha_secret = ""
202recaptcha_key = ""
203
204[clients]
205username = "%i"
206realname = "KiwiIRC on secluded.site"
207
208[server.1]
209bind = "0.0.0.0"
210port = 7264
211
212[fileserving]
213enabled = true
214webroot = /usr/share/kiwiirc/
215
216[transports]
217websocket
218sockjs
219kiwiirc
220
221[reverse_proxies]
222127.0.0.0/8
22310.0.0.0/8
224172.16.0.0/12
225192.168.0.0/16
226"::1/128"
227"fd00::/8"
228
229[upstream.1]
230hostname = "irc.nixnet.services"
231port = 6697
232tls = true
233timeout = 5
234throttle = 2
235webirc = ""
236```
237
238`/etc/kiwiirc/client.json`
239
240``` json
241{
242 "windowTitle": "Secluded.Site Chat",
243 "startupScreen": "welcome",
244 "kiwiServer": "/webirc/kiwiirc/",
245 "restricted": true,
246 "hideAdvanced": true,
247 "showAutoComplete": true,
248 "showSendButton": true,
249 "sidebarDefault": "nicklist",
250 "theme": "dark",
251 "themes": [
252 { "name": "Default", "url": "static/themes/default" },
253 { "name": "Dark", "url": "static/themes/dark" },
254 { "name": "Coffee", "url": "static/themes/coffee" },
255 { "name": "GrayFox", "url": "static/themes/grayfox" },
256 { "name": "Nightswatch", "url": "static/themes/nightswatch" },
257 { "name": "Osprey", "url": "static/themes/osprey" },
258 { "name": "Radioactive", "url": "static/themes/radioactive" },
259 { "name": "Sky", "url": "static/themes/sky" }
260 ],
261 "buffers" : {
262 "messageLayout": "compact",
263 "show_timestamps": false,
264 "show_hostnames": false,
265 "show_joinparts": false,
266 "show_topics": true,
267 "show_nick_changes": true,
268 "show_mode_changes": false,
269 "traffic_as_activity": false,
270 "coloured_nicklist": true,
271 "colour_nicknames_in_messages": true,
272 "block_pms": true,
273 "show_emoticons": true,
274 "extra_formatting": true,
275 "mute_sound": false,
276 "hide_message_counts": false,
277 "show_realnames": false,
278 "default_kick_reason": "Your behaviour is not conducive to this environment.",
279 "shared_input": false,
280 "show_message_info": true,
281 "share_typing": true,
282 "flash_title": "off",
283 "nicklist_avatars": true,
284 "show_link_previews": true,
285 "inline_link_previews": true,
286 "inline_link_auto_preview_whitelist": "secluded.site|nixnet.services",
287 "channel": "#secluded"
288 },
289 "startupOptions" : {
290 "server": "irc.nixnet.services",
291 "port": 6697,
292 "tls": true,
293 "direct": false,
294 "channel": "#secluded",
295 "nick": "viewer?",
296 "greetingText": "Welcome!",
297 "infoBackground": "",
298 "infoContent": ""
299 }
300}
301```
302
303## Actually streaming
304Once you're ready to start streaming content, I recommend using [OBS
305Studio.](https://github.com/obsproject/obs-studio/) If you're noticing
306issues with stream performance, play around with your output resolution
307and FPS---those are the biggest factors. To use OBS with NGINX, you'll
308need to go to `Settings`, `Stream`, and set `Server` to
309`rtmp://localhost/live/`. If you're using my configs as they are, the
310key will need to be `stream`. Literally every component requires
311specific paths so, unless you're careful, things will break and you'll
312spend hours trying figure it out like I did. Also don't forget that the
313connection *has* to be tunnelled if you want authentication as I
314mentioned above. If you don't have `localhost:1935` on your streaming
315machine tunnelled to port 1935 on your server, OBS is going to throw
316errors about not being able to connect.
317
318## Summary
319I'm pretty proud of [the set up](/live) I have now but it could still do
320with some improvements. For example, I plan to mess with the CSS and
321make both the video and chat panes *much* wider as well as side-by-side
322rather than on top of each other. Everything is crammed together and
323it's not a very good experience.
324
325## References
326This post has pieces taken from a few other articles and sites that also
327deserve a mention as well as a read. NGINX actually has an [official
328blog
329post](https://www.nginx.com/blog/video-streaming-for-remote-learning-with-nginx/)
330on setting up RTMP streaming (though they compile NGINX from source as
331well) that was a *massive* help. I also found another post that is very
332similar to this one about [HTML5 Live Streaming with
333MPEG-DASH.](https://www.isrv.pw/html5-live-streaming-with-mpeg-dash) A
334good number of the parts are the same but I used the NGINX module in
335Debian repos and they used a fork of it with additional features. My
336NGINX setup was mostly from the NGINX blog post and the embedded stream
337was primarily from Inanity's. I figured out some of the components I
338could use for all of this from [Drew
339DeVault.](https://live.drewdevault.com/)
340
341---
342
343This was posted as part of
344[#100DaysToOffload,](https://100daystooffload.com/) an [awesome
345idea](https://fosstodon.org/@kev/104053977554016690) from [Kev
346Quirk.](https://kevq.uk/) If you want to participate, just write
347something every day for 100 days and post a link on social media with
348the hashtag!