throttle commands to chromecast socket
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 10 Oct 2020 21:25:46 +0000 (23:25 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 10 Oct 2020 21:25:46 +0000 (23:25 +0200)
music_assistant/providers/chromecast/player.py

index f2d718fff8128912f3572665c5efc4a999e8137f..7349a15a46b5c66894584e04216fea93e5df9796 100644 (file)
@@ -5,6 +5,7 @@ from datetime import datetime
 from typing import List, Optional
 
 import pychromecast
+from asyncio_throttle import Throttler
 from music_assistant.helpers.typing import MusicAssistantType
 from music_assistant.helpers.util import async_yield_chunks, compare_strings
 from music_assistant.models.config_entry import ConfigEntry
@@ -52,6 +53,7 @@ class ChromecastPlayer(Player):
         self._available = False
         self._status_listener: Optional[CastStatusListener] = None
         self._is_speaker_group = False
+        self._throttler = Throttler(rate_limit=2, period=1)
 
     @property
     def player_id(self) -> str:
@@ -195,8 +197,8 @@ class ChromecastPlayer(Player):
         if self._chromecast and not self._available:
             try:
                 self.disconnect()
-            except Exception as exc:  # pylint: disable=broad-except
-                LOGGER.exception(exc)
+            except Exception:  # pylint: disable=broad-except
+                pass
         elif self._chromecast is not None:
             return
         LOGGER.debug(
@@ -298,7 +300,7 @@ class ChromecastPlayer(Player):
             self._available = new_available
             self.update_state()
             if self._cast_info.is_audio_group and new_available:
-                self.try_chromecast_command(
+                self.__try_chromecast_command(
                     self._chromecast.mz_controller.update_members
                 )
 
@@ -308,7 +310,9 @@ class ChromecastPlayer(Player):
             "group_player"
         ):
             # the group player wants very accurate elapsed_time state so we request it very often
-            self.try_chromecast_command(self._chromecast.media_controller.update_status)
+            await self.__async_try_chromecast_command(
+                self._chromecast.media_controller.update_status
+            )
         self.update_state()
 
     # ========== Service Calls ==========
@@ -316,31 +320,43 @@ class ChromecastPlayer(Player):
     async def async_cmd_stop(self) -> None:
         """Send stop command to player."""
         if self._chromecast and self._chromecast.media_controller:
-            self.try_chromecast_command(self._chromecast.media_controller.stop)
+            await self.__async_try_chromecast_command(
+                self._chromecast.media_controller.stop
+            )
 
     async def async_cmd_play(self) -> None:
         """Send play command to player."""
         if self._chromecast.media_controller:
-            self.try_chromecast_command(self._chromecast.media_controller.play)
+            await self.__async_try_chromecast_command(
+                self._chromecast.media_controller.play
+            )
 
     async def async_cmd_pause(self) -> None:
         """Send pause command to player."""
         if self._chromecast.media_controller:
-            self.try_chromecast_command(self._chromecast.media_controller.pause)
+            await self.__async_try_chromecast_command(
+                self._chromecast.media_controller.pause
+            )
 
     async def async_cmd_next(self) -> None:
         """Send next track command to player."""
         if self._chromecast.media_controller:
-            self.try_chromecast_command(self._chromecast.media_controller.queue_next)
+            await self.__async_try_chromecast_command(
+                self._chromecast.media_controller.queue_next
+            )
 
     async def async_cmd_previous(self) -> None:
         """Send previous track command to player."""
         if self._chromecast.media_controller:
-            self.try_chromecast_command(self._chromecast.media_controller.queue_prev)
+            await self.__async_try_chromecast_command(
+                self._chromecast.media_controller.queue_prev
+            )
 
     async def async_cmd_power_on(self) -> None:
         """Send power ON command to player."""
-        self.try_chromecast_command(self._chromecast.set_volume_muted, False)
+        await self.__async_try_chromecast_command(
+            self._chromecast.set_volume_muted, False
+        )
 
     async def async_cmd_power_off(self) -> None:
         """Send power OFF command to player."""
@@ -349,17 +365,25 @@ class ChromecastPlayer(Player):
             or self.media_status.player_is_paused
             or self.media_status.player_is_idle
         ):
-            self.try_chromecast_command(self._chromecast.media_controller.stop)
+            await self.__async_try_chromecast_command(
+                self._chromecast.media_controller.stop
+            )
         # chromecast has no real poweroff so we send mute instead
-        self.try_chromecast_command(self._chromecast.set_volume_muted, True)
+        await self.__async_try_chromecast_command(
+            self._chromecast.set_volume_muted, True
+        )
 
     async def async_cmd_volume_set(self, volume_level: int) -> None:
         """Send new volume level command to player."""
-        self.try_chromecast_command(self._chromecast.set_volume, volume_level / 100)
+        await self.__async_try_chromecast_command(
+            self._chromecast.set_volume, volume_level / 100
+        )
 
     async def async_cmd_volume_mute(self, is_muted: bool = False) -> None:
         """Send mute command to player."""
-        self.try_chromecast_command(self._chromecast.set_volume_muted, is_muted)
+        await self.__async_try_chromecast_command(
+            self._chromecast.set_volume_muted, is_muted
+        )
 
     async def async_cmd_play_uri(self, uri: str) -> None:
         """Play single uri on player."""
@@ -370,7 +394,9 @@ class ChromecastPlayer(Player):
             queue_item.name = "Music Assistant"
             queue_item.uri = uri
             return await self.async_cmd_queue_load([queue_item, queue_item])
-        self.try_chromecast_command(self._chromecast.play_media, uri, "audio/flac")
+        await self.__async_try_chromecast_command(
+            self._chromecast.play_media, uri, "audio/flac"
+        )
 
     async def async_cmd_queue_load(self, queue_items: List[QueueItem]) -> None:
         """Load (overwrite) queue with new items."""
@@ -385,7 +411,7 @@ class ChromecastPlayer(Player):
             "startIndex": 0,  # Item index to play after this request or keep same item if undefined
             "items": cc_queue_items,  # only load 50 tracks at once or the socket will crash
         }
