Fix Spotify Premium Account Audio Key Retrieval
Some checks failed
CodeQL / Analyze (python) (push) Has been cancelled
Some checks failed
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
@@ -53,7 +53,7 @@ spoticlub_client_serial: typing.Optional[str] = None
|
|||||||
spoticlub_loaded_logged: bool = False
|
spoticlub_loaded_logged: bool = False
|
||||||
_spoticlub_audio_key_loader_enabled: bool = False
|
_spoticlub_audio_key_loader_enabled: bool = False
|
||||||
_spoticlub_audio_key_loader_lock = threading.Lock()
|
_spoticlub_audio_key_loader_lock = threading.Lock()
|
||||||
########################################
|
######################################################
|
||||||
|
|
||||||
def _spoticlub_notify_session_done() -> None:
|
def _spoticlub_notify_session_done() -> None:
|
||||||
global spoticlub_user, spoticlub_password, spoticlub_client_serial
|
global spoticlub_user, spoticlub_password, spoticlub_client_serial
|
||||||
@@ -289,6 +289,7 @@ class AbsChunkedInputStream(io.BytesIO, HaltListener):
|
|||||||
|
|
||||||
class AudioKeyManager(PacketsReceiver, Closeable):
|
class AudioKeyManager(PacketsReceiver, Closeable):
|
||||||
audio_key_request_timeout = 20
|
audio_key_request_timeout = 20
|
||||||
|
max_spotify_audio_key_retries = 2
|
||||||
logger = logging.getLogger("Librespot:AudioKeyManager")
|
logger = logging.getLogger("Librespot:AudioKeyManager")
|
||||||
__callbacks: typing.Dict[int, Callback] = {}
|
__callbacks: typing.Dict[int, Callback] = {}
|
||||||
__seq_holder = 0
|
__seq_holder = 0
|
||||||
@@ -347,43 +348,88 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _get_spotify_audio_key(self, gid: bytes, file_id: bytes, retry: bool = True) -> bytes:
|
def _get_spotify_audio_key(
|
||||||
"""Request the audio key directly from Spotify (Premium accounts)."""
|
self,
|
||||||
tries = 0
|
gid: bytes,
|
||||||
|
file_id: bytes,
|
||||||
|
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
|
||||||
|
|
||||||
|
callback = AudioKeyManager.SyncCallback(self)
|
||||||
|
AudioKeyManager.__callbacks[seq] = callback
|
||||||
|
|
||||||
last_err: typing.Optional[Exception] = None
|
last_err: typing.Optional[Exception] = None
|
||||||
|
|
||||||
while True:
|
try:
|
||||||
tries += 1
|
out = io.BytesIO()
|
||||||
callback = AudioKeyManager.SyncCallback(self)
|
out.write(file_id)
|
||||||
|
out.write(gid)
|
||||||
|
out.write(struct.pack(">i", seq))
|
||||||
|
out.write(self.__zero_short)
|
||||||
|
out.seek(0)
|
||||||
|
|
||||||
with self.__seq_holder_lock:
|
# Send the key request to Spotify.
|
||||||
AudioKeyManager.__seq_holder += 1
|
self.__session.send(Packet.Type.request_key, out.read())
|
||||||
seq = AudioKeyManager.__seq_holder
|
|
||||||
AudioKeyManager.__callbacks[seq] = callback
|
|
||||||
|
|
||||||
try:
|
key = callback.wait_response()
|
||||||
payload = struct.pack(">i", seq) + gid + file_id
|
if key is not None:
|
||||||
self.__session.send(Packet.Type.request_key, payload)
|
|
||||||
|
|
||||||
key = callback.wait_response()
|
|
||||||
if key is None:
|
|
||||||
raise RuntimeError("Audio key request failed")
|
|
||||||
return key
|
return key
|
||||||
except Exception as exc: # noqa: BLE001
|
|
||||||
last_err = exc
|
|
||||||
self.logger.warning("Spotify audio key request failed (try %d): %s", tries, exc)
|
|
||||||
if not retry or tries >= 3:
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
finally:
|
|
||||||
try:
|
|
||||||
AudioKeyManager.__callbacks.pop(seq, None)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
# 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",
|
||||||
|
_attempt,
|
||||||
|
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,
|
||||||
|
file_id,
|
||||||
|
retry=True,
|
||||||
|
_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,
|
||||||
|
util.bytes_to_hex(gid),
|
||||||
|
util.bytes_to_hex(file_id),
|
||||||
|
last_err,
|
||||||
|
)
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Failed fetching Audio Key from Spotify for gid: {}, fileId: {} (last error: {})".format(
|
"Failed fetching Audio Key from Spotify for gid: {}, fileId: {} (last error: {})".format(
|
||||||
util.bytes_to_hex(gid), util.bytes_to_hex(file_id), last_err
|
util.bytes_to_hex(gid),
|
||||||
|
util.bytes_to_hex(file_id),
|
||||||
|
last_err,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user