# 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, Last.fm bio / similar / top songs, Ticketmaster events, and iTunes cross-reference showing owned vs. missing tracks. - **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 iTunes 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 to private storage; an Offline tab to play and manage them; downloaded tracks play from disk on every screen. - **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 │ ├── 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 │ │ └── settings.js # Settings page │ ├── state.js # Shared mutable state (S object) │ ├── utils.js # Helpers (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, ABS proxy │ └── lib/ │ ├── config.js # Env var → config object │ ├── jobs.js # In-memory download job store │ ├── metadata.js # iTunes lookup, artwork, filename helpers │ ├── ytdlp.js # yt-dlp download + ffmpeg metadata embedding │ ├── downloader.js # Unified downloadTrack / downloadAlbumTracks │ └── romaji.js # Japanese → romaji search index ├── android/ # Native Android app (Jetpack Compose + Media3) │ └── app/src/main/kotlin/com/novasonic/app/ │ ├── api/ # SubsonicClient, AbsClient, LrcLibClient, ItunesClient │ ├── data/ # PrefsStore (DataStore), MusicRepository │ ├── 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 ├── nginx.conf └── docker-entrypoint.sh # Injects NAVIDROME_URL / BACKEND_URL at runtime ``` --- ## Setup ### 1. Clone and configure ```bash git clone cd NovaSonic cp .env.example .env ``` Edit `.env`: ```env NAVIDROME_URL=https://music.yourdomain.com BACKEND_URL=https://novasonic-api.yourdomain.com 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 ``` Frontend on port 8080, backend on port 3700. Route both through your reverse proxy. ### 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 --- ## 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 | | `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) | --- ## 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 ```