@@ -2,6 +2,7 @@ package mpris
22
33import (
44 "context"
5+ "slices"
56 "strings"
67
78 "github.com/godbus/dbus/v5"
@@ -62,6 +63,11 @@ func (l *Listener) handleSignal(sig *dbus.Signal) {
6263 l .handlePropertiesChanged (sig )
6364 case DBUS_NAME_OWNER_CHANGED :
6465 l .handleNameOwnerChanged (sig )
66+ case MPRIS_SIGNAL_TRACKLIST_REPLACED , MPRIS_SIGNAL_TRACK_ADDED ,
67+ MPRIS_SIGNAL_TRACK_REMOVED , MPRIS_SIGNAL_TRACK_METADATA_CHANGED :
68+ if busName := l .resolveSender (sig ); busName != "" {
69+ l .handleTrackListSignal (busName , sig )
70+ }
6571 default :
6672 logger .Debug ("[mpris] unhandled signal: %s" , sig .Name )
6773 }
@@ -73,17 +79,12 @@ func (l *Listener) handlePropertiesChanged(sig *dbus.Signal) {
7379 // Body[1] = changed properties (map[string]Variant)
7480 // Body[2] = invalidated properties ([]string)
7581
76- if len (sig .Body ) < 2 {
77- return
78- }
79-
80- iface , ok := sig .Body [0 ].(string )
81- if ! ok || iface != MPRIS_PLAYER_IFACE {
82- // We only care about Player changes
82+ iface , ok := arg [string ](sig , 0 )
83+ if ! ok {
8384 return
8485 }
8586
86- changed , ok := sig . Body [ 1 ].( map [string ]dbus.Variant )
87+ changed , ok := arg [ map [string ]dbus.Variant ]( sig , 1 )
8788 if ! ok {
8889 return
8990 }
@@ -96,6 +97,31 @@ func (l *Listener) handlePropertiesChanged(sig *dbus.Signal) {
9697 return
9798 }
9899
100+ if iface == MPRIS_TRACKLIST_IFACE {
101+ if v , ok := changed ["CanEditTracks" ]; ok {
102+ if err := l .backend .UpdateCanEditTracks (busName , v ); err != nil {
103+ logger .Error ("[mpris] failed to update CanEditTracks for %s: %v" , busName , err )
104+ }
105+ }
106+ // Some players (VLC) never emit usable TrackList signals (VLC mangles
107+ // their interface name) and only report queue changes here — as a
108+ // changed value or a bare invalidation. Skipping when the IDs match
109+ // the cache keeps players that emit both from doubling up.
110+ if v , ok := changed ["Tracks" ]; ok {
111+ if ids , ok := v .Value ().([]dbus.ObjectPath ); ok && ! l .backend .tracklistMatches (busName , ids ) {
112+ l .refreshTracklist (busName , ids )
113+ }
114+ return
115+ }
116+ if invalidated , ok := arg [[]string ](sig , 2 ); ok && slices .Contains (invalidated , "Tracks" ) {
117+ l .refetchTracklist (busName )
118+ }
119+ return
120+ }
121+ if iface != MPRIS_PLAYER_IFACE {
122+ return
123+ }
124+
99125 // Check if PlaybackStatus changed for deduplication
100126 if statusVar , hasStatus := changed ["PlaybackStatus" ]; hasStatus {
101127 if status , ok := extractString (statusVar ); ok {
@@ -178,6 +204,124 @@ func (l *Listener) handleNameOwnerChanged(sig *dbus.Signal) {
178204 }
179205}
180206
207+ // resolveSender maps a signal's unique-name sender to a cached busName.
208+ // Dropping unknown senders is the safety net for the broad interface+path
209+ // match rule, which delivers TrackList signals from any sender.
210+ func (l * Listener ) resolveSender (sig * dbus.Signal ) string {
211+ busName := l .backend .findPlayerByUniqueName (sig .Sender )
212+ if busName == "" {
213+ logger .Debug ("[mpris] dropping %s from unknown sender %s" , sig .Name , sig .Sender )
214+ }
215+ return busName
216+ }
217+
218+ // refreshTracklist replaces a player's cached tracklist from a list of IDs,
219+ // fetching their metadata in one call (IDs only on failure).
220+ func (l * Listener ) refreshTracklist (busName string , ids []dbus.ObjectPath ) {
221+ tracks := tracksFromIDs (ids )
222+ if len (ids ) > 0 {
223+ if metas , err := newPlayer (l .backend , busName ).getTracksMetadata (ids ); err == nil {
224+ tracks = tracksFromMetadata (ids , metas )
225+ } else {
226+ logger .Debug ("[mpris] GetTracksMetadata failed for %s, keeping IDs only: %v" , busName , err )
227+ }
228+ }
229+
230+ if err := l .backend .ReplaceTracklist (busName , tracks ); err != nil {
231+ logger .Error ("[mpris] failed to replace tracklist for %s: %v" , busName , err )
232+ }
233+ }
234+
235+ // refetchTracklist re-reads the Tracks property after an invalidation
236+ // (no value in the signal) and refreshes the cache if it changed.
237+ func (l * Listener ) refetchTracklist (busName string ) {
238+ v , err := l .backend .getProperty (busName , MPRIS_TRACKLIST_IFACE , "Tracks" )
239+ if err != nil {
240+ logger .Debug ("[mpris] failed to fetch Tracks for %s: %v" , busName , err )
241+ return
242+ }
243+ ids , ok := v .Value ().([]dbus.ObjectPath )
244+ if ! ok {
245+ return
246+ }
247+ if ! l .backend .tracklistMatches (busName , ids ) {
248+ l .refreshTracklist (busName , ids )
249+ }
250+ }
251+
252+ // handleTrackListSignal dispatches TrackList signals once the sender is
253+ // resolved to a cached player.
254+ func (l * Listener ) handleTrackListSignal (busName string , sig * dbus.Signal ) {
255+ switch sig .Name {
256+ case MPRIS_SIGNAL_TRACKLIST_REPLACED :
257+ l .handleTrackListReplaced (busName , sig )
258+ case MPRIS_SIGNAL_TRACK_ADDED :
259+ l .handleTrackAdded (busName , sig )
260+ case MPRIS_SIGNAL_TRACK_REMOVED :
261+ l .handleTrackRemoved (busName , sig )
262+ case MPRIS_SIGNAL_TRACK_METADATA_CHANGED :
263+ l .handleTrackMetadataChanged (busName , sig )
264+ }
265+ }
266+
267+ // handleTrackListReplaced processes a wholesale tracklist replacement.
268+ // Body[0] = new track IDs; Body[1] = current track (unused)
269+ func (l * Listener ) handleTrackListReplaced (busName string , sig * dbus.Signal ) {
270+ ids , ok := arg [[]dbus.ObjectPath ](sig , 0 )
271+ if ! ok {
272+ return
273+ }
274+
275+ l .refreshTracklist (busName , ids )
276+ }
277+
278+ // handleTrackAdded processes a track insertion.
279+ // Body[0] = track metadata; Body[1] = the track it was inserted after
280+ func (l * Listener ) handleTrackAdded (busName string , sig * dbus.Signal ) {
281+ meta , ok := arg [map [string ]dbus.Variant ](sig , 0 )
282+ if ! ok {
283+ return
284+ }
285+ afterTrack , ok := arg [dbus.ObjectPath ](sig , 1 )
286+ if ! ok {
287+ return
288+ }
289+
290+ if err := l .backend .AddTrackToCache (busName , trackFromSignalMetadata (meta ), string (afterTrack )); err != nil {
291+ logger .Error ("[mpris] failed to add track for %s: %v" , busName , err )
292+ }
293+ }
294+
295+ // handleTrackRemoved processes a track removal.
296+ // Body[0] = removed track ID
297+ func (l * Listener ) handleTrackRemoved (busName string , sig * dbus.Signal ) {
298+ trackID , ok := arg [dbus.ObjectPath ](sig , 0 )
299+ if ! ok {
300+ return
301+ }
302+
303+ if err := l .backend .RemoveTrackFromCache (busName , string (trackID )); err != nil {
304+ logger .Error ("[mpris] failed to remove track for %s: %v" , busName , err )
305+ }
306+ }
307+
308+ // handleTrackMetadataChanged processes per-track metadata updates.
309+ // Body[0] = track ID; Body[1] = new metadata
310+ func (l * Listener ) handleTrackMetadataChanged (busName string , sig * dbus.Signal ) {
311+ trackID , ok := arg [dbus.ObjectPath ](sig , 0 )
312+ if ! ok {
313+ return
314+ }
315+ meta , ok := arg [map [string ]dbus.Variant ](sig , 1 )
316+ if ! ok {
317+ return
318+ }
319+
320+ if err := l .backend .UpdateTrackMetadataInCache (busName , string (trackID ), trackFromSignalMetadata (meta )); err != nil {
321+ logger .Error ("[mpris] failed to update track metadata for %s: %v" , busName , err )
322+ }
323+ }
324+
181325// Stop stops the listener
182326func (l * Listener ) Stop () {
183327 logger .Info ("[mpris] stopping listener" )
0 commit comments