Skip to content

audio: fall back to the next CDN URL when a fetch returns a non-206 status#1722

Merged
kingosticks merged 2 commits into
librespot-org:devfrom
dmeiselman:cdn-failover-on-non-206
Jun 25, 2026
Merged

audio: fall back to the next CDN URL when a fetch returns a non-206 status#1722
kingosticks merged 2 commits into
librespot-org:devfrom
dmeiselman:cdn-failover-on-non-206

Conversation

@dmeiselman

Copy link
Copy Markdown
Contributor

Problem

Spotify hands librespot several CDN URLs for each track and expects the client to try the next one if a URL doesn't work. Right now, librespot only moves on to the next URL when a download fails at the connection level (timeout, DNS, TLS, etc.).

But a CDN edge can accept the connection and still answer with an HTTP error — e.g. a 500 Internal Server Error from an edge node that's being drained. To librespot that counts as a "successful" response, so it stops on that first broken URL and never tries the others. The track fails to load, and playback never starts.

This currently breaks playback completely for some accounts: Spotify is handing out a bad edge as the first URL, so every track dies on the first try even though working URLs were right there in the list.

This fix builds on #1524, which added CDN-URL fallback but only for connection-level errors. This change extends that same fallback to cover HTTP error responses too.

Evidence

For one track, storage-resolve returned 4 CDN URLs. Fetching each directly with a range request (curl):

audio-fa-del-874.spotifycdn.com - 500 ← the only one librespot tried
audio-cf.spotifycdn.com - 206
audio-fa.scdn.co - 206
audio4-ak.spotifycdn.com - 206

librespot's log on the unpatched build:

DEBUG librespot_audio::fetch] Opening audio file expected partial content but got: 500 Internal Server Error
ERROR librespot_playback::player] Unable to load encrypted file: Error { kind: FailedPrecondition, error: StatusCode(500) }

It tried the broken URL, treated the 500 as a final answer, and gave up — never attempting the three working URLs.

Fix

A successful fetch is now defined as one that actually returns 206 Partial Content (what a range request should return). Anything else — a 500, or any other status — is logged and treated like the other failures, so the loop continues to the next CDN URL instead of stopping.

The change is in the existing URL-retry loop in audio/src/fetch/mod.rs (AudioFileStreaming::open): the "is this 206?" check, which previously ran after the loop had already committed to one URL, now runs inside the loop so a non-206 response falls through to the next candidate.

Testing

Built from this branch and played tracks against the same account that was failing:

  • Before: 0 tracks loaded (every one hit the 500 and gave up).
  • After: 31/31 tracks loaded and played — librespot skipped the 500 edge and streamed from the next 206 URL automatically.

Also verified end-to-end through the spotty helper / Lyrion Music Server, where this bug was originally observed: full tracks decode and play.

AudioFileStreaming::open() resolves several CDN URLs from storage-resolve and loops over them to find one that streams, but the loop only treats a transport error (the Err arm) as a reason to try the next URL. An HTTP error status such as 500 is a successful response at the transport layer, so the Ok(_) arm matches and the loop breaks on that first URL. The StatusCode::PARTIAL_CONTENT check then runs AFTER the loop and returns an error, so the remaining (working) CDN URLs are never attempted and playback fails (surfacing as "Unable to load encrypted file: FailedPrecondition").

Reproducible against Spotify today: storage-resolve returns multiple CDN URLs where the first host (e.g. audio-fa-del-874.spotifycdn.com) returns 500 while the others (audio-cf.spotifycdn.com, audio-fa.scdn.co, audio4-ak.spotifycdn.com) return 206. Every track fails even though working URLs were provided in the same response.

Move the 206 acceptance into the loop: only break when a URL responds with PARTIAL_CONTENT; otherwise log the unexpected status and fall through to the next URL. The post-loop status check is kept as a safety net.

Tested against a live Premium account: before, 0 tracks loaded (every track returned 500 on the first URL); after, tracks load by failing over from the 500 edge to a 206 one.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Extends the existing CDN URL fallback logic in AudioFileStreaming::open so that librespot also retries the next CDN URL when the HTTP response is not 206 Partial Content, not just when connection-level errors occur. This aligns the client behavior with Spotify’s expectation that multiple CDN URLs are tried until a working one is found.

Changes:

  • Treat non-206 Partial Content HTTP responses as a failed attempt and continue to the next CDN URL.
  • Log non-206 responses as retryable failures during initial streaming open.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread audio/src/fetch/mod.rs
@milnivlek

Copy link
Copy Markdown

This is very timely, as a Spotify-side issue apparently arose this morning which this PR should help mitigate. #1723 (comment)

Please merge!

@kingosticks

Copy link
Copy Markdown
Contributor

Can you address the copilot review suggestion, which I think is sensible. And add a changelog entry.

@dmeiselman

Copy link
Copy Markdown
Contributor Author

removed the duplicate check and updated CHANGELOG.md

@michaelherger

Copy link
Copy Markdown
Contributor

FWIW: I can report one more happy user with this patch!

michaelherger added a commit to michaelherger/librespot that referenced this pull request Jun 22, 2026
@DejaVuMan

DejaVuMan commented Jun 24, 2026

Copy link
Copy Markdown

A bit late to the party, but this can be mocked up to some degree by blocking the top-most served CDN (audio-fa-del-874.spotifycdn.com) in firewall, which will give you a TCP connect error and let librespot try the next CDN

@LikelyLucid

Copy link
Copy Markdown

This patch fixes the same issue for me as well.

Repro was ncspot/librespot on NixOS with a Premium account: cached tracks played, but uncached tracks failed with Spotify CDN responses like 500/530:

Opening audio file expected partial content but got: 530
Unable to load encrypted file: Error { kind: FailedPrecondition, error: StatusCode(530) }
Skipping to next track, unable to load track

I applied this fallback patch locally to ncspot's vendored librespot-audio 0.8.0, and uncached tracks started playing immediately.

@kingosticks kingosticks merged commit db1ef7a into librespot-org:dev Jun 25, 2026
11 of 12 checks passed
MichaelPachec0 added a commit to MichaelPachec0/ncspot that referenced this pull request Jun 26, 2026
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.

7 participants