secluded/content/posts/custom-streaming-setup.md

350 lines
12 KiB
Markdown

---
title: "Custom Streaming Setup"
description: "My second post of 100 Days To Offload details my custom streaming setup"
author: Amolith
date: 2020-04-26T20:24:38-04:00
draft: false
cover: /assets/pngs/stream.png
categories:
- Technology
tags:
- Gaming
- Streaming
- NGINX
- OBS
- 100 Days To Offload
toc: true
---
The other day, I decided that I wanted to start streaming. I'll
definitely be playing some games but I might also stream some other
things like me playing music. We'll see where that goes. In any case, I
don't like relying on third parties for things and didn't want to use
Twitch so I started figuring out how to build my own open source and
privacy-friendly "platform" (which is really just a [page.](/live))
## The search for a platform
Before settling on my own custom thing, I did some digging into
ready-made platforms I could just throw on one of my servers and run.
Two of the ones I found were
[OpenStreamingPlatform](https://openstreamingplatform.com/) and
[Restreamer.](https://datarhei.github.io/restreamer/) The latter isn't
exactly what I was looking for but it could have worked quite well. The
former, at first glance, was absolutely *perfect*. On a functional
level, it still is. However, take a look at [the installation
guide.](https://wiki.openstreamingplatform.com/Install/Manual)
`<rant>`
Steps 3 and 7 are unnecessary unless you feel like manually compiling
your web server; it's already available in the [Debian
repos](https://packages.debian.org/buster/libnginx-mod-rtmp) and, by
extension, Ubuntu's. It's even been backported to Stretch. In step 4, he
has `sed -i 's/appendfsync everysec/appendfsync no/'`. Like so many
application developers, he's assuming that this is the only project that
will be installed on the system. If someone is already using redis in
production and they have a different value there, that command will
fail. In step 9, the commands are copying the SystemD service files to
`/lib/systemd/` but this is where the package manager, `apt`, stores its
services. When you have your own that you're writing or copying from
somewhere else, best practise is to put them in `/etc/systemd/system`.
In addition, all of this is scripted for the "standard" install. Yes,
you're always supposed to review scripts before running them but who
really does that? When I see a project whose only supported installation
method is a script, I nope right on out of there for exactly this
reason. I know how my system *is* set up and I know how I *want* it set
up. I can't stand it when they assume they know what's best. Just tell
me what you *recommend* and I'll make decisions from there.
`</rant>`
## NGINX & RTMP
RTMP stands for [Real-Time Messaging
Protocol](https://wikipedia.org/wiki/Real-Time_Messaging_Protocol) and
facilitates streaming audio, video, and other data over the internet in
real-time. The NGINX module mentioned above adds functionality to NGINX
that allows it to handle RTMP streams and turn them into something a
browser or media streaming client can use. Connecting directly via
`rtmp://example.com/live/stream` is not very widely supported so
protocols such as
[MPEG-DASH](https://wikipedia.org/wiki/Dynamic_Adaptive_Streaming_over_HTTP)
and [HLS](https://wikipedia.org/wiki/HTTP_Live_Streaming) are used
instead.
On Debian-based systems, adding RTMP functionality to NGINX is as simple
as `apt install libnginx-mod-rtmp`. After that, you'll need to add some
things to your `nginx.conf` and whatever host file you're using for your
website.
``` c
rtmp {
server {
listen 1935;
application live {
deny publish all;
allow publish 127.0.0.1;
live on;
interleave on;
hls on;
hls_path /tmp/hls;
hls_fragment 15s;
dash on;
dash_path /tmp/dash;
dash_fragment 15s;
}
}
}
```
`1935` is the default RTMP port. `deny publish all` means you are
denying *anyone* from publishing a stream (that includes you. `allow
publish 127.0.0.1` allows *local* connections to publish content. I'm
using this as a form of authentication---before streaming anything, I
have to tunnel my connection to my server via SSH or a VPN. At the
moment, I'm using SSH:
``` text
ssh -L 1935:localhost:1935 user@example.com
```
The other options are just the basics needed to get DASH and HLS to
work. The only other thing to do is use NGINX as a reverse proxy (sort
of) to serve the streams. Add this to your site's virtual host.
``` c
location /dash {
root /tmp;
}
location /hls {
root /tmp;
}
```
That's it! Now you'll need to test your stream and verify that it
actually works.
``` bash
ffmpeg -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
```
This command has FFmpeg play the video and stream it to the server. You
should then be able to open the stream in something like
[VLC](https://www.videolan.org/) or [MPV](https://mpv.io/) and watch it
from anywhere.
``` bash
mpv https://example.com/dash/stream.mpd
```
However, I also wanted to embed it in a website and this is where it
gets a little unstable.
## Browser playback
`dash.js` is currently one of the best ways to play a live stream in a
browser plus it's pretty easy to work with. The code can be found [on
GitHub.](https://github.com/Dash-Industry-Forum/dash.js) Using the setup
with NGINX I detailed above, this should work perfectly fine out of the
box.
``` js
<div>
<video id="videoPlayer" poster="/assets/jpgs/stream.jpg" controls></video>
</div>
<script src="/assets/js/dash.all.min.js"></script>
<script>
(function(){
var url = "/dash/stream.mpd";
var player = dashjs.MediaPlayer().create();
player.initialize(document.querySelector("#videoPlayer"), url, true);
})();
</script>
```
## Web chat
The last thing every stream needs is something for web chat. I tried a
few different solutions and had mixed results. The first was
[KiwiIRC](https://kiwiirc.com/) but the iframe wouldn't even finish
loading because it connected to so many third parties with a lot of
tracking. It functions very well and I might set it up on my own site
eventually but it was a bit much to go through at the time. As an
intermediate solution, I embedded [my
instance](https://irc.nixnet.services) of [The
Lounge,](https://thelounge.chat) a fully-functional web-based IRC
client. This loaded perfectly right out of the box but it wasn't quite
what I wanted; there were *too* many options and the friends of mine who
tested it got frustrated because some of the essential UI elements were
hidden due to the small viewport. It's just not quite suitable for
embedded webchat.
Finally, I landed on [qwebirc](https://qwebirc.org/) and it was pretty
much *exactly* what I wanted. When the iframe loads, you're prompted to
enter a nick, you click connect, wait a minute, and done! My one
complaint is that the theme is very bright but I'll work on that later
on. It's good enough for now :wink:
**EDIT:** Since the time of writing, I have switched to hosting
[KiwiIRC](https://kiwiirc.com/) on
[Secluded.Site](https://chat.secluded.site) so all of the trackers and
third parties aren't in use. My configs are below but I recommend going
through [the
wiki](https://github.com/kiwiirc/kiwiirc/wiki/Configuration-Options) and
making your own decisions.
`/etc/kiwiirc/config.conf`
``` ini
logLevel = 3
identd = false
gateway_name = "webircgateway"
secret = "changeme"
[verify]
recaptcha_secret = ""
recaptcha_key = ""
[clients]
username = "%i"
realname = "KiwiIRC on secluded.site"
[server.1]
bind = "0.0.0.0"
port = 7264
[fileserving]
enabled = true
webroot = /usr/share/kiwiirc/
[transports]
websocket
sockjs
kiwiirc
[reverse_proxies]
127.0.0.0/8
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
"::1/128"
"fd00::/8"
[upstream.1]
hostname = "irc.nixnet.services"
port = 6697
tls = true
timeout = 5
throttle = 2
webirc = ""
```
`/etc/kiwiirc/client.json`
``` json
{
"windowTitle": "Secluded.Site Chat",
"startupScreen": "welcome",
"kiwiServer": "/webirc/kiwiirc/",
"restricted": true,
"hideAdvanced": true,
"showAutoComplete": true,
"showSendButton": true,
"sidebarDefault": "nicklist",
"theme": "dark",
"themes": [
{ "name": "Default", "url": "static/themes/default" },
{ "name": "Dark", "url": "static/themes/dark" },
{ "name": "Coffee", "url": "static/themes/coffee" },
{ "name": "GrayFox", "url": "static/themes/grayfox" },
{ "name": "Nightswatch", "url": "static/themes/nightswatch" },
{ "name": "Osprey", "url": "static/themes/osprey" },
{ "name": "Radioactive", "url": "static/themes/radioactive" },
{ "name": "Sky", "url": "static/themes/sky" }
],
"buffers" : {
"messageLayout": "compact",
"show_timestamps": false,
"show_hostnames": false,
"show_joinparts": false,
"show_topics": true,
"show_nick_changes": true,
"show_mode_changes": false,
"traffic_as_activity": false,
"coloured_nicklist": true,
"colour_nicknames_in_messages": true,
"block_pms": true,
"show_emoticons": true,
"extra_formatting": true,
"mute_sound": false,
"hide_message_counts": false,
"show_realnames": false,
"default_kick_reason": "Your behaviour is not conducive to this environment.",
"shared_input": false,
"show_message_info": true,
"share_typing": true,
"flash_title": "off",
"nicklist_avatars": true,
"show_link_previews": true,
"inline_link_previews": true,
"inline_link_auto_preview_whitelist": "secluded.site|nixnet.services",
"channel": "#secluded"
},
"startupOptions" : {
"server": "irc.nixnet.services",
"port": 6697,
"tls": true,
"direct": false,
"channel": "#secluded",
"nick": "viewer?",
"greetingText": "Welcome!",
"infoBackground": "",
"infoContent": ""
}
}
```
## Actually streaming
Once you're ready to start streaming content, I recommend using [OBS
Studio.](https://github.com/obsproject/obs-studio/) If you're noticing
issues with stream performance, play around with your output resolution
and FPS---those are the biggest factors. To use OBS with NGINX, you'll
need to go to `Settings`, `Stream`, and set `Server` to
`rtmp://localhost/live/`. If you're using my configs as they are, the
key will need to be `stream`. Literally every component requires
specific paths so, unless you're careful, things will break and you'll
spend hours trying figure it out like I did. Also don't forget that the
connection *has* to be tunnelled if you want authentication as I
mentioned above. If you don't have `localhost:1935` on your streaming
machine tunnelled to port 1935 on your server, OBS is going to throw
errors about not being able to connect.
## Summary
I'm pretty proud of [the set up](/live) I have now but it could still do
with some improvements. For example, I plan to mess with the CSS and
make both the video and chat panes *much* wider as well as side-by-side
rather than on top of each other. Everything is crammed together and
it's not a very good experience.
## References
This post has pieces taken from a few other articles and sites that also
deserve a mention as well as a read. NGINX actually has an [official
blog
post](https://www.nginx.com/blog/video-streaming-for-remote-learning-with-nginx/)
on setting up RTMP streaming (though they compile NGINX from source as
well) that was a *massive* help. I also found another post that is very
similar to this one about [HTML5 Live Streaming with
MPEG-DASH.](https://www.isrv.pw/html5-live-streaming-with-mpeg-dash) A
good number of the parts are the same but I used the NGINX module in
Debian repos and they used a fork of it with additional features. My
NGINX setup was mostly from the NGINX blog post and the embedded stream
was primarily from Inanity's. I figured out some of the components I
could use for all of this from [Drew
DeVault.](https://live.drewdevault.com/)
---
This was posted as part of
[#100DaysToOffload,](https://100daystooffload.com/) an [awesome
idea](https://fosstodon.org/@kev/104053977554016690) from [Kev
Quirk.](https://kevq.uk/) If you want to participate, just write
something every day for 100 days and post a link on social media with
the hashtag!