) -> list[Track]:
"""Call the registered music providers for dynamic tracks."""
queue = self._queues[queue_id]
+ queue_track_items = [q.media_item for q in self._queue_items[queue_id] if q.media_item]
if not queue.radio_source:
# this may happen during race conditions as this method is called delayed
return None
)
available_base_tracks: list[Track] = []
base_track_sample_size = 5
- # Grab all the available base tracks based on the selected source items.
- # shuffle the source items, just in case
- for radio_item in random.sample(queue.radio_source, len(queue.radio_source)):
- ctrl = self.mass.music.get_controller(radio_item.media_type)
- try:
- available_base_tracks += [
- track
- for track in await ctrl.radio_mode_base_tracks(
- radio_item.item_id, radio_item.provider
+ # Some providers have very deterministic similar track algorithms when providing
+ # a single track item. When we have a radio mode based on 1 track and we have to
+ # refill the queue (ie not initial radio mode), we use the play history as base tracks
+ if (
+ len(queue.radio_source) == 1
+ and queue.radio_source[0].media_type == MediaType.TRACK
+ and not is_initial_radio_mode
+ ):
+ available_base_tracks = queue_track_items
+ else:
+ # Grab all the available base tracks based on the selected source items.
+ # shuffle the source items, just in case
+ for radio_item in random.sample(queue.radio_source, len(queue.radio_source)):
+ ctrl = self.mass.music.get_controller(radio_item.media_type)
+ try:
+ available_base_tracks += [
+ track
+ for track in await ctrl.radio_mode_base_tracks(
+ radio_item.item_id, radio_item.provider
+ )
+ # Avoid duplicate base tracks
+ if track not in available_base_tracks
+ ]
+ except UnsupportedFeaturedException as err:
+ self.logger.debug(
+ "Skip loading radio items for %s: %s ",
+ radio_item.uri,
+ str(err),
)
- # Avoid duplicate base tracks
- if track not in available_base_tracks
- ]
- except UnsupportedFeaturedException as err:
- self.logger.debug(
- "Skip loading radio items for %s: %s ",
- radio_item.uri,
- str(err),
- )
- if not available_base_tracks:
- raise UnsupportedFeaturedException("Radio mode not available for source items")
+ if not available_base_tracks:
+ raise UnsupportedFeaturedException("Radio mode not available for source items")
# Sample tracks from the base tracks, which will be used to calculate the dynamic ones
base_tracks = random.sample(
if dynamic_tracks:
break
for base_track in base_tracks:
- [
- dynamic_tracks.add(track)
- for track in await self.mass.music.tracks.similar_tracks(
+ try:
+ _similar_tracks = await self.mass.music.tracks.similar_tracks(
base_track.item_id,
base_track.provider,
allow_lookup=allow_lookup,
)
- if track not in base_tracks
- # Ignore tracks that are too long for radio mode, e.g. mixes
- and track.duration <= RADIO_TRACK_MAX_DURATION_SECS
- ]
+ except MediaNotFoundError:
+ # Some providers don't have similar tracks for all items. For example,
+ # Tidal can sometimes return a 404 when the 'similar_tracks' endpoint is called.
+ # in that case, just skip the track.
+ self.logger.debug("No similar tracks not found for track %s", base_track.name)
+ continue
+ for track in _similar_tracks:
+ if (
+ track not in base_tracks
+ # Exclude tracks we have already played / queued
+ and track not in queue_track_items
+ # Ignore tracks that are too long for radio mode, e.g. mixes
+ and track.duration <= RADIO_TRACK_MAX_DURATION_SECS
+ ):
+ dynamic_tracks.add(track)
if len(dynamic_tracks) >= 50:
break
queue_tracks: list[Track] = []