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 reviewpull/110953/head
parent
8fa347fb4c
commit
e879ab0eef
|
@ -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]
|
||||
if stream_type != StreamType.HLS and not (await camera.stream_source()):
|
||||
return None
|
||||
|
||||
else:
|
||||
not_shown += 1
|
||||
continue
|
||||
return _media_source_for_camera(camera, content_type)
|
||||
|
||||
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,
|
||||
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,
|
||||
|
|
|
@ -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,16 +43,30 @@ 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."""
|
||||
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:
|
||||
"""Test resolving."""
|
||||
|
|
Loading…
Reference in New Issue