Update docs
This commit is contained in:
parent
cfddc1edea
commit
e560543181
212
PRELOADING.md
212
PRELOADING.md
@ -1,212 +0,0 @@
|
||||
# Smart Preloading Implementation
|
||||
|
||||
This document describes the smart preloading system that automatically queues downloads for upcoming tracks in the playback queue.
|
||||
|
||||
## Overview
|
||||
|
||||
The preloading system monitors playback and automatically queues background downloads for the next 3 tracks in the queue. This ensures smooth playback transitions and offline availability without requiring manual downloads.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Backend (Rust)
|
||||
|
||||
**Smart Cache Engine** - `/src-tauri/src/download/cache.rs`
|
||||
- Manages preload configuration
|
||||
- Default settings: preload 3 tracks, works on any connection (not wifi-only)
|
||||
- Storage limit: 10GB
|
||||
- Album affinity tracking (future feature)
|
||||
|
||||
**Queue Manager** - `/src-tauri/src/player/queue.rs`
|
||||
- New method: `get_upcoming(count)` returns next N tracks
|
||||
- Respects shuffle order
|
||||
- Handles repeat modes (Off, All, One)
|
||||
- Wraps around when RepeatMode::All is enabled
|
||||
|
||||
**Preload Command** - `/src-tauri/src/commands/player.rs`
|
||||
- `player_preload_upcoming` - Main preload endpoint
|
||||
- Checks SmartCache configuration
|
||||
- Gets upcoming tracks from queue
|
||||
- Skips already downloaded/queued items
|
||||
- Queues new downloads with low priority (-100)
|
||||
- Returns stats: queued_count, already_downloaded, skipped
|
||||
|
||||
**Configuration Commands**
|
||||
- `player_set_cache_config` - Update preload settings
|
||||
- `player_get_cache_config` - Get current settings
|
||||
|
||||
### Frontend (TypeScript/Svelte)
|
||||
|
||||
**Preload Service** - `/src/lib/services/preload.ts`
|
||||
- Main function: `preloadUpcomingTracks()`
|
||||
- Automatically gets current user ID from auth store
|
||||
- Calls backend preload command
|
||||
- Fails silently - never interrupts playback
|
||||
- Logs meaningful results only
|
||||
|
||||
**Integration Points** - `/src/lib/services/playerEvents.ts`
|
||||
1. **On playback start** - When state changes to "playing"
|
||||
2. **On track advance** - After successfully advancing to next track
|
||||
|
||||
## How It Works
|
||||
|
||||
### Playback Flow
|
||||
|
||||
```
|
||||
User plays track/queue
|
||||
↓
|
||||
Backend starts playback
|
||||
↓
|
||||
Emits "state_changed" event with "playing"
|
||||
↓
|
||||
playerEvents.handleStateChanged() catches event
|
||||
↓
|
||||
Calls preloadUpcomingTracks()
|
||||
↓
|
||||
Backend queues downloads for next 3 tracks (if not already downloaded)
|
||||
```
|
||||
|
||||
### Track Advance Flow
|
||||
|
||||
```
|
||||
Track ends (or user clicks next)
|
||||
↓
|
||||
playerEvents.handlePlaybackEnded() or manual next()
|
||||
↓
|
||||
Calls player_next backend command
|
||||
↓
|
||||
Backend plays next track, emits "state_changed"
|
||||
↓
|
||||
Calls preloadUpcomingTracks() again
|
||||
↓
|
||||
Queue is shifted forward, new track gets preloaded
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Default Settings
|
||||
|
||||
```typescript
|
||||
{
|
||||
queuePrecacheEnabled: true,
|
||||
queuePrecacheCount: 3,
|
||||
albumAffinityEnabled: true,
|
||||
albumAffinityThreshold: 3,
|
||||
storageLimit: 10737418240, // 10GB
|
||||
wifiOnly: false
|
||||
}
|
||||
```
|
||||
|
||||
### Updating Configuration
|
||||
|
||||
```typescript
|
||||
import { updateCacheConfig } from '$lib/services/preload';
|
||||
|
||||
await updateCacheConfig({
|
||||
queuePrecacheCount: 5, // Preload 5 tracks instead of 3
|
||||
wifiOnly: true // Only preload on WiFi
|
||||
});
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
### Intelligent Queueing
|
||||
- ✅ Checks if tracks are already downloaded
|
||||
- ✅ Skips tracks already in download queue
|
||||
- ✅ Low priority (-100) so user-initiated downloads go first
|
||||
- ✅ Respects shuffle and repeat modes
|
||||
- ✅ No duplicate downloads
|
||||
|
||||
### Offline First
|
||||
- ✅ Existing `create_media_item()` in player.rs checks local downloads first
|
||||
- ✅ Preloading ensures next tracks become local over time
|
||||
- ✅ Seamless offline playback without manual intervention
|
||||
|
||||
### Non-Intrusive
|
||||
- ✅ Background operation - never blocks playback
|
||||
- ✅ Fails silently - errors are logged but don't affect UX
|
||||
- ✅ Automatic - no user interaction required
|
||||
- ✅ Configurable - users can adjust or disable
|
||||
|
||||
## Files Changed
|
||||
|
||||
### Backend
|
||||
- `src-tauri/src/player/queue.rs` - Added `get_upcoming()` method
|
||||
- `src-tauri/src/download/cache.rs` - Made `CacheConfig` serializable, updated defaults
|
||||
- `src-tauri/src/commands/player.rs` - Added preload commands and `SmartCacheWrapper`
|
||||
- `src-tauri/src/lib.rs` - Initialized SmartCache, registered commands
|
||||
|
||||
### Frontend
|
||||
- `src/lib/services/preload.ts` - New preload service (created)
|
||||
- `src/lib/services/playerEvents.ts` - Integrated preload triggers
|
||||
|
||||
### Configuration
|
||||
- `src-tauri/Cargo.toml` - Added tempfile dev dependency for tests
|
||||
|
||||
## Testing
|
||||
|
||||
### Rust Tests
|
||||
```bash
|
||||
cd src-tauri
|
||||
cargo test queue::tests::test_get_upcoming
|
||||
cargo test cache::tests::test_default_config
|
||||
```
|
||||
|
||||
All tests pass ✅
|
||||
|
||||
### Manual Testing
|
||||
|
||||
1. **Start playback**
|
||||
- Play a queue of 5+ tracks
|
||||
- Check console for: `[Preload] Queued N track(s) for background download`
|
||||
- Verify download queue shows 3 pending downloads with priority -100
|
||||
|
||||
2. **Track advance**
|
||||
- Let track finish or click next
|
||||
- Check console for new preload log
|
||||
- Verify queue shifts (old track 2 becomes current, new track gets queued)
|
||||
|
||||
3. **Repeat mode**
|
||||
- Enable Repeat All
|
||||
- Play to end of queue
|
||||
- Verify wraps around and continues preloading
|
||||
|
||||
4. **Already downloaded**
|
||||
- Download all tracks in an album
|
||||
- Play the album
|
||||
- Check logs show: `already_downloaded: 3, queued: 0`
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Album Affinity** - If user plays 3+ tracks from an album, auto-download the rest
|
||||
2. **WiFi Detection** - Respect `wifi_only` setting on mobile
|
||||
3. **Storage Management** - Auto-evict LRU items when storage limit reached
|
||||
4. **Smart Priority** - Boost priority as track gets closer in queue
|
||||
5. **Bandwidth Throttling** - Limit download speed to not interfere with streaming
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Preload not working
|
||||
- Check console for `[Preload]` logs
|
||||
- Verify user is logged in: `auth.getUserId()` returns value
|
||||
- Check SmartCache config: `queuePrecacheEnabled` should be true
|
||||
|
||||
### Downloads not starting
|
||||
- Preload only queues downloads, doesn't start them
|
||||
- Check download manager is processing queue
|
||||
- Verify backend has download worker running
|
||||
|
||||
### Too many downloads
|
||||
- Reduce `queuePrecacheCount` in config
|
||||
- Enable `wifiOnly` mode
|
||||
- Adjust `storageLimit`
|
||||
|
||||
## Performance Impact
|
||||
|
||||
- **Minimal** - Background downloads use low priority
|
||||
- **Non-blocking** - Async operation, no playback delay
|
||||
- **Bandwidth-friendly** - Only downloads when needed
|
||||
- **Storage-aware** - Respects configured limits
|
||||
|
||||
## Summary
|
||||
|
||||
Smart preloading transforms JellyTau into an offline-first music player. By automatically queueing downloads for upcoming tracks, it ensures seamless playback and offline availability without requiring users to manually manage downloads. The system is intelligent (checks what's already downloaded), non-intrusive (fails silently), and configurable (users can adjust or disable).
|
||||
88
README.md
88
README.md
@ -16,14 +16,14 @@ A cross-platform Jellyfin client built with Tauri, SvelteKit, and TypeScript.
|
||||
|----|-------------|----------|--------|
|
||||
| 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 | Planned |
|
||||
| UR-004 | Play audio uninterrupted | High | Planned |
|
||||
| UR-005 | Control media playback (pause, play, skip, scrub) | High | In Progress |
|
||||
| UR-006 | Control media when device is on lock screen or via BLE headsets | Medium | Planned |
|
||||
| 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 | Planned |
|
||||
| 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 |
|
||||
@ -36,23 +36,23 @@ A cross-platform Jellyfin client built with Tauri, SvelteKit, and TypeScript.
|
||||
| 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 | In Progress |
|
||||
| UR-024 | View recently added content on server | 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 | Planned |
|
||||
| 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 | Planned (Linux only) |
|
||||
| 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 | Planned |
|
||||
| UR-035 | View cast/crew (actors, directors) on movie/show detail pages | High | Planned |
|
||||
| UR-036 | Navigate to actor/person page showing their filmography | Medium | Planned |
|
||||
| UR-037 | Visually appealing video library with poster grids and metadata | High | Planned |
|
||||
| UR-038 | Movie/show detail page with backdrop, ratings, and rich metadata | High | Planned |
|
||||
| UR-039 | Navigate between main sections via bottom navigation bar | High | In Progress |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
@ -66,16 +66,16 @@ External system integrations and platform-specific implementations.
|
||||
|----|-------------|----------|-----------|--------|
|
||||
| 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 | In Progress |
|
||||
| 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 | In Progress |
|
||||
| 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 | In Progress |
|
||||
| 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 | Planned |
|
||||
| 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 |
|
||||
@ -85,9 +85,9 @@ External system integrations and platform-specific implementations.
|
||||
| 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 | Planned |
|
||||
| IR-023 | Database schema for person/cast caching | Storage | UR-035, UR-036 | Planned |
|
||||
| IR-024 | Jellyfin API client for home screen data (featured, continue watching) | API | UR-034 | 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
|
||||
|
||||
@ -108,9 +108,9 @@ API endpoints and data contracts required for Jellyfin integration.
|
||||
| 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 | Planned |
|
||||
| JA-015 | Get "Continue Watching" items | Items | UR-023 | Planned |
|
||||
| JA-016 | Get recently added items | Items | UR-024 | Planned |
|
||||
| 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 |
|
||||
@ -142,7 +142,7 @@ Internal architecture, components, and application logic.
|
||||
| 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 | Planned |
|
||||
| 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 |
|
||||
@ -158,29 +158,29 @@ Internal architecture, components, and application logic.
|
||||
| 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 | Planned |
|
||||
| DR-027 | "Recently Added" home section | UI | UR-024 | 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 | Planned |
|
||||
| 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 | Planned |
|
||||
| DR-035 | Gapless playback between sequential tracks | Player | UR-032 | Done |
|
||||
| DR-036 | Volume normalization with preset levels (Loud/Normal/Quiet) | Player | UR-033 | Done |
|
||||
| DR-037 | Remote session browser and control UI | UI | UR-010 | Planned |
|
||||
| DR-038 | Home screen with hero banner carousel (featured/continue watching) | UI | UR-034 | Planned |
|
||||
| DR-039 | Home screen horizontal carousels (recently added, recommendations) | UI | UR-034, UR-024 | Planned |
|
||||
| DR-040 | Cast/crew section on movie/show detail pages | UI | UR-035 | Planned |
|
||||
| DR-041 | Person/actor detail page with filmography grid | UI | UR-036 | Planned |
|
||||
| DR-042 | Video library grid with poster cards, year, and rating badges | UI | UR-037 | Planned |
|
||||
| DR-043 | Movie/show detail page with backdrop hero, synopsis, and metadata | UI | UR-038 | Planned |
|
||||
| DR-044 | Horizontal scrolling actor/cast row with profile images | UI | UR-035 | Planned |
|
||||
| DR-045 | Bottom navigation bar with Home, Library, Search buttons | UI | UR-039 | In Progress |
|
||||
| DR-046 | Dedicated search page with input and results | UI | UR-039 | In Progress |
|
||||
| DR-047 | Next episode auto-play popup with configurable countdown | Player | UR-023 | In Progress |
|
||||
| DR-048 | Video settings (auto-play toggle, countdown duration) | Settings | UR-023, UR-026 | In Progress |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -2,42 +2,11 @@
|
||||
|
||||
This document describes the current architecture of JellyTau, a cross-platform Jellyfin client built with Tauri, SvelteKit, and Rust.
|
||||
|
||||
**Last Updated:** 2026-01-18
|
||||
**Architecture Status:** Phase 2-3 of TypeScript to Rust migration complete
|
||||
**Last Updated:** 2026-01-26
|
||||
|
||||
## Major Architectural Changes (Recent)
|
||||
## Architecture Overview
|
||||
|
||||
JellyTau has undergone a significant architectural transformation, migrating ~3,500 lines of business logic from TypeScript to Rust:
|
||||
|
||||
### ✅ Completed Migrations
|
||||
|
||||
1. **HTTP Client & Connectivity** (Phase 1)
|
||||
- Exponential backoff retry logic moved to Rust
|
||||
- Adaptive connectivity monitoring (30s online, 5s offline)
|
||||
- Event-driven architecture for network state changes
|
||||
|
||||
2. **Repository Pattern** (Phase 2)
|
||||
- Complete MediaRepository trait implementation in Rust
|
||||
- Cache-first parallel racing (100ms cache timeout)
|
||||
- Handle-based resource management (UUID handles)
|
||||
- 27 new Tauri commands for data access
|
||||
- Eliminated 1,061 lines of TypeScript
|
||||
|
||||
3. **Database Service Abstraction** (Phase 2.5)
|
||||
- Async wrapper over synchronous rusqlite
|
||||
- All DB operations use `spawn_blocking` to prevent UI freezing
|
||||
- ~1-2ms overhead per query (acceptable tradeoff)
|
||||
|
||||
4. **Playback Mode Management** (Phase 3)
|
||||
- Local/Remote/Idle mode tracking
|
||||
- Seamless queue transfer to remote Jellyfin sessions
|
||||
- Position synchronization during transfers
|
||||
|
||||
### 🔄 In Progress
|
||||
|
||||
- **Authentication & Session Management** (Phase 4)
|
||||
- Session restoration and credential management
|
||||
- Re-authentication flow
|
||||
JellyTau uses a modern client-server architecture with a thin Svelte UI layer and comprehensive Rust backend:
|
||||
|
||||
### Architecture Principles
|
||||
|
||||
@ -521,7 +490,6 @@ flowchart TB
|
||||
- Attempts to sync with Jellyfin server
|
||||
- Marks as synced if successful, otherwise leaves `pending_sync = 1`
|
||||
3. UI reflects the change immediately without waiting for server response
|
||||
4. Future: Sync queue will retry failed syncs when online
|
||||
|
||||
**Components**:
|
||||
|
||||
@ -653,13 +621,21 @@ flowchart TB
|
||||
JellyTau's music library uses a category-based navigation system with a dedicated landing page that routes users to specialized views for different content types.
|
||||
|
||||
**Route Structure:**
|
||||
```
|
||||
/library/music # Landing page with category cards
|
||||
├── /tracks # All songs (ALWAYS list view)
|
||||
├── /artists # Artist grid (ALWAYS grid view)
|
||||
├── /albums # Album grid (ALWAYS grid view)
|
||||
├── /playlists # Playlist grid (ALWAYS grid view)
|
||||
└── /genres # Genre browser (two-level)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Music["/library/music<br/>(Landing page with category cards)"]
|
||||
Tracks["Tracks<br/>(List view only)"]
|
||||
Artists["Artists<br/>(Grid view)"]
|
||||
Albums["Albums<br/>(Grid view)"]
|
||||
Playlists["Playlists<br/>(Grid view)"]
|
||||
Genres["Genres<br/>(Genre browser)"]
|
||||
|
||||
Music --> Tracks
|
||||
Music --> Artists
|
||||
Music --> Albums
|
||||
Music --> Playlists
|
||||
Music --> Genres
|
||||
```
|
||||
|
||||
**View Enforcement:**
|
||||
@ -740,8 +716,7 @@ sequenceDiagram
|
||||
|
||||
**Database Schema Notes:**
|
||||
- The `user_data` table stores playback progress using Jellyfin IDs directly (as TEXT)
|
||||
- Foreign key constraint on `item_id` was removed in migration 003 to allow tracking progress for items not yet synced to local database
|
||||
- This enables playback tracking even when the full item metadata hasn't been downloaded yet
|
||||
- Playback progress can be tracked even when the full item metadata hasn't been downloaded yet
|
||||
|
||||
**Resume Playback Feature:**
|
||||
- When loading media for playback, the app checks local database for saved progress
|
||||
@ -1004,50 +979,66 @@ pub struct ConnectivityMonitor {
|
||||
|
||||
### 3.8 Component Hierarchy
|
||||
|
||||
```
|
||||
src/routes/
|
||||
├── +page.svelte # Login page
|
||||
├── library/
|
||||
│ ├── +layout.svelte # Main layout with MiniPlayer
|
||||
│ ├── +page.svelte # Library browser (library selector)
|
||||
│ ├── [id]/+page.svelte # Album/series detail
|
||||
│ └── music/ # Music library structure
|
||||
│ ├── +page.svelte # Music category landing page
|
||||
│ ├── tracks/+page.svelte # All tracks (list view only)
|
||||
│ ├── artists/+page.svelte # Artists grid
|
||||
│ ├── albums/+page.svelte # Albums grid
|
||||
│ ├── playlists/+page.svelte # Playlists grid
|
||||
│ └── genres/+page.svelte # Genre browser
|
||||
├── downloads/
|
||||
│ └── +page.svelte # Manage downloads (Active/Completed tabs)
|
||||
├── settings/
|
||||
│ └── +page.svelte # Settings (includes download settings)
|
||||
└── player/
|
||||
└── [id]/+page.svelte # Full player page
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Routes["Routes (src/routes/)"]
|
||||
LoginPage["Login Page"]
|
||||
LibLayout["Library Layout"]
|
||||
LibDetail["Album/Series Detail"]
|
||||
MusicCategory["Music Category Landing"]
|
||||
Tracks["Tracks"]
|
||||
Artists["Artists"]
|
||||
Albums["Albums"]
|
||||
Playlists["Playlists"]
|
||||
Genres["Genres"]
|
||||
Downloads["Downloads Page"]
|
||||
Settings["Settings Page"]
|
||||
PlayerPage["Player Page"]
|
||||
end
|
||||
|
||||
src/lib/components/
|
||||
├── Search.svelte
|
||||
├── player/
|
||||
│ ├── AudioPlayer.svelte # Full screen audio player
|
||||
│ ├── MiniPlayer.svelte # Bottom bar mini player (auto-hides for video, includes cast button)
|
||||
│ ├── Controls.svelte # Play/pause/skip controls
|
||||
│ └── Queue.svelte # Queue list view
|
||||
├── sessions/
|
||||
│ ├── CastButton.svelte # Cast button with session picker (integrated in MiniPlayer)
|
||||
│ ├── SessionPickerModal.svelte # Modal for selecting remote session
|
||||
│ ├── SessionCard.svelte # Individual session display card
|
||||
│ ├── SessionsList.svelte # List of all controllable sessions
|
||||
│ └── RemoteControls.svelte # Full remote playback control UI
|
||||
├── downloads/
|
||||
│ └── DownloadItem.svelte # Download list item with progress/actions
|
||||
├── FavoriteButton.svelte # Reusable heart/like button
|
||||
└── library/
|
||||
├── LibraryGrid.svelte # Grid of media items (supports forceGrid)
|
||||
├── LibraryListView.svelte # List view for albums/artists
|
||||
├── TrackList.svelte # Dedicated track list (now with showDownload prop)
|
||||
├── DownloadButton.svelte # Download button with progress ring
|
||||
├── MediaCard.svelte # Individual item card
|
||||
└── AlbumView.svelte # Album detail with tracks
|
||||
subgraph PlayerComps["Player Components"]
|
||||
AudioPlayer["AudioPlayer"]
|
||||
MiniPlayer["MiniPlayer"]
|
||||
Controls["Controls"]
|
||||
Queue["Queue"]
|
||||
end
|
||||
|
||||
subgraph SessionComps["Sessions Components"]
|
||||
CastButton["CastButton"]
|
||||
SessionModal["SessionPickerModal"]
|
||||
SessionCard["SessionCard"]
|
||||
SessionsList["SessionsList"]
|
||||
RemoteControls["RemoteControls"]
|
||||
end
|
||||
|
||||
subgraph LibraryComps["Library Components"]
|
||||
LibGrid["LibraryGrid"]
|
||||
LibListView["LibraryListView"]
|
||||
TrackList["TrackList"]
|
||||
DownloadBtn["DownloadButton"]
|
||||
MediaCard["MediaCard"]
|
||||
end
|
||||
|
||||
subgraph OtherComps["Other Components"]
|
||||
Search["Search"]
|
||||
FavoriteBtn["FavoriteButton"]
|
||||
DownloadItem["DownloadItem"]
|
||||
end
|
||||
|
||||
LibLayout --> PlayerComps
|
||||
LibLayout --> LibDetail
|
||||
MusicCategory --> Tracks
|
||||
MusicCategory --> Artists
|
||||
MusicCategory --> Albums
|
||||
MusicCategory --> Playlists
|
||||
MusicCategory --> Genres
|
||||
LibDetail --> LibraryComps
|
||||
Downloads --> DownloadItem
|
||||
PlayerPage --> PlayerComps
|
||||
|
||||
MiniPlayer --> CastButton
|
||||
CastButton --> SessionModal
|
||||
PlayerComps --> LibraryComps
|
||||
```
|
||||
|
||||
---
|
||||
@ -1080,7 +1071,6 @@ sequenceDiagram
|
||||
else Cache timeout or empty
|
||||
Server-->>Hybrid: Fresh result
|
||||
Hybrid-->>Rust: Return server result
|
||||
Note over Hybrid: TODO: Update cache in background
|
||||
end
|
||||
|
||||
Rust-->>Client: SearchResult
|
||||
@ -1967,7 +1957,7 @@ listen<DownloadEvent>('download-event', (event) => {
|
||||
|
||||
### 8.9 Database Schema
|
||||
|
||||
**downloads table** (enhanced in migration 004):
|
||||
**downloads table**:
|
||||
|
||||
```sql
|
||||
CREATE TABLE downloads (
|
||||
@ -1995,139 +1985,9 @@ CREATE INDEX idx_downloads_queue
|
||||
|
||||
---
|
||||
|
||||
## 9. TypeScript to Rust Migration Status
|
||||
## 9. Connectivity & Network Architecture
|
||||
|
||||
### 9.1 Migration Overview
|
||||
|
||||
JellyTau has undergone a phased migration from TypeScript to Rust, moving ~3,500 lines of business logic to Rust while simplifying the TypeScript layer to thin UI wrappers.
|
||||
|
||||
**Approach**: Incremental migration with direct replacement
|
||||
- Complete one phase at a time with full testing
|
||||
- Delete TypeScript implementations after Rust validation
|
||||
- Each phase is independently deployable
|
||||
- No parallel implementations maintained
|
||||
|
||||
### 9.2 Completed Phases
|
||||
|
||||
#### ✅ Phase 1: HTTP Client & Connectivity Foundation (Complete)
|
||||
|
||||
**Created:**
|
||||
- `src-tauri/src/jellyfin/http_client.rs` (289 lines)
|
||||
- HTTP client with exponential backoff retry (1s, 2s, 4s)
|
||||
- Error classification (Network, Authentication, Server, Client)
|
||||
- Automatic retry on network/5xx errors
|
||||
- `src-tauri/src/connectivity/mod.rs` (351 lines)
|
||||
- Background monitoring with adaptive polling (30s online, 5s offline)
|
||||
- Event emission to frontend
|
||||
- Manual reachability marking
|
||||
|
||||
**Simplified:**
|
||||
- `src/lib/stores/connectivity.ts`: 301→249 lines (-17%)
|
||||
- Removed polling logic
|
||||
- Now listens to Rust events
|
||||
- Thin wrapper over Rust commands
|
||||
|
||||
**Commands Added:** 7 connectivity commands
|
||||
|
||||
**Impact:** Eliminated TypeScript polling/retry logic, improved battery efficiency
|
||||
|
||||
#### ✅ Phase 2: Repository Pattern & Data Layer (Complete)
|
||||
|
||||
**Created:**
|
||||
- `src-tauri/src/repository/` (complete module)
|
||||
- `mod.rs`: MediaRepository trait + handle-based management
|
||||
- `types.rs`: Type definitions (RepoError, Library, MediaItem, etc.)
|
||||
- `hybrid.rs`: Cache-first parallel racing (100ms cache timeout)
|
||||
- `online.rs`: OnlineRepository (HTTP API calls)
|
||||
- `offline.rs`: OfflineRepository (SQLite queries with caching)
|
||||
|
||||
**Replaced:**
|
||||
- Deleted `src/lib/api/repository.ts` (1061 lines)
|
||||
- Created `src/lib/api/repository-client.ts` (~100 lines)
|
||||
- Thin wrapper with handle-based resource management
|
||||
- All methods delegate to Rust commands
|
||||
|
||||
**Commands Added:** 27 repository commands
|
||||
|
||||
**Key Features:**
|
||||
- Handle-based resource management (UUID handles)
|
||||
- Cache-first racing: parallel cache (100ms timeout) vs server queries
|
||||
- Returns cache if meaningful content, else server result
|
||||
- Supports multiple concurrent repository instances
|
||||
|
||||
#### ✅ Phase 2.5: Database Service Abstraction (Complete)
|
||||
|
||||
**Created:**
|
||||
- `src-tauri/src/storage/db_service.rs`
|
||||
- `DatabaseService` trait with async methods
|
||||
- `RusqliteService` implementation using `spawn_blocking`
|
||||
- Prevents blocking Tokio async runtime
|
||||
|
||||
**Impact:**
|
||||
- Eliminated UI freezing from blocking database operations
|
||||
- All DB queries now use `spawn_blocking` thread pool
|
||||
- ~1-2ms overhead per query (acceptable tradeoff)
|
||||
|
||||
**Migration Status:**
|
||||
- ✅ Phase 1: Foundation (Complete)
|
||||
- ✅ Phase 2: OfflineRepository (18 methods migrated)
|
||||
- 🔄 Phase 3: Command layer (97 operations across 5 files - in progress)
|
||||
|
||||
#### ✅ Phase 3: Playback Mode System (Complete)
|
||||
|
||||
**Created:**
|
||||
- `src-tauri/src/playback_mode/mod.rs`
|
||||
- `PlaybackMode` enum (Local, Remote, Idle)
|
||||
- `PlaybackModeManager` for mode transfers
|
||||
- Transfer queue between local device and remote sessions
|
||||
|
||||
**Simplified:**
|
||||
- `src/lib/stores/playbackMode.ts`: 303→150 lines (-50%)
|
||||
- Thin wrapper calling Rust commands
|
||||
- Maintains UI state only
|
||||
|
||||
**Commands Added:** 5 playback mode commands
|
||||
|
||||
**Features:**
|
||||
- Seamless transfer of playback queue to remote sessions
|
||||
- Position synchronization during transfer
|
||||
- Automatic local player stop when transferring to remote
|
||||
|
||||
### 9.3 In Progress
|
||||
|
||||
#### 🔄 Phase 4: Authentication & Session Management
|
||||
|
||||
**Status:** Partially complete
|
||||
- Session restoration logic migrated
|
||||
- Credential storage using secure keyring
|
||||
- Re-authentication flow in progress
|
||||
|
||||
**Target:** Simplify `src/lib/stores/auth.ts` from 616→150 lines
|
||||
|
||||
### 9.4 Architecture Summary
|
||||
|
||||
**Before Migration:**
|
||||
- TypeScript: ~3,300 lines of business logic
|
||||
- Rust: ~4,000 lines (player, storage, downloads)
|
||||
- Total Commands: 73
|
||||
|
||||
**After Migration (Current):**
|
||||
- TypeScript: ~800 lines (thin wrappers, UI state)
|
||||
- Rust: ~8,000 lines (business logic, HTTP, repository, etc.)
|
||||
- Total Commands: 80+
|
||||
|
||||
**Lines Eliminated:** ~2,500 lines of TypeScript business logic
|
||||
|
||||
**Benefits:**
|
||||
- Improved performance (zero-cost abstractions)
|
||||
- Better reliability (type safety, memory safety)
|
||||
- Reduced battery drain (efficient async I/O)
|
||||
- Easier maintenance (centralized business logic)
|
||||
- No UI freezing (async database operations)
|
||||
|
||||
## 10. Connectivity & Network Architecture
|
||||
|
||||
### 10.1 HTTP Client with Retry Logic
|
||||
### 9.1 HTTP Client with Retry Logic
|
||||
|
||||
**Location**: `src-tauri/src/jellyfin/http_client.rs`
|
||||
|
||||
@ -2161,7 +2021,7 @@ pub enum ErrorKind {
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 Connectivity Monitor
|
||||
### 9.2 Connectivity Monitor
|
||||
|
||||
**Location**: `src-tauri/src/connectivity/mod.rs`
|
||||
|
||||
@ -2203,7 +2063,7 @@ listen<{ isReachable: boolean }>("connectivity:changed", (event) => {
|
||||
});
|
||||
```
|
||||
|
||||
### 10.3 Network Resilience Architecture
|
||||
### 9.3 Network Resilience Architecture
|
||||
|
||||
The connectivity system provides resilience through multiple layers:
|
||||
|
||||
@ -2218,336 +2078,9 @@ The connectivity system provides resilience through multiple layers:
|
||||
- **Adaptive Polling**: Reduce polling frequency when online, increase when offline
|
||||
- **Event-Driven**: Frontend reacts to connectivity changes via events
|
||||
|
||||
## 11. Architecture Extensions
|
||||
|
||||
### 11.1 Native Async Database (Future)
|
||||
|
||||
**Future Enhancement**: Migrate from `rusqlite` + `spawn_blocking` to native async database:
|
||||
|
||||
```rust
|
||||
// Current: spawn_blocking overhead (~1-2ms per query)
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let conn = connection.lock().unwrap();
|
||||
conn.query_row(...)
|
||||
}).await?
|
||||
|
||||
// Future: Native async with tokio-rusqlite (zero overhead)
|
||||
use tokio_rusqlite::Connection;
|
||||
|
||||
let conn = Connection::open(path).await?;
|
||||
conn.call(|conn| {
|
||||
conn.query_row(...)
|
||||
}).await?
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- Eliminate spawn_blocking overhead
|
||||
- Better integration with Tokio runtime
|
||||
- Improved throughput for high-frequency queries
|
||||
|
||||
**Migration Path:**
|
||||
- DatabaseService trait already abstracts implementation
|
||||
- Swap RusqliteService for TokioRusqliteService
|
||||
- No changes to command layer needed
|
||||
|
||||
### 11.2 Remote Session Control (Existing Feature - Documented)
|
||||
|
||||
Remote session control allows JellyTau to discover and control playback on other Jellyfin clients (TVs, web browsers, etc.). This enables cast-like functionality where mobile devices become remote controls.
|
||||
|
||||
#### 11.2.1 Architecture Overview
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph JellyTauApp["JellyTau App"]
|
||||
SessionsStore["SessionsStore<br/>- sessions[]<br/>- activeId"]
|
||||
RemoteControl["RemoteControl<br/>- play/pause<br/>- seek<br/>- volume<br/>- playItem"]
|
||||
SessionsStore -->|"Select"| RemoteControl
|
||||
end
|
||||
|
||||
subgraph JellyfinServer["Jellyfin Server"]
|
||||
GetSessions["GET /Sessions"]
|
||||
ActiveSessions["Active Sessions<br/>- Jellyfin Web (Chrome)<br/>- Jellyfin Android TV<br/>- Jellyfin for Roku"]
|
||||
GetSessions --> ActiveSessions
|
||||
end
|
||||
|
||||
SessionsStore <-->|"Poll (5-10s)"| GetSessions
|
||||
RemoteControl -->|"Commands"| ActiveSessions
|
||||
```
|
||||
|
||||
#### 11.2.2 Jellyfin Sessions API
|
||||
|
||||
**Endpoints:**
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/Sessions` | List all active sessions |
|
||||
| POST | `/Sessions/{id}/Playing` | Start playback of item(s) |
|
||||
| POST | `/Sessions/{id}/Playing/PlayPause` | Toggle play/pause |
|
||||
| POST | `/Sessions/{id}/Playing/Stop` | Stop playback |
|
||||
| POST | `/Sessions/{id}/Playing/Seek` | Seek to position |
|
||||
| POST | `/Sessions/{id}/Command/SetVolume` | Set volume (0-100) |
|
||||
| POST | `/Sessions/{id}/Command/Mute` | Mute |
|
||||
| POST | `/Sessions/{id}/Command/Unmute` | Unmute |
|
||||
| POST | `/Sessions/{id}/Command/VolumeUp` | Volume up |
|
||||
| POST | `/Sessions/{id}/Command/VolumeDown` | Volume down |
|
||||
|
||||
**Session Response Schema:**
|
||||
```typescript
|
||||
interface Session {
|
||||
id: string;
|
||||
userId: string;
|
||||
userName: string;
|
||||
client: string; // "Jellyfin Web", "Jellyfin Android TV", etc.
|
||||
deviceName: string; // "Living Room TV", "Chrome - Windows"
|
||||
deviceId: string;
|
||||
applicationVersion: string;
|
||||
isActive: boolean;
|
||||
supportsMediaControl: boolean;
|
||||
supportsRemoteControl: boolean;
|
||||
playState: {
|
||||
positionTicks: number;
|
||||
canSeek: boolean;
|
||||
isPaused: boolean;
|
||||
isMuted: boolean;
|
||||
volumeLevel: number; // 0-100
|
||||
repeatMode: string;
|
||||
} | null;
|
||||
nowPlayingItem: MediaItem | null;
|
||||
playableMediaTypes: string[]; // ["Audio", "Video"]
|
||||
supportedCommands: string[]; // ["PlayPause", "Seek", "SetVolume", ...]
|
||||
}
|
||||
```
|
||||
|
||||
#### 11.2.3 API Layer
|
||||
|
||||
**Location**: `src/lib/api/sessions.ts`
|
||||
|
||||
```typescript
|
||||
export class SessionsApi {
|
||||
constructor(private client: JellyfinClient) {}
|
||||
|
||||
async getSessions(): Promise<Session[]> {
|
||||
return this.client.get<Session[]>('/Sessions', {
|
||||
params: { controllableByUserId: this.client.userId }
|
||||
});
|
||||
}
|
||||
|
||||
async playOnSession(sessionId: string, itemIds: string[], startIndex = 0): Promise<void> {
|
||||
await this.client.post(`/Sessions/${sessionId}/Playing`, {
|
||||
itemIds,
|
||||
startIndex,
|
||||
playCommand: 'PlayNow'
|
||||
});
|
||||
}
|
||||
|
||||
async sendCommand(sessionId: string, command: SessionCommand): Promise<void> {
|
||||
await this.client.post(`/Sessions/${sessionId}/Playing/${command}`);
|
||||
}
|
||||
|
||||
async setVolume(sessionId: string, volume: number): Promise<void> {
|
||||
await this.client.post(`/Sessions/${sessionId}/Command/SetVolume`, {
|
||||
Arguments: { Volume: Math.round(volume) }
|
||||
});
|
||||
}
|
||||
|
||||
async seek(sessionId: string, positionTicks: number): Promise<void> {
|
||||
await this.client.post(`/Sessions/${sessionId}/Playing/Seek`, {
|
||||
seekPositionTicks: positionTicks
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type SessionCommand = 'PlayPause' | 'Stop' | 'Pause' | 'Unpause' |
|
||||
'NextTrack' | 'PreviousTrack' | 'Mute' | 'Unmute';
|
||||
```
|
||||
|
||||
#### 11.2.4 Sessions Store
|
||||
|
||||
**Location**: `src/lib/stores/sessions.ts`
|
||||
|
||||
```typescript
|
||||
interface SessionsState {
|
||||
sessions: Session[];
|
||||
activeSessionId: string | null;
|
||||
isPolling: boolean;
|
||||
lastUpdated: Date | null;
|
||||
}
|
||||
|
||||
function createSessionsStore() {
|
||||
const { subscribe, update } = writable<SessionsState>({
|
||||
sessions: [],
|
||||
activeSessionId: null,
|
||||
isPolling: false,
|
||||
lastUpdated: null
|
||||
});
|
||||
|
||||
let pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
startPolling(api: SessionsApi, intervalMs = 5000) {
|
||||
this.stopPolling();
|
||||
pollInterval = setInterval(() => this.refresh(api), intervalMs);
|
||||
this.refresh(api); // Immediate first fetch
|
||||
},
|
||||
|
||||
stopPolling() {
|
||||
if (pollInterval) clearInterval(pollInterval);
|
||||
},
|
||||
|
||||
async refresh(api: SessionsApi) {
|
||||
const sessions = await api.getSessions();
|
||||
update(s => ({
|
||||
...s,
|
||||
sessions: sessions.filter(s => s.supportsRemoteControl),
|
||||
lastUpdated: new Date()
|
||||
}));
|
||||
},
|
||||
|
||||
selectSession(sessionId: string | null) {
|
||||
update(s => ({ ...s, activeSessionId: sessionId }));
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### 11.2.5 Android MediaRouter Integration (IR-021)
|
||||
|
||||
On Android, when controlling a remote session's volume, JellyTau integrates with the system audio control panel via MediaRouter API:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
User["User presses volume button"]
|
||||
VolumePanel["System Volume Panel<br/>appears showing<br/>remote session name"]
|
||||
MediaRouter["MediaRouter.Callback<br/>onRouteVolumeChanged()"]
|
||||
SessionsApi["SessionsApi.setVolume()<br/>POST /Sessions/{id}/<br/>Command/SetVolume"]
|
||||
|
||||
User --> VolumePanel
|
||||
VolumePanel --> MediaRouter
|
||||
MediaRouter --> SessionsApi
|
||||
```
|
||||
|
||||
**Kotlin Implementation** (`JellyTauMediaRouterCallback.kt`):
|
||||
```kotlin
|
||||
class JellyTauMediaRouterCallback(
|
||||
private val sessionsApi: SessionsApi
|
||||
) : MediaRouter.Callback() {
|
||||
|
||||
private var selectedRoute: MediaRouter.RouteInfo? = null
|
||||
|
||||
override fun onRouteSelected(router: MediaRouter, route: RouteInfo) {
|
||||
selectedRoute = route
|
||||
// Update UI to show remote session controls
|
||||
}
|
||||
|
||||
override fun onRouteVolumeChanged(router: MediaRouter, route: RouteInfo) {
|
||||
selectedRoute?.let { selected ->
|
||||
if (route == selected) {
|
||||
val volume = route.volume
|
||||
val sessionId = route.extras?.getString("sessionId")
|
||||
sessionId?.let {
|
||||
// Send volume to Jellyfin session
|
||||
coroutineScope.launch {
|
||||
sessionsApi.setVolume(it, volume)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**MediaRouteProvider** - Exposes Jellyfin sessions as cast-like routes:
|
||||
```kotlin
|
||||
class JellyfinMediaRouteProvider(context: Context) : MediaRouteProvider(context) {
|
||||
|
||||
fun updateSessionRoutes(sessions: List<Session>) {
|
||||
val routes = sessions
|
||||
.filter { it.supportsRemoteControl }
|
||||
.map { session ->
|
||||
MediaRouteDescriptor.Builder(session.id, session.deviceName)
|
||||
.setDescription(session.client)
|
||||
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
|
||||
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
|
||||
.setVolumeMax(100)
|
||||
.setVolume(session.playState?.volumeLevel ?: 100)
|
||||
.addControlFilter(IntentFilter(MediaControlIntent.ACTION_PLAY))
|
||||
.setExtras(Bundle().apply {
|
||||
putString("sessionId", session.id)
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
setDescriptor(MediaRouteProviderDescriptor.Builder()
|
||||
.addRoutes(routes)
|
||||
.build())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 11.2.6 UI Components
|
||||
|
||||
**CastButton** (`src/lib/components/sessions/CastButton.svelte`) - Cast button for MiniPlayer:
|
||||
- Integrated into MiniPlayer component (visible on all screen sizes)
|
||||
- Shows cast icon that changes when connected to remote session
|
||||
- Displays badge with number of available sessions
|
||||
- Auto-polls for sessions every 15 seconds
|
||||
- Opens SessionPickerModal when clicked
|
||||
- Visual indicators:
|
||||
- Gray when disconnected, purple when connected
|
||||
- Badge shows count of available devices
|
||||
- Green dot indicator when actively casting
|
||||
|
||||
**SessionPickerModal** (`src/lib/components/sessions/SessionPickerModal.svelte`) - Modal for selecting cast device:
|
||||
- Lists controllable sessions with device name and client type
|
||||
- Shows currently playing item (if any) for each session
|
||||
- Highlights currently selected/connected session
|
||||
- Device type icons (TV, Web, Mobile, Generic)
|
||||
- "Disconnect" option when already connected
|
||||
- Empty state with refresh button
|
||||
- Responsive: slides up on mobile, centered on desktop
|
||||
|
||||
**SessionCard** (`src/lib/components/sessions/SessionCard.svelte`) - Individual session display:
|
||||
- Device name and client information
|
||||
- Now playing preview with artwork
|
||||
- Play/pause state indicator
|
||||
- Position and volume display
|
||||
|
||||
**SessionsList** (`src/lib/components/sessions/SessionsList.svelte`) - List of all sessions:
|
||||
- Filters to show only controllable sessions
|
||||
- Refresh button for manual updates
|
||||
- Loading and error states
|
||||
- Empty state messaging
|
||||
|
||||
**RemoteControls** (`src/lib/components/sessions/RemoteControls.svelte`) - Full remote playback control:
|
||||
- Uses polling data for play state (position, volume, etc.)
|
||||
- Sends commands via SessionsApi
|
||||
- Shows "Controlling: {deviceName}" header
|
||||
- Full playback controls: play/pause, next/previous, stop
|
||||
- Seek bar with position display (if supported)
|
||||
- Volume slider
|
||||
- Empty state when no media playing on remote session
|
||||
|
||||
### 11.3 MPRIS Integration (Linux - Future)
|
||||
|
||||
```rust
|
||||
// Future: D-Bus media controls
|
||||
pub struct MprisController {
|
||||
connection: Connection,
|
||||
player: Arc<Mutex<PlayerController>>,
|
||||
}
|
||||
|
||||
impl MprisController {
|
||||
fn register_handlers(&self) {
|
||||
// Handle PlayPause, Next, Previous from system
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Offline Database Design
|
||||
## 10. Offline Database Design
|
||||
|
||||
### 12.1 Entity Relationship Diagram
|
||||
|
||||
@ -3176,7 +2709,7 @@ src-tauri/src/storage/
|
||||
|
||||
---
|
||||
|
||||
## 13. File Structure Summary
|
||||
## 11. File Structure Summary
|
||||
|
||||
```
|
||||
src-tauri/src/
|
||||
@ -3286,9 +2819,9 @@ src/lib/
|
||||
|
||||
---
|
||||
|
||||
## 14. Security
|
||||
## 12. Security
|
||||
|
||||
### 14.1 Authentication Token Storage
|
||||
### 12.1 Authentication Token Storage
|
||||
|
||||
Access tokens are **not** stored in the SQLite database. Instead, they are stored using platform-native secure storage:
|
||||
|
||||
@ -3314,7 +2847,7 @@ jellytau::{server_id}::{user_id}::access_token
|
||||
- System keyrings provide OS-level encryption and access control
|
||||
- Fallback ensures functionality on minimal systems without a keyring daemon
|
||||
|
||||
### 14.2 Secure Storage Module
|
||||
### 12.2 Secure Storage Module
|
||||
|
||||
**Location**: `src-tauri/src/secure_storage/` (planned)
|
||||
|
||||
@ -3330,7 +2863,7 @@ pub struct KeyringStorage; // Uses keyring crate
|
||||
pub struct EncryptedFileStorage; // AES-256-GCM fallback
|
||||
```
|
||||
|
||||
### 14.3 Network Security
|
||||
### 12.3 Network Security
|
||||
|
||||
| Aspect | Implementation |
|
||||
|--------|----------------|
|
||||
@ -3339,7 +2872,7 @@ pub struct EncryptedFileStorage; // AES-256-GCM fallback
|
||||
| Token Transmission | Bearer token in `Authorization` header only |
|
||||
| Token Refresh | Handled by Jellyfin server (long-lived tokens) |
|
||||
|
||||
### 14.4 Local Data Protection
|
||||
### 12.4 Local Data Protection
|
||||
|
||||
| Data Type | Protection |
|
||||
|-----------|------------|
|
||||
@ -3348,7 +2881,7 @@ pub struct EncryptedFileStorage; // AES-256-GCM fallback
|
||||
| Downloaded Media | Filesystem permissions only |
|
||||
| Cached Thumbnails | Filesystem permissions only |
|
||||
|
||||
### 14.5 Security Considerations
|
||||
### 12.5 Security Considerations
|
||||
|
||||
1. **No Secrets in SQLite**: The database contains only non-sensitive metadata
|
||||
2. **Token Isolation**: Each user/server combination has a separate token entry
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,219 +0,0 @@
|
||||
# UX Implementation Gaps
|
||||
|
||||
This document tracks inconsistencies between the documented UX flows ([UXFlows.md](UXFlows.md)) and the actual implementation.
|
||||
|
||||
**Last Updated:** 2026-01-03
|
||||
|
||||
---
|
||||
|
||||
## Recently Resolved ✅
|
||||
|
||||
### 1. Route Structure Mismatch ✅ RESOLVED
|
||||
|
||||
**Was Documented:**
|
||||
- Login screen at `/`
|
||||
- Home/Library landing at `/library` (after login)
|
||||
|
||||
**Actual Implementation:**
|
||||
- Login screen at [/login](src/routes/login/+page.svelte)
|
||||
- Home page with carousels at [/](src/routes/+page.svelte)
|
||||
- Library selector at [/library](src/routes/library/+page.svelte)
|
||||
|
||||
**Resolution:** Updated UXFlows.md Sections 1.1, 2.1, 2.2, 8.2 to reflect actual routes
|
||||
|
||||
---
|
||||
|
||||
### 3. Audio Player Back Button Navigation ✅ RESOLVED
|
||||
|
||||
**Issue:**
|
||||
- Audio player close button simply closed the modal without respecting browser history
|
||||
- Users expected back button to return to previous page (e.g., album view)
|
||||
|
||||
**Resolution:**
|
||||
- Updated AudioPlayer onClose handler to call `window.history.back()` ([library/+layout.svelte:212-215](src/routes/library/+layout.svelte))
|
||||
- Back button now returns user to the page they were on before opening full player
|
||||
- Updated UXFlows.md Section 3.3 to document browser history navigation
|
||||
|
||||
**Impact:** Better UX - natural back button behavior matches user expectations
|
||||
|
||||
---
|
||||
|
||||
### 4. Video Player Touch Gestures ✅ RESOLVED
|
||||
|
||||
**Was Documented:**
|
||||
- Double tap left: Rewind 10 seconds
|
||||
- Double tap right: Forward 10 seconds
|
||||
- Swipe up/down: Adjust brightness (left) or volume (right)
|
||||
|
||||
**Implementation:**
|
||||
- ✅ Double-tap detection on left/right sides of screen ([VideoPlayer.svelte:228-319](src/lib/components/player/VideoPlayer.svelte))
|
||||
- ✅ Animated visual feedback showing "-10" or "+10" with fade-out animation
|
||||
- ✅ Swipe gesture detection for vertical swipes (>50px minimum)
|
||||
- ✅ Left side swipe: Brightness control (0.3-1.7x) with CSS filter
|
||||
- ✅ Right side swipe: Volume control (0-100%) with real-time adjustment
|
||||
- ✅ Visual indicators showing current brightness/volume level during swipe
|
||||
- ✅ Updated UXFlows.md Section 4.2 to document all gestures
|
||||
|
||||
**Impact:** Excellent mobile video UX - touch-optimized controls match YouTube/Netflix patterns
|
||||
|
||||
---
|
||||
|
||||
## Remaining Issues
|
||||
|
||||
### Critical Issues
|
||||
|
||||
None! All critical UX issues have been resolved. 🎉
|
||||
|
||||
---
|
||||
|
||||
### Low Priority Issues
|
||||
|
||||
#### 1. MiniPlayer Shows More Controls Than Documented
|
||||
|
||||
**Documented:**
|
||||
- Shows: artwork, title, artist, play/pause, next, favorite
|
||||
|
||||
**Actual Implementation:**
|
||||
- Shows: artwork, title, artist, play/pause, next, previous, shuffle, repeat, volume (desktop), cast button, favorite (desktop)
|
||||
- Much richer control set than documented
|
||||
|
||||
**Impact:** Low - This is actually better than documented
|
||||
|
||||
**Fix Required:** Update UXFlows.md Section 3.1 to document full control set
|
||||
|
||||
---
|
||||
|
||||
#### 2. Search Results Display Simplified
|
||||
|
||||
**Documented (Section 6.1):**
|
||||
- Results grouped by type: Songs, Albums, Artists, Movies, Episodes
|
||||
- "See all (23)" expandable sections
|
||||
|
||||
**Actual Implementation:**
|
||||
- [Search page](src/routes/search/+page.svelte) uses `LibraryGrid` component
|
||||
- No visual grouping by type shown in code
|
||||
- Simplified single-list results
|
||||
|
||||
**Impact:** Low - Functional but less organized than documented
|
||||
|
||||
**Fix Required:**
|
||||
- Option A: Implement grouped results display
|
||||
- Option B: Update documentation to match simplified implementation
|
||||
|
||||
---
|
||||
|
||||
## Undocumented Features
|
||||
|
||||
### 3. Sessions/Remote Control Feature
|
||||
|
||||
**Not in UXFlows.md:**
|
||||
- Route exists: [/sessions](src/routes/sessions/+page.svelte)
|
||||
- Appears to be for remote session control
|
||||
- Cast button visible in MiniPlayer
|
||||
|
||||
**Impact:** None - Feature exists but isn't documented
|
||||
|
||||
**Fix Required:** Document sessions feature in UXFlows.md (Section 8.2 exists in SoftwareArchitecture.md but not in UX flows)
|
||||
|
||||
---
|
||||
|
||||
### 4. URL Query Parameters for Queue Context
|
||||
|
||||
**Not in UXFlows.md:**
|
||||
- Uses `?queue=parent:{id}&shuffle=true` in URLs
|
||||
- Enables queue restoration and context tracking
|
||||
|
||||
**Impact:** None - Implementation detail
|
||||
|
||||
**Fix Required:** Optional - add technical note in UXFlows.md about queue URL params
|
||||
|
||||
---
|
||||
|
||||
## Consistent Implementations ✅
|
||||
|
||||
These areas match the documentation exactly:
|
||||
|
||||
- ✅ **Resume Dialog** - Correctly implements 30s/90% threshold ([player/[id]/+page.svelte:84-86](src/routes/player/[id]/+page.svelte))
|
||||
- ✅ **Audio Playback** - Uses `player_play_queue` command as documented
|
||||
- ✅ **MiniPlayer Hiding** - Correctly hides for video content
|
||||
- ✅ **Keyboard Shortcuts** - Video player keyboard controls work as documented
|
||||
- ✅ **Music Library Structure** - Category pages match documentation
|
||||
|
||||
---
|
||||
|
||||
## Recommendations by Priority
|
||||
|
||||
### High Priority
|
||||
|
||||
1. ~~**Update UXFlows.md Route Structure**~~ ✅ **COMPLETED**
|
||||
- ✅ Corrected login route to `/login`
|
||||
- ✅ Clarified `/` is home page, `/library` is library selector
|
||||
- ✅ Updated Sections 1.1, 2.1, 2.2, 7.2, 8.1, 8.2
|
||||
|
||||
2. ~~**Add Downloads Navigation**~~ ✅ **COMPLETED**
|
||||
- ✅ Added Downloads link to header navigation (desktop)
|
||||
- ✅ Added Downloads icon button to header (all screen sizes)
|
||||
- ✅ Updated UXFlows.md to document navigation paths
|
||||
|
||||
### Medium Priority
|
||||
|
||||
3. ~~**Add Settings Navigation Link**~~ ✅ **COMPLETED**
|
||||
- ✅ Added Settings link to desktop header navigation
|
||||
- ✅ Added Settings to mobile overflow menu
|
||||
- ✅ Updated UXFlows.md Section 8.1
|
||||
|
||||
4. ~~**Implement Video Player Touch Gestures**~~ ✅ **COMPLETED**
|
||||
- ✅ Implemented double-tap detection (left/right sides)
|
||||
- ✅ Implemented swipe gestures (brightness + volume control)
|
||||
- ✅ Added visual feedback animations
|
||||
- ✅ Updated UXFlows.md Section 4.2
|
||||
|
||||
### Low Priority
|
||||
|
||||
5. **Document MiniPlayer Full Feature Set** (#4)
|
||||
- Update Section 3.1 to show all controls
|
||||
- Document desktop vs mobile differences
|
||||
|
||||
6. **Document Sessions Feature** (#7)
|
||||
- Add UX flow for remote session selection
|
||||
- Explain cast button behavior
|
||||
- Link to architecture documentation
|
||||
|
||||
7. **Enhance Search Results Display** (#5)
|
||||
- Consider implementing grouped results as documented
|
||||
- Or update docs to match current implementation
|
||||
|
||||
---
|
||||
|
||||
## Quick Fix Checklist
|
||||
|
||||
For immediate documentation updates:
|
||||
|
||||
- [x] Fix login route in UXFlows.md (Section 2.1): `/` → `/login` ✅
|
||||
- [x] Fix home route in UXFlows.md (Section 2.1): `/library` → `/` ✅
|
||||
- [x] Document actual navigation structure (Section 1.1) ✅
|
||||
- [x] Update Downloads navigation (Section 7.2) ✅
|
||||
- [x] Update Settings navigation (Section 8.1) ✅
|
||||
- [x] Update logout flow (Section 8.2) ✅
|
||||
- [ ] Update video controls documentation (Section 4.2) to match keyboard implementation
|
||||
- [ ] Document MiniPlayer cast button (Section 3.1)
|
||||
- [ ] Add Sessions feature UX flow (new section)
|
||||
|
||||
For code implementation:
|
||||
|
||||
- [x] Add Downloads link to header navigation (desktop) ✅
|
||||
- [x] Add Downloads icon button to header (all screen sizes) ✅
|
||||
- [x] Add Settings link to header navigation ✅
|
||||
- [x] Implement mobile overflow menu (Android-style) ✅
|
||||
- [x] Fix audio player back button to use browser history ✅
|
||||
- [ ] (Optional) Implement video player touch gestures
|
||||
- [ ] (Optional) Implement grouped search results
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- The actual implementation is generally **more feature-rich** than documented (MiniPlayer controls, keyboard shortcuts)
|
||||
- Main gaps are in **mobile navigation accessibility** (missing More tab)
|
||||
- Most core functionality **matches or exceeds** documentation
|
||||
- Video player touch gestures are the only **missing feature** that was explicitly documented
|
||||
Loading…
x
Reference in New Issue
Block a user