# Requirements Specification This document captures JellyTau's user requirements, software requirements, traceability matrix, test traceability, and known technical debt. For a narrative overview of the system design, see [docs/architecture/](architecture/). For development workflows, see the [README](../README.md) and [scripts/README.md](../scripts/README.md). ## 1. User Requirements | ID | Requirement | Priority | Status | |----|-------------|----------|--------| | UR-001 | Run the app on multiple platforms (Linux, Android) | High | In Progress | | UR-002 | Access media when online or offline | High | Done | | UR-003 | Play videos | High | Done | | UR-004 | Play audio uninterrupted | High | Done | | UR-005 | Control media playback (pause, play, skip, scrub) | High | Done | | UR-006 | Control media when device is on lock screen or via BLE headsets | Medium | Done | | UR-007 | Navigate media in library | High | Done | | UR-008 | Search media across libraries | High | Done | | UR-009 | Connect to Jellyfin to access media | High | Done | | UR-010 | Control playback of Jellyfin remote sessions | Low | Done | | UR-011 | Download media on demand | Medium | Done | | UR-012 | Login info shall be stored securely and persistently | High | Done | | UR-013 | View and manage downloaded media | Medium | Done | | UR-014 | Make and edit playlists of music that sync back to Jellyfin | Medium | Done | | UR-015 | View and manage current audio queue (add, reorder tracks) | Medium | Done | | UR-016 | Change system settings while playing (brightness, volume) | Low | Planned | | UR-017 | Like or unlike audio, albums, movies, etc. | Medium | Done | | UR-018 | Choose to download series, albums, songs, artist discography | Medium | Done | | UR-019 | Resume playback from where you left off (movies, shows, albums) | High | Done | | UR-020 | Select subtitles for video content | High | Done | | UR-021 | Select audio track for video content | High | Done | | UR-022 | Control streaming quality and transcoding settings | Medium | Planned | | UR-023 | View "Next Up" / Continue Watching on home screen; auto-play next episode with countdown popup and configurable episode limit | Medium | Done | | UR-024 | View recently added content on server | Medium | Done | | UR-025 | Sync watch history and progress back to Jellyfin | High | Done | | UR-026 | Sleep timer for audio and video playback (roller UI, time/track/episode modes) | Low | Done | | UR-027 | Audio equalizer for sound customization | Low | Done (Linux only) | | UR-028 | Navigate to artist/album by tapping names in now playing view | High | Done | | UR-029 | Toggle between grid and list view in library | Medium | Done | | UR-030 | Quick genre browsing and filtering | Medium | Done | | UR-031 | Crossfade between audio tracks | Low | Not implemented (blocked — see DR-034) | | UR-032 | Gapless playback for seamless album listening | Medium | Done (Linux only) | | UR-033 | Volume normalization to prevent volume jumps between tracks | Low | Done (Linux only) | | UR-034 | Rich home screen with hero banners, carousels, and personalized sections | High | Done | | UR-035 | View cast/crew (actors, directors) on movie/show detail pages | High | Done | | UR-036 | Navigate to actor/person page showing their filmography | Medium | Done | | UR-037 | Visually appealing video library with poster grids and metadata | High | Done | | UR-038 | Movie/show detail page with backdrop, ratings, and rich metadata | High | Done | | UR-039 | Navigate between main sections via bottom navigation bar | High | Done | | UR-040 | Keep a video's audio playing when the app is backgrounded or the screen is locked, stopping video decode until the app returns to the foreground (per-player toggle; Android) | Medium | Done (pending device verification) | | UR-041 | Continue watching *locally-playing video* in a floating picture-in-picture window when leaving the app (Android) — PiP applies to video only, never to audio playback, library/menu browsing, or remote/cast sessions | Medium | Done | | UR-042 | Authenticate to a server and manage the session lifecycle (connect, log in, Quick Connect, background session verification, re-authenticate, log out) | High | Done | | UR-043 | Automatically detect server reachability and switch between online and offline operation without user intervention | High | Done | | UR-044 | Pin downloaded media so it is protected from automatic cache eviction | Low | Done | | UR-045 | Predictively pre-cache likely-next media (queue lookahead and album affinity) within a storage budget | Low | Done | | UR-046 | Group multiple remote players into a synchronized playback group (LMS SyncGroups) | Low | Done | | UR-047 | Manage multiple Jellyfin servers (add, list, remove) and switch the active server/account | Medium | Planned (backend store done; switcher UI pending) | | UR-048 | See the next episodes of a series directly below the episode/series being viewed, above cast and similar-shows content, so continuing a show is the shortest path (see [ux-flows.md §5B](ux-flows.md)) | High | Done | | UR-049 | Search is scoped by where it was started — inside a library it searches that library, from Home/library-root/search-tab it searches everything — with the scope shown as filter chips under the search bar that preselect from context and can be changed without retyping (see [ux-flows.md §6.1](ux-flows.md)) | High | Implemented | | UR-050 | Reorder search result groups (Songs, Albums, Artists, Movies, TV Shows) by drag and drop in settings, so the media a user cares about most appears first (see [ux-flows.md §6.3](ux-flows.md)) | Medium | Implemented | | UR-051 | Browse library pages in a consistent layout where card shape signals media type (square music, poster video, thumbnail episode), ordinal content stays listed, and the grid/list preference persists across pages (see [ux-flows.md §5A](ux-flows.md)) | Medium | Partial (implemented; toggle not reachable from settings) | | UR-052 | While offline, library pages show only media available on the device by default, with an opt-in toggle that additionally reveals the cached server catalog as greyed-out entries which can be queued for download on the next reconnect | High | Done | | UR-053 | Restrict media downloads to unmetered networks via a "WiFi Only" setting: when enabled, queued downloads are held while the device is on cellular or a metered connection (including metered WiFi hotspots) and resume automatically once an unmetered network is available | Medium | Done (pending device verification) | | UR-054 | Reach account actions (Settings, Downloads, Display preferences, Sign out) from every authenticated screen via a single account menu anchored to the user's name, identical on desktop and mobile (see [ux-flows.md §1.2](ux-flows.md)) | High | Done | | UR-055 | Browse downloaded media as an offline-scoped library — reusing the same library grids, cards, and detail pages as online browsing, showing only libraries/containers with downloaded content — with the transfer-progress list demoted to a secondary "Transfers" view (see [ux-flows.md §7.2](ux-flows.md)) | High | Done | | UR-056 | See how much disk each downloaded item/album/series consumes, in familiar rounded units shown on the card and detail page, with a device total on the Downloaded surface and a reclaim amount stated at the point of removal (see [ux-flows.md §7.3.1](ux-flows.md)) | Medium | Done | | UR-057 | Settings apply the instant a control is changed — no "Save" button and no save/dirty state — so leaving the page never loses a change; sliders show a live readout while dragging but persist on release (see [ux-flows.md §8.1](ux-flows.md)) | Medium | Done | | UR-058 | On the home screen, a tap on a media card opens the item (movie/episode detail page, or the series Episode Focus View for episodes) rather than starting playback; a long-press starts "play now" after a confirm; an episode detail/focus page links back to its parent series and season (see [ux-flows.md §5B.5](ux-flows.md) and [§5B.1](ux-flows.md)) | Medium | Done | | UR-059 | Skipping to the next episode records the episode left behind as **fully watched** rather than saving a mid-episode resume point — skipping means "done with this one", not "stopped here" — and Continue Watching hides episodes the viewer has already moved past (a partial position behind that series' next-up episode), so the row only ever offers genuinely unfinished media | Medium | Done | | UR-060 | Search results are ordered by how well they match: a name that *starts* with the query outranks one matching mid-word (typing "parks" finds "Parks and Recreation" before "Sparks of Love"), and at equal match quality a container outranks its contents (a series before its episodes). Results are grouped into distinct categories — TV Shows, Episodes, Movies, Songs, Albums, Artists and People — so a show never competes with its own episodes for the same slot, and searching an actor's name reaches their bio | High | Done | | UR-061 | Double tapping the video skips within it — right half jumps **forward 30 seconds**, left half jumps **back 10 seconds** — with an on-screen indicator naming the amount. A double tap leaves the play state unchanged — playing jumps and keeps playing, paused jumps and stays paused — because the second tap re-toggles what the first tap toggled (see DR-098); the skip lands relative to the position the player actually reports, and repeated double taps accumulate rather than all skipping from the same spot | Medium | Done | | UR-062 | Opening a TV series lands the viewer **where they are in it**, not at season 1: the series page scrolls the current season into view and highlights the current episode, and the hero button opens that episode (labelled `Resume S2E4` / `Play S1E1`). "Current" means the episode in progress, else the server's Next Up for that series, else the first unwatched episode, else the first — resolved by the backend so it also works offline. A season is **never a page of its own**: every route that names a season lands on the series with that season in view, so the episodes of all seasons are always one continuous scrollable list | High | Done | | UR-063 | Each video library is **one page**, not three. Browsing (hero, Continue Watching, Next Up, Recently Added, genre rows), the full title grid, and the genre browser are tabs of `/library/tv` and `/library/movies` rather than separate routes with inconsistent names (`/library/tv/shows` vs `/library/movies/all`, `/library/shows/genres` vs `/library/movies/genres`). The old routes redirect so existing links keep working | Medium | Done | | UR-064 | Watch history can be **erased**, per series and per season, from the series page. Clearing marks every episode inside unwatched and clears resume positions, so the show returns to "never watched" and reopens on its premiere. It asks for confirmation first (it cannot be undone) and requires a connection to the server, since history cleared only locally would be undone by the next sync | Medium | Done | | UR-065 | Search answers from a **locally indexed copy of the library**, so results appear as fast as the device can query rather than at the speed of a round trip to the server, and the same results are found with the server unreachable. A background job keeps the index current — refreshing on a schedule rather than only at app start, dropping media removed from the server, and covering everything the result groups can show (including artists and people). The server is still queried in the background so media added since the last index still turns up, merged in without reordering what is already on screen | High | Implemented | | UR-066 | The app's own chrome stays clear of the device's system chrome. On Android the bottom navigation sits above the navigation/gesture bar instead of underneath it, the header clears the status bar, and full-screen video and audio playback keep their controls inside the usable screen — clear of the gesture bar and, in landscape, of the display notch. This must hold across navigation modes (gesture and 3-button) and rotation, not only on the handsets it happened to be tested on | High | Done | | UR-067 | Favourited media can be **found again**. A Favourites page lists everything favourited across all libraries, scoped by tabs (All / Movies / Shows / Music); the home screen carries favourite rows for movies, shows and music, hidden when a category is empty; and each library page can be filtered to favourites in place. Without this the like button writes to a store nothing reads | Medium | Done | | UR-068 | Anything the app shows can be favourited where it is shown — from a movie, series, episode, album, artist or playlist page, and from any card in a grid or carousel — not only from the player while the item happens to be playing | Medium | Done | | UR-069 | Favourite state agrees with the server in both directions. An item favourited in another Jellyfin client shows as favourited here without being touched, and an item favourited here while the server is unreachable reaches the server once it returns — without the user going back to the screen where they marked it | Medium | Done | | UR-070 | Playback quality is the viewer's choice: the player offers the bitrates the server can produce for what is playing, and changing one resumes at the same point with the same audio and subtitle tracks. Because the chosen rendition can change at any moment, nothing that streams for playback is treated as a stored copy unless it happens to be byte-identical to the real file | Medium | Proposed | | UR-071 | Media the viewer is watching can be **kept**, by a whole-file download that runs in the background independently of playback and at its own quality, so it is unaffected by bitrate changes. Where the streamed bytes already are that file (direct play), they are kept rather than fetched twice. A completed download is then played from disk rather than streamed again | Medium | Proposed | | UR-073 | Watched state is something the viewer can **set**, not only something playback records. Any episode, season, series or movie can be marked watched — or unwatched again — from where it is shown, without sitting through it or erasing its history wholesale. Marking a season or series covers the episodes inside it, and works with the server unreachable | Medium | Done | | UR-072 | Each page opens where a page should open. Moving to a new screen starts at the top of it, and going Back returns the viewer to the place they left — their position in a long library grid or home screen, not the top of it. A page never inherits the scroll position of the page before it | Medium | Done | | UR-074 | Video streaming can be held to a **bandwidth budget the viewer sets**, rather than spent at whatever rate the server would otherwise send. A ceiling chosen once — from the source's own bitrate down to a rung that still plays on a poor connection — governs every video the app opens, live TV included, and survives a restart, so a metered connection is not quietly drained by the next thing played. A single video can be moved to a different ceiling from the player, resuming where it was, without disturbing that default | Medium | Done | --- ## 2. Software Requirements ### 2.1 Integration Requirements External system integrations and platform-specific implementations. | ID | Requirement | Category | Traces To | Status | |----|-------------|----------|-----------|--------| | IR-001 | Build system supporting multiple targets (Linux, Android) | Build | UR-001 | Done | | IR-002 | Build scripts for Android and Linux | Build | UR-001 | Done | | IR-003 | Integration of libmpv for Linux playback | Playback | UR-003, UR-004 | Done | | IR-004 | Integration of ExoPlayer for Android playback | Playback | UR-003, UR-004 | In Progress (basic playback works, audio settings missing) | | IR-005 | MPRIS D-Bus integration for Linux lockscreen/media controls | Platform | UR-006 | Planned | | IR-006 | Android MediaSession integration for lockscreen controls | Platform | UR-006 | Done | | IR-007 | Bluetooth AVRCP integration via system media session | Platform | UR-006 | Planned | | IR-008 | Android audio focus handling (pause on call) | Platform | UR-004, UR-006 | Done | | IR-009 | Jellyfin API client for authentication | API | UR-009, UR-012 | Done | | IR-010 | Jellyfin API client for library browsing | API | UR-007, UR-008 | Done | | IR-011 | Jellyfin API client for playback streaming | API | UR-003, UR-004 | Done | | IR-012 | Jellyfin Sessions API for remote playback control | API | UR-010 | Done | | IR-021 | Android MediaRouter integration for remote volume in system panel | Platform | UR-010, UR-016 | Planned | | IR-013 | SQLite integration for local database | Storage | UR-002, UR-011 | Done | | IR-014 | Secure credential storage (keyring/keychain) | Security | UR-012 | Done | | IR-015 | Jellyfin API client for playback progress reporting | API | UR-019, UR-025 | Done | | IR-016 | Jellyfin API client for subtitle/audio track info | API | UR-020, UR-021 | Done | | IR-017 | Jellyfin API client for transcoding parameters | API | UR-022 | Planned | | IR-018 | libmpv subtitle rendering and selection | Playback | UR-020 | Planned | | IR-019 | libmpv audio track selection | Playback | UR-021 | Planned | | IR-020 | libmpv/ExoPlayer equalizer integration | Playback | UR-027 | Done (Linux/MPV; Android parity pending) | | IR-022 | Jellyfin API client for person/cast data | API | UR-035, UR-036 | Done | | IR-023 | Database schema for person/cast caching | Storage | UR-035, UR-036 | Done | | IR-024 | Jellyfin API client for home screen data (featured, continue watching) | API | UR-034 | Done | | IR-025 | Android background-audio handoff: WebView `