Add specs for the account menu, downloads-as-offline-library, offline downloaded-only filter, and scoped search (+ boundary revision). Add the new UR/DR entries to requirements.md, update ux-flows, and regenerate the traceability matrix. TRACES: UR-049, UR-050, UR-052, UR-053, UR-054, UR-055, UR-056
538 lines
42 KiB
Markdown
538 lines
42 KiB
Markdown
# 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 | Planned |
|
|
| 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 | Done (Linux only) |
|
|
| 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 | Broken (toggle does not gate the listing; see issue #10) |
|
|
| 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 | Planned |
|
|
| 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 | Planned |
|
|
|
|
---
|
|
|
|
## 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 | Planned |
|
|
| 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 `<video>` → native ExoPlayer foreground service on background/lock, and back on foreground (audio continues, video decode stops) | Platform | UR-040 | Done (pending device verification) |
|
|
| IR-026 | Android picture-in-picture: auto-enter on user-leave-hint via `enterPictureInPictureMode`, **only while a local video surface is actively rendering** (never for audio-only playback, menu/library browsing, or remote/cast sessions — enforced by the native `canEnterPip` guard, re-checked at leave time); aspect-ratio sizing; a play/pause RemoteAction that **reflects live player play/pause state** (updated whenever playback state changes, not only on button press); WebView hide/restore on mode change | Platform | UR-041 | Done |
|
|
| IR-027 | Jellyfin `/System/Info/Public` reachability probe used as an offline→online recovery detector | API | UR-043 | Done |
|
|
| IR-028 | Jellyfin/LMS SyncGroups API client (list, create, join, unsync, dissolve sync groups) | API | UR-046 | Done |
|
|
| IR-029 | Android `ConnectivityManager`/`NetworkCapabilities` transport probe with a `NetworkCallback` change subscription, surfaced to the frontend via the `AndroidNetworkType` JS bridge and the `jellytau-network-changed` WebView event (requires `ACCESS_NETWORK_STATE`) | Platform | UR-053 | Done (pending device verification) |
|
|
|
|
### 2.2 Jellyfin API Requirements
|
|
|
|
API endpoints and data contracts required for Jellyfin integration.
|
|
|
|
| ID | Requirement | Endpoint Category | Traces To | Status |
|
|
|----|-------------|-------------------|-----------|--------|
|
|
| JA-001 | Server connection and discovery | System | UR-009 | Done |
|
|
| JA-002 | User authentication (username/password) | Users | UR-009, UR-012 | Done |
|
|
| JA-003 | Get user library views | UserViews | UR-007 | Done |
|
|
| JA-004 | Get library items (paginated) | Items | UR-007 | Done |
|
|
| JA-005 | Get item details and metadata | Items | UR-007 | Done |
|
|
| JA-006 | Search across libraries | Items | UR-008 | Done |
|
|
| JA-007 | Get playback info and stream URL | MediaInfo | UR-003, UR-004 | Done |
|
|
| JA-008 | Get available subtitles for item | MediaInfo | UR-020 | Done |
|
|
| JA-009 | Get available audio tracks for item | MediaInfo | UR-021 | Done |
|
|
| JA-010 | Report playback start | Sessions | UR-025 | Done |
|
|
| JA-011 | Report playback progress (periodic) | Sessions | UR-025 | Done |
|
|
| JA-012 | Report playback stopped | Sessions | UR-025 | Done |
|
|
| JA-013 | Get resume position for item | UserData | UR-019 | Done |
|
|
| JA-014 | Get "Next Up" items | Shows | UR-023 | Done |
|
|
| JA-015 | Get "Continue Watching" items | Items | UR-023 | Done |
|
|
| JA-016 | Get recently added items | Items | UR-024 | Done |
|
|
| JA-017 | Mark item as favorite | UserData | UR-017 | Done |
|
|
| JA-018 | Remove item from favorites | UserData | UR-017 | Done |
|
|
| JA-019 | Get/create/update playlists | Playlists | UR-014 | Done |
|
|
| JA-020 | Add/remove items from playlist | Playlists | UR-014 | Done |
|
|
| JA-021 | Get active sessions list | Sessions | UR-010 | Done |
|
|
| JA-022 | Send playback commands to remote session (play/pause/stop) | Sessions | UR-010 | Done |
|
|
| JA-023 | Send seek command to remote session | Sessions | UR-010 | Done |
|
|
| JA-024 | Send next/previous track commands to remote session | Sessions | UR-010 | Done |
|
|
| JA-025 | Play specific item on remote session | Sessions | UR-010 | Done |
|
|
| JA-026 | Send volume/mute commands to remote session | Sessions | UR-010 | Done |
|
|
| JA-027 | Get transcoding options | MediaInfo | UR-022 | Planned |
|
|
| JA-028 | Get image/artwork URLs | Images | UR-007 | Done |
|
|
| JA-029 | Get cast/crew for item (actors, directors) | Items | UR-035 | Done |
|
|
| JA-030 | Get person details and filmography | Persons | UR-036 | Done |
|
|
| JA-031 | Get items by person (actor/director filmography) | Items | UR-036 | Done |
|
|
| JA-032 | Get audio-only stream URL for a video item (selected audio-stream index) | MediaInfo | UR-040 | Done |
|
|
|
|
### 2.3 Development Requirements
|
|
|
|
Internal architecture, components, and application logic.
|
|
|
|
| ID | Requirement | Category | Traces To | Status |
|
|
|----|-------------|----------|-----------|--------|
|
|
| DR-001 | Player state machine (idle, loading, playing, paused, seeking, error) | Player | UR-005 | Done |
|
|
| DR-002 | MediaItem struct tracking source, location, duration, metadata | Player | UR-003, UR-004 | Done |
|
|
| DR-003 | Source-agnostic media abstraction (Remote, Local, DirectUrl) | Player | UR-002, UR-011 | Done |
|
|
| DR-004 | PlayerBackend trait for platform-agnostic playback | Player | UR-003, UR-004 | Done |
|
|
| DR-005 | Queue manager with shuffle, repeat, history | Player | UR-005, UR-015 | Done |
|
|
| DR-006 | Audio pre-caching for seamless track transitions | Player | UR-004 | Planned |
|
|
| DR-007 | Library browsing screens (grid view, search, filters) | UI | UR-007, UR-008 | Done |
|
|
| DR-008 | Album/Series detail view with track listing | UI | UR-007 | Done |
|
|
| DR-009 | Audio player UI (mini player, full screen) | UI | UR-005 | Done |
|
|
| DR-010 | Video player UI (fullscreen, controls overlay) | UI | UR-003, UR-005 | Done |
|
|
| DR-011 | Search bar with cross-library search | UI | UR-008 | Done |
|
|
| DR-012 | Local database for media metadata cache | Storage | UR-002 | Done |
|
|
| DR-013 | Repository pattern for online/offline data access | Storage | UR-002 | Done |
|
|
| DR-014 | Offline mutation queue for sync-back operations | Storage | UR-002, UR-014, UR-017 | Done |
|
|
| DR-015 | Download manager with queue and progress tracking | Storage | UR-011, UR-018 | Done |
|
|
| DR-016 | Thumbnail caching and sync with server | Storage | UR-007 | Done |
|
|
| DR-017 | "Manage Downloads" screen for local media management | UI | UR-013 | Done |
|
|
| DR-018 | Download buttons on library/album/player screens | UI | UR-011, UR-018 | Done |
|
|
| DR-019 | Playlist creation and editing UI | UI | UR-014 | Done |
|
|
| DR-020 | Queue management UI (add, remove, reorder) | UI | UR-015 | Done |
|
|
| DR-021 | Like/favorite functionality on media items | UI | UR-017 | Done |
|
|
| DR-022 | Resume position tracking and restoration on play | Player | UR-019 | Done |
|
|
| DR-023 | Subtitle selection UI in video player | UI | UR-020 | Done |
|
|
| DR-024 | Audio track selection UI in video player | UI | UR-021 | Done |
|
|
| DR-025 | Quality/transcoding settings UI | UI | UR-022 | Planned |
|
|
| DR-026 | "Continue Watching" / "Next Up" home section | UI | UR-023 | Done |
|
|
| DR-027 | "Recently Added" home section | UI | UR-024 | Done |
|
|
| DR-028 | Playback progress sync service (periodic reporting) | Player | UR-025 | Done |
|
|
| DR-029 | Sleep timer with roller UI, time/track/episode modes, and auto-stop (audio + video players) | Player | UR-026 | Done |
|
|
| DR-049 | Auto-play episode limit (configurable max episodes per session) | Player | UR-023 | Done |
|
|
| DR-050 | Reusable scroll picker (roller) component | UI | UR-026 | Done |
|
|
| DR-030 | Equalizer UI with presets and custom bands | UI | UR-027 | Planned |
|
|
| DR-031 | Clickable artist/album links in now playing view | UI | UR-028 | Done |
|
|
| DR-032 | List view option for library browsing (albums, artists) | UI | UR-029 | Done |
|
|
| DR-033 | Genre browsing screen with quick filters | UI | UR-030 | Done |
|
|
| DR-034 | Crossfade engine with configurable duration (0-12s) | Player | UR-031 | Done (Linux only) |
|
|
| DR-035 | Gapless playback between sequential tracks | Player | UR-032 | Done (Linux only) |
|
|
| DR-036 | Volume normalization with preset levels (Loud/Normal/Quiet) | Player | UR-033 | Done (Linux only) |
|
|
| DR-037 | Remote session browser and control UI | UI | UR-010 | Done |
|
|
| DR-038 | Home screen with hero banner carousel (featured/continue watching) | UI | UR-034 | Done |
|
|
| DR-039 | Home screen horizontal carousels (recently added, recommendations) | UI | UR-034, UR-024 | Done |
|
|
| DR-040 | Cast/crew section on movie/show detail pages | UI | UR-035 | Done |
|
|
| DR-041 | Person/actor detail page with filmography grid | UI | UR-036 | Done |
|
|
| DR-042 | Video library grid with poster cards, year, and rating badges | UI | UR-037 | Done |
|
|
| DR-043 | Movie/show detail page with backdrop hero, synopsis, and metadata | UI | UR-038 | Done |
|
|
| DR-044 | Horizontal scrolling actor/cast row with profile images | UI | UR-035 | Done |
|
|
| DR-045 | Bottom navigation bar with Home, Library, Search buttons | UI | UR-039 | Done |
|
|
| DR-046 | Dedicated search page with input and results | UI | UR-039 | Done |
|
|
| DR-047 | Next episode auto-play popup with configurable countdown and episode limit | Player | UR-023 | Done |
|
|
| DR-048 | Video settings (auto-play toggle, countdown duration, episode limit) | Settings | UR-023, UR-026 | Done |
|
|
| DR-051 | Background-audio toggle button in the video player controls (suppresses auto-PiP while enabled) | UI | UR-040 | Done (pending device verification) |
|
|
| DR-052 | Background-audio handoff state machine: on background/lock tear down the WebView `<video>`/HLS decode and start native audio-only playback at the current position; on foreground return position and resume `<video>`; exactly one audio source active at every transition (no dual audio) | Player | UR-040 | Done (pending device verification) |
|
|
| DR-053 | PictureInPictureManager: `canEnterPip` gate (local video surface actively rendering — false for audio, browsing, and remote/cast), aspect-ratio clamp, a RemoteAction play/pause receiver whose icon reflects live player state (refreshed on every playback-state change while in PiP, not only on button press), WebView hide/restore, surface re-fit on exit; plus the `AndroidPictureInPicture` JS bridge and the PiP button (shown only when PiP is supported) in the video player | UI | UR-041 | Done |
|
|
| DR-054 | Auth manager and session lifecycle: connect-to-server, login, Quick Connect verification poll (start/stop), session get/set, background session verifier, re-authenticate, logout | Auth | UR-042 | Done |
|
|
| DR-055 | ConnectivityMonitor deriving reachability from real repository traffic, with online/offline state, mark-reachable/unreachable reporting, and a probe-based recovery poller active only while offline | Connectivity | UR-043 | Done |
|
|
| DR-056 | Download pinning (pin/unpin/is-pinned) that excludes an item from smart-cache eviction | Storage | UR-044 | Done |
|
|
| DR-057 | Smart cache manager: album-affinity tracking, queue-lookahead pre-cache, storage-limit enforcement, config, stats, and recommendations | Storage | UR-045 | Done |
|
|
| DR-058 | Remote sync-group control (LMS SyncGroups): list, create, unsync a player, dissolve a group | Player | UR-046 | Done |
|
|
| DR-059 | Playback-mode transfer state machine: get/set current mode, transferring guard, transfer-to-remote / transfer-to-local, remote session status | Player | UR-010 | Done |
|
|
| DR-060 | Multi-server store and active-account selection: save/get/delete server, save/get user, set/get active user (per-server), active-session resolution | Storage | UR-047 | Partial (store done; server-switcher UI pending) |
|
|
| DR-061 | Episode Focus View: episode hero followed *immediately* by the "More Episodes" strip — a forward-biased window (~3 before / ~6 after) around the current episode, spanning season boundaries in series order, with the current episode present and badged, per-card resume progress and watched state, and click-to-swap focus (no playback) | UI | UR-048 | Done |
|
|
| DR-062 | Detail-page section ordering: continuation content precedes discovery content — Episode Focus View renders hero → episode strip → cast → similar; Series renders hero → seasons/episodes → cast → similar | UI | UR-048 | Done |
|
|
| DR-063 | Search scope resolver mapping the originating route to an `includeItemTypes` set (All / Music / Movies / TV), defaulting to All for Home, `/library`, and the search tab | UI | UR-049 | Implemented |
|
|
| DR-064 | Scope chip row rendered under the search bar on both the search page and the in-library header search: preselected from context, horizontally scrollable, re-runs the search preserving the query on change | UI | UR-049 | Implemented |
|
|
| DR-065 | Thread `SearchOptions.includeItemTypes` through `library.search()` so the global/header search honours scope (backend online + offline paths already support it) | UI | UR-049 | Implemented |
|
|
| DR-066 | Persisted search result group order with a drag-and-drop settings list, keyboard-accessible reordering, a shipped default (Songs → Albums → Artists → Movies → TV Shows), and empty-group omission | Settings | UR-050 | Implemented |
|
|
| DR-067 | `SearchResults` renders groups in the user-configured order rather than hardcoded markup order, without altering intra-group ranking | UI | UR-050 | Implemented |
|
|
| DR-068 | Library card shape by media type: 1:1 square for music (circular mask for artists), 2:3 poster for movies/series/seasons, 16:9 for episodes and collection folders | UI | UR-051 | Done |
|
|
| DR-069 | Responsive library grid (2/3/4/5/6 columns across base→xl) with two-line truncated card text and artwork-overlay progress/watched state | UI | UR-051 | Done |
|
|
| DR-070 | Global persisted grid/list view preference honoured by browse pages, suppressed for ordinal content (album tracks, season episodes) | UI | UR-051, UR-029 | Partial (persisted store + page-header toggle; no settings entry) |
|
|
| DR-075 | Shared `AccountMenu` component: identity header (user + server), Downloads / Settings / Display entries, divider, Sign out last; anchored to the username/avatar trigger and identical on desktop and mobile | UI | UR-054 | Done |
|
|
| DR-076 | App shell exposes the header (and therefore the account menu) on every authenticated non-immersive route, including `/`, `/search`, and `/downloads`; only `/player/*` and `/login` remain chrome-free | UI | UR-054 | Done |
|
|
| DR-077 | Display section in Settings binding the existing persisted grid/list `viewMode` store, giving the preference a discoverable home | Settings | UR-054, UR-029 | Done |
|
|
| DR-078 | Catalog-visibility gate spanning the "Show all server media" toggle → `set_show_server_catalog` → `INCLUDE_CATALOG_BROWSE` → the synced-catalog UNION branch of offline `get_items`. Visibility resolves to `serverReachable \|\| showServerCatalog`, so offline with the toggle off lists downloaded/local media only | Storage | UR-052, UR-002 | Partial (gate implemented and unit-tested; defeated upstream by DR-079 and by the repository fallback in DR-080) |
|
|
| DR-079 | `isConnected` derives from backend-reported server reachability alone; `navigator.onLine` is advisory and may only trigger a recheck, never force or clear the offline state (a reachable LAN server while the browser reports offline, and an unreachable server on a live link, must both resolve correctly) | Connectivity | UR-052, UR-043 | Broken (`isConnected` ANDs in `navigator.onLine`, so a live link with an unreachable server never enters offline listing) |
|
|
| DR-080 | With the catalog-browse gate off, an empty offline `get_items` result is authoritative "no downloads here" and must be returned as-is; the hybrid repository must not treat it as a cache miss and fall through to the server | Storage | UR-052, UR-013 | Broken (`has_content()` cache-hit test in `HybridRepository::get_items`/`parallel_race` falls through to the server on an intentionally empty result) |
|
|
| DR-074 | WiFi-only download gate: `NetworkState`/`NetworkType` transport model reported from the platform via `set_network_state`, checked in `pump_download_queue` before starting any pending row (cellular/metered/unknown fail closed, WiFi and Ethernet require `NOT_METERED`); blocked rows stay `pending` and re-pump on network change, with a `waitingForNetwork` event driving the "Waiting for WiFi" notice. Also wires the previously inert Smart Caching / Queue Pre-caching / WiFi Only settings toggles to `CacheConfig` | Downloads | UR-053 | Done (pending device verification) |
|
|
| DR-081 | `/downloads` split into a default **Downloaded** browse view and a secondary **Transfers** activity view, with a view switch and a Transfers badge shown only while transfers are active | UI | UR-055 | Planned |
|
|
| DR-082 | Offline-scoped browse entry point in the repository client: browse downloaded content only (offline repository `get_items`/`get_libraries` — downloaded items plus their containers) independent of server reachability, without merging server catalog | Storage | UR-055 | Planned |
|
|
| DR-083 | Downloaded browse reuses library grids, cards, and detail pages via the offline-scoped source; omits libraries/containers with no downloaded content; badges partially- vs fully-downloaded containers; play uses the local file; remove available at item/album/season/series level | UI | UR-055 | Planned |
|
|
| DR-084 | Transfers view renders only in-flight rows (downloading/queued/paused/failed/waiting-for-WiFi) with Pause/Resume/Cancel/Retry; completed transfers leave the view and appear in Downloaded | UI | UR-055 | Planned |
|
|
| DR-085 | Per-item on-disk size: stat downloaded files, aggregate to album/season/series subtotals and a device total, format in consistent rounded human units; surface size on cards and detail pages, the device total on the Downloaded surface, and a reclaim figure in the remove confirmation | Downloads | UR-056 | Planned |
|
|
|
|
---
|
|
|
|
## 3. Traceability Matrix
|
|
|
|
### User Requirements to Software Requirements
|
|
|
|
| User Req | Integration Requirements | Development Requirements |
|
|
|----------|-------------------------|-------------------------|
|
|
| UR-001 | IR-001, IR-002 | - |
|
|
| UR-002 | IR-013 | DR-003, DR-012, DR-013, DR-014 |
|
|
| UR-003 | IR-003, IR-004, IR-011 | DR-002, DR-004, DR-010 |
|
|
| UR-004 | IR-003, IR-004, IR-008, IR-011 | DR-002, DR-004, DR-006 |
|
|
| UR-005 | - | DR-001, DR-005, DR-009 |
|
|
| UR-006 | IR-005, IR-006, IR-007, IR-008 | - |
|
|
| UR-007 | IR-010 | DR-007, DR-008, DR-016 |
|
|
| UR-008 | IR-010 | DR-007, DR-011 |
|
|
| UR-009 | IR-009, IR-010, IR-011 | - |
|
|
| UR-010 | IR-012, IR-021 | DR-037, DR-059 |
|
|
| UR-011 | IR-013 | DR-003, DR-015, DR-018 |
|
|
| UR-012 | IR-009, IR-014 | - |
|
|
| UR-013 | IR-013 | DR-017 |
|
|
| UR-014 | IR-010 | DR-014, DR-019 |
|
|
| UR-015 | - | DR-005, DR-020 |
|
|
| UR-016 | - | - |
|
|
| UR-017 | - | DR-014, DR-021 |
|
|
| UR-018 | IR-013 | DR-015, DR-018 |
|
|
| UR-019 | IR-015 | DR-022 |
|
|
| UR-020 | IR-016, IR-018 | DR-023 |
|
|
| UR-021 | IR-016, IR-019 | DR-024 |
|
|
| UR-022 | IR-017 | DR-025 |
|
|
| UR-023 | IR-010 | DR-026, DR-047, DR-048, DR-049 |
|
|
| UR-024 | IR-010 | DR-027 |
|
|
| UR-025 | IR-015 | DR-028 |
|
|
| UR-026 | - | DR-029, DR-048, DR-050 |
|
|
| UR-027 | IR-020 | DR-030 |
|
|
| UR-028 | - | DR-031 |
|
|
| UR-029 | - | DR-032 |
|
|
| UR-030 | IR-010 | DR-033 |
|
|
| UR-031 | - | DR-034 |
|
|
| UR-032 | - | DR-035 |
|
|
| UR-033 | - | DR-036 |
|
|
| UR-034 | IR-010, IR-024 | DR-038, DR-039 |
|
|
| UR-035 | IR-022, IR-023 | DR-040, DR-044 |
|
|
| UR-036 | IR-022, IR-023 | DR-041 |
|
|
| UR-037 | IR-010 | DR-042 |
|
|
| UR-038 | IR-010 | DR-043 |
|
|
| UR-039 | - | DR-045, DR-046 |
|
|
| UR-040 | IR-025 | DR-051, DR-052 |
|
|
| UR-041 | IR-026 | DR-053 |
|
|
| UR-042 | IR-009, IR-014 | DR-054 |
|
|
| UR-043 | IR-027 | DR-055 |
|
|
| UR-044 | - | DR-056 |
|
|
| UR-045 | - | DR-057 |
|
|
| UR-046 | IR-028 | DR-058 |
|
|
| UR-047 | IR-013 | DR-060 |
|
|
| UR-048 | - | DR-061, DR-062 |
|
|
| UR-049 | IR-010 | DR-063, DR-064, DR-065 |
|
|
| UR-050 | - | DR-066, DR-067 |
|
|
| UR-051 | - | DR-068, DR-069, DR-070 |
|
|
| UR-052 | IR-027 | DR-078, DR-079, DR-080 |
|
|
| UR-053 | IR-029 | DR-074 |
|
|
| UR-054 | - | DR-075, DR-076, DR-077 |
|
|
| UR-055 | - | DR-081, DR-082, DR-083, DR-084 |
|
|
| UR-056 | - | DR-085 |
|
|
|
|
---
|
|
|
|
## 4. Test Traceability
|
|
|
|
### Unit Tests to Software Requirements
|
|
|
|
| Test ID | Test Description | Traces To | Status |
|
|
|---------|-----------------|-----------|--------|
|
|
| UT-001 | Player state transitions | DR-001 | Pending |
|
|
| UT-002 | MediaItem source URL resolution | DR-002, DR-003 | Pending |
|
|
| UT-003 | Queue next/previous navigation | DR-005 | Pending |
|
|
| UT-004 | Queue shuffle order generation | DR-005 | Pending |
|
|
| UT-005 | Queue repeat mode behavior | DR-005 | Pending |
|
|
| UT-006 | Jellyfin authentication flow | IR-009 | Pending |
|
|
| UT-007 | Jellyfin library items parsing | IR-010 | Pending |
|
|
| UT-008 | Repository pattern online/offline switching | DR-013 | Pending |
|
|
| UT-009 | Offline mutation queue persistence | DR-014 | Pending |
|
|
| UT-010 | Download queue management | DR-015 | Done |
|
|
| UT-011 | Resume position storage and retrieval | DR-022 | Pending |
|
|
| UT-012 | Sleep timer countdown logic | DR-029 | Pending |
|
|
| UT-013 | Playback progress reporting throttling | DR-028 | Pending |
|
|
| UT-014 | Database open and in-memory mode | IR-013, DR-012 | Done |
|
|
| UT-015 | Database migrations run successfully | IR-013, DR-012 | Done |
|
|
| UT-016 | All database tables created | IR-013, DR-012 | Done |
|
|
| UT-017 | FTS5 search table created | IR-013, DR-012 | Done |
|
|
| UT-018 | Server CRUD operations | IR-013, DR-012 | Done |
|
|
| UT-019 | User CRUD operations | IR-013, DR-012 | Done |
|
|
| UT-020 | Cascade delete server removes users | IR-013, DR-012 | Done |
|
|
| UT-021 | Item insert and FTS search | IR-013, DR-012 | Done |
|
|
| UT-022 | User data playback position storage | IR-013, DR-012, DR-022 | Done |
|
|
| UT-023 | Sync queue operations | IR-013, DR-014 | Done |
|
|
| UT-024 | Downloads table operations | IR-013, DR-015 | Done |
|
|
| UT-025 | Migrations are idempotent | IR-013, DR-012 | Done |
|
|
| UT-026 | NullBackend volume default value | DR-004 | Done |
|
|
| UT-027 | NullBackend set volume | DR-004 | Done |
|
|
| UT-028 | NullBackend volume clamping (high/low) | DR-004 | Done |
|
|
| UT-029 | NullBackend volume boundary values | DR-004 | Done |
|
|
| UT-030 | PlayerController volume default | DR-004, DR-009 | Done |
|
|
| UT-031 | PlayerController set volume | DR-004, DR-009 | Done |
|
|
| UT-032 | PlayerController muted default | DR-004, DR-009 | Done |
|
|
| UT-033 | PlayerController volume delegates to backend | DR-004, DR-009 | Done |
|
|
| UT-034 | Download event serialization roundtrip | DR-015 | Done |
|
|
| UT-035 | Download event completed serialization | DR-015 | Done |
|
|
| UT-036 | Download event failed serialization | DR-015 | Done |
|
|
| UT-037 | Download worker exponential backoff | DR-015 | Done |
|
|
| UT-038 | Download worker error retryable check | DR-015 | Done |
|
|
| UT-039 | Download manager creation | DR-015 | Done |
|
|
| UT-040 | Download manager set max concurrent | DR-015 | Done |
|
|
| UT-041 | Download info serialization | DR-015 | Done |
|
|
| UT-042 | Download command filename sanitization | DR-015, DR-018 | Done |
|
|
| UT-043 | Download command filename extension preservation | DR-015, DR-018 | Done |
|
|
| UT-044 | Offline item serialization | DR-017 | Done |
|
|
| UT-045 | Smart cache default config | DR-015 | Done |
|
|
| UT-046 | Smart cache album affinity tracking | DR-015 | Done |
|
|
| UT-047 | Smart cache queue precache config | DR-015 | Done |
|
|
| UT-048 | Smart cache storage limit check | DR-015 | Done |
|
|
| UT-049 | Playlist create (offline) | DR-019, JA-019 | Done |
|
|
| UT-050 | Playlist delete (offline) | DR-019, JA-019 | Done |
|
|
| UT-051 | Playlist rename (offline) | DR-019, JA-019 | Done |
|
|
| UT-052 | Playlist get items (offline) | DR-019, JA-019 | Done |
|
|
| UT-053 | Playlist add items (offline) | DR-019, JA-020 | Done |
|
|
| UT-054 | Playlist remove items (offline) | DR-019, JA-020 | Done |
|
|
| UT-055 | Playlist reorder items (offline) | DR-019, JA-020 | Done |
|
|
| UT-056 | Playlist entry serialization | DR-019, JA-019 | Done |
|
|
| UT-057 | Playlist Tauri command param naming (camelCase) | DR-019, JA-019, JA-020 | Done |
|
|
| UT-058 | Playlist repository client methods | DR-019, JA-019, JA-020 | Done |
|
|
| UT-059 | Audio-only stream URL builder for a video item (selected audio-stream index) | JA-032, DR-052 | Pending |
|
|
| UT-060 | Background-audio handoff state machine (background→audio, foreground→video; no dual audio) | DR-052 | Pending |
|
|
| UT-061 | Background-audio Tauri command param naming (camelCase) | DR-052 | Pending |
|
|
| UT-067 | Offline `get_items` gates the synced-catalog UNION on the catalog-browse flag (downloads only when off, full catalog when on) | DR-078 | Done |
|
|
| UT-068 | Catalog visibility resolves to `serverReachable \|\| showServerCatalog`, and is pushed to the backend on every change of either input | DR-078, DR-079 | Pending |
|
|
| UT-069 | `isConnected` follows backend reachability alone: false when the server is unreachable on a live link, true for a reachable server while `navigator.onLine` is false | DR-079 | Pending |
|
|
| UT-070 | Hybrid `get_items` returns an empty offline result as-is when the catalog-browse gate is off, without querying the server | DR-080 | Pending |
|
|
| UT-066 | WiFi-only download gate: cellular and metered WiFi blocked, unmetered WiFi/Ethernet allowed, unknown/none fail closed, desktop default ungated; plus the frontend network reporter (transport reporting, change subscription, teardown, fail-open queries) | DR-074 | Done |
|
|
|
|
### Integration Tests
|
|
|
|
| Test ID | Test Description | Traces To | Status |
|
|
|---------|-----------------|-----------|--------|
|
|
| IT-001 | End-to-end authentication with Jellyfin server | IR-009, UR-009 | Pending |
|
|
| IT-002 | Library browsing and item loading | IR-010, UR-007 | Pending |
|
|
| IT-003 | Audio playback via libmpv | IR-003, UR-004 | Pending |
|
|
| IT-004 | Video playback via libmpv | IR-003, UR-003 | Pending |
|
|
| IT-005 | MPRIS lockscreen controls on Linux | IR-005, UR-006 | Pending |
|
|
| IT-006 | Offline mode with local database | IR-013, UR-002 | Pending |
|
|
| IT-007 | Media download and local playback | DR-015, UR-011 | Pending |
|
|
| IT-008 | Subtitle track selection via libmpv | IR-018, UR-020 | Pending |
|
|
| IT-009 | Audio track selection via libmpv | IR-019, UR-021 | Pending |
|
|
| IT-010 | Playback progress sync to Jellyfin | IR-015, UR-025 | Pending |
|
|
| IT-011 | Resume playback from server position | IR-015, UR-019 | Pending |
|
|
| IT-012 | Equalizer bands via libmpv | IR-020, UR-027 | Pending |
|
|
| IT-013 | Background-audio handoff on Android: background/lock continues audio via native service and stops video decode; foreground resumes video at position | IR-025, UR-040 | Pending |
|
|
| IT-016 | Offline library listing end-to-end: with the server unreachable, a library page lists only downloaded media with the toggle off, and additionally reveals greyed-out cached catalog entries with the toggle on | UR-052, DR-078, DR-079, DR-080 | Pending |
|
|
| IT-017 | A download queued from a greyed-out offline catalog entry persists and is resolved and started on reconnect | UR-052, UR-011 | Pending |
|
|
|
|
---
|
|
|
|
## 5. Technical Debt
|
|
|
|
### Linux Keyring Integration Workaround
|
|
|
|
**Issue**: The `keyring-rs` crate (v3.x) has issues with retrieving credentials from the Linux Secret Service API, despite successfully saving them.
|
|
|
|
**Symptoms**:
|
|
- Credentials are saved to the system keyring successfully (verified with `secret-tool search`)
|
|
- Retrieval via the `keyring-rs` library fails with `NoEntry` error
|
|
- Session restoration fails on app restart even though credentials exist
|
|
|
|
**Root Cause**:
|
|
The `keyring-rs` library's Linux backend doesn't correctly retrieve entries from the Secret Service that it previously stored. This appears to be a bug in how the library interfaces with the Secret Service D-Bus API.
|
|
|
|
**Current Workaround**:
|
|
We bypass the `keyring-rs` library on Linux and use direct system calls to `secret-tool`:
|
|
- **Save**: `secret-tool store --label <label> service <service> username <username>`
|
|
- **Retrieve**: `secret-tool lookup service <service> username <username>`
|
|
- **Delete**: `secret-tool clear service <service> username <username>`
|
|
|
|
**Implementation**:
|
|
See [src-tauri/src/credentials.rs](../src-tauri/src/credentials.rs) for the
|
|
Linux-specific `secret-tool` save/get/delete paths.
|
|
|
|
**Future Fix**:
|
|
- Monitor `keyring-rs` for bug fixes in future versions
|
|
- Consider alternative secure storage libraries
|
|
- Test if newer versions of `keyring-rs` (v4.x+) resolve the issue
|
|
- Once fixed, remove the Linux-specific workaround and use the cross-platform `keyring-rs` API
|
|
|
|
**Impact**:
|
|
- Low - The workaround is functionally equivalent to proper keyring integration
|
|
- Credentials are stored securely in the system keyring
|
|
- Session restoration works correctly
|
|
- Only affects Linux; macOS and Windows use the standard `keyring-rs` implementation
|
|
|
|
**Dependencies**:
|
|
- Requires `secret-tool` to be installed on Linux systems (part of `libsecret-tools` package)
|
|
- Already available on most Linux distributions by default
|
|
|
|
---
|
|
|
|
### Platform Playback Backend Parity (Linux vs Android)
|
|
|
|
**Issue**: The Linux (MPV) and Android (ExoPlayer) playback backends have diverged in feature implementation and architecture patterns.
|
|
|
|
**Symptoms**:
|
|
- Audio settings (crossfade, gapless playback, volume normalization) work on Linux but not on Android
|
|
- Position update frequency differs between platforms (Linux: 250ms polling, Android: on-demand callbacks)
|
|
- Thread safety models differ (Linux: `Arc<Mutex<>>`, Android: global `OnceLock` statics)
|
|
|
|
**Root Cause**:
|
|
The `PlayerBackend` trait defines optional audio settings methods with default empty implementations. The Linux `MpvBackend` overrides these with full MPV property commands, but `ExoPlayerBackend` uses the defaults.
|
|
|
|
**Affected Files**:
|
|
- [src-tauri/src/player/backend.rs](../src-tauri/src/player/backend.rs) - Trait with default empty implementations
|
|
- [src-tauri/src/player/mpv_backend.rs](../src-tauri/src/player/mpv_backend.rs) - Full audio settings support
|
|
- [src-tauri/src/player/android/mod.rs](../src-tauri/src/player/android/mod.rs) - Missing audio settings implementation
|
|
|
|
**Feature Parity Matrix**:
|
|
|
|
| Feature | Linux (MPV) | Android (ExoPlayer) | Status |
|
|
|---------|-------------|---------------------|--------|
|
|
| Basic playback | ✅ | ✅ | Parity |
|
|
| Volume control | ✅ | ✅ | Parity |
|
|
| Seek | ✅ | ✅ | Parity |
|
|
| Crossfade | ✅ | ❌ | Gap |
|
|
| Gapless playback | ✅ | ❌ | Gap |
|
|
| Volume normalization | ✅ | ❌ | Gap |
|
|
| Position updates | 250ms | On-demand | Inconsistent |
|
|
|
|
**Future Fix**:
|
|
1. Implement `set_audio_settings()` in `ExoPlayerBackend`
|
|
2. Add Kotlin-side ExoPlayer configuration for crossfade (using `ConcatenatingMediaSource` or `DefaultMediaSourceFactory`)
|
|
3. Implement gapless via ExoPlayer's built-in gapless support
|
|
4. Add volume normalization via ExoPlayer's `LoudnessEnhancer` or audio processor
|
|
5. Standardize position update frequency across platforms
|
|
|
|
**Impact**:
|
|
- Medium - Android users lack audio enhancement features advertised in requirements
|
|
- User experience differs between platforms
|
|
- UR-031 (Crossfade), UR-032 (Gapless), UR-033 (Normalization) only work on Linux
|
|
|
|
**Traces To**: IR-004, UR-031, UR-032, UR-033, DR-034, DR-035, DR-036
|
|
|
|
---
|
|
|
|
### Frontend Playback Code Duplication
|
|
|
|
**Issue**: Playback control handlers and state derivations are duplicated between `AudioPlayer.svelte` and `MiniPlayer.svelte`.
|
|
|
|
**Symptoms**:
|
|
- Identical try-catch wrapped handler functions in both components (~44 lines duplicated)
|
|
- Same `$derived` state merging logic for local/remote playback in both components
|
|
- Position conversion (ticks ↔ seconds) scattered across multiple files
|
|
|
|
**Affected Files**:
|
|
- [src/lib/components/player/AudioPlayer.svelte](../src/lib/components/player/AudioPlayer.svelte) - Duplicate handlers
|
|
- [src/lib/components/player/MiniPlayer.svelte](../src/lib/components/player/MiniPlayer.svelte) - Duplicate handlers
|
|
- [src/lib/services/playbackControl.ts](../src/lib/services/playbackControl.ts) - Position conversion
|
|
- [src/lib/stores/playbackMode.ts](../src/lib/stores/playbackMode.ts) - Position conversion
|
|
- [src/lib/services/playbackReporting.ts](../src/lib/services/playbackReporting.ts) - Position conversion
|
|
|
|
**Duplicated Code**:
|
|
```typescript
|
|
// These handlers are identical in both AudioPlayer and MiniPlayer:
|
|
handlePlayPause(), handleNext(), handlePrevious(),
|
|
handleToggleShuffle(), handleCycleRepeat(), handleVolumeChange()
|
|
|
|
// These derived states use identical logic:
|
|
displayMedia, displayIsPlaying, displayPosition, displayDuration
|
|
```
|
|
|
|
**Future Fix**:
|
|
1. Create `src/lib/utils/playbackUnits.ts`:
|
|
```typescript
|
|
export const TICKS_PER_SECOND = 10_000_000;
|
|
export const secondsToTicks = (s: number) => Math.floor(s * TICKS_PER_SECOND);
|
|
export const ticksToSeconds = (t: number) => t / TICKS_PER_SECOND;
|
|
```
|
|
|
|
2. Create `src/lib/composables/useMergedPlaybackState.svelte.ts`:
|
|
- Export `displayMedia`, `displayIsPlaying`, `displayPosition`, `displayDuration`
|
|
- Single source of truth for merged local/remote state
|
|
|
|
3. Simplify handler wrappers using a utility:
|
|
```typescript
|
|
export const withErrorHandler = (fn: () => Promise<void>, context: string) =>
|
|
async () => { try { await fn(); } catch (e) { console.error(`${context}:`, e); } };
|
|
```
|
|
|
|
**Impact**:
|
|
- Low - Code works correctly but violates DRY principle
|
|
- Maintenance burden when logic needs to change
|
|
- Risk of handlers diverging over time
|
|
|
|
**Traces To**: DR-009
|