labels:
- 'new-feature'
- 'new-provider'
- - 'enhancement'
- 'refactor'
default: patch
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
CONF_AUTO_PLAY,
CONF_CROSSFADE,
CONF_CROSSFADE_DURATION,
+ CONF_ENFORCE_MP3,
CONF_EQ_BASS,
CONF_EQ_MID,
CONF_EQ_TREBLE,
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,
+)
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"
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
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:
# 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")
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,
from music_assistant.common.models.config_entries import (
CONF_ENTRY_CROSSFADE_DURATION,
+ CONF_ENTRY_ENFORCE_MP3,
CONF_ENTRY_FLOW_MODE,
ConfigEntry,
ConfigValueType,
)
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
)
CONF_ENQUEUE_NEXT = "enqueue_next"
-CONF_ENFORCE_MP3 = "enforce_mp3"
+
PLAYER_CONFIG_ENTRIES = (
ConfigEntry(
),
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"
):
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
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