Show WebRTC cameras that also support HLS in the media browser (#108796)

* Show WebRTC cameras in the media browser

* Only show webrtc cameras with source in the browser

* Address code review

* Refactor BrowseMediaSource creation

* Refactor

* Address code review
pull/110953/head
On Freund 2024-02-18 20:12:08 +02:00 committed by GitHub
parent 8fa347fb4c
commit e879ab0eef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 57 additions and 36 deletions

View File

@ -1,6 +1,8 @@
"""Expose cameras as media sources."""
from __future__ import annotations
import asyncio
from homeassistant.components.media_player import BrowseError, MediaClass
from homeassistant.components.media_source.error import Unresolvable
from homeassistant.components.media_source.models import (
@ -23,6 +25,19 @@ async def async_get_media_source(hass: HomeAssistant) -> CameraMediaSource:
return CameraMediaSource(hass)
def _media_source_for_camera(camera: Camera, content_type: str) -> BrowseMediaSource:
return BrowseMediaSource(
domain=DOMAIN,
identifier=camera.entity_id,
media_class=MediaClass.VIDEO,
media_content_type=content_type,
title=camera.name,
thumbnail=f"/api/camera_proxy/{camera.entity_id}",
can_play=True,
can_expand=False,
)
class CameraMediaSource(MediaSource):
"""Provide camera feeds as media sources."""
@ -71,36 +86,28 @@ class CameraMediaSource(MediaSource):
can_stream_hls = "stream" in self.hass.config.components
# Root. List cameras.
component: EntityComponent[Camera] = self.hass.data[DOMAIN]
children = []
not_shown = 0
for camera in component.entities:
async def _filter_browsable_camera(camera: Camera) -> BrowseMediaSource | None:
stream_type = camera.frontend_stream_type
if stream_type is None:
content_type = camera.content_type
return _media_source_for_camera(camera, camera.content_type)
if not can_stream_hls:
return None
elif can_stream_hls and stream_type == StreamType.HLS:
content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER]
content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER]
if stream_type != StreamType.HLS and not (await camera.stream_source()):
return None
else:
not_shown += 1
continue
children.append(
BrowseMediaSource(
domain=DOMAIN,
identifier=camera.entity_id,
media_class=MediaClass.VIDEO,
media_content_type=content_type,
title=camera.name,
thumbnail=f"/api/camera_proxy/{camera.entity_id}",
can_play=True,
can_expand=False,
)
)
return _media_source_for_camera(camera, content_type)
component: EntityComponent[Camera] = self.hass.data[DOMAIN]
results = await asyncio.gather(
*(_filter_browsable_camera(camera) for camera in component.entities),
return_exceptions=True,
)
children = [
result for result in results if isinstance(result, BrowseMediaSource)
]
not_shown = len(results) - len(children)
return BrowseMediaSource(
domain=DOMAIN,
identifier=None,

View File

@ -17,7 +17,7 @@ async def setup_media_source(hass):
async def test_browsing_hls(hass: HomeAssistant, mock_camera_hls) -> None:
"""Test browsing camera media source."""
"""Test browsing HLS camera media source."""
item = await media_source.async_browse_media(hass, "media-source://camera")
assert item is not None
assert item.title == "Camera"
@ -34,7 +34,7 @@ async def test_browsing_hls(hass: HomeAssistant, mock_camera_hls) -> None:
async def test_browsing_mjpeg(hass: HomeAssistant, mock_camera) -> None:
"""Test browsing camera media source."""
"""Test browsing MJPEG camera media source."""
item = await media_source.async_browse_media(hass, "media-source://camera")
assert item is not None
assert item.title == "Camera"
@ -43,15 +43,29 @@ async def test_browsing_mjpeg(hass: HomeAssistant, mock_camera) -> None:
assert item.children[0].media_content_type == "image/jpg"
async def test_browsing_filter_web_rtc(
hass: HomeAssistant, mock_camera_web_rtc
) -> None:
"""Test browsing camera media source hides non-HLS cameras."""
item = await media_source.async_browse_media(hass, "media-source://camera")
assert item is not None
assert item.title == "Camera"
assert len(item.children) == 0
assert item.not_shown == 3
async def test_browsing_web_rtc(hass: HomeAssistant, mock_camera_web_rtc) -> None:
"""Test browsing WebRTC camera media source."""
# 3 cameras:
# one only supports WebRTC (no stream source)
# one raises when getting the source
# One has a stream source, and should be the only browsable one
with patch(
"homeassistant.components.camera.Camera.stream_source",
side_effect=["test", None, Exception],
):
item = await media_source.async_browse_media(hass, "media-source://camera")
assert item is not None
assert item.title == "Camera"
assert len(item.children) == 0
assert item.not_shown == 3
# Adding stream enables HLS camera
hass.config.components.add("stream")
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].media_content_type == FORMAT_CONTENT_TYPE["hls"]
async def test_resolving(hass: HomeAssistant, mock_camera_hls) -> None: