Skip to content

Conversation

@paulfariello
Copy link
Contributor

@paulfariello paulfariello commented Sep 23, 2025

Taking back on @wisp3rwind work for adding MPRIS support #1341.

  • Rebased on recent dev branch
  • ensure cargo fmt and cargo clippy passes on each commit
  • fix a few todos
  • send signal when position changed
  • return error when MPRIS command are done in wrong context or internal fails
  • handle position
  • handle identity
  • choose biggest art url
  • ensure metadata are always up to date or at least not invalid
  • notify on various property change

Not sure about:

  • Sending current state of player for all new listener (59767ce)

@paulfariello paulfariello force-pushed the mpris branch 7 times, most recently from f4a3b30 to 5e8fc7d Compare September 25, 2025 15:20
@paulfariello paulfariello force-pushed the mpris branch 2 times, most recently from 7fc4159 to f6481fd Compare September 30, 2025 15:13
@paulfariello paulfariello changed the title Draft: Add MPRIS support Add MPRIS support Sep 30, 2025
@paulfariello
Copy link
Contributor Author

@roderickvd this PR can probably be considered ready. The only missing feature is to handle the track list. It can probably be done in another PR.

Right now, CI is failing but doesn't seems to be related to this specific PR.

Do you have an opinion concerning 59767ce which is a broader modification than just MPRIS support?

Copy link
Contributor

@wisp3rwind wisp3rwind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for moving this forward!

I've looked through the PR superficially; here are a few comments. I didn't really check the actual implementation of MPRIS commands.

