Files
jellytau/docs/architecture/README.md
T
dtourolle 1677f5f299 refactor(player): delete the webview video path; mpv selects its own tracks
DR-235 phase 3. Every video renderer is native now: mpv on Linux and
Windows, ExoPlayer on Android, all drawing behind the transparent
webview. The HTML5 <video> path is gone, not bypassed:

- Frontend: hls.js, Html5PlayerAdapter and its compatibility shim, the
  createAdapter factory, streamTransport, hlsRecovery, timeTracking,
  videoFit, the <video>/<track> markup and every element handler in
  VideoPlayer (3277 -> 2144 lines), the experimentalNativeVideo store
  and its Settings toggle, webviewVideoFallback/supportsNativeVideo, and
  the setHtml5VideoState PiP bridge call. NativePlayerAdapter is the one
  video adapter; webview audio gets its own adapter kind.
- Rust: use_html5 dropped from player_seek_video,
  player_switch_audio_track and player_set_stream_quality with the
  Html5* strategies and ReloadStream responses; use_html5_element and
  VideoBackend dropped from PlayerStatus; player_play_item always loads
  the backend (set_current_item removed); Capabilities::webview removed;
  the WebKitGTK GStreamer/VAAPI setup (and its gst-inspect spawn) removed.
- Android: the HTML5 video state in PictureInPictureManager and
  ScreenWakeManager, and the bridge method feeding it.
- CSP: connect-src loses http:/https: and worker-src loses blob: -
  both existed for hls.js; with it gone they were only an exfiltration
  channel and a blob worker for injected script. A test now keeps them
  out.

mpv takes over what the <video> element did (mpv_tracks, UT-275):
subtitles are the WebVTT list the play request carries, queued on
sub-files and selected by position in that list, starting off; audio
tracks are selected by position in the file; sid/aid are reset before
each load. Without this, Linux video had no subtitle selection and a
direct-play audio switch failed since mpv became its renderer.

Verified: Rust 948 passing, and the same 948 cross-compiled for Windows
under wine against the shipped DLL (track tests included); frontend
1111 passing; aarch64 debug APK builds. Lint warnings 158 -> 146, CI
ratchet tightened to match. Not yet seen on Windows hardware.
2026-09-24 23:11:17 -04:00

14 KiB

JellyTau Software Architecture

This document describes the current architecture of JellyTau, a cross-platform Jellyfin client built with Tauri, SvelteKit, and Rust.

Last Updated: 2026-06-20

Architecture Overview

JellyTau uses a client-server architecture: business logic lives in a comprehensive Rust backend, while a UI-rich Svelte frontend handles presentation and interaction.

Architecture Principles

  • Business Logic in Rust: Core logic — playback, repository, sync, downloads, connectivity — lives in Rust for performance, reliability, and type safety.
  • Presentation in Svelte: The frontend (~20.5k non-test lines) owns UI, layout, navigation, and interaction state and invokes Rust commands. It is intentionally UI-heavy, not a thin wrapper. Largest pieces: components + routes (~14.6k lines), stores (~3.4k), api/services/utils (~2.4k); VideoPlayer.svelte alone is ~1.6k lines.
  • Events + Polling hybrid: Rust emits events the frontend listens to, and the UI also polls status on short intervals in a few hot spots (e.g. queue status in library/+layout.svelte, playback progress in VideoPlayer.svelte).
  • Unified player boundary: UI components control playback only through the frontend facade src/lib/player/index.ts (playerController), never by calling commands.player* directly. Webview-rendered HTML5 video reports its state back into Rust via src/lib/player/html5Adapter.ts and the player_report_* commands, so the PlayerController stays the single source of truth in both native (MPV/ExoPlayer) and HTML5 modes (see 05-platform-backends.md).
  • Handle-Based Resources: UUID handles for stateful Rust objects.
  • Cache-First: Parallel queries with intelligent fallback.
  • Single source of truth for reachability: Server reachability is derived from the outcome of real repository traffic, not a side-channel poller. The OnlineRepository reports each server result to the ConnectivityMonitor (classified via RepoError), which applies a time-window debounce before declaring the server offline and recovers instantly on the first success. The standalone /System/Info/Public probe runs only while offline, as a recovery detector for idle sessions.
  • Poison-tolerant locking: Shared std::sync state is accessed via the MutexSafe/RwLockSafe helpers in utils/lock.rs, which recover a poisoned lock instead of cascading a panic across the player.
  • Graceful backend init: If a native player backend (MPV/ExoPlayer) fails to initialize, the app falls back to a no-op backend and emits a backend-init-failed event rather than crashing.
