Files
NovaSonic/README.md
T
marcandClaude Opus 5 62391e4aae feat(docker): pull-based prod stack (docker-compose.prod.yml + Registry-Images)
Produktivsystem baut nicht mehr selbst, sondern zieht die drei fertigen Images
(novasonic-frontend/-backend/-scrobble) aus der Gitea-Registry. Das bestehende
docker-compose.yml bleibt der Build-auf-dem-Host-Weg. Dazu .dockerignore fuers
Frontend-Image (android/, backend/, .git raus) und README/CLAUDE.md aktualisiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 16:13:46 +02:00

196 lines
9.7 KiB
Markdown

# NovaSonic
A self-hosted music streaming PWA and Android app for Navidrome / Subsonic-compatible servers.
Built with Vite + vanilla ES modules (web) and Jetpack Compose + Media3/ExoPlayer (Android).
---
## Features
- **Library** — Stream from Navidrome / Subsonic; browse artists, albums, playlists, genres, starred, and random; search; star + context menu on every row.
- **Playback** — Queue with shuffle, repeat, and radio mode (endless similar songs); gapless-style preload; lock-screen / notification controls; Navidrome scrobbling.
- **Lyrics** — Synced and plain lyrics via lrclib.net, auto-fetched and cached per session.
- **Artist pages** — Hero card, bio / similar artists / top songs, Ticketmaster events, and a Deezer cross-reference showing owned vs. missing tracks. Same-name artists can be **pinned** (Deezer search or a pasted Deezer link/id) to fix the wrong-discography and wrong-photo problem; a pinned artist's similar list comes from Deezer.
- **Playlists** — Create, rename, delete, add/remove tracks, and upload a cover (Web + Android).
- **Importer & downloads** — Paste a Spotify / YouTube / YouTube Music / SoundCloud link to download via yt-dlp, with Deezer metadata enrichment and duplicate detection.
- **Metadata editor** (web) — Edit tags + cover art; files are renamed/moved and Navidrome is rescanned on save.
- **Offline** (Android) — Download albums, playlists, or single songs to private storage; an Offline tab to play and manage them (works without a server connection); downloaded tracks play from disk on every screen.
- **Listening stats** — A separate, self-hosted scrobble service records **timestamped plays** (multi-user; Navidrome only keeps a play *count*). A statistics view (Web + Android, from Settings) shows totals, top artists/tracks/albums with cover art, a day/month/year play series, and a 24h listening clock, plus a **Musik-Match** that compares two users' taste. Runs *in addition to* Navidrome/Last.fm scrobbling.
- **Audiobookshelf** — Browse and stream audiobooks with chapter navigation, speed control, sleep timer, and progress sync.
Web is an installable PWA (themes, German UI); Android is native Jetpack Compose + Media3/ExoPlayer with a mini player, media/ABS notifications, and Android Auto. See [CHANGELOG.md](CHANGELOG.md) for the full web/Android parity matrix.
---
## Project Structure
```
NovaSonic/
├── src/
│ ├── player/
│ │ ├── player.js # Playback, queue, transport, media session, preload
│ │ ├── nowplaying.js # Now Playing overlay + synced lyrics
│ │ └── scrobbleLog.js # Play-log capture + offline queue → scrobble service
│ ├── views/
│ │ ├── detail.js # Artist / album detail pages
│ │ ├── views.js # Navigation, page loaders, search
│ │ ├── events.js # Ticketmaster concert events
│ │ ├── home.js # Home page
│ │ ├── search.js # Search, artist/album navigation
│ │ ├── playlists.js # Playlist management
│ │ ├── spotify.js # Spotify importer
│ │ ├── youtube.js # YouTube importer
│ │ ├── soundcloud.js # SoundCloud importer
│ │ ├── navigation.js # Client-side router
│ │ └── abs.js # Audiobookshelf integration
│ ├── ui/
│ │ ├── ui.js # Modals, sidebar, context menu
│ │ ├── components.js # Song rows, cards, shared UI
│ │ ├── downloads.js # Download modal + job tracker
│ │ ├── metadata.js # Metadata editor
│ │ ├── modals.js # Song info, events modals
│ │ ├── notify.js # Toast notifications
│ │ ├── stats.js # Listening statistics view (overlay)
│ │ └── settings.js # Settings page
│ ├── state.js # Shared mutable state (S object)
│ ├── utils.js # Helpers (md5, esc, formatDur, toast)
│ ├── api.js # Subsonic API, Deezer API, cover art
│ └── main.js # App init, audio wiring, window globals
├── backend/
│ ├── novasonic-server.js # Express API: downloads, metadata, events proxy, ABS proxy
│ └── lib/
│ ├── config.js # Env var → config object
│ ├── jobs.js # In-memory download job store
│ ├── deezer.js # Deezer metadata: track/album/artist lookups
│ ├── metadata.js # Artwork, filename helpers, duplicate detection
│ ├── ytdlp.js # yt-dlp download + ffmpeg metadata embedding
│ ├── downloader.js # Unified downloadTrack / downloadAlbumTracks
│ └── romaji.js # Japanese → romaji search index
├── scrobble/ # Isolated listening-stats service (own container + DB)
│ └── scrobble-server.js # node:sqlite play-log: ingest / history / stats / health
├── android/ # Native Android app (Jetpack Compose + Media3)
│ └── app/src/main/kotlin/com/novasonic/app/
│ ├── api/ # SubsonicClient, AbsClient, LrcLibClient, ItunesClient (Deezer-backed)
│ ├── data/ # PrefsStore (DataStore), MusicRepository, StarState
│ ├── offline/ # Room DB, DAO, OfflineRepository, DownloadWorker (offline downloads)
│ ├── player/ # PlayerController, MusicService (Media3)
│ ├── ui/
│ │ ├── screen/ # HomeScreen, NowPlayingScreen, AbsScreen, SettingsScreen, …
│ │ ├── component/ # MiniPlayer, SongRow, SongContextSheet, …
│ │ └── theme/ # Color, Type, Theme
│ └── viewmodel/ # ViewModel per screen
├── public/
│ ├── sw.js # Service worker
│ ├── manifest.json # PWA manifest
│ └── config.js # Runtime config (generated by Docker entrypoint)
├── index.html
├── styles.css
├── vite.config.js
├── Dockerfile
├── docker-compose.yml
├── docker-compose.prod.yml # Produktiv: zieht fertige Images aus der Registry
├── nginx.conf
└── docker-entrypoint.sh # Injects NAVIDROME_URL / BACKEND_URL / SCROBBLE_URL at runtime
```
---
## Setup
### 1. Clone and configure
```bash
git clone <repo>
cd NovaSonic
cp .env.example .env
```
Edit `.env`:
```env
NAVIDROME_URL=https://music.yourdomain.com
BACKEND_URL=https://novasonic-api.yourdomain.com
SCROBBLE_URL=https://novasonic-scrobble.yourdomain.com # listening-stats service
MUSIC_PATH=/srv/navidrome/music
AUDIO_FMT=mp3 # mp3 | flac | opus | best
AUDIO_QUALITY=0 # 0 = best (yt-dlp)
MAX_PARALLEL=2
TICKETMASTER_API_KEY= # optional — free key from developer.ticketmaster.com
```
### 2. Start
```bash
docker compose up -d --build
```
Builds locally from the Dockerfiles (deps baked in → instant restarts; rebuilds after a `git pull`
are fast thanks to Docker's layer cache). No registry needed.
**Produktiv ohne Bauen** — die drei Images kommen fertig aus der Registry:
```bash
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d
```
Dafür in der `.env` `REGISTRY` (und optional `TAG`) setzen; die Images heißen
`$REGISTRY/novasonic-frontend|-backend|-scrobble`. Neue Stände von der Dev-Maschine aus mit
`npm run push-images` (bzw. `docker build -t $REGISTRY/<name>:latest <context> && docker push …`)
hochladen — Kontexte: `.` (frontend), `backend/`, `scrobble/`.
Frontend on port 8080, backend on port 3700, scrobble service on port 3710. Route each through your reverse proxy. **Important:** the scrobble subdomain must *not* sit behind an SSO / forward-auth gateway — it authenticates itself via Navidrome (`ping.view`), and a gateway in front redirects the app's API calls to a login page.
### 3. First launch
1. Enter your Navidrome server URL, username, and password
2. Optionally add a Last.fm API key in Settings for bios and recommendations
3. Optionally connect Audiobookshelf in Settings
4. Optionally add the NovaSonic backend URL in Settings to enable downloads
5. Optionally add the NovaSonic scrobble URL in Settings to record listening stats
---
## Environment Variables
### Frontend container
| Variable | Description |
|---|---|
| `NAVIDROME_URL` | Pre-fills the Navidrome server URL on the login screen |
| `BACKEND_URL` | Pre-fills the download backend URL on the login screen |
| `SCROBBLE_URL` | Pre-fills the listening-stats (scrobble) service URL on the login screen |
### Backend container
| Variable | Default | Description |
|---|---|---|
| `PORT` | `3700` | Port the backend listens on |
| `MUSIC_PATH` | `/music` | Absolute path to your Navidrome music folder |
| `AUDIO_FMT` | `mp3` | Download format (`mp3`, `flac`, `opus`, `best`) |
| `AUDIO_QUALITY` | `0` | yt-dlp audio quality (0 = best) |
| `MAX_PARALLEL` | `2` | Max concurrent download jobs |
| `TICKETMASTER_API_KEY` | — | Free key from [developer.ticketmaster.com](https://developer.ticketmaster.com) |
### Scrobble container
| Variable | Default | Description |
|---|---|---|
| `PORT` | `3710` | Port the scrobble service listens on |
| `DATA_PATH` | `/data` | Where the play-log SQLite DB is stored (own volume) |
| `NAVIDROME_URL` | — | Used to verify user credentials via `ping.view` (multi-user auth) |
---
## Development
```bash
npm install
npm run dev # Vite dev server with HMR
npm run build # Production build → dist/
npm run preview # Preview the production build locally
```