Compare commits
5 Commits
ec47643c32
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0596c18939 | ||
|
|
8e74981e63 | ||
|
|
a399bb04fd | ||
|
|
b57796b7b5 | ||
|
|
15377b9c66 |
@@ -6,9 +6,11 @@ description = "Deploying my new IPv4 expose setup"
|
||||
[taxonomies]
|
||||
categories = ["Homelab"]
|
||||
tags = ["Homelab", "IPv4", "IPv6"]
|
||||
series = ["homelab-ipv4-expose"]
|
||||
|
||||
[extra]
|
||||
toc = true
|
||||
series_order = 2
|
||||
+++
|
||||
|
||||
Implementing the ideas and conclusions from my initial post about exposing my IPv6 Homelab to IPv4 clients ([Homelab IPv4 Expose](@/homelab-ipv4-expose/index.md)) in the real world and deploying the entire system.
|
||||
|
||||
@@ -6,9 +6,11 @@ description = "How I expose my IPv6 only Homelab for IPv4 clients"
|
||||
[taxonomies]
|
||||
categories = ["Homelab"]
|
||||
tags = ["Homelab", "IPv4", "IPv6"]
|
||||
series = ["homelab-ipv4-expose"]
|
||||
|
||||
[extra]
|
||||
toc = true
|
||||
series_order = 1
|
||||
+++
|
||||
|
||||
How I plan to expose the services running in my homelab to IPv4 users despite not having a publicly routable IPv4 address/subnet.
|
||||
|
||||
@@ -7,9 +7,11 @@ draft = true
|
||||
[taxonomies]
|
||||
categories = ["Programming"]
|
||||
tags = ["Factorio", "Rust"]
|
||||
series = ["quickfacts"]
|
||||
|
||||
[extra]
|
||||
toc = true
|
||||
series_order = 3
|
||||
+++
|
||||
|
||||
The first real entry in my series about my quickfacts project.
|
||||
|
||||
@@ -7,9 +7,11 @@ draft = true
|
||||
[taxonomies]
|
||||
categories = ["Programming"]
|
||||
tags = ["Factorio", "Rust"]
|
||||
series = ["quickfacts"]
|
||||
|
||||
[extra]
|
||||
toc = true
|
||||
series_order = 2
|
||||
+++
|
||||
|
||||
Testing
|
||||
|
||||
@@ -7,9 +7,11 @@ draft = true
|
||||
[taxonomies]
|
||||
categories = ["Programming"]
|
||||
tags = ["Factorio", "Rust", "Quickfacts"]
|
||||
series = ["quickfacts"]
|
||||
|
||||
[extra]
|
||||
toc = true
|
||||
series_order = 1
|
||||
+++
|
||||
|
||||
A small introduction to my ongoing quickfacts project.
|
||||
|
||||
28
content/reversi-intro/index.md
Normal file
28
content/reversi-intro/index.md
Normal file
@@ -0,0 +1,28 @@
|
||||
+++
|
||||
title = "Reversi - Intro"
|
||||
date = 2026-10-31
|
||||
description = "An introduction to reversi and my goals of this series"
|
||||
draft = true
|
||||
|
||||
[taxonomies]
|
||||
categories = ["Programming"]
|
||||
tags = ["Rust", "Reversi", "Optimization"]
|
||||
series = ["reversi"]
|
||||
|
||||
[extra]
|
||||
toc = true
|
||||
series_order = 1
|
||||
+++
|
||||
|
||||
So this first entry in the series will be an introduction into the game reversi, and the modified rules for this case, and talk a bit about the context and goals of this series overall.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
# Motivation
|
||||
TODO
|
||||
|
||||
# Reversi the Game
|
||||
TODO
|
||||
|
||||
# Goal
|
||||
TODO
|
||||
@@ -1,12 +1,11 @@
|
||||
+++
|
||||
title = "Streaming setup"
|
||||
date = 2026-10-01
|
||||
title = "Streaming for Teamspeak3"
|
||||
date = 2026-10-18
|
||||
description = "Custom streaming setup to complement teamspeak"
|
||||
draft = true
|
||||
|
||||
[taxonomies]
|
||||
categories = ["Homelab"]
|
||||
tags = ["Homelab"]
|
||||
tags = ["Homelab", "Teamspeak3"]
|
||||
|
||||
[extra]
|
||||
toc = true
|
||||
@@ -26,18 +25,82 @@ But as anyone that has used teamspeak before might know, Teamspeak3 does not sup
|
||||
But given that teamspeak allows for creating your own plugins and makes it relatively easy to distribute them, I thought it might be interesting to try and basically add this functionality myself using a plugin.
|
||||
|
||||
# The Plan
|
||||
TODO
|
||||
So the plan is to have a teamspeak plugin, which we use to authenticate users, automate the streaming setup and allow for easily watching streams.
|
||||
In practice that means you are required to log into my self-hosted auth server, which both ensures that you are actually allowed to stream and allows me to send dynamic information about the streaming setup to the client (like some secret key to use for generating the actual streaming key later on).
|
||||
If someone wants to start streaming, the plugin provides you the URL and stream-key for manual use, but can also start OBS itself and properly configure the streaming details, if the OBS websocket server has been enabled before.
|
||||
As a viewer you can just click on someone in teamspeak and click on "watch", which opens up a new window containing just the stream.
|
||||
|
||||
# OvenMediaEngine + OBS
|
||||
Luckily I found OvenMediaEngine, which is an entire software setup to basically host your own streaming
|
||||
platform. This is obviously overkill for our goal, but works better than I would have expected at first.
|
||||
# OvenMediaEngine
|
||||
OvenMediaEngine[^OvenMediaEngine] is a project I found, that allows you to basically setup your own streaming platform with all kinds of features.
|
||||
However for this use-case the setup will be limited to the most basic configuration of just directly rebroadcasting the incoming streams, without any reencoding or the like.
|
||||
|
||||
So it accepts streams over rtmp from streaming sources, like OBS, and then listens for any "consumer" and forwards the stream using WebRTC over UDP to them.
|
||||
This allows for low-latency streaming, which is very important as you are likely talking to the person streaming and any large delays in the stream would greatly reduce the overall enjoyability of this plugin.
|
||||
|
||||
# OBS
|
||||
OBS[^OBS] in this case is just the default choice for streaming, as it is open-source and relatively easy to use by everyone.
|
||||
The main requirement is that it needs to be able to stream over rtmp, but a benefit of using OBS is that you can (once enabled) interact with OBS programmatically over a websocket server.
|
||||
This means that users do not have to manually enter the stream credentials every time they want to start the stream, but instead the plugin can actually configure these settings for the user.
|
||||
|
||||
# Teamspeak3 Plugin
|
||||
To make the setup for my friends easier and to add some form of security/authentication, I wrote a small
|
||||
teamspeak plugin, which generates the credentials and urls needed to both stream yourself and watch streams.
|
||||
The plugin itself is relatively simple, as all it has to do is authenticate the user and give them two new buttons for starting a stream and watching streams.
|
||||
Teamspeak plugins[^ts3plugin] in general are relatively straight forward to build, as they are just dynamically linked library, exposing a specific set of functions, which are loaded at runtime by the teamspeak client.
|
||||
|
||||
# Custom Website
|
||||
TODO
|
||||
To ease the development of the plugin itself, I actually also wrote my own small rust crate that provides a nicer API for specific features.
|
||||
This includes easier creation of menus and their corresponding handlers and providing a tracing subscriber that prints out the messages to the teamspeak log, for easier debugging.
|
||||
|
||||
The authentication works very roughly, by starting a local webserver and providing a link to my auth server.
|
||||
This is a simple OAuth flow, where the user gets redirected to the local webserver of the plugin, allowing it to get all the information it needs.
|
||||
This information includes both the endpoint to stream to and a secret key, which gets used together with the users ID to generate the stream-key, which authenticates them against OvenMediaEngine.
|
||||
|
||||
Starting streaming is relatively simple, the plugin uses the secret key and the users current teamspeak ID and generates the stream-key to use in OBS or other programs.
|
||||
For manual usage, the plugin outputs the streaming endpoint and the stream-key in a message to the user itself.
|
||||
For automatic usage, the plugin tries to launch OBS and connect to it over the websocket server, if that fails it will just ignore the failure, but if it can connect it will automatically update the streaming settings to the correct information.
|
||||
|
||||
Watching is the easiest part, as the plugin just launches the bundled custom viewer, with the teamspeak ID of the person they want to watch and the secret-key.
|
||||
The custom viewer will then take care of the authentication for watching the stream and actually begin the video player, which is actually using the provided player implementation of OvenMediaEngine.
|
||||
|
||||
## Custom Viewer
|
||||
The custom viewer, is based on a react site statically bundled into an electron app.
|
||||
I chose this approach, as OvenMediaEngine provides a library for embedding a stream in a website, which handles all the player and related logic making it very easy to use.
|
||||
|
||||
One detail is that it requires some extra information provided as command line arguments, like the ID of the stream to watch and a secret key to authenticate against OvenMediaEngine.
|
||||
|
||||
# Limitations
|
||||
Although the current system works, there are still quite a bit of short comings.
|
||||
|
||||
## IPv6 only
|
||||
OvenMediaEngine uses direct UDP streams between the server and the viewers, thus the server requires a publicly reachable address.
|
||||
However in my homelab setup, where this is hosted, I natively only have public IPv6 address and no public IPv4, meaning that only people with IPv6 can use the current setup.
|
||||
|
||||
This could be solved by either getting ahold of some public IPv4 addresses for my servers (very unlikely) or I could maybe use some of the more advanced features of OvenMediaEngine, which includes streaming to "satelite" servers, which then serve the viewers.
|
||||
Basically instead of the source server streaming to the viewers directly, it forwards the stream to these other servers, which then forward it to the viewers.
|
||||
This would allow me to have an external server with both IPv4 and IPv6, which can basically proxy all the streams, but it is unlikely that I will do this as it would incur more costs and maintenance for limited benefits.
|
||||
|
||||
## OBS not bundled
|
||||
Everything for using the plugin to watch streams is already bundled in the single installer, to make it easy to use for everyone.
|
||||
However I have not found a good way to achieve this with OBS and especially certain OBS settings.
|
||||
Like I mentioned before, the websocket server that the plugin is using to configure OBS, has to be enabled before the plugin can actually use it, all parameters about the websocket server can be configured via the cli apart from actually enabling it.
|
||||
This means, that users have to start OBS manually at least once to enable the websocket server, before the plugin can programmatically interact with OBS, which just creates some really unexpected pain-points in my experience.
|
||||
|
||||
## Stream IDs can become mismatched
|
||||
Currently the plugin is just using the teamspeak ID of a user as its stream ID, as that makes it very easy to determine the stream ID of anyone a user wants to watch.
|
||||
However the teamspeak ID changes whenever a user disconnects from the teamspeak server, so if someone:
|
||||
1. starts a stream
|
||||
2. disconnects
|
||||
3. reconnects
|
||||
|
||||
Then noone will be able to view their currently running stream, as it was started under their old teamspeak ID which has now changed.
|
||||
|
||||
# Conclusion
|
||||
TODO
|
||||
Overall I am pretty happy with the setup and plugin right now.
|
||||
Obviously there are things to improve and its kind of hard to explain to someone using it for the first time, but once everything has been setup once, it actually works suprisingly well.
|
||||
The main issue currently is the limitation to IPv6, as I have friends whose internet provider does not give them an IPv6 address for some reason, meaning they cannot use the streaming.
|
||||
|
||||
Apart from that I would generally like to improve the viewer itself some more, as it is currently just the bare minimum to get something working, but I have a couple of ideas to improve the viewing experience.
|
||||
And I would really love to properly open-source the project, however since the plugin currently has the auth server hardcoded into it, it would not really benefit anyone, but I am thinking about how I might be able to remove the blockers for open-source.
|
||||
|
||||
# References
|
||||
[^OvenMediaEngine]: [OvenMediaEngine](https://ovenmedia.com/)
|
||||
[^OBS]: [OBS](https://obsproject.com/de)
|
||||
[^ts3plugin]: [Teamspeak3 Plugin SDK](https://github.com/teamspeak/ts3client-pluginsdk)
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
</svg>
|
||||
|
||||
{% if page.date %}Published: {{ page.date | date(format="%F") }}{% endif %}
|
||||
|
||||
{% if page.taxonomies.series %}
|
||||
<br>
|
||||
{% set series_name = page.taxonomies.series | first %}
|
||||
{% set series_term = get_taxonomy_term(kind="series", term=series_name) %}
|
||||
|
||||
Part {{ page.extra.series_order }} of the <a href="{{ series_term.permalink }}">{{ series_name }}</a> series
|
||||
{% endif %}
|
||||
{% endmacro meta %}
|
||||
|
||||
{% macro page_in_list(page) %}
|
||||
|
||||
20
templates/page.html
Normal file
20
templates/page.html
Normal file
@@ -0,0 +1,20 @@
|
||||
{% extends "radion/templates/page.html" %}
|
||||
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
|
||||
{% if page.taxonomies.series %}
|
||||
{% set series_name = page.taxonomies.series | first %}
|
||||
{% set series_term = get_taxonomy_term(kind="series", term=series_name) %}
|
||||
|
||||
<div class="series">
|
||||
<a href="{{ series_term.permalink }}">
|
||||
Series: {{ series_term.name }}
|
||||
</a>
|
||||
|
||||
{% if page.extra.series_order %}
|
||||
<span> · Part {{ page.extra.series_order }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock header %}
|
||||
29
templates/series/list.html
Normal file
29
templates/series/list.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block title %}Browse Series{% endblock title %}
|
||||
{% block og_title %}Browse Series{% endblock og_title %}
|
||||
{% block twitter_title %}Browse Series{% endblock twitter_title %}
|
||||
|
||||
{% block meta_description %}
|
||||
<meta name="description" content="Discover articles organized by series in this site index." />
|
||||
<meta property="og:description" content="Discover articles organized by series in this site index." />
|
||||
<meta property="twitter:description" content="Discover articles organized by series in this site index." />
|
||||
{% endblock meta_description %}
|
||||
|
||||
{% block content %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% endblock header %}
|
||||
|
||||
<h1>Series</h1>
|
||||
|
||||
{% if terms %}
|
||||
<ul>
|
||||
{% for term in terms %}
|
||||
<li>
|
||||
<a href="{{ term.permalink | safe }}">{{ term.name }}</a>({{ term.pages | length }})
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
||||
23
templates/series/single.html
Normal file
23
templates/series/single.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% extends "index.html" %}
|
||||
|
||||
{% block title %}Posts in "{{ term.name }}"{% endblock title %}
|
||||
{% block og_title %}Posts in "{{ term.name }}"{% endblock og_title %}
|
||||
{% block twitter_title %}Posts in "{{ term.name }}"{% endblock twitter_title %}
|
||||
|
||||
{% block meta_description %}
|
||||
<meta name="description" content="Discover articles from the '{{ term.name }}' series in this site index." />
|
||||
<meta property="og:description" content="Discover articles from the '{{ term.name }}' series in this site index." />
|
||||
<meta property="twitter:description" content="Discover articles from the '{{ term.name }}' series in this site index." />
|
||||
{% endblock meta_description %}
|
||||
|
||||
{% block content %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% endblock header %}
|
||||
|
||||
<h1>{{ term.name }} - Series</h1>
|
||||
|
||||
{% for page in term.pages %}
|
||||
{{ post_macros::page_in_list(page=page) }}
|
||||
{% endfor %}
|
||||
{% endblock content %}
|
||||
@@ -13,6 +13,7 @@ taxonomies = [
|
||||
# You can enable/disable RSS
|
||||
{ name = "categories", feed = true },
|
||||
{ name = "tags", feed = true },
|
||||
{ name = "series", feed = true },
|
||||
]
|
||||
|
||||
author = "Leon"
|
||||
@@ -37,6 +38,7 @@ taxonomies = [
|
||||
# You can enable/disable RSS
|
||||
{ name = "categories", feed = true },
|
||||
{ name = "tags", feed = true },
|
||||
{ name = "series", feed = true },
|
||||
]
|
||||
|
||||
radion_title = "Leon's Personal Blog"
|
||||
@@ -44,6 +46,7 @@ radion_menu = [
|
||||
{ url = "$BASE_URL", name = "Home" },
|
||||
{ url = "$BASE_URL/categories", name = "Categories" },
|
||||
{ url = "$BASE_URL/tags", name = "Tags" },
|
||||
{ url = "$BASE_URL/series", name = "Series" },
|
||||
]
|
||||
theme = "toggle" # options: {light, dark, auto, toggle}
|
||||
toc = true
|
||||
|
||||
Reference in New Issue
Block a user