Add support for announce to Squeezebox media player (#129460)
* initial * Add support for announce: true to media player * Move play_announcement to _player * update snapshot * conftest update * remove conftest update * Update conftest.py * Test Updates * Updates post moving functions to library * test fixes * Review updates * Snapshot update * rebase updates * Merge updates * Review updates * Review updatespull/136885/head
parent
a45fb57595
commit
d1f0e0a70f
|
@ -36,3 +36,5 @@ CONF_BROWSE_LIMIT = "browse_limit"
|
|||
CONF_VOLUME_STEP = "volume_step"
|
||||
DEFAULT_BROWSE_LIMIT = 1000
|
||||
DEFAULT_VOLUME_STEP = 5
|
||||
ATTR_ANNOUNCE_VOLUME = "announce_volume"
|
||||
ATTR_ANNOUNCE_TIMEOUT = "announce_timeout"
|
||||
|
|
|
@ -14,6 +14,7 @@ import voluptuous as vol
|
|||
from homeassistant.components import media_source
|
||||
from homeassistant.components.media_player import (
|
||||
ATTR_MEDIA_ENQUEUE,
|
||||
ATTR_MEDIA_EXTRA,
|
||||
BrowseError,
|
||||
BrowseMedia,
|
||||
MediaPlayerEnqueue,
|
||||
|
@ -52,6 +53,8 @@ from .browse_media import (
|
|||
media_source_content_filter,
|
||||
)
|
||||
from .const import (
|
||||
ATTR_ANNOUNCE_TIMEOUT,
|
||||
ATTR_ANNOUNCE_VOLUME,
|
||||
CONF_BROWSE_LIMIT,
|
||||
CONF_VOLUME_STEP,
|
||||
DEFAULT_BROWSE_LIMIT,
|
||||
|
@ -157,6 +160,26 @@ async def async_setup_entry(
|
|||
entry.async_on_unload(async_at_start(hass, start_server_discovery))
|
||||
|
||||
|
||||
def get_announce_volume(extra: dict) -> float | None:
|
||||
"""Get announce volume from extra service data."""
|
||||
if ATTR_ANNOUNCE_VOLUME not in extra:
|
||||
return None
|
||||
announce_volume = float(extra[ATTR_ANNOUNCE_VOLUME])
|
||||
if not (0 < announce_volume <= 1):
|
||||
raise ValueError
|
||||
return announce_volume * 100
|
||||
|
||||
|
||||
def get_announce_timeout(extra: dict) -> int | None:
|
||||
"""Get announce volume from extra service data."""
|
||||
if ATTR_ANNOUNCE_TIMEOUT not in extra:
|
||||
return None
|
||||
announce_timeout = int(extra[ATTR_ANNOUNCE_TIMEOUT])
|
||||
if announce_timeout < 1:
|
||||
raise ValueError
|
||||
return announce_timeout
|
||||
|
||||
|
||||
class SqueezeBoxMediaPlayerEntity(
|
||||
CoordinatorEntity[SqueezeBoxPlayerUpdateCoordinator], MediaPlayerEntity
|
||||
):
|
||||
|
@ -184,6 +207,7 @@ class SqueezeBoxMediaPlayerEntity(
|
|||
| MediaPlayerEntityFeature.STOP
|
||||
| MediaPlayerEntityFeature.GROUPING
|
||||
| MediaPlayerEntityFeature.MEDIA_ENQUEUE
|
||||
| MediaPlayerEntityFeature.MEDIA_ANNOUNCE
|
||||
)
|
||||
_attr_has_entity_name = True
|
||||
_attr_name = None
|
||||
|
@ -437,7 +461,11 @@ class SqueezeBoxMediaPlayerEntity(
|
|||
await self.coordinator.async_refresh()
|
||||
|
||||
async def async_play_media(
|
||||
self, media_type: MediaType | str, media_id: str, **kwargs: Any
|
||||
self,
|
||||
media_type: MediaType | str,
|
||||
media_id: str,
|
||||
announce: bool | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Send the play_media command to the media player."""
|
||||
index = None
|
||||
|
@ -460,6 +488,32 @@ class SqueezeBoxMediaPlayerEntity(
|
|||
)
|
||||
media_id = play_item.url
|
||||
|
||||
if announce:
|
||||
if media_type not in MediaType.MUSIC:
|
||||
raise ServiceValidationError(
|
||||
"Announcements must have media type of 'music'. Playlists are not supported"
|
||||
)
|
||||
|
||||
extra = kwargs.get(ATTR_MEDIA_EXTRA, {})
|
||||
cmd = "announce"
|
||||
try:
|
||||
announce_volume = get_announce_volume(extra)
|
||||
except ValueError:
|
||||
raise ServiceValidationError(
|
||||
f"{ATTR_ANNOUNCE_VOLUME} must be a number greater than 0 and less than or equal to 1"
|
||||
) from None
|
||||
else:
|
||||
self._player.set_announce_volume(announce_volume)
|
||||
|
||||
try:
|
||||
announce_timeout = get_announce_timeout(extra)
|
||||
except ValueError:
|
||||
raise ServiceValidationError(
|
||||
f"{ATTR_ANNOUNCE_TIMEOUT} must be a whole number greater than 0"
|
||||
) from None
|
||||
else:
|
||||
self._player.set_announce_timeout(announce_timeout)
|
||||
|
||||
if media_type in MediaType.MUSIC:
|
||||
if not media_id.startswith(SQUEEZEBOX_SOURCE_STRINGS):
|
||||
# do not process special squeezebox "source" media ids
|
||||
|
|
|
@ -120,6 +120,11 @@ def config_entry(hass: HomeAssistant) -> MockConfigEntry:
|
|||
return config_entry
|
||||
|
||||
|
||||
async def mock_async_play_announcement(media_id: str) -> bool:
|
||||
"""Mock the announcement."""
|
||||
return True
|
||||
|
||||
|
||||
async def mock_async_browse(
|
||||
media_type: MediaType, limit: int, browse_id: tuple | None = None
|
||||
) -> dict | None:
|
||||
|
@ -222,6 +227,11 @@ def mock_pysqueezebox_player(uuid: str) -> MagicMock:
|
|||
mock_player.generate_image_url_from_track_id = MagicMock(
|
||||
return_value="http://lms.internal:9000/html/images/favorites.png"
|
||||
)
|
||||
mock_player.set_announce_volume = MagicMock(return_value=True)
|
||||
mock_player.set_announce_timeout = MagicMock(return_value=True)
|
||||
mock_player.async_play_announcement = AsyncMock(
|
||||
side_effect=mock_async_play_announcement
|
||||
)
|
||||
mock_player.name = TEST_PLAYER_NAME
|
||||
mock_player.player_id = uuid
|
||||
mock_player.mode = "stop"
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
'original_name': None,
|
||||
'platform': 'squeezebox',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <MediaPlayerEntityFeature: 3078079>,
|
||||
'supported_features': <MediaPlayerEntityFeature: 4126655>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff',
|
||||
'unit_of_measurement': None,
|
||||
|
@ -88,7 +88,7 @@
|
|||
}),
|
||||
'repeat': <RepeatMode.OFF: 'off'>,
|
||||
'shuffle': False,
|
||||
'supported_features': <MediaPlayerEntityFeature: 3078079>,
|
||||
'supported_features': <MediaPlayerEntityFeature: 4126655>,
|
||||
'volume_level': 0.01,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
|
|
@ -10,9 +10,11 @@ from syrupy import SnapshotAssertion
|
|||
|
||||
from homeassistant.components.media_player import (
|
||||
ATTR_GROUP_MEMBERS,
|
||||
ATTR_MEDIA_ANNOUNCE,
|
||||
ATTR_MEDIA_CONTENT_ID,
|
||||
ATTR_MEDIA_CONTENT_TYPE,
|
||||
ATTR_MEDIA_ENQUEUE,
|
||||
ATTR_MEDIA_EXTRA,
|
||||
ATTR_MEDIA_POSITION,
|
||||
ATTR_MEDIA_POSITION_UPDATED_AT,
|
||||
ATTR_MEDIA_REPEAT,
|
||||
|
@ -31,6 +33,8 @@ from homeassistant.components.media_player import (
|
|||
RepeatMode,
|
||||
)
|
||||
from homeassistant.components.squeezebox.const import (
|
||||
ATTR_ANNOUNCE_TIMEOUT,
|
||||
ATTR_ANNOUNCE_VOLUME,
|
||||
DISCOVERY_INTERVAL,
|
||||
DOMAIN,
|
||||
PLAYER_UPDATE_INTERVAL,
|
||||
|
@ -436,6 +440,115 @@ async def test_squeezebox_play(
|
|||
configured_player.async_play.assert_called_once()
|
||||
|
||||
|
||||
async def test_squeezebox_play_media_with_announce(
|
||||
hass: HomeAssistant, configured_player: MagicMock
|
||||
) -> None:
|
||||
"""Test play service call with announce."""
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_player",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
configured_player.async_load_url.assert_called_once_with(
|
||||
FAKE_VALID_ITEM_ID, "announce"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"announce_volume",
|
||||
["0.2", 0.2],
|
||||
)
|
||||
async def test_squeezebox_play_media_with_announce_volume(
|
||||
hass: HomeAssistant, configured_player: MagicMock, announce_volume: str | int
|
||||
) -> None:
|
||||
"""Test play service call with announce."""
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_player",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
ATTR_MEDIA_EXTRA: {ATTR_ANNOUNCE_VOLUME: announce_volume},
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
configured_player.set_announce_volume.assert_called_once_with(20)
|
||||
configured_player.async_load_url.assert_called_once_with(
|
||||
FAKE_VALID_ITEM_ID, "announce"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("announce_volume", ["1.1", 1.1, "text", "-1", -1, 0, "0"])
|
||||
async def test_squeezebox_play_media_with_announce_volume_invalid(
|
||||
hass: HomeAssistant, configured_player: MagicMock, announce_volume: str | int
|
||||
) -> None:
|
||||
"""Test play service call with announce and volume zero."""
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_player",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
ATTR_MEDIA_EXTRA: {ATTR_ANNOUNCE_VOLUME: announce_volume},
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("announce_timeout", ["-1", "text", -1, 0, "0"])
|
||||
async def test_squeezebox_play_media_with_announce_timeout_invalid(
|
||||
hass: HomeAssistant, configured_player: MagicMock, announce_timeout: str | int
|
||||
) -> None:
|
||||
"""Test play service call with announce and invalid timeout."""
|
||||
with pytest.raises(ServiceValidationError):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_player",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
ATTR_MEDIA_EXTRA: {ATTR_ANNOUNCE_TIMEOUT: announce_timeout},
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("announce_timeout", ["100", 100])
|
||||
async def test_squeezebox_play_media_with_announce_timeout(
|
||||
hass: HomeAssistant, configured_player: MagicMock, announce_timeout: str | int
|
||||
) -> None:
|
||||
"""Test play service call with announce."""
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN,
|
||||
SERVICE_PLAY_MEDIA,
|
||||
{
|
||||
ATTR_ENTITY_ID: "media_player.test_player",
|
||||
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
|
||||
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
|
||||
ATTR_MEDIA_ANNOUNCE: True,
|
||||
ATTR_MEDIA_EXTRA: {ATTR_ANNOUNCE_TIMEOUT: announce_timeout},
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
configured_player.set_announce_timeout.assert_called_once_with(100)
|
||||
configured_player.async_load_url.assert_called_once_with(
|
||||
FAKE_VALID_ITEM_ID, "announce"
|
||||
)
|
||||
|
||||
|
||||
async def test_squeezebox_play_pause(
|
||||
hass: HomeAssistant, configured_player: MagicMock
|
||||
) -> None:
|
||||
|
|
Loading…
Reference in New Issue