From: Marcel van der Veldt Date: Tue, 26 Apr 2022 10:49:18 +0000 (+0200) Subject: Fix spotify playback (#269) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=ff3559d952b36b75e30e42daecb3a35f8f2a8969;p=music-assistant-server.git Fix spotify playback (#269) * Fix Spotify playback issues * add check if cache dir still exists --- diff --git a/music_assistant/controllers/stream.py b/music_assistant/controllers/stream.py index ceb99c3c..3c482ab0 100644 --- a/music_assistant/controllers/stream.py +++ b/music_assistant/controllers/stream.py @@ -154,8 +154,7 @@ class StreamController: self.mass.create_task(writer) # read bytes from final output - chunksize = 32000 if output_fmt == ContentType.MP3 else 90000 - async for audio_chunk in sox_proc.iterate_chunks(chunksize): + async for audio_chunk in sox_proc.iterate_chunks(): await resp.write(audio_chunk) return resp @@ -305,7 +304,7 @@ class StreamController: async def reader(): """Read bytes from final output and put chunk on child queues.""" chunks_sent = 0 - async for chunk in sox_proc.iterate_chunks(256000): + async for chunk in sox_proc.iterate_chunks(): chunks_sent += 1 coros = [] for player_id in list(self._client_queues[queue_id].keys()): diff --git a/music_assistant/helpers/process.py b/music_assistant/helpers/process.py index f0f2ea20..2e149d0f 100644 --- a/music_assistant/helpers/process.py +++ b/music_assistant/helpers/process.py @@ -14,7 +14,7 @@ from async_timeout import timeout as _timeout LOGGER = logging.getLogger("AsyncProcess") -DEFAULT_CHUNKSIZE = 512000 +DEFAULT_CHUNKSIZE = 64000 DEFAULT_TIMEOUT = 120 diff --git a/music_assistant/providers/spotify/__init__.py b/music_assistant/providers/spotify/__init__.py index fda11339..614a84e5 100644 --- a/music_assistant/providers/spotify/__init__.py +++ b/music_assistant/providers/spotify/__init__.py @@ -7,6 +7,7 @@ import os import platform import time from json.decoder import JSONDecodeError +from tempfile import gettempdir from typing import List, Optional import aiohttp @@ -33,6 +34,8 @@ from music_assistant.models.media_items import ( ) from music_assistant.models.provider import MusicProvider +CACHE_DIR = gettempdir() + class SpotifyProvider(MusicProvider): """Implementation of a Spotify MusicProvider.""" @@ -52,6 +55,7 @@ class SpotifyProvider(MusicProvider): self._password = password self._auth_token = None self._sp_user = None + self._librespot_bin = None self._throttler = Throttler(rate_limit=4, period=1) async def setup(self) -> None: @@ -266,13 +270,13 @@ class SpotifyProvider(MusicProvider): return None # make sure that the token is still valid by just requesting it await self.get_token() - spotty = await self.get_spotty_binary() - spotty_exec = f'{spotty} -n temp -c "/tmp" -b 320 --single-track --pass-through spotify://track:{track.item_id}' + librespot = await self.get_librespot_binary() + librespot_exec = f'{librespot} -c "{CACHE_DIR}" --pass-through -b 320 --single-track spotify://track:{track.item_id}' return StreamDetails( type=StreamType.EXECUTABLE, item_id=track.item_id, provider=self.id, - path=spotty_exec, + path=librespot_exec, content_type=ContentType.OGG, sample_rate=44100, bit_depth=16, @@ -412,12 +416,16 @@ class SpotifyProvider(MusicProvider): async def get_token(self): """Get auth token on spotify.""" # return existing token if we have one in memory - if self._auth_token and (self._auth_token["expiresAt"] > int(time.time()) + 20): + if ( + self._auth_token + and os.path.isdir(CACHE_DIR) + and (self._auth_token["expiresAt"] > int(time.time()) + 20) + ): return self._auth_token tokeninfo = {} if not self._username or not self._password: return tokeninfo - # retrieve token with spotty + # retrieve token with librespot tokeninfo = await self._get_token() if tokeninfo: self._auth_token = tokeninfo @@ -431,8 +439,22 @@ class SpotifyProvider(MusicProvider): return tokeninfo async def _get_token(self): - """Get spotify auth token with spotty bin.""" - # get token with spotty + """Get spotify auth token with librespot bin.""" + # authorize with username and password (NOTE: this can also be Spotify Connect) + args = [ + await self.get_librespot_binary(), + "-O", + "-c", + CACHE_DIR, + "-a", + "-u", + self._username, + "-p", + self._password, + ] + librespot = await asyncio.create_subprocess_exec(*args) + await librespot.wait() + # get token with (authorized) librespot scopes = [ "user-read-playback-state", "user-read-currently-playing", @@ -452,26 +474,20 @@ class SpotifyProvider(MusicProvider): ] scope = ",".join(scopes) args = [ - await self.get_spotty_binary(), + await self.get_librespot_binary(), + "-O", "-t", "--client-id", get_app_var(2), "--scope", scope, - "-n", - "temp-spotty", - "-u", - self._username, - "-p", - self._password, "-c", - "/tmp", - "--disable-discovery", + CACHE_DIR, ] - spotty = await asyncio.create_subprocess_exec( + librespot = await asyncio.create_subprocess_exec( *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT ) - stdout, _ = await spotty.communicate() + stdout, _ = await librespot.communicate() try: result = json.loads(stdout) except JSONDecodeError: @@ -577,37 +593,60 @@ class SpotifyProvider(MusicProvider): ) as response: return await response.text() - @staticmethod - async def get_spotty_binary(): - """Find the correct spotty binary belonging to the platform.""" + async def get_librespot_binary(self): + """Find the correct librespot binary belonging to the platform.""" + if self._librespot_bin is not None: + return self._librespot_bin + + async def check_librespot(librespot_path: str) -> str | None: + try: + librespot = await asyncio.create_subprocess_exec( + *[librespot_path, "-V"], stdout=asyncio.subprocess.PIPE + ) + stdout, _ = await librespot.communicate() + if librespot.returncode == 0 and b"librespot" in stdout: + self._librespot_bin = librespot_path + return librespot_path + except OSError: + return None + + base_path = os.path.join(os.path.dirname(__file__), "librespot") if platform.system() == "Windows": - return os.path.join( - os.path.dirname(__file__), "spotty", "windows", "spotty.exe" - ) + if librespot := await check_librespot( + os.path.join(base_path, "windows", "librespot.exe") + ): + return librespot if platform.system() == "Darwin": # macos binary is x86_64 intel - return os.path.join(os.path.dirname(__file__), "spotty", "osx", "spotty") + if librespot := await check_librespot( + os.path.join(base_path, "osx", "librespot") + ): + return librespot if platform.system() == "Linux": architecture = platform.machine() if architecture in ["AMD64", "x86_64"]: # generic linux x86_64 binary - return os.path.join( - os.path.dirname(__file__), "spotty", "linux", "spotty-x86_64" - ) + if librespot := await check_librespot( + os.path.join( + base_path, + "linux", + "librespot-x86_64", + ) + ): + return librespot # arm architecture... try all options one by one... for arch in ["aarch64", "armv7", "armhf", "arm"]: - spotty_path = os.path.join( - os.path.dirname(__file__), "spotty", "linux", f"spotty-{arch}" - ) - try: - spotty = await asyncio.create_subprocess_exec( - *[spotty_path, "-V"], stdout=asyncio.subprocess.PIPE + if librespot := await check_librespot( + os.path.join( + base_path, + "linux", + f"librespot-{arch}", ) - stdout, _ = await spotty.communicate() - if spotty.returncode == 0 and b"librespot" in stdout: - return spotty_path - except OSError: - pass - return None + ): + return librespot + + raise RuntimeError( + f"Unable to locate Libespot for platform {platform.system()}" + ) diff --git a/music_assistant/providers/spotify/librespot/linux/librespot-aarch64 b/music_assistant/providers/spotify/librespot/linux/librespot-aarch64 new file mode 100755 index 00000000..5359098f Binary files /dev/null and b/music_assistant/providers/spotify/librespot/linux/librespot-aarch64 differ diff --git a/music_assistant/providers/spotify/librespot/linux/librespot-arm b/music_assistant/providers/spotify/librespot/linux/librespot-arm new file mode 100755 index 00000000..5cd38c75 Binary files /dev/null and b/music_assistant/providers/spotify/librespot/linux/librespot-arm differ diff --git a/music_assistant/providers/spotify/librespot/linux/librespot-armhf b/music_assistant/providers/spotify/librespot/linux/librespot-armhf new file mode 100755 index 00000000..18c2e05b Binary files /dev/null and b/music_assistant/providers/spotify/librespot/linux/librespot-armhf differ diff --git a/music_assistant/providers/spotify/librespot/linux/librespot-armv7 b/music_assistant/providers/spotify/librespot/linux/librespot-armv7 new file mode 100755 index 00000000..0a792b2e Binary files /dev/null and b/music_assistant/providers/spotify/librespot/linux/librespot-armv7 differ diff --git a/music_assistant/providers/spotify/librespot/linux/librespot-x86_64 b/music_assistant/providers/spotify/librespot/linux/librespot-x86_64 new file mode 100755 index 00000000..e025abdb Binary files /dev/null and b/music_assistant/providers/spotify/librespot/linux/librespot-x86_64 differ diff --git a/music_assistant/providers/spotify/librespot/osx/librespot b/music_assistant/providers/spotify/librespot/osx/librespot new file mode 100755 index 00000000..c1b37543 Binary files /dev/null and b/music_assistant/providers/spotify/librespot/osx/librespot differ diff --git a/music_assistant/providers/spotify/librespot/windows/librespot.exe b/music_assistant/providers/spotify/librespot/windows/librespot.exe new file mode 100755 index 00000000..a973f4e1 Binary files /dev/null and b/music_assistant/providers/spotify/librespot/windows/librespot.exe differ diff --git a/music_assistant/providers/spotify/spotty/linux/spotty-aarch64 b/music_assistant/providers/spotify/spotty/linux/spotty-aarch64 deleted file mode 100755 index 5359098f..00000000 Binary files a/music_assistant/providers/spotify/spotty/linux/spotty-aarch64 and /dev/null differ diff --git a/music_assistant/providers/spotify/spotty/linux/spotty-arm b/music_assistant/providers/spotify/spotty/linux/spotty-arm deleted file mode 100755 index 5cd38c75..00000000 Binary files a/music_assistant/providers/spotify/spotty/linux/spotty-arm and /dev/null differ diff --git a/music_assistant/providers/spotify/spotty/linux/spotty-armhf b/music_assistant/providers/spotify/spotty/linux/spotty-armhf deleted file mode 100755 index 18c2e05b..00000000 Binary files a/music_assistant/providers/spotify/spotty/linux/spotty-armhf and /dev/null differ diff --git a/music_assistant/providers/spotify/spotty/linux/spotty-armv7 b/music_assistant/providers/spotify/spotty/linux/spotty-armv7 deleted file mode 100755 index 0a792b2e..00000000 Binary files a/music_assistant/providers/spotify/spotty/linux/spotty-armv7 and /dev/null differ diff --git a/music_assistant/providers/spotify/spotty/linux/spotty-x86_64 b/music_assistant/providers/spotify/spotty/linux/spotty-x86_64 deleted file mode 100755 index e025abdb..00000000 Binary files a/music_assistant/providers/spotify/spotty/linux/spotty-x86_64 and /dev/null differ diff --git a/music_assistant/providers/spotify/spotty/osx/spotty b/music_assistant/providers/spotify/spotty/osx/spotty deleted file mode 100755 index c1b37543..00000000 Binary files a/music_assistant/providers/spotify/spotty/osx/spotty and /dev/null differ diff --git a/music_assistant/providers/spotify/spotty/windows/spotty.exe b/music_assistant/providers/spotify/spotty/windows/spotty.exe deleted file mode 100755 index a973f4e1..00000000 Binary files a/music_assistant/providers/spotify/spotty/windows/spotty.exe and /dev/null differ