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
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:
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
syncpkg "git.fabledsword.com/bvandeusen/minstrel/internal/sync"
|
||||
)
|
||||
|
||||
// histAt builds a histogram of n blocks all at one loudness.
|
||||
@@ -162,6 +163,17 @@ func TestAlbumLoudness_Integration(t *testing.T) {
|
||||
return res
|
||||
}
|
||||
|
||||
albumChanges := func() int {
|
||||
t.Helper()
|
||||
var n int
|
||||
if err := pool.QueryRow(ctx, `SELECT count(*) FROM library_changes
|
||||
WHERE entity_type = 'album' AND entity_id = $1`,
|
||||
syncpkg.FormatUUID(album.ID)).Scan(&n); err != nil {
|
||||
t.Fatalf("count album changes: %v", err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
second := addTrack("b", album)
|
||||
measure(first, -10, -1)
|
||||
|
||||
@@ -173,6 +185,11 @@ func TestAlbumLoudness_Integration(t *testing.T) {
|
||||
t.Fatalf("waiting album = lufs %v, %d/%d settled; want nil, 1/2", lufs, settled, total)
|
||||
}
|
||||
|
||||
// Still no album value: clients have nothing new to read (#4997).
|
||||
if n := albumChanges(); n != 0 {
|
||||
t.Fatalf("a waiting album logged %d sync changes, want 0", n)
|
||||
}
|
||||
|
||||
// 2. Measuring the second track changes the digest; the album is leveled.
|
||||
measure(second, -10, -0.5)
|
||||
if res := pass(); res.Recomputed != 1 || res.Leveled != 1 {
|
||||
@@ -183,10 +200,18 @@ func TestAlbumLoudness_Integration(t *testing.T) {
|
||||
t.Fatalf("leveled album = lufs %v peak %v, want -10 and -0.5", lufs, peak)
|
||||
}
|
||||
|
||||
// 3. Nothing changed: nothing recomputed.
|
||||
// Leveled: clients are told, once.
|
||||
if n := albumChanges(); n != 1 {
|
||||
t.Fatalf("leveling the album logged %d sync changes, want 1", n)
|
||||
}
|
||||
|
||||
// 3. Nothing changed: nothing recomputed, nothing logged.
|
||||
if res := pass(); res.Recomputed != 0 {
|
||||
t.Fatalf("idle pass = %+v, want nothing recomputed", res)
|
||||
}
|
||||
if n := albumChanges(); n != 1 {
|
||||
t.Fatalf("an idle pass logged album changes (now %d), want still 1", n)
|
||||
}
|
||||
|
||||
// 4. A track joins (here, retagged onto this album): recomputed.
|
||||
other, err := q.UpsertAlbum(ctx, dbq.UpsertAlbumParams{Title: "Other", SortTitle: "Other", ArtistID: artist.ID})
|
||||
@@ -232,9 +257,14 @@ func TestAlbumLoudness_Integration(t *testing.T) {
|
||||
if _, err := pool.Exec(ctx, "UPDATE tracks SET missing_since = now() WHERE id = $1", first.ID); err != nil {
|
||||
t.Fatalf("mark missing: %v", err)
|
||||
}
|
||||
before := albumChanges()
|
||||
if res := pass(); res.Orphans != 1 {
|
||||
t.Fatalf("pass with every track missing = %+v, want 1 orphan dropped", res)
|
||||
}
|
||||
// Dropping the row takes the album gain away, so clients are told.
|
||||
if n := albumChanges(); n != before+1 {
|
||||
t.Fatalf("dropping the orphan logged %d album changes, want 1", n-before)
|
||||
}
|
||||
var n int
|
||||
if err := pool.QueryRow(ctx, "SELECT count(*) FROM album_loudness WHERE album_id = $1", album.ID).Scan(&n); err != nil {
|
||||
t.Fatalf("count: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user