-        self.try_chromecast_command(self.__send_player_queue, queuedata)
+        await self.__async_try_chromecast_command(self.__send_player_queue, queuedata)
         if len(queue_items) > 50:
             await self.async_cmd_queue_append(queue_items[51:])
 
@@ -398,7 +424,9 @@ class ChromecastPlayer(Player):
                 "insertBefore": None,
                 "items": chunk,
             }
-            self.try_chromecast_command(self.__send_player_queue, queuedata)
+            await self.__async_try_chromecast_command(
+                self.__send_player_queue, queuedata
+            )
 
     def __create_queue_items(self, tracks) -> None:
         """Create list of CC queue items from tracks."""
@@ -454,7 +482,11 @@ class ChromecastPlayer(Player):
         else:
             send_queue()
 
-    def try_chromecast_command(self, func, *args, **kwargs):
+    async def __try_chromecast_command(self, func, *args, **kwargs):
+        """Try to execute Chromecast command."""
+        self.mass.add_job(self.__async_try_chromecast_command(func, *args, **kwargs))
+
+    async def __async_try_chromecast_command(self, func, *args, **kwargs):
         """Try to execute Chromecast command."""
 
         def handle_command(func, *args, **kwarg):
@@ -464,16 +496,20 @@ class ChromecastPlayer(Player):
                 or not self._available
             ):
                 LOGGER.error(
-                    "Error while executing command on player %s: Chromecast is not available!"
+                    "Error while executing command %s on player %s: Chromecast is not available!",
+                    func.__name__,
+                    self.name,
                 )
+                return
             try:
                 return func(*args, **kwargs)
             except (
                 pychromecast.NotConnected,
                 pychromecast.ChromecastConnectionError,
             ) as exc:
-                LOGGER.error(
-                    "Error while executing command on player %s: %s",
+                LOGGER.warning(
+                    "Error while executing command %s on player %s: %s",
+                    func.__name__,
                     self.name,
                     str(exc),
                 )
@@ -481,4 +517,5 @@ class ChromecastPlayer(Player):
             except Exception as exc:  # pylint: disable=broad-except
                 LOGGER.exception(exc)
 
-        self.mass.add_job(handle_command, func, *args, **kwargs)
+        async with self._throttler:
+            self.mass.add_job(handle_command, func, *args, **kwargs)