Skip to content

Conversation

SapiensAnatis
Copy link
Contributor

Discussed in #1525. Adds a basic level of support for playing back local files from a connect device. The librespot binary must be launched with --local-file-dir / -l flags specifying search paths for local files. If a request is received to play a local file, and a file match is made based on the file's artist, album, track title, and duration, then librespot will now be able to play it.

This support has the following caveats:

  • Unlike the official client, we cannot play local files if they have a sample rate != 44,100 Hz. This is the sample rate used by the player - without adjusting it, playback of local files with differing sample rates will sound either slowed or sped up. Adjusting the sample rate of the player on the fly is a non-trivial task which may be implemented later.
  • The dedicated 'Local Files' playlist cannot be used to play a local file. This is a special playlist with a URI of spotify:local-files which currently causes an error in the context handling code, as librespot attempts to fetch it from the API and appears to get a 404. I tried to make this work, but it feels like an abstraction for context would be beneficial so we could generate context from the filesystem (currently I think we use the proto models which are obviously tightly coupled to the Spotify API). Also, I do not understand enough about the context handling code to do a good job 😅

Spotify Connect does not allow you to move playback of a local file to
the librespot device as it says that it "can't play this track". Note
that this is slightly inconsistent as Spotify allows you to switch to
a local file if librespot is already playing a non-local file, which
currently fails with an error.

However, it is possible for the desktop and iOS client to accept
playback of local files. In looking at the PUT request sent to
`connect-state/v1/devices/<id>` from the iOS client, it can be seen that
it includes `audio/local` as an entry in the `supported_types`
capability field.

This commit introduces this field to the capabilities that librespot
sends. For now, it is a complete lie as we do not support local file
playback, but it will make the ongoing development of this feature
easier, as we will not have to queue up a non-local track and attempt
to switch to a local one.

Testing shows that with this flag the "can't play this track" message
disappears and allows librespot to (attempt) to play a local file
before erroring out.
}

fn get_uri_from_file(audio_path: &Path, extension: &str) -> Result<SpotifyUri, Error> {
let src = File::open(audio_path)?;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All of my I/O for this feature is synchronous for now.

I tried using tokio::fs APIs but they don't appear to play ball with Symphonia's MediaSource trait bound, however maybe into_std() which I have just found while writing this comment could help.

Additionally, we create this lookup inside player::new(), I get that in Rust there are no constructors and new() is not special, but something about my C# background makes me very hesitant to make new() async, am I right to think this?

@SapiensAnatis SapiensAnatis changed the title Basic local file support feat: basic local file support Sep 22, 2025
@SapiensAnatis SapiensAnatis changed the title feat: basic local file support feat: Basic local file support Sep 22, 2025
Comment on lines +234 to +242
// If we can't get metadata from the container, fall back to other tags found by probing.
// Note that this is only relevant for local files.
if metadata.current().is_none() {
if let Some(ref mut probe_metadata) = self.probed_metadata {
if let Some(inner_probe_metadata) = probe_metadata.get() {
metadata = inner_probe_metadata;
}
}
}
Copy link
Contributor Author

@SapiensAnatis SapiensAnatis Sep 22, 2025

Choose a reason for hiding this comment

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

This part is new, I found calling self.format.metadata returned empty metadata with the files I had, and found this snippet in Symphonia's player example: https://github.com/pdeljanov/Symphonia/blob/master/symphonia-play/src/main.rs#L533-L536

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.

1 participant