Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 69 additions & 34 deletions homeassistant/components/media_player/kodi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -78,17 +81,14 @@
})

SERVICE_ADD_MEDIA = 'kodi_add_to_playlist'
SERVICE_SET_SHUFFLE = 'kodi_set_shuffle'

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,
})

MEDIA_PLAYER_ADD_MEDIA_SCHEMA = MEDIA_PLAYER_SCHEMA.extend({
vol.Required(ATTR_MEDIA_TYPE): cv.string,
vol.Optional(ATTR_MEDIA_ID): cv.string,
Expand All @@ -100,15 +100,14 @@
SERVICE_ADD_MEDIA: {
'method': 'async_add_media_to_playlist',
'schema': MEDIA_PLAYER_ADD_MEDIA_SCHEMA},
SERVICE_SET_SHUFFLE: {
'method': 'async_set_shuffle',
'schema': MEDIA_PLAYER_SET_SHUFFLE_SCHEMA},
}


@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] = []
Copy link
Copy Markdown
Contributor

@emlove emlove May 11, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's instead make this a dict with the entity_id as the key, so we don't have to search it later.

host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
tcp_port = config.get(CONF_TCP_PORT)
Expand All @@ -130,6 +129,7 @@ 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)

@asyncio.coroutine
Expand All @@ -141,23 +141,37 @@ 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)

if hass.services.has_service(DOMAIN, SERVICE_ADD_MEDIA):
return

descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))

for service in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service].get(
'schema', MEDIA_PLAYER_SCHEMA)
schema = SERVICE_TO_METHOD[service]['schema']
hass.services.async_register(
DOMAIN, service, async_service_handler,
description=None, schema=schema)
description=descriptions.get(service), schema=schema)


def cmd(func):
Expand Down Expand Up @@ -657,16 +671,16 @@ 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):
"""Set shuffle mode, for the first player."""
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(
self, media_type, media_id=None, media_name='', artist_name=''):
self, media_type, media_id=None, media_name='ALL', artist_name=''):
"""Add a media to default playlist (i.e. playlistid=0).

First the media type must be selected, then
Expand All @@ -675,13 +689,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:
Expand All @@ -691,12 +706,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 exc:
result = exc.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).
Expand Down Expand Up @@ -734,9 +759,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):
Expand Down Expand Up @@ -769,8 +798,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):
Expand Down
20 changes: 20 additions & 0 deletions homeassistant/components/media_player/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,23 @@ soundtouch_remove_zone_slave:
slaves:
description: Name of slaves entities to remove from the existing zone
example: 'media_player.soundtouch_bedroom'

kodi_add_to_playlist:
description: Add music to the default playlist (i.e. playlistid=0).

fields:
entity_id:
description: Name(s) of the Kodi entities where to add the media.
example: 'media_player.living_room_kodi'
media_type:
description: Media type identifier. It must be one of SONG or ALBUM.
example: ALBUM
media_id:
description: Unique Id of the media entry to add (`songid` or albumid`). If not defined, `media_name` and `artist_name` are needed to search the Kodi music library.
example: 123456
media_name:
description: Optional media name for filtering media. Can be 'ALL' when `media_type` is 'ALBUM' and `artist_name` is specified, to add all songs from one artist.
example: 'Highway to Hell'
artist_name:
description: Optional artist name for filtering media.
example: 'AC/DC'