From c9eee26dc8e45b7fffe3249c0e92e8d3d5446d52 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sun, 10 Mar 2024 23:09:12 +0100 Subject: [PATCH] A collection of small enhancements (#1130) --- .github/release-drafter.yml | 1 - .github/workflows/pr-labels.yaml | 2 +- .../common/models/config_entries.py | 13 ++++++++++ music_assistant/constants.py | 1 + music_assistant/server/helpers/util.py | 2 +- .../server/providers/airplay/__init__.py | 18 ++++++++----- .../server/providers/dlna/__init__.py | 17 +++---------- .../server/providers/snapcast/__init__.py | 25 +++++++++++++------ 8 files changed, 50 insertions(+), 29 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 2c0fca595..797c1170f 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -22,6 +22,5 @@ version-resolver: labels: - 'new-feature' - 'new-provider' - - 'enhancement' - 'refactor' default: patch diff --git a/.github/workflows/pr-labels.yaml b/.github/workflows/pr-labels.yaml index 995d1148b..102d4c756 100644 --- a/.github/workflows/pr-labels.yaml +++ b/.github/workflows/pr-labels.yaml @@ -20,4 +20,4 @@ jobs: uses: ludeeus/action-require-labels@1.1.0 with: labels: >- - breaking-change, bugfix, refactor, new-feature, maintenance, ci, dependencies, new-provider + breaking-change, bugfix, refactor, new-feature, maintenance, enhancement, ci, dependencies, new-provider diff --git a/music_assistant/common/models/config_entries.py b/music_assistant/common/models/config_entries.py index 52f888883..6250ef8e8 100644 --- a/music_assistant/common/models/config_entries.py +++ b/music_assistant/common/models/config_entries.py @@ -15,6 +15,7 @@ CONF_AUTO_PLAY, CONF_CROSSFADE, CONF_CROSSFADE_DURATION, + CONF_ENFORCE_MP3, CONF_EQ_BASS, CONF_EQ_MID, CONF_EQ_TREBLE, @@ -408,3 +409,15 @@ class CoreConfig(Config): default_value=False, advanced=True, ) + +CONF_ENTRY_ENFORCE_MP3 = ConfigEntry( + key=CONF_ENFORCE_MP3, + type=ConfigEntryType.BOOLEAN, + label="Enforce (lossy) mp3 stream", + default_value=False, + description="By default, Music Assistant sends lossless, high quality audio " + "to all players. Some players can not deal with that and require the stream to be packed " + "into a lossy mp3 codec. \n\n " + "Only enable when needed. Saves some bandwidth at the cost of audio quality.", + advanced=True, +) diff --git a/music_assistant/constants.py b/music_assistant/constants.py index ab5b81083..1bde9e68c 100644 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -53,6 +53,7 @@ CONF_CROSSFADE: Final[str] = "crossfade" CONF_GROUP_MEMBERS: Final[str] = "group_members" CONF_HIDE_PLAYER: Final[str] = "hide_player" +CONF_ENFORCE_MP3: Final[str] = "enforce_mp3" # config default values DEFAULT_HOST: Final[str] = "0.0.0.0" diff --git a/music_assistant/server/helpers/util.py b/music_assistant/server/helpers/util.py index 33b9eacb4..2c8c6b9c9 100644 --- a/music_assistant/server/helpers/util.py +++ b/music_assistant/server/helpers/util.py @@ -80,7 +80,7 @@ async def is_hass_supervisor() -> bool: def _check(): try: - urllib.request.urlopen("http://supervisor/core") + urllib.request.urlopen("http://supervisor/core", timeout=1) except urllib.error.URLError as err: # this should return a 401 unauthorized if it exists return getattr(err, "code", 999) == 401 diff --git a/music_assistant/server/providers/airplay/__init__.py b/music_assistant/server/providers/airplay/__init__.py index 89e19c694..d9e8723ae 100644 --- a/music_assistant/server/providers/airplay/__init__.py +++ b/music_assistant/server/providers/airplay/__init__.py @@ -160,9 +160,15 @@ def get_model_from_am(am_property: str | None) -> tuple[str, str]: def get_primary_ip_address(discovery_info: AsyncServiceInfo) -> str | None: """Get primary IP address from zeroconf discovery info.""" - return next( - (x for x in discovery_info.parsed_addresses(IPVersion.V4Only) if x != "127.0.0.1"), None - ) + for address in discovery_info.parsed_addresses(IPVersion.V4Only): + if address.startswith("127"): + # filter out loopback address + continue + if address.startswith("169.254"): + # filter out APIPA address + continue + return address + return None class AirplayStreamJob: @@ -357,12 +363,12 @@ async def _audio_reader(self) -> None: # EOF chunk break self._cliraop_proc.stdin.write(chunk) - with suppress(BrokenPipeError): + with suppress(BrokenPipeError, ConnectionResetError): await self._cliraop_proc.stdin.drain() # send EOF if self._cliraop_proc.returncode is None and not self._cliraop_proc.stdin.is_closing(): self._cliraop_proc.stdin.write_eof() - with suppress(BrokenPipeError): + with suppress(BrokenPipeError, ConnectionResetError): await self._cliraop_proc.stdin.drain() logger.debug("Audio reader finished") @@ -453,10 +459,10 @@ async def on_mdns_service_state_change( if mass_player := self.mass.players.get(player_id): cur_address = get_primary_ip_address(info) if cur_address and cur_address != airplay_player.address: - airplay_player.address = cur_address airplay_player.logger.info( "Address updated from %s to %s", airplay_player.address, cur_address ) + airplay_player.address = cur_address mass_player.device_info = DeviceInfo( model=mass_player.device_info.model, manufacturer=mass_player.device_info.manufacturer, diff --git a/music_assistant/server/providers/dlna/__init__.py b/music_assistant/server/providers/dlna/__init__.py index d0b6ade95..6e5d02443 100644 --- a/music_assistant/server/providers/dlna/__init__.py +++ b/music_assistant/server/providers/dlna/__init__.py @@ -25,6 +25,7 @@ from music_assistant.common.models.config_entries import ( CONF_ENTRY_CROSSFADE_DURATION, + CONF_ENTRY_ENFORCE_MP3, CONF_ENTRY_FLOW_MODE, ConfigEntry, ConfigValueType, @@ -38,7 +39,7 @@ ) from music_assistant.common.models.errors import PlayerUnavailableError from music_assistant.common.models.player import DeviceInfo, Player -from music_assistant.constants import CONF_CROSSFADE, CONF_FLOW_MODE, CONF_PLAYERS +from music_assistant.constants import CONF_CROSSFADE, CONF_ENFORCE_MP3, CONF_FLOW_MODE, CONF_PLAYERS from music_assistant.server.helpers.didl_lite import create_didl_metadata from music_assistant.server.models.player_provider import PlayerProvider @@ -63,7 +64,7 @@ ) CONF_ENQUEUE_NEXT = "enqueue_next" -CONF_ENFORCE_MP3 = "enforce_mp3" + PLAYER_CONFIG_ENTRIES = ( ConfigEntry( @@ -88,17 +89,7 @@ ), CONF_ENTRY_FLOW_MODE, CONF_ENTRY_CROSSFADE_DURATION, - ConfigEntry( - key=CONF_ENFORCE_MP3, - type=ConfigEntryType.BOOLEAN, - label="Enforce (lossy) mp3 stream", - default_value=False, - description="By default, Music Assistant sends lossless, high quality audio " - "to all players. Some players can not deal with that and require the stream to be packed " - "into a lossy mp3 codec. \n\n " - "Only enable when needed. Saves some bandwidth at the cost of audio quality.", - advanced=True, - ), + CONF_ENTRY_ENFORCE_MP3, ) CONF_NETWORK_SCAN = "network_scan" diff --git a/music_assistant/server/providers/snapcast/__init__.py b/music_assistant/server/providers/snapcast/__init__.py index 13b0900e8..3e81f4818 100644 --- a/music_assistant/server/providers/snapcast/__init__.py +++ b/music_assistant/server/providers/snapcast/__init__.py @@ -295,13 +295,18 @@ async def _streamer() -> None: ): writer.write(pcm_chunk) await writer.drain() - - finally: - await self._snapserver.stream_remove_stream(stream.identifier) + # end of the stream reached if writer.can_write_eof(): - writer.close() + writer.write_eof() + await writer.drain() + # we need to wait a bit before removing the stream to ensure + # that all snapclients have consumed the audio + # https://github.com/music-assistant/hass-music-assistant/issues/1962 + await asyncio.sleep(30) + finally: if not writer.is_closing(): writer.close() + await self._snapserver.stream_remove_stream(stream.identifier) self.logger.debug("Closed connection to %s:%s", host, port) # start streaming the queue (pcm) audio in a background task @@ -340,12 +345,18 @@ async def _streamer() -> None: async for pcm_chunk in stream_job.subscribe(player_id): writer.write(pcm_chunk) await writer.drain() - finally: - await self._snapserver.stream_remove_stream(stream.identifier) + # end of the stream reached if writer.can_write_eof(): - writer.close() + writer.write_eof() + await writer.drain() + # we need to wait a bit before removing the stream to ensure + # that all snapclients have consumed the audio + # https://github.com/music-assistant/hass-music-assistant/issues/1962 + await asyncio.sleep(30) + finally: if not writer.is_closing(): writer.close() + await self._snapserver.stream_remove_stream(stream.identifier) self.logger.debug("Closed connection to %s:%s", host, port) # start streaming the queue (pcm) audio in a background task