_returncode, crossfaded_audio, _stderr = await communicate(args, fade_in_part)
if crossfaded_audio:
LOGGER.log(
- 5,
+ VERBOSE_LOG_LEVEL,
"crossfaded 2 pcm chunks. fade_in_part: %s - "
"fade_out_part: %s - fade_length: %s seconds",
len(fade_in_part),
# return stripped audio
bytes_stripped = len(audio_data) - len(stripped_data)
- if LOGGER.isEnabledFor(5):
+ if LOGGER.isEnabledFor(VERBOSE_LOG_LEVEL):
pcm_sample_size = int(sample_rate * (bit_depth / 8) * 2)
seconds_stripped = round(bytes_stripped / pcm_sample_size, 2)
location = "end" if reverse else "begin"
LOGGER.log(
- 5,
+ VERBOSE_LOG_LEVEL,
"stripped %s seconds of silence from %s of pcm audio. bytes stripped: %s",
seconds_stripped,
location,
Do not try to request streamdetails in advance as this is expiring data.
param media_item: The QueueItem for which to request the streamdetails for.
"""
+ time_start = time.time()
+ LOGGER.debug("Getting streamdetails for %s", queue_item.uri)
if seek_position and (queue_item.media_type == MediaType.RADIO or not queue_item.duration):
LOGGER.warning("seeking is not possible on duration-less streams!")
seek_position = 0
streamdetails.target_loudness = None
else:
streamdetails.target_loudness = player_settings.get_value(CONF_VOLUME_NORMALIZATION_TARGET)
-
+ process_time = int((time.time() - time_start) * 1000)
+ LOGGER.debug("retrieved streamdetails for %s in %s milliseconds", queue_item.uri, process_time)
return streamdetails
# return early on exact item_id match
if compare_item_ids(base_item, compare_item):
return True
- # return early on (un)matched external id
+ # return early on (un)matched primary/unique external id
for ext_id in (
ExternalID.MB_RECORDING,
- ExternalID.DISCOGS,
+ ExternalID.MB_TRACK,
ExternalID.ACOUSTID,
+ ):
+ external_id_match = compare_external_ids(
+ base_item.external_ids, compare_item.external_ids, ext_id
+ )
+ if external_id_match is not None:
+ return external_id_match
+ # check secondary external id matches
+ for ext_id in (
+ ExternalID.DISCOGS,
ExternalID.TADB,
- # make sure to check musicbrainz before isrc
- # https://github.com/music-assistant/hass-music-assistant/issues/2316
ExternalID.ISRC,
ExternalID.ASIN,
):
external_id_match = compare_external_ids(
base_item.external_ids, compare_item.external_ids, ext_id
)
- if external_id_match is not None:
- return external_id_match
+ if external_id_match is True:
+ # we got a 'soft-match' on a secondary external id (like ISRC)
+ # but we do a double check on duration
+ if abs(base_item.duration - compare_item.duration) <= 2:
+ return True
# compare name
if not compare_strings(base_item.name, compare_item.name, strict=True):
"""Compare two Playlist items and return True if they match."""
if base_item is None or compare_item is None:
return False
- # return early on exact item_id match
- if compare_item_ids(base_item, compare_item):
- return True
- # compare owner (if not ItemMapping)
+ # require (exact) name match
+ if not compare_strings(base_item.name, compare_item.name, strict=strict):
+ return False
+ # require exact owner match (if not ItemMapping)
if isinstance(base_item, Playlist) and isinstance(compare_item, Playlist):
if not compare_strings(base_item.owner, compare_item.owner):
return False
- # compare version
- if not compare_version(base_item.version, compare_item.version):
- return False
- # finally comparing on (exact) name match
- return compare_strings(base_item.name, compare_item.name, strict=strict)
+ # a playlist is always unique - so do a strict compare on item id(s)
+ return compare_item_ids(base_item, compare_item)
def compare_radio(
await airplay_player.raop_stream.send_cli_command(f"VOLUME={volume_level}\n")
mass_player = self.mass.players.get(player_id)
mass_player.volume_level = volume_level
+ mass_player.volume_muted = volume_level == 0
self.mass.players.update(player_id)
# store last state in cache
await self.mass.cache.set(player_id, volume_level, base_key=CACHE_KEY_PREV_VOLUME)
async def get_country_folders(self, base_path: str) -> list[BrowseFolder]:
"""Get a list of country names as BrowseFolder."""
items: list[BrowseFolder] = []
- for country in await self.radios.countries(order=Order.NAME, hide_broken=True):
+ for country in await self.radios.countries(order=Order.NAME, hide_broken=True, limit=1000):
folder = BrowseFolder(
item_id=country.code.lower(),
provider=self.domain,
"""Get radio stations by popularity."""
stations = await self.radios.stations(
hide_broken=True,
- limit=5000,
+ limit=1000,
order=Order.CLICK_COUNT,
reverse=True,
)
filter_by=FilterBy.TAG_EXACT,
filter_term=tag,
hide_broken=True,
- order=Order.NAME,
+ limit=1000,
+ order=Order.CLICK_COUNT,
reverse=False,
)
for station in stations:
filter_by=FilterBy.COUNTRY_CODE_EXACT,
filter_term=country_code,
hide_broken=True,
- order=Order.NAME,
+ limit=1000,
+ order=Order.CLICK_COUNT,
reverse=False,
)
for station in stations:
auth_info = await self.login()
librespot = await self.get_librespot_binary()
spotify_uri = f"spotify://track:{streamdetails.item_id}"
- args = [
- librespot,
- "-c",
- CACHE_DIR,
- "-M",
- "256M",
- "--passthrough",
- "-b",
- "320",
- "--backend",
- "pipe",
- "--single-track",
- spotify_uri,
- "--token",
- auth_info["access_token"],
- ]
- if seek_position:
- args += ["--start-position", str(int(seek_position))]
- chunk_size = get_chunksize(streamdetails.audio_format)
- stderr = None if self.logger.isEnabledFor(VERBOSE_LOG_LEVEL) else False
- self.logger.log(VERBOSE_LOG_LEVEL, f"Start streaming {spotify_uri} using librespot")
for retry in (True, False):
+ args = [
+ librespot,
+ "-c",
+ CACHE_DIR,
+ "-M",
+ "256M",
+ "--passthrough",
+ "-b",
+ "320",
+ "--backend",
+ "pipe",
+ "--single-track",
+ spotify_uri,
+ "--token",
+ auth_info["access_token"],
+ ]
+ if seek_position:
+ args += ["--start-position", str(int(seek_position))]
+ chunk_size = get_chunksize(streamdetails.audio_format)
+ stderr = None if self.logger.isEnabledFor(VERBOSE_LOG_LEVEL) else False
+ self.logger.log(VERBOSE_LOG_LEVEL, f"Start streaming {spotify_uri} using librespot")
+
async with AsyncProcess(
args,
stdout=True,
raise AudioError(
f"Failed to stream {spotify_uri} - error: {librespot_proc.returncode}"
)
- # do one retry attempt
+ # do one retry attempt - accounting for the fact that the token might have expired
auth_info = await self.login(force_refresh=True)
def _parse_artist(self, artist_obj):