Skip to content

Commit c7ed80c

Browse files
authored
Merge pull request #1 from OzGav/CDN-Fix-plus-Podcast
2 parents 7127672 + 97c81ca commit c7ed80c

File tree

4 files changed

+74
-24
lines changed

4 files changed

+74
-24
lines changed

audio/src/fetch/mod.rs

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ struct AudioFileDownloadStatus {
306306
}
307307

308308
struct AudioFileShared {
309-
cdn_url: CdnUrl,
309+
cdn_url: String,
310310
file_size: usize,
311311
bytes_per_second: usize,
312312
cond: Condvar,
@@ -426,25 +426,44 @@ impl AudioFileStreaming {
426426
) -> Result<AudioFileStreaming, Error> {
427427
let cdn_url = CdnUrl::new(file_id).resolve_audio(&session).await?;
428428

429-
if let Ok(url) = cdn_url.try_get_url() {
430-
trace!("Streaming from {}", url);
431-
}
432-
433429
let minimum_download_size = AudioFetchParams::get().minimum_download_size;
434430

435-
// When the audio file is really small, this `download_size` may turn out to be
436-
// larger than the audio file we're going to stream later on. This is OK; requesting
437-
// `Content-Range` > `Content-Length` will return the complete file with status code
438-
// 206 Partial Content.
439-
let mut streamer =
440-
session
431+
let mut response_streamer_url = None;
432+
let urls = cdn_url.try_get_urls()?;
433+
for url in &urls {
434+
// When the audio file is really small, this `download_size` may turn out to be
435+
// larger than the audio file we're going to stream later on. This is OK; requesting
436+
// `Content-Range` > `Content-Length` will return the complete file with status code
437+
// 206 Partial Content.
438+
let mut streamer = session
441439
.spclient()
442-
.stream_from_cdn(&cdn_url, 0, minimum_download_size)?;
440+
.stream_from_cdn(url, 0, minimum_download_size)?;
441+
442+
// Get the first chunk with the headers to get the file size.
443+
// The remainder of that chunk with possibly also a response body is then
444+
// further processed in `audio_file_fetch`.
445+
let resp = streamer.next().await.map_or_else(
446+
|| Err(AudioFileError::NoData.into()),
447+
|x| x.map_err(Error::from),
448+
);
449+
450+
match resp {
451+
Ok(r) => {
452+
response_streamer_url = Some((r, streamer, url));
453+
break;
454+
}
455+
Err(e) => warn!("Fetching {url} failed with error {e:?}, trying next"),
456+
}
457+
}
458+
459+
let Some((response, streamer, url)) = response_streamer_url else {
460+
return Err(Error::unavailable(format!(
461+
"{} URLs failed, none left to try",
462+
urls.len()
463+
)));
464+
};
443465

444-
// Get the first chunk with the headers to get the file size.
445-
// The remainder of that chunk with possibly also a response body is then
446-
// further processed in `audio_file_fetch`.
447-
let response = streamer.next().await.ok_or(AudioFileError::NoData)??;
466+
trace!("Streaming from {}", url);
448467

449468
let code = response.status();
450469
if code != StatusCode::PARTIAL_CONTENT {
@@ -473,7 +492,7 @@ impl AudioFileStreaming {
473492
};
474493

475494
let shared = Arc::new(AudioFileShared {
476-
cdn_url,
495+
cdn_url: url.to_string(),
477496
file_size,
478497
bytes_per_second,
479498
cond: Condvar::new(),

core/src/cdn_url.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl CdnUrl {
7878
Ok(cdn_url)
7979
}
8080

81+
#[deprecated = "This function only returns the first valid URL. Use try_get_urls instead, which allows for fallback logic."]
8182
pub fn try_get_url(&self) -> Result<&str, Error> {
8283
if self.urls.is_empty() {
8384
return Err(CdnUrlError::Unresolved.into());
@@ -95,6 +96,34 @@ impl CdnUrl {
9596
Err(CdnUrlError::Expired.into())
9697
}
9798
}
99+
100+
pub fn try_get_urls(&self) -> Result<Vec<&str>, Error> {
101+
if self.urls.is_empty() {
102+
return Err(CdnUrlError::Unresolved.into());
103+
}
104+
105+
let now = Date::now_utc();
106+
let urls: Vec<&str> = self
107+
.urls
108+
.iter()
109+
.filter_map(|MaybeExpiringUrl(url, expiry)| match *expiry {
110+
Some(expiry) => {
111+
if now < expiry {
112+
Some(url.as_str())
113+
} else {
114+
None
115+
}
116+
}
117+
None => Some(url.as_str()),
118+
})
119+
.collect();
120+
121+
if urls.is_empty() {
122+
Err(CdnUrlError::Expired.into())
123+
} else {
124+
Ok(urls)
125+
}
126+
}
98127
}
99128

100129
impl TryFrom<CdnUrlMessage> for MaybeExpiringUrls {

core/src/spclient.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::{
66
use crate::config::{os_version, OS};
77
use crate::{
88
apresolve::SocketAddress,
9-
cdn_url::CdnUrl,
109
config::SessionConfig,
1110
error::ErrorKind,
1211
protocol::{
@@ -450,7 +449,12 @@ impl SpClient {
450449

451450
// Reconnection logic: retrieve the endpoint every iteration, so we can try
452451
// another access point when we are experiencing network issues (see below).
453-
let mut url = self.base_url().await?;
452+
let mut url = if endpoint.starts_with("/metadata") {
453+
String::from("https://spclient.wg.spotify.com")
454+
} else {
455+
self.base_url().await?
456+
};
457+
454458
url.push_str(endpoint);
455459

456460
// Add metrics. There is also an optional `partner` key with a value like
@@ -732,14 +736,13 @@ impl SpClient {
732736

733737
pub fn stream_from_cdn(
734738
&self,
735-
cdn_url: &CdnUrl,
739+
cdn_url: &str,
736740
offset: usize,
737741
length: usize,
738742
) -> Result<IntoStream<ResponseFuture>, Error> {
739-
let url = cdn_url.try_get_url()?;
740743
let req = Request::builder()
741744
.method(&Method::GET)
742-
.uri(url)
745+
.uri(cdn_url)
743746
.header(
744747
RANGE,
745748
HeaderValue::from_str(&format!("bytes={}-{}", offset, offset + length - 1))?,

src/main.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,13 +1971,12 @@ async fn main() {
19711971
if let Some(ref track_id) = setup.single_track {
19721972
// Handle playback of single track + exit
19731973

1974-
let mut track = SpotifyId::from_uri(
1974+
let track = SpotifyId::from_uri(
19751975
track_id
19761976
.replace("spotty://", "spotify:track:")
19771977
.replace("://", ":")
19781978
.as_str(),
19791979
).unwrap();
1980-
track.item_type = SpotifyItemType::Track;
19811980

19821981
if let Some(credentials) = setup.credentials {
19831982
info!("Connecting...");

0 commit comments

Comments
 (0)