Skip to content

Commit

Permalink
Merge branch 'dev' of https://github.com/music-assistant/server into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelveldt committed Feb 10, 2025
2 parents fb4d136 + a4a567d commit 8373ba4
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 16 deletions.
3 changes: 3 additions & 0 deletions music_assistant/controllers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ async def save_player_config(
async def remove_player_config(self, player_id: str) -> None:
"""Remove PlayerConfig."""
conf_key = f"{CONF_PLAYERS}/{player_id}"
dsp_conf_key = f"{CONF_PLAYER_DSP}/{player_id}"
existing = self.get(conf_key)
if not existing:
msg = f"Player configuration for {player_id} does not exist"
Expand Down Expand Up @@ -435,6 +436,8 @@ async def remove_player_config(self, player_id: str) -> None:
self.mass.players.remove(player_id, cleanup_config=False)
# remove the actual config if all of the above passed
self.remove(conf_key)
# Also remove the DSP config if it exists
self.remove(dsp_conf_key)

@api_command("config/players/dsp/get")
def get_player_dsp_config(self, player_id: str) -> DSPConfig:
Expand Down
6 changes: 3 additions & 3 deletions music_assistant/controllers/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ async def serve_queue_item_stream(self, request: web.Request) -> web.Response:
)
raise web.HTTPNotFound(reason=f"No streamdetails for Queue item: {queue_item_id}")
# work out output format/details
output_format = await self._get_output_format(
output_format = await self.get_output_format(
output_format_str=request.match_info["fmt"],
player=queue_player,
default_sample_rate=queue_item.streamdetails.audio_format.sample_rate,
Expand Down Expand Up @@ -385,7 +385,7 @@ async def serve_queue_flow_stream(self, request: web.Request) -> web.Response:
flow_pcm_format = await self._select_flow_format(queue_player)

# work out output format/details
output_format = await self._get_output_format(
output_format = await self.get_output_format(
output_format_str=request.match_info["fmt"],
player=queue_player,
default_sample_rate=flow_pcm_format.sample_rate,
Expand Down Expand Up @@ -890,7 +890,7 @@ def _log_request(self, request: web.Request) -> None:
request.headers,
)

async def _get_output_format(
async def get_output_format(
self,
output_format_str: str,
player: Player,
Expand Down
34 changes: 25 additions & 9 deletions music_assistant/providers/player_group/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ async def play_media(
self.ugp_streams[player_id] = UGPStream(
audio_source=audio_source, audio_format=UGP_FORMAT, base_pcm_format=UGP_FORMAT
)
base_url = f"{self.mass.streams.base_url}/ugp/{player_id}.mp3"
base_url = f"{self.mass.streams.base_url}/ugp/{player_id}.flac"

# set the state optimistically
group_player.current_media = media
Expand Down Expand Up @@ -632,7 +632,7 @@ async def cmd_group(self, player_id: str, target_player: str) -> None:
# handle resync/resume if group player was already playing
if group_player.state == PlayerState.PLAYING and group_type == GROUP_TYPE_UNIVERSAL:
child_player_provider = self.mass.players.get_player_provider(player_id)
base_url = f"{self.mass.streams.base_url}/ugp/{group_player.player_id}.mp3"
base_url = f"{self.mass.streams.base_url}/ugp/{group_player.player_id}.flac"
await child_player_provider.play_media(
player_id,
media=PlayerMedia(
Expand Down Expand Up @@ -739,9 +739,15 @@ async def _register_group_player(
model_name = "Universal Group"
manufacturer = self.name
# register dynamic route for the ugp stream
route_path = f"/ugp/{group_player_id}.mp3"
self._on_unload.append(
self.mass.streams.register_dynamic_route(route_path, self._serve_ugp_stream)
self.mass.streams.register_dynamic_route(
f"/ugp/{group_player_id}.flac", self._serve_ugp_stream
)
)
self._on_unload.append(
self.mass.streams.register_dynamic_route(
f"/ugp/{group_player_id}.mp3", self._serve_ugp_stream
)
)
can_group_with = {
# allow grouping with all providers, except the playergroup provider itself
Expand Down Expand Up @@ -891,10 +897,20 @@ async def _serve_ugp_stream(self, request: web.Request) -> web.Response:
"""Serve the UGP (multi-client) flow stream audio to a player."""
ugp_player_id = request.path.rsplit(".")[0].rsplit("/")[-1]
child_player_id = request.query.get("player_id") # optional!

# Right now we default to MP3 output format, since it's the most compatible
# TODO: use the player's preferred output format
output_format = AudioFormat(content_type=ContentType.MP3)
output_format_str = request.path.rsplit(".")[-1]

if child_player_id and (child_player := self.mass.players.get(child_player_id)):
# Use the preferred output format of the child player
output_format = await self.mass.streams.get_output_format(
output_format_str=output_format_str,
player=child_player,
default_sample_rate=UGP_FORMAT.sample_rate,
default_bit_depth=24,
)
elif output_format_str == "flac":
output_format = AudioFormat(content_type=ContentType.FLAC)
else:
output_format = AudioFormat(content_type=ContentType.MP3)

if not (ugp_player := self.mass.players.get(ugp_player_id)):
raise web.HTTPNotFound(reason=f"Unknown UGP player: {ugp_player_id}")
Expand All @@ -907,7 +923,7 @@ async def _serve_ugp_stream(self, request: web.Request) -> web.Response:
)
headers = {
**DEFAULT_STREAM_HEADERS,
"Content-Type": "audio/mp3",
"Content-Type": f"audio/{output_format_str}",
"Accept-Ranges": "none",
"Cache-Control": "no-cache",
"Connection": "close",
Expand Down
10 changes: 6 additions & 4 deletions music_assistant/providers/player_group/ugp_stream.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""
Implementation of a Stream for the Universal Group Player.
Basically this is like a fake radio radio stream (MP3) format with multiple subscribers.
The MP3 format is chosen because it is widely supported.
Stream handler for Universal Groups, managing audio distribution to group members.
Essentially, it multicasts an audio source to multiple client streams, allowing individual
filter_params for each client.
"""

from __future__ import annotations
Expand All @@ -25,8 +26,9 @@ class UGPStream:
"""
Implementation of a Stream for the Universal Group Player.
Basically this is like a fake radio radio stream (MP3) format with multiple subscribers.
The MP3 format is chosen because it is widely supported.
Stream handler for Universal Groups, managing audio distribution to group members.
Essentially, it multicasts an audio source to multiple client streams, allowing individual
filter_params for each client.
"""

def __init__(
Expand Down

0 comments on commit 8373ba4

Please sign in to comment.