Duncan Tourolle 07f3bf04ca
Some checks failed
🏗️ Build and Test JellyTau / Build Android APK (push) Has been cancelled
🏗️ Build and Test JellyTau / Run Tests (push) Has been cancelled
Traceability Validation / Check Requirement Traces (push) Failing after 1s
fix tests
2026-02-14 17:15:53 +01:00
2026-02-14 17:15:53 +01:00
2026-02-14 00:09:47 +01:00
2026-01-26 22:21:54 +01:00
2026-02-14 00:09:47 +01:00
2026-02-14 17:15:53 +01:00
2026-01-26 22:21:54 +01:00
2026-01-26 22:21:54 +01:00
2026-01-26 22:21:54 +01:00
2026-01-26 22:21:54 +01:00
2026-02-14 16:49:14 +01:00
2026-02-14 16:39:46 +01:00
2026-02-14 12:21:03 +01:00
2026-01-26 22:31:37 +01:00
2026-01-26 22:31:37 +01:00
2026-01-26 22:21:54 +01:00
2026-01-26 22:21:54 +01:00
2026-01-26 22:21:54 +01:00
2026-01-26 22:21:54 +01:00
2026-01-26 22:21:54 +01:00

JellyTau

A cross-platform Jellyfin client built with Tauri, SvelteKit, and TypeScript.

VS Code + Svelte + Tauri + rust-analyzer.


Requirements Specification

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 In Progress
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 Planned
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 Planned
UR-021 Select audio track for video content High Planned
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 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 playback 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

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

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 Planned
JA-009 Get available audio tracks for item MediaInfo UR-021 Planned
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 Planned
JA-020 Add/remove items from playlist Playlists UR-014 Planned
JA-021 Get active sessions list Sessions UR-010 Planned
JA-022 Send playback commands to remote session (play/pause/stop) Sessions UR-010 Planned
JA-023 Send seek command to remote session Sessions UR-010 Planned
JA-024 Send next/previous track commands to remote session Sessions UR-010 Planned
JA-025 Play specific item on remote session Sessions UR-010 Planned
JA-026 Send volume/mute commands to remote session Sessions UR-010 Planned
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 Planned
JA-030 Get person details and filmography Persons UR-036 Planned
JA-031 Get items by person (actor/director filmography) Items UR-036 Planned

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 Planned
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 Planned
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 Planned
DR-024 Audio track selection UI in video player UI UR-021 Planned
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 countdown and auto-stop Player 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 Player UR-023 Done
DR-048 Video settings (auto-play toggle, countdown duration) Settings UR-023, UR-026 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
UR-011 IR-013 DR-003, DR-015, DR-018
UR-012 IR-009, IR-014 -
UR-013 IR-013 DR-017
UR-014 - 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
UR-024 IR-010 DR-027
UR-025 IR-015 DR-028
UR-026 - DR-029, DR-048
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

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

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

5. Development Commands

# Activate Rust environment (fish shell)
source "$HOME/.cargo/env.fish"

# Install dependencies
bun install

# Development
bun run tauri dev

# Type checking
bun run check

# Build for Linux
bun run tauri build

# Build for Android
bun run tauri android build

6. Architecture Overview

jellytau/
├── src/                          # Svelte frontend
│   ├── lib/
│   │   ├── api/                  # Jellyfin API client (repository pattern)
│   │   ├── components/           # UI components (player, library)
│   │   └── stores/               # Svelte stores (auth, library, player, queue)
│   └── routes/                   # SvelteKit pages
├── src-tauri/                    # Rust backend
│   ├── src/
│   │   ├── commands/             # Tauri commands
│   │   └── player/               # Player architecture
│   │       ├── state.rs          # State machine
│   │       ├── media.rs          # MediaItem, MediaSource
│   │       ├── queue.rs          # Queue management
│   │       └── backend.rs        # PlayerBackend trait
│   └── gen/android/              # Android project
└── README.md

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

  • Lines 165-209: save_to_keyring() - Uses secret-tool store on Linux
  • Lines 214-248: get_from_keyring() - Uses secret-tool lookup on Linux
  • Lines 254-286: delete_from_keyring() - Uses secret-tool clear on Linux

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

Description
A jellyfin client with Tauri
Readme 1.5 MiB
Languages
Rust 50.3%
Svelte 23.2%
TypeScript 20.8%
Kotlin 4.5%
Shell 0.8%
Other 0.3%