diff --git a/homeassistant/components/media_player/mpd.py b/homeassistant/components/media_player/mpd.py index d8dafc2be10..793588a8d9f 100644 --- a/homeassistant/components/media_player/mpd.py +++ b/homeassistant/components/media_player/mpd.py @@ -5,20 +5,19 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/media_player.mpd/ """ import logging -import socket from datetime import timedelta import voluptuous as vol from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, PLATFORM_SCHEMA, - SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, + SUPPORT_PREVIOUS_TRACK, SUPPORT_STOP, SUPPORT_VOLUME_SET, SUPPORT_PLAY_MEDIA, SUPPORT_PLAY, MEDIA_TYPE_PLAYLIST, SUPPORT_SELECT_SOURCE, SUPPORT_CLEAR_PLAYLIST, SUPPORT_SHUFFLE_SET, SUPPORT_SEEK, MediaPlayerDevice) from homeassistant.const import ( - STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_PORT, CONF_PASSWORD, - CONF_HOST, CONF_NAME) + STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING, + CONF_PORT, CONF_PASSWORD, CONF_HOST, CONF_NAME) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle @@ -31,10 +30,11 @@ DEFAULT_PORT = 6600 PLAYLIST_UPDATE_INTERVAL = timedelta(seconds=120) -SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_TURN_OFF | \ - SUPPORT_TURN_ON | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ +SUPPORT_MPD = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \ + SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | SUPPORT_SELECT_SOURCE | \ - SUPPORT_CLEAR_PLAYLIST | SUPPORT_SHUFFLE_SET | SUPPORT_SEEK + SUPPORT_CLEAR_PLAYLIST | SUPPORT_SHUFFLE_SET | SUPPORT_SEEK | \ + SUPPORT_STOP PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, @@ -47,34 +47,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the MPD platform.""" - daemon = config.get(CONF_HOST) + host = config.get(CONF_HOST) port = config.get(CONF_PORT) name = config.get(CONF_NAME) password = config.get(CONF_PASSWORD) - import mpd - # pylint: disable=no-member - try: - mpd_client = mpd.MPDClient() - mpd_client.connect(daemon, port) - - if password is not None: - mpd_client.password(password) - - mpd_client.close() - mpd_client.disconnect() - except socket.error: - _LOGGER.error("Unable to connect to MPD") - return False - except mpd.CommandError as error: - - if "incorrect password" in str(error): - _LOGGER.error("MPD reported incorrect password") - return False - else: - raise - - add_devices([MpdDevice(daemon, port, password, name)]) + device = MpdDevice(host, port, password, name) + add_devices([device], True) class MpdDevice(MediaPlayerDevice): @@ -89,37 +68,64 @@ class MpdDevice(MediaPlayerDevice): self.port = port self._name = name self.password = password - self.status = None - self.currentsong = None - self.playlists = [] - self.currentplaylist = None - self.client = mpd.MPDClient() - self.client.timeout = 10 - self.client.idletimeout = None - self.update() + self._status = None + self._currentsong = None + self._playlists = [] + self._currentplaylist = None + self._is_connected = False + + # set up MPD client + self._client = mpd.MPDClient() + self._client.timeout = 5 + self._client.idletimeout = None + if password is not None: + self._client.password(password) + + def _connect(self): + """Connect to MPD.""" + import mpd + try: + self._client.connect(self.server, self.port) + except mpd.ConnectionError: + return + + self._is_connected = True + + def _disconnect(self): + """Disconnect from MPD.""" + import mpd + try: + self._client.disconnect() + except mpd.ConnectionError: + pass + self._is_connected = False + self._status = None + + def _fetch_status(self): + """Fetch status from MPD.""" + self._status = self._client.status() + self._currentsong = self._client.currentsong() + + self._update_playlists() + + @property + def available(self): + """True if MPD is available and connected.""" + return self._is_connected def update(self): """Get the latest data and update the state.""" import mpd + try: - self.status = self.client.status() - self.currentsong = self.client.currentsong() - self._update_playlists() + if not self._is_connected: + self._connect() + + self._fetch_status() except (mpd.ConnectionError, OSError, BrokenPipeError, ValueError): # Cleanly disconnect in case connection is not in valid state - try: - self.client.disconnect() - except mpd.ConnectionError: - pass - - self.client.connect(self.server, self.port) - - if self.password is not None: - self.client.password(self.password) - - self.status = self.client.status() - self.currentsong = self.client.currentsong() + self._disconnect() @property def name(self): @@ -129,17 +135,19 @@ class MpdDevice(MediaPlayerDevice): @property def state(self): """Return the media state.""" - if self.status['state'] == 'play': + if self._status is None: + return STATE_OFF + elif self._status['state'] == 'play': return STATE_PLAYING - elif self.status['state'] == 'pause': + elif self._status['state'] == 'pause': return STATE_PAUSED - return STATE_OFF + return STATE_ON @property def media_content_id(self): """Return the content ID of current playing media.""" - return self.currentsong.get('file') + return self._currentsong.get('file') @property def media_content_type(self): @@ -150,13 +158,13 @@ class MpdDevice(MediaPlayerDevice): def media_duration(self): """Return the duration of current playing media in seconds.""" # Time does not exist for streams - return self.currentsong.get('time') + return self._currentsong.get('time') @property def media_title(self): """Return the title of current playing media.""" - name = self.currentsong.get('name', None) - title = self.currentsong.get('title', None) + name = self._currentsong.get('name', None) + title = self._currentsong.get('title', None) if name is None and title is None: return "None" @@ -170,17 +178,17 @@ class MpdDevice(MediaPlayerDevice): @property def media_artist(self): """Return the artist of current playing media (Music track only).""" - return self.currentsong.get('artist') + return self._currentsong.get('artist') @property def media_album_name(self): """Return the album of current playing media (Music track only).""" - return self.currentsong.get('album') + return self._currentsong.get('album') @property def volume_level(self): """Return the volume level.""" - return int(self.status['volume'])/100 + return int(self._status['volume'])/100 @property def supported_features(self): @@ -190,98 +198,93 @@ class MpdDevice(MediaPlayerDevice): @property def source(self): """Name of the current input source.""" - return self.currentplaylist + return self._currentplaylist @property def source_list(self): """Return the list of available input sources.""" - return self.playlists + return self._playlists def select_source(self, source): """Choose a different available playlist and play it.""" self.play_media(MEDIA_TYPE_PLAYLIST, source) - def turn_off(self): - """Service to send the MPD the command to stop playing.""" - self.client.stop() - - def turn_on(self): - """Service to send the MPD the command to start playing.""" - self.client.play() - self._update_playlists(no_throttle=True) - @Throttle(PLAYLIST_UPDATE_INTERVAL) def _update_playlists(self, **kwargs): """Update available MPD playlists.""" - self.playlists = [] - for playlist_data in self.client.listplaylists(): - self.playlists.append(playlist_data['playlist']) + self._playlists = [] + for playlist_data in self._client.listplaylists(): + self._playlists.append(playlist_data['playlist']) def set_volume_level(self, volume): """Set volume of media player.""" - self.client.setvol(int(volume * 100)) + self._client.setvol(int(volume * 100)) def volume_up(self): """Service to send the MPD the command for volume up.""" - current_volume = int(self.status['volume']) + current_volume = int(self._status['volume']) if current_volume <= 100: - self.client.setvol(current_volume + 5) + self._client.setvol(current_volume + 5) def volume_down(self): """Service to send the MPD the command for volume down.""" - current_volume = int(self.status['volume']) + current_volume = int(self._status['volume']) if current_volume >= 0: - self.client.setvol(current_volume - 5) + self._client.setvol(current_volume - 5) def media_play(self): """Service to send the MPD the command for play/pause.""" - self.client.pause(0) + self._client.pause(0) def media_pause(self): """Service to send the MPD the command for play/pause.""" - self.client.pause(1) + self._client.pause(1) + + def media_stop(self): + """Service to send the MPD the command for stop.""" + self._client.stop() def media_next_track(self): """Service to send the MPD the command for next track.""" - self.client.next() + self._client.next() def media_previous_track(self): """Service to send the MPD the command for previous track.""" - self.client.previous() + self._client.previous() def play_media(self, media_type, media_id, **kwargs): """Send the media player the command for playing a playlist.""" - _LOGGER.info(str.format("Playing playlist: {0}", media_id)) + _LOGGER.debug(str.format("Playing playlist: {0}", media_id)) if media_type == MEDIA_TYPE_PLAYLIST: - if media_id in self.playlists: - self.currentplaylist = media_id + if media_id in self._playlists: + self._currentplaylist = media_id else: - self.currentplaylist = None + self._currentplaylist = None _LOGGER.warning(str.format("Unknown playlist name %s.", media_id)) - self.client.clear() - self.client.load(media_id) - self.client.play() + self._client.clear() + self._client.load(media_id) + self._client.play() else: - self.client.clear() - self.client.add(media_id) - self.client.play() + self._client.clear() + self._client.add(media_id) + self._client.play() @property def shuffle(self): """Boolean if shuffle is enabled.""" - return bool(self.status['random']) + return bool(self._status['random']) def set_shuffle(self, shuffle): """Enable/disable shuffle mode.""" - self.client.random(int(shuffle)) + self._client.random(int(shuffle)) def clear_playlist(self): """Clear players playlist.""" - self.client.clear() + self._client.clear() def media_seek(self, position): """Send seek command.""" - self.client.seekcur(position) + self._client.seekcur(position)