MASS_LOGO_ONLINE,
)
from music_assistant.helpers.api import api_command
-from music_assistant.helpers.audio import get_stream_details
+from music_assistant.helpers.audio import get_stream_details, get_stream_dsp_details
from music_assistant.helpers.throttle_retry import BYPASS_THROTTLER
from music_assistant.helpers.util import get_changed_keys
from music_assistant.models.core_controller import CoreController
elapsed_time: int
stream_title: str | None
content_type: str | None
+ group_childs_count: int
class PlayerQueuesController(CoreController):
next_item_id=None,
elapsed_time=0,
stream_title=None,
+ group_childs_count=0,
),
)
content_type=queue.current_item.streamdetails.audio_format.output_format_str
if queue.current_item and queue.current_item.streamdetails
else None,
+ group_childs_count=len(player.group_childs),
)
changed_keys = get_changed_keys(prev_state, new_state)
# return early if nothing changed
else:
self._prev_states.pop(queue_id, None)
+ if "group_childs_count" in changed_keys:
+ # refresh DSP details since a player has been added/removed from the group
+ dsp = get_stream_dsp_details(self.mass, queue_id)
+ if queue.current_item and queue.current_item.streamdetails:
+ queue.current_item.streamdetails.dsp = dsp
+ if queue.next_item and queue.next_item.streamdetails:
+ queue.next_item.streamdetails.dsp = dsp
+
# detect change in current index to report that a item has been played
prev_item_id = prev_state["current_item_id"]
player_stopped = (
import aiofiles
from aiohttp import ClientTimeout
+from music_assistant_models.dsp import DSPConfig, DSPDetails, DSPState
from music_assistant_models.enums import (
ContentType,
MediaType,
if TYPE_CHECKING:
from music_assistant_models.config_entries import CoreConfig, PlayerConfig
+ from music_assistant_models.player import Player
from music_assistant_models.player_queue import QueueItem
from music_assistant_models.streamdetails import StreamDetails
return stripped_data
+def get_player_dsp_details(mass: MusicAssistant, player: Player) -> DSPDetails:
+ """Return DSP details of single a player."""
+ dsp_config = mass.config.get_player_dsp_config(player.player_id)
+ dsp_state = DSPState.ENABLED if dsp_config.enabled else DSPState.DISABLED
+ if dsp_state == DSPState.ENABLED and is_grouping_preventing_dsp(player):
+ dsp_state = DSPState.DISABLED_BY_UNSUPPORTED_GROUP
+ dsp_config = DSPConfig(enabled=False)
+
+ # remove disabled filters
+ dsp_config.filters = [x for x in dsp_config.filters if x.enabled]
+
+ return DSPDetails(
+ state=dsp_state,
+ input_gain=dsp_config.input_gain,
+ filters=dsp_config.filters,
+ output_gain=dsp_config.output_gain,
+ output_limiter=dsp_config.output_limiter,
+ )
+
+
+def get_stream_dsp_details(
+ mass: MusicAssistant,
+ queue_id: str,
+) -> dict[str, DSPDetails]:
+ """Return DSP details of all players playing this queue, keyed by player_id."""
+ player = mass.players.get(queue_id)
+ dsp = {}
+
+ # We skip the PlayerGroups as they don't provide an audio output
+ # by themselves, but only sync other players.
+ if not player.provider.startswith("player_group"):
+ details = get_player_dsp_details(mass, player)
+ details.is_leader = True
+ dsp[player.player_id] = details
+
+ if player and player.group_childs:
+ # grouped playback, get DSP details for each player in the group
+ for child_id in player.group_childs:
+ if child_player := mass.players.get(child_id):
+ dsp[child_id] = get_player_dsp_details(mass, child_player)
+ return dsp
+
+
async def get_stream_details(
mass: MusicAssistant,
queue_item: QueueItem,
core_config, player_settings, streamdetails
)
+ # attach the DSP details of all group members
+ streamdetails.dsp = get_stream_dsp_details(mass, streamdetails.queue_id)
+
process_time = int((time.time() - time_start) * 1000)
LOGGER.debug(
"retrieved streamdetails for %s in %s milliseconds",
return int((320000 / 8) * seconds)
+def is_grouping_preventing_dsp(player: Player) -> bool:
+ """Check if grouping is preventing DSP from being applied.
+
+ If this returns True, no DSP should be applied to the player.
+ """
+ is_grouped = bool(player.synced_to) or bool(player.group_childs)
+ multi_device_dsp_supported = PlayerFeature.MULTI_DEVICE_DSP in player.supported_features
+ return is_grouped and not multi_device_dsp_supported
+
+
def get_player_filter_params(
mass: MusicAssistant,
player_id: str,
dsp = mass.config.get_player_dsp_config(player_id)
if player := mass.players.get(player_id):
- is_grouped = bool(player.synced_to) or bool(player.group_childs)
- if is_grouped and PlayerFeature.MULTI_DEVICE_DSP not in player.supported_features:
+ if is_grouping_preventing_dsp(player):
# We can not correctly apply DSP to a grouped player without multi-device DSP support,
# so we disable it.
dsp.enabled = False