Watching¶
This page is for everyone who watches from the server: which app to use on each device, the settings that keep playback smooth, what the Jellyfin look and plugins add, subtitles, watching away from home, and the optional Plex server.
Which player where¶
Jellyfin is the media server. Every app below talks to it.
| Where you are | Use | Why |
|---|---|---|
| Apple TV | Neptune or Swiftfin | Native tvOS apps that play files directly; Neptune has the most polished TV interface |
| Android TV, Fire TV, Samsung or LG TV, Roku | The Jellyfin app from the TV's store | The official app for each platform |
| A TV at home without Tailscale | JellySee, or any Jellyfin app on http://<lan-ip>:8096 |
The home network reaches Jellyfin and Seerr, which JellySee needs |
| Phone, tablet or laptop | Jellyfin in the browser | Gets the full look described below, with Seerr requests and recommendations. On an iPhone, add it to the Home Screen |
| Phone, native app | Streamyfin (shows the same home rows) or Swiftfin | Offline downloads |
| Away from home | Any of the above, with Tailscale on | See Watching away from home |
The first time, an app asks for the server address, because Jellyfin has no cloud account:
| From | Server address |
|---|---|
| Anywhere, over Tailscale | http://<tailscale-ip>:8096 |
| At home, on the home network | http://<lan-ip>:8096 |
Sign in with your own account. The owner uses JELLYFIN_USER / JELLYFIN_PASSWORD from .env;
everyone else gets one from viewer-add.py (Giving someone access). The
app remembers the server after that.
Settings that keep playback smooth¶
Everything is tuned so that the device you watch on plays the file as it is ("direct play"). A Raspberry Pi 5 has no hardware video encoder, so converting video on the fly ("transcoding") is slow and stutters. In every Jellyfin app:
- Settings → Playback → Video quality: Auto, or the highest bitrate offered. Never a fixed low bitrate: any cap below the file's own bitrate forces a transcode.
- Leave Allow direct play and Allow direct stream switched on.
To check, play something and open Jellyfin → Dashboard → Activity. The session should say Direct Play or Direct Stream, not Transcode. The owner also gets a push notification whenever a stream is transcoded in software.
On an x86 server with Intel or AMD graphics, hardware transcoding can be switched on
(compose.gpu.yml and HWACCEL, see Getting started). A hardware
transcode is cheap and raises no alert; direct play is still the goal.
Chromecast: use the Cast button inside the Jellyfin app, so the Chromecast pulls the stream straight from the server. Don't mirror your phone's screen: the phone then re-encodes its own display, which looks worse and drains the battery.
The Jellyfin look and plugins¶
configure-jellyfin-plugins.py sets all of this up on the server, and is safe to re-run:
| Piece | What it adds |
|---|---|
| Abyss theme | A dark, streaming-service style theme with a mobile layout, set as Custom CSS in Dashboard → Branding |
| Abyss Spotlight | A full-width rotating banner with title logos, Play and info buttons |
| Home Screen Sections plugin | The home rows (listed below). Streamyfin shows them too |
| Jellyfin Enhanced plugin | Seerr results and requests in search; requestable Recommended and Similar rows on every detail page; quality and rating badges; Calendar, Downloads and Recommendations pages in the user menu; links to Sonarr, Radarr and Bazarr |
| File Transformation, Plugin Pages | Needed by the two plugins above |
| Neptune Indexers, Neptune MDM | The server side of the Neptune app. Neptune installs them itself; the script lists them so a rebuilt server gets them back |
| TMDb Box Sets | Automatic collections once you own two or more films of a series. The script runs its first scan once on a fresh install |
| Fanart | Clear logos and extra backdrops, used by the Spotlight banner and TV apps |
| Trakt | Watch-history sync with Trakt. Idle until you link an account: Dashboard → Plugins → Trakt |
| Open Subtitles | Subtitle search and downloads when OPENSUBTITLES_USER and OPENSUBTITLES_PASS are set |
The home rows, top to bottom: Continue Watching, Next Up, Top 10, Because You Watched (up to three rows), Discover (with request buttons), Recently Added Movies, Recently Added Shows, Genre (up to two rows), Discover Movies, Discover TV, Upcoming Shows and Upcoming Movies (from Sonarr and Radarr), My List, My Requests and Watch Again.
Things worth knowing:
- Some rows stay hidden at first. Because You Watched, Genre and Top 10 are built from watched movies, so they appear once some have been played.
- Row order must have no gaps. Home Screen Sections 3.0.2 shows an empty home screen when the first row is 0 and the next one isn't 1; the script numbers them 0, 1, 2 and so on. If the home screen ever says "Nothing here", see the known issue.
- Spotlight isn't a plugin.
apps/jellyfin/custom-cont-init.d/abyss-spotlight.sh(vendored from Abyss, MIT) adds its loader to Jellyfin'sindex.htmlon every container start, so it survives image updates. - Links follow the address you used. The plugins reach Seerr and the arr apps over the
Docker network with the API keys from
.env, and links shown to your browser are mapped to the address you opened Jellyfin on (Tailscale, home network,.localor localhost). - Requests need a Seerr account. Each Jellyfin user needs a linked Seerr account with request
permission;
viewer-add.pycreates it. - Optional extras.
TMDB_API_KEYadds reviews and "where to stream";MDBLIST_API_KEYadds IMDb and Rotten Tomatoes ratings on posters. Fill them in.env, then re-run the script. - The server's name in apps is
JELLYFIN_SERVER_NAME(default "Home Media"). - No Skip Intro button. The Intro Skipper plugin was removed; see Maintenance.
Where it shows: everything works in the browser and the iPhone app. On TVs the home rows work, but Jellyfin Enhanced's extras may be missing, since it supports Jellyfin Desktop only from its unreleased version 3.0. The official Android TV app is native and ignores all of this.
Subtitles¶
Bazarr fetches English subtitles automatically, a few minutes after a file is imported. It saves
them as separate .srt files next to the video, on purpose:
- A browser can only show subtitles embedded in the file after Jellyfin reads the whole file to extract them, which leaves playback stuck on "Fetching additional data".
- Image subtitles (PGS, VOBSUB) have to be burned into the picture, which is a full transcode. Text subtitles (SubRip, ASS) can be switched on without touching the video.
To find a subtitle yourself in Jellyfin's browser interface:
- Open a movie or episode's ⋯ menu, then Edit subtitles.
- Choose the language and search.
- Use Download to add the subtitle to the server for playback, or Save to device beside
an Open Subtitles result to save an
.srtfile through your browser.
No browser extension is needed. Viewers can also upload subtitles; deleting subtitles requires an administrator. Save to device is added to the server's web interface and does not appear in native TV apps. Bazarr still handles automatic downloads.
The owner can also open Bazarr (:6767), find the title and search with the magnifying glass.
To see which subtitle formats a file carries:
docker exec jellyfin /usr/lib/jellyfin-ffmpeg/ffprobe -v error \
-select_streams s -show_entries stream=index,codec_name \
-of csv=p=0 "/data/media/movies/<title>/<file>.mkv"
If it reports pgs or dvd_subtitle, let Bazarr fetch an external .srt for that title and pick
that track instead.
Set OpenSubtitles.com credentials in .env as OPENSUBTITLES_USER / OPENSUBTITLES_PASS,
then re-run configure-bazarr.py and configure-jellyfin-plugins.py. The latter installs and
configures the official Open Subtitles plugin and enables subtitle management for existing active
viewers; new viewers get that permission from viewer-add.py. Bazarr and Jellyfin share the
account's download limits. SUBSOURCE_API_KEY optionally adds another source to Bazarr.
apps/jellyfin/custom-cont-init.d/subtitle-downloads.sh reloads Save to device on every
container start, including after image replacement. Jellyfin updates can change the subtitle
screen or API; an unsupported screen omits or disables the button while Jellyfin stays usable.
Series whose original language is French are tagged french in Sonarr, which gives them Bazarr's
French subtitle profile, since English subtitles for French TV rarely exist.
Watching away from home¶
- Install Tailscale on the phone, tablet or laptop and sign in.
- Open Jellyfin as usual, at
http://<tailscale-ip>:8096.
Tailscale makes the server reachable as if you were at home, encrypted end to end, with no port open on your router. Family and friends get their own account and a limited slice of your tailnet: see Giving someone access.
Everything you watch away from home has to fit through your home upload:
| File | Bitrate | Away from home |
|---|---|---|
| 1080p encode (most things) | 8 to 15 Mbps | Comfortable on most connections |
| 1080p remux (large files) | 15 to 25 Mbps | One stream |
| 4K | 80 to 100 Mbps | Beyond most home uploads |
This is one reason the library targets 1080p (why). Torrent seeding shares the same upload and is capped permanently at 10 Mbps to leave room for streams; Usenet uploads nothing. Game downloads from the games page share it too.
If a stream stutters away from home, lower the app's quality while away and set it back to maximum at home. That makes the server transcode; a Raspberry Pi 5 manages one 1080p transcode at most, so don't ask for two at once.
Plex (optional)¶
Plex runs when the plex module is in COMPOSE_PROFILES, already tuned for direct play but not
linked to a Plex account. Both servers read the same library side by side without conflict; Plex
mainly adds slightly more polished TV apps. To claim it:
- Get a claim token from https://plex.tv/claim. It expires in about four minutes.
- Put it in
.envasPLEX_CLAIM. docker compose up -d --force-recreate plex, then openhttp://<tailscale-ip>:32400/web.
Plex runs on the host network and is reachable over Tailscale only. configure-plex.py keeps its
preferences right (it stops Plex while it writes them, because Plex rewrites its preferences file
on exit):
| Setting | Why |
|---|---|
The home network and Tailscale's 100.64.0.0/10 count as local |
Otherwise Plex treats tailnet devices as remote, caps their bitrate and forces a transcode |
| Scheduled daily library scans, no file-system watching | The media drive is a spinning disk; watching for changes would keep waking it |
| No video preview thumbnails, chapter thumbnails or loudness analysis | Each one reads every file in full |
Transcode scratch space in RAM (/transcode, 2 GB) |
Rare transcodes don't grind the system disk |
In each Plex app, set quality to Original and switch off "Automatically Adjust Quality".
Quick answers¶
| Problem | Answer |
|---|---|
| Something new doesn't appear | The media drive may be off, or the library hasn't rescanned. Jellyfin rescans the moment Radarr or Sonarr imports, and on a schedule every 12 hours; real-time folder watching is off so the drive can rest. Force it with Dashboard → Scan All Libraries |
| Buffering at home | Dashboard → Activity says Transcode: an app's quality setting is too low (see above) |
| Works at home, not away | Tailscale isn't connected on that device |
| A show has no poster or description | See the known issue |
| "Account disabled" | Jellyfin locks a non-admin account after 3 wrong passwords; the owner re-enables it in Jellyfin → Users |
| Subtitles missing | Open the title's ⋯ → Edit subtitles in Jellyfin and search, as above |
More in Troubleshooting.