[GH-ISSUE #1266] Support additionnal SpotifyUri types #582

Open
opened 2026-02-27 19:31:22 +03:00 by kerem · 6 comments
Owner

Originally created by @acolombier on GitHub (Mar 19, 2024).
Original GitHub issue: https://github.com/librespot-org/librespot/issues/1266

Is your feature request related to a problem? Please describe.

Currently, only a subset of URI are supported, while some other like genre or medialistconfig aren't, which preventing deserializing data coming from certain endpoint like rootlist

Describe the solution you'd like

SpotifyId could be refactored to aligned better with the underlying core concept; this way, SpotifyId and SpotifyItemType would become SpotifyUri, and the enum would define sub part, such as SpotifyID (Spotify ID in the above doc), but not exclusively, like it is the case with the current structure.

Describe alternatives you've considered

Alternatively, I have currently extended the existing Struct/Enum relation, which allows supporting these additional URI type in a non-breaking way, but this leads to inconsistency, which is likely to grow as more URI type are added

Additional context

N/A

Originally created by @acolombier on GitHub (Mar 19, 2024). Original GitHub issue: https://github.com/librespot-org/librespot/issues/1266 **Is your feature request related to a problem? Please describe.** Currently, only a [subset of URI are supported](https://github.com/librespot-org/librespot/blob/ef1f35ba9dda5f63eb6654387994f4abb3459100/core/src/spotify_id.rs#L18), while some other like `genre` or `medialistconfig` aren't, which preventing deserializing data coming from certain endpoint like `rootlist` **Describe the solution you'd like** `SpotifyId` could be refactored to aligned better with [the underlying core concept](https://developer.spotify.com/documentation/web-api/concepts/spotify-uris-ids); this way, `SpotifyId` and `SpotifyItemType` would become `SpotifyUri`, and the enum would define sub part, such as `SpotifyID` (`Spotify ID` in the above doc), but not exclusively, like it is the case with the current structure. **Describe alternatives you've considered** Alternatively, I have currently extended the existing Struct/Enum relation, which allows supporting these additional URI type in a non-breaking way, but this leads to inconsistency, which is likely to grow as more URI type are added **Additional context** N/A
Author
Owner

@acolombier commented on GitHub (Mar 19, 2024):

Happy to help implementing the later if that is of interest

<!-- gh-comment-id:2008130377 --> @acolombier commented on GitHub (Mar 19, 2024): Happy to help implementing the later if that is of interest
Author
Owner

@roderickvd commented on GitHub (Mar 31, 2024):

Sounds like a good idea. Feel free to open a PR!

<!-- gh-comment-id:2028619058 --> @roderickvd commented on GitHub (Mar 31, 2024): Sounds like a good idea. Feel free to open a PR!
Author
Owner

@SapiensAnatis commented on GitHub (Aug 11, 2025):

Had a bit of a look at this since SpotifyId is currently also unable to represent a local file URI. Local file URIs are arbitrary-length strings of the form spotify:local:{artist}:{album_title}:{track_title}:{duration_in_seconds}. @photovoltex mentioned tackling this as a potential first step to supporting local files but noted it may not be the best solution if the breaking changes are too large.

I implemented a struct like this on top of SpotifyId:

#[derive(Clone, PartialEq, Eq, Hash)]
pub enum SpotifyResourceName {
    Id(SpotifyId),
    String(String),
}

#[derive(Clone, PartialEq, Eq, Hash)]
pub struct SpotifyUri {
    pub item_type: SpotifyItemType,
    pub name: SpotifyResourceName,
}

and then changed the player to use SpotifyUri instead of SpotifyId internally. We can probably manage the player's method interface reasonably easily by just adding new methods instead of changing existing ones (perhaps with #[deprecated] depending on what overall direction we want to take), for example for player.load():

  enum PlayerCommand {
       Load {
-          track_id: SpotifyId,
+          track_id: SpotifyUri,
           play: bool,
           position_ms: u32,
       },
  }

  pub fn load(&self, track_id: SpotifyId, start_playing: bool, position_ms: u32) {
       self.command(PlayerCommand::Load {
-          track_id,
+          track_id: track_id.into(),
           play: start_playing,
           position_ms,
       });
   }

+  pub fn load_uri(&self, track_id: SpotifyUri, start_playing: bool, position_ms: u32) {
+      self.command(PlayerCommand::Load {
+          track_id,
+          play: start_playing,
+      });
+  }

However we will have breaking changes due to changing the player structs and enums. In particular PlayerEvent is often used to manage user interfaces and the like -- the same way the librespot binary sets up a player. For example spotifyd uses it here: https://github.com/Spotifyd/spotifyd/blob/master/src/process.rs

That is unless there is some further mitigation we can do like keeping track of both a SpotifyId and a SpotifyUri in the internal player state?

My attempt also involved breaking changes to some other function signatures which I assume are only called internally by spirc, but that I could be wrong about.


I got it to build and you can see the changes here. The bulk of the changed lines of code are because SpotifyUri cannot implement Copy like SpotifyId can, so I have abused .clone() all over the place -- this is a first draft though and there may be a more intelligent way to make the borrow checker happy.

I suppose I'm just wondering what level of breaking change we think is acceptable vs. how much do we want to do this refactor?

<!-- gh-comment-id:3176808912 --> @SapiensAnatis commented on GitHub (Aug 11, 2025): Had a bit of a look at this since `SpotifyId` is currently also unable to represent a local file URI. Local file URIs are arbitrary-length strings of the form `spotify:local:{artist}:{album_title}:{track_title}:{duration_in_seconds}`. @photovoltex mentioned tackling this as a potential first step to supporting local files but noted it may not be the best solution if the breaking changes are too large. I implemented a struct like this on top of `SpotifyId`: ```rs #[derive(Clone, PartialEq, Eq, Hash)] pub enum SpotifyResourceName { Id(SpotifyId), String(String), } #[derive(Clone, PartialEq, Eq, Hash)] pub struct SpotifyUri { pub item_type: SpotifyItemType, pub name: SpotifyResourceName, } ``` and then changed the player to use `SpotifyUri` instead of `SpotifyId` internally. We can probably manage the player's method interface reasonably easily by just adding new methods instead of changing existing ones (perhaps with `#[deprecated]` depending on what overall direction we want to take), for example for `player.load()`: ```diff enum PlayerCommand { Load { - track_id: SpotifyId, + track_id: SpotifyUri, play: bool, position_ms: u32, }, } pub fn load(&self, track_id: SpotifyId, start_playing: bool, position_ms: u32) { self.command(PlayerCommand::Load { - track_id, + track_id: track_id.into(), play: start_playing, position_ms, }); } + pub fn load_uri(&self, track_id: SpotifyUri, start_playing: bool, position_ms: u32) { + self.command(PlayerCommand::Load { + track_id, + play: start_playing, + }); + } ``` However we will have breaking changes due to changing the player structs and enums. In particular `PlayerEvent` is often used to manage user interfaces and the like -- the same way the `librespot` binary sets up a player. For example spotifyd uses it here: https://github.com/Spotifyd/spotifyd/blob/master/src/process.rs That is unless there is some further mitigation we can do like keeping track of both a `SpotifyId` and a `SpotifyUri` in the internal player state? My attempt also involved breaking changes to some other function signatures which I _assume_ are only called internally by spirc, but that I could be wrong about. --- I got it to build and you can see the changes [here](https://github.com/SapiensAnatis/librespot/commit/f6b6eb82be86dd3ba934ceb9cd88c1e3105a752d). The bulk of the changed lines of code are because `SpotifyUri` cannot implement `Copy` like `SpotifyId` can, so I have abused `.clone()` all over the place -- this is a first draft though and there may be a more intelligent way to make the borrow checker happy. I suppose I'm just wondering what level of breaking change we think is acceptable vs. how much do we want to do this refactor?
Author
Owner

@acolombier commented on GitHub (Aug 12, 2025):

I got it to build and you can see the changes here. The bulk of the changed lines of code are because SpotifyUri cannot implement Copy like SpotifyId can, so I have abused .clone() all over the place -- this is a first draft though and there may be a more intelligent way to make the borrow checker happy.

I suppose I'm just wondering what level of breaking change we think is acceptable vs. how much do we want to do this refactor?

This is exactly where I left that! Also had breaking change type level of changes and thus thought it was worth carrying on, but if this would be acceptable, I think this would be a fair solution.

Thanks for taking over on this one by the way!

<!-- gh-comment-id:3180495133 --> @acolombier commented on GitHub (Aug 12, 2025): > I got it to build and you can see the changes [here](https://github.com/SapiensAnatis/librespot/commit/f6b6eb82be86dd3ba934ceb9cd88c1e3105a752d). The bulk of the changed lines of code are because SpotifyUri cannot implement Copy like SpotifyId can, so I have abused .clone() all over the place -- this is a first draft though and there may be a more intelligent way to make the borrow checker happy. > I suppose I'm just wondering what level of breaking change we think is acceptable vs. how much do we want to do this refactor? This is exactly where I left that! Also had breaking change type level of changes and thus thought it was worth carrying on, but if this would be acceptable, I think this would be a fair solution. Thanks for taking over on this one by the way!
Author
Owner

@photovoltex commented on GitHub (Sep 11, 2025):

Now we have a quite more flexible struct representing the uri of an item. Isn't this issue done by that? Or do we want to add more types and close it afterwards?

Just asking to figure out what the closing condition of this ticket would be to get it fully resolved :)

<!-- gh-comment-id:3282456309 --> @photovoltex commented on GitHub (Sep 11, 2025): Now we have a quite more flexible struct representing the uri of an item. Isn't this issue done by that? Or do we want to add more types and close it afterwards? Just asking to figure out what the closing condition of this ticket would be to get it fully resolved :)
Author
Owner

@acolombier commented on GitHub (Oct 1, 2025):

Perhaps we could close it and track subtypes individually?

<!-- gh-comment-id:3355088274 --> @acolombier commented on GitHub (Oct 1, 2025): Perhaps we could close it and track subtypes individually?
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/librespot#582
No description provided.