Centralize polling logic (#390)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 30 Jun 2022 21:38:28 +0000 (23:38 +0200)
committerGitHub <noreply@github.com>
Thu, 30 Jun 2022 21:38:28 +0000 (23:38 +0200)
Move polling logic to player manager

music_assistant/controllers/players.py
music_assistant/models/player.py
music_assistant/models/player_queue.py

index 425780c83233142e4597d491789470a34e531b02..2ac4b3719666b810e0e62e140a607bce35f92ef5 100755 (executable)
@@ -1,9 +1,10 @@
 """Logic to play music from MusicProviders to supported players."""
 from __future__ import annotations
 
+import asyncio
 from typing import TYPE_CHECKING, Dict, Tuple
 
-from music_assistant.models.enums import EventType
+from music_assistant.models.enums import EventType, PlayerState
 from music_assistant.models.errors import AlreadyRegisteredError
 from music_assistant.models.event import MassEvent
 from music_assistant.models.player import Player
@@ -13,9 +14,6 @@ if TYPE_CHECKING:
     from music_assistant.mass import MusicAssistant
 
 
-DB_TABLE = "queue_settings"
-
-
 class PlayerController:
     """Controller holding all logic to play music from MusicProviders to supported players."""
 
@@ -28,7 +26,7 @@ class PlayerController:
 
     async def setup(self) -> None:
         """Async initialize of module."""
-        # nothing to setup (yet)
+        self.mass.create_task(self._poll_players())
 
     async def cleanup(self) -> None:
         """Cleanup on exit."""
@@ -91,3 +89,20 @@ class PlayerController:
         self.mass.signal_event(
             MassEvent(EventType.PLAYER_ADDED, object_id=player.player_id, data=player)
         )
+
+    async def _poll_players(self) -> None:
+        """Poll players every X interval."""
+        interval = 30
+        cur_tick = 0
+        while True:
+            for player in self.players:
+                if cur_tick == interval or player.state in (
+                    PlayerState.PLAYING,
+                    PlayerState.PAUSED,
+                ):
+                    player.update_state()
+            if cur_tick == interval:
+                cur_tick = 0
+            else:
+                cur_tick += 1
+            await asyncio.sleep(1)
index a800302526179ea32ab1d3969178463ac6f86fa5..43ee4c1efe9cbd66de50f5f53b9b27616537dce6 100755 (executable)
@@ -270,6 +270,9 @@ class Player(ABC):
         """Toggle power on player."""
         await self.power(not self.powered)
 
+    def on_update(self) -> None:
+        """Call when player is about to be updated in the player manager."""
+
     def on_child_update(self, player_id: str, changed_keys: set) -> None:
         """Call when one of the child players of a playergroup updates."""
         self.update_state(skip_forward=True)
@@ -296,9 +299,12 @@ class Player(ABC):
         if self.mass is None or self.mass.closed:
             # guard
             return
+        self.on_update()
         # basic throttle: do not send state changed events if player did not change
         cur_state = self.to_dict()
-        changed_keys = get_changed_keys(self._prev_state, cur_state)
+        changed_keys = get_changed_keys(
+            self._prev_state, cur_state, ignore_keys=["elapsed_time"]
+        )
 
         # always update the playerqueue
         self.mass.players.get_player_queue(self.player_id).on_player_update()
@@ -307,10 +313,9 @@ class Player(ABC):
             return
 
         self._prev_state = cur_state
-        if changed_keys != {"elapsed_time"}:
-            self.mass.signal_event(
-                MassEvent(EventType.PLAYER_UPDATED, object_id=self.player_id, data=self)
-            )
+        self.mass.signal_event(
+            MassEvent(EventType.PLAYER_UPDATED, object_id=self.player_id, data=self)
+        )
 
         if skip_forward:
             return
index e6b25f531e5266e4e33a128c517f9d2e9b5be80d..ea3e5b8e40394bace1ca0f8cff4fbe1c8adff368 100644 (file)
@@ -5,7 +5,7 @@ import asyncio
 import os
 import pathlib
 import random
-from asyncio import Task, TimerHandle
+from asyncio import TimerHandle
 from dataclasses import dataclass
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
 
@@ -62,7 +62,6 @@ class PlayerQueue:
         self._last_state = str
         self._items: List[QueueItem] = []
         self._save_task: TimerHandle = None
-        self._update_task: Task = None
         self._last_player_update: int = 0
         self._last_stream_id: str = ""
         self._snapshot: Optional[QueueSnapShot] = None
@@ -595,20 +594,6 @@ class PlayerQueue:
                     self.signal_next = False
                     self.mass.create_task(self.resume())
 
-            # start poll/updater task if playback starts on player
-            async def updater() -> None:
-                """Update player queue every second while playing."""
-                while True:
-                    await asyncio.sleep(1)
-                    self.update_state()
-
-            if self.player.state == PlayerState.PLAYING and self.active:
-                if not self._update_task or self._update_task.done():
-                    self._update_task = self.mass.create_task(updater)
-            elif self._update_task:
-                self._update_task.cancel()
-                self._update_task = None
-
         self.update_state()
 
     def update_state(self) -> None: