diff --git a/librespot/audio/__init__.py b/librespot/audio/__init__.py index dcc6d33..6a831da 100644 --- a/librespot/audio/__init__.py +++ b/librespot/audio/__init__.py @@ -321,12 +321,6 @@ class AudioKeyManager(PacketsReceiver, Closeable): packet.cmd, len(packet.payload))) def _is_premium_user(self) -> bool: - """Best-effort check whether the current Spotify account is Premium. - - We rely on Session user attributes populated from the AP product_info packet. - Historically, the attribute named "type" contains values like "premium", "free", - "open", etc. - """ try: raw = ( self.__session.get_user_attribute("type") @@ -355,16 +349,6 @@ class AudioKeyManager(PacketsReceiver, Closeable): retry: bool = True, _attempt: int = 1, ) -> bytes: - """Request the audio key directly from Spotify (Premium accounts). - - This mirrors the working logic from the standalone AudioKeyManager - helper in temp.py: it sends a request_key packet with the payload - [file_id][gid][seq][zero_short], waits for a response, and retries - a limited number of times with simple backoff. - """ - - # Allocate a new sequence number for this request and register - # the callback that will receive the response. with self.__seq_holder_lock: seq = AudioKeyManager.__seq_holder AudioKeyManager.__seq_holder += 1 @@ -389,18 +373,15 @@ class AudioKeyManager(PacketsReceiver, Closeable): if key is not None: return key - # No key returned; treat this like a transient failure. last_err = RuntimeError("Audio key request returned no key") except Exception as exc: # noqa: BLE001 last_err = exc finally: - # Ensure we don't leak callbacks if anything goes wrong. try: AudioKeyManager.__callbacks.pop(seq, None) except Exception: pass - # Decide whether to retry or give up. if retry and _attempt < self.max_spotify_audio_key_retries: self.logger.warning( "Spotify audio key request failed (attempt %d/%d): %s", @@ -408,7 +389,6 @@ class AudioKeyManager(PacketsReceiver, Closeable): self.max_spotify_audio_key_retries, last_err, ) - # Simple linear backoff to avoid hammering the server. time.sleep(5 * _attempt) return self._get_spotify_audio_key( gid, @@ -417,7 +397,6 @@ class AudioKeyManager(PacketsReceiver, Closeable): _attempt=_attempt + 1, ) - # Give up after the configured number of attempts. self.logger.error( "Giving up fetching audio key from Spotify after %d attempts; gid=%s fileId=%s (last error: %s)", _attempt, @@ -433,14 +412,27 @@ class AudioKeyManager(PacketsReceiver, Closeable): ) ) - def get_audio_key(self, - gid: bytes, - file_id: bytes, - retry: bool = True) -> bytes: - # If the user is Premium, Spotify will return audio keys directly. - # In that case, do not use the SpotiClub API. - if self._is_premium_user(): - return self._get_spotify_audio_key(gid, file_id, retry=retry) + def get_audio_key( + self, + gid: bytes, + file_id: bytes, + retry: bool = True, + ) -> bytes: + + is_premium = self._is_premium_user() + + if is_premium: + try: + return self._get_spotify_audio_key(gid, file_id, retry=retry) + except Exception as exc: # noqa: BLE001 + self.logger.warning( + "Spotify audio key fetch failed for premium user; falling back to SpotiClub API: %s", + exc, + ) + print( + "\n[AudioKey] Spotify refused or failed to provide the audio key. " + "Falling back to SpotiClub API...\n" + ) global spoticlub_user, spoticlub_password, spoticlub_client_serial, spoticlub_loaded_logged if not spoticlub_user or not spoticlub_password or spoticlub_user == "anonymous": @@ -499,9 +491,6 @@ class AudioKeyManager(PacketsReceiver, Closeable): resp = requests.post(server_url, json=payload, timeout=AudioKeyManager.audio_key_request_timeout) - # If another client instance is already active for this - # SpotiClub user, we will will reply with HTTP 423 and - # instruct this client to wait before retrying. if resp.status_code == 423: try: data = resp.json() @@ -549,7 +538,6 @@ class AudioKeyManager(PacketsReceiver, Closeable): if len(key_bytes) != 16: raise RuntimeError("[SpotiClub API] Woops, received Audio Key must be 16 bytes long") - # After the first successful SpotiClub key fetch, enable the loader for future calls. with _spoticlub_audio_key_loader_lock: _spoticlub_audio_key_loader_enabled = True return key_bytes