src/main.rs Outdated
.optopt(
POSITION_UPDATE_SHORT,
POSITION_UPDATE,
"Update position interval in ms",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe explain that this is about player events and thereby also MPRIS? I feel like without that context, it be quite unclear what the purpose of this option is?

Personally, I'd prefer the option to be --position-update-interval to make it really explicit, but it does become very long that way...

Copy link
Contributor Author

@paulfariello paulfariello Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 40ec0a3

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need it configurable? I'm not so sure if we want that...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either we want it configurable or we should choose a sensible default value other than 0 (which prevent position to be updated).

src/main.rs Outdated

let position_update_interval = opt_str(POSITION_UPDATE).as_deref().map(|position_update| {
match position_update.parse::<u64>() {
Ok(value) => Duration::from_millis(value),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also add a lower bound here? Either by returning an error if lower, or maybe better by simply taking the value.min(MIN_INTERVAL)? (Not sure what value should be, a few ms at least?) Or at least require it to be nonzero?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea, what sensible value should we use? 100ms seems quite sensible lower bound.

@roderickvd
Copy link
Member

@roderickvd this PR can probably be considered ready. The only missing feature is to handle the track list. It can probably be done in another PR.

Thanks! I saw you processed quite a bit of comments after, so ping me when you feel you're ready for another round of review.

Do you have an opinion concerning 59767ce which is a broader modification than just MPRIS support?

What'd be the negatives? 😄 No honestly. It seems like an improvement to me, but let me know if I should be aware of any downsides.

@roderickvd
Copy link
Member

Don't forget to add a changelog entry.

@paulfariello
Copy link
Contributor Author

Don't forget to add a changelog entry.

Done in 0a5c3aa

@paulfariello
Copy link
Contributor Author

@roderickvd this PR can probably be considered ready. The only missing feature is to handle the track list. It can probably be done in another PR.

Thanks! I saw you processed quite a bit of comments after, so ping me when you feel you're ready for another round of review.

AFAICT I've rework everything raised by comment. Should be ok for a last review.

Do you have an opinion concerning 59767ce which is a broader modification than just MPRIS support?

What'd be the negatives? 😄 No honestly. It seems like an improvement to me, but let me know if I should be aware of any downsides.

If you don't see any negatives then let's go with that :)

@paulfariello
Copy link
Contributor Author

Small reminder that everything seems ready for last review here

@roderickvd
Copy link
Member

👍 I’m away for a few days before I can review, but if anyone wants to beat me to it…

Copy link
Member

@roderickvd roderickvd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost there I think!
Sorry for the late response.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's getting pretty large. I know it's tedious but if you'd have time to split it up into sub-modules then that'd be great.

Stopped {
play_request_id: u64,
track_id: SpotifyUri,
play_request_id: Option<u64>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please tag in the changelog that this is a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Cargo.toml Outdated
"sync",
"process",
] }
time = { version = "0.3", features = ["formatting"] }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only used for MPRIS, so to be made optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/main.rs Outdated
"Knee width (dB) of the dynamic limiter from 0.0 to 10.0. Defaults to 5.0.",
"KNEE",
)
.optopt(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do without this feature flag? Because it seems pretty advanced. What would it matter to users of we did or did not drop it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed option in favor of a 400ms default

wisp3rwind and others added 7 commits November 13, 2025 16:25
- which is just a tokio::sync::mpsc sender, so this should be safe
- prep for MPRIS support, which will use this to control playback
- preparation for MPRIS support
- now that the data is there, also yield from player_event_handler
- preparation for MPRIS support
- following the spec at https://specifications.freedesktop.org/mpris-spec/latest/

- some properties/commands are not fully supported, yet
@andriyor
Copy link

Just tested it on KDE
Medial player doesn't show title and control's

image

Official spotify client:

image

If it’s possible, I’d love to sponsor this work a bit

@paulfariello
Copy link
Contributor Author

@andriyor could you check the following command on both dbus services (org.mpris.MediaPlayer2.librespot and the one used by official client).

dbus-send --print-reply --dest=REPLACE_WITH_DBUS_SERVICE --type=method_call --print-reply /org/mpris/MediaPlayer2 org.freedesktop.DBus.Properties.GetAll "string:REPLACE_WITH_DBUS_SERVICE"

If you don't know the official spotify client service name use the following command:

dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames

@andriyor
Copy link

andriyor commented Nov 18, 2025

spotify:

❯ dbus-send --print-reply \
  --dest=org.mpris.MediaPlayer2.spotify \  
  --type=method_call \
  /org/mpris/MediaPlayer2 \
  org.freedesktop.DBus.Properties.GetAll \
  string:"org.mpris.MediaPlayer2.Player"

method return time=1763494565.329229 sender=:1.305 -> destination=:1.308 serial=213 reply_serial=2
   array [
      dict entry(
         string "PlaybackStatus"
         variant             string "Playing"
      )
      dict entry(
         string "LoopStatus"
         variant             string "None"
      )
      dict entry(
         string "Rate"
         variant             double 1
      )
      dict entry(
         string "Shuffle"
         variant             boolean false
      )
      dict entry(
         string "Metadata"
         variant             array [
               dict entry(
                  string "mpris:trackid"
                  variant                      string "/com/spotify/track/4GKJALTXTwhZgLPp0usd5C"
               )
               dict entry(
                  string "mpris:length"
                  variant                      uint64 268906000
               )
               dict entry(
                  string "mpris:artUrl"
                  variant                      string "https://i.scdn.co/image/ab67616d0000b273b23737878e75582c2e255029"
               )
               dict entry(
                  string "xesam:album"
                  variant                      string "Skinty Fia"
               )
               dict entry(
                  string "xesam:albumArtist"
                  variant                      array [
                        string "Fontaines D.C."
                     ]
               )
               dict entry(
                  string "xesam:artist"
                  variant                      array [
                        string "Fontaines D.C."
                     ]
               )
               dict entry(
                  string "xesam:autoRating"
                  variant                      double 0.7
               )
               dict entry(
                  string "xesam:discNumber"
                  variant                      int32 1
               )
               dict entry(
                  string "xesam:title"
                  variant                      string "Roman Holiday"
               )
               dict entry(
                  string "xesam:trackNumber"
                  variant                      int32 6
               )
               dict entry(
                  string "xesam:url"
                  variant                      string "https://open.spotify.com/track/4GKJALTXTwhZgLPp0usd5C"
               )
            ]
      )
      dict entry(
         string "Volume"
         variant             double 0.53109
      )
      dict entry(
         string "Position"
         variant             int64 16842000
      )
      dict entry(
         string "MinimumRate"
         variant             double 1
      )
      dict entry(
         string "MaximumRate"
         variant             double 1
      )
      dict entry(
         string "CanGoNext"
         variant             boolean true
      )
      dict entry(
         string "CanGoPrevious"
         variant             boolean true
      )
      dict entry(
         string "CanPlay"
         variant             boolean true
      )
      dict entry(
         string "CanPause"
         variant             boolean true
      )
      dict entry(
         string "CanSeek"
         variant             boolean true
      )
      dict entry(
         string "CanControl"
         variant             boolean true
      )
   ]

~ 

librespot:

❯ dbus-send --print-reply \
  --dest=org.mpris.MediaPlayer2.librespot \
  --type=method_call \
  /org/mpris/MediaPlayer2 \
  org.freedesktop.DBus.Properties.GetAll \
  string:"org.mpris.MediaPlayer2.Player"

method return time=1763494469.425111 sender=:1.288 -> destination=:1.291 serial=58 reply_serial=2
   array [
      dict entry(
         string "MinimumRate"
         variant             double 1
      )
      dict entry(
         string "PlaybackStatus"
         variant             string "Playing"
      )
      dict entry(
         string "Position"
         variant             int64 76107000
      )
      dict entry(
         string "CanControl"
         variant             boolean true
      )
      dict entry(
         string "MaximumRate"
         variant             double 1
      )
      dict entry(
         string "Shuffle"
         variant             boolean false
      )
      dict entry(
         string "CanGoPrevious"
         variant             boolean true
      )
      dict entry(
         string "CanPause"
         variant             boolean true
      )
      dict entry(
         string "CanPlay"
         variant             boolean true
      )
      dict entry(
         string "Rate"
         variant             double 1
      )
      dict entry(
         string "LoopStatus"
         variant             string "None"
      )
      dict entry(
         string "CanGoNext"
         variant             boolean true
      )
      dict entry(
         string "CanSeek"
         variant             boolean true
      )
      dict entry(
         string "Metadata"
         variant             array [
               dict entry(
                  string "mpris:artUrl"
                  variant                      string "https://i.scdn.co/image/ab67616d0000b273b23737878e75582c2e255029"
               )
               dict entry(
                  string "mpris:length"
                  variant                      int64 268906000
               )
               dict entry(
                  string "mpris:trackId"
                  variant                      object path "/org/librespot/track/4GKJALTXTwhZgLPp0usd5C"
               )
               dict entry(
                  string "xesam:album"
                  variant                      string "Skinty Fia"
               )
               dict entry(
                  string "xesam:albumArtist"
                  variant                      array [
                        string "Fontaines D.C."
                     ]
               )
               dict entry(
                  string "xesam:artist"
                  variant                      array [
                        string "Fontaines D.C."
                     ]
               )
               dict entry(
                  string "xesam:contentCreated"
                  variant                      string "2022-04-22T00:00:00.000000000Z"
               )
               dict entry(
                  string "xesam:discNumber"
                  variant                      int32 1
               )
               dict entry(
                  string "xesam:trackNumber"
                  variant                      int32 6
               )
            ]
      )
      dict entry(
         string "Volume"
         variant             double 0.499992
      )
   ]

~ 
❯ 

I found that unofficial widget supports it:
https://github.com/ccatterina/plasmusic-toolbar

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants