Add EntityFeature enum to Media Player (#69119)

This commit is contained in:
Franck Nijhof 2022-04-03 05:58:23 +02:00 committed by GitHub
parent 721db6d962
commit 17403f930f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 158 additions and 114 deletions

View File

@ -10,24 +10,7 @@ from homeassistant.components.media_player.const import (
MEDIA_TYPE_MUSIC,
MEDIA_TYPE_TVSHOW,
REPEAT_MODE_OFF,
SUPPORT_CLEAR_PLAYLIST,
SUPPORT_GROUPING,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
SUPPORT_PLAY,
SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK,
SUPPORT_REPEAT_SET,
SUPPORT_SEEK,
SUPPORT_SELECT_SOUND_MODE,
SUPPORT_SELECT_SOURCE,
SUPPORT_SHUFFLE_SET,
SUPPORT_STOP,
SUPPORT_TURN_OFF,
SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE,
SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP,
MediaPlayerEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING
@ -75,48 +58,48 @@ SOUND_MODE_LIST = ["Music", "Movie"]
DEFAULT_SOUND_MODE = "Music"
YOUTUBE_PLAYER_SUPPORT = (
SUPPORT_PAUSE
| SUPPORT_VOLUME_SET
| SUPPORT_VOLUME_MUTE
| SUPPORT_TURN_ON
| SUPPORT_TURN_OFF
| SUPPORT_PLAY_MEDIA
| SUPPORT_PLAY
| SUPPORT_SHUFFLE_SET
| SUPPORT_SELECT_SOUND_MODE
| SUPPORT_SEEK
| SUPPORT_STOP
MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_MUTE
| MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.PLAY_MEDIA
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.SHUFFLE_SET
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
| MediaPlayerEntityFeature.SEEK
| MediaPlayerEntityFeature.STOP
)
MUSIC_PLAYER_SUPPORT = (
SUPPORT_PAUSE
| SUPPORT_VOLUME_SET
| SUPPORT_VOLUME_MUTE
| SUPPORT_TURN_ON
| SUPPORT_TURN_OFF
| SUPPORT_CLEAR_PLAYLIST
| SUPPORT_GROUPING
| SUPPORT_PLAY
| SUPPORT_SHUFFLE_SET
| SUPPORT_REPEAT_SET
| SUPPORT_VOLUME_STEP
| SUPPORT_PREVIOUS_TRACK
| SUPPORT_NEXT_TRACK
| SUPPORT_SELECT_SOUND_MODE
| SUPPORT_STOP
MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_MUTE
| MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.CLEAR_PLAYLIST
| MediaPlayerEntityFeature.GROUPING
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.SHUFFLE_SET
| MediaPlayerEntityFeature.REPEAT_SET
| MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.PREVIOUS_TRACK
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
| MediaPlayerEntityFeature.STOP
)
NETFLIX_PLAYER_SUPPORT = (
SUPPORT_PAUSE
| SUPPORT_TURN_ON
| SUPPORT_TURN_OFF
| SUPPORT_SELECT_SOURCE
| SUPPORT_PLAY
| SUPPORT_SHUFFLE_SET
| SUPPORT_PREVIOUS_TRACK
| SUPPORT_NEXT_TRACK
| SUPPORT_SELECT_SOUND_MODE
| SUPPORT_STOP
MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.SHUFFLE_SET
| MediaPlayerEntityFeature.PREVIOUS_TRACK
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
| MediaPlayerEntityFeature.STOP
)

View File

@ -126,6 +126,7 @@ from .const import ( # noqa: F401
SUPPORT_VOLUME_MUTE,
SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP,
MediaPlayerEntityFeature,
)
from .errors import BrowseError
@ -241,52 +242,61 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
await component.async_setup(config)
component.async_register_entity_service(
SERVICE_TURN_ON, {}, "async_turn_on", [SUPPORT_TURN_ON]
SERVICE_TURN_ON, {}, "async_turn_on", [MediaPlayerEntityFeature.TURN_ON]
)
component.async_register_entity_service(
SERVICE_TURN_OFF, {}, "async_turn_off", [SUPPORT_TURN_OFF]
SERVICE_TURN_OFF, {}, "async_turn_off", [MediaPlayerEntityFeature.TURN_OFF]
)
component.async_register_entity_service(
SERVICE_TOGGLE, {}, "async_toggle", [SUPPORT_TURN_OFF | SUPPORT_TURN_ON]
SERVICE_TOGGLE,
{},
"async_toggle",
[MediaPlayerEntityFeature.TURN_OFF | MediaPlayerEntityFeature.TURN_ON],
)
component.async_register_entity_service(
SERVICE_VOLUME_UP,
{},
"async_volume_up",
[SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP],
[MediaPlayerEntityFeature.VOLUME_SET, MediaPlayerEntityFeature.VOLUME_STEP],
)
component.async_register_entity_service(
SERVICE_VOLUME_DOWN,
{},
"async_volume_down",
[SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP],
[MediaPlayerEntityFeature.VOLUME_SET, MediaPlayerEntityFeature.VOLUME_STEP],
)
component.async_register_entity_service(
SERVICE_MEDIA_PLAY_PAUSE,
{},
"async_media_play_pause",
[SUPPORT_PLAY | SUPPORT_PAUSE],
[MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE],
)
component.async_register_entity_service(
SERVICE_MEDIA_PLAY, {}, "async_media_play", [SUPPORT_PLAY]
SERVICE_MEDIA_PLAY, {}, "async_media_play", [MediaPlayerEntityFeature.PLAY]
)
component.async_register_entity_service(
SERVICE_MEDIA_PAUSE, {}, "async_media_pause", [SUPPORT_PAUSE]
SERVICE_MEDIA_PAUSE, {}, "async_media_pause", [MediaPlayerEntityFeature.PAUSE]
)
component.async_register_entity_service(
SERVICE_MEDIA_STOP, {}, "async_media_stop", [SUPPORT_STOP]
SERVICE_MEDIA_STOP, {}, "async_media_stop", [MediaPlayerEntityFeature.STOP]
)
component.async_register_entity_service(
SERVICE_MEDIA_NEXT_TRACK, {}, "async_media_next_track", [SUPPORT_NEXT_TRACK]
SERVICE_MEDIA_NEXT_TRACK,
{},
"async_media_next_track",
[MediaPlayerEntityFeature.NEXT_TRACK],
)
component.async_register_entity_service(
SERVICE_MEDIA_PREVIOUS_TRACK,
{},
"async_media_previous_track",
[SUPPORT_PREVIOUS_TRACK],
[MediaPlayerEntityFeature.PREVIOUS_TRACK],
)
component.async_register_entity_service(
SERVICE_CLEAR_PLAYLIST, {}, "async_clear_playlist", [SUPPORT_CLEAR_PLAYLIST]
SERVICE_CLEAR_PLAYLIST,
{},
"async_clear_playlist",
[MediaPlayerEntityFeature.CLEAR_PLAYLIST],
)
component.async_register_entity_service(
SERVICE_VOLUME_SET,
@ -297,7 +307,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_rename_keys(volume=ATTR_MEDIA_VOLUME_LEVEL),
),
"async_set_volume_level",
[SUPPORT_VOLUME_SET],
[MediaPlayerEntityFeature.VOLUME_SET],
)
component.async_register_entity_service(
SERVICE_VOLUME_MUTE,
@ -308,7 +318,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_rename_keys(mute=ATTR_MEDIA_VOLUME_MUTED),
),
"async_mute_volume",
[SUPPORT_VOLUME_MUTE],
[MediaPlayerEntityFeature.VOLUME_MUTE],
)
component.async_register_entity_service(
SERVICE_MEDIA_SEEK,
@ -319,25 +329,25 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
_rename_keys(position=ATTR_MEDIA_SEEK_POSITION),
),
"async_media_seek",
[SUPPORT_SEEK],
[MediaPlayerEntityFeature.SEEK],
)
component.async_register_entity_service(
SERVICE_JOIN,
{vol.Required(ATTR_GROUP_MEMBERS): vol.All(cv.ensure_list, [cv.entity_id])},
"async_join_players",
[SUPPORT_GROUPING],
[MediaPlayerEntityFeature.GROUPING],
)
component.async_register_entity_service(
SERVICE_SELECT_SOURCE,
{vol.Required(ATTR_INPUT_SOURCE): cv.string},
"async_select_source",
[SUPPORT_SELECT_SOURCE],
[MediaPlayerEntityFeature.SELECT_SOURCE],
)
component.async_register_entity_service(
SERVICE_SELECT_SOUND_MODE,
{vol.Required(ATTR_SOUND_MODE): cv.string},
"async_select_sound_mode",
[SUPPORT_SELECT_SOUND_MODE],
[MediaPlayerEntityFeature.SELECT_SOUND_MODE],
)
component.async_register_entity_service(
SERVICE_PLAY_MEDIA,
@ -350,23 +360,23 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
),
),
"async_play_media",
[SUPPORT_PLAY_MEDIA],
[MediaPlayerEntityFeature.PLAY_MEDIA],
)
component.async_register_entity_service(
SERVICE_SHUFFLE_SET,
{vol.Required(ATTR_MEDIA_SHUFFLE): cv.boolean},
"async_set_shuffle",
[SUPPORT_SHUFFLE_SET],
[MediaPlayerEntityFeature.SHUFFLE_SET],
)
component.async_register_entity_service(
SERVICE_UNJOIN, {}, "async_unjoin_player", [SUPPORT_GROUPING]
SERVICE_UNJOIN, {}, "async_unjoin_player", [MediaPlayerEntityFeature.GROUPING]
)
component.async_register_entity_service(
SERVICE_REPEAT_SET,
{vol.Required(ATTR_MEDIA_REPEAT): vol.In(REPEAT_MODES)},
"async_set_repeat",
[SUPPORT_REPEAT_SET],
[MediaPlayerEntityFeature.REPEAT_SET],
)
return True
@ -766,72 +776,74 @@ class MediaPlayerEntity(Entity):
@property
def support_play(self):
"""Boolean if play is supported."""
return bool(self.supported_features & SUPPORT_PLAY)
return bool(self.supported_features & MediaPlayerEntityFeature.PLAY)
@property
def support_pause(self):
"""Boolean if pause is supported."""
return bool(self.supported_features & SUPPORT_PAUSE)
return bool(self.supported_features & MediaPlayerEntityFeature.PAUSE)
@property
def support_stop(self):
"""Boolean if stop is supported."""
return bool(self.supported_features & SUPPORT_STOP)
return bool(self.supported_features & MediaPlayerEntityFeature.STOP)
@property
def support_seek(self):
"""Boolean if seek is supported."""
return bool(self.supported_features & SUPPORT_SEEK)
return bool(self.supported_features & MediaPlayerEntityFeature.SEEK)
@property
def support_volume_set(self):
"""Boolean if setting volume is supported."""
return bool(self.supported_features & SUPPORT_VOLUME_SET)
return bool(self.supported_features & MediaPlayerEntityFeature.VOLUME_SET)
@property
def support_volume_mute(self):
"""Boolean if muting volume is supported."""
return bool(self.supported_features & SUPPORT_VOLUME_MUTE)
return bool(self.supported_features & MediaPlayerEntityFeature.VOLUME_MUTE)
@property
def support_previous_track(self):
"""Boolean if previous track command supported."""
return bool(self.supported_features & SUPPORT_PREVIOUS_TRACK)
return bool(self.supported_features & MediaPlayerEntityFeature.PREVIOUS_TRACK)
@property
def support_next_track(self):
"""Boolean if next track command supported."""
return bool(self.supported_features & SUPPORT_NEXT_TRACK)
return bool(self.supported_features & MediaPlayerEntityFeature.NEXT_TRACK)
@property
def support_play_media(self):
"""Boolean if play media command supported."""
return bool(self.supported_features & SUPPORT_PLAY_MEDIA)
return bool(self.supported_features & MediaPlayerEntityFeature.PLAY_MEDIA)
@property
def support_select_source(self):
"""Boolean if select source command supported."""
return bool(self.supported_features & SUPPORT_SELECT_SOURCE)
return bool(self.supported_features & MediaPlayerEntityFeature.SELECT_SOURCE)
@property
def support_select_sound_mode(self):
"""Boolean if select sound mode command supported."""
return bool(self.supported_features & SUPPORT_SELECT_SOUND_MODE)
return bool(
self.supported_features & MediaPlayerEntityFeature.SELECT_SOUND_MODE
)
@property
def support_clear_playlist(self):
"""Boolean if clear playlist command supported."""
return bool(self.supported_features & SUPPORT_CLEAR_PLAYLIST)
return bool(self.supported_features & MediaPlayerEntityFeature.CLEAR_PLAYLIST)
@property
def support_shuffle_set(self):
"""Boolean if shuffle is supported."""
return bool(self.supported_features & SUPPORT_SHUFFLE_SET)
return bool(self.supported_features & MediaPlayerEntityFeature.SHUFFLE_SET)
@property
def support_grouping(self):
"""Boolean if player grouping is supported."""
return bool(self.supported_features & SUPPORT_GROUPING)
return bool(self.supported_features & MediaPlayerEntityFeature.GROUPING)
async def async_toggle(self):
"""Toggle the power on the media player."""
@ -853,7 +865,10 @@ class MediaPlayerEntity(Entity):
await self.hass.async_add_executor_job(self.volume_up)
return
if self.volume_level < 1 and self.supported_features & SUPPORT_VOLUME_SET:
if (
self.volume_level < 1
and self.supported_features & MediaPlayerEntityFeature.VOLUME_SET
):
await self.async_set_volume_level(min(1, self.volume_level + 0.1))
async def async_volume_down(self):
@ -865,7 +880,10 @@ class MediaPlayerEntity(Entity):
await self.hass.async_add_executor_job(self.volume_down)
return
if self.volume_level > 0 and self.supported_features & SUPPORT_VOLUME_SET:
if (
self.volume_level > 0
and self.supported_features & MediaPlayerEntityFeature.VOLUME_SET
):
await self.async_set_volume_level(max(0, self.volume_level - 0.1))
async def async_media_play_pause(self):
@ -907,12 +925,12 @@ class MediaPlayerEntity(Entity):
supported_features = self.supported_features or 0
data = {}
if supported_features & SUPPORT_SELECT_SOURCE and (
if supported_features & MediaPlayerEntityFeature.SELECT_SOURCE and (
source_list := self.source_list
):
data[ATTR_INPUT_SOURCE_LIST] = source_list
if supported_features & SUPPORT_SELECT_SOUND_MODE and (
if supported_features & MediaPlayerEntityFeature.SELECT_SOUND_MODE and (
sound_mode_list := self.sound_mode_list
):
data[ATTR_SOUND_MODE_LIST] = sound_mode_list
@ -1147,7 +1165,7 @@ async def websocket_browse_media(hass, connection, msg):
connection.send_error(msg["id"], "entity_not_found", "Entity not found")
return
if not player.supported_features & SUPPORT_BROWSE_MEDIA:
if not player.supported_features & MediaPlayerEntityFeature.BROWSE_MEDIA:
connection.send_message(
websocket_api.error_message(
msg["id"], ERR_NOT_SUPPORTED, "Player does not support browsing media"

View File

@ -1,4 +1,6 @@
"""Provides the constants needed for component."""
from enum import IntEnum
# How long our auth signature on the content should be valid for
CONTENT_AUTH_EXPIRY_TIME = 3600 * 24
@ -89,6 +91,34 @@ REPEAT_MODE_OFF = "off"
REPEAT_MODE_ONE = "one"
REPEAT_MODES = [REPEAT_MODE_OFF, REPEAT_MODE_ALL, REPEAT_MODE_ONE]
class MediaPlayerEntityFeature(IntEnum):
"""Supported features of the media player entity."""
PAUSE = 1
SEEK = 2
VOLUME_SET = 4
VOLUME_MUTE = 8
PREVIOUS_TRACK = 16
NEXT_TRACK = 32
TURN_ON = 128
TURN_OFF = 256
PLAY_MEDIA = 512
VOLUME_STEP = 1024
SELECT_SOURCE = 2048
STOP = 4096
CLEAR_PLAYLIST = 8192
PLAY = 16384
SHUFFLE_SET = 32768
SELECT_SOUND_MODE = 65536
BROWSE_MEDIA = 131072
REPEAT_SET = 262144
GROUPING = 524288
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the MediaPlayerEntityFeature enum instead.
SUPPORT_PAUSE = 1
SUPPORT_SEEK = 2
SUPPORT_VOLUME_SET = 4

View File

@ -336,7 +336,11 @@ async def test_play_media(hass):
ent_id = "media_player.living_room"
state = hass.states.get(ent_id)
assert mp.SUPPORT_PLAY_MEDIA & state.attributes.get(ATTR_SUPPORTED_FEATURES) > 0
assert (
mp.MediaPlayerEntityFeature.PLAY_MEDIA
& state.attributes.get(ATTR_SUPPORTED_FEATURES)
> 0
)
assert state.attributes.get(mp.ATTR_MEDIA_CONTENT_ID) is not None
with pytest.raises(vol.Invalid):
@ -347,7 +351,11 @@ async def test_play_media(hass):
blocking=True,
)
state = hass.states.get(ent_id)
assert mp.SUPPORT_PLAY_MEDIA & state.attributes.get(ATTR_SUPPORTED_FEATURES) > 0
assert (
mp.MediaPlayerEntityFeature.PLAY_MEDIA
& state.attributes.get(ATTR_SUPPORTED_FEATURES)
> 0
)
assert state.attributes.get(mp.ATTR_MEDIA_CONTENT_ID) != "some_id"
await hass.services.async_call(
@ -361,7 +369,11 @@ async def test_play_media(hass):
blocking=True,
)
state = hass.states.get(ent_id)
assert mp.SUPPORT_PLAY_MEDIA & state.attributes.get(ATTR_SUPPORTED_FEATURES) > 0
assert (
mp.MediaPlayerEntityFeature.PLAY_MEDIA
& state.attributes.get(ATTR_SUPPORTED_FEATURES)
> 0
)
assert state.attributes.get(mp.ATTR_MEDIA_CONTENT_ID) == "some_id"
@ -374,7 +386,7 @@ async def test_seek(hass, mock_media_seek):
ent_id = "media_player.living_room"
state = hass.states.get(ent_id)
assert state.attributes[ATTR_SUPPORTED_FEATURES] & mp.SUPPORT_SEEK
assert state.attributes[ATTR_SUPPORTED_FEATURES] & mp.MediaPlayerEntityFeature.SEEK
assert not mock_media_seek.called
with pytest.raises(vol.Invalid):

View File

@ -34,12 +34,12 @@ class ExtendedMediaPlayer(mp.MediaPlayerEntity):
def supported_features(self):
"""Flag media player features that are supported."""
return (
mp.const.SUPPORT_VOLUME_SET
| mp.const.SUPPORT_VOLUME_STEP
| mp.const.SUPPORT_PLAY
| mp.const.SUPPORT_PAUSE
| mp.const.SUPPORT_TURN_OFF
| mp.const.SUPPORT_TURN_ON
mp.const.MediaPlayerEntityFeature.VOLUME_SET
| mp.const.MediaPlayerEntityFeature.VOLUME_STEP
| mp.const.MediaPlayerEntityFeature.PLAY
| mp.const.MediaPlayerEntityFeature.PAUSE
| mp.const.MediaPlayerEntityFeature.TURN_OFF
| mp.const.MediaPlayerEntityFeature.TURN_ON
)
def set_volume_level(self, volume):
@ -110,12 +110,12 @@ class SimpleMediaPlayer(mp.MediaPlayerEntity):
def supported_features(self):
"""Flag media player features that are supported."""
return (
mp.const.SUPPORT_VOLUME_SET
| mp.const.SUPPORT_VOLUME_STEP
| mp.const.SUPPORT_PLAY
| mp.const.SUPPORT_PAUSE
| mp.const.SUPPORT_TURN_OFF
| mp.const.SUPPORT_TURN_ON
mp.const.MediaPlayerEntityFeature.VOLUME_SET
| mp.const.MediaPlayerEntityFeature.VOLUME_STEP
| mp.const.MediaPlayerEntityFeature.PLAY
| mp.const.MediaPlayerEntityFeature.PAUSE
| mp.const.MediaPlayerEntityFeature.TURN_OFF
| mp.const.MediaPlayerEntityFeature.TURN_ON
)
def set_volume_level(self, volume):

View File

@ -164,7 +164,7 @@ async def test_media_browse(hass, hass_ws_client):
with patch(
"homeassistant.components.demo.media_player.YOUTUBE_PLAYER_SUPPORT",
media_player.SUPPORT_BROWSE_MEDIA,
media_player.MediaPlayerEntityFeature.BROWSE_MEDIA,
), patch(
"homeassistant.components.media_player.MediaPlayerEntity.async_browse_media",
return_value=BrowseMedia(
@ -207,7 +207,7 @@ async def test_media_browse(hass, hass_ws_client):
with patch(
"homeassistant.components.demo.media_player.YOUTUBE_PLAYER_SUPPORT",
media_player.SUPPORT_BROWSE_MEDIA,
media_player.MediaPlayerEntityFeature.BROWSE_MEDIA,
), patch(
"homeassistant.components.media_player.MediaPlayerEntity.async_browse_media",
return_value={"bla": "yo"},
@ -238,7 +238,8 @@ async def test_group_members_available_when_off(hass):
# Fake group support for DemoYoutubePlayer
with patch(
"homeassistant.components.demo.media_player.YOUTUBE_PLAYER_SUPPORT",
media_player.SUPPORT_GROUPING | media_player.SUPPORT_TURN_OFF,
media_player.MediaPlayerEntityFeature.GROUPING
| media_player.MediaPlayerEntityFeature.TURN_OFF,
):
await hass.services.async_call(
"media_player",