librespot-org / librespot

Open Source Spotify client library
MIT License
4.76k stars 590 forks source link

Possible ratelimiting introduced by Spotify #1319

Closed x528491x closed 5 days ago

x528491x commented 3 weeks ago

When shuffling between tracks too fast, I get the error message below while fetching the audio_key for decryption:

Error { kind: Unavailable, error: AesKey }

I had not run into such errors a few days ago, and I have seen other people report the same error recently on discord servers.

x528491x commented 2 weeks ago

Spotify might be sending a retry after header like it does for most rate limited endpoints.

The header, if it is sent, needs to be exposed.

roderickvd commented 2 weeks ago

IIRC I did implement "retry after" in dev, no?

Edit: but only for HTTP and getting the key goes through Mercury instead.

x528491x commented 2 weeks ago

A retry mechanism for the audio keys is direly needed as the rate limiting almost makes my entire project unusable.

roderickvd commented 2 weeks ago

If you're a developer yourself, a PR would be welcome.

Edit: curious, what is your situation that it's unusable today?

x528491x commented 2 weeks ago

If you're a developer yourself, a PR would be welcome.

I'm a golang developer and my first and only rust project is using this library 😅

I do intend to learn rust and contribute to this project, but for now I'll wait for someone more knowledgeable and well versed with the project's codebase to work on it.

michaelherger commented 2 weeks ago

Is there a good reason for the rate limiting? Or could the reason for the rate limiting be addressed instead of adding retry?

x528491x commented 2 weeks ago

Is there a good reason for the rate limiting? Or could the reason for the rate limiting be addressed instead of adding retry?

My project revolves around curating music after experiencing the "vibe" of a song, almost in a speedrun manner. (Might be the most cringe description)

To do so, I let the user quickly shuffle between tracks in a playlist, and automatically jump to a random position (somewhere around the middle of the track) and play it for a couple of seconds, giving the user the option to add it to their playlist should they have liked the preview.

The whole idea is that time is precious and a playlist can never be long enough ;)

As to why Spotify introduced the rate limiting that did not exist before, I do not know why. Probably they just discovered the lapse while pushing the other changes they had made to the API lately.

I cannot really "address" the cause of my project being rate limited due to the very nature of it. However, I'll still be happy to have it working as before while complying to Spotify's rate limit policies which I have yet to see stated with actual limits anywhere on their documentation.

michaelherger commented 1 week ago

Ok, sounds like a good reason to be rate limited 😄. Your application certainly is not typical behaviour, and from their point of view it might look like someone trying to mass download music files. Why else would a normal user try to get tracks in quick succession? Your application is out of the ordinary.

Whether they changed rate limiting recently or not, we can only speculate. But if I were a service provider, I'd probably block this kind of pattern, too.

x528491x commented 1 week ago

Ok, sounds like a good reason to be rate limited 😄. Your application certainly is not typical behaviour, and from their point of view it might look like someone trying to mass download music files. Why else would a normal user try to get tracks in quick succession? Your application is out of the ordinary.

Whether they changed rate limiting recently or not, we can only speculate. But if I were a service provider, I'd probably block this kind of pattern, too.

I do know that my application is in the grey area in regards to spamming requests for tracks one after the other in short intervals.

But that is... kind of the appeal of it. To help the user take shortcuts in the process and save some time.

Sure, I get how Spotify might view my application as a batch downloader of sorts, and I admit that my use case may be mildly abusive, but I want to adhere to their rate limits so as to not face failed requests nor get my account banned.

Retry-After would fix my application:

Once the user requests a shuffle, I'll send a request to fetch the next song,

and if I'm already rate limited, I'll wait for the Retry-After interval to be reached, and until then I'll continue playing the current track and notify the user that the shuffle will be done in x seconds.

roderickvd commented 1 week ago

In Mercury, which is just TCP-based, there is no retry after header. Until someone adds rate limiting there in librespot (you could mostly copy it from the way it's done with HTTP) I suggest that you do it on the caller end (your end).

kingosticks commented 1 week ago

There are some rate limiting error codes in the esdk ( kSpErrorAPIRateLimited, kSpErrorPlaybackRateLimited, kSpErrorSkipLimitReached), and they've been there for a while, but I can't tell if it was before switching to http media. Sadly I also couldn't find any explicit description of those limits in the public esdk docs. Perhaps there are some hints in old firmware blobs you could try.

Have you considered using their unencrypted (30 second?) song previews instead? Might that work? I think it could be considered a breach of their ToS but maybe it's a friendlier thing to do?

x528491x commented 1 week ago

There are some rate limiting error codes in the esdk ( kSpErrorAPIRateLimited, kSpErrorPlaybackRateLimited, kSpErrorSkipLimitReached), and they've been there for a while, but I can't tell if it was before switching to http media. Sadly I also couldn't find any explicit description of those limits in the public esdk docs. Perhaps there are some hints in old firmware blobs you could try.

Have you considered using their unencrypted (30 second?) song previews instead? Might that work? I think it could be considered a breach of their ToS but maybe it's a friendlier thing to do?

Oh, my first prototype used the 30 second previews and I had thought that using the previews was the easy way to go until I ran into availability issues - not every track has a preview file. The availability is further deteriorated for indie artists and less popular languages.

Here is a Spotify employee confirming the availability issues

And apparently Spotify gives the artists the option to select what portion of the track they want to list for preview.

And if the artist had skipped generating a preview, the Spotify employee who approves the listing does it manually.

Source for the above

This is a hit or miss in two ways - The employee might skip generating the preview too, leaving no preview for the track at all,

or select a random portion of the track that has no real accoustic significance.

This is why I changed my approach and took it upon myself to generate a preview off of the actual track which would always be available, no matter what.

I too could add ratelimiting or a retry mechanism in my app. However it would be a better fix if we could get the Retry-After header as the implementation would be an educated one instead of it being a guessing game.

And while my use case is probably very unique, I reckon that addressing it in librespot would benefit its other users too as this rate limit is sometimes surprisingly easy to hit, especially when the application gives the control of playback to many users.

kingosticks commented 1 week ago

a better fix if we could get the Retry-After header

What header? From where? There are no headers for Mercury requests like this.

I don't recall any rate limited playback issues here but perhaps these are downstream problems from projects doing weird things that we don't hear about.

x528491x commented 1 week ago

a better fix if we could get the Retry-After header

What header? From where? There are no headers for Mercury requests like this.

I don't recall any rate limited playback issues here but perhaps these are downstream problems from projects doing weird things that we don't hear about.

Apologies. I missed roderick's earlier message stating that there are no Retry-After headers.

In that case, I guess I'll just have to modify my code to fetch the preview files and fall back to the actual track if the preview file is not found.

Feel free to close this issue if any retry implementation is not planned for such edge cases.

roderickvd commented 5 days ago

I do think you should handle it on the caller side.