fix polling
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 8 May 2024 00:23:14 +0000 (02:23 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 8 May 2024 00:23:14 +0000 (02:23 +0200)
music_assistant/common/models/player.py
music_assistant/server/controllers/config.py
music_assistant/server/controllers/players.py
music_assistant/server/models/player_provider.py
music_assistant/server/providers/chromecast/__init__.py
music_assistant/server/providers/dlna/__init__.py
music_assistant/server/providers/fully_kiosk/__init__.py
music_assistant/server/providers/sonos/__init__.py

index bdc786a83b64dec1b7927d01de9663af70be0bc6..d0c37de8aa7e3b74e32cb9e09ffc62d2978fa5b9 100644 (file)
@@ -94,6 +94,13 @@ class Player(DataClassDictMixin):
     # can be used by a player provider to exclude some sort of players
     enabled_by_default: bool = True
 
+    # needs_poll: bool that can be set by the player(provider)
+    # if this player needs to be polled for state changes by the player manager
+    needs_poll: bool = False
+
+    # poll_interval: a (dynamic) interval in seconds to poll the player (used with needs_poll)
+    poll_interval: int = 30
+
     #
     # THE BELOW ATTRIBUTES ARE MANAGED BY CONFIG AND THE PLAYER MANAGER
     #
index 783365c0c342e6ef11c30836bffdaeed9b558752..42b2cf681a7096ad27e8854d5c3e3e6b91b97f7c 100644 (file)
@@ -326,8 +326,11 @@ class ConfigController:
             if include_values
             else PlayerConfig.parse([], raw_conf)
             for raw_conf in list(self.get(CONF_PLAYERS, {}).values())
-            # filter out unavailable providers
-            if raw_conf["provider"] in get_global_cache_value("available_providers", [])
+            # filter out unavailable providers (only if we requested the full info)
+            if (
+                not include_values
+                or raw_conf["provider"] in get_global_cache_value("available_providers", [])
+            )
             # optional provider filter
             and (provider in (None, raw_conf["provider"]))
         ]
@@ -430,6 +433,8 @@ class ConfigController:
         if provider := self.mass.get_provider(existing["provider"]):
             assert isinstance(provider, PlayerProvider)
             provider.on_player_config_removed(player_id)
+        if not player:
+            self.mass.signal_event(EventType.PLAYER_REMOVED, player_id)
 
     def create_default_player_config(
         self,
index 8c05316e84a4014f84836ad482409f6e8f5f5b7e..4020be7ff4d7e46e5bdd57e2ca054b374e201a34 100644 (file)
@@ -996,14 +996,11 @@ class PlayerController(CoreController):
                 if player_playing:
                     self.mass.loop.call_soon(self.update, player_id)
                 # Poll player;
-                # - every 120 seconds if the player if not powered
-                # - every 30 seconds if the player is powered
-                # - every 5 seconds if the player is playing
-                if (
-                    (player.powered and count % 30 == 0)
-                    or (player_playing and count % 5 == 0)
-                    or count % 120 == 0
-                ) and (player_prov := self.get_player_provider(player_id)):
+                if not player.needs_poll:
+                    continue
+                if count % player.poll_interval == 0 and (
+                    player_prov := self.get_player_provider(player_id)
+                ):
                     try:
                         await player_prov.poll_player(player_id)
                     except PlayerUnavailableError:
index bc902c0f899814b3f9b8cf161822058f7f6b9e5f..3d2fec59344cb64f11e2a854b106009e787f5bdb 100644 (file)
@@ -279,16 +279,7 @@ class PlayerProvider(Provider):
         """Poll player for state updates.
 
         This is called by the Player Manager;
-        - every 360 seconds if the player if not powered
-        - every 30 seconds if the player is powered
-        - every 10 seconds if the player is playing
-
-        Use this method to request any info that is not automatically updated and/or
-        to detect if the player is still alive.
-        If this method raises the PlayerUnavailable exception,
-        the player is marked as unavailable until
-        the next successful poll or event where it becomes available again.
-        If the player does not need any polling, simply do not override this method.
+        if 'needs_poll' is set to True in the player object.
         """
 
     def on_child_power(self, player_id: str, child_player_id: str, new_power: bool) -> None:
index aefd42323c45ec2914f6e74545d5963b68e84db6..1a8b5503d3b256586f817c49d4e8ceeaea6ab7b3 100644 (file)
@@ -278,20 +278,7 @@ class ChromecastProvider(PlayerProvider):
         )
 
     async def poll_player(self, player_id: str) -> None:
-        """Poll player for state updates.
-
-        This is called by the Player Manager;
-        - every 360 seconds if the player if not powered
-        - every 30 seconds if the player is powered
-        - every 10 seconds if the player is playing
-
-        Use this method to request any info that is not automatically updated and/or
-        to detect if the player is still alive.
-        If this method raises the PlayerUnavailable exception,
-        the player is marked as unavailable until
-        the next successful poll or event where it becomes available again.
-        If the player does not need any polling, simply do not override this method.
-        """
+        """Poll player for state updates."""
         castplayer = self.castplayers[player_id]
         # only update status of media controller if player is on
         if not castplayer.player.powered:
@@ -300,7 +287,7 @@ class ChromecastProvider(PlayerProvider):
             return
         try:
             now = time.time()
