1
mirror of https://github.com/home-assistant/core synced 2024-07-12 07:21:24 +02:00

Add additional supported feature support to universal media player (#44711)

* add additional supported feature support to universal media player

* add missing services
This commit is contained in:
Raman Gupta 2021-02-14 19:35:14 -05:00 committed by GitHub
parent 0af634a9f8
commit a3b733f1ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 188 additions and 2 deletions

View File

@ -20,6 +20,7 @@ from homeassistant.components.media_player.const import (
ATTR_MEDIA_PLAYLIST,
ATTR_MEDIA_POSITION,
ATTR_MEDIA_POSITION_UPDATED_AT,
ATTR_MEDIA_REPEAT,
ATTR_MEDIA_SEASON,
ATTR_MEDIA_SEEK_POSITION,
ATTR_MEDIA_SERIES_TITLE,
@ -28,13 +29,23 @@ from homeassistant.components.media_player.const import (
ATTR_MEDIA_TRACK,
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
ATTR_SOUND_MODE,
ATTR_SOUND_MODE_LIST,
DOMAIN,
SERVICE_CLEAR_PLAYLIST,
SERVICE_PLAY_MEDIA,
SERVICE_SELECT_SOUND_MODE,
SERVICE_SELECT_SOURCE,
SUPPORT_CLEAR_PLAYLIST,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
SUPPORT_PLAY,
SUPPORT_PREVIOUS_TRACK,
SUPPORT_REPEAT_SET,
SUPPORT_SELECT_SOUND_MODE,
SUPPORT_SELECT_SOURCE,
SUPPORT_SHUFFLE_SET,
SUPPORT_STOP,
SUPPORT_TURN_OFF,
SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE,
@ -55,7 +66,9 @@ from homeassistant.const import (
SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_SEEK,
SERVICE_MEDIA_STOP,
SERVICE_REPEAT_SET,
SERVICE_SHUFFLE_SET,
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_VOLUME_DOWN,
@ -382,6 +395,16 @@ class UniversalMediaPlayer(MediaPlayerEntity):
"""Name of the current running app."""
return self._child_attr(ATTR_APP_NAME)
@property
def sound_mode(self):
"""Return the current sound mode of the device."""
return self._override_or_child_attr(ATTR_SOUND_MODE)
@property
def sound_mode_list(self):
"""List of available sound modes."""
return self._override_or_child_attr(ATTR_SOUND_MODE_LIST)
@property
def source(self):
"""Return the current input source of the device."""
@ -392,6 +415,11 @@ class UniversalMediaPlayer(MediaPlayerEntity):
"""List of available input sources."""
return self._override_or_child_attr(ATTR_INPUT_SOURCE_LIST)
@property
def repeat(self):
"""Boolean if repeating is enabled."""
return self._override_or_child_attr(ATTR_MEDIA_REPEAT)
@property
def shuffle(self):
"""Boolean if shuffling is enabled."""
@ -407,6 +435,22 @@ class UniversalMediaPlayer(MediaPlayerEntity):
if SERVICE_TURN_OFF in self._cmds:
flags |= SUPPORT_TURN_OFF
if SERVICE_MEDIA_PLAY_PAUSE in self._cmds:
flags |= SUPPORT_PLAY | SUPPORT_PAUSE
else:
if SERVICE_MEDIA_PLAY in self._cmds:
flags |= SUPPORT_PLAY
if SERVICE_MEDIA_PAUSE in self._cmds:
flags |= SUPPORT_PAUSE
if SERVICE_MEDIA_STOP in self._cmds:
flags |= SUPPORT_STOP
if SERVICE_MEDIA_NEXT_TRACK in self._cmds:
flags |= SUPPORT_NEXT_TRACK
if SERVICE_MEDIA_PREVIOUS_TRACK in self._cmds:
flags |= SUPPORT_PREVIOUS_TRACK
if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN]]):
flags |= SUPPORT_VOLUME_STEP
if SERVICE_VOLUME_SET in self._cmds:
@ -415,7 +459,10 @@ class UniversalMediaPlayer(MediaPlayerEntity):
if SERVICE_VOLUME_MUTE in self._cmds and ATTR_MEDIA_VOLUME_MUTED in self._attrs:
flags |= SUPPORT_VOLUME_MUTE
if SERVICE_SELECT_SOURCE in self._cmds:
if (
SERVICE_SELECT_SOURCE in self._cmds
and ATTR_INPUT_SOURCE_LIST in self._attrs
):
flags |= SUPPORT_SELECT_SOURCE
if SERVICE_CLEAR_PLAYLIST in self._cmds:
@ -424,6 +471,15 @@ class UniversalMediaPlayer(MediaPlayerEntity):
if SERVICE_SHUFFLE_SET in self._cmds and ATTR_MEDIA_SHUFFLE in self._attrs:
flags |= SUPPORT_SHUFFLE_SET
if SERVICE_REPEAT_SET in self._cmds and ATTR_MEDIA_REPEAT in self._attrs:
flags |= SUPPORT_REPEAT_SET
if (
SERVICE_SELECT_SOUND_MODE in self._cmds
and ATTR_SOUND_MODE_LIST in self._attrs
):
flags |= SUPPORT_SELECT_SOUND_MODE
return flags
@property
@ -502,6 +558,13 @@ class UniversalMediaPlayer(MediaPlayerEntity):
"""Play or pause the media player."""
await self._async_call_service(SERVICE_MEDIA_PLAY_PAUSE)
async def async_select_sound_mode(self, sound_mode):
"""Select sound mode."""
data = {ATTR_SOUND_MODE: sound_mode}
await self._async_call_service(
SERVICE_SELECT_SOUND_MODE, data, allow_override=True
)
async def async_select_source(self, source):
"""Set the input source."""
data = {ATTR_INPUT_SOURCE: source}
@ -516,6 +579,15 @@ class UniversalMediaPlayer(MediaPlayerEntity):
data = {ATTR_MEDIA_SHUFFLE: shuffle}
await self._async_call_service(SERVICE_SHUFFLE_SET, data, allow_override=True)
async def async_set_repeat(self, repeat):
"""Set repeat mode."""
data = {ATTR_MEDIA_REPEAT: repeat}
await self._async_call_service(SERVICE_REPEAT_SET, data, allow_override=True)
async def async_toggle(self):
"""Toggle the power on the media player."""
await self._async_call_service(SERVICE_TOGGLE)
async def async_update(self):
"""Update state in HA."""
for child_name in self._children:

View File

@ -51,6 +51,7 @@ class MockMediaPlayer(media_player.MediaPlayerEntity):
self._tracks = 12
self._media_image_url = None
self._shuffle = False
self._sound_mode = None
self.service_calls = {
"turn_on": mock_service(
@ -71,6 +72,9 @@ class MockMediaPlayer(media_player.MediaPlayerEntity):
"media_pause": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PAUSE
),
"media_stop": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_STOP
),
"media_previous_track": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PREVIOUS_TRACK
),
@ -92,12 +96,21 @@ class MockMediaPlayer(media_player.MediaPlayerEntity):
"media_play_pause": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_MEDIA_PLAY_PAUSE
),
"select_sound_mode": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOUND_MODE
),
"select_source": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOURCE
),
"toggle": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_TOGGLE
),
"clear_playlist": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_CLEAR_PLAYLIST
),
"repeat_set": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_REPEAT_SET
),
"shuffle_set": mock_service(
hass, media_player.DOMAIN, media_player.SERVICE_SHUFFLE_SET
),
@ -162,18 +175,30 @@ class MockMediaPlayer(media_player.MediaPlayerEntity):
"""Mock pause."""
self._state = STATE_PAUSED
def select_sound_mode(self, sound_mode):
"""Set the sound mode."""
self._sound_mode = sound_mode
def select_source(self, source):
"""Set the input source."""
self._source = source
def async_toggle(self):
"""Toggle the power on the media player."""
self._state = STATE_OFF if self._state == STATE_ON else STATE_ON
def clear_playlist(self):
"""Clear players playlist."""
self._tracks = 0
def set_shuffle(self, shuffle):
"""Clear players playlist."""
"""Enable/disable shuffle mode."""
self._shuffle = shuffle
def set_repeat(self, repeat):
"""Enable/disable repeat mode."""
self._repeat = repeat
class TestMediaPlayer(unittest.TestCase):
"""Test the media_player module."""
@ -205,9 +230,18 @@ class TestMediaPlayer(unittest.TestCase):
self.mock_source_id = f"{input_select.DOMAIN}.source"
self.hass.states.set(self.mock_source_id, "dvd")
self.mock_sound_mode_list_id = f"{input_select.DOMAIN}.sound_mode_list"
self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie"])
self.mock_sound_mode_id = f"{input_select.DOMAIN}.sound_mode"
self.hass.states.set(self.mock_sound_mode_id, "music")
self.mock_shuffle_switch_id = switch.ENTITY_ID_FORMAT.format("shuffle")
self.hass.states.set(self.mock_shuffle_switch_id, STATE_OFF)
self.mock_repeat_switch_id = switch.ENTITY_ID_FORMAT.format("repeat")
self.hass.states.set(self.mock_repeat_switch_id, STATE_OFF)
self.config_children_only = {
"name": "test",
"platform": "universal",
@ -230,6 +264,9 @@ class TestMediaPlayer(unittest.TestCase):
"source_list": self.mock_source_list_id,
"state": self.mock_state_switch_id,
"shuffle": self.mock_shuffle_switch_id,
"repeat": self.mock_repeat_switch_id,
"sound_mode_list": self.mock_sound_mode_list_id,
"sound_mode": self.mock_sound_mode_id,
},
}
self.addCleanup(self.tear_down_cleanup)
@ -507,6 +544,17 @@ class TestMediaPlayer(unittest.TestCase):
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
assert ump.is_volume_muted
def test_sound_mode_list_children_and_attr(self):
"""Test sound mode list property w/ children and attrs."""
config = validate_config(self.config_children_and_attr)
ump = universal.UniversalMediaPlayer(self.hass, **config)
assert "['music', 'movie']" == ump.sound_mode_list
self.hass.states.set(self.mock_sound_mode_list_id, ["music", "movie", "game"])
assert "['music', 'movie', 'game']" == ump.sound_mode_list
def test_source_list_children_and_attr(self):
"""Test source list property w/ children and attrs."""
config = validate_config(self.config_children_and_attr)
@ -518,6 +566,17 @@ class TestMediaPlayer(unittest.TestCase):
self.hass.states.set(self.mock_source_list_id, ["dvd", "htpc", "game"])
assert "['dvd', 'htpc', 'game']" == ump.source_list
def test_sound_mode_children_and_attr(self):
"""Test sound modeproperty w/ children and attrs."""
config = validate_config(self.config_children_and_attr)
ump = universal.UniversalMediaPlayer(self.hass, **config)
assert "music" == ump.sound_mode
self.hass.states.set(self.mock_sound_mode_id, "movie")
assert "movie" == ump.sound_mode
def test_source_children_and_attr(self):
"""Test source property w/ children and attrs."""
config = validate_config(self.config_children_and_attr)
@ -579,8 +638,17 @@ class TestMediaPlayer(unittest.TestCase):
"volume_down": excmd,
"volume_mute": excmd,
"volume_set": excmd,
"select_sound_mode": excmd,
"select_source": excmd,
"repeat_set": excmd,
"shuffle_set": excmd,
"media_play": excmd,
"media_pause": excmd,
"media_stop": excmd,
"media_next_track": excmd,
"media_previous_track": excmd,
"toggle": excmd,
"clear_playlist": excmd,
}
config = validate_config(config)
@ -598,13 +666,41 @@ class TestMediaPlayer(unittest.TestCase):
| universal.SUPPORT_TURN_OFF
| universal.SUPPORT_VOLUME_STEP
| universal.SUPPORT_VOLUME_MUTE
| universal.SUPPORT_SELECT_SOUND_MODE
| universal.SUPPORT_SELECT_SOURCE
| universal.SUPPORT_REPEAT_SET
| universal.SUPPORT_SHUFFLE_SET
| universal.SUPPORT_VOLUME_SET
| universal.SUPPORT_PLAY
| universal.SUPPORT_PAUSE
| universal.SUPPORT_STOP
| universal.SUPPORT_NEXT_TRACK
| universal.SUPPORT_PREVIOUS_TRACK
| universal.SUPPORT_CLEAR_PLAYLIST
)
assert check_flags == ump.supported_features
def test_supported_features_play_pause(self):
"""Test supported media commands with play_pause function."""
config = copy(self.config_children_and_attr)
excmd = {"service": "media_player.test", "data": {"entity_id": "test"}}
config["commands"] = {"media_play_pause": excmd}
config = validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config["name"])
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.schedule_update_ha_state()
self.hass.block_till_done()
asyncio.run_coroutine_threadsafe(ump.async_update(), self.hass.loop).result()
check_flags = universal.SUPPORT_PLAY | universal.SUPPORT_PAUSE
assert check_flags == ump.supported_features
def test_service_call_no_active_child(self):
"""Test a service call to children with no active child."""
config = validate_config(self.config_children_and_attr)
@ -663,6 +759,11 @@ class TestMediaPlayer(unittest.TestCase):
).result()
assert 1 == len(self.mock_mp_2.service_calls["media_pause"])
asyncio.run_coroutine_threadsafe(
ump.async_media_stop(), self.hass.loop
).result()
assert 1 == len(self.mock_mp_2.service_calls["media_stop"])
asyncio.run_coroutine_threadsafe(
ump.async_media_previous_track(), self.hass.loop
).result()
@ -696,6 +797,11 @@ class TestMediaPlayer(unittest.TestCase):
).result()
assert 1 == len(self.mock_mp_2.service_calls["media_play_pause"])
asyncio.run_coroutine_threadsafe(
ump.async_select_sound_mode("music"), self.hass.loop
).result()
assert 1 == len(self.mock_mp_2.service_calls["select_sound_mode"])
asyncio.run_coroutine_threadsafe(
ump.async_select_source("dvd"), self.hass.loop
).result()
@ -706,11 +812,19 @@ class TestMediaPlayer(unittest.TestCase):
).result()
assert 1 == len(self.mock_mp_2.service_calls["clear_playlist"])
asyncio.run_coroutine_threadsafe(
ump.async_set_repeat(True), self.hass.loop
).result()
assert 1 == len(self.mock_mp_2.service_calls["repeat_set"])
asyncio.run_coroutine_threadsafe(
ump.async_set_shuffle(True), self.hass.loop
).result()
assert 1 == len(self.mock_mp_2.service_calls["shuffle_set"])
asyncio.run_coroutine_threadsafe(ump.async_toggle(), self.hass.loop).result()
assert 1 == len(self.mock_mp_2.service_calls["toggle"])
def test_service_call_to_command(self):
"""Test service call to command."""
config = copy(self.config_children_only)