Jellyfin MPV Shim is a cross-platform cast client designed to enhance your media playback experience with Jellyfin. It allows users to enjoy high-quality media streaming without transcoding, leveraging the powerful MPV player for advanced video and audio features.
Key Features:
Direct play most media files using MPV for optimal quality.
SyncPlay integration enables synchronized playback with friends.
Shim mode runs in the background, allowing full control via Jellyfin mobile apps.
Reconfigure subtitles for entire seasons or series at once.
Advanced video enhancements through Shader Packs and SVP integration.
Discord Rich Presence to share your media activity with friends.
Audience & Benefit:
Ideal for media enthusiasts and professionals seeking a high-quality, customizable streaming experience. Users benefit from seamless integration with Jellyfin, enhanced playback features, and the ability to control the client remotely via mobile apps. The software supports advanced customization, enabling users to tailor their experience based on preferences.
Jellyfin MPV Shim can be installed via winget for easy setup, ensuring a smooth and efficient user experience.
README
Jellyfin MPV Shim
Jellyfin MPV Shim is a cross-platform client for Jellyfin. It can run in the background as a cast target or act as a fully-featured desktop client with offline sync support.
It has support for all your advanced media files without transcoding, as well as tons of
features which set it apart from other multimedia clients:
Direct play most media using MPV.
Watch videos with friends using SyncPlay.
Offers a shim mode which runs in the background.
The Jellyfin mobile apps can fully control the client.
Prevents having to regularly change subtitles/audio settings for each episode.
To use the client, simply launch it and log into your Jellyfin server. You’ll need to enter the
URL to your server, for example http://server_ip:8096 or https://secure_domain. Make sure to
include the subdirectory and port number if applicable. You can then cast your media
from another Jellyfin application. You can also use quick connect, which is easier and faster.
The application runs with a notification icon by default on Windows and if installed on Linux. If
you would like the application to run in the background without the notification icon, enable it in settings.
Note: Due to the huge number of questions and issues that have been submitted about URLs, I now tolerate
bare IP addresses and not specifying the port by default. If you want to connect to port 80 instead of
8096, you must add the :80 to the URL because :8096 is now the default.
Limitations
Live TV is partially supported. The home screen's "On Now" row appears when your server has a
tuner, and playing an entry from it tunes the channel. There is no channel guide, no Live TV
library browsing and no DVR/recording management — for those, use the web client.
A single active session still reports as one device to a given server. For sharing the player between
people, see Fast User Switching, which keeps each local user on its own device
identity. (Related issue.)
Known Issues
Please note the following issues with controlling SyncPlay:
If you attempt to join a SyncPlay group when casting to MPV Shim, it will play the media but it will not activate SyncPlay.
If you would like to create a group or join a group for currently playing media, use menu within MPV.
SyncPlay can still be fragile. You may need to rejoin or even restart the client. Please report any issues you find.
Music playback works, but gapless playback is not planned at this time.
The shader packs feature is sensitive to graphics hardware. If the application fails to launch, use
--reset-shaders command line argument to reset the shader configuration. If it's merely garbled graphics
you can press k to disable the last used shader config.
Advanced Features
Menu
Most of these are also reachable from the player UI's settings (gear) menu, which is usually
easier. This menu is the older text-based one, and it still covers a few things the player UI
does not.
To open the menu, press c on your computer or use the navigation controls
in the mobile/web app.
The menu enables you to:
Adjust video transcoding quality.
Change the default transcoder settings.
Change subtitles or audio, while knowing the track names.
Change subtitles or audio for an entire series at once.
Mark the media as unwatched and quit.
Enable and disable SyncPlay.
Configure shader packs and SVP profiles.
Take screenshots.
On your computer, use the mouse or arrow keys, enter, and escape to navigate.
On your phone, use the arrow buttons, ok, back, and home to navigate.
Fast User Switching
The local library browser can hold several users, letting more than one person share the same
player without their servers, sessions, and remote-control state colliding. Jellyfin (and jellyfin-web)
has no built-in fast user switching; because this client owns its own UI, it can.
A user is a local grouping of one or more server logins that connect together. Only one user is active
at a time. Switching disconnects the active user's servers and connects the selected user's, then updates
the server selector.
Managing users — open Settings → Servers. The existing server(s) are kept as a (default) user
(which you can rename). Use Add User to create more, then Switch to a user and add its servers
with the normal Add a server form (each user's servers are managed while that user is active). Any
server address already used by another user is offered under Previously added servers with Use and
Quick Connect shortcuts, so you don't retype URLs when provisioning a new account.
Switching — a user drop-down appears to the left of the server selector in the top bar once you have
more than one user. Pick a user to switch to it.
Separate device identity — each non-default user gets its own Jellyfin device id (and a device name
like hostname (Kids)), so two users logged into the same server don't fight over one server-side
session. The (default) user keeps the original device id, so its existing sessions and tokens are
untouched.
PIN protection (parental controls) — a user can be given a PIN (Set PIN). Switching into a
locked user always requires the PIN. You can additionally tick Require this PIN at startup and when
reopening the window, which re-locks the browser whenever the app starts or the window is reopened from
the tray, so a locked profile can't be resumed without the PIN. This is a parental-control convenience,
not a security boundary — the PIN is only salted-hashed in the config, and the media itself is not
encrypted.
The first time you close the browser window, you're asked whether closing should Minimize to Tray
(keep the app running as a cast target) or Exit. Your choice is remembered and can be changed later
via Close to Tray (keep running) in Settings → Interface.
Users are stored in users.json in the config folder (next to cred.json). On first run with this
feature, your existing cred.json is migrated into the (default) user automatically.
Shader Packs
Shader packs let you use advanced video shaders and video quality settings without the
configuration they normally require. MPV Shim's default shader pack comes with
FSRCNNX and Anime4K
preconfigured. Try experimenting with video profiles! It may greatly improve your experience.
To use, navigate to the Video Playback Profiles option and select a profile.
Profiles leave your graphics API alone, so HDR output keeps working. If video breaks
when you load one, pick a different API under Settings → Video Enhancement → Graphics
API for Shaders (shader_pack_gpu_api); opengl is the most compatible.
For details on the shader settings, please see default-shader-pack.
If you would like to customize the shader pack, there are details in the configuration section.
SVP Integration
SVP integration allows you to easily configure SVP support, change profiles, and enable/disable
SVP without having to exit the player. It is not enabled by default, please see the configuration
instructions for instructions on how to enable it.
Display Mirroring
Casting an item from another Jellyfin client shows it on your display before you play it,
similar to Chromecast. This is on by default and needs no configuration — the item's page
opens in the library browser, and you can drive it from there with the remote's arrow keys.
Display mirroring doesn't interrupt playback.
Cast-target mode (headless)
For a box that should only be a cast target — a TV in a shared space, say — set headless
to true in the config file. The player then shows a "Ready to cast" backdrop instead of the
library, and the library cannot be reached from the machine itself: no browsing, no search, no
settings, and no queue view. Casting, playback and the player controls all work normally,
including transport controls for music.
This is not a security feature. It stops someone plugging in a mouse and playing random
things from your library, which is what it is for. It does not stop anyone with real access to
the machine: the config file is editable, and the systray menu still reaches Settings and the
log viewer. If you need the box genuinely locked down, use the operating system for that.
Most settings are editable in the app under Settings, so you rarely need to touch the
config file. The full list of options, including the ones with no UI, is in
docs/configuration.md.
The config file lives in a per-platform folder — the systray icon can open it for you, and
the path is printed at startup on Linux and macOS. See
the reference for the locations.
Tips and Tricks
Various tips have been found that allow the media player to support special
functionality, albeit with more configuration required.
Open on Specific Monitor (#19)
Please note: Edits to the mpv.conf will not take effect until you restart the application. You can open the config directory by using the menu option in the system tray icon.
Option 1: Select fullscreen output screen through MPV.
Determine which screen you would like MPV to show up on.
If you are on Windows, right click the desktop and select "Display Settings". Take the monitor number and subtract one.
If you are on Linux, run xrandr. The screen number is the number you want. If there is only one proceed to Option 2.
Add the following to your mpv.conf in the config directory, replacing 0 with the number from the previous step:
fs=yes
fs-screen=0
Option 2: (Linux Only) If option 1 does not work, both of your monitors are likely configured as a single "screen".
Run xrandr. It should look something like this:
Screen 0: minimum 8 x 8, current 3520 x 1080, maximum 16384 x 16384
VGA-0 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 521mm x 293mm
1920x1080 60.00*+
1680x1050 59.95
1440x900 59.89
1280x1024 75.02 60.02
1280x960 60.00
1280x800 59.81
1280x720 60.00
1152x864 75.00
1024x768 75.03 70.07 60.00
800x600 75.00 72.19 60.32 56.25
640x480 75.00 59.94
LVDS-0 connected 1600x900+1920+180 (normal left inverted right x axis y axis) 309mm x 174mm
1600x900 59.98*+
If you want MPV to open on VGA-0 for instance, add the following to your mpv.conf in the config directory:
fs=yes
geometry=1920x1080+0+0
Option 3: (Linux Only) If your window manager supports it, you can tell the window manager to always open on a specific screen.
Set mpv_ext to true in the config. Add script=/path/to/mpris.so to mpv.conf.
Run Multiple Instances (#45)
Pass --config /path/to/folder to run another copy of the player.
Each config directory gets its own instance: the single-instance guard is a lock inside the
config directory, so copies pointed at different folders coexist by design. Launching a second
copy with the same config directory instead raises the window of the one already running,
which is what makes the desktop launcher and the tray behave sensibly.
To shut one down, run jellyfin-mpv-shim stop (with the same --config folder, if you used
one). It reaches the instance owning that directory over the same channel a second launch uses
to raise the window, so it stops the right copy without hunting for a process id, and the app
runs its normal shutdown rather than being killed. It exits non-zero only if an instance is
holding the lock but not answering.
Audio Passthrough
This is built in now — see Audio Output. Set audio_mode to hdmi or
optical in Settings and tick the formats your receiver accepts; there is no need to hand-edit
mpv.conf.
This section used to recommend an mpv.conf snippet setting audio-spdif and
af=lavcac3enc together. Don't do that. The two are mutually exclusive per track: the AC3
encoder is handed a compressed frame it cannot convert, the filter chain fails to build, and mpv
recovers by silently disabling the filter — so the encoder never runs and nothing tells you why.
audio_mode=optical handles this properly by choosing between passthrough and the encoder per
track, based on what the track actually is.
MPV Crashes with "The sub-scale option must be a floating point number or a ratio"
Run the jellyfin-mpv-shim program with LC_NUMERIC=C.
Development
Build instructions, dev installation, packaging and translation are in
docs/development.md.
Linux Installation
You can install the software from flathub. The pip installation is less integrated but takes up less space if you're not already using flatpak.
If you are on Linux, you can install via pip. You'll need libmpv or mpv installed.
MPV 0.41 or newer is recommended. Older versions work, with two differences:
minimizing the library to the tray quits MPV rather than just dropping its
window (it comes back on the next play, or from the tray), and copy/paste in
text fields needs wl-clipboard on Wayland or xclip/xsel on X11, because
MPV had no X11 clipboard of its own before 0.41.
pip3 install --upgrade jellyfin-mpv-shim
This installs the main UI, tkinter is not needed anymore. By default the application does not run
in the background on Linux unless you enable it in settings or install the optional systray support:
pip3 install 'jellyfin-mpv-shim[systray]'
# Requires gi and appindicator, e.g. on Debian:
sudo apt install python3-gi gir1.2-ayatanaappindicator3-0.1
You must use a virtualenv with --system-site-packages enabled for systray support.
With pipx, use pipx install --system-site-packages 'jellyfin-mpv-shim[systray]'.
Discord rich presence support (must be enabled in config) can be installed with:
sudo pip3 install jellyfin-mpv-shim[discord]
If your distribution ships an old MPV, building it from source gets you better codec support and
the current renderer. Follow the instructions in
mpv-build — it builds MPV together with matching
FFmpeg, libass and libplacebo, and its README lists the build dependencies for your distribution.
libmpv is what this client loads, and mpv-build produces it by default (libmpv defaults to
true in MPV's own meson options), so no extra configuration is needed. Afterwards run
sudo ldconfig so the new library is picked up.
Note, if it has been a while since you have compiled mpv, modern copies of mpv-build don't
support --enable-libmpv-shared in the mpv_options file, you can clear the file as libmpv
is now enabled by default.
macOS Installation
Currently on macOS only the external MPV backend seems to be working. I cannot test on macOS, so please report any issues you find.
Install jellyfin-mpv-shim and pystray. pipx install 'jellyfin-mpv-shim[systray]'
Run jellyfin-mpv-shim.
Display mirroring is not tested on macOS, but may be installable with 'pipx install 'jellyfin-mpv-shim[mirror]'`.
Building on Windows
There is a prebuilt version for Windows in the releases section, so you only need this if you are
working on the client itself.
These steps mirror .github/workflows/main.yml, which is what actually produces the releases.
If this section and the workflow ever disagree, the workflow is right — check it first.
Make sure Python and libmpv are both 64-bit or both 32-bit; mismatching them fails at runtime.
Install Git for Windows. Open Git Bash and run git clone https://github.com/jellyfin/jellyfin-mpv-shim; cd jellyfin-mpv-shim.
You can update the project later with git pull.
Install Python 3 with PATH enabled (CI builds on 3.14) and 7zip.
Install Inno Setup — it builds the installer at the end.
CI does this with winget install --id JRSoftware.InnoSetup -e -s winget.
Open cmd and run pip install wheel then pip install .[all] pywin32.
64-bit: mpv-dev-x86_64-v3-*.7z. The v3 builds need a CPU supporting x86-64-v3; for older
hardware use the plain mpv-dev-x86_64-*-git-*.7z (this is what the "legacy64" release is).
32-bit: mpv-dev-i686-*.7z.
Extract it and move libmpv-2.dll into the jellyfin-mpv-shim folder, renaming it to
mpv-2.dll. The build scripts look for that name.
In Git Bash, build the PyInstaller bootloader from source:
./gen_pkg.sh --get-pyinstaller
cd pyinstaller/bootloader && python ./waf distclean all && cd .. && pip install .
cd ..
PyInstaller is only needed to produce the .exe. It is not a dependency of the
application, and nobody running the client needs it.
A stock pip install pyinstaller also works, but ships a prebuilt bootloader that
antivirus products have a long history of flagging. Building it locally gives the
installer a bootloader that isn't already on every heuristic blocklist, which is why CI
does it this way.
In Git Bash, run ./gen_pkg.sh --skip-build.
This builds the translation files and downloads the shader packs.
Run build-win.bat from cmd (build-win-32.bat for 32-bit, build-win-dbg.bat for a
console-attached debug build).
The 32-bit script reads the same mpv-2.dll in the same place — just extract the i686 one
instead. There is no separate mpv32 folder.