Skip to content

Commit df5f957

Browse files
refactor: Introduce SpotifyUri struct (#1538)
* refactor: Introduce SpotifyUri struct Contributes to #1266 Introduces a new `SpotifyUri` struct which is layered on top of the existing `SpotifyId`, but has the capability to support URIs that do not confirm to the canonical base62 encoded format. This allows it to describe URIs like `spotify:local`, `spotify:genre` and others that `SpotifyId` cannot represent. Changed the internal player state to use these URIs as much as possible, such that the player could in the future accept a URI of the type `spotify:local`, as a means of laying the groundwork for local file support. * fix: Don't pass unknown URIs from deprecated player methods * refactor: remove SpotifyUri::to_base16 This should be deprecated for the same reason to_base62 is, and could unpredictably throw errors -- consumers should match on the inner ID if they need a base62 representation and handle failure appropriately * refactor: Store original data in SpotifyUri::Unknown Instead of assuming Unknown has a u128 SpotifyId, store the original data and type that we failed to parse. * refactor: Remove SpotifyItemType * refactor: Address review feedback * test: Add more SpotifyUri tests * chore: Correctly mark changes as breaking in CHANGELOG.md * refactor: Respond to review feedback * chore: Changelog updates
1 parent 0e5531f commit df5f957

File tree

23 files changed

+937
-625
lines changed

23 files changed

+937
-625
lines changed

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) since v0.2.0.
77

8+
## [Unreleased]
9+
10+
### Added
11+
12+
- [core] Add `SpotifyUri` type to represent more types of URI than `SpotifyId` can
13+
14+
### Changed
15+
16+
- [playback] Changed type of `SpotifyId` fields in `PlayerEvent` members to `SpotifyUri` (breaking)
17+
- [metadata] Changed arguments for `Metadata` trait from `&SpotifyId` to `&SpotifyUri` (breaking)
18+
- [player] `load` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
19+
- [player] `preload` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
20+
- [spclient] `get_radio_for_track` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
21+
22+
23+
### Removed
24+
25+
- [core] Removed `SpotifyItemType` enum; the new `SpotifyUri` is an enum over all item types and so which variant it is
26+
describes its item type (breaking)
27+
- [core] Removed `NamedSpotifyId` struct; it was made obsolete by `SpotifyUri` (breaking)
28+
- [core] The following methods have been removed from `SpotifyId` and moved to `SpotifyUri` (breaking):
29+
- `is_playable`
30+
- `from_uri`
31+
- `to_uri`
32+
833
## [v0.7.1] - 2025-08-31
934

1035
### Changed

