docs: v2.0 — document the scrobble / listening-stats system
- CHANGELOG: new [2.0] entry (own timestamped play-log service, stats view on web + android with cover art, remix-dedup fix, user-select UI fix, node 20->22); Platform Parity Matrix gains play-log / stats / stats-cover rows (+ Wrapped 🔜). - README: Listening stats feature, scrobble/ in the project tree, SCROBBLE_URL env + scrobble container table, port 3710 + SSO-exclusion note in setup. - CLAUDE.md: new 'Listening stats / scrobble system' section (isolation rationale, node:sqlite gotchas, ping.view multi-user auth, capture point, cover resolution, SSO deploy gotcha). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
906b3b48cb
commit
561014c83f
3 files changed
+93
-3
No files matched your search
@@ -54,11 +54,46 @@ Versioning: two-part `MAJOR.MINOR` — each release bumps the minor (features an
|
||||
| Offline playback | ❌ | ✅ |
|
||||
| Offline detail / manage downloads | ❌ | ✅ |
|
||||
| Player bar on all screens | ✅ | ✅ |
|
||||
| Timestamped play-log (own scrobble) | ✅ | ✅ |
|
||||
| Listening statistics view | ✅ | ✅ |
|
||||
| Stats cover art (artist/album) | ✅ | ✅ |
|
||||
| Yearly Wrapped | 🔜 | 🔜 |
|
||||
| Romaji / Japanese sort | ✅ | ❌ |
|
||||
| Settings (Navidrome, ABS, etc.) | ✅ | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## [2.0] — 2026-08-07
|
||||
|
||||
Big change: **NovaSonic gets its own self-hosted, multi-user listening archive.** Navidrome only stores a play *count* and the *last* play date — no timestamped history — so per-day/month/year stats and a yearly "Wrapped" were impossible. Koito and Spindle (the off-the-shelf options) are effectively single-user. Since NovaSonic *is* the player, it now records every counted play into an **isolated scrobble service** with its own database, and shows the results in a **statistics view on web and Android**. This runs **in addition to** the existing Navidrome/Last.fm scrobble — it never replaces it.
|
||||
|
||||
### Added
|
||||
- **New `novasonic-scrobble` service** — a small, isolated container (`node:22-slim` + built-in `node:sqlite`, own volume) that owns the play-log. Kept separate from the download backend on purpose: the backend is heavy (yt-dlp/ffmpeg) and restarts regularly, while an archive needs uptime + data integrity. Routes:
|
||||
- `POST /scrobble/plays` — batch ingest, **idempotent by uuid** (safe offline-retry)
|
||||
- `GET /scrobble/history` — raw chronological log (the archive)
|
||||
- `GET /scrobble/stats` — totals, top artists/albums/tracks, day/month/year series, 24h listening clock (on-the-fly `GROUP BY strftime`, local-tz bucketing)
|
||||
- `DELETE /scrobble/plays?user=` — purge one user's rows (test cleanup)
|
||||
- `GET /scrobble/health` — liveness
|
||||
- **Multi-user**: every request carries the same Subsonic token creds the app already uses; the service verifies them via Navidrome `ping.view` (cached), so the stored `user` is real, not spoofable. No new login.
|
||||
- **Play capture (Web + Android)** — fires at the *existing* counted-play moment (respects the user's `scrobbleMode`, consistent with Last.fm), with an **offline-safe queue** (localStorage on web, a JSON file on Android) flushed as an idempotent batch on start and when connectivity returns.
|
||||
- **Statistics view (Web + Android)** — opened from Settings ("📊 Meine Statistiken"): totals tiles (**Plays** + listening time), a day/month/year play series, top artists/tracks/albums, and a 24h listening clock, with period tabs (Alles / Dieses Jahr / Letzte 30 Tage).
|
||||
- **Cover art in stats (Web + Android)** — top artists show a round artist image, tracks/albums a square cover, resolved client-side from Navidrome (the scrobble service stays cover-agnostic).
|
||||
- **`SCROBBLE_URL`** environment variable (frontend) + a **Scrobble URL** field with a test button in Settings on both platforms.
|
||||
|
||||
### Changed
|
||||
- **Backend** — base image bumped `node:20-slim` → **`node:22-slim`** (Node 20 reaches end-of-life April 2026; enables the built-in `node:sqlite`).
|
||||
|
||||
### Fixed
|
||||
- **Downloads** — remix / live / acoustic / instrumental tracks were skipped as duplicates of the base track: duplicate detection stripped *all* bracketed content, collapsing "Song (Remix)" → "Song". Version-marker brackets are now preserved, so a distinct recording downloads (noise brackets like `(feat. X)` / `(Official Video)` are still stripped). Verified against Deezer + unit-tested both directions.
|
||||
- **UI** — dragging the mouse near a field edge (Global search, importer) started selecting the surrounding titles/labels instead of feeling like an app. `user-select` is now disabled on the `#app` shell; real text fields and lyrics stay selectable.
|
||||
|
||||
### Notes
|
||||
- The scrobble subdomain **must be excluded from any SSO / forward-auth gateway** (Authelia/Authentik/etc.), exactly like the download backend — it self-authenticates via `ping.view`, and a gateway in front 302-redirects the browser's API calls to the login portal (breaks `fetch`).
|
||||
- Deploy: add `SCROBBLE_URL` to `.env`, `docker compose up -d novasonic-scrobble`, and route port **3710** through your reverse proxy.
|
||||
- Planned next: a yearly **Wrapped** (`/scrobble/wrapped` + story page) and an optional **ListenBrainz relay** — not in this release.
|
||||
|
||||
---
|
||||
|
||||
## [1.3] — 2026-07-31
|
||||
|
||||
Big change: **the entire metadata/search stack moved from Apple's iTunes to Deezer** — on web, backend downloads *and* Android. Apple's iTunes `/search` returns **403 from server/datacenter IPs**, which had broken global search, charts, artist/album/single downloads and the metadata-editor autofill in production. Deezer is free, needs no key, allows more requests, and returns explicit versions + real artist images + track durations. Web and Android now share the **same** source, so an artist resolves to one consistent name everywhere.
|
||||
|
||||
Reference in new issue
Block a user