Files
jellytau/docs/requirements.md
T
dtourolle eb76c96e94 feat(player): skipping an episode marks it watched, not paused
Skipping to the next episode left a mid-episode resume point behind, so
the skipped episode reappeared in Continue Watching with a partial
progress bar. Skipping means "done with this one", not "stopped here".

- reportSkippedEpisode marks the outgoing episode played instead of
  reporting a stop position, and arms a one-shot suppression consumed by
  the player's stop handler, so VideoPlayer's post-navigation unmount
  stop report can't overwrite the 100% progress with the partial one.
- Continue Watching drops resume entries superseded by Next Up: an
  in-progress episode whose series has a next-up entry strictly later in
  series order (season, then episode) is hidden from the Home and TV
  rows. Movies, series without a next-up entry, and items with unknown
  or mixed ordering are always kept.

Adds UR-059, DR-088, DR-089.

TRACES: UR-059 | DR-088, DR-089
2026-07-25 09:20:56 +02:00

46 KiB
Raw Permalink Blame History

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/. For development workflows, see the README and 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 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) 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) 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) 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) 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) 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) 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) 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) 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 and §5B.1) 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

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 <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 Done
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_catalogINCLUDE_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 Done
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 Done
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 Done
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 Done
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 Done
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 Done
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 Done
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 Done
DR-086 Settings page persists each control on change via per-group writers (playerSetAudioSettings / playerSetVideoSettings / updateCacheConfig) rather than a batch Save action; slider controls persist on change (pointer release) not each input tick; no Save button, saving, or saveMessage state Settings UR-057 Done
DR-087 MediaCard gains an onLongPress prop with pointer-based long-press detection (~500 ms hold, cancelled on >10 px move so carousel scroll is unaffected, trailing click suppressed); home carousels wire tap→detail/focus routing and long-press→confirm→player; episode taps route to /library/<seriesId>?episode=<id>; the bare-episode detail page links to its parent series/season UI UR-058 Done
DR-088 Skip-to-next-episode marks the outgoing episode played (markAsPlayed) instead of reporting a stop position, and arms a one-shot suppression consumed by the player's stop handler so VideoPlayer's post-navigation unmount stop report cannot overwrite the 100% progress with the partial position UI UR-059 Done
DR-089 Continue Watching suppresses resume entries superseded by Next Up: an in-progress episode whose series has a next-up entry strictly later in series order (season, then episode) is dropped from the Home and TV rows; movies, series without a next-up entry, and items with unknown/mixed episode ordering are always kept UI UR-059 Done

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
UR-057 - DR-086
UR-058 - DR-087

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 Done
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 Done
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 Done
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
UT-071 Byte-size formatter: zero/negative/non-finite → "0 B"; decimal unit thresholds; 23 significant-figure banding; trailing-zero trimming; largest-unit cap DR-085 Done
UT-072 Downloaded-only browse returns a downloaded leaf and its container, filtered to the requested album parent; a non-downloaded sibling is omitted DR-082, DR-083 Done
UT-073 An empty downloaded-only browse is authoritative — no rows, no error — regardless of the catalog-browse flag DR-082 Done
UT-074 Only libraries with downloaded content are listed; an empty one is omitted DR-082 Done
UT-075 Disk usage reports a leaf's own size, a container's summed descendants, and reconciles the device total with the sum of leaves DR-085 Done
UT-076 Downloaded library browse lists album containers, not their individual tracks; drilling into the album returns the tracks DR-082, DR-083 Done
UT-077 Downloaded TV library browse lists the series, not seasons/episodes; drilling returns the season then the episode DR-082, DR-083 Done
UT-078 A downloaded leaf with no cached container (e.g. a movie) still surfaces at the library level DR-082, DR-083 Done
UT-079 Each EQ preset returns a 10-band gain curve within range; Flat is all zeros; Bass Boost lifts lows and leaves highs flat DR-030 Done
UT-080 with_equalizer_normalised clamps out-of-range gains and forces the band vector to exactly 10 entries (pad short, truncate long) DR-030 Done
UT-081 Old persisted AudioSettings JSON without EQ fields loads as disabled + flat DR-030 Done
UT-082 EQ fields serialize as camelCase (equalizerEnabled/equalizerBands) and round-trip DR-030 Done
UT-083 EQ filter entries are empty when disabled or when the curve is flat (clears the af filter) IR-020 Done
UT-084 Enabled EQ builds one peaking equalizer per non-zero band at the right frequency and gain inside a single lavfi chain IR-020 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 Done
IT-017 A download queued from a greyed-out offline catalog entry persists and is resolved and started on reconnect UR-052, UR-011 Done

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 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:

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:

Duplicated Code:

// 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:

    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:

    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