Require passing target player when resolving media (#72593)
This commit is contained in:
parent
b6575aa66b
commit
f76afffd5a
5 changed files with 46 additions and 15 deletions
|
@ -18,10 +18,11 @@ from homeassistant.components.media_player.browse_media import (
|
||||||
)
|
)
|
||||||
from homeassistant.components.websocket_api import ActiveConnection
|
from homeassistant.components.websocket_api import ActiveConnection
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.frame import report
|
||||||
from homeassistant.helpers.integration_platform import (
|
from homeassistant.helpers.integration_platform import (
|
||||||
async_process_integration_platforms,
|
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 homeassistant.loader import bind_hass
|
||||||
|
|
||||||
from . import local_source
|
from . import local_source
|
||||||
|
@ -80,15 +81,15 @@ async def _process_media_source_platform(
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _get_media_item(
|
def _get_media_item(
|
||||||
hass: HomeAssistant, media_content_id: str | None
|
hass: HomeAssistant, media_content_id: str | None, target_media_player: str | None
|
||||||
) -> MediaSourceItem:
|
) -> MediaSourceItem:
|
||||||
"""Return media item."""
|
"""Return media item."""
|
||||||
if media_content_id:
|
if media_content_id:
|
||||||
item = MediaSourceItem.from_uri(hass, media_content_id)
|
item = MediaSourceItem.from_uri(hass, media_content_id, target_media_player)
|
||||||
else:
|
else:
|
||||||
# We default to our own domain if its only one registered
|
# We default to our own domain if its only one registered
|
||||||
domain = None if len(hass.data[DOMAIN]) > 1 else DOMAIN
|
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]:
|
if item.domain is not None and item.domain not in hass.data[DOMAIN]:
|
||||||
raise ValueError("Unknown media source")
|
raise ValueError("Unknown media source")
|
||||||
|
@ -108,7 +109,7 @@ async def async_browse_media(
|
||||||
raise BrowseError("Media Source not loaded")
|
raise BrowseError("Media Source not loaded")
|
||||||
|
|
||||||
try:
|
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:
|
except ValueError as err:
|
||||||
raise BrowseError(str(err)) from err
|
raise BrowseError(str(err)) from err
|
||||||
|
|
||||||
|
@ -124,13 +125,21 @@ async def async_browse_media(
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@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."""
|
"""Get info to play media."""
|
||||||
if DOMAIN not in hass.data:
|
if DOMAIN not in hass.data:
|
||||||
raise Unresolvable("Media Source not loaded")
|
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:
|
try:
|
||||||
item = _get_media_item(hass, media_content_id)
|
item = _get_media_item(hass, media_content_id, target_media_player)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
raise Unresolvable(str(err)) from err
|
raise Unresolvable(str(err)) from err
|
||||||
|
|
||||||
|
|
|
@ -264,7 +264,7 @@ class UploadMediaView(http.HomeAssistantView):
|
||||||
raise web.HTTPBadRequest() from err
|
raise web.HTTPBadRequest() from err
|
||||||
|
|
||||||
try:
|
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:
|
except ValueError as err:
|
||||||
LOGGER.error("Received invalid upload data: %s", err)
|
LOGGER.error("Received invalid upload data: %s", err)
|
||||||
raise web.HTTPBadRequest() from err
|
raise web.HTTPBadRequest() from err
|
||||||
|
@ -328,7 +328,7 @@ async def websocket_remove_media(
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Remove media."""
|
"""Remove media."""
|
||||||
try:
|
try:
|
||||||
item = MediaSourceItem.from_uri(hass, msg["media_content_id"])
|
item = MediaSourceItem.from_uri(hass, msg["media_content_id"], None)
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err))
|
connection.send_error(msg["id"], websocket_api.ERR_INVALID_FORMAT, str(err))
|
||||||
return
|
return
|
||||||
|
|
|
@ -50,6 +50,7 @@ class MediaSourceItem:
|
||||||
hass: HomeAssistant
|
hass: HomeAssistant
|
||||||
domain: str | None
|
domain: str | None
|
||||||
identifier: str
|
identifier: str
|
||||||
|
target_media_player: str | None
|
||||||
|
|
||||||
async def async_browse(self) -> BrowseMediaSource:
|
async def async_browse(self) -> BrowseMediaSource:
|
||||||
"""Browse this item."""
|
"""Browse this item."""
|
||||||
|
@ -94,7 +95,9 @@ class MediaSourceItem:
|
||||||
return cast(MediaSource, self.hass.data[DOMAIN][self.domain])
|
return cast(MediaSource, self.hass.data[DOMAIN][self.domain])
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Create an item from a uri."""
|
||||||
if not (match := URI_SCHEME_REGEX.match(uri)):
|
if not (match := URI_SCHEME_REGEX.match(uri)):
|
||||||
raise ValueError("Invalid media source URI")
|
raise ValueError("Invalid media source URI")
|
||||||
|
@ -102,7 +105,7 @@ class MediaSourceItem:
|
||||||
domain = match.group("domain")
|
domain = match.group("domain")
|
||||||
identifier = match.group("identifier")
|
identifier = match.group("identifier")
|
||||||
|
|
||||||
return cls(hass, domain, identifier)
|
return cls(hass, domain, identifier, target_media_player)
|
||||||
|
|
||||||
|
|
||||||
class MediaSource(ABC):
|
class MediaSource(ABC):
|
||||||
|
|
|
@ -49,7 +49,7 @@ async def test_get_media_source(hass: HomeAssistant) -> None:
|
||||||
async def test_resolve_media_unconfigured(hass: HomeAssistant) -> None:
|
async def test_resolve_media_unconfigured(hass: HomeAssistant) -> None:
|
||||||
"""Test resolve_media without any devices being configured."""
|
"""Test resolve_media without any devices being configured."""
|
||||||
source = DmsMediaSource(hass)
|
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"):
|
with pytest.raises(Unresolvable, match="No sources have been configured"):
|
||||||
await source.async_resolve_media(item)
|
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:
|
async def test_browse_media_unconfigured(hass: HomeAssistant) -> None:
|
||||||
"""Test browse_media without any devices being configured."""
|
"""Test browse_media without any devices being configured."""
|
||||||
source = DmsMediaSource(hass)
|
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"):
|
with pytest.raises(BrowseError, match="No sources have been configured"):
|
||||||
await source.async_browse_media(item)
|
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"):
|
with pytest.raises(BrowseError, match="No sources have been configured"):
|
||||||
await source.async_browse_media(item)
|
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
|
dms_device_mock.async_browse_metadata.side_effect = UpnpError
|
||||||
|
|
||||||
# Browse by source_id
|
# 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)
|
dms_source = DmsMediaSource(hass)
|
||||||
with pytest.raises(BrowseError):
|
with pytest.raises(BrowseError):
|
||||||
await dms_source.async_browse_media(item)
|
await dms_source.async_browse_media(item)
|
||||||
|
|
|
@ -109,6 +109,25 @@ async def test_async_resolve_media(hass):
|
||||||
assert media.mime_type == "audio/mpeg"
|
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):
|
async def test_async_unresolve_media(hass):
|
||||||
"""Test browse media."""
|
"""Test browse media."""
|
||||||
assert await async_setup_component(hass, media_source.DOMAIN, {})
|
assert await async_setup_component(hass, media_source.DOMAIN, {})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue