diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 25c55f46b7b..57167317553 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -31,6 +31,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' SERVICE_PLAY_MEDIA = 'play_media' SERVICE_SELECT_SOURCE = 'select_source' +SERVICE_CLEAR_PLAYLIST = 'clear_playlist' ATTR_MEDIA_VOLUME_LEVEL = 'volume_level' ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted' @@ -75,6 +76,7 @@ SUPPORT_PLAY_MEDIA = 512 SUPPORT_VOLUME_STEP = 1024 SUPPORT_SELECT_SOURCE = 2048 SUPPORT_STOP = 4096 +SUPPORT_CLEAR_PLAYLIST = 8192 # simple services that only take entity_id(s) as optional argument SERVICE_TO_METHOD = { @@ -89,7 +91,8 @@ SERVICE_TO_METHOD = { SERVICE_MEDIA_STOP: 'media_stop', SERVICE_MEDIA_NEXT_TRACK: 'media_next_track', SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track', - SERVICE_SELECT_SOURCE: 'select_source' + SERVICE_SELECT_SOURCE: 'select_source', + SERVICE_CLEAR_PLAYLIST: 'clear_playlist' } ATTR_TO_PROPERTY = [ @@ -272,6 +275,12 @@ def select_source(hass, source, entity_id=None): hass.services.call(DOMAIN, SERVICE_SELECT_SOURCE, data) +def clear_playlist(hass, entity_id=None): + """Send the media player the command for clear playlist.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + hass.services.call(DOMAIN, SERVICE_CLEAR_PLAYLIST, data) + + def setup(hass, config): """Track states and offer events for media_players.""" component = EntityComponent( @@ -542,6 +551,10 @@ class MediaPlayerDevice(Entity): """Select input source.""" raise NotImplementedError() + def clear_playlist(self): + """Clear players playlist.""" + raise NotImplementedError() + # No need to overwrite these. @property def support_pause(self): @@ -588,6 +601,11 @@ class MediaPlayerDevice(Entity): """Boolean if select source command supported.""" return bool(self.supported_media_commands & SUPPORT_SELECT_SOURCE) + @property + def support_clear_playlist(self): + """Boolean if clear playlist command supported.""" + return bool(self.supported_media_commands & SUPPORT_CLEAR_PLAYLIST) + def toggle(self): """Toggle the power on the media player.""" if self.state in [STATE_OFF, STATE_IDLE]: diff --git a/homeassistant/components/media_player/demo.py b/homeassistant/components/media_player/demo.py index ddc5b368d78..d59e6ef77d8 100644 --- a/homeassistant/components/media_player/demo.py +++ b/homeassistant/components/media_player/demo.py @@ -8,7 +8,7 @@ from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SELECT_SOURCE, MediaPlayerDevice) + SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, MediaPlayerDevice) from homeassistant.const import STATE_OFF, STATE_PAUSED, STATE_PLAYING @@ -32,7 +32,7 @@ YOUTUBE_PLAYER_SUPPORT = \ MUSIC_PLAYER_SUPPORT = \ SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ - SUPPORT_TURN_ON | SUPPORT_TURN_OFF + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_CLEAR_PLAYLIST NETFLIX_PLAYER_SUPPORT = \ SUPPORT_PAUSE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE @@ -214,12 +214,12 @@ class DemoMusicPlayer(AbstractDemoPlayer): @property def media_title(self): """Return the title of current playing media.""" - return self.tracks[self._cur_track][1] + return self.tracks[self._cur_track][1] if len(self.tracks) > 0 else "" @property def media_artist(self): """Return the artist of current playing media (Music track only).""" - return self.tracks[self._cur_track][0] + return self.tracks[self._cur_track][0] if len(self.tracks) > 0 else "" @property def media_album_name(self): @@ -257,6 +257,13 @@ class DemoMusicPlayer(AbstractDemoPlayer): self._cur_track += 1 self.update_ha_state() + def clear_playlist(self): + """Clear players playlist.""" + self.tracks = [] + self._cur_track = 0 + self._player_state = STATE_OFF + self.update_ha_state() + class DemoTVShowPlayer(AbstractDemoPlayer): """A Demo media player that only supports YouTube.""" diff --git a/homeassistant/components/media_player/services.yaml b/homeassistant/components/media_player/services.yaml index 9ab831bdbb4..421010fc1a9 100644 --- a/homeassistant/components/media_player/services.yaml +++ b/homeassistant/components/media_player/services.yaml @@ -146,6 +146,14 @@ select_source: description: Name of the source to switch to. Platform dependent. example: 'video1' +clear_playlist: + description: Send the media player the command to clear players playlist. + + fields: + entity_id: + description: Name(s) of entites to change source on + example: 'media_player.living_room_chromecast' + sonos_group_players: description: Send Sonos media player the command for grouping all players into one (party mode). diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 7d0cd12175a..1711f583c31 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -12,7 +12,8 @@ from os import path from homeassistant.components.media_player import ( ATTR_MEDIA_ENQUEUE, DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, - SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_CLEAR_PLAYLIST, + SUPPORT_SELECT_SOURCE, MediaPlayerDevice) from homeassistant.const import ( STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_OFF) from homeassistant.config import load_yaml_config_file @@ -31,13 +32,17 @@ _REQUESTS_LOGGER.setLevel(logging.ERROR) SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA |\ - SUPPORT_SEEK + SUPPORT_SEEK | SUPPORT_CLEAR_PLAYLIST | SUPPORT_SELECT_SOURCE SERVICE_GROUP_PLAYERS = 'sonos_group_players' SERVICE_UNJOIN = 'sonos_unjoin' SERVICE_SNAPSHOT = 'sonos_snapshot' SERVICE_RESTORE = 'sonos_restore' +SUPPORT_SOURCE_LINEIN = 'Line-in' +SUPPORT_SOURCE_TV = 'TV' +SUPPORT_SOURCE_RADIO = 'Radio' + # pylint: disable=unused-argument, too-many-locals def setup_platform(hass, config, add_devices, discovery_info=None): @@ -162,12 +167,12 @@ class SonosDevice(MediaPlayerDevice): # pylint: disable=too-many-arguments def __init__(self, hass, player): """Initialize the Sonos device.""" + from soco.snapshot import Snapshot + self.hass = hass self.volume_increment = 5 - super(SonosDevice, self).__init__() self._player = player self.update() - from soco.snapshot import Snapshot self.soco_snapshot = Snapshot(self._player) @property @@ -268,6 +273,10 @@ class SonosDevice(MediaPlayerDevice): ) if 'title' in self._status: return self._trackinfo['title'] + if self._player.is_playing_line_in: + return SUPPORT_SOURCE_LINEIN + if self._player.is_playing_tv: + return SUPPORT_SOURCE_TV @property def supported_media_commands(self): @@ -290,6 +299,36 @@ class SonosDevice(MediaPlayerDevice): """Mute (true) or unmute (false) media player.""" self._player.mute = mute + def select_source(self, source): + """Select input source.""" + if source == SUPPORT_SOURCE_LINEIN: + self._player.switch_to_line_in() + elif source == SUPPORT_SOURCE_TV: + self._player.switch_to_tv() + + @property + def source_list(self): + """List of available input sources.""" + source = [] + + # generate list of supported device + source.append(SUPPORT_SOURCE_LINEIN) + source.append(SUPPORT_SOURCE_TV) + source.append(SUPPORT_SOURCE_RADIO) + + return source + + @property + def source(self): + """Name of the current input source.""" + if self._player.is_playing_line_in: + return SUPPORT_SOURCE_LINEIN + if self._player.is_playing_tv: + return SUPPORT_SOURCE_TV + if self._player.is_playing_radio: + return SUPPORT_SOURCE_RADIO + return None + @only_if_coordinator def turn_off(self): """Turn off media player.""" @@ -320,6 +359,11 @@ class SonosDevice(MediaPlayerDevice): """Send seek command.""" self._player.seek(str(datetime.timedelta(seconds=int(position)))) + @only_if_coordinator + def clear_playlist(self): + """Clear players playlist.""" + self._player.clear_queue() + @only_if_coordinator def turn_on(self): """Turn the media player on.""" diff --git a/homeassistant/components/media_player/universal.py b/homeassistant/components/media_player/universal.py index 4b5bd501819..e5b9f0321b1 100644 --- a/homeassistant/components/media_player/universal.py +++ b/homeassistant/components/media_player/universal.py @@ -17,8 +17,9 @@ from homeassistant.components.media_player import ( ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SUPPORTED_MEDIA_COMMANDS, DOMAIN, SERVICE_PLAY_MEDIA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, ATTR_INPUT_SOURCE, - SERVICE_SELECT_SOURCE, MediaPlayerDevice) + SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, + ATTR_INPUT_SOURCE, SERVICE_SELECT_SOURCE, SERVICE_CLEAR_PLAYLIST, + MediaPlayerDevice) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE, CONF_NAME, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, @@ -349,6 +350,9 @@ class UniversalMediaPlayer(MediaPlayerDevice): if SERVICE_SELECT_SOURCE in self._cmds: flags |= SUPPORT_SELECT_SOURCE + if SERVICE_CLEAR_PLAYLIST in self._cmds: + flags |= SUPPORT_CLEAR_PLAYLIST + return flags @property @@ -424,6 +428,10 @@ class UniversalMediaPlayer(MediaPlayerDevice): data = {ATTR_INPUT_SOURCE: source} self._call_service(SERVICE_SELECT_SOURCE, data) + def clear_playlist(self): + """Clear players playlist.""" + self._call_service(SERVICE_CLEAR_PLAYLIST) + def update(self): """Update state in HA.""" for child_name in self._children: diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index 82133a1d484..aa7350ff930 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -38,6 +38,15 @@ class TestDemoMediaPlayer(unittest.TestCase): state = self.hass.states.get(entity_id) assert 'xbox' == state.attributes.get('source') + def test_clear_playlist(self): + """Test clear playlist.""" + assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert self.hass.states.is_state(entity_id, 'playing') + + mp.clear_playlist(self.hass, entity_id) + self.hass.pool.block_till_done() + assert self.hass.states.is_state(entity_id, 'off') + def test_volume_services(self): """Test the volume service.""" assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) diff --git a/tests/components/media_player/test_universal.py b/tests/components/media_player/test_universal.py index a6de09a3eb1..b7018945551 100644 --- a/tests/components/media_player/test_universal.py +++ b/tests/components/media_player/test_universal.py @@ -25,6 +25,7 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): self._media_title = None self._supported_media_commands = 0 self._source = None + self._tracks = 12 self.service_calls = { 'turn_on': mock_service( @@ -59,6 +60,9 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): 'select_source': mock_service( hass, media_player.DOMAIN, media_player.SERVICE_SELECT_SOURCE), + 'clear_playlist': mock_service( + hass, media_player.DOMAIN, + media_player.SERVICE_CLEAR_PLAYLIST), } @property @@ -114,6 +118,10 @@ class MockMediaPlayer(media_player.MediaPlayerDevice): """Set the input source.""" self._state = source + def clear_playlist(self): + """Clear players playlist.""" + self._tracks = 0 + class TestMediaPlayer(unittest.TestCase): """Test the media_player module.""" @@ -510,6 +518,10 @@ class TestMediaPlayer(unittest.TestCase): self.assertEqual( 1, len(self.mock_mp_2.service_calls['select_source'])) + ump.clear_playlist() + self.assertEqual( + 1, len(self.mock_mp_2.service_calls['clear_playlist'])) + def test_service_call_to_command(self): """Test service call to command.""" config = self.config_children_only