From: Maxim Raznatovski Date: Mon, 10 Feb 2025 15:28:04 +0000 (+0100) Subject: Feat: Use the player prefered (lossless) format in universal groups (#1949) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=a4a567d8148cdb81b6d5ec5fc498e13b9cc45f68;p=music-assistant-server.git Feat: Use the player prefered (lossless) format in universal groups (#1949) * refactor: promote get_output_format to public * feat: use the player preferred (lossless) audio format in universal groups * docs: adjust ugp_stream doc comment --- diff --git a/music_assistant/controllers/streams.py b/music_assistant/controllers/streams.py index 11b966bd..dfe6934e 100644 --- a/music_assistant/controllers/streams.py +++ b/music_assistant/controllers/streams.py @@ -284,7 +284,7 @@ class StreamsController(CoreController): ) 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, @@ -385,7 +385,7 @@ class StreamsController(CoreController): 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, @@ -890,7 +890,7 @@ class StreamsController(CoreController): request.headers, ) - async def _get_output_format( + async def get_output_format( self, output_format_str: str, player: Player, diff --git a/music_assistant/providers/player_group/__init__.py b/music_assistant/providers/player_group/__init__.py index 3c1ee150..3aa2eeca 100644 --- a/music_assistant/providers/player_group/__init__.py +++ b/music_assistant/providers/player_group/__init__.py @@ -503,7 +503,7 @@ class PlayerGroupProvider(PlayerProvider): 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 @@ -632,7 +632,7 @@ class PlayerGroupProvider(PlayerProvider): # 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( @@ -739,9 +739,15 @@ class PlayerGroupProvider(PlayerProvider): 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 @@ -891,10 +897,20 @@ class PlayerGroupProvider(PlayerProvider): """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}") @@ -907,7 +923,7 @@ class PlayerGroupProvider(PlayerProvider): ) headers = { **DEFAULT_STREAM_HEADERS, - "Content-Type": "audio/mp3", + "Content-Type": f"audio/{output_format_str}", "Accept-Ranges": "none", "Cache-Control": "no-cache", "Connection": "close", diff --git a/music_assistant/providers/player_group/ugp_stream.py b/music_assistant/providers/player_group/ugp_stream.py index 434905e5..e0c69876 100644 --- a/music_assistant/providers/player_group/ugp_stream.py +++ b/music_assistant/providers/player_group/ugp_stream.py @@ -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 @@ -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__(