English · 简体中文
A native desktop client for Jellyfin & Emby — real MPV playback with no Electron, no WebView, and no forced transcoding.
Custom-drawn in safe Rust with iced: lightweight, cross-platform, and responsive. Linux plays through the pinned embedded MPV fork; Windows and macOS drive your own MPV. Direct Play from your server, every time.
Android risk bring-up: Kotlin/Compose, the Rust bridge, and libmpv modules now live under android/. The features and releases described below remain desktop-specific. The user has reported physical-device manual verification passed; Android is not yet a complete client, and the detailed device/HDR matrix remains unrecorded. See the implementation status, build commands, and evidence.
▶ Watch the 65-second demo: launch → browse → series detail → embedded MPV playback with floating controls.
JellyPilot is a native desktop client for Jellyfin and Emby: a complete library browser, player, and cast receiver — not a wrapper around the web interface. Native UI and real MPV playback, with no browser in between.
- 🖥️ A full desktop client, not a web wrapper: Browse movies and series, curate Favorites, Watchlist, and Watch History, and switch between multiple servers and accounts — all in a fast, custom-drawn native interface.
- 🎬 Embedded MPV playback (Linux default): The pinned, host-enabled mpv fork renders right inside the player window — 10-bit Vulkan surface, direct VAAPI hardware decoding, and Dolby Vision Profile 5 SDR tone-mapping — with floating controls and zero interference from your system MPV configuration.
- 🚀 External MPV playback (Windows & macOS default, optional on Linux): Drives a standalone MPV process over JSON IPC. Your personal
mpv.conf, custom GLSL shaders, input scripts, and profiles remain fully in charge. - 📺 Cast receiver & remote control: Automatically discovered on your local network as a native cast target by Jellyfin clients. Transport commands, playback progress, and track changes sync bidirectionally across the main window, player bar, and system tray.
Dark theme · Home and playback controls
Dark theme · Series library and filters
Embedded MPV playback · On-screen controls and track selection
| Feature | Description |
|---|---|
| 🎞️ Multi-Server & Account Management | Connect to multiple Jellyfin and Emby servers with saved profiles, OS keychain credential storage, Quick Connect, and instant account switching |
| 📚 Rich Browsing & Personal Lists | Movies and series libraries with live filters, virtualized poster grids, and cached artwork; account-scoped Watchlist, Favorites, and detailed Watch History |
| 🎬 Dual-Mode MPV Playback Engine | Embedded MPV by default on Linux (10-bit Vulkan surface, direct VAAPI hardware decoding, Dolby Vision Profile 5 SDR tone-mapping); External MPV on Windows/macOS over JSON IPC (your configs, shaders, and scripts stay in charge) |
| ⏭️ Smart Playback & Binge-Watching | In-player episode drawer, automatic/manual native intro skipping, per-season volume memory, automatic next-episode progression, responsive floating controls, and customizable shortcuts |
| 💬 Audio Tracks & External Subtitles | Server-hosted external subtitles, embedded audio/subtitle stream switcher, and configurable preferred subtitle language order |
| 📺 Cast Receiver & Remote Sync | Discovered as a native cast device in Jellyfin; bidirectional playback command mirroring, progress reporting, and full system tray background integration |
| 🎨 Thoughtfully Crafted Native UI | Light and Dark themes, title logo hero headers, native corner smoothing, and backdrop blur; instant English & Simplified Chinese switching, plus a compact Control-Only mode |
| 🍏 Pure Native Architecture & Diagnostics | 100% safe Rust and iced with zero WebViews or Electron overhead; built-in diagnostic event viewer, MPV player log capture, and one-click support bundle export |
| Server | Supported | Capabilities |
|---|---|---|
| Jellyfin | ✅ | Password login, Quick Connect, saved profiles, multi-account switching, library browsing, personal lists (Favorites, Watchlist, Watch History), user data actions, embedded & external MPV playback, cast target registration, remote control, and native Intro Skipper |
| Emby | ✅ | Password login, saved profiles, multi-account switching, library browsing, personal lists (Favorites, Watchlist, Watch History), user data actions, embedded & external MPV playback, remote control, and playback progress reporting |
Emby support uses the same library, personal lists, and player workflow as Jellyfin where the server APIs are compatible. Jellyfin-specific features such as Quick Connect and Intro Skipper are not advertised for Emby connections.
Jellyfin 12.0 does not require legacy authorization to be enabled: JellyPilot uses the standard Authorization header for API requests and ApiKey query parameters for playback, subtitles, and remote sessions. Modern ApiKey values are redacted in exported diagnostics.
Enter the server's actual base URL, including any configured reverse-proxy base path; Jellyfin 12 removed the automatic /emby and /mediabrowser route aliases.
Intro Skipper reads Jellyfin's native media segments, including ranges published by the Intro Skipper plugin or server-side scheduled tasks. Automatic, Manual, and Off modes apply to intro/credit ranges independently. The deprecated plugin endpoint is no longer used. If no native segment ranges are available, playback continues without skipping; ensure the server's segment extraction or plugin synchronization has populated them.
- MPRIS support — Linux desktop media-player integration for keys and widgets
- Embedded Playback (Linux default): Requires the pinned host-enabled
libmpvbuild and baseline configuration (provided by packaged installs or built viabun run task mpv build), along with a Vulkan driver supportingRgb10a2Unormpresentation surfaces. Missing capability triggers an explicit error rather than falling back to an unverified configuration or external player. While a Playback Session is active and unpaused, embedded playback also holds a session idle inhibitor — the Wayland idle-inhibit protocol (zwp_idle_inhibitor_v1) on the presentation surface under Wayland,XScreenSaverSuspendunder X11 — so the desktop does not auto-lock mid-episode. Pausing or stopping playback releases it. On Wayland the inhibitor follows the visible window: closing the window to the tray pauses embedded playback and releases it; it is reacquired only after playback resumes in a visible window. Compositors without the idle-inhibit protocol cannot be inhibited (playback continues normally). - External Playback (Windows & macOS default, optional on Linux): Requires MPV with Lua scripting support, discoverable on
PATHor configured explicitly in Settings → Playback. A bundled Lua hook captures volume and temporary mute states before MPV resets options at the end of playback.
- In both Full and Control-Only, closing the main window with an initialized tray keeps JellyPilot's runtime and remote Playback Target available. Full restores its page, history, filters, scroll position, and unsaved Settings drafts; dialogs and menus are dismissed.
- Embedded Playback pauses before a requested window close completes and retains its Playback Session. If pause fails, the window stays visible and reports the error. Tray Show or a second launch restores the window without resuming playback; explicit remote/tray Play or Resume first restores the player.
- External Playback continues in MPV's independent window when JellyPilot's main window closes.
- Use Quit to end playback, the connection, and the application. If the tray could not initialize, closing the window also performs orderly shutdown.
All packaged Linux distributions install the pinned mpv fork (/usr/lib/jellypilot/libmpv.so and /usr/share/jellypilot/mpv-baseline.conf) and start Embedded MPV Playback by default. External MPV remains an option in Settings.
Install the prebuilt package from the AUR:
paru -S jellypilot-binOr build from source via the AUR:
paru -S jellypilotyay and other AUR helpers work the same way. The two packages conflict; pick one.
Download the latest jellypilot_*_amd64.deb from GitHub Releases, then install it:
sudo apt install ./jellypilot_*_amd64.debDownload the standalone jellypilot-*-x86_64.AppImage from GitHub Releases, make it executable, and run:
chmod +x jellypilot-*-x86_64.AppImage
./jellypilot-*-x86_64.AppImageDownload jellypilot-*-setup.exe from GitHub Releases and run the installer.
Note
Windows uses External MPV Playback by default. Install MPV and ensure it is available on PATH, or specify its executable path in Settings → Playback.
Download jellypilot-*.dmg from GitHub Releases, open the disk image, and drag JellyPilot to Applications.
Note
macOS uses External MPV Playback by default. Install MPV (e.g. brew install mpv) and ensure it is available on PATH, or specify its executable path in Settings → Playback.
Development prerequisites
- Rust 1.98 or newer
- Bun 1.3.14 or newer (task dispatcher only — there is no JavaScript frontend)
- Linux GUI: GTK 3,
libxkbcommon, and Wayland development packages (libgtk-3-dev,libxkbcommon-dev,libwayland-dev,wayland-protocols) - Linux Embedded MPV (optional if using external MPV): Meson >= 1.3, Ninja, C/C++ compiler, pkg-config, Vulkan development headers/loader, FFmpeg dev libraries,
libplacebo>= 7.360.1, andlibass. - Windows Embedded MPV: the same native build tools and media libraries in a Windows environment such as MSYS2 UCRT64, plus Lua or LuaJIT development files. The build requires D3D hardware decoding and Lua support; Lua provides the
oscoption even when the host setsosc=no.
git clone https://github.com/hewel/jellypilot.git
cd jellypilot
bun install --frozen-lockfile
# Optional (Linux or Windows): build and stage the pinned mpv fork for Embedded Playback
bun run task mpv build
# Build the release launcher binary
bun run task iced build --releaseThe release binary is target/release/jellypilot (jellypilot.exe on Windows).
Maintained Cargo tasks prepare the repository-owned target/vendor/iced before building.
tools/embedded-mpv/iced-source.json is the authority for the published fork revision and
remote. Preparation fetches that exact commit remotely, verifies HEAD and tracked cleanliness,
and makes all iced crates use the same checkout. There is no checked-in patch or fallback
revision. Existing owned vendor checkouts are preserved when the stable link changes.
An inaccessible commit fails explicitly. An optional local source can provide the same
committed contents without being modified; it is not the remote cold-prepare acceptance path:
bun run task iced prepare --source /absolute/path/to/icedThis accepted integration supersedes ADR 0027's external-only playback restriction, not its native iced/no-webview decision. Linux defaults to Embedded MPV Playback with the pinned fork, including a one-time migration of existing settings that still recorded External. Windows and macOS keep External as their default. Windows also supports Embedded MPV with the pinned DLL and its Windows baseline. In Settings → Playback, select External if you want your own MPV process, configuration, shaders, and scripts; restart to apply. Switching preserves the external executable and arguments. Show video opens the player surface while browsing. The same player, transport, queue, volume, subtitles, and remote-session controller are reused.
The embedded player uses one full-window video surface with floating controls in both windowed and fullscreen playback; the video keeps its aspect ratio without cropping.
| Input | Embedded player action |
|---|---|
| Left / Right | Seek −5 / +5 seconds; held keys repeat |
| Up / Down | Volume +5 / −5 percentage points, limited to 0–100%; held keys repeat |
| F | Toggle fullscreen; held-key repeats ignored |
| I | Toggle mpv's stats overlay; held-key repeats ignored |
| Esc | Leave fullscreen; an open menu or modal takes priority |
| Space / click video | Toggle pause; Space repeats ignored |
| Back button | Stop playback, then restore the source page and leave fullscreen |
These keys apply only while the embedded player is visible, not while browsing. Captured input, menus, modals and shortcut capture take priority; the listed unmodified keys are reserved in the player. Other configured episode/intro bindings remain available. Search is unavailable within the standalone player; Settings can open over windowed playback.
The cursor reappears on any pointer movement and hides after three idle seconds while playing, independently of the controls. Moving near the bottom control area (including a 48px approach band) reveals the bar; movement over the middle of the picture does not reveal or prolong it. The top-left Back button has its own reveal region and three-second timeout. Pausing, dragging, open menus and an intro prompt keep controls visible. Keyboard seek and volume changes show brief feedback. Hovering the timeline previews its time; dragging changes the target preview and seeks once on release. Seek availability retains the existing positive, finite-duration check, not a separate backend seekability signal. A failed Stop keeps the player open so Back can be retried.
Slider keyboard/wheel adjustments submit immediately rather than waiting for a mouse release. Switching fullscreen during a drag cancels its unfinished preview; releasing afterward does not submit the cancelled target. Clicking the picture outside an open player menu only dismisses that menu and does not pause or resume playback.
The bar uses a translucent background without blur. Its transport stays centered in one row down to 768 logical pixels; at 900 pixels and below, the landscape thumbnail is hidden.
# Linux prerequisites: Meson >=1.3, Ninja, C/C++ compiler, pkg-config,
# Vulkan development headers/loader, FFmpeg, libplacebo >=7.360.1, libass.
bun run task mpv build --source /absolute/path/to/mpv
bun run task iced runtools/embedded-mpv/source.json is the authority for the mpv revision, baseline and Meson
options. The supplied source must be at that revision with no tracked changes. Without
--source, the task fetches that revision from the configured fork; an inaccessible
revision is a hard prerequisite failure. No source commit or push is performed.
The build stages target/embedded-mpv/lib/jellypilot/libmpv.so,
target/embedded-mpv/share/jellypilot/mpv-baseline.conf, and manifest.json.
On Windows, the library is lib/jellypilot/libmpv-2.dll and the baseline comes from
tools/embedded-mpv/baseline-windows.conf. The task adds windowsMesonOptions from
source.json, so missing D3D hardware decoding or Lua support fails at configuration time.
The Windows baseline requests hwdec=d3d11va-copy: hardware-decoded frames pass through
system memory before upload to the existing Vulkan renderer. Unsupported codecs or devices
can fall back to software decoding. Start it with bun run task iced run --release --embedded.
Windows presentation prefers Rgb10a2Unorm, then falls back to Bgra8Unorm or
Rgba8Unorm if the window surface cannot present 10-bit output. The mpv producer and
private video texture remain 10-bit; final UI composition and presentation use 8-bit targets.
The selected format is recorded in startup logs, with a warning on 8-bit fallback.
sRGB attachments are excluded to avoid encoding mpv's gamma-encoded SDR pixels twice.
Linux continues to require Rgb10a2Unorm presentation (ADR 0041).
The MPV build task stages the main DLL; use the Windows packaging task below to collect
its runtime dependencies for distribution. Changing the source baseline requires restaging
it (the MPV build task does this); an already running player keeps its startup configuration.
Confirm hwdec-current=d3d11va-copy during playback before claiming hardware decoding;
the current iced regress runner remains Linux-only.
For a Windows installer, build MPV in MSYS2 UCRT64, then run from a native Windows shell with Rust/MSVC and Bun available:
bun run task package windows --runtime-dir C:\msys64\ucrt64\binIf cargo-packager 0.11.8 is unavailable, the task installs that exact version under
target/tools without replacing a globally installed version.
Use the UCRT64 directory from the same environment that built MPV. The task verifies the
staged MPV revision and artifact hashes, collects the recursive x64 DLL dependency closure
into a fresh directory, includes MPV/MSYS2 license notices and manifests, then builds the
launcher and NSIS installer under target/release/bundle. The launcher's VC runtime DLLs are
collected from the installed Visual Studio x64 redistributable directory and placed beside
the EXE. Missing dependencies fail packaging.
The shared Packager.toml remains the metadata authority; the task adds Windows resources
without affecting Linux/macOS packages. Direct cargo packager --config Packager.toml does
not add Windows embedded resources.
The release workflow installs and checks the NSIS payload on a disposable Windows runner,
including hashes, pinned host exports, baseline options, and headless libmpv initialization
without the build toolchain on PATH. This gate does not verify GPU playback. On a real Windows
desktop, select Embedded MPV in Settings → Playback, restart, play a video, and confirm
hwdec-current=d3d11va-copy. For local package inspection without installing over an existing
app, extract NSIS with 7-Zip and run packaging/windows/verify-package.ps1 -Root <extracted-dir> -ExpectedExecutable target/release/jellypilot.exe in a separate PowerShell process.
The manifest records source, configuration, tool/dependency versions and artifact hashes.
Source and options are pinned; host libraries/compiler and auto-selected dependencies are
recorded, not pinned, so this is not a bit-reproducible or self-contained distribution.
Vulkan headers may be supplied explicitly through CFLAGS=-I/absolute/sdk/include (and
dependencies through PKG_CONFIG_PATH); neither is silently obtained from another build tree.
Development run/hot commands pass staged asset paths for the Linux Embedded default and for
saved Embedded settings. Absolute JELLYPILOT_LIBMPV and JELLYPILOT_MPV_BASELINE overrides are
supported; only trusted files implementing the pinned host ABI may be loaded. A directly
launched binary looks beside itself, then in the executable prefix (../lib/jellypilot and
../share/jellypilot), so /usr/bin/jellypilot loads /usr/lib/jellypilot/libmpv.so.
Linux packages ship that pinned fork; they do not use system libmpv. Missing Vulkan or
host assets is an error, not a fallback to system libmpv or External MPV. To start External
MPV instead, launch jellypilot --external or select External in Settings and restart.
The host retains the actual enabled Vulkan feature chain and shares its device/queue with
the official iced renderer. Video reaches a private 10-bit texture through three ordered
command buffers: tracked COPY_DST transition, raw copy, tracked RESOURCE transition.
Queue submissions, acquisition/presentation/discard and image/screenshot submits share a
gate; device polling and synchronous mpv calls stay outside it. MPV owns playback time.
The private launcher contains only the two unsafe engine/surface handoffs; Vulkan/libmpv
FFI lives in jellypilot-mpv-host, while src-iced retains its unsafe-code prohibition.
Direct VAAPI hardware decoding is supported via DMA-BUF import extensions on the shared
Vulkan device when supported by hardware and drivers. This does not claim HDR presentation,
zero-copy playback, or downstream compositor presentation feedback. Windows uses the
D3D11VA-copy path described above; embedded startup on macOS remains unavailable.
The daemon factory retains the host/device resources across last-window close; reopening
creates a new surface and renderer for the same playback session. Daemon exit terminates
mpv before removing its process-private IPC directory.
The locked combination is the JellyPilot commit and its working-tree state, Cargo.lock,
tools/embedded-mpv/iced-source.json, and tools/embedded-mpv/source.json, together with the
staged target/embedded-mpv/manifest.json and the actual library/baseline hashes. The manifests
and lockfile are the source of truth; this document intentionally has no second version table.
The native manifest records host tools and dependencies, not a bit-reproducible environment.
An override library is not attested by the staged manifest: retain its actual hash and origin.
For local direct VAAPI candidates, the product enables supported DMA-BUF import extensions on the shared Vulkan device. Direct decoding additionally requires the host-enabled mpv implementation that resolves a DRM render node from that same physical device; extension support alone does not establish hardware decoding. The RGB10A2 three-slot output, private texture copy and queue synchronization are unchanged. A dirty fork candidate must be loaded with the explicit asset overrides above, retaining its HEAD, dirty patch and actual library hash; the published source pin does not describe those uncommitted changes.
GPU regression reports include decoderSamples read from the actual embedded IPC client:
hwdec-current, requested hwdec, input/output formats and dropped-frame counters at each
settled lifecycle stage. Optional --hwdec <no|vaapi|vaapi-copy> on gpu/all exercises
the host's explicit playback-option override after the baseline. Check the actual decoder
and interop logs to distinguish direct VAAPI from CPU copy-back; the lifecycle pass alone
does not assert a decoder. For diagnostic logging, use a full trusted baseline copy with
log-file and msg-level settings, retaining its hash, rather than a nested include wrapper.
The runner retains audio-session endpoint lookup while keeping app configuration and IPC
private, respecting existing PipeWire/Pulse routing overrides. For real audio acceptance,
set one strict backend in the full candidate baseline (for example ao=pipewire, without
a fallback comma). GPU samples record current-ao, audio-params and audio-out-params,
and reject a mismatch with that explicit backend. Report negotiated channels separately
from physical speakers and human listening acceptance.
For an upstream synchronization, work on a disposable sync branch in each affected fork. Read the iced maintenance guide and mpv maintenance guide. Review the upstream range and the fork's actual ABI/source changes; do not invent an ABI version bump when no ABI contract changes. Publish the candidate fork commits through the normal owner workflow, update the application manifests as one candidate combination, then:
- In a fresh application checkout, run
bun install --frozen-lockfileandbun run task iced preparewithout--source. Preparation must fetch the manifest's exact published commit and all iced packages must resolve undertarget/vendor/iced. - Build the pinned mpv with
bun run task mpv build, preserving its generated manifest and any explicit SDK/dependency inputs. Run the applicable project gates. - Run
bun run task iced regress all --file /absolute/path/to/real-moving-clip.mp4. This is the sole joint-probe entry; native regression policy defines prerequisites and evidence. A missing GPU fixture is rejected before preparation, build or application startup. Normal run/hot/smoke commands do not enable these probes. - Complete the separate manual color comparison. Review the exact candidate combination, current-run automatic report, human record and explicitly unavailable coverage before accepting a sync. A passing lifecycle probe alone is not color, HDR presentation, hardware decoding or Dolby Vision acceptance.
The default local artifact directory is target/native-regression/: report.json aggregates
only the requested scenarios; tray.json, external.json and gpu.json carry individual
results. Every automatic report has a fresh runId; every scenario is pass, fail or
unavailable. Check both the command exit status and that identity, not an old file's presence.
Unrequested scenario files can belong to earlier runs. Preserve reports outside this directory
before the next invocation if needed. These ignored local artifacts are not published docs.
Rollback means restoring the last accepted combination, not mixing one old library with
new host bindings. Preserve the pre-sync application revision, source manifests/lockfile,
baseline, native build manifest and binary hashes before changing pins. In a separate checkout
of that accepted application revision, prepare its iced pin and rebuild/stage its mpv pin
with the recorded inputs, or restore its verified archived artifacts. Keep the candidate tree
and local work intact. Start with jellypilot --external if embedded prerequisites are absent,
then rerun the applicable gates and joint acceptance before calling embedded recovery complete.
This is a human protocol, separate from the automatic GPU lifecycle probe. Keep
target/native-regression/color-comparison.json as a human-authored record; the regression
command never writes it, captures reference images, changes display settings, or updates a
reference to make a candidate pass. Retain the old accepted native binary and baseline before
the sync; if they are unavailable, mark that comparison unavailable, not equivalent.
Compare old accepted native mpv / candidate native mpv / candidate embedded JellyPilot.
The native executables must come from the respective recorded fork revisions, not an
unidentified system mpv. The application mpv build stages libmpv only; obtain native comparison
binaries from the corresponding fork build workflow and record their source/binary hashes.
Use the same explicitly loaded, hash-recorded baseline (--no-config --include=<baseline>
for native mpv) and record the embedded host backend differences; do not silently change color,
tone-mapping, scaling or hardware-decoding options between columns.
For each of SDR, HDR10, and Dolby Vision Profile 5:
- Use a real authorized local fixture. Record its hash and ffprobe stream metadata: codec,
dimensions, pixel format, color primaries/transfer/matrix/range, and HDR/DOVI side data.
HDR10 requires actual PQ/BT.2020/HDR metadata evidence. Profile 5 requires an actual DOVI
configuration record identifying
dv_profile=5; a filename, HEVC 10-bit stream, BT.2020 tag, synthetic pattern or another DV profile is not proof. Missing evidence or fixture isunavailable; do not synthesize a substitute and claim Profile 5 coverage. - Fix and record the same timestamp/frame, physical video-picture dimensions (not logical window size), monitor, compositor session, display mode/HDR state, scaling and baseline hash for all three columns. Pause at the agreed timestamp, wait for the decoded frame, and compare the same picture region with overlays removed equally.
- The human checks hue/skin tones, neutral grays, shadow detail, highlights/clipping,
saturation and gradients. Record what was actually observed and the compared column pair,
with
pass,failorunavailable, reviewer and timestamp. Do not promote screenshot byte differences or an automated decode success to a visual conclusion. - Identify the run and candidate/accepted artifacts in the human record and link any
intentionally captured human evidence. Automatic
report.jsoncontinues to describe only automated coverage. Approval or replacement of an old reference is a separate explicit human decision; retain the old record rather than overwriting it during a sync.
- Launch JellyPilot from your application menu or terminal.
- Choose a server type: Jellyfin or Emby on the login screen.
- Authenticate with your Server URL and credentials; Jellyfin also supports Quick Connect.
- Manage accounts: Add multiple server accounts and switch between them anytime via the sidebar profile menu.
- Browse and curate: Browse movies and series with live filters, explore Personal Lists (Watchlist, Favorites, Watch History), and view rich details.
- Play or cast: Start playback directly in JellyPilot (Embedded MPV on Linux, External MPV on Windows/macOS), or cast to "JellyPilot" from another Jellyfin client.
- Control playback: Use responsive on-screen player controls, keyboard shortcuts, the player bar, system tray, or remote media server sessions. Open the episode queue to jump anywhere in the current season.
- Switch app modes: Toggle between Full library mode and Control-Only mode (a compact standalone controller window) in Settings.
Season volume memory is enabled by default under Settings → Playback. Player volume adjustments are remembered per season on this device, separately for each server and account. The next episode, a manually selected episode, or a later session restores that season's volume before playback starts. Movies and episodes without a saved, reliably identified season use MPV's startup volume instead. This remembers the player's volume, not system volume, and does not normalize audio loudness.
Turning the switch off stops saving and restoring without deleting existing records or changing the current volume. Turning it back on restores saved values on the next load. Temporary mute is retained during continuous episode playback, but is not saved for a later playback session.
Diagnostics and player logs under Settings → Diagnostics provide retained event logging with category and level filtering. Enabling Capture player logs records raw embedded or external MPV log entries across playback events. You can export complete support logs with a single click.
One Rust workspace: jellypilot-ui owns the custom iced presentation layer, while the domain and infrastructure crates remain display-free and test-covered.
flowchart LR
subgraph JellyPilot[JellyPilot — iced application]
App[src-iced shell and screens]
UI[jellypilot-ui design system]
Core[jellypilot-core browse/config/diagnostics]
Session[jellypilot-session WebSocket remote sessions]
MS[jellypilot-media-server HTTP + artwork]
Auth[jellypilot-auth login + keyring]
Mpv[jellypilot-mpv process lifecycle + IPC]
App --> UI
App --> Core
App --> Session
App --> MS
App --> Auth
App --> Mpv
end
Session <-->|WebSocket| Server[Jellyfin / Emby server]
MS <-->|REST| Server
Server -->|original/direct source| MPV[External MPV process]
Mpv <-->|JSON IPC| MPV
Mpv <-->|JSON IPC, Linux default| Host[Embedded MPV host]
Server -->|original/direct source| Host
Host -->|10-bit Vulkan copy and sample| App
src-iced— the application: shell, screens, tray, subscriptions, orchestration, embedded compositor, and player primitives.crates/jellypilot-launcher— executable-only entry point and private unsafe engine/surface handoffs.crates/jellypilot-mpv-host— Linux host ABI, retained Vulkan device/features, DMA-BUF import extensions for VAAPI, bounded producer images, and GPU copy lifecycle.crates/jellypilot-ui— the design system: tokens, theme/Catalog styles, custom widgets, overlay, and Reicon iconography.crates/jellypilot-core— display-free browse model, personal lists, configuration, request gate, diagnostics, player logs, and artwork load planning.crates/jellypilot-media-server— Jellyfin/Emby HTTP adapter over the generated OpenAPI clients incrates/media-server-api/.crates/media-server-api— generated OpenAPI client bindings for Jellyfin and Emby APIs.crates/jellypilot-auth— login workflows, session persistence, and OS keychain token storage.crates/jellypilot-mpv— MPV process lifecycle, JSON IPC, player logs, and the authoritative Playback Session transition/projection contract.crates/jellypilot-session— media-server WebSocket transport and Playback Target capability registration.
Playback orchestration consumes the Playback Session's accepted transitions instead of predicting controller acceptance. Replacement invalidation happens when a start, stop, or teardown is accepted, including queued replacements. Presentation busy state excludes background refreshes; cleanup settlement still waits for all controller work, including detached operations.
src-iced/src/app/playback/remote.rs owns remote resources across profile initialization.
It serializes capability registration, coalesces target-name changes, and keeps reconnecting targets
unavailable to commands until current registration completes. Disconnect, profile switch, failure,
and quit share one cleanup path; late waiters join the same resource cleanup rather than starting
another close.
| Task | Command |
|---|---|
| Run the app | bun run task iced run |
| Startup smoke gate | xvfb-run -a bun run task iced run --smoke |
| Build release binary | bun run task iced build --release |
| Build embedded MPV | bun run task mpv build |
| Native regression probes | bun run task iced regress all --file <clip> |
| Check everything | bun run check |
| Format code | bun run task rust fmt && bun run task fmt |
| Rust tests | bun run task rust test |
| Rust clippy | bun run task rust clippy |
| Regenerate API clients | bun run task api |
- Rust: formatting is enforced by
bun run task rust fmt;unsafe_codeis forbidden workspace-wide; clippy warnings are errors. - Display-free logic lives in
jellypilot-coreand is tested there;src-icedkeeps orchestration and views. - Domain language: CONTEXT.md is the glossary; docs/adr/ records architecture decisions.
- Design and promo artwork: logo sources, screenshots, fonts, and the renderer live in the separate
jellypilot-designproject. From that checkout, runbun run promo, then preview publication withbun run sync:app -- /absolute/path/to/jellypilot; add--writeto copy the selected exports. This repository keeps the published files inassets/promo/andassets/screenshots/, plus the logo used by those assets.assets/promo/demo.webmanddemo-poster.webpare a recorded demo, not design-project exports.
Releases ≤ 1.4.x shipped a Tauri/Solid.js frontend with an embedded web player and a local FFmpeg HLS pipeline. That stack was retired per ADR 0027. Linux now defaults to the pinned Embedded MPV fork (ADR 0040); settings and saved profiles start fresh — no Tauri Store data is imported.