core/tests/components/esphome/test_media_player.py

437 lines
14 KiB
Python

"""Test ESPHome media_players."""
from collections.abc import Awaitable, Callable
from unittest.mock import AsyncMock, Mock, call, patch
from aioesphomeapi import (
APIClient,
EntityInfo,
EntityState,
MediaPlayerCommand,
MediaPlayerEntityState,
MediaPlayerFormatPurpose,
MediaPlayerInfo,
MediaPlayerState,
MediaPlayerSupportedFormat,
UserService,
)
import pytest
from homeassistant.components import media_source
from homeassistant.components.media_player import (
ATTR_MEDIA_ANNOUNCE,
ATTR_MEDIA_CONTENT_ID,
ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_EXTRA,
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
DOMAIN as MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_STOP,
SERVICE_PLAY_MEDIA,
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET,
BrowseMedia,
MediaClass,
MediaType,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
import homeassistant.helpers.device_registry as dr
from homeassistant.setup import async_setup_component
from .conftest import MockESPHomeDevice
from tests.common import mock_platform
from tests.typing import WebSocketGenerator
async def test_media_player_entity(
hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
) -> None:
"""Test a generic media_player entity."""
entity_info = [
MediaPlayerInfo(
object_id="mymedia_player",
key=1,
name="my media_player",
unique_id="my_media_player",
supports_pause=True,
)
]
states = [
MediaPlayerEntityState(
key=1, volume=50, muted=True, state=MediaPlayerState.PAUSED
)
]
user_service: list[UserService] = []
await mock_generic_device_entry(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
)
state = hass.states.get("media_player.test_mymedia_player")
assert state is not None
assert state.state == "paused"
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_MUTE,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_VOLUME_MUTED: True,
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls(
[call(1, command=MediaPlayerCommand.MUTE)]
)
mock_client.media_player_command.reset_mock()
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_MUTE,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_VOLUME_MUTED: True,
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls(
[call(1, command=MediaPlayerCommand.MUTE)]
)
mock_client.media_player_command.reset_mock()
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_SET,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_VOLUME_LEVEL: 0.5,
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls([call(1, volume=0.5)])
mock_client.media_player_command.reset_mock()
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_PAUSE,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls(
[call(1, command=MediaPlayerCommand.PAUSE)]
)
mock_client.media_player_command.reset_mock()
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_PLAY,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls(
[call(1, command=MediaPlayerCommand.PLAY)]
)
mock_client.media_player_command.reset_mock()
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_STOP,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls(
[call(1, command=MediaPlayerCommand.STOP)]
)
mock_client.media_player_command.reset_mock()
async def test_media_player_entity_with_source(
hass: HomeAssistant,
mock_client: APIClient,
hass_ws_client: WebSocketGenerator,
mock_generic_device_entry,
) -> None:
"""Test a generic media_player entity media source."""
await async_setup_component(hass, "media_source", {"media_source": {}})
await hass.async_block_till_done()
esphome_platform_mock = Mock(
async_get_media_browser_root_object=AsyncMock(
return_value=[
BrowseMedia(
title="Spotify",
media_class=MediaClass.APP,
media_content_id="",
media_content_type="spotify",
thumbnail="https://brands.home-assistant.io/_/spotify/logo.png",
can_play=False,
can_expand=True,
)
]
),
async_browse_media=AsyncMock(
return_value=BrowseMedia(
title="Spotify Favourites",
media_class=MediaClass.PLAYLIST,
media_content_id="",
media_content_type="spotify",
can_play=True,
can_expand=False,
)
),
async_play_media=AsyncMock(return_value=False),
)
mock_platform(hass, "test.esphome", esphome_platform_mock)
await async_setup_component(hass, "test", {"test": {}})
await async_setup_component(hass, "media_source", {"media_source": {}})
await hass.async_block_till_done()
entity_info = [
MediaPlayerInfo(
object_id="mymedia_player",
key=1,
name="my media_player",
unique_id="my_media_player",
supports_pause=True,
)
]
states = [
MediaPlayerEntityState(
key=1, volume=50, muted=True, state=MediaPlayerState.PLAYING
)
]
user_service: list[UserService] = []
await mock_generic_device_entry(
mock_client=mock_client,
entity_info=entity_info,
user_service=user_service,
states=states,
)
state = hass.states.get("media_player.test_mymedia_player")
assert state is not None
assert state.state == "playing"
with pytest.raises(media_source.error.Unresolvable):
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
ATTR_MEDIA_CONTENT_ID: "media-source://local/xz",
},
blocking=True,
)
mock_client.media_player_command.reset_mock()
play_media = media_source.PlayMedia(
url="http://www.example.com/xy.mp3",
mime_type="audio/mp3",
)
await hass.async_block_till_done()
with patch(
"homeassistant.components.media_source.async_resolve_media",
return_value=play_media,
):
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_CONTENT_TYPE: "audio/mp3",
ATTR_MEDIA_CONTENT_ID: "media-source://local/xy",
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls(
[call(1, media_url="http://www.example.com/xy.mp3", announcement=None)]
)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "media_player/browse_media",
"entity_id": "media_player.test_mymedia_player",
}
)
response = await client.receive_json()
assert response["success"]
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_CONTENT_TYPE: MediaType.URL,
ATTR_MEDIA_CONTENT_ID: "media-source://tts?message=hello",
ATTR_MEDIA_ANNOUNCE: True,
},
blocking=True,
)
mock_client.media_player_command.assert_has_calls(
[call(1, media_url="media-source://tts?message=hello", announcement=True)]
)
async def test_media_player_proxy(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_client: APIClient,
mock_esphome_device: Callable[
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
Awaitable[MockESPHomeDevice],
],
) -> None:
"""Test a media_player entity with a proxy URL."""
mock_device: MockESPHomeDevice = await mock_esphome_device(
mock_client=mock_client,
entity_info=[
MediaPlayerInfo(
object_id="mymedia_player",
key=1,
name="my media_player",
unique_id="my_media_player",
supports_pause=True,
supported_formats=[
MediaPlayerSupportedFormat(
format="flac",
sample_rate=0, # source rate
num_channels=0, # source channels
purpose=MediaPlayerFormatPurpose.DEFAULT,
sample_bytes=0, # source width
),
MediaPlayerSupportedFormat(
format="wav",
sample_rate=16000,
num_channels=1,
purpose=MediaPlayerFormatPurpose.ANNOUNCEMENT,
sample_bytes=2,
),
MediaPlayerSupportedFormat(
format="mp3",
sample_rate=48000,
num_channels=2,
purpose=MediaPlayerFormatPurpose.DEFAULT,
),
],
)
],
user_service=[],
states=[
MediaPlayerEntityState(
key=1, volume=50, muted=False, state=MediaPlayerState.PAUSED
)
],
)
await hass.async_block_till_done()
dev = device_registry.async_get_device(
connections={(dr.CONNECTION_NETWORK_MAC, mock_device.entry.unique_id)}
)
assert dev is not None
state = hass.states.get("media_player.test_mymedia_player")
assert state is not None
assert state.state == "paused"
media_url = "http://127.0.0.1/test.mp3"
proxy_url = f"/api/esphome/ffmpeg_proxy/{dev.id}/test-id.flac"
with (
patch(
"homeassistant.components.esphome.media_player.async_create_proxy_url",
return_value=proxy_url,
) as mock_async_create_proxy_url,
):
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
ATTR_MEDIA_CONTENT_ID: media_url,
},
blocking=True,
)
# Should be the default format
mock_async_create_proxy_url.assert_called_once()
device_id = mock_async_create_proxy_url.call_args[0][1]
mock_async_create_proxy_url.assert_called_once_with(
hass,
device_id,
media_url,
media_format="flac",
rate=None,
channels=None,
width=None,
)
media_args = mock_client.media_player_command.call_args.kwargs
assert not media_args["announcement"]
# Reset
mock_async_create_proxy_url.reset_mock()
# Set announcement flag
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
ATTR_MEDIA_CONTENT_ID: media_url,
ATTR_MEDIA_ANNOUNCE: True,
},
blocking=True,
)
# Should be the announcement format
mock_async_create_proxy_url.assert_called_once()
device_id = mock_async_create_proxy_url.call_args[0][1]
mock_async_create_proxy_url.assert_called_once_with(
hass,
device_id,
media_url,
media_format="wav",
rate=16000,
channels=1,
width=2,
)
media_args = mock_client.media_player_command.call_args.kwargs
assert media_args["announcement"]
# test with bypass_proxy flag
mock_async_create_proxy_url.reset_mock()
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_mymedia_player",
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
ATTR_MEDIA_CONTENT_ID: media_url,
ATTR_MEDIA_EXTRA: {
"bypass_proxy": True,
},
},
blocking=True,
)
mock_async_create_proxy_url.assert_not_called()
media_args = mock_client.media_player_command.call_args.kwargs
assert media_args["media_url"] == media_url