Laterna turns your Jellyfin server into a real cinema for the whole household: a polished interface to watch, a console to run things, in a single container.
A screening room to watch. A control room to run it.
One app, one session, the same services behind it. Everyone sees what concerns them.
Address /
Cinema
The screen everyone lands on. Every title is a poster, and everything you can do with it starts from that poster: play, resume, add to your list, request.
Full-bleed banner, rows of posters, search
Personal lists and title requests
In-page playback, draggable mini-player
Address /gestion
Management
All your services in a single console, wearing the same design as the cinema.
Downloads, service status, storage
Radarr and Sonarr libraries, calendar, statistics
Connections and settings made in the app
Two roles: admin and user. A user browses, watches, keeps lists and makes requests; changes to the underlying services belong to the admin and are checked by the server.
Pick, press, watch.
Posters, a banner, and the film starts in one tap, on a computer as on a phone.
A home screen that builds itself
Continue watching, My list, today's Top 10, recently added, one row per genre and recommendations.
One search
Your library, TMDB and people at once, sorted afterwards. Natural-language queries work.
"90s thriller"
My list
To watch, Requests and Watched. Requests update live, and a film finished on the TV shows as watched right away.
Request what's missing
A title outside the library is requested from its own page, with each person's Jellyseerr account. For a series, you pick the seasons.
Offline beta
Download a film, an episode or a whole season for the train or the plane. It plays without a connection, and the position syncs when the network is back.
On the big screen
Chromecast and AirPlay. The home screen is fully drivable with the arrow keys, which makes it usable with a TV remote.
An app on your home screen
Laterna installs to the home screen and sends notifications, iPhone and iPad included.
Generated subtitles
Optional: when a title only has subtitles in one language, an online model translates them while the film plays. Monthly budget and daily limit per account.
Four languages
English, French, Spanish and German. Audio and subtitle preferences are Jellyfin's own, shared with its other apps.
Lumen reads the file exactly as it sits on disk, repackages it in the browser tab and hands it to the device's video chip. 4K, HDR10, Dolby Vision and multichannel sound included.
0pixels re-encoded on the native path. The file's picture reaches the screen bit for bit.
15-56msto repackage a group of pictures, against about 2,500 to download it.
5audio decoders compiled to WebAssembly, loaded only when a film needs them.
7,000+automated tests, plus benches that put real files through their paces.
01Architecture
Seven parts, one path.
From the server's disk to the video chip, every stage has its own part. Bytes travel, pixels stay untouched.
Before every film, Lumen asks the browser: this format, this encoder, this Dolby Vision profile. Its answer picks the path.
AudioCore
Audio decoding
Track accepted: copied bit for bit
Otherwise: decoded in WebAssembly, then re-encoded to AAC or Opus by the browser, every channel in its place
PlaybackBridge
Playback fallback
A format out of reach or a connection too slow: Jellyfin takes over in HLS, at the same second, with the same track.
02Formats
Everything Lumen plays.
Lumen recognises the format from the file's own bytes. Depending on the device, each track is copied as is or decoded in the tab, and Jellyfin's player takes the rest.
Video
HEVCH.265 · 8 and 10 bit · up to 4K
AV18 and 10 bit
H.264AVC · everywhere
HDR
Dolby Visionprofiles 5 and 8 · native where the device accepts it
HDR10native on an HDR screen
HDR10+played as HDR10, metadata passed on
Audio
Dolby Atmosin Dolby Digital Plus, passed through intact on Safari
Dolby TrueHDdecoded · 7.1WASM
DTS-HDMaster Audio · HRWASM
DTSthe DTS coreWASM
DTS ExpressDTS-HD LBRWASM
Dolby Digital PlusE-AC-3copied or WASM
Dolby DigitalAC-3copied or WASM
FLAC16 and 24 bitcopied or WASM
AACup to 7.1copied
Opusmono to multichannelcopied
MLPlosslessWASM
5.1 · 7.1every channel in its place, in the order the device expects
10 audio familiescopied as is or decoded on the device, without going through the server
Containers and subtitles
MKVMatroska
MP4 · MOVthrough the same pipeline
SRTadjustable size and position
MP4 textan MP4's own subtitles
ASS · SSAthe text
Externalfiles next to the film
Two at oncea second subtitle above the main one, each with its own offset
Generatedtranslated while the film plays, from a complete subtitleoptional
Forcedrecognised automatically
PGS · VobSubburned in by the server when casting to Chromecastserver
Played by Jellyfin
Everything elseJellyfin's server player takes over, at the same second and with the same track
VP9videoserver
VP8videoserver
MPEG-2videoserver
MPEG-4 ASPold .avi filesserver
AVIcontainerserver
Fragmented MP4containerserver
MP3audioserver
MP2audioserver
Vorbisaudioserver
PCMuncompressed audioserver
copied handed to the browser as isWASM decoded in the tab, then re-encoded to AAC or Opusserver played by Jellyfin's server player
03Engineering
The details that make the difference.
storageIPBbb
display04213
decode01234
Decode times, reconstructed
Matroska stores only presentation times; MP4 needs decode times too. Lumen sorts each group's presentation times and deals them out in decode order: the exact timeline, whatever the reorder depth.
IDRCRABLATRAIL_R
14 of the 193 keyframes announced by a real file were not access points.
Access points, verified
Before starting or seeking, Lumen reads the frame's first NAL unit: only IDR, CRA and BLA open a segment. When the point it finds lands too far, it steps back through the index, 12 seconds at a time.
download a group≈ 2,500 ms
repackage it15-56 ms
The network sets the pace
The computing is nearly free, so Lumen shapes its requests: six-chunk readahead, chunks of one read requested together, the file's header and index read in parallel at open, stale reads dropped on every seek.
standardLRCLFELsRs
Apple AACCLRLsRsLFE
Every channel in its place
Each system orders its channels differently. Lumen hands the device's encoder the order it expects: dialogue in the centre, effects at the back, an order checked system by system.
iPhone · iPad105 MB
Chromium · Firefox150 MiB
Mac · Safari304 MB
Memory sized for each engine
Safari caps each video buffer in bytes, about thirty seconds of 4K on an iPad. Lumen counts its fill in bytes actually appended and frees memory behind the playhead itself.
0.01dBTrueHD
0.05dBFLAC
Decoders measured against FFmpeg
The embedded decoders were compared with FFmpeg channel by channel, on real files. The largest gap measured for TrueHD is a hundredth of a decibel.
A safety net
For a file the browser can't carry, Lumen hands over to Jellyfin, which chooses between direct play and transcoding. A panel shows the path taken and why. This fallback can be switched off in one setting.
Slow connection, steady playback
Lumen compares the bitrate it receives with the film's. When the link can't keep up, a lighter version is prepared in the background and the player switches to it at the same second, same language, same subtitles.
Black bars, gone
The server measures the real picture on the scrubbing thumbnails, without decoding any video, then the player enlarges the film to the edge of the screen. Only black is cropped.
Exact resumeInstant resume from the deviceChaptersScrubbing previewsSkip introNext episode preloadedLanguage switch at the same positionTwo subtitles at onceDialogue enhancementPlayback speed
Desktop and phone each get their own interface, with the same navigation.
On a large screen
A rail on the left that opens on hover, a banner that follows the title you land on, a grid you can drive with the keyboard.
On a phone
A bottom bar, a carousel of posters, sheets you swipe away and haptic feedback on Android.
Back goes back one screen
Every open screen lives in the address: the browser's Back button or an edge swipe returns to the previous screen, and a title's page can be shared by its link.
Play+ My list
ResumeStart from the beginning
Your whole install, at a glance.
What's running, what's arriving, what's stuck: the admin's dashboard.
Radarr and Sonarr views as a grid or a list, filters, sorting by rating, release search and adding series, all inside the app.
Activity and health
Active accounts, sessions playing now, the week's errors, recent imports, and a status page that shows the error each service itself returned.
Set up in the app
Service connections, playback, generated subtitles and defaults for new accounts: everything is set here.
Your server, your accounts, your rules.
Laterna runs entirely on your machine, with your household's Jellyfin accounts.
Self-hosted
One Docker container and one volume for its data.
Jellyfin accounts
Everyone signs in with their Jellyfin account. Each sign-in is a device you can revoke.
Discreet
A signed-out visitor only sees whether they can sign in and watch. Service details are for signed-in accounts.
Free and open
The code is public, under the AGPL-3.0 licence. You can read it, change it and share it.
From an empty folder to your cinema in ten minutes.
A compose file and your browser are all it takes: secrets are generated on first launch, and every setting is made in the setup assistant.
The compose file
Create an empty folder and save this file as docker-compose.yml. It works as it stands: the app answers on the server's port 3000, and its data lives in a named volume.
docker-compose.yml
# Laterna - minimal deployment# No .env: secrets are generated on first launch,# and every setting is made in the setup assistant.services:
cine-app:
image: ghcr.io/lk59/cine-app:latest# or :<sha> to pin a versioncontainer_name: cine-apprestart: unless-stopped# Reachable from the server only (http://localhost:3000).# Behind a proxy on the same Docker network, remove these two lines.ports:
- "127.0.0.1:3000:3000"# The Docker network of your reverse proxy (and ideally Jellyfin).# networks:# - proxyvolumes:
# Database, settings, secrets and logs: all of the app's state.
- cine-data:/app/data# Optional, read-only: your media, for the statistics.# - /path/to/media:/mnt/media/video:roenvironment:
- TZ=Etc/UTC# Behind an HTTPS proxy (step 3):# - COOKIE_SECURE=true# - TRUSTED_PROXIES=nginx-proxy-manager# Optional: a memory ceiling.# mem_limit: 2ghealthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/api/ping"]
interval: 30stimeout: 5sretries: 3start_period: 60svolumes:
cine-data:
name: cine-app-data# fixed name, used by backups# networks:# proxy:# external: true # the name `docker network ls` gives
Set TZ to your time zone (Europe/London, America/New_York...). To keep the data in a ./data folder next to the file, replace the volume line with - ./data:/app/data and create the folder first: mkdir data && sudo chown 1001:1001 data.
Start and check
After about a minute, the container reads "healthy".
On a new install, the logs print a one-time setup code. The assistant asks for it to create the admin account, so only someone with access to the server can read it.
If the container stops, its last log line gives the reason. The most common case is a ./data folder owned by root: sudo chown -R 1001:1001 data.
Behind a reverse proxy
HTTPS enables home-screen install, notifications and every player feature. Pick your proxy:
Hosts → Proxy Hosts → Add Proxy HostDetails tab: your domain name, scheme http, forward hostname cine-app, port 3000.
SSL tabRequest a Let's Encrypt certificate, then tick Force SSL and HTTP/2 Support.
Advanced tabPaste these two lines. The first keeps page navigation smooth, the second accepts the screenshots attached to problem reports.
In the compose file, join the app to the proxy's network (the networks blocks), remove ports, then uncomment these two lines with your proxy's container name:
docker-compose.yml
- COOKIE_SECURE=true
- TRUSTED_PROXIES=nginx-proxy-manager# or caddy, traefik...
Restart with docker compose up -d. Keep these two lines commented while you test over http://.
The setup assistant
Open your address: the assistant shows until setup is finished. Each screen saves when you press Next, and tests the service you just filled in.
Welcomethe install's language
Accountthe setup code, then the local admin
Jellyfinaddress and API key, seen from the container
No, it builds on it. Jellyfin stays the server that knows your files, your accounts and your play state; Laterna is its interface. What you mark as watched, your language preferences and your positions carry over to the other Jellyfin apps.
Do I need Radarr, Sonarr and the rest?
No. With Jellyfin and TMDB alone, the cinema shows your whole library. Radarr and Sonarr add management, Jellyseerr adds requests, qBittorrent adds download progress.
Will my server be transcoding all the time?
No. On the native path, the device decodes and the server serves the file. Transcoding only steps in when a file truly can't be played by the browser or the connection is too slow, and that fallback can be turned off.
Which devices does it run on?
Any recent browser: computer, tablet, phone. On iPhone, iPad and Android, Laterna installs to the home screen like an app. For the TV: Chromecast, AirPlay, or the TV's browser with a keyboard or remote.
Does anything leave my server?
Metadata comes from TMDB, as it does for Jellyfin. Generated subtitles, when enabled, send the subtitle text to the provider the admin chose. Your videos stay on your server.
Is it really free?
Yes. Laterna is free software under the GNU AGPL-3.0 licence, free in full.
Light the lantern.
Laterna is named after the magic lantern, the ancestor of cinema. Install it on your server in a few minutes.