feat(api): deliver loudness gains to every client (M464 #4997)
release / web (push) Successful in 1m44s
release / go (push) Successful in 2m12s
release / govulncheck (push) Successful in 40s
release / android (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 4m42s
release / integration (push) Successful in 15m33s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 1m21s
release / Verify release artifacts (tag releases only) (push) Skipped

ReplayGain 2.0 values (gain to -18 LUFS, linear peak) derived from the
stored track and album loudness:

- Web: GET /api/tracks/replay-gain?ids=... (up to 200), a lookup the
  player calls for its queue, rather than a field on every TrackRef
  surface.
- Android: track_gain/track_peak and album_gain/album_peak on the sync
  views, so cached tracks level offline. Storing a measurement logs a
  track change, and an album's values moving logs an album change, both
  before the write (#2704), so caches pick the gains up.
- OpenSubsonic: replayGain on every song (album, getSong, search3,
  starred), as a JSON object and an XML element.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 13:54:05 -04:00
co-authored by Claude Opus 5.5
parent c2f81bf8df
commit e3aa8629d3
19 changed files with 797 additions and 38 deletions
+11 -2
View File
@@ -10,6 +10,7 @@ import (
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
"git.fabledsword.com/bvandeusen/minstrel/internal/library"
syncpkg "git.fabledsword.com/bvandeusen/minstrel/internal/sync"
)
@@ -151,8 +152,12 @@ func (h *handlers) hydrateUpserts(
if err != nil {
return nil, err
}
gains, err := library.ReplayGainForAlbums(ctx, q, uuids)
if err != nil {
return nil, err
}
for _, a := range albums {
b, _ := json.Marshal(toAlbumSyncView(a))
b, _ := json.Marshal(toAlbumSyncView(a, gains[a.ID]))
out["album"] = append(out["album"], b)
}
}
@@ -162,8 +167,12 @@ func (h *handlers) hydrateUpserts(
if err != nil {
return nil, err
}
gains, err := library.ReplayGainForTracks(ctx, q, uuids)
if err != nil {
return nil, err
}
for _, t := range tracks {
b, _ := json.Marshal(toTrackSyncView(t))
b, _ := json.Marshal(toTrackSyncView(t, gains[t.ID]))
out["track"] = append(out["track"], b)
}
}