-            if (now - castplayer.last_poll) >= 30:
+            if (now - castplayer.last_poll) >= 60:
                 castplayer.last_poll = now
                 await asyncio.to_thread(castplayer.cc.media_controller.update_status)
             await self.update_flow_metadata(castplayer)
@@ -389,6 +376,7 @@ class ChromecastProvider(PlayerProvider):
                         PlayerFeature.PAUSE,
                     ),
                     enabled_by_default=enabled_by_default,
+                    needs_poll=True,
                 ),
             )
             self.castplayers[player_id] = castplayer
@@ -587,6 +575,7 @@ class ChromecastProvider(PlayerProvider):
     async def update_flow_metadata(self, castplayer: CastPlayer) -> None:
         """Update the metadata of a cast player running the flow stream."""
         if not castplayer.player.powered:
+            castplayer.player.poll_interval = 300
             return
         if not castplayer.cc.media_controller.status.player_is_playing:
             return
@@ -600,6 +589,7 @@ class ChromecastProvider(PlayerProvider):
             return
         if not (queue.flow_mode or current_item.media_type == MediaType.RADIO):
             return
+        castplayer.player.poll_interval = 10
         media_controller = castplayer.cc.media_controller
         # update metadata of current item chromecast
         if media_controller.status.media_custom_data["queue_item_id"] != current_item.queue_item_id:
index 46228bbdb7da1925885b4cf42989b0b08c036ebc..117bb7c640fd0a358b83508b6cb21b3fd5284962 100644 (file)
@@ -392,20 +392,7 @@ class DLNAPlayerProvider(PlayerProvider):
         await dlna_player.device.async_mute_volume(muted)
 
     async def poll_player(self, player_id: str) -> None:
-        """Poll player for state updates.
-
-        This is called by the Player Manager;
-        - every 360 seconds if the player if not powered
-        - every 30 seconds if the player is powered
-        - every 10 seconds if the player is playing
-
-        Use this method to request any info that is not automatically updated and/or
-        to detect if the player is still alive.
-        If this method raises the PlayerUnavailable exception,
-        the player is marked as unavailable until
-        the next successful poll or event where it becomes available again.
-        If the player does not need any polling, simply do not override this method.
-        """
+        """Poll player for state updates."""
         dlna_player = self.dlnaplayers[player_id]
 
         # try to reconnect the device if the connection was lost
@@ -535,6 +522,8 @@ class DLNAPlayerProvider(PlayerProvider):
                             address=description_url,
                             manufacturer="unknown",
                         ),
+                        needs_poll=True,
+                        poll_interval=30,
                     ),
                     description_url=description_url,
                 )
index 62e1ff5656e9eabd58cf21328899f8f823c64f3d..3f6605cd91dc86addbb351590e1b18b2993331ed 100644 (file)
@@ -132,6 +132,8 @@ class FullyKioskProvider(PlayerProvider):
                     address=address,
                 ),
                 supported_features=(PlayerFeature.VOLUME_SET,),
+                needs_poll=True,
+                poll_interval=10,
             )
         self.mass.players.register_or_update(player)
         self._handle_player_update()
@@ -193,20 +195,7 @@ class FullyKioskProvider(PlayerProvider):
         self.mass.players.update(player_id)
 
     async def poll_player(self, player_id: str) -> None:
-        """Poll player for state updates.
-
-        This is called by the Player Manager;
-        - every 360 seconds if the player if not powered
-        - every 30 seconds if the player is powered
-        - every 10 seconds if the player is playing
-
-        Use this method to request any info that is not automatically updated and/or
-        to detect if the player is still alive.
-        If this method raises the PlayerUnavailable exception,
-        the player is marked as unavailable until
-        the next successful poll or event where it becomes available again.
-        If the player does not need any polling, simply do not override this method.
-        """
+        """Poll player for state updates."""
         try:
             async with asyncio.timeout(15):
                 await self._fully.getDeviceInfo()
index e1dfaad0d20f7bcd1294fab47ea83912b9bcf40a..342386f99ca88849dac0927f3bf96fa9dceca467 100644 (file)
@@ -424,20 +424,7 @@ class SonosPlayerProvider(PlayerProvider):
             return
 
     async def poll_player(self, player_id: str) -> None:
-        """Poll player for state updates.
-
-        This is called by the Player Manager;
-        - every 360 seconds if the player if not powered
-        - every 30 seconds if the player is powered
-        - every 10 seconds if the player is playing
-
-        Use this method to request any info that is not automatically updated and/or
-        to detect if the player is still alive.
-        If this method raises the PlayerUnavailable exception,
-        the player is marked as unavailable until
-        the next successful poll or event where it becomes available again.
-        If the player does not need any polling, simply do not override this method.
-        """
+        """Poll player for state updates."""
         if player_id not in self.sonosplayers:
             return
         sonos_player = self.sonosplayers[player_id]
@@ -524,6 +511,8 @@ class SonosPlayerProvider(PlayerProvider):
                     address=soco.ip_address,
                     manufacturer="SONOS",
                 ),
+                needs_poll=True,
+                poll_interval=120,
             )
         self.sonosplayers[player_id] = sonos_player = SonosPlayer(
             self,