flowchart TB
    subgraph Frontend["Svelte Frontend"]
        subgraph Stores["Stores (Thin Wrappers)"]
            auth["auth"]
            player["player"]
            queue["queue"]
            library["library"]
            connectivity["connectivity"]
            playbackMode["playbackMode"]
        end
        subgraph Components
            playerComp["player/"]
            libraryComp["library/"]
            Search["Search"]
        end
        subgraph Routes
            routeLibrary["/library"]
            routePlayer["/player"]
            routeRoot["/"]
        end
        subgraph API["API Layer (Thin Client)"]
            RepositoryClient["RepositoryClient<br/>(Handle-based)"]
            JellyfinClient["JellyfinClient<br/>(Helper)"]
        end
    end

    Frontend -->|"Tauri IPC (invoke)"| Backend

    subgraph Backend["Rust Backend (Business Logic)"]
        subgraph Commands["Tauri Commands (90+)"]
            PlayerCmds["player.rs"]
            RepoCmds["repository.rs (27)"]
            PlaybackModeCmds["playback_mode.rs (5)"]
            StorageCmds["storage.rs"]
            ConnectivityCmds["connectivity.rs (7)"]
        end

        subgraph Core["Core Modules"]
            MediaSessionManager["MediaSessionManager<br/>(Audio/Movie/TvShow/Idle)"]

            PlayerController["PlayerController<br/>+ PlayerBackend<br/>+ QueueManager"]

            Repository["Repository Layer<br/>HybridRepository (cache-first)<br/>OnlineRepository (HTTP)<br/>OfflineRepository (SQLite)"]

            PlaybackModeManager["PlaybackModeManager<br/>(Local/Remote/Idle)"]

            ConnectivityMonitor["ConnectivityMonitor<br/>(Adaptive polling)"]

            HttpClient["HttpClient<br/>(Exponential backoff retry)"]
        end

        subgraph Storage["Storage Layer"]
            DatabaseService["DatabaseService<br/>(Async trait)"]
            SQLite["SQLite Database<br/>(13 tables)"]
        end

        Commands --> Core
        Core --> Storage
        Repository --> HttpClient
        Repository --> DatabaseService
        Repository -->|"reports server outcome<br/>(success / RepoError)"| ConnectivityMonitor
    end

The Repository --> ConnectivityMonitor edge is the source of truth for the offline/online banner: every server request the user actually makes updates reachability. The monitor's own polling is now an offline-only recovery probe (see 07-connectivity.md).


Detailed Documentation

Each major subsystem is documented in its own file in this directory:

Document Contents
01 - Rust Backend Media session state machine, player state machine, playback mode, media items, queue manager, favorites (marking + browsing), player backend trait, player controller, playlist system, domain vocabulary owned by Rust (search scope, library exclusions, streaming quality ladder), background workers (catalog indexer, drains), Tauri commands
02 - Svelte Frontend Store structure, music library navigation, playback reporting, repository architecture, playback mode system, database service abstraction, component hierarchy, MiniPlayer, sleep timer, auto-play, navigation guard, playlist management UI, library mosaic, series/episode navigation, downloaded browse, safe-area insets, native-video store, logging
03 - Data Flow Repository query flow (cache-first), locally-indexed search, playback initiation, playback mode transfer, queue navigation, volume control
04 - Type Sync & Threading Rust/TypeScript type synchronization, Tauri v2 IPC parameter naming convention, thread safety patterns
05 - Platform Backends Player events system, MpvBackend (Linux), ExoPlayerBackend (Android) incl. audio settings parity, native video compositing, MediaSession & remote volume, album art caching, backend initialization
06 - Downloads & Offline Download manager, download worker, smart caching engine, one storage model (cache entries are downloads), offline catalog visibility, download/offline commands, player integration, frontend store, UI components
07 - Connectivity HTTP client with retry logic, connectivity monitor, network resilience architecture
08 - Database Design Entity relationships, all table definitions (servers, users, libraries, items, user_data, downloads, media_streams, sync_queue, thumbnails, playlists), key queries, data flow diagrams, storage estimates
09 - Security Authentication token storage, secure storage module, network security, webview CSP + asset-protocol scope, path confinement and input binding, local data protection

File Structure Summary

