Jellyfin ombouwen tot jaren 90 videotheek met Halcyon
Transformeer uw Jellyfin-bibliotheek naar een virtuele videotheek uit de jaren 90 met Halcyon. Ontdek de benodigde Docker-commando's, proxy-instellingen en de beperkingen van deze tool.
Wat Halcyon doet met uw Jellyfin-bibliotheek
Halcyon Video geeft uw Jellyfin-bibliotheek weer als een beloopbare videotheek uit de jaren 90 in de browser. Elke film die u bezit, wordt een hoesje in een schap. U loopt onder de tl-verlichting door de gangpaden, pakt een doosje uit het schap, draait het om om de specificaties op de achterkant te lezen en brengt het naar de balie om het afspelen te starten. Het afspelen rapporteert de start, voortgang en stop terug naar Jellyfin, zodat hervattingspunten en de kijkgeschiedenis correct blijven.
Halcyon leest een bestaande Jellyfin-server via de Jellyfin API en houdt geen eigen bibliotheek bij. Deze handleiding gaat ervan uit dat Jellyfin al draait en correct scant. Als dat niet het geval is, stel dan eerst Jellyfin in als mediaserver op een VPS in en keer terug zodra uw bibliotheek er correct uitziet in de normale webclient. Dit is het type software dat u installeert omdat de bibliotheek al aanwezig is, niet omdat u een extra service nodig had op uw lijst met zelfgehoste diensten.
Het project is GPL-3.0 en geschreven door één persoon, en de README stelt duidelijk dat er geen pull requests worden geaccepteerd. De ontwikkeling gaat snel en er is geen tweede beheerder om een regressie op te vangen, dus zet de image-versie vast voordat u de winkel aan iemand anders laat zien. De laatste sectie behandelt hoe u dit doet.
Waar vindt de rendering plaats?
In de browser. Halcyon is een Vite- en TypeScript-applicatie gebouwd op three.js, een JavaScript-bibliotheek die 3D-graphics tekent via WebGL (web graphics library, de interface van de browser naar de GPU). De geometrie van de winkel en de box art worden samengesteld door de machine die het scherm aanstuurt.
De container doet zeer weinig. Deze voert npm run serve uit, wat vite preview --port 1420 --strictPort --host is, en serveert de gebouwde bestanden plus enkele kleine middleware-routes. Halcyon voegt geen transcoding toe en draait geen engine op de server.
De vraag over de GPU is dus voorbehouden aan de client. Een kleine VPS serveert dit zonder problemen, omdat serveren in dit geval neerkomt op statische bestanden via HTTP. De laptop, tablet of televisie die de browser draait, bepaalt of de winkel soepel beweegt of hapert.
Eén functie doorbreekt die regel. Remote Play start headless Chromium-instanties op de server en streamt de gerenderde winkel naar een telefoon of set-top box via WebRTC (web real time communication). Dat pad rendert op de server, standaard beperkt tot twee instanties en aanpasbaar met REMOTE_PLAY_MAX_INSTANCES. Zonder een gemapt /dev/dri-apparaat renderen die instanties op de CPU, waardoor een VPS met twee cores de impact van elke extra kijker direct merkt.
Wat de winkel leest uit uw bibliotheek
De gangpaden zijn afkomstig uit de eigen structuur van Jellyfin. Halcyon deelt secties in op basis van uw bibliotheken en genres, en groepeert vervolgdelen uit uw BoxSets. De specificaties die op de achterkant van elk doosje staan, zijn afkomstig uit de MediaStreams-metadata die Jellyfin al bevat. Dit betekent dat alles wat in Jellyfin ontbreekt, ook op de plank ontbreekt.
Hierdoor is de winkel een getrouwe weergave van uw metadata. Een bibliotheek die wordt gevoed door een arr-stack in Docker Compose, inclusief artwork en ingevulde genres, ziet er hier aanzienlijk beter uit dan een map met losse bestanden en generieke namen. Fotobibliotheken zijn op dezelfde manier afhankelijk van de indexering; dit is belangrijk om in gedachten te houden wanneer u PhotoPrism versus Immich afweegt voor de foto's die op dezelfde server staan.
Probeer de videowinkel-demo voordat u iets installeert
Het project publiceert de volledige winkel die draait op een synthetische bibliotheek via de gehoste demo. Het toevoegen van ?demo=1 aan elke Halcyon-URL doet hetzelfde op uw eigen implementatie.
Gebruik dit als een hardwaretest. De demo-bibliotheek bevat ongeveer 2.000 titels en vereist ruwweg 2 GB aan browsergeheugen, wat zwaarder is dan de meeste persoonlijke bibliotheken. Als de demo hapert op het apparaat dat u wilt gebruiken, zal uw eigen bibliotheek ook haperen. De oplossing is in dat geval de 2.5D-modus die hieronder wordt beschreven, in plaats van een krachtigere VPS.
Uitvoeren met Docker
Dit is het commando dat de upstream-documentatie voorschrijft.
docker run -d --name halcyon --network host --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-videoControleer vervolgens of de service is opgestart.
docker logs halcyon
curl -I http://127.0.0.1:1420Het logbestand moet tonen dat de preview-server luistert op poort 1420 en dat curl antwoordt op HTTP/1.1 200 OK. Een container die binnen enkele seconden afsluit, heeft bijna altijd een poortconflict. --strictPort betekent dat de server weigert uit te wijken naar 1421 wanneer 1420 bezet is, waardoor deze stopt.
--network host is bedoeld voor Remote Play, niet voor de store. WebRTC moet het werkelijke adres van de machine adverteren aan het apparaat dat de stream wil ontvangen. Achter de standaard Docker-bridge kent de container alleen zijn eigen 172.x-adres, dat onbereikbaar is voor telefoons in uw netwerk; hierdoor komt de streamverbinding niet tot stand. Als u de store alleen in een browser wilt gebruiken, publiceer dan de poort.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-videoDit is de betere standaardinstelling op een VPS, omdat host-networking de container op elke interface van de machine plaatst, inclusief de publieke interface. Docker draaien op een VPS behandelt de rest van deze afweging. --restart unless-stopped zorgt ervoor dat de store na een herstart weer beschikbaar is; dit werkt volgens hetzelfde principe als Compose-services die opstarten bij boot.
Het klonen van de repository en het uitvoeren van docker compose up -d bouwt de image lokaal. Het meegeleverde Compose-bestand bouwt standaard vanuit de broncode en bevat de regel image: als commentaar; verwijder het commentaarteken als u de gepubliceerde image wilt gebruiken in Compose.
Eén harde beperking per augustus 2026: de gepubliceerde image is uitsluitend linux/amd64. Het arm64-gedeelte van de multi-architectuur-push is mislukt onder emulatie en wacht op native arm-runners. Op een arm64 VPS mislukt de pull met no matching manifest for linux/arm64/v8 in the manifest list entries; in dat geval is bouwen vanuit de clone de enige oplossing.
Verwijs naar uw Jellyfin-server
Open http://<host>:1420 en log in met het adres, de gebruikersnaam en het wachtwoord van uw Jellyfin-server. Het bestand .env.local.example in de repository is uitsluitend bedoeld voor lokale ontwikkeling. Vite stelt variabelen met het voorvoegsel VITE_ beschikbaar aan client-side code; een Jellyfin-wachtwoord dat daar wordt ingevuld, wordt gecompileerd in de JavaScript-bundel die elke bezoeker downloadt. Log op een server die voor anderen toegankelijk is in via de interface.
De browser communiceert rechtstreeks met Jellyfin. De container van Halcyon fungeert niet als proxy voor de Jellyfin API, en dat heeft twee gevolgen die u moet kennen voordat u begint met debuggen.
Ten eerste moet Jellyfin bereikbaar zijn vanuit de browser, niet alleen vanaf de VPS die Halcyon host. Een Jellyfin-instantie die is gebonden aan 127.0.0.1:8096 is prima voor een lokale test, maar zorgt ervoor dat de schappen voor anderen leeg blijven.
Ten tweede betreft het een cross-origin verzoek, van het adres van Halcyon naar dat van Jellyfin. Jellyfin beantwoordt API-verzoeken standaard met Access-Control-Allow-Origin: *, waardoor het zonder extra configuratie werkt. Als u deze instelling heeft beperkt, of een authenticatie-proxy voor de Jellyfin API heeft geplaatst, rapporteert de browserconsole blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource en laadt de winkel met lege schappen.
Plaats het achter een reverse proxy, met authenticatie aan de voorzijde
vite preview is een preview-server. Deze beëindigt geen TLS (transport layer security) en beschikt niet over eigen toegangscontrole; plaats deze daarom achter nginx of Caddy als de server publiek toegankelijk is.
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;
}
}Een domeinnaam voor de container vereist een extra instelling. Halcyon reageert op localhost, ruwe IP-adressen en de namen van de machine waarop het draait, als beveiliging tegen DNS-rebinding. Binnen een container is de machine waarop het draait de container zelf, dus de hostname is niet die van u. Een verzoek dat binnenkomt als halcyon.example.com wordt geweigerd en het antwoord vermeldt de host die werd geweigerd. Voeg die naam toe.
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-videoDe waarde is door komma's gescheiden, een voorafgaande punt zoals .example.com komt overeen met subdomeinen en all schakelt de controle uit. Gebruik all alleen op een machine die vanaf buitenaf niet bereikbaar is.
Zodra de store via https:// wordt geserveerd, moet het Jellyfin-adres dat u bij het inloggen invoert ook https:// zijn. Een browser blokkeert een standaard http:// API-aanroep die vanaf een HTTPS-pagina wordt gedaan en de console geeft Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource aan. Het inloggen mislukt simpelweg, zonder uitleg binnen Halcyon. Serveer beide via TLS, of houd beide op standaard HTTP binnen een privénetwerk.
Vervolgens authenticatie. De store vraagt om Jellyfin-inloggegevens, dus een vreemde die de URL vindt, krijgt een inlogscherm te zien. Eén functie verandert dat. Het inschakelen van Remote Play, onder Settings en vervolgens Connection, stelt uw Jellyfin-sessie beschikbaar aan de server, zodat bezoekers van /remote.html hun eigen instantie van uw echte bibliotheek krijgen. Dat is het doel van de functie, en het betekent dat de geheimhouding van de URL de enige barrière is tussen het internet en uw films. Als u Remote Play inschakelt, plaats dan single sign-on voor de gehele site met Authentik als een self-hosted SSO-gateway, of laat de publieke hostname vallen en benader de store via een WireGuard-tunnel beheerd met wg-easy.
Twee details zijn hierbij van belang. De reverse proxy transporteert alleen de store: de Remote Play-stream is WebRTC via UDP en loopt niet via een HTTP-proxy, dus deze heeft een eigen pad nodig op 3478/udp en op 49200 tot 49260/udp wanneer de meegeleverde TURN-relay in gebruik is. En de standaard docker run hierboven behoudt geen volume, dus de Remote Play-seed overleeft docker rm niet. Het Compose-bestand mount om precies die reden een halcyon-data-volume op /data en stelt REMOTE_PLAY_SEED in op /data/remote-play-seed.json.
Wat te doen als de store slecht presteert
Halcyon rendert op aanvraag. Een inactieve store berekent geen frames, en het verliezen van de focus op het venster stopt de animatieloop; daarom verbruikt een openstaand tabblad de accu van een laptop niet. Dit helpt een machine die net aan de eisen voldoet. Het doet niets voor een machine die de store helemaal niet kan tekenen.
Voor dergelijke clients is er een 2.5D-modus, bestaande uit standaard HTML en CSS zonder WebGL, bedoeld voor hardware zo klein als een Raspberry Pi. U schakelt tussen 3D en 2.5D via de instellingen of het powermenu zonder de pagina te herladen, waardoor het testen van beide op hetzelfde apparaat slechts seconden duurt. Wees realistisch over het resultaat: de auteur omschrijft de platte modus als ruw en nog in ontwikkeling. Beschouw het als een fallback voor zwakke clients.
Wanneer een client te beperkt is voor de 3D-store, is de foutmelding duidelijk. Het tabblad herlaadt zichzelf, of de browser meldt een verloren WebGL-context, meestal terwijl de schappen nog worden gevuld. Schakel dat apparaat over naar 2.5D in plaats van uw bibliotheek in te korten.
Pin de image en controleer deze voordat u een pull uitvoert
Neem dit onderdeel serieus. Tags v0.1.0 tot en met v0.3.1 verschenen allemaal binnen enkele dagen na elkaar, en v0.2.1 bestaat alleen omdat de image-push voor v0.2.0 mislukte. Bugrapporten zijn welkom bij de upstream-ontwikkelaars, patches niet; de release-stream is dus de actuele werkstatus van één persoon.
Het uitvoeren van latest met de gewoonte om docker pull te gebruiken, betekent dat de store op een willekeurige dinsdag onder uw voeten kan veranderen. Pin de image op basis van de digest, de enige referentie die niet kan wijzigen.
docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1Dit commando toont de digest achter de tag. Gebruik deze in plaats van de tag.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video@sha256:747dcc821a3d2fa318b50e76024783c1835609047e84f502e23d021bc1898b20Deze digest was 0.3.1 op 10 augustus 2026. Lees zelf de actuele digest in plaats van deze te kopiëren en lees de release notes voordat u een update uitvoert, omdat een patch-release hier zowel wijzigingen in de store-indeling als bugfixes kan bevatten.
FAQ
Heeft Halcyon een GPU nodig op mijn VPS?
Niet voor normaal gebruik. De winkel wordt door three.js in de browser getekend, dus de client-machine verzorgt de rendering en de container serveert enkel statische bestanden op poort 1420. De uitzondering is Remote Play, dat headless Chromium op de server draait en het resultaat streamt. Dat pad rendert op de CPU, tenzij u /dev/dri toewijst aan de container voor hardwareversnelling.
Kan ik Halcyon op het publieke internet plaatsen?
Alleen achter authenticatie. De winkel vraagt om Jellyfin-inloggegevens, maar het inschakelen van Remote Play stelt uw Jellyfin-sessie beschikbaar aan de server. Iedereen die /remote.html laadt, krijgt dus een instantie van uw echte bibliotheek zonder in te loggen. Plaats een reverse proxy met single sign-on voor de applicatie, of houd de hostnaam uit de publieke DNS en benader de winkel via een VPN.
Waarom zijn de schappen leeg nadat ik ben ingelogd?
De browser roept de Jellyfin API rechtstreeks aan, dus Jellyfin moet bereikbaar zijn vanuit de browser en niet alleen vanaf de VPS. Open de browserconsole. blocked by CORS policy betekent dat Jellyfin het verzoek vanaf het adres van Halcyon niet accepteert. Een Mixed Content-melding betekent dat de pagina via HTTPS wordt geladen, terwijl het ingevoerde Jellyfin-adres plain HTTP is.
Heb ik --network host nodig?
Alleen voor Remote Play. WebRTC moet het werkelijke adres van de machine adverteren, en achter de Docker-bridge kan de container alleen een 172.x-adres aanbieden dat geen enkele telefoon op uw netwerk kan bereiken. Voor het browsen door de winkel in een browser volstaat -p 1420:1420, wat aanzienlijk minder van de host blootstelt.
Welke image-tag moet ik gebruiken?
Pin een digest in plaats van latest. Lees de digest voor een versie met docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1, voer die digest uit en stap pas over na het lezen van de release notes. Sinds augustus 2026 is de gepubliceerde image alleen linux/amd64, dus een arm64-host moet bouwen vanaf de clone met docker compose up -d.