From bc107d1ceed30f78abc1c5fb3d1a601a4f099e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Wed, 3 Jun 2026 20:03:08 -0400 Subject: [PATCH] fix(scrobbler): proxy NowPlaying even when ignoreScrobble is set (#5559) * fix(scrobbler): proxy NowPlaying even when ignoreScrobble is set When a client reports playback with ignoreScrobble=true, the reportPlayback handler suppressed both the scrobble submission and the NowPlaying update sent to external agents (Last.fm, ListenBrainz, plugins). These are independent concerns: ignoring the scrobble submission should not stop Navidrome from telling external services what is currently playing. The !params.IgnoreScrobble guard now applies only to the scrobble submission and play-count path; the NowPlaying dispatch is gated solely by the player's ScrobbleEnabled flag. This mirrors the legacy scrobble endpoint, where submission=false has always still set NowPlaying. * test(scrobbler): assert no scrobble dispatch when ignoreScrobble=true Address PR review feedback: explicitly verify that ignoreScrobble=true suppresses the scrobble submission (not just the play count) while NowPlaying is still dispatched, so the flag cannot regress into ignoring nothing. Also expand the NowPlaying gating comment to spell out the IgnoreScrobble vs ScrobbleEnabled rules and identify the external agents involved. --- core/scrobbler/play_tracker.go | 9 ++++++++- core/scrobbler/play_tracker_test.go | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/core/scrobbler/play_tracker.go b/core/scrobbler/play_tracker.go index bdb261ef2..860a80bce 100644 --- a/core/scrobbler/play_tracker.go +++ b/core/scrobbler/play_tracker.go @@ -371,7 +371,14 @@ func (p *playTracker) ReportPlayback(ctx context.Context, params ReportPlaybackP p.broker.SendBroadcastMessage(ctx, &events.NowPlayingCount{Count: p.playMap.Len()}) } - if !params.IgnoreScrobble && player.ScrobbleEnabled && + // NowPlaying gating, by design distinct from scrobble submission: + // - IgnoreScrobble=true -> still send NowPlaying (suppresses only the + // scrobble submission/play-count above), mirroring the legacy scrobble + // endpoint's submission=false behavior. + // - player.ScrobbleEnabled=false -> never send NowPlaying. + // External agents here are the active scrobblers (Last.fm, ListenBrainz, and + // scrobbler plugins) returned by getActiveScrobblers; see dispatchNowPlaying. + if player.ScrobbleEnabled && (params.State == StateStarting || params.State == StatePlaying) { if info, err := p.playMap.Get(clientId); err == nil { p.enqueueNowPlaying(ctx, clientId, user.ID, &info.MediaFile, int(params.PositionMs/1000)) diff --git a/core/scrobbler/play_tracker_test.go b/core/scrobbler/play_tracker_test.go index 5383244cd..b5a478c2a 100644 --- a/core/scrobbler/play_tracker_test.go +++ b/core/scrobbler/play_tracker_test.go @@ -521,6 +521,7 @@ var _ = Describe("PlayTracker", func() { }) It("does NOT scrobble when ignoreScrobble=true even if threshold met", func() { + fake.ScrobbleCalled.Store(false) err := tracker.ReportPlayback(ctx, ReportPlaybackParams{ MediaId: "123", PositionMs: 0, State: "starting", PlaybackRate: 1.0, ClientId: defaultClientId, }) @@ -531,6 +532,7 @@ var _ = Describe("PlayTracker", func() { }) Expect(err).ToNot(HaveOccurred()) Expect(track.PlayCount).To(Equal(int64(0))) + Consistently(func() bool { return fake.ScrobbleCalled.Load() }).Should(BeFalse()) }) It("does NOT scrobble when player ScrobbleEnabled=false even if threshold met", func() { @@ -715,14 +717,14 @@ var _ = Describe("PlayTracker", func() { Consistently(func() bool { return fake.GetNowPlayingCalled() }).Should(BeFalse()) }) - It("does NOT dispatch when ignoreScrobble=true", func() { + It("still dispatches NowPlaying when ignoreScrobble=true", func() { fake.nowPlayingCalled.Store(false) err := tracker.ReportPlayback(ctx, ReportPlaybackParams{ MediaId: "123", PositionMs: 0, State: "starting", PlaybackRate: 1.0, ClientId: defaultClientId, IgnoreScrobble: true, }) Expect(err).ToNot(HaveOccurred()) - Consistently(func() bool { return fake.GetNowPlayingCalled() }).Should(BeFalse()) + Eventually(func() bool { return fake.GetNowPlayingCalled() }).Should(BeTrue()) }) It("does NOT dispatch when ScrobbleEnabled=false", func() {