src-tauri/src/
├── lib.rs                    # Tauri app setup, state initialization
├── commands/                 # Tauri command handlers (~245 #[tauri::command] fns)
│   ├── mod.rs               # Command exports
│   ├── player/              # Player commands: queue, remote, session, settings, timers
│   ├── repository.rs        # Repository commands (items, search, favourites, disk usage)
│   ├── catalog.rs           # Catalog sync + the background index pass
│   ├── favorites.rs         # Offline favourite drain
│   ├── library.rs           # Library listing + folder exclusions
│   ├── playlist.rs          # Playlist commands
│   ├── playback_mode.rs     # Local/remote transfer
│   ├── playback_reporting.rs
│   ├── connectivity.rs      # Connectivity commands
│   ├── storage/             # Storage & database commands: people, series_prefs, thumbnails
│   ├── download/            # Download commands: mod, pinning, smart_cache
│   ├── offline.rs           # Offline commands
│   ├── device.rs            # Device id / capabilities
│   ├── sessions.rs          # Remote sessions
│   ├── sync.rs              # Sync queue commands
│   └── sync_drain.rs        # Background sync-queue drain
├── repository/              # Repository pattern implementation
│   ├── mod.rs               # MediaRepository trait, handle management
│   ├── types.rs             # RepoError, Library, MediaItem, etc.
│   ├── hybrid.rs            # HybridRepository with cache-first racing
│   ├── online.rs            # OnlineRepository (HTTP API)
│   └── offline.rs           # OfflineRepository (SQLite queries)
├── playback_mode/           # Playback mode manager
│   └── mod.rs               # PlaybackMode enum, transfer logic
├── connectivity/            # Connectivity monitoring
│   └── mod.rs               # ConnectivityMonitor, adaptive polling
├── jellyfin/                # Jellyfin API client
│   ├── mod.rs               # Module exports
│   ├── http_client.rs       # HTTP client with retry logic
│   └── client.rs            # JellyfinClient for API calls
├── storage/                 # Database layer
│   ├── mod.rs               # Database struct, migrations
│   ├── db_service.rs        # DatabaseService trait (async wrapper)
│   ├── schema.rs            # Table definitions
│   └── queries/             # Query modules
├── download/                # Download manager module
│   ├── mod.rs               # DownloadManager, DownloadInfo, DownloadTask
│   ├── worker.rs            # DownloadWorker, HTTP streaming, retry logic
│   ├── events.rs            # DownloadEvent enum
│   └── cache.rs             # SmartCache, CacheConfig, LRU eviction
└── player/                  # Player subsystem
    ├── mod.rs               # PlayerController
    ├── session.rs           # MediaSessionManager, MediaSessionType
    ├── state.rs             # PlayerState, PlayerEvent
    ├── media.rs             # MediaItem, MediaSource, MediaType
    ├── queue.rs             # QueueManager, RepeatMode
    ├── backend.rs           # PlayerBackend trait, NullBackend
    ├── events.rs            # PlayerStatusEvent, TauriEventEmitter
    ├── mpv/                 # Linux MPV backend
    │   ├── mod.rs           # MpvBackend implementation
    │   └── event_loop.rs    # Dedicated thread for MPV operations
    └── android/             # Android ExoPlayer backend
        └── mod.rs           # ExoPlayerBackend + JNI bindings

src/lib/
├── api/                     # Thin API layer (~200 lines total)
│   ├── types.ts             # TypeScript type definitions
│   ├── repository-client.ts # RepositoryClient wrapper (~100 lines)
│   ├── client.ts            # JellyfinClient (helper for streaming)
│   └── sessions.ts          # SessionsApi (remote session control)
├── player/                  # Unified player boundary (frontend)
│   ├── index.ts             # playerController facade — the only write-side entry point for playback
├── services/
│   ├── playerEvents.ts      # Tauri event listener for player events
│   └── playbackReporting.ts # Thin wrapper (~50 lines)
├── stores/                  # Thin reactive wrappers over Rust commands
│   ├── index.ts             # Re-exports
│   ├── auth.ts              # Auth store (calls Rust commands)
│   ├── player.ts            # Player store
│   ├── queue.ts             # Queue store
│   ├── library.ts           # Library store
│   ├── playbackMode.ts      # Playback mode store (~150 lines)
│   ├── connectivity.ts      # Connectivity store (~250 lines)
│   └── downloads.ts         # Downloads store with event listeners
└── components/
    ├── Search.svelte
    ├── player/              # Player UI components
    ├── playlist/            # Playlist modals (Create, AddTo)
    ├── sessions/            # Remote session control UI
    ├── downloads/           # Download UI components
    └── library/             # Library UI components + PlaylistDetailView

Key Architecture Changes

What moved to Rust (~3,500 lines of business logic):

  1. HTTP Client (338 lines) - Retry logic with exponential backoff
  2. Connectivity Monitor (301 lines) - Reachability derived from real repository traffic, time-window debounce, offline-only recovery probe, event emission
  3. Repository Pattern (1061 lines) - Cache-first hybrid with parallel racing
  4. Database Service - Async wrapper preventing UI freezing
  5. Playback Mode (303 lines) - Local/remote transfer coordination

Svelte/TypeScript frontend (~20.5k non-test lines, plus ~9.6k test lines):

  • Components + routes (~14.6k lines) — UI and presentation
  • Stores (~3.4k lines) — reactive state that invokes Rust commands and listens for events
  • api / services / utils (~2.4k lines) — typed clients, event listeners, conversion helpers

The frontend is genuinely UI-heavy; business decisions live in Rust, but the UI owns layout, navigation, and interaction state.

Total Commands: ~245 #[tauri::command] functions across 17 command modules (~58k lines of Rust, ~37k non-test lines of TypeScript/Svelte).

Counts and line totals in this file are periodic snapshots, not gates — the authority is the tree. Regenerate with grep -rc '#\[tauri::command\]' src-tauri/src and wc -l.