custom-streaming-setup.md

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