async def async_browse_media( hass: HomeAssistant, media_content_id: str | None, *, content_filter: Callable[[BrowseMedia], bool] | None = None, ) -> BrowseMediaSource: """Return media player browse media results.""" if DOMAIN not in hass.data: raise BrowseError("Media Source not loaded") try: item = await _get_media_item(hass, media_content_id).async_browse() except ValueError as err: raise BrowseError("Not a media source item") from err if content_filter is None or item.children is None: return item old_count = len(item.children) item.children = [ child for child in item.children if child.can_expand or content_filter(child) ] item.not_shown = old_count - len(item.children) return item
async def browse_node(media_library, media_content_type, media_content_id): """Browse a node of a Volumio media hierarchy.""" json_item = json.loads(media_content_id) navigation = await media_library.browse(json_item["uri"]) if "lists" not in navigation: raise BrowseError( f"Media not found: {media_content_type} / {media_content_id}") # we only use the first list since the second one could include all tracks first_list = navigation["lists"][0] children = [ _item_payload(media_library, item, parent_item=json_item) for item in first_list["items"] ] info = navigation.get("info") title = first_list.get("title") if not title: if info: title = f"{info.get('album')} ({info.get('artist')})" else: title = "Media Library" payload = _raw_item_payload(media_library, json_item, title=title, info=info) return BrowseMedia(**payload, children=children)
async def async_browse_media( hass: HomeAssistant, media_content_type: str | None, media_content_id: str | None, *, can_play_artist: bool = True, ) -> BrowseMedia: """Browse Spotify media.""" parsed_url = None info = None # Check if caller is requesting the root nodes if media_content_type is None and media_content_id is None: children = [] for config_entry_id, info in hass.data[DOMAIN].items(): config_entry = hass.config_entries.async_get_entry(config_entry_id) assert config_entry is not None children.append( BrowseMedia( title=config_entry.title, media_class=MEDIA_CLASS_APP, media_content_id=f"{MEDIA_PLAYER_PREFIX}{config_entry_id}", media_content_type=f"{MEDIA_PLAYER_PREFIX}library", thumbnail= "https://brands.home-assistant.io/_/spotify/logo.png", can_play=False, can_expand=True, )) return BrowseMedia( title="Spotify", media_class=MEDIA_CLASS_APP, media_content_id=MEDIA_PLAYER_PREFIX, media_content_type="spotify", thumbnail="https://brands.home-assistant.io/_/spotify/logo.png", can_play=False, can_expand=True, children=children, ) if media_content_id is None or not media_content_id.startswith( MEDIA_PLAYER_PREFIX): raise BrowseError("Invalid Spotify URL specified") # Check for config entry specifier, and extract Spotify URI parsed_url = yarl.URL(media_content_id) if (info := hass.data[DOMAIN].get(parsed_url.host)) is None: raise BrowseError("Invalid Spotify account specified")
async def async_browse_media(hass, media_content_type, media_content_id, *, can_play_artist=True): """Browse Spotify media.""" if not (info := next(iter(hass.data[DOMAIN].values()), None)): raise BrowseError("No Spotify accounts available")
async def test_websocket_browse_media(hass, hass_ws_client): """Test browse media websocket.""" assert await async_setup_component(hass, media_source.DOMAIN, {}) await hass.async_block_till_done() client = await hass_ws_client(hass) media = media_source.models.BrowseMediaSource( domain=media_source.DOMAIN, identifier="/media", title="Local Media", media_class=MEDIA_CLASS_DIRECTORY, media_content_type="listing", can_play=False, can_expand=True, ) with patch( "homeassistant.components.media_source.async_browse_media", return_value=media, ): await client.send_json( { "id": 1, "type": "media_source/browse_media", } ) msg = await client.receive_json() assert msg["success"] assert msg["id"] == 1 assert media.as_dict() == msg["result"] with patch( "homeassistant.components.media_source.async_browse_media", side_effect=BrowseError("test"), ): await client.send_json( { "id": 2, "type": "media_source/browse_media", "media_content_id": "invalid", } ) msg = await client.receive_json() assert not msg["success"] assert msg["error"]["code"] == "browse_media_failed" assert msg["error"]["message"] == "test"
async def build_item_response(player, payload): """Create response payload for search described by payload.""" search_id = payload["search_id"] search_type = payload["search_type"] media_class = CONTENT_TYPE_MEDIA_CLASS[search_type] if search_id and search_id != search_type: browse_id = (SQUEEZEBOX_ID_BY_TYPE[search_type], search_id) else: browse_id = None result = await player.async_browse( MEDIA_TYPE_TO_SQUEEZEBOX[search_type], limit=BROWSE_LIMIT, browse_id=browse_id, ) children = None if result is not None and result.get("items"): item_type = CONTENT_TYPE_TO_CHILD_TYPE[search_type] child_media_class = CONTENT_TYPE_MEDIA_CLASS[item_type] children = [] for item in result["items"]: children.append( BrowseMedia( title=item["title"], media_class=child_media_class["item"], media_content_id=str(item["id"]), media_content_type=item_type, can_play=True, can_expand=child_media_class["children"] is not None, thumbnail=item.get("image_url"), )) if children is None: raise BrowseError(f"Media not found: {search_type} / {search_id}") return BrowseMedia( title=result.get("title"), media_class=media_class["item"], children_media_class=media_class["children"], media_content_id=search_id, media_content_type=search_type, can_play=True, children=children, can_expand=True, )
async def async_browse_media( self, media_content_type: Optional[str], media_content_id: Optional[str]) -> "BrowseMedia": builder = None if media_content_type in [None, "library"]: builder = self._browse_media_library elif media_content_type == "bouquet": builder = self._browse_media_bouquet response = None if builder: response = await self.hass.async_add_executor_job( builder, media_content_type, media_content_id) if response is None: raise BrowseError( f"Media not found: {media_content_type} / {media_content_id}") return response
async def async_browse_media(hass, media_content_type, media_content_id, platform=None): """Browse Plex media.""" plex_server = next(iter(hass.data[PLEX_DOMAIN][SERVERS].values()), None) if not plex_server: raise BrowseError("No Plex servers available") is_internal = is_internal_request(hass) return await hass.async_add_executor_job( partial( browse_media, hass, is_internal, media_content_type, media_content_id, platform=platform, ) )
async def async_browse_media( hass: HomeAssistant, media_content_id: str | None, *, content_filter: Callable[[BrowseMedia], bool] | None = None, ) -> BrowseMediaSource: """Return media player browse media results.""" if DOMAIN not in hass.data: raise BrowseError("Media Source not loaded") item = await _get_media_item(hass, media_content_id).async_browse() if content_filter is None or item.children is None: return item item.children = [ child for child in item.children if child.can_expand or content_filter(child) ] return item
async def async_browse_media_internal( hass: HomeAssistant, spotify: Spotify, session: OAuth2Session, current_user: dict[str, Any], media_content_type: str | None, media_content_id: str | None, *, can_play_artist: bool = True, ) -> BrowseMedia: """Browse spotify media.""" if media_content_type in (None, f"{MEDIA_PLAYER_PREFIX}library"): return await hass.async_add_executor_job( partial(library_payload, can_play_artist=can_play_artist)) if not session.valid_token: await session.async_ensure_token_valid() await hass.async_add_executor_job(spotify.set_auth, session.token["access_token"]) # Strip prefix if media_content_type: media_content_type = media_content_type[len(MEDIA_PLAYER_PREFIX):] payload = { "media_content_type": media_content_type, "media_content_id": media_content_id, } response = await hass.async_add_executor_job( partial( build_item_response, spotify, current_user, payload, can_play_artist=can_play_artist, )) if response is None: raise BrowseError( f"Media not found: {media_content_type} / {media_content_id}") return response
item_thumbnail = entity.get_browse_image_url( item_type, item_id, artwork_track_id) children.append( BrowseMedia( title=item["title"], media_class=child_media_class["item"], media_content_id=item_id, media_content_type=item_type, can_play=True, can_expand=child_media_class["children"] is not None, thumbnail=item_thumbnail, )) if children is None: raise BrowseError(f"Media not found: {search_type} / {search_id}") return BrowseMedia( title=result.get("title"), media_class=media_class["item"], children_media_class=media_class["children"], media_content_id=search_id, media_content_type=search_type, can_play=True, children=children, can_expand=True, ) async def library_payload(hass, player): """Create response payload to describe contents of library."""
async def async_browse_media(self, media_content_type=None, media_content_id=None): """Implement the websocket media browsing helper.""" _LOGGER.debug("Meural device %s: Browsing media. Media_content_type is %s, media_content_id is %s", self.name, media_content_type, media_content_id) if media_content_id in (None, "") and media_content_type in (None, ""): response = BrowseMedia( title="Meural Canvas", media_class=MEDIA_CLASS_DIRECTORY, media_content_id="", media_content_type="", can_play=False, can_expand=True, children=[BrowseMedia( title="Media Source", media_class=MEDIA_CLASS_DIRECTORY, media_content_id="", media_content_type="localmediasource", can_play=False, can_expand=True), BrowseMedia( title="Meural Playlists", media_class=MEDIA_CLASS_DIRECTORY, media_content_id="", media_content_type="meuralplaylists", can_play=False, can_expand=True), ] ) return response elif media_source.is_media_source_id(media_content_id) or media_content_type=="localmediasource": kwargs = {} if MAJOR_VERSION > 2022 or (MAJOR_VERSION == 2022 and MINOR_VERSION >= 2): kwargs['content_filter'] = lambda item: item.media_content_type in ('image/jpg', 'image/png', 'image/jpeg') response = await media_source.async_browse_media(self.hass, media_content_id, **kwargs) return response elif media_content_type=="meuralplaylists": response = BrowseMedia( title="Meural Playlists", media_class=MEDIA_CLASS_DIRECTORY, media_content_id="", media_content_type="", can_play=False, can_expand=True, children=[]) device_galleries = await self.meural.get_device_galleries(self.meural_device_id) _LOGGER.info("Meural device %s: Browsing media. Getting %d device galleries from Meural server", self.name, len(device_galleries)) user_galleries = await self.meural.get_user_galleries() _LOGGER.info("Meural device %s: Browsing media. Getting %d user galleries from Meural server", self.name, len(user_galleries)) [device_galleries.append(x) for x in user_galleries if x not in device_galleries] self._remote_galleries = device_galleries _LOGGER.info("Meural device %s: Browsing media. Has %d unique remote galleries on Meural server" % (self.name, len(self._remote_galleries))) for g in self._galleries: thumb=next((h["cover"] for h in self._remote_galleries if h["id"] == int(g["id"])), None) if thumb == None and (int(g["id"])>4): _LOGGER.debug("Meural device %s: Browsing media. Gallery %s misses thumbnail, getting gallery items", self.name, g["id"]) album_items = await self.local_meural.send_get_items_by_gallery(g["id"]) _LOGGER.info("Meural device %s: Browsing media. Replacing missing thumbnail of gallery %s with first gallery item image. Getting information from Meural server for item %s", self.name, g["id"], album_items[0]["id"]) first_item = await self.meural.get_item(album_items[0]["id"]) thumb = first_item["image"] _LOGGER.debug("Meural device %s: Browsing media. Thumbnail image for gallery %s is %s", self.name, g["id"], thumb) response.children.append(BrowseMedia( title=g["name"], media_class=MEDIA_TYPE_PLAYLIST, media_content_id=g["id"], media_content_type=MEDIA_TYPE_PLAYLIST, can_play=True, can_expand=False, thumbnail=thumb, ) ) return response else: _LOGGER.error("Meural device %s: Browsing media. Media not found, media_content_type is %s, media_content_id is %s", self.name, media_content_type, media_content_id) raise BrowseError( f"Media not found: {media_content_type} / {media_content_id}" )
async def async_browse_media( hass: HomeAssistant, media_content_type: str, media_content_id: str, cast_type: str, ) -> BrowseMedia | None: """Browse media.""" if media_content_type != DOMAIN: return None try: get_url(hass, require_ssl=True, prefer_external=True) except NoURLAvailableError as err: raise BrowseError(NO_URL_AVAILABLE_ERROR) from err # List dashboards. if not media_content_id: children = [ BrowseMedia( title="Default", media_class=MEDIA_CLASS_APP, media_content_id=DEFAULT_DASHBOARD, media_content_type=DOMAIN, thumbnail= "https://brands.home-assistant.io/_/lovelace/logo.png", can_play=True, can_expand=False, ) ] for url_path in hass.data[DOMAIN]["dashboards"]: if url_path is None: continue info = await _get_dashboard_info(hass, url_path) children.append(_item_from_info(info)) root = (await async_get_media_browser_root_object(hass, CAST_TYPE_CHROMECAST))[0] root.children = children return root try: info = await _get_dashboard_info(hass, media_content_id) except ValueError as err: raise BrowseError(f"Dashboard {media_content_id} not found") from err children = [] for view in info["views"]: children.append( BrowseMedia( title=view["title"], media_class=MEDIA_CLASS_APP, media_content_id=f'{info["url_path"]}/{view["path"]}', media_content_type=DOMAIN, thumbnail= "https://brands.home-assistant.io/_/lovelace/logo.png", can_play=True, can_expand=False, )) root = _item_from_info(info) root.children = children return root