-
-
Notifications
You must be signed in to change notification settings - Fork 38.2k
Fix Kodi specific services registry and add descriptions #7551
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,16 +9,18 @@ | |
| import logging | ||
| import urllib | ||
| import re | ||
| import os | ||
|
|
||
| import aiohttp | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.config import load_yaml_config_file | ||
| from homeassistant.components.media_player import ( | ||
| SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, | ||
| SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP, | ||
| SUPPORT_TURN_OFF, SUPPORT_PLAY, SUPPORT_VOLUME_STEP, MediaPlayerDevice, | ||
| PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO, | ||
| MEDIA_TYPE_PLAYLIST, MEDIA_PLAYER_SCHEMA, DOMAIN) | ||
| SUPPORT_TURN_OFF, SUPPORT_PLAY, SUPPORT_VOLUME_STEP, SUPPORT_SHUFFLE_SET, | ||
| MediaPlayerDevice, PLATFORM_SCHEMA, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, | ||
| MEDIA_TYPE_VIDEO, MEDIA_TYPE_PLAYLIST, MEDIA_PLAYER_SCHEMA, DOMAIN) | ||
| from homeassistant.const import ( | ||
| STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME, | ||
| CONF_PORT, CONF_SSL, CONF_PROXY_SSL, CONF_USERNAME, CONF_PASSWORD, | ||
|
|
@@ -61,8 +63,9 @@ | |
| } | ||
|
|
||
| SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ | ||
| SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \ | ||
| SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_VOLUME_STEP | ||
| SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \ | ||
| SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_SHUFFLE_SET | \ | ||
| SUPPORT_PLAY | SUPPORT_VOLUME_STEP | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_HOST): cv.string, | ||
|
|
@@ -78,22 +81,25 @@ | |
| }) | ||
|
|
||
| SERVICE_ADD_MEDIA = 'kodi_add_to_playlist' | ||
| SERVICE_SET_SHUFFLE = 'kodi_set_shuffle' | ||
| SERVICE_SET_SHUFFLE = 'kodi_set_shuffle' # this is the same as mp.shuffle_set | ||
|
|
||
| DATA_KODI = 'kodi' | ||
|
|
||
| ATTR_MEDIA_TYPE = 'media_type' | ||
| ATTR_MEDIA_NAME = 'media_name' | ||
| ATTR_MEDIA_ARTIST_NAME = 'artist_name' | ||
| ATTR_MEDIA_ID = 'media_id' | ||
|
|
||
| MEDIA_PLAYER_SET_SHUFFLE_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ | ||
| vol.Required('shuffle_on'): cv.boolean, | ||
| vol.Exclusive('shuffle_on', 'shuffle'): cv.boolean, | ||
| vol.Exclusive('shuffle', 'shuffle'): cv.boolean, | ||
| }) | ||
|
|
||
| MEDIA_PLAYER_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({ | ||
| vol.Required(ATTR_MEDIA_TYPE): cv.string, | ||
| vol.Optional(ATTR_MEDIA_ID): cv.string, | ||
| vol.Optional(ATTR_MEDIA_NAME): cv.string, | ||
| vol.Optional(ATTR_MEDIA_ARTIST_NAME): cv.string, | ||
| vol.Inclusive(ATTR_MEDIA_NAME, 'media_search'): cv.string, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we keep these optional, and default it to |
||
| vol.Inclusive(ATTR_MEDIA_ARTIST_NAME, 'media_search'): cv.string, | ||
| }) | ||
|
|
||
| SERVICE_TO_METHOD = { | ||
|
|
@@ -109,6 +115,8 @@ | |
| @asyncio.coroutine | ||
| def async_setup_platform(hass, config, async_add_devices, discovery_info=None): | ||
| """Set up the Kodi platform.""" | ||
| if DATA_KODI not in hass.data: | ||
| hass.data[DATA_KODI] = [] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| host = config.get(CONF_HOST) | ||
| port = config.get(CONF_PORT) | ||
| tcp_port = config.get(CONF_TCP_PORT) | ||
|
|
@@ -130,7 +138,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): | |
| password=config.get(CONF_PASSWORD), | ||
| turn_off_action=config.get(CONF_TURN_OFF_ACTION), websocket=websocket) | ||
|
|
||
| hass.data[DATA_KODI].append(entity) | ||
| async_add_devices([entity], update_before_add=True) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Somehow this was missed before, but this is a coroutine. If we make this
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is not possible, since v0.40, the passed in I also did not like having to use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I missed that. In that case then yeah, going back to the list seems to make the most sense. |
||
| descriptions = yield from hass.loop.run_in_executor( | ||
| None, load_yaml_config_file, os.path.join( | ||
| os.path.dirname(__file__), 'services.yaml')) | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we want to add a guard clause here to check if the services are already registered. Something like if hass.services.has_service(DOMAIN, SERVICE_ADD_MEDIA):
return |
||
| @asyncio.coroutine | ||
| def async_service_handler(service): | ||
|
|
@@ -141,23 +153,30 @@ def async_service_handler(service): | |
|
|
||
| params = {key: value for key, value in service.data.items() | ||
| if key != 'entity_id'} | ||
|
|
||
| yield from getattr(entity, method['method'])(**params) | ||
| entity_ids = service.data.get('entity_id') | ||
| if entity_ids: | ||
| target_players = [player for player in hass.data[DATA_KODI] | ||
| if player.entity_id in entity_ids] | ||
| else: | ||
| target_players = hass.data[DATA_KODI] | ||
|
|
||
| update_tasks = [] | ||
| if entity.should_poll: | ||
| update_coro = entity.async_update_ha_state(True) | ||
| update_tasks.append(update_coro) | ||
| for player in target_players: | ||
| yield from getattr(player, method['method'])(**params) | ||
|
|
||
| for player in target_players: | ||
| if player.should_poll: | ||
| update_coro = player.async_update_ha_state(True) | ||
| update_tasks.append(update_coro) | ||
|
|
||
| if update_tasks: | ||
| yield from asyncio.wait(update_tasks, loop=hass.loop) | ||
|
|
||
| for service in SERVICE_TO_METHOD: | ||
| schema = SERVICE_TO_METHOD[service].get( | ||
| 'schema', MEDIA_PLAYER_SCHEMA) | ||
| schema = SERVICE_TO_METHOD[service].get('schema') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could even make this |
||
| hass.services.async_register( | ||
| DOMAIN, service, async_service_handler, | ||
| description=None, schema=schema) | ||
| description=descriptions.get(service), schema=schema) | ||
|
|
||
|
|
||
| def cmd(func): | ||
|
|
@@ -657,12 +676,13 @@ def async_play_media(self, media_type, media_id, **kwargs): | |
| {"item": {"file": str(media_id)}}) | ||
|
|
||
| @asyncio.coroutine | ||
| def async_set_shuffle(self, shuffle_on): | ||
| def async_set_shuffle(self, shuffle_on=None, shuffle=None): | ||
| """Set shuffle mode, for the first player.""" | ||
| shuffle = shuffle if shuffle is not None else shuffle_on | ||
| if len(self._players) < 1: | ||
| raise RuntimeError("Error: No active player.") | ||
| yield from self.server.Player.SetShuffle( | ||
| {"playerid": self._players[0]['playerid'], "shuffle": shuffle_on}) | ||
| {"playerid": self._players[0]['playerid'], "shuffle": shuffle}) | ||
|
|
||
| @asyncio.coroutine | ||
| def async_add_media_to_playlist( | ||
|
|
@@ -675,13 +695,14 @@ def async_add_media_to_playlist( | |
| All the albums of an artist can be added with | ||
| media_name="ALL" | ||
| """ | ||
| import jsonrpc_base | ||
| params = {"playlistid": 0} | ||
| if media_type == "SONG": | ||
| if media_id is None: | ||
| media_id = yield from self.async_find_song( | ||
| media_name, artist_name) | ||
|
|
||
| yield from self.server.Playlist.Add( | ||
| {"playlistid": 0, "item": {"songid": int(media_id)}}) | ||
| if media_id: | ||
| params["item"] = {"songid": int(media_id)} | ||
|
|
||
| elif media_type == "ALBUM": | ||
| if media_id is None: | ||
|
|
@@ -691,12 +712,22 @@ def async_add_media_to_playlist( | |
|
|
||
| media_id = yield from self.async_find_album( | ||
| media_name, artist_name) | ||
| if media_id: | ||
| params["item"] = {"albumid": int(media_id)} | ||
|
|
||
| yield from self.server.Playlist.Add( | ||
| {"playlistid": 0, "item": {"albumid": int(media_id)}}) | ||
| else: | ||
| raise RuntimeError("Unrecognized media type.") | ||
|
|
||
| if media_id is not None: | ||
| try: | ||
| yield from self.server.Playlist.Add(params) | ||
| except jsonrpc_base.jsonrpc.ProtocolError as e: | ||
| result = e.args[2]['error'] | ||
| _LOGGER.error('Run API method %s.Playlist.Add(%s) error: %s', | ||
| self.entity_id, media_type, result) | ||
| else: | ||
| _LOGGER.warning('No media detected for Playlist.Add') | ||
|
|
||
| @asyncio.coroutine | ||
| def async_add_all_albums(self, artist_name): | ||
| """Add all albums of an artist to default playlist (i.e. playlistid=0). | ||
|
|
@@ -734,9 +765,13 @@ def async_get_albums(self, artist_id=None): | |
| def async_find_artist(self, artist_name): | ||
| """Find artist by name.""" | ||
| artists = yield from self.async_get_artists() | ||
| out = self._find( | ||
| artist_name, [a['artist'] for a in artists['artists']]) | ||
| return artists['artists'][out[0][0]]['artistid'] | ||
| try: | ||
| out = self._find( | ||
| artist_name, [a['artist'] for a in artists['artists']]) | ||
| return artists['artists'][out[0][0]]['artistid'] | ||
| except KeyError: | ||
| _LOGGER.warning('No artists were found: %s', artist_name) | ||
| return None | ||
|
|
||
| @asyncio.coroutine | ||
| def async_get_songs(self, artist_id=None): | ||
|
|
@@ -769,8 +804,14 @@ def async_find_album(self, album_name, artist_name=''): | |
| artist_id = yield from self.async_find_artist(artist_name) | ||
|
|
||
| albums = yield from self.async_get_albums(artist_id) | ||
| out = self._find(album_name, [a['label'] for a in albums['albums']]) | ||
| return albums['albums'][out[0][0]]['albumid'] | ||
| try: | ||
| out = self._find( | ||
| album_name, [a['label'] for a in albums['albums']]) | ||
| return albums['albums'][out[0][0]]['albumid'] | ||
| except KeyError: | ||
| _LOGGER.warning('No albums were found with artist: %s, album: %s', | ||
| artist_name, album_name) | ||
| return None | ||
|
|
||
| @staticmethod | ||
| def _find(key_word, words): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just eliminate this. It only existed for a short while before core shuffle support was added.