diff --git a/.coveragerc b/.coveragerc index 08de2bfa3c85bd355eb5d2dea645a1a96fb69284..9a1a31257404dd93dfb0ee99123bcb0bb11a7329 100644 --- a/.coveragerc +++ b/.coveragerc @@ -218,6 +218,7 @@ omit = homeassistant/components/media_player/aquostv.py homeassistant/components/media_player/braviatv.py homeassistant/components/media_player/cast.py + homeassistant/components/media_player/clementine.py homeassistant/components/media_player/cmus.py homeassistant/components/media_player/denon.py homeassistant/components/media_player/denonavr.py diff --git a/homeassistant/components/media_player/clementine.py b/homeassistant/components/media_player/clementine.py new file mode 100644 index 0000000000000000000000000000000000000000..e85019b79911c94b5edcb840596d5ed0cd847a99 --- /dev/null +++ b/homeassistant/components/media_player/clementine.py @@ -0,0 +1,220 @@ +""" +Support for Clementine Music Player as media player. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.clementine/ +""" + +import asyncio +from datetime import timedelta +import logging +import time + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.media_player import ( + SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, PLATFORM_SCHEMA, + SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, SUPPORT_PLAY, MEDIA_TYPE_MUSIC, + SUPPORT_VOLUME_SET, MediaPlayerDevice) +from homeassistant.const import ( + CONF_HOST, CONF_NAME, CONF_PORT, CONF_ACCESS_TOKEN, + STATE_OFF, STATE_PLAYING, STATE_PAUSED, STATE_UNKNOWN) + +REQUIREMENTS = ['python-clementine-remote==1.0.1'] + +SCAN_INTERVAL = timedelta(seconds=5) + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = 'Clementine Remote' + +SUPPORT_CLEMENTINE = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ + SUPPORT_PREVIOUS_TRACK | SUPPORT_VOLUME_SET | \ + SUPPORT_NEXT_TRACK | \ + SUPPORT_SELECT_SOURCE | SUPPORT_PLAY + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=5500): cv.positive_int, + vol.Optional(CONF_ACCESS_TOKEN, default=None): cv.positive_int, +}) + + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Clementine platform.""" + from clementineremote import ClementineRemote + client = ClementineRemote(config.get(CONF_HOST), config.get(CONF_PORT), + config.get(CONF_ACCESS_TOKEN), reconnect=True) + + add_devices([ClementineDevice(client, config[CONF_NAME])]) + + +class ClementineDevice(MediaPlayerDevice): + """Representation of Clementine Player.""" + + def __init__(self, client, name): + """Initialize the Clementine device.""" + self._client = client + self._name = name + self._muted = False + self._volume = 0.0 + self._track_id = 0 + self._last_track_id = 0 + self._track_name = '' + self._track_artist = '' + self._track_album_name = '' + self._state = STATE_UNKNOWN + + def update(self): + """Retrieve the latest data from the Clementine Player.""" + try: + client = self._client + + if client.state == 'Playing': + self._state = STATE_PLAYING + elif client.state == 'Paused': + self._state = STATE_PAUSED + elif client.state == 'Disconnected': + self._state = STATE_OFF + else: + self._state = STATE_PAUSED + + if client.last_update and (time.time() - client.last_update > 40): + self._state = STATE_OFF + + self._volume = float(client.volume) if client.volume else 0.0 + + if client.current_track: + self._track_id = client.current_track['track_id'] + self._track_name = client.current_track['title'] + self._track_artist = client.current_track['track_artist'] + self._track_album_name = client.current_track['track_album'] + + except: + self._state = STATE_OFF + raise + + @property + def name(self): + """Return the name of the device.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def volume_level(self): + """Volume level of the media player (0..1).""" + return self._volume / 100.0 + + @property + def source(self): + """Return current source name.""" + source_name = "Unknown" + client = self._client + if client.active_playlist_id in client.playlists: + source_name = client.playlists[client.active_playlist_id]['name'] + return source_name + + @property + def source_list(self): + """List of available input sources.""" + source_names = [s["name"] for s in self._client.playlists.values()] + return source_names + + def select_source(self, source): + """Select input source.""" + client = self._client + sources = [s for s in client.playlists.values() if s['name'] == source] + if len(sources) == 1: + client.change_song(sources[0]['id'], 0) + + @property + def media_content_type(self): + """Content type of current playing media.""" + return MEDIA_TYPE_MUSIC + + @property + def media_title(self): + """Title of current playing media.""" + return self._track_name + + @property + def media_artist(self): + """Artist of current playing media, music track only.""" + return self._track_artist + + @property + def media_album_name(self): + """Album name of current playing media, music track only.""" + return self._track_album_name + + @property + def supported_features(self): + """Flag media player features that are supported.""" + return SUPPORT_CLEMENTINE + + @property + def media_image_hash(self): + """Hash value for media image.""" + if self._client.current_track: + return self._client.current_track['track_id'] + + return None + + @asyncio.coroutine + def async_get_media_image(self): + """Fetch media image of current playing image.""" + if self._client.current_track: + image = bytes(self._client.current_track['art']) + return (image, 'image/png') + + return None, None + + def volume_up(self): + """Volume up the media player.""" + newvolume = min(self._client.volume + 4, 100) + self._client.set_volume(newvolume) + + def volume_down(self): + """Volume down media player.""" + newvolume = max(self._client.volume - 4, 0) + self._client.set_volume(newvolume) + + def mute_volume(self, mute): + """Send mute command.""" + self._client.set_volume(0) + + def set_volume_level(self, volume): + """Set volume level.""" + self._client.set_volume(int(100 * volume)) + + def media_play_pause(self): + """Simulate play pause media player.""" + if self._state == STATE_PLAYING: + self.media_pause() + else: + self.media_play() + + def media_play(self): + """Send play command.""" + self._state = STATE_PLAYING + self._client.play() + + def media_pause(self): + """Send media pause command to media player.""" + self._state = STATE_PAUSED + self._client.pause() + + def media_next_track(self): + """Send next track command.""" + self._client.next() + + def media_previous_track(self): + """Send the previous track command.""" + self._client.previous() diff --git a/requirements_all.txt b/requirements_all.txt index cc41335110a3f09c9fad3c7d26422537e871e601..cf59bcc75c1bfaf68bc17d4d66647f5d67a788a1 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -537,6 +537,9 @@ pysma==0.1.3 # homeassistant.components.sensor.snmp pysnmp==4.3.3 +# homeassistant.components.media_player.clementine +python-clementine-remote==1.0.1 + # homeassistant.components.digital_ocean python-digitalocean==1.10.1