1 """Media Source Implementation."""
3 from __future__
import annotations
5 from typing
import TYPE_CHECKING, Any
7 from music_assistant_models.media_items
import MediaItemType
18 from .const
import DEFAULT_NAME, DOMAIN
21 from music_assistant_client
import MusicAssistantClient
23 MEDIA_TYPE_RADIO =
"radio"
25 PLAYABLE_MEDIA_TYPES = [
33 LIBRARY_ARTISTS =
"artists"
34 LIBRARY_ALBUMS =
"albums"
35 LIBRARY_TRACKS =
"tracks"
36 LIBRARY_PLAYLISTS =
"playlists"
37 LIBRARY_RADIO =
"radio"
41 LIBRARY_ARTISTS:
"Artists",
42 LIBRARY_ALBUMS:
"Albums",
43 LIBRARY_TRACKS:
"Tracks",
44 LIBRARY_PLAYLISTS:
"Playlists",
45 LIBRARY_RADIO:
"Radio stations",
48 LIBRARY_MEDIA_CLASS_MAP = {
49 LIBRARY_ARTISTS: MediaClass.ARTIST,
50 LIBRARY_ALBUMS: MediaClass.ALBUM,
51 LIBRARY_TRACKS: MediaClass.TRACK,
52 LIBRARY_PLAYLISTS: MediaClass.PLAYLIST,
53 LIBRARY_RADIO: MediaClass.MUSIC,
56 MEDIA_CONTENT_TYPE_FLAC =
"audio/flac"
61 """Filter media sources."""
62 return item.media_content_type.startswith(
"audio/")
67 mass: MusicAssistantClient,
68 media_content_id: str |
None,
69 media_content_type: str |
None,
72 if media_content_id
is None:
75 assert media_content_type
is not None
77 if media_source.is_media_source_id(media_content_id):
78 return await media_source.async_browse_media(
79 hass, media_content_id, content_filter=media_source_filter
82 if media_content_id == LIBRARY_ARTISTS:
84 if media_content_id == LIBRARY_ALBUMS:
86 if media_content_id == LIBRARY_TRACKS:
88 if media_content_id == LIBRARY_PLAYLISTS:
90 if media_content_id == LIBRARY_RADIO:
92 if "artist" in media_content_id:
94 if "album" in media_content_id:
96 if "playlist" in media_content_id:
99 raise BrowseError(f
"Media not found: {media_content_type} / {media_content_id}")
103 """Build main browse listing."""
104 children: list[BrowseMedia] = []
105 for library, media_class
in LIBRARY_MEDIA_CLASS_MAP.items():
107 media_class=MediaClass.DIRECTORY,
108 media_content_id=library,
109 media_content_type=DOMAIN,
110 title=LIBRARY_TITLE_MAP[library],
111 children_media_class=media_class,
115 children.append(child_source)
118 item = await media_source.async_browse_media(
119 hass,
None, content_filter=media_source_filter
122 if item.domain
is None and item.children
is not None:
123 children.extend(item.children)
125 children.append(item)
130 media_class=MediaClass.DIRECTORY,
132 media_content_type=DOMAIN,
141 """Build Playlists browse listing."""
142 media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_PLAYLISTS]
144 media_class=MediaClass.DIRECTORY,
145 media_content_id=LIBRARY_PLAYLISTS,
146 media_content_type=MediaType.PLAYLIST,
147 title=LIBRARY_TITLE_MAP[LIBRARY_PLAYLISTS],
150 children_media_class=media_class,
156 for item
in await mass.music.get_library_playlists(limit=500)
159 key=
lambda x: x.title,
165 mass: MusicAssistantClient, identifier: str
167 """Build Playlist items browse listing."""
168 playlist = await mass.music.get_item_by_uri(identifier)
171 media_class=MediaClass.PLAYLIST,
172 media_content_id=playlist.uri,
173 media_content_type=MediaType.PLAYLIST,
177 children_media_class=MediaClass.TRACK,
182 for item
in await mass.music.get_playlist_tracks(
183 playlist.item_id, playlist.provider
191 """Build Albums browse listing."""
192 media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_ARTISTS]
195 media_class=MediaClass.DIRECTORY,
196 media_content_id=LIBRARY_ARTISTS,
197 media_content_type=MediaType.ARTIST,
198 title=LIBRARY_TITLE_MAP[LIBRARY_ARTISTS],
201 children_media_class=media_class,
207 for artist
in await mass.music.get_library_artists(limit=500)
210 key=
lambda x: x.title,
216 mass: MusicAssistantClient, identifier: str
218 """Build Artist items browse listing."""
219 artist = await mass.music.get_item_by_uri(identifier)
220 albums = await mass.music.get_artist_albums(artist.item_id, artist.provider)
223 media_class=MediaType.ARTIST,
224 media_content_id=artist.uri,
225 media_content_type=MediaType.ARTIST,
229 children_media_class=MediaClass.ALBUM,
239 """Build Albums browse listing."""
240 media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_ALBUMS]
243 media_class=MediaClass.DIRECTORY,
244 media_content_id=LIBRARY_ALBUMS,
245 media_content_type=MediaType.ALBUM,
246 title=LIBRARY_TITLE_MAP[LIBRARY_ALBUMS],
249 children_media_class=media_class,
255 for album
in await mass.music.get_library_albums(limit=500)
258 key=
lambda x: x.title,
264 mass: MusicAssistantClient, identifier: str
266 """Build Album items browse listing."""
267 album = await mass.music.get_item_by_uri(identifier)
268 tracks = await mass.music.get_album_tracks(album.item_id, album.provider)
271 media_class=MediaType.ALBUM,
272 media_content_id=album.uri,
273 media_content_type=MediaType.ALBUM,
277 children_media_class=MediaClass.TRACK,
279 build_item(mass, track,
False)
for track
in tracks
if track.available
285 """Build Tracks browse listing."""
286 media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_TRACKS]
289 media_class=MediaClass.DIRECTORY,
290 media_content_id=LIBRARY_TRACKS,
291 media_content_type=MediaType.TRACK,
292 title=LIBRARY_TITLE_MAP[LIBRARY_TRACKS],
295 children_media_class=media_class,
301 for track
in await mass.music.get_library_tracks(limit=500)
304 key=
lambda x: x.title,
310 """Build Radio browse listing."""
311 media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_RADIO]
313 media_class=MediaClass.DIRECTORY,
314 media_content_id=LIBRARY_RADIO,
315 media_content_type=DOMAIN,
316 title=LIBRARY_TITLE_MAP[LIBRARY_RADIO],
319 children_media_class=media_class,
321 build_item(mass, track, can_expand=
False, media_class=media_class)
324 for track
in await mass.music.get_library_radios(limit=500)
331 mass: MusicAssistantClient,
333 can_expand: bool =
True,
334 media_class: Any =
None,
336 """Return BrowseMedia for MediaItem."""
337 if artists := getattr(item,
"artists",
None):
338 title = f
"{artists[0].name} - {item.name}"
341 img_url = mass.get_media_item_image_url(item)
344 media_class=media_class
or item.media_type.value,
345 media_content_id=item.uri,
346 media_content_type=MediaType.MUSIC,
349 can_expand=can_expand,