connect/src/spirc.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
LoadContextOptions, LoadRequestOptions, PlayContext,
33
context_resolver::{ContextAction, ContextResolver, ResolveContext},
44
core::{
5-
Error, Session, SpotifyId,
5+
Error, Session, SpotifyUri,
66
authentication::Credentials,
77
dealer::{
88
manager::{BoxedStream, BoxedStreamResult, Reply, RequestReply},
@@ -778,7 +778,7 @@ impl SpircTask {
778778
return Ok(());
779779
}
780780
PlayerEvent::Unavailable { track_id, .. } => {
781-
self.handle_unavailable(track_id)?;
781+
self.handle_unavailable(&track_id)?;
782782
if self.connect_state.current_track(|t| &t.uri) == &track_id.to_uri()? {
783783
self.handle_next(None)?
784784
}
@@ -1499,7 +1499,7 @@ impl SpircTask {
14991499
}
15001500

15011501
// Mark unavailable tracks so we can skip them later
1502-
fn handle_unavailable(&mut self, track_id: SpotifyId) -> Result<(), Error> {
1502+
fn handle_unavailable(&mut self, track_id: &SpotifyUri) -> Result<(), Error> {
15031503
self.connect_state.mark_unavailable(track_id)?;
15041504
self.handle_preload_next_track();
15051505

@@ -1704,7 +1704,7 @@ impl SpircTask {
17041704
}
17051705

17061706
let current_uri = self.connect_state.current_track(|t| &t.uri);
1707-
let id = SpotifyId::from_uri(current_uri)?;
1707+
let id = SpotifyUri::from_uri(current_uri)?;
17081708
self.player.load(id, start_playing, position_ms);
17091709

17101710
self.connect_state

connect/src/state/context.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
core::{Error, SpotifyId},
2+
core::{Error, SpotifyId, SpotifyUri},
33
protocol::{
44
context::Context,
55
context_page::ContextPage,
@@ -449,8 +449,10 @@ impl ConnectState {
449449
(Some(uri), _) if uri.contains(['?', '%']) => {
450450
Err(StateError::InvalidTrackUri(Some(uri.clone())))?
451451
}
452-
(Some(uri), _) if !uri.is_empty() => SpotifyId::from_uri(uri)?,
453-
(_, Some(gid)) if !gid.is_empty() => SpotifyId::from_raw(gid)?,
452+
(Some(uri), _) if !uri.is_empty() => SpotifyUri::from_uri(uri)?,
453+
(_, Some(gid)) if !gid.is_empty() => SpotifyUri::Track {
454+
id: SpotifyId::from_raw(gid)?,
455+
},
454456
_ => Err(StateError::InvalidTrackUri(None))?,
455457
};
456458

connect/src/state/tracks.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
core::{Error, SpotifyId},
2+
core::{Error, SpotifyUri},
33
protocol::player::ProvidedTrack,
44
state::{
55
ConnectState, SPOTIFY_MAX_NEXT_TRACKS_SIZE, SPOTIFY_MAX_PREV_TRACKS_SIZE, StateError,
@@ -352,14 +352,14 @@ impl<'ct> ConnectState {
352352
Ok(())
353353
}
354354

355-
pub fn preview_next_track(&mut self) -> Option<SpotifyId> {
355+
pub fn preview_next_track(&mut self) -> Option<SpotifyUri> {
356356
let next = if self.repeat_track() {
357357
self.current_track(|t| &t.uri)
358358
} else {
359359
&self.next_tracks().first()?.uri
360360
};
361361

362-
SpotifyId::from_uri(next).ok()
362+
SpotifyUri::from_uri(next).ok()
363363
}
364364

365365
pub fn has_next_tracks(&self, min: Option<usize>) -> bool {
@@ -381,7 +381,7 @@ impl<'ct> ConnectState {
381381
prev
382382
}
383383

384-
pub fn mark_unavailable(&mut self, id: SpotifyId) -> Result<(), Error> {
384+
pub fn mark_unavailable(&mut self, id: &SpotifyUri) -> Result<(), Error> {
385385
let uri = id.to_uri()?;
386386

387387
debug!("marking {uri} as unavailable");

core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ mod socket;
3232
#[allow(dead_code)]
3333
pub mod spclient;
3434
pub mod spotify_id;
35+
pub mod spotify_uri;
3536
pub mod token;
3637
#[doc(hidden)]
3738
pub mod util;
@@ -42,3 +43,4 @@ pub use error::Error;
4243
pub use file_id::FileId;
4344
pub use session::Session;
4445
pub use spotify_id::SpotifyId;
46+
pub use spotify_uri::SpotifyUri;

core/src/spclient.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55

66
use crate::config::{OS, os_version};
77
use crate::{
8-
Error, FileId, SpotifyId,
8+
Error, FileId, SpotifyId, SpotifyUri,
99
apresolve::SocketAddress,
1010
config::SessionConfig,
1111
error::ErrorKind,
@@ -676,10 +676,10 @@ impl SpClient {
676676
.await
677677
}
678678

679-
pub async fn get_radio_for_track(&self, track_id: &SpotifyId) -> SpClientResult {
679+
pub async fn get_radio_for_track(&self, track_uri: &SpotifyUri) -> SpClientResult {
680680
let endpoint = format!(
681681
"/inspiredby-mix/v2/seed_to_playlist/{}?response-format=json",
682-
track_id.to_uri()?
682+
track_uri.to_uri()?
683683
);
684684

685685
self.request_as_json(&Method::GET, &endpoint, None, None)

0 commit comments

Comments
 (0)