diff --git a/homeassistant/components/media_source/__init__.py b/homeassistant/components/media_source/__init__.py index 3c42016f8f7..4818934d1dd 100644 --- a/homeassistant/components/media_source/__init__.py +++ b/homeassistant/components/media_source/__init__.py @@ -18,10 +18,11 @@ from homeassistant.components.media_player.browse_media import ( ) from homeassistant.components.websocket_api import ActiveConnection from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.frame import report from homeassistant.helpers.integration_platform import ( async_process_integration_platforms, ) -from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType from homeassistant.loader import bind_hass from . import local_source @@ -80,15 +81,15 @@ async def _process_media_source_platform( @callback def _get_media_item( - hass: HomeAssistant, media_content_id: str | None + hass: HomeAssistant, media_content_id: str | None, target_media_player: str | None ) -> MediaSourceItem: """Return media item.""" if media_content_id: - item = MediaSourceItem.from_uri(hass, media_content_id) + item = MediaSourceItem.from_uri(hass, media_content_id, target_media_player) else: # We default to our own domain if its only one registered domain = None if len(hass.data[DOMAIN]) > 1 else DOMAIN - return MediaSourceItem(hass, domain, "") + return MediaSourceItem(hass, domain, "", target_media_player) if item.domain is not None and item.domain not in hass.data[DOMAIN]: raise ValueError("Unknown media source") @@ -108,7 +109,7 @@ async def async_browse_media( raise BrowseError("Media Source not loaded") try: - item = await _get_media_item(hass, media_content_id).async_browse() + item = await _get_media_item(hass, media_content_id, None).async_browse() except ValueError as err: raise BrowseError(str(err)) from err @@ -124,13 +125,21 @@ async def async_browse_media( @bind_hass -async def async_resolve_media(hass: HomeAssistant, media_content_id: str) -> PlayMedia: +async def async_resolve_media( + hass: HomeAssistant, + media_content_id: str, + target_media_player: str | None | UndefinedType = UNDEFINED, +) -> PlayMedia: """Get info to play media.""" if DOMAIN not in hass.data: raise Unresolvable("Media Source not loaded") + if target_media_player is UNDEFINED: + report("calls media_source.async_resolve_media without passing an entity_id") + target_media_player = None + try: - item = _get_media_item(hass, media_content_id) + item = _get_media_item(hass, media_content_id, target_media_player) except ValueError as err: raise Unresolvable(str(err)) from err diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index 89feba5317f..863380b7600 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -264,7 +264,7 @@ class UploadMediaView(http.HomeAssistantView): raise web.HTTPBadRequest() from err try: - item = MediaSourceItem.from_uri(self.hass, data["media_content_id"]) + item = MediaSourceItem.from_uri(self.hass, data["media_content_id"], None) except ValueError as err: LOGGER.error("Received invalid upload data: %s", err) raise web.HTTPBadRequest() from err @@ -328,7 +328,7 @@ async def websocket_remove_media( ) -> None: """Remove media.""" try: - item = MediaSourceItem.from_uri(hass, msg["media_content_id"]) + item = MediaSourceItem.from_uri(hass, msg["media_content_id"], None) except ValueError as err: connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err)) return diff --git a/homeassistant/components/media_source/models.py b/homeassistant/components/media_source/models.py index ceb57ef1fb4..0aee6ad1330 100644 --- a/homeassistant/components/media_source/models.py +++ b/homeassistant/components/media_source/models.py @@ -50,6 +50,7 @@ class MediaSourceItem: hass: HomeAssistant domain: str | None identifier: str + target_media_player: str | None async def async_browse(self) -> BrowseMediaSource: """Browse this item.""" @@ -94,7 +95,9 @@ class MediaSourceItem: return cast(MediaSource, self.hass.data[DOMAIN][self.domain]) @classmethod - def from_uri(cls, hass: HomeAssistant, uri: str) -> MediaSourceItem: + def from_uri( + cls, hass: HomeAssistant, uri: str, target_media_player: str | None + ) -> MediaSourceItem: """Create an item from a uri.""" if not (match := URI_SCHEME_REGEX.match(uri)): raise ValueError("Invalid media source URI") @@ -102,7 +105,7 @@ class MediaSourceItem: domain = match.group("domain") identifier = match.group("identifier") - return cls(hass, domain, identifier) + return cls(hass, domain, identifier, target_media_player) class MediaSource(ABC): diff --git a/tests/components/dlna_dms/test_media_source.py b/tests/components/dlna_dms/test_media_source.py index f2c3011e274..5f76b061590 100644 --- a/tests/components/dlna_dms/test_media_source.py +++ b/tests/components/dlna_dms/test_media_source.py @@ -49,7 +49,7 @@ async def test_get_media_source(hass: HomeAssistant) -> None: async def test_resolve_media_unconfigured(hass: HomeAssistant) -> None: """Test resolve_media without any devices being configured.""" source = DmsMediaSource(hass) - item = MediaSourceItem(hass, DOMAIN, "source_id/media_id") + item = MediaSourceItem(hass, DOMAIN, "source_id/media_id", None) with pytest.raises(Unresolvable, match="No sources have been configured"): await source.async_resolve_media(item) @@ -116,11 +116,11 @@ async def test_resolve_media_success( async def test_browse_media_unconfigured(hass: HomeAssistant) -> None: """Test browse_media without any devices being configured.""" source = DmsMediaSource(hass) - item = MediaSourceItem(hass, DOMAIN, "source_id/media_id") + item = MediaSourceItem(hass, DOMAIN, "source_id/media_id", None) with pytest.raises(BrowseError, match="No sources have been configured"): await source.async_browse_media(item) - item = MediaSourceItem(hass, DOMAIN, "") + item = MediaSourceItem(hass, DOMAIN, "", None) with pytest.raises(BrowseError, match="No sources have been configured"): await source.async_browse_media(item) @@ -239,7 +239,7 @@ async def test_browse_media_source_id( dms_device_mock.async_browse_metadata.side_effect = UpnpError # Browse by source_id - item = MediaSourceItem(hass, DOMAIN, f"{MOCK_SOURCE_ID}/:media-item-id") + item = MediaSourceItem(hass, DOMAIN, f"{MOCK_SOURCE_ID}/:media-item-id", None) dms_source = DmsMediaSource(hass) with pytest.raises(BrowseError): await dms_source.async_browse_media(item) diff --git a/tests/components/media_source/test_init.py b/tests/components/media_source/test_init.py index 491b1972cb6..f2a8ff13533 100644 --- a/tests/components/media_source/test_init.py +++ b/tests/components/media_source/test_init.py @@ -109,6 +109,25 @@ async def test_async_resolve_media(hass): assert media.mime_type == "audio/mpeg" +@patch("homeassistant.helpers.frame._REPORTED_INTEGRATIONS", set()) +async def test_async_resolve_media_no_entity(hass, caplog): + """Test browse media.""" + assert await async_setup_component(hass, media_source.DOMAIN, {}) + await hass.async_block_till_done() + + media = await media_source.async_resolve_media( + hass, + media_source.generate_media_source_id(media_source.DOMAIN, "local/test.mp3"), + ) + assert isinstance(media, media_source.models.PlayMedia) + assert media.url == "/media/local/test.mp3" + assert media.mime_type == "audio/mpeg" + assert ( + "calls media_source.async_resolve_media without passing an entity_id" + in caplog.text + ) + + async def test_async_unresolve_media(hass): """Test browse media.""" assert await async_setup_component(hass, media_source.DOMAIN, {})