HoneyandClaude Opus 4.8 065d2f7319 feat: disambiguate same-name artists by Deezer fan count + show fans
Deezer's artist search doesn't rank by popularity, so a name collision (5 artists named
"Joji") returned a niche one first (62 fans) instead of the real Joji (559k) — the artist
page then loaded the wrong discography and owned songs didn't show as downloaded.

- api.js: _dzArtist carries fanCount (nb_fan); artist search sorts by fanCount desc.
- search.js: artist cards show the fan count as subtitle; fan count passed to the artist page.
- detail.js: pick the most-popular name match (not the first); for local Navidrome artists
  with a real collision (>=2 same-name candidates), tie-break by SONG OVERLAP — prefer the
  Deezer candidate whose top tracks actually overlap the library (each artist has different
  songs, so this nails the right person). Costs a few calls only on a collision.
- Artist hero shows "<n> Fans" (immediately for global-search artists, after match for local).
- Android: DzArtist.nb_fan + /search/artist sorted by nb_fan so the right artist is first.
- Also: web global-search "unavailable" message now says Deezer, not Apple. SW ns-v52.

Verified live against Deezer: both fan-sort and overlap select the real Joji (id 5078097).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 16:11:45 +02:00

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 a Deezer 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 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.
  • 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 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, 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
├── 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
├── 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

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

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

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
S
Description
No description provided
Readme
5 MiB
0 Stars 0 Watchers 0 Forks
2026-09-02 09:26:49 +02:00
Languages
Kotlin 47.4%
JavaScript 40.1%
CSS 5.5%
HTML 5.4%
Python 0.8%
Other 0.8%