From: Marcel van der Veldt Date: Wed, 25 Feb 2026 13:24:46 +0000 (+0100) Subject: Add play action in progress signal X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=7405a33234330756c6406cd96c6c7cbb96090324;p=music-assistant-server.git Add play action in progress signal To inform frontend that a play media action is processing --- diff --git a/music_assistant/constants.py b/music_assistant/constants.py index 351cbd2b..6c3bf3a8 100644 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -990,6 +990,7 @@ ATTR_SUPPORTED_FEATURES: Final[str] = "supported_features" ATTR_MUTE_CONTROL: Final[str] = "mute_control" ATTR_VOLUME_CONTROL: Final[str] = "volume_control" ATTR_POWER_CONTROL: Final[str] = "power_control" +ATTR_PLAY_ACTION_IN_PROGRESS: Final[str] = "play_action_in_progress" # Album type detection patterns LIVE_INDICATORS = [ diff --git a/music_assistant/controllers/player_queues.py b/music_assistant/controllers/player_queues.py index c5d00a02..b0209d6d 100644 --- a/music_assistant/controllers/player_queues.py +++ b/music_assistant/controllers/player_queues.py @@ -14,11 +14,13 @@ but it can also be something else, hence the loose coupling. from __future__ import annotations import asyncio +import functools import random import time +from collections.abc import Awaitable, Callable, Coroutine from contextlib import suppress from types import NoneType -from typing import TYPE_CHECKING, Any, TypedDict, cast +from typing import TYPE_CHECKING, Any, Concatenate, TypedDict, cast import shortuuid from music_assistant_models.config_entries import ConfigEntry, ConfigValueOption, ConfigValueType @@ -65,6 +67,7 @@ from music_assistant_models.queue_item import QueueItem from music_assistant.constants import ( ATTR_ACTIVE_PLAYLIST, ATTR_ANNOUNCEMENT_IN_PROGRESS, + ATTR_PLAY_ACTION_IN_PROGRESS, MASS_LOGO_ONLINE, PLAYBACK_REPORT_INTERVAL_SECONDS, PLAYLIST_MEDIA_TYPES, @@ -111,6 +114,41 @@ CACHE_CATEGORY_PLAYER_QUEUE_STATE = 0 CACHE_CATEGORY_PLAYER_QUEUE_ITEMS = 1 +def handle_play_action[PlayerQueuesControllerT: "PlayerQueuesController", **P, R]( + func: Callable[Concatenate[PlayerQueuesControllerT, P], Awaitable[R]], +) -> Callable[Concatenate[PlayerQueuesControllerT, P], Coroutine[Any, Any, R]]: + """ + Decorator to mark a play action in progress on the queue. + + Fetches the queue based on queue_id, sets ATTR_PLAY_ACTION_IN_PROGRESS + to True before calling the function, and removes it after the function completes. + + :param func: The function to wrap. + """ # noqa: D401 + + @functools.wraps(func) + async def wrapper(self: PlayerQueuesControllerT, *args: P.args, **kwargs: P.kwargs) -> R: + """Execute function with play action flag set.""" + queue_id = kwargs.get("queue_id") or args[0] + assert isinstance(queue_id, str) # for type checking + queue = self._queues.get(queue_id) + if queue is None: + # Queue not found, just call the function and let it handle the error + return await func(self, *args, **kwargs) + try: + has_flag = bool(queue.extra_attributes.get(ATTR_PLAY_ACTION_IN_PROGRESS)) + if not has_flag: + queue.extra_attributes[ATTR_PLAY_ACTION_IN_PROGRESS] = True + self.signal_update(queue_id) + return await func(self, *args, **kwargs) + finally: + if not has_flag: + queue.extra_attributes.pop(ATTR_PLAY_ACTION_IN_PROGRESS, None) + self.signal_update(queue_id) + + return wrapper + + class CompareState(TypedDict): """Simple object where we store the (previous) state of a queue. @@ -424,6 +462,7 @@ class PlayerQueuesController(CoreController): await self.resume(queue_id) @api_command("player_queues/play_media") + @handle_play_action async def play_media( self, queue_id: str, @@ -981,6 +1020,7 @@ class PlayerQueuesController(CoreController): raise QueueEmpty(msg) @api_command("player_queues/play_index") + @handle_play_action async def play_index( self, queue_id: str,