Milestone 3: Navidrome (OpenSubsonic) playback provider

Replace the directory-scan queue with a real ingestion pipeline:
provider -> fetcher -> cache -> ready queue, driven by a background
prefetch thread.

- subsonic.py: minimal OpenSubsonic client (salted-token auth,
  getPlaylists/getPlaylist, raw streaming download).
- providers/navidrome.py: pick tracks from a playlist (by name or id),
  with anti-repeat and periodic playlist reload.
- fetchers/subsonic.py: atomic download into the shared cache.
- db.py: SQLite state — append-only play history (anti-repeat + stats)
  and cache_files LRU retention (keep the N most recently played).
- queue.py: prefetch buffer + retention on play; graceful degradation
  to the stream's local-cache fallback when no source is configured.
- api.py: GET /next now carries real title/artist metadata.
- Config via .env (Navidrome credentials), persistent state/ volume,
  httpx dependency.

Verified end-to-end against a live Navidrome: playlist resolved,
tracks downloaded and broadcast, retention and history correct.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nemunaire 2026-07-02 17:23:45 +08:00
commit 8c27498632
17 changed files with 594 additions and 71 deletions

8
.gitignore vendored
View file

@ -2,12 +2,16 @@
/cache/*
!/cache/.gitkeep
# État de l'ingestion (jalons suivants)
# Secrets locaux
.env
# État de l'ingestion (base SQLite persistante)
/state/
*.db
*.db-journal
*.db-wal
# Python (jalons suivants)
# Python
__pycache__/
*.pyc
.venv/