diff --git a/.coveragerc b/.coveragerc index 8bf5509c126..397db5394d6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -665,6 +665,7 @@ omit = homeassistant/components/viaggiatreno/sensor.py homeassistant/components/vizio/media_player.py homeassistant/components/vlc/media_player.py + homeassistant/components/vlc_telnet/media_player.py homeassistant/components/volkszaehler/sensor.py homeassistant/components/volumio/media_player.py homeassistant/components/volvooncall/* diff --git a/CODEOWNERS b/CODEOWNERS index dfdd9a4d396..91e6ab48947 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -276,6 +276,7 @@ homeassistant/components/utility_meter/* @dgomes homeassistant/components/velux/* @Julius2342 homeassistant/components/version/* @fabaff homeassistant/components/vizio/* @raman325 +homeassistant/components/vlc_telnet/* @rodripf homeassistant/components/waqi/* @andrey-git homeassistant/components/watson_tts/* @rutkai homeassistant/components/weather/* @fabaff diff --git a/homeassistant/components/vlc_telnet/__init__.py b/homeassistant/components/vlc_telnet/__init__.py new file mode 100644 index 00000000000..91a3eb35444 --- /dev/null +++ b/homeassistant/components/vlc_telnet/__init__.py @@ -0,0 +1 @@ +"""The vlc component.""" diff --git a/homeassistant/components/vlc_telnet/manifest.json b/homeassistant/components/vlc_telnet/manifest.json new file mode 100644 index 00000000000..1e0f1c71df5 --- /dev/null +++ b/homeassistant/components/vlc_telnet/manifest.json @@ -0,0 +1,10 @@ +{ + "domain": "vlc_telnet", + "name": "VLC telnet", + "documentation": "https://www.home-assistant.io/components/vlc-telnet", + "requirements": [ + "python-telnet-vlc==1.0.4" + ], + "dependencies": [], + "codeowners": ["@rodripf"] +} diff --git a/homeassistant/components/vlc_telnet/media_player.py b/homeassistant/components/vlc_telnet/media_player.py new file mode 100644 index 00000000000..096afcc1044 --- /dev/null +++ b/homeassistant/components/vlc_telnet/media_player.py @@ -0,0 +1,233 @@ +"""Provide functionality to interact with the vlc telnet interface.""" +import logging +import voluptuous as vol + +from python_telnet_vlc import VLCTelnet, ConnectionError as ConnErr + +from homeassistant.components.media_player import ( + MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.components.media_player.const import ( + MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, SUPPORT_PLAY, + SUPPORT_PLAY_MEDIA, SUPPORT_STOP, SUPPORT_VOLUME_MUTE, + SUPPORT_VOLUME_SET, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, + SUPPORT_NEXT_TRACK, SUPPORT_CLEAR_PLAYLIST, SUPPORT_SHUFFLE_SET) +from homeassistant.const import ( + CONF_NAME, STATE_IDLE, STATE_PAUSED, STATE_PLAYING, STATE_UNAVAILABLE, + CONF_HOST, CONF_PORT, CONF_PASSWORD) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'vlc_telnet' + +DEFAULT_NAME = 'VLC-TELNET' +DEFAULT_PORT = 4212 + +SUPPORT_VLC = SUPPORT_PAUSE | SUPPORT_SEEK | SUPPORT_VOLUME_SET \ + | SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK \ + | SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA | SUPPORT_STOP \ + | SUPPORT_CLEAR_PLAYLIST | SUPPORT_PLAY \ + | SUPPORT_SHUFFLE_SET +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.positive_int, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the vlc platform.""" + add_entities([VlcDevice(config.get(CONF_NAME), + config.get(CONF_HOST), + config.get(CONF_PORT), + config.get(CONF_PASSWORD))], True) + + +class VlcDevice(MediaPlayerDevice): + """Representation of a vlc player.""" + + def __init__(self, name, host, port, passwd): + """Initialize the vlc device.""" + self._instance = None + self._name = name + self._volume = None + self._muted = None + self._state = STATE_UNAVAILABLE + self._media_position_updated_at = None + self._media_position = None + self._media_duration = None + self._host = host + self._port = port + self._password = passwd + self._vlc = None + self._available = False + self._volume_bkp = 0 + self._media_artist = "" + self._media_title = "" + + def update(self): + """Get the latest details from the device.""" + if self._vlc is None: + try: + self._vlc = VLCTelnet(self._host, self._password, self._port) + self._state = STATE_IDLE + self._available = True + except (ConnErr, EOFError): + self._available = False + self._vlc = None + else: + try: + status = self._vlc.status() + if status: + if 'volume' in status: + self._volume = int(status['volume']) / 500.0 + else: + self._volume = None + if 'state' in status: + state = status["state"] + if state == "playing": + self._state = STATE_PLAYING + elif state == "paused": + self._state = STATE_PAUSED + else: + self._state = STATE_IDLE + else: + self._state = STATE_IDLE + + self._media_duration = self._vlc.get_length() + self._media_position = self._vlc.get_time() + + info = self._vlc.info() + if info: + self._media_artist = info[0].get('artist') + self._media_title = info[0].get('title') + + except (ConnErr, EOFError): + self._available = False + self._vlc = None + + return True + + @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 available(self) -> bool: + """Return True if entity is available.""" + return self._available + + @property + def volume_level(self): + """Volume level of the media player (0..1).""" + return self._volume + + @property + def is_volume_muted(self): + """Boolean if volume is currently muted.""" + return self._muted + + @property + def supported_features(self): + """Flag media player features that are supported.""" + return SUPPORT_VLC + + @property + def media_content_type(self): + """Content type of current playing media.""" + return MEDIA_TYPE_MUSIC + + @property + def media_duration(self): + """Duration of current playing media in seconds.""" + return self._media_duration + + @property + def media_position(self): + """Position of current playing media in seconds.""" + return self._media_position + + @property + def media_position_updated_at(self): + """When was the position of the current playing media valid.""" + return self._media_position_updated_at + + @property + def media_title(self): + """Title of current playing media.""" + return self._media_title + + @property + def media_artist(self): + """Artist of current playing media, music track only.""" + return self._media_artist + + def media_seek(self, position): + """Seek the media to a specific location.""" + track_length = self._vlc.get_length() / 1000 + self._vlc.seek(position / track_length) + + def mute_volume(self, mute): + """Mute the volume.""" + if mute: + self._volume_bkp = self._volume + self._volume = 0 + self._vlc.set_volume("0") + else: + self._vlc.set_volume(str(self._volume_bkp)) + self._volume = self._volume_bkp + + self._muted = mute + + def set_volume_level(self, volume): + """Set volume level, range 0..1.""" + self._vlc.set_volume(str(volume * 500)) + self._volume = volume + + def media_play(self): + """Send play command.""" + self._vlc.play() + self._state = STATE_PLAYING + + def media_pause(self): + """Send pause command.""" + self._vlc.pause() + self._state = STATE_PAUSED + + def media_stop(self): + """Send stop command.""" + self._vlc.stop() + self._state = STATE_IDLE + + def play_media(self, media_type, media_id, **kwargs): + """Play media from a URL or file.""" + if media_type != MEDIA_TYPE_MUSIC: + _LOGGER.error( + "Invalid media type %s. Only %s is supported", + media_type, MEDIA_TYPE_MUSIC) + return + self._vlc.add(media_id) + self._state = STATE_PLAYING + + def media_previous_track(self): + """Send previous track command.""" + self._vlc.prev() + + def media_next_track(self): + """Send next track command.""" + self._vlc.next() + + def clear_playlist(self): + """Clear players playlist.""" + self._vlc.clear() + + def set_shuffle(self, shuffle): + """Enable/disable shuffle mode.""" + self._vlc.random(shuffle) diff --git a/requirements_all.txt b/requirements_all.txt index 6863078985b..05693f649e9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1477,6 +1477,9 @@ python-tado==0.2.9 # homeassistant.components.telegram_bot python-telegram-bot==11.1.0 +# homeassistant.components.vlc_telnet +python-telnet-vlc==1.0.4 + # homeassistant.components.twitch python-twitch-client==0.6.0