Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions homeassistant/components/camera/media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
PlayMedia,
)
from homeassistant.components.stream import FORMAT_CONTENT_TYPE, HLS_PROVIDER
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_component import EntityComponent
Expand All @@ -25,13 +26,20 @@ async def async_get_media_source(hass: HomeAssistant) -> CameraMediaSource:
return CameraMediaSource(hass)


def _media_source_for_camera(camera: Camera, content_type: str) -> BrowseMediaSource:
def _media_source_for_camera(
hass: HomeAssistant, camera: Camera, content_type: str
) -> BrowseMediaSource:
camera_state = hass.states.get(camera.entity_id)
title = camera.name
if camera_state:
title = camera_state.attributes.get(ATTR_FRIENDLY_NAME, camera.name)

return BrowseMediaSource(
domain=DOMAIN,
identifier=camera.entity_id,
media_class=MediaClass.VIDEO,
media_content_type=content_type,
title=camera.name,
title=title,
thumbnail=f"/api/camera_proxy/{camera.entity_id}",
can_play=True,
can_expand=False,
Expand Down Expand Up @@ -89,15 +97,15 @@ async def async_browse_media(
async def _filter_browsable_camera(camera: Camera) -> BrowseMediaSource | None:
stream_type = camera.frontend_stream_type
if stream_type is None:
return _media_source_for_camera(camera, camera.content_type)
return _media_source_for_camera(self.hass, camera, camera.content_type)
if not can_stream_hls:
return None

content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER]
if stream_type != StreamType.HLS and not (await camera.stream_source()):
return None

return _media_source_for_camera(camera, content_type)
return _media_source_for_camera(self.hass, camera, content_type)

component: EntityComponent[Camera] = self.hass.data[DOMAIN]
results = await asyncio.gather(
Expand Down
35 changes: 35 additions & 0 deletions tests/components/camera/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from homeassistant.components.camera.const import StreamType
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.setup import async_setup_component

from .common import WEBRTC_ANSWER
Expand Down Expand Up @@ -69,3 +70,37 @@ async def mock_camera_web_rtc_fixture(hass):
return_value=WEBRTC_ANSWER,
):
yield


@pytest.fixture(name="mock_camera_with_device")
async def mock_camera_with_device_fixture():
"""Initialize a demo camera platform with a device."""
dev_info = DeviceInfo(
identifiers={("camera", "test_unique_id")},
name="Test Camera Device",
)

class UniqueIdMock(PropertyMock):
def __get__(self, obj, obj_type=None):
return obj.name

with patch(
"homeassistant.components.camera.Camera.has_entity_name",
new_callable=PropertyMock(return_value=True),
), patch(
"homeassistant.components.camera.Camera.unique_id", new=UniqueIdMock()
), patch(
"homeassistant.components.camera.Camera.device_info",
new_callable=PropertyMock(return_value=dev_info),
):
yield


@pytest.fixture(name="mock_camera_with_no_name")
async def mock_camera_with_no_name_fixture(mock_camera_with_device):
"""Initialize a demo camera platform with a device and no name."""
with patch(
"homeassistant.components.camera.Camera._attr_name",
new_callable=PropertyMock(return_value=None),
):
yield
21 changes: 21 additions & 0 deletions tests/components/camera/test_media_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ async def setup_media_source(hass):
assert await async_setup_component(hass, "media_source", {})


async def test_device_with_device(
hass: HomeAssistant, mock_camera_with_device, mock_camera
) -> None:
"""Test browsing when camera has a device and a name."""
item = await media_source.async_browse_media(hass, "media-source://camera")
assert item.not_shown == 2
assert len(item.children) == 1
assert item.children[0].title == "Test Camera Device Demo camera without stream"


async def test_device_with_no_name(
hass: HomeAssistant, mock_camera_with_no_name, mock_camera
) -> None:
"""Test browsing when camera has device and name == None."""
item = await media_source.async_browse_media(hass, "media-source://camera")
assert item.not_shown == 2
assert len(item.children) == 1
assert item.children[0].title == "Test Camera Device Demo camera without stream"


async def test_browsing_hls(hass: HomeAssistant, mock_camera_hls) -> None:
"""Test browsing HLS camera media source."""
item = await media_source.async_browse_media(hass, "media-source://camera")
Expand All @@ -41,6 +61,7 @@ async def test_browsing_mjpeg(hass: HomeAssistant, mock_camera) -> None:
assert len(item.children) == 1
assert item.not_shown == 2
assert item.children[0].media_content_type == "image/jpg"
assert item.children[0].title == "Demo camera without stream"


async def test_browsing_web_rtc(hass: HomeAssistant, mock_camera_web_rtc) -> None:
Expand Down