Halcyon: Jellyfin als Videothek der 90er nutzen
Halcyon macht Ihre Jellyfin-Bibliothek zur begehbaren Videothek im Browser. Hier finden Sie Docker-Befehl, Reverse-Proxy-Konfiguration und wichtige Einschränkungen.
Was Halcyon mit Ihrer Jellyfin-Bibliothek macht
Halcyon Video stellt Ihre Jellyfin-Bibliothek im Browser als begehbaren Videoladen der 1990er-Jahre dar. Jeder Film in Ihrer Bibliothek wird zu einer Hülle in einem Regal. Sie gehen unter den Leuchtstoffröhren durch die Gänge, nehmen eine Hülle aus dem Regal, drehen sie um, lesen die technischen Angaben auf der Rückseite und bringen sie zum Tresen, um die Wiedergabe zu starten. Der Wiedergabestatus mit Start, Fortschritt und Stopp wird an Jellyfin zurückgemeldet. Dadurch bleiben Fortsetzungspunkte und Wiedergabeverlauf korrekt.
Halcyon liest einen vorhandenen Jellyfin-Server über die Jellyfin API und führt keine eigene Bibliothek. Diese Anleitung setzt voraus, dass Jellyfin bereits läuft und die Bibliothek fehlerfrei scannt. Falls das nicht der Fall ist, richten Sie zuerst Jellyfin als Medienserver auf einem VPS ein und kehren Sie zurück, sobald Ihre Bibliothek im normalen Webclient korrekt angezeigt wird. Sie installieren diesen Dienst, weil die Bibliothek bereits vorhanden ist, nicht weil Sie einen weiteren Dienst auf Ihrer Self-Hosting-Liste benötigen.
Das Projekt steht unter der GPL-3.0-Lizenz und wird von einer einzigen Person entwickelt. In der README steht ausdrücklich, dass keine Pull Requests akzeptiert werden. Die Entwicklung geht schnell voran, und es gibt keinen zweiten Maintainer, der Regressionen auffangen kann. Legen Sie daher die Image-Version fest, bevor Sie den Videoladen anderen Personen zeigen. Der letzte Abschnitt beschreibt, wie das geht.
Wo findet das Rendering statt?
Im Browser. Halcyon ist eine Vite- und TypeScript-Anwendung auf Basis von three.js, einer JavaScript-Bibliothek, die 3D-Grafiken über WebGL (Web Graphics Library, die Schnittstelle des Browsers zur GPU) zeichnet. Die Geometrie des Stores und die Grafiken der Verpackungen werden von dem Gerät zusammengesetzt, an das der Bildschirm angeschlossen ist.
Der Container übernimmt nur sehr wenig. Er führt npm run serve aus, also vite preview --port 1420 --strictPort --host, und stellt die erstellten Dateien sowie einige kleine Middleware-Routen bereit. Halcyon führt keine Transcodierung durch und keine Engine auf dem Server aus.
Die Frage nach der GPU betrifft daher den Client. Ein kleiner VPS kann diese Aufgabe problemlos übernehmen, weil dabei lediglich statische Dateien über HTTP bereitgestellt werden. Ob sich der Store flüssig bewegt oder ruckelt, hängt vom Laptop, Tablet oder Fernseher ab, auf dem der Browser läuft.
Eine Funktion durchbricht diese Regel. Remote Play startet auf dem Server Headless-Chromium-Instanzen und überträgt den gerenderten Store per WebRTC (Web Real-Time Communication) an ein Smartphone oder eine Set-Top-Box. Bei diesem Pfad findet das Rendering auf dem Server statt. Standardmäßig ist die Anzahl auf zwei Instanzen begrenzt und lässt sich mit REMOTE_PLAY_MAX_INSTANCES anpassen. Ohne ein zugeordnetes /dev/dri-Gerät rendern diese Instanzen auf der CPU. Auf einem VPS mit zwei Kernen macht sich daher jeder zusätzliche Zuschauer bemerkbar.
Was das Geschäft aus Ihrer Bibliothek ausliest
Die Gänge übernehmen Jellyfins eigene Struktur. Halcyon erstellt die Bereiche anhand Ihrer Bibliotheken und Genres und gruppiert Fortsetzungen aus Ihren BoxSets. Die technischen Angaben auf der Rückseite jeder Hülle stammen aus den MediaStreams-Metadaten, die Jellyfin bereits gespeichert hat. Alles, was in Jellyfin fehlt, fehlt daher auch im Regal.
Damit bildet das Geschäft Ihre Metadaten realistisch ab. Eine Bibliothek, die von einem arr-Stack in Docker Compose mit bereits hinterlegten Grafiken und Genres versorgt wird, sieht hier deutlich besser aus als ein Ordner mit einzelnen Dateien und generischen Namen. Fotobibliotheken hängen ebenfalls von der Software ab, die sie indexiert hat. Das sollten Sie berücksichtigen, wenn Sie PhotoPrism mit Immich vergleichen und die Fotos auf demselben Server verwalten wollen.
Testen Sie die Videoshop-Demo, bevor Sie etwas installieren
Das Projekt stellt den vollständigen Shop mit einer synthetischen Bibliothek unter der gehosteten Demo bereit. Wenn Sie an eine beliebige Halcyon-URL ?demo=1 anhängen, erhalten Sie dasselbe Verhalten in Ihrer eigenen Bereitstellung.
Verwenden Sie die Demo als Hardwaretest. Die Demo-Bibliothek enthält etwa 2,000 Titel und benötigt ungefähr 2 GB Browserspeicher. Damit ist sie anspruchsvoller als die meisten persönlichen Bibliotheken. Wenn die Demo auf dem Gerät ruckelt, von dem aus Sie die Bibliothek durchsuchen möchten, wird auch Ihre eigene Bibliothek ruckeln. Die Lösung ist dann der weiter unten beschriebene 2.5D-Modus und nicht ein größeres VPS.
Mit Docker ausführen
Dies ist der vom Projekt dokumentierte Befehl.
docker run -d --name halcyon --network host --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-videoPrüfen Sie anschließend, ob der Dienst gestartet ist.
docker logs halcyon
curl -I http://127.0.0.1:1420Das Log sollte zeigen, dass der Preview-Server auf Port 1420 lauscht und curl mit HTTP/1.1 200 OK antwortet. Ein Container, der innerhalb weniger Sekunden beendet wird, scheitert fast immer am Port. --strictPort bedeutet, dass der Server nicht automatisch auf 1421 ausweicht, wenn 1420 bereits belegt ist, und deshalb beendet wird.
--network host wird für Remote Play benötigt, nicht für den Store. WebRTC muss dem Gerät, das den Stream anfordert, die echte Adresse des Rechners mitteilen. Hinter der standardmäßigen Docker-Bridge kennt der Container nur seine eigene 172.x-Adresse. Kein Telefon in Ihrem Netzwerk kann diese Adresse erreichen, daher wird die Verbindung zum Stream nie hergestellt. Wenn Sie den Store nur in einem Browser verwenden möchten, veröffentlichen Sie stattdessen den Port.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-videoDas ist auf einem VPS die bessere Standardeinstellung, weil das Host-Netzwerk den Container an jede Schnittstelle des Rechners bindet, einschließlich der öffentlichen Schnittstelle. Docker auf einem VPS ausführen behandelt die übrigen Auswirkungen dieser Entscheidung. --restart unless-stopped sorgt dafür, dass der Store nach einem Reboot wieder gestartet wird, nach demselben Prinzip wie Compose-Dienste, die beim Booten starten.
Wenn Sie das Repository klonen und docker compose up -d ausführen, wird das Image stattdessen lokal erstellt. Die versionierte Compose-Datei erstellt standardmäßig das Image aus dem Quellcode und enthält die auskommentierte image:-Zeile für das vorgefertigte Image. Entfernen Sie den Kommentar vor dieser Zeile, wenn Sie unter Compose das veröffentlichte Image verwenden möchten.
Eine feste Einschränkung gilt im August 2026: Das veröffentlichte Image ist ausschließlich für linux/amd64 verfügbar. Der arm64-Teil des Multi-Architecture-Pushs ist bei der Emulation fehlgeschlagen und wartet auf native arm-Runner. Auf einem arm64-VPS schlägt der Pull mit no matching manifest for linux/arm64/v8 in the manifest list entries fehl. In diesem Fall ist das Erstellen aus dem geklonten Repository der geeignete Weg.
Auf Ihren Jellyfin-Server verweisen
Öffnen Sie http://<host>:1420 und melden Sie sich mit der Adresse Ihres Jellyfin-Servers, Ihrem Benutzernamen und Ihrem Passwort an. Die Datei .env.local.example im Repository ist nur für die lokale Entwicklung vorgesehen. Vite stellt Variablen mit dem Präfix VITE_ dem clientseitigen Code zur Verfügung. Ein dort eingetragenes Jellyfin-Passwort wird daher in das JavaScript-Bundle kompiliert, das jeder Besucher herunterlädt. Auf einem Server, der für andere erreichbar ist, melden Sie sich über die Benutzeroberfläche an.
Der Browser kommuniziert direkt mit Jellyfin. Der Container von Halcyon leitet die Jellyfin-API nicht als Proxy weiter. Daraus ergeben sich zwei wichtige Konsequenzen, die Sie vor der Fehlersuche kennen sollten.
Erstens muss Jellyfin vom Browser aus erreichbar sein, nicht nur von dem VPS, der Halcyon bereitstellt. Ein Jellyfin, das an 127.0.0.1:8096 gebunden ist, eignet sich für einen lokalen Test. Für alle anderen bleiben die Regale leer.
Zweitens ist der Aufruf Cross-Origin: Er geht von der Adresse von Halcyon zur Adresse von Jellyfin. Jellyfin beantwortet API-Anfragen standardmäßig mit Access-Control-Allow-Origin: *. Daher funktioniert der Aufruf ohne zusätzliche Konfiguration. Wenn Sie diese Einstellung eingeschränkt oder einen Authentifizierungs-Proxy vor die Jellyfin-API gesetzt haben, meldet die Browserkonsole blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource, und der Store wird mit leeren Regalen geladen.
Stellen Sie es hinter einen Reverse Proxy und setzen Sie eine Authentifizierung davor
vite preview ist ein Vorschau-Server. Er terminiert kein TLS (Transport Layer Security) und verfügt über keine eigene Zugriffskontrolle. Bei öffentlicher Erreichbarkeit gehört er deshalb hinter nginx oder Caddy.
server {
listen 443 ssl;
server_name halcyon.example.com;
location / {
proxy_pass http://127.0.0.1:1420;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Vor dem Container benötigt ein Domainname eine weitere Einstellung. Halcyon antwortet auf localhost, rohe IP-Adressen und die Namen des Systems, auf dem es ausgeführt wird. Damit schützt es sich vor DNS-Rebinding. In einem Container ist das System, auf dem Halcyon ausgeführt wird, der Container. Sein Hostname ist daher nicht Ihr Hostname. Eine Anfrage an halcyon.example.com wird abgewiesen. Die Antwort nennt den abgewiesenen Host. Fügen Sie diesen Namen hinzu.
docker run -d --name halcyon -p 127.0.0.1:1420:1420 --restart unless-stopped \
-e HALCYON_ALLOWED_HOSTS=halcyon.example.com \
ghcr.io/halcyon-video/halcyon-videoDer Wert ist durch Kommas getrennt. Ein führender Punkt wie .example.com gilt auch für Subdomains. all deaktiviert die Prüfung. Verwenden Sie all nur auf einem System, das von außerhalb nicht erreichbar ist.
Sobald der Store über https:// bereitgestellt wird, muss auch die Jellyfin-Adresse, die Sie bei der Anmeldung eingeben, https:// lauten. Ein Browser blockiert einen nicht verschlüsselten http://-API-Aufruf von einer HTTPS-Seite. Die Konsole zeigt Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource an. Die Anmeldung schlägt dann einfach fehl, ohne dass Halcyon eine Erklärung liefert. Stellen Sie beide Dienste über TLS bereit. Alternativ können Sie beide innerhalb eines privaten Netzwerks über unverschlüsseltes HTTP betreiben.
Nun zur Authentifizierung. Der Store fordert Jellyfin-Anmeldedaten an. Eine fremde Person, die die URL findet, sieht daher eine Anmeldeseite. Eine Funktion ändert dieses Verhalten. Wenn Sie Remote Play unter Settings und anschließend Connection aktivieren, übergibt Ihre Jellyfin-Sitzung dem Server Ihre Bibliothek. Besucher von /remote.html erhalten dadurch eine eigene Instanz Ihrer tatsächlichen Bibliothek. Genau dafür ist diese Funktion vorgesehen. Damit hängt die Sicherheit Ihrer Filme allein von der Geheimhaltung der URL ab. Wenn Sie Remote Play aktivieren, setzen Sie Single Sign-On vor die gesamte Website, beispielsweise mit Authentik als selbst gehostetem SSO-Gateway, oder entfernen Sie den öffentlichen Hostnamen und greifen Sie über einen mit wg-easy verwalteten WireGuard-Tunnel auf den Store zu.
Dabei sind zwei Details zu beachten. Der Reverse Proxy transportiert nur den Store. Der Remote-Play-Datenstrom verwendet WebRTC über UDP und läuft nicht über einen HTTP-Proxy. Dafür benötigt er einen eigenen Pfad über 3478/udp sowie über 49200 bis 49260/udp, wenn das enthaltene TURN-Relay verwendet wird. Außerdem speichert das einfache docker run von oben kein Volume. Der Remote-Play-Seed bleibt daher über docker rm hinweg nicht erhalten. Die Compose-Datei bindet aus genau diesem Grund ein halcyon-data-Volume unter /data ein und setzt REMOTE_PLAY_SEED auf /data/remote-play-seed.json.
Was tun, wenn der Store schlecht läuft
Halcyon rendert Inhalte bei Bedarf. Ein inaktiver Store erzeugt keine Frames. Wenn das Fenster den Fokus verliert, wird die Animationsschleife angehalten. Deshalb belastet ein geöffneter Tab den Akku eines Laptops nicht dauerhaft. Das hilft bei einem Gerät, dessen Leistung gerade noch ausreicht. Es hilft nicht bei einem Gerät, das den Store überhaupt nicht darstellen kann.
Für solche Clients gibt es einen 2.5D-Modus. Er verwendet einfaches HTML und CSS ohne WebGL und ist für Hardware bis hin zu einem Raspberry Pi vorgesehen. Zwischen 3D und 2.5D wechseln Sie in den Einstellungen oder im power menu, ohne die Seite neu zu laden. Dadurch können Sie beide Modi auf demselben Gerät innerhalb weniger Sekunden testen. Die Erwartungen sollten realistisch bleiben: Der Autor beschreibt den flachen Modus als unfertig und noch in Entwicklung. Betrachten Sie ihn als Fallback für leistungsschwache Clients.
Wenn ein Client für den 3D-Store zu leistungsschwach ist, tritt der Fehler deutlich auf. Der Tab lädt sich selbst neu, oder der Browser meldet einen verlorenen WebGL-Kontext. Das geschieht meist, während die Regale noch gefüllt werden. Stellen Sie das Gerät auf 2.5D um, anstatt Ihre Bibliothek zu verkleinern.
Das Image fest anheften und vor dem Abruf prüfen
Nehmen Sie diesen Teil ernst. Die Tags v0.1.0 bis v0.3.1 wurden innerhalb weniger Tage veröffentlicht, und v0.2.1 existiert nur, weil der Push des Images für v0.2.0 fehlgeschlagen ist. Fehlerberichte sind im Upstream willkommen, Patches jedoch nicht. Der Release-Zweig entspricht daher dem Arbeitsstand einer einzelnen Person.
Wenn Sie latest mit der Gewohnheit docker pull ausführen, kann sich der Store an jedem beliebigen Dienstag unbemerkt ändern. Verwenden Sie den Digest zum Anheften. Nur diese Referenz bleibt unverändert.
docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1Dieser Befehl gibt den Digest hinter dem Tag aus. Verwenden Sie ihn anstelle des Tags.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video@sha256:747dcc821a3d2fa318b50e76024783c1835609047e84f502e23d021bc1898b20Dieser Digest lautete am 10. August 2026 0.3.1. Ermitteln Sie den aktuellen Wert selbst, statt ihn zu kopieren. Lesen Sie außerdem die Release Notes, bevor Sie aktualisieren, da ein Patch-Release hier neben Fehlerbehebungen auch Änderungen am Store-Layout enthalten kann.
FAQ
Benötigt Halcyon eine GPU auf meinem VPS?
Für die normale Nutzung nicht. Der Store wird mit three.js im Browser dargestellt. Daher übernimmt das Client-Gerät das Rendering, während der Container nur statische Dateien auf Port 1420 bereitstellt. Eine Ausnahme ist Remote Play. Dabei wird Chromium im Headless-Modus auf dem Server ausgeführt und das Ergebnis gestreamt. Dieser Pfad rendert auf der CPU, sofern Sie nicht /dev/dri in den Container einbinden, um Hardwarebeschleunigung zu aktivieren.
Kann ich Halcyon im öffentlichen Internet bereitstellen?
Nur hinter einer Authentifizierung. Der Store fordert Jellyfin-Zugangsdaten an. Wenn Sie jedoch Remote Play aktivieren, übergeben Sie Ihre Jellyfin-Sitzung an den Server. Dadurch erhält jeder, der /remote.html lädt, ohne Anmeldung Zugriff auf eine Instanz Ihrer echten Bibliothek. Schalten Sie einen Reverse Proxy mit Single Sign-on davor. Alternativ können Sie den Hostnamen aus dem öffentlichen DNS heraushalten und den Store über ein VPN erreichen.
Warum sind die Regale leer, nachdem ich mich angemeldet habe?
Der Browser ruft die Jellyfin-API direkt auf. Daher muss Jellyfin vom Browser aus erreichbar sein und nicht nur vom VPS. Öffnen Sie die Browserkonsole. blocked by CORS policy bedeutet, dass Jellyfin die Anfrage von der Adresse von Halcyon nicht akzeptiert. Eine Mixed Content-Meldung bedeutet, dass die Seite HTTPS verwendet, während die von Ihnen eingegebene Jellyfin-Adresse reines HTTP ist.
Benötige ich --network host?
Nur für Remote Play. WebRTC muss die tatsächliche Adresse des Rechners bekannt geben. Hinter der Docker-Bridge kann der Container jedoch nur eine 172.x-Adresse anbieten, die kein Telefon in Ihrem Netzwerk erreichen kann. Für das Durchsuchen des Stores im Browser funktioniert -p 1420:1420 und legt deutlich weniger vom Host offen.
Welchen Image-Tag sollte ich verwenden?
Verwenden Sie einen Digest anstelle von latest. Lesen Sie den Digest für eine Version mit docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1, führen Sie dieses Image mit dem Digest aus und aktualisieren Sie erst, nachdem Sie die Release Notes gelesen haben. Im August 2026 ist das veröffentlichte Image ausschließlich linux/amd64. Ein arm64-Host muss daher aus dem Clone mit docker compose up -d bauen.