42c12cbd3a30a1492fe360c3d229c88df0a28887
GenreViewModel: GenreUiState(genres = it) left isLoading=true (default), so genres loaded but spinner never cleared. Added isLoading=false explicitly. AppNavigation: replace BackHandler + navController.enableOnBackPressed with a DisposableEffect that re-adds an OnBackPressedCallback each time NowPlaying opens. Since callbacks are LIFO, a freshly-added callback always beats the BackHandler NavHost registers internally, regardless of composition order. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NovaSonic
A self-hosted music streaming PWA and Android app for Navidrome / Subsonic-compatible servers.
Built with Vite + vanilla ES modules, served via nginx in Docker.
Features
Music Library
- Stream music from your Navidrome / Subsonic library
- Browse artists, albums, playlists, starred tracks, and random picks
- Sort and filter views with chip filters
- Search / filter bar inside playlist detail and favorites (instant, client-side)
- Synced and plain lyrics via LRCLIB (auto-fetched per track)
- Play count display — optional toggle in Settings
- Star and context menu on every song row
Artist Pages
- Full-bleed hero card — artist photo as portrait + blurred background (desktop); full-bleed image with name overlay (mobile)
- Bio, similar artists, top songs pulled from Last.fm via Navidrome
- Concert events via Ticketmaster (upcoming shows with venue, date, ticket link)
- Albums cross-referenced against iTunes catalog — shows what's downloaded, partially downloaded, or missing
- Quick actions: play top songs, shuffle all, open concerts
Playback
- Queue management with shuffle, repeat (all / one), and radio mode (endless random)
- Spotify-style shuffle — pre-shuffles the full queue so every song plays exactly once
- Now Playing overlay with swipe-to-dismiss
- Gapless-style playback — next track pre-buffered 60 seconds before current track ends
- Lock screen / notification media controls (Android / iOS / desktop)
- Scrobbling — reported to Navidrome (configurable: 15 s / 30 s / ⅓ / ⅔ / end)
Discovery
- iTunes / Last.fm integration — charts, global search, 30-second previews, similar artists, recommendations
- Song Importer — paste a Spotify, YouTube, YouTube Music, or SoundCloud link to preview and download tracks
Downloads (yt-dlp backend)
- Download individual tracks, full albums, or entire artists
- Sources: YouTube Music (primary), YouTube, SoundCloud, Spotify (via importer)
- iTunes enrichment — album, year, track number, genre, 1000×1000 cover art
- Duplicate detection — skips tracks already present on the server
- Standard filename:
01 - Artist - Title.ext, organised intoAlbumArtist/Album/folders
Metadata Editor
- Edit title, artist, album artist, album, genre, year, track number, disc number
- Update embedded cover art via URL or file upload
- File renamed and moved automatically on save
- Navidrome rescan triggered after every save
Playlists
- Create, edit, and delete playlists
- Edit name, description, cover art, public/private toggle
- Add / remove tracks; reorder via drag and drop
Audiobookshelf Integration
- Browse and stream audiobooks from your Audiobookshelf server
- Chapters list, playback speed, sleep timer, progress sync
App
- PWA — installable on Android / iOS / desktop
- Android APK — native WebView wrapper with Media3 / ExoPlayer backend
- Native OS notification with skip buttons
- Pre-buffers last played track on cold start for instant resume
- Artwork pre-warming so notification never shows blank cover
- Multiple color themes, dark mode, mobile-first responsive layout
- German UI
Project Structure
NovaSonic/
├── src/
│ ├── player/
│ │ ├── player.js # Playback, queue, transport, media session, preload
│ │ └── nowplaying.js # Now Playing overlay
│ ├── views/
│ │ ├── detail.js # Artist / album detail pages
│ │ ├── views.js # Navigation, page loaders, search
│ │ ├── events.js # Ticketmaster concert events
│ │ ├── home.js # Home page, artist image cache
│ │ ├── 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, settings, 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
│ │ └── settings.js # Settings page
│ ├── state.js # Shared mutable state (S object)
│ ├── utils.js # md5, esc, formatDur, toast
│ ├── api.js # Subsonic API, iTunes API, cover art
│ └── main.js # App init, audio wiring, window globals
├── backend/
│ ├── novasonic-server.js # Express API: downloads, metadata, events proxy
│ └── lib/
│ ├── config.js # Env var → config object
│ ├── jobs.js # In-memory download job store
│ ├── metadata.js # iTunes lookup, artwork, lyrics, filename helpers
│ ├── ytdlp.js # yt-dlp download + ffmpeg metadata embedding
│ ├── squid.js # Qobuz (squid.wtf) download backend
│ ├── downloader.js # Unified downloadTrack / downloadAlbumTracks
│ └── romaji.js # Japanese → romaji search index
├── android/ # Android APK (WebView + Media3/ExoPlayer)
├── 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
├── nginx.conf
└── docker-entrypoint.sh # Injects NAVIDROME_URL / BACKEND_URL at runtime
Setup
1. Clone and configure
git clone <repo>
cd NovaSonic
cp .env.example .env
Edit .env:
NAVIDROME_URL=https://music.yourdomain.com
BACKEND_URL=https://novasonic-api.yourdomain.com
MUSIC_PATH=/srv/navidrome/music # absolute path to your music folder on the host
AUDIO_FMT=mp3 # mp3 | flac | opus | best
AUDIO_QUALITY=0 # 0 = best (yt-dlp)
MAX_PARALLEL=2
DOWNLOAD_BACKEND=auto # ytdlp | squid | auto
SQUID_QUALITY=mp3 # mp3 | flac | hi-res
TICKETMASTER_API_KEY= # optional — free key from developer.ticketmaster.com
2. Start
docker compose up -d
This starts both the frontend (nginx on port 8080) and the backend (Node.js on port 3700). Route both ports through your reverse proxy.
3. First launch
- Open the app and enter your Navidrome server URL, username and password
- Optionally add a Last.fm API key in Settings for recommendations and artist bios
- Optionally connect Audiobookshelf in Settings to stream audiobooks
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 |
Backend container
| Variable | Default | Description |
|---|---|---|
PORT |
3700 |
Port the backend listens on |
MUSIC_PATH |
/music |
Absolute path to your Navidrome music folder (mounted as volume) |
AUDIO_FMT |
mp3 |
Download output format (mp3, flac, opus, best) |
AUDIO_QUALITY |
0 |
yt-dlp audio quality (0 = best) |
MAX_PARALLEL |
2 |
Max concurrent download jobs |
DOWNLOAD_BACKEND |
auto |
ytdlp / squid / auto (tries Qobuz first, falls back to yt-dlp) |
SQUID_QUALITY |
mp3 |
Qobuz quality: mp3, flac, hi-res |
TICKETMASTER_API_KEY |
— | Free API key from developer.ticketmaster.com — enables concert events on artist pages |
Development
npm install
npm run dev # Vite dev server with HMR
npm run build # Production build → dist/
npm run preview # Preview the production build locally
Releases
1
NovaSonic 1.26
Latest
Languages
Kotlin
47.4%
JavaScript
40.1%
CSS
5.5%
HTML
5.4%
Python
0.8%
Other
0.8%