# Jellybloom A desktop media client for Jellyfin. i built this because i think nobody should have to rent back their own culture from a server they already run. This code is released under CC0 — take it, change it, run your own fork, or ignore it entirely. No attribution needed, no permission required. The culture belongs to everyone. ## What Jellybloom does Jellybloom is a complete alternative to the default Jellyfin web client, not a reskin. It's a React + TypeScript + Vite app wrapped in Tauri, and it talks directly to your Jellyfin server over the same APIs the official client uses. The difference is in the details — everything is denser, faster, and built around the idea that your library is yours to explore however you want. ### Browsing and search The home page is built from shelves you can curate yourself, or you can let the app suggest rows based on what you actually watch. There's a unified search that fuzzy-matches across titles, people, and collections, with saved searches if you find yourself looking for the same things repeatedly. The poster grid uses virtualized scrolling so it stays smooth even with thousands of items, and a right-click on any poster opens a quick-look modal so you can peek at details without losing your scroll position. A notification bell in the header tracks new episodes and movies, deduplicated by series so a full-season drop doesn't flood the list. ### The player Most of the work went into the player. It's built on Vidstack with hls.js for HLS playback, and the entire chrome layer is custom. Subtitles get special attention. The app renders ASS and SSA natively through libass-wasm, which means styled fansubs look exactly as intended. SRT, WebVTT, and plain text work too. You can search OpenSubtitles from inside the player, adjust size, font, outline, color, background opacity, and positioning. If the encode is bad, there's a picture filter for brightness, contrast, saturation, hue, and gamma. An audio graph gives you a compressor and parametric EQ for when the mix is too quiet or too loud, and you can pass through audio to your receiver when it's capable. Playback features include bookmarks with notes (shown as dots on the scrubber), A-B looping, chapter navigation, trickplay thumbnails on hover, speed control from 0.5x to 3x, and a sleep timer. There's a "still watching?" prompt that gently checks in after a few episodes so your stats don't run away from you. The resume prompt picks up where you left off but asks first, so you don't spoil a cold open. The end card shows the next episode or a related title as the credits roll. You can pop out a mini player to keep watching while doing other things, cast to another Jellyfin session on your network, or join a SyncPlay group to watch together with friends. Downloads work via blob URLs in the session, so you can save a movie or episode for offline playback. Trakt.tv scrobbling is optional and uses device-code auth — no browser redirects, no OAuth callbacks. Every keyboard action in the player and app can be rebound from the settings. ### Detail pages Every title gets a rich detail page that pulls from multiple sources. The hero shows a backdrop, poster, and logo overlay from Jellyfin or TMDB, plus rating badges and action buttons. Cast and crew are clickable, with in-library matches highlighted in a person's filmography. The composer gets their own block because the score matters. Awards come from Wikidata. Filming locations are mapped with Leaflet. Reception aggregates every available rating source — IMDb, TMDB, Rotten Tomatoes, Metacritic, plus your own. Reviews, trailers, production trivia, and tech specs (codec, container, resolution, HDR format) are all there. There's a collection strip for franchises, a series status block for air days and upcoming episodes, and anime filler detection based on a bundled dataset. If you have multiple versions of a file, a selector lets you pick. The watch timeline draws from your diary, and the personal section keeps your ratings, rewatch counts, and notes local. ### Discover The Discover page is for finding things you don't already own. It needs a free TMDB API key and respects your region and adult-content preference. There's a personalized spotlight based on your top genre, a trending pick, mood chips that resolve to filtered rows, a roulette for random highly-rated titles, decade strips, canonical lists, an "on this day" feature, and a library gap finder that shows the highest-rated missing titles in genres you already watch. You can browse by genre, language, studio, or network, with inline expansion on each tile, or use advanced filters for year range, rating, vote count, and sort order. ### Profile and stats The profile page tracks your watch streak, genre breakdown, year-in-review summary, personally top-rated titles, and recent diary entries. The diary itself lets you log watches with a date, rating, and rewatch flag, and export to Markdown, JSON, or Letterboxd CSV. ### Settings The settings page is split into sections: server connection (with support for multiple servers and a live dashboard), playback defaults, audio and subtitle preferences, display options (density, accent color, UI scale, reduced motion), home page row toggles, detail page section toggles, episode defaults, discovery preferences, TMDB and Fanart.tv keys, Trakt.tv connection, Sonarr and Radarr configuration (including multiple instances and override rules), personal data management, privacy controls, and keyboard shortcut rebinding. ## Stack React 19 and TypeScript, Vite and Tailwind CSS, Tauri 2 for the desktop shell, Zustand for state, TanStack Query for data fetching, TanStack Virtual for lists, Vidstack and hls.js for playback, libass-wasm for subtitles, Framer Motion for transitions, Leaflet for maps, Fuse.js for fuzzy search, and Radix UI for accessible primitives. ## Dev ``` npm install npm run dev # browser dev npm run tauri:dev # desktop dev npm run build # production web build npm run tauri:build # production desktop build ``` ## License CC0. This code is a common resource. The culture belongs to everyone.