Add media source support to unifiprotect (#67570)

This commit is contained in:
Paulus Schoutsen 2022-03-14 13:16:22 -07:00 committed by GitHub
parent 7fc0ffd5c5
commit a9fd744247
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 9 deletions

View File

@ -7,13 +7,19 @@ from typing import Any
from pyunifiprotect.data import Camera from pyunifiprotect.data import Camera
from pyunifiprotect.exceptions import StreamError from pyunifiprotect.exceptions import StreamError
from homeassistant.components import media_source
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
BrowseMedia,
MediaPlayerDeviceClass, MediaPlayerDeviceClass,
MediaPlayerEntity, MediaPlayerEntity,
MediaPlayerEntityDescription, MediaPlayerEntityDescription,
) )
from homeassistant.components.media_player.browse_media import (
async_process_play_media_url,
)
from homeassistant.components.media_player.const import ( from homeassistant.components.media_player.const import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_MUSIC,
SUPPORT_BROWSE_MEDIA,
SUPPORT_PLAY_MEDIA, SUPPORT_PLAY_MEDIA,
SUPPORT_STOP, SUPPORT_STOP,
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_SET,
@ -74,7 +80,11 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
self._attr_name = f"{self.device.name} Speaker" self._attr_name = f"{self.device.name} Speaker"
self._attr_supported_features = ( self._attr_supported_features = (
SUPPORT_PLAY_MEDIA | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_STEP | SUPPORT_STOP SUPPORT_PLAY_MEDIA
| SUPPORT_VOLUME_SET
| SUPPORT_VOLUME_STEP
| SUPPORT_STOP
| SUPPORT_BROWSE_MEDIA
) )
self._attr_media_content_type = MEDIA_TYPE_MUSIC self._attr_media_content_type = MEDIA_TYPE_MUSIC
@ -112,16 +122,20 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
self, media_type: str, media_id: str, **kwargs: Any self, media_type: str, media_id: str, **kwargs: Any
) -> None: ) -> None:
"""Play a piece of media.""" """Play a piece of media."""
if media_source.is_media_source_id(media_id):
media_type = MEDIA_TYPE_MUSIC
play_item = await media_source.async_resolve_media(self.hass, media_id)
media_id = async_process_play_media_url(self.hass, play_item.url)
if media_type != MEDIA_TYPE_MUSIC: if media_type != MEDIA_TYPE_MUSIC:
raise ValueError("Only music media type is supported") raise HomeAssistantError("Only music media type is supported")
_LOGGER.debug("Playing Media %s for %s Speaker", media_id, self.device.name) _LOGGER.debug("Playing Media %s for %s Speaker", media_id, self.device.name)
await self.async_media_stop() await self.async_media_stop()
try: try:
await self.device.play_audio(media_id, blocking=False) await self.device.play_audio(media_id, blocking=False)
except StreamError as err: except StreamError as err:
raise HomeAssistantError from err raise HomeAssistantError(err) from err
else: else:
# update state after starting player # update state after starting player
self._async_updated_event() self._async_updated_event()
@ -129,3 +143,13 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
await self.device.wait_until_audio_completes() await self.device.wait_until_audio_completes()
self._async_updated_event() self._async_updated_event()
async def async_browse_media(
self, media_content_type: str | None = None, media_content_id: str | None = None
) -> BrowseMedia:
"""Implement the websocket media browsing helper."""
return await media_source.async_browse_media(
self.hass,
media_content_id,
content_filter=lambda item: item.media_content_type.startswith("audio/"),
)

View File

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from copy import copy from copy import copy
from unittest.mock import AsyncMock, Mock from unittest.mock import AsyncMock, Mock, patch
import pytest import pytest
from pyunifiprotect.data import Camera from pyunifiprotect.data import Camera
@ -80,7 +80,7 @@ async def test_media_player_setup(
assert state assert state
assert state.state == STATE_IDLE assert state.state == STATE_IDLE
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 5636 assert state.attributes[ATTR_SUPPORTED_FEATURES] == 136708
assert state.attributes[ATTR_MEDIA_CONTENT_TYPE] == "music" assert state.attributes[ATTR_MEDIA_CONTENT_TYPE] == "music"
assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == expected_volume assert state.attributes[ATTR_MEDIA_VOLUME_LEVEL] == expected_volume
@ -166,7 +166,6 @@ async def test_media_player_play(
camera: tuple[Camera, str], camera: tuple[Camera, str],
): ):
"""Test media_player entity test play_media.""" """Test media_player entity test play_media."""
camera[0].__fields__["stop_audio"] = Mock() camera[0].__fields__["stop_audio"] = Mock()
camera[0].__fields__["play_audio"] = Mock() camera[0].__fields__["play_audio"] = Mock()
camera[0].__fields__["wait_until_audio_completes"] = Mock() camera[0].__fields__["wait_until_audio_completes"] = Mock()
@ -179,13 +178,48 @@ async def test_media_player_play(
"play_media", "play_media",
{ {
ATTR_ENTITY_ID: camera[1], ATTR_ENTITY_ID: camera[1],
"media_content_id": "/test.mp3", "media_content_id": "http://example.com/test.mp3",
"media_content_type": "music", "media_content_type": "music",
}, },
blocking=True, blocking=True,
) )
camera[0].play_audio.assert_called_once_with("/test.mp3", blocking=False) camera[0].play_audio.assert_called_once_with(
"http://example.com/test.mp3", blocking=False
)
camera[0].wait_until_audio_completes.assert_called_once()
async def test_media_player_play_media_source(
hass: HomeAssistant,
camera: tuple[Camera, str],
):
"""Test media_player entity test play_media."""
camera[0].__fields__["stop_audio"] = Mock()
camera[0].__fields__["play_audio"] = Mock()
camera[0].__fields__["wait_until_audio_completes"] = Mock()
camera[0].stop_audio = AsyncMock()
camera[0].play_audio = AsyncMock()
camera[0].wait_until_audio_completes = AsyncMock()
with patch(
"homeassistant.components.media_source.async_resolve_media",
return_value=Mock(url="http://example.com/test.mp3"),
):
await hass.services.async_call(
"media_player",
"play_media",
{
ATTR_ENTITY_ID: camera[1],
"media_content_id": "media-source://some_source/some_id",
"media_content_type": "audio/mpeg",
},
blocking=True,
)
camera[0].play_audio.assert_called_once_with(
"http://example.com/test.mp3", blocking=False
)
camera[0].wait_until_audio_completes.assert_called_once() camera[0].wait_until_audio_completes.assert_called_once()
@ -198,7 +232,7 @@ async def test_media_player_play_invalid(
camera[0].__fields__["play_audio"] = Mock() camera[0].__fields__["play_audio"] = Mock()
camera[0].play_audio = AsyncMock() camera[0].play_audio = AsyncMock()
with pytest.raises(ValueError): with pytest.raises(HomeAssistantError):
await hass.services.async_call( await hass.services.async_call(
"media_player", "media_player",
"play_media", "play_media",