502427d02c6852025ad92659db17361174f2fd5b
When the current song ends, Android's battery optimization briefly kills background network access. By starting the next song's stream request while the current track is still playing (and Chrome holds an active audio session), the buffer is ready before the gap hits. The same URL is reused by the main element so Chrome can serve from its in-memory buffer. Also reset _trackChanging on play() failure to avoid permanently suppressing pause events. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NovaSonic
A self-hosted music streaming PWA for Navidrome / Subsonic-compatible servers.
Built with Vite + vanilla ES modules, served via nginx in Docker.
Features
- Stream music from your Navidrome / Subsonic library
- Browse artists, albums, playlists, starred tracks and random picks
- Queue management with shuffle, repeat (all / once) and radio mode (endless random)
- Now Playing overlay with swipe-to-dismiss and lock screen media controls (Android/iOS)
- iTunes / Last.fm integration — charts, global search, 30s previews, recommendations
- Download tracks from YouTube/SoundCloud via yt-dlp directly into your music library
- Duplicate detection — skips tracks that already exist on the server
- Edit playlists (name, description, cover art, public toggle)
- PWA — installable, offline-capable via service worker
- Dark theme, mobile-first responsive layout
Project Structure
Music_App/
├── src/ # ES modules (Vite entry)
│ ├── state.js # Shared mutable state (S object)
│ ├── utils.js # md5, esc, formatDur, toast
│ ├── api.js # Subsonic API, Navidrome REST, iTunes API
│ ├── player.js # Playback, transport, queue, media session, preview
│ ├── views.js # Navigation, page loaders, card makers, search
│ ├── ui.js # Modals, overlays, sidebar, settings, context menu
│ ├── pwa.js # Service worker registration + update banner
│ └── main.js # App init, audio wiring, window globals
├── public/ # Static assets (copied as-is to dist/)
│ ├── sw.js # Service worker
│ ├── manifest.json # PWA manifest
│ ├── icon.svg # App icon
│ └── config.js # Runtime config placeholder (generated by Docker)
├── backend/
│ └── novasonic-server.js # Node.js download backend (yt-dlp + ffmpeg)
├── index.html # App shell + Vite entry point
├── styles.css # All styles
├── vite.config.js
├── Dockerfile # Multi-stage: Node build → nginx serve
├── docker-compose.yml
├── nginx.conf
└── docker-entrypoint.sh # Injects NS_SERVER_URL / NS_BACKEND_URL at startup
Docker Setup
Frontend only
docker compose up -d
Environment variables (set in docker-compose.yml or via -e):
| Variable | Description |
|---|---|
NS_SERVER_URL |
Pre-fills the Navidrome server URL on the login screen |
NS_BACKEND_URL |
Pre-fills the download backend URL |
With download backend
The backend requires yt-dlp and ffmpeg. A separate docker-compose.yml lives in backend/.
cd backend
docker compose up -d
Environment variables for the backend:
| Variable | Default | Description |
|---|---|---|
PORT |
3700 |
Port the backend listens on |
MUSIC_PATH |
/music |
Path to your Navidrome music folder (mount as volume) |
AUDIO_FMT |
mp3 |
Output format (mp3, flac, opus, …) |
AUDIO_QUALITY |
0 |
yt-dlp audio quality (0 = best) |
MAX_PARALLEL |
2 |
Maximum concurrent downloads |
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
First Launch
- Open the app in a browser
- Enter your Navidrome server URL, username and password
- Optionally add a Last.fm API key (Settings) for recommendations and similar artists
- Optionally add the backend URL (Settings) to enable yt-dlp downloads
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%