From 4f9f54892923f558f3d495216ad50be912beddf2 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 13 Dec 2023 17:26:34 +0100 Subject: [PATCH] Add volume_step property to MediaPlayerEntity (#105574) * Add volume_step property to MediaPlayerEntity * Improve tests * Address review comments --- .../components/media_player/__init__.py | 22 ++++++++- .../media_player/test_async_helpers.py | 45 +++++++++++++++++-- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 50365f90f1f..2ca47b97275 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -454,6 +454,7 @@ class MediaPlayerEntityDescription(EntityDescription): """A class that describes media player entities.""" device_class: MediaPlayerDeviceClass | None = None + volume_step: float | None = None class MediaPlayerEntity(Entity): @@ -505,6 +506,7 @@ class MediaPlayerEntity(Entity): _attr_state: MediaPlayerState | None = None _attr_supported_features: MediaPlayerEntityFeature = MediaPlayerEntityFeature(0) _attr_volume_level: float | None = None + _attr_volume_step: float # Implement these for your media player @property @@ -533,6 +535,18 @@ class MediaPlayerEntity(Entity): """Volume level of the media player (0..1).""" return self._attr_volume_level + @property + def volume_step(self) -> float: + """Return the step to be used by the volume_up and volume_down services.""" + if hasattr(self, "_attr_volume_step"): + return self._attr_volume_step + if ( + hasattr(self, "entity_description") + and (volume_step := self.entity_description.volume_step) is not None + ): + return volume_step + return 0.1 + @property def is_volume_muted(self) -> bool | None: """Boolean if volume is currently muted.""" @@ -956,7 +970,9 @@ class MediaPlayerEntity(Entity): and self.volume_level < 1 and self.supported_features & MediaPlayerEntityFeature.VOLUME_SET ): - await self.async_set_volume_level(min(1, self.volume_level + 0.1)) + await self.async_set_volume_level( + min(1, self.volume_level + self.volume_step) + ) async def async_volume_down(self) -> None: """Turn volume down for media player. @@ -972,7 +988,9 @@ class MediaPlayerEntity(Entity): and self.volume_level > 0 and self.supported_features & MediaPlayerEntityFeature.VOLUME_SET ): - await self.async_set_volume_level(max(0, self.volume_level - 0.1)) + await self.async_set_volume_level( + max(0, self.volume_level - self.volume_step) + ) async def async_media_play_pause(self) -> None: """Play or pause the media player.""" diff --git a/tests/components/media_player/test_async_helpers.py b/tests/components/media_player/test_async_helpers.py index cf71b52c046..a24c9cc76b2 100644 --- a/tests/components/media_player/test_async_helpers.py +++ b/tests/components/media_player/test_async_helpers.py @@ -10,6 +10,7 @@ from homeassistant.const import ( STATE_PLAYING, STATE_STANDBY, ) +from homeassistant.core import HomeAssistant class ExtendedMediaPlayer(mp.MediaPlayerEntity): @@ -148,28 +149,64 @@ class SimpleMediaPlayer(mp.MediaPlayerEntity): self._state = STATE_STANDBY +class AttrMediaPlayer(SimpleMediaPlayer): + """Media player setting properties via _attr_*.""" + + _attr_volume_step = 0.2 + + +class DescrMediaPlayer(SimpleMediaPlayer): + """Media player setting properties via entity description.""" + + entity_description = mp.MediaPlayerEntityDescription(key="test", volume_step=0.3) + + @pytest.fixture(params=[ExtendedMediaPlayer, SimpleMediaPlayer]) def player(hass, request): """Return a media player.""" return request.param(hass) -async def test_volume_up(player) -> None: +@pytest.mark.parametrize( + ("player_class", "volume_step"), + [ + (ExtendedMediaPlayer, 0.1), + (SimpleMediaPlayer, 0.1), + (AttrMediaPlayer, 0.2), + (DescrMediaPlayer, 0.3), + ], +) +async def test_volume_up( + hass: HomeAssistant, player_class: type[mp.MediaPlayerEntity], volume_step: float +) -> None: """Test the volume_up and set volume methods.""" + player = player_class(hass) assert player.volume_level == 0 await player.async_set_volume_level(0.5) assert player.volume_level == 0.5 await player.async_volume_up() - assert player.volume_level == 0.6 + assert player.volume_level == 0.5 + volume_step -async def test_volume_down(player) -> None: +@pytest.mark.parametrize( + ("player_class", "volume_step"), + [ + (ExtendedMediaPlayer, 0.1), + (SimpleMediaPlayer, 0.1), + (AttrMediaPlayer, 0.2), + (DescrMediaPlayer, 0.3), + ], +) +async def test_volume_down( + hass: HomeAssistant, player_class: type[mp.MediaPlayerEntity], volume_step: float +) -> None: """Test the volume_down and set volume methods.""" + player = player_class(hass) assert player.volume_level == 0 await player.async_set_volume_level(0.5) assert player.volume_level == 0.5 await player.async_volume_down() - assert player.volume_level == 0.4 + assert player.volume_level == 0.5 - volume_step async def test_media_play_pause(player) -> None: