Skip to content

Commit d007e3a

Browse files
authored
Fix: Harden the transfer flow for unexpected data (#1581)
* fix: transfer data has an invalid uri and no tracks * fix: play/pause button being disabled on transfer
1 parent 57a5fbb commit d007e3a

File tree

3 files changed

+81
-48
lines changed

3 files changed

+81
-48
lines changed

CHANGELOG.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616

1717
### Changed
1818

19-
- [playback] Changed type of `SpotifyId` fields in `PlayerEvent` members to `SpotifyUri` (breaking)
2019
- [metadata] Changed arguments for `Metadata` trait from `&SpotifyId` to `&SpotifyUri` (breaking)
21-
- [player] `load` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
22-
- [player] `preload` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
23-
- [spclient] `get_radio_for_track` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
20+
- [playback] Changed type of `SpotifyId` fields in `PlayerEvent` members to `SpotifyUri` (breaking)
21+
- [playback] `load` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
22+
- [playback] `preload` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
23+
- [core] `get_radio_for_track` function changed from accepting a `SpotifyId` to accepting a `SpotifyUri` (breaking)
2424

2525
### Fixed
2626

27+
- [connect] Fixed failed transferring with transfer data that had an empty context uri and no tracks
2728
- [connect] Use the provided index or the first as fallback value to always play a track on loading
2829

2930
### Removed

connect/src/spirc.rs

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,13 +1087,17 @@ impl SpircTask {
10871087
fn handle_transfer(&mut self, mut transfer: TransferState) -> Result<(), Error> {
10881088
let mut ctx_uri = match transfer.current_session.context.uri {
10891089
None => Err(SpircError::NoUri("transfer context"))?,
1090-
// can apparently happen when a state is transferred stared with "uris" via the api
1091-
Some(ref uri) if uri == "-" => String::new(),
1092-
Some(ref uri) => uri.clone(),
1090+
// can apparently happen when a state is transferred and was started with "uris" via the api
1091+
Some(ref uri) if uri == "-" || uri.is_empty() => None,
1092+
Some(ref uri) => Some(uri.clone()),
10931093
};
10941094

1095-
self.connect_state
1096-
.reset_context(ResetContext::WhenDifferent(&ctx_uri));
1095+
self.connect_state.reset_context(
1096+
ctx_uri
1097+
.as_deref()
1098+
.map(ResetContext::WhenDifferent)
1099+
.unwrap_or(ResetContext::Completely),
1100+
);
10971101

10981102
match self.connect_state.current_track_from_transfer(&transfer) {
10991103
Err(why) => warn!("didn't find initial track: {why}"),
@@ -1105,44 +1109,47 @@ impl SpircTask {
11051109

11061110
let autoplay = self.connect_state.current_track(|t| t.is_autoplay());
11071111
if autoplay {
1108-
ctx_uri = ctx_uri.replace("station:", "");
1112+
ctx_uri = ctx_uri.map(|c| c.replace("station:", ""));
11091113
}
11101114

11111115
let fallback = self.connect_state.current_track(|t| &t.uri).clone();
1112-
let load_from_context_uri = !ctx_uri.is_empty();
1113-
1114-
if load_from_context_uri {
1115-
self.context_resolver.add(ResolveContext::from_uri(
1116-
ctx_uri.clone(),
1117-
&fallback,
1118-
ContextType::Default,
1119-
ContextAction::Replace,
1120-
));
1121-
} else {
1122-
self.load_context_from_tracks(
1123-
transfer
1116+
let load_from_context_uri = ctx_uri.is_some();
1117+
1118+
match ctx_uri {
1119+
Some(ref uri) => {
1120+
self.context_resolver.add(ResolveContext::from_uri(
1121+
uri.clone(),
1122+
&fallback,
1123+
ContextType::Default,
1124+
ContextAction::Replace,
1125+
));
1126+
}
1127+
None => {
1128+
let all_tracks = transfer
11241129
.current_session
11251130
.context
11261131
.pages
11271132
.iter()
11281133
.cloned()
11291134
.flat_map(|p| p.tracks)
1130-
.collect::<Vec<_>>(),
1131-
)?
1132-
}
1135+
.collect::<Vec<_>>();
11331136

1134-
self.context_resolver.add(ResolveContext::from_uri(
1135-
ctx_uri.clone(),
1136-
&fallback,
1137-
ContextType::Default,
1138-
ContextAction::Replace,
1139-
));
1137+
if !all_tracks.is_empty() {
1138+
self.load_context_from_tracks(all_tracks)?;
1139+
} else {
1140+
warn!(
1141+
"tried to transfer with an invalid state, using fallback as ctx_uri ({fallback})"
1142+
);
1143+
ctx_uri = Some(fallback.clone())
1144+
}
1145+
}
1146+
};
11401147

11411148
self.handle_activate();
11421149

11431150
let timestamp = self.now_ms();
11441151
let state = &mut self.connect_state;
1145-
state.handle_initial_transfer(&mut transfer);
1152+
state.handle_initial_transfer(&mut transfer, ctx_uri.clone());
11461153

11471154
// adjust active context, so resolve knows for which context it should set up the state
11481155
state.active_context = if autoplay {
@@ -1166,24 +1173,34 @@ impl SpircTask {
11661173
let is_playing = !transfer.playback.is_paused();
11671174

11681175
if self.connect_state.current_track(|t| t.is_autoplay()) || autoplay {
1169-
debug!("currently in autoplay context, async resolving autoplay for {ctx_uri}");
1170-
1171-
self.context_resolver.add(ResolveContext::from_uri(
1172-
ctx_uri,
1173-
fallback,
1174-
ContextType::Autoplay,
1175-
ContextAction::Replace,
1176-
))
1176+
if let Some(ctx_uri) = ctx_uri {
1177+
debug!("currently in autoplay context, async resolving autoplay for {ctx_uri}");
1178+
self.context_resolver.add(ResolveContext::from_uri(
1179+
ctx_uri,
1180+
fallback,
1181+
ContextType::Autoplay,
1182+
ContextAction::Replace,
1183+
))
1184+
} else {
1185+
warn!("couldn't resolve autoplay context without a context uri");
1186+
}
11771187
}
11781188

11791189
if load_from_context_uri {
11801190
self.transfer_state = Some(transfer);
11811191
} else {
1182-
let ctx = self.connect_state.get_context(ContextType::Default)?;
1183-
let idx = ConnectState::find_index_in_context(ctx, |pt| {
1184-
self.connect_state.current_track(|t| pt.uri == t.uri)
1185-
})?;
1186-
self.connect_state.reset_playback_to_position(Some(idx))?;
1192+
match self.connect_state.get_context(ContextType::Default) {
1193+
Err(why) => {
1194+
warn!("continuing transfer in an unknown state. {why}");
1195+
self.transfer_state = Some(transfer);
1196+
}
1197+
Ok(ctx) => {
1198+
let idx = ConnectState::find_index_in_context(ctx, |pt| {
1199+
self.connect_state.current_track(|t| pt.uri == t.uri)
1200+
})?;
1201+
self.connect_state.reset_playback_to_position(Some(idx))?;
1202+
}
1203+
}
11871204
}
11881205

11891206
self.load_track(is_playing, position.try_into()?)
@@ -1397,7 +1414,11 @@ impl SpircTask {
13971414
}
13981415

13991416
fn load_context_from_tracks(&mut self, tracks: impl Into<ContextPage>) -> Result<(), Error> {
1417+
const WEB_API_URI: &str = "spotify:web-api";
14001418
let ctx = Context {
1419+
// by providing values for uri/url the player in the official client's isn't frozen
1420+
uri: Some(WEB_API_URI.into()),
1421+
url: Some(format!("context://{WEB_API_URI}")),
14011422
pages: vec![tracks.into()],
14021423
..Default::default()
14031424
};

connect/src/state/transfer.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ impl ConnectState {
1717
transfer: &TransferState,
1818
) -> Result<ProvidedTrack, Error> {
1919
let track = if transfer.queue.is_playing_queue.unwrap_or_default() {
20+
debug!("transfer track was used from the queue");
2021
transfer.queue.tracks.first()
2122
} else {
23+
debug!("transfer track was the current track");
2224
transfer.playback.current_track.as_ref()
2325
}
2426
.ok_or(StateError::CouldNotResolveTrackFromTransfer)?;
@@ -37,7 +39,11 @@ impl ConnectState {
3739
}
3840

3941
/// handles the initially transferable data
40-
pub fn handle_initial_transfer(&mut self, transfer: &mut TransferState) {
42+
pub fn handle_initial_transfer(
43+
&mut self,
44+
transfer: &mut TransferState,
45+
ctx_uri: Option<String>,
46+
) {
4147
let current_context_metadata = self.context.as_ref().map(|c| c.metadata.clone());
4248
let player = self.player_mut();
4349

@@ -84,8 +90,13 @@ impl ConnectState {
8490
}
8591
}
8692

87-
player.context_url.clear();
88-
player.context_uri.clear();
93+
const UNKNOWN_URI: &str = "spotify:unknown";
94+
// it's important to always set the url/uri to a value
95+
// so that the player doesn't go into an inactive state
96+
let uri = ctx_uri.unwrap_or(UNKNOWN_URI.into());
97+
98+
player.context_url = format!("context://{uri}");
99+
player.context_uri = uri;
89100

90101
if let Some(metadata) = current_context_metadata {
91102
for (key, value) in metadata {

0 commit comments

Comments
 (0)