Jellyfin Media Server
Control playback on Jellyfin clients and read what is currently playing across a self-hosted Jellyfin media server. Jellyfin is the free, open-source media system — a community fork of Emby and a drop-in alternative to Plex — so this driver is the path for sites that run their own library instead of a cloud service.
One Jellyfin server is the GEM device. Each GEM zone maps to one Jellyfin client
(a player app: Apple TV, Android TV, Fire TV, the web/desktop app, a Kodi/MPV add-on, etc.)
addressed by that client's stable DeviceId. Playback commands (play, pause, stop, skip,
seek, volume, mute) are issued to the client's live session; a background poll of /Sessions
keeps each zone's state and now_playing in sync.
Authentication is a single API key minted from the Jellyfin dashboard — no OAuth, no cloud round-trip. Everything happens on the LAN.
Prerequisites
- Jellyfin server 10.8 or newer running on the LAN and reachable from the GEM server (default port 8096 for HTTP, 8920 for HTTPS).
- An API key. In the Jellyfin web UI: Dashboard → Advanced → API Keys → (+), give it a
name such as
GEM, and copy the generated key. API keys carry server-wide privileges — treat the value like a password. - Every client you want to control must be signed in to the same server and support remote
control. A client advertises this as
SupportsRemoteControlin its session entry; most native apps do, some third-party TV apps do not.
Setup steps
- Go to Devices → Add Device, choose the Jellyfin Media Server driver.
- Enter the server IP / Host and paste the API key into the API Key field. Leave
Port at
8096(or8920with Protocol set tohttpsif you terminate TLS on the server itself). - Save, then run the get_sessions command on the device. This returns the clients currently
connected, each with a
DeviceId,DeviceName, andClientstring. - For each player you want to automate, create a zone and set the zone Address to that
client's
DeviceId.
get_sessions enumerates clients so you can copy their DeviceId, but it does not create zones.
Add a zone per client yourself and paste the id into the zone Address field. This keeps you in
control of which players become automatable targets.
Attributes
Device
| Attribute | Type | Required | Description |
|---|---|---|---|
ip | string | yes | LAN IP or hostname of the Jellyfin server. |
token | string (secure) | yes | Jellyfin API key from Dashboard → Advanced → API Keys. Stored encrypted. |
port | int | no | Server port. Default 8096 (HTTP); use 8920 for HTTPS. |
protocol | string | no | http (LAN, default) or https (TLS terminated on the server). |
status_interval | int | no | How often /Sessions is polled, in milliseconds. Default 5000, minimum 1000. |
client_id | string (read-only) | no | Auto-generated stable id sent as DeviceId in the authorization header — this is how GEM appears in the server's Devices list. The driver sets it on first connect; you do not edit it. |
Zone
Zones need no attributes beyond the Address (see below). The driver writes these read-only state attributes onto each zone as it polls:
| Attribute | Meaning |
|---|---|
state | playing, paused, idle (connected, nothing playing), or offline (client not connected). |
now_playing | Formatted title of the current item (series — season — episode, or artist — track). |
media_type | Jellyfin item type: Movie, Episode, Audio, MusicVideo, Video, … |
volume | Client volume 0–100, when the client reports it. |
muted | Boolean mute state, when the client reports it. |
Zone address format
The zone Address is the Jellyfin client DeviceId — a stable identifier per player
device (unlike the per-connection session Id, which changes each time the app reconnects).
Run get_sessions on the device to list live clients and their DeviceId values.
Commands
| Command | Args | Notes |
|---|---|---|
play | address | Resume (unpause) the client. |
pause | address | Pause the client. |
play_pause | address | Toggle play/pause. |
stop | address | Stop playback. |
skip_next | address | Next track / next item. |
skip_previous | address | Previous track / previous item. |
seek | address, offset | Seek to an absolute position in milliseconds; converted to Jellyfin 100-ns ticks on the wire. |
set_volume | address, level | Set client volume 0–100. Only clients advertising SetVolume honour it. |
mute / unmute | address | Mute / unmute the client (capability-dependent). |
send_message | address, text, header | Display an on-screen message on the client (default header GEM). |
get_sessions | — | List active client sessions and their DeviceId. |
get_server_info | — | Read the server identity / version document. |
Known limitations
- Clients must be connected. A player only appears in
/Sessionswhile its app is open and connected. A closed or backgrounded app drops off the list, and the driver reportsstate=offlinefor that zone until it returns. - Volume / mute are advisory. Jellyfin forwards
SetVolume/Muteonly to clients that implement them. Many smart-TV apps ignore volume control; use the display's own driver (CEC, IP, or IR) for guaranteed volume in those rooms. - No library browsing / "play this title" yet. This release covers transport control and
now-playing reporting. Queuing specific items by id (
POST /Sessions/{id}/Playing?ItemIds=…) is not wired up; it is a natural follow-up once library-search commands are added. - One server per device. Multi-server setups need one GEM device per Jellyfin server.
Troubleshooting
| Symptom | Check |
|---|---|
401 Unauthorized on every request | The API key was deleted or mistyped. Re-create it in Dashboard → Advanced → API Keys and update the token attribute. |
get_sessions returns an empty array | No client apps are currently connected. Open a Jellyfin player so it registers a session, then retry. |
| Command returns "client not currently connected" | The DeviceId in the zone Address is not in the live session list. Confirm the player is open and re-run get_sessions to verify the id. |
| Commands ack but nothing happens on screen | The target client does not advertise SupportsRemoteControl (visible in the get_sessions output). Some third-party apps cannot be remote-controlled. |
| Connection flaps / timeouts | Confirm the server IP and port, and that protocol matches how the server is actually exposed (http vs https). A reverse proxy in front of Jellyfin may rewrite paths — point the driver at the server directly. |
Related Documentation
- Devices
- Zones
- Plex Media Server — the same integration pattern for a Plex library.