Fix spotify playback (#269)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Tue, 26 Apr 2022 10:49:18 +0000 (12:49 +0200)
committerGitHub <noreply@github.com>
Tue, 26 Apr 2022 10:49:18 +0000 (12:49 +0200)
* Fix Spotify playback issues

* add check if cache dir still exists

17 files changed:
music_assistant/controllers/stream.py
music_assistant/helpers/process.py
music_assistant/providers/spotify/__init__.py
music_assistant/providers/spotify/librespot/linux/librespot-aarch64 [new file with mode: 0755]
music_assistant/providers/spotify/librespot/linux/librespot-arm [new file with mode: 0755]
music_assistant/providers/spotify/librespot/linux/librespot-armhf [new file with mode: 0755]
music_assistant/providers/spotify/librespot/linux/librespot-armv7 [new file with mode: 0755]
music_assistant/providers/spotify/librespot/linux/librespot-x86_64 [new file with mode: 0755]
music_assistant/providers/spotify/librespot/osx/librespot [new file with mode: 0755]
music_assistant/providers/spotify/librespot/windows/librespot.exe [new file with mode: 0755]
music_assistant/providers/spotify/spotty/linux/spotty-aarch64 [deleted file]
music_assistant/providers/spotify/spotty/linux/spotty-arm [deleted file]
music_assistant/providers/spotify/spotty/linux/spotty-armhf [deleted file]
music_assistant/providers/spotify/spotty/linux/spotty-armv7 [deleted file]
music_assistant/providers/spotify/spotty/linux/spotty-x86_64 [deleted file]
music_assistant/providers/spotify/spotty/osx/spotty [deleted file]
music_assistant/providers/spotify/spotty/windows/spotty.exe [deleted file]

index ceb99c3c976f998442768bcfecff825e8269e9ad..3c482ab0da256a66094e2476f687796932755e6a 100644 (file)
@@ -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()):
index f0f2ea201a55c6ff1450ad742bb549a046a467c5..2e149d0f4b05ece7db2d9b8c776e998637399a86 100644 (file)
@@ -14,7 +14,7 @@ from async_timeout import timeout as _timeout
 
 LOGGER = logging.getLogger("AsyncProcess")
 
-DEFAULT_CHUNKSIZE = 512000
+DEFAULT_CHUNKSIZE = 64000
 DEFAULT_TIMEOUT = 120
 
 
index fda1133965efea5d8d5dc3e4079166afbb16a211..614a84e5f11ae66715721ab33511d432c59d5244 100644 (file)
@@ -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 (executable)
index 0000000..5359098
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 (executable)
index 0000000..5cd38c7
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 (executable)
index 0000000..18c2e05
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 (executable)
index 0000000..0a792b2
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 (executable)
index 0000000..e025abd
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 (executable)
index 0000000..c1b3754
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 (executable)
index 0000000..a973f4e
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 (executable)
index 5359098..0000000
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 (executable)
index 5cd38c7..0000000
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 (executable)
index 18c2e05..0000000
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 (executable)
index 0a792b2..0000000
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 (executable)
index e025abd..0000000
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 (executable)
index c1b3754..0000000
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 (executable)
index a973f4e..0000000
Binary files a/music_assistant/providers/spotify/spotty/windows/spotty.exe and /dev/null differ