core/homeassistant/components/media_player/kodi.py

284 lines
8.5 KiB
Python
Raw Normal View History

2015-06-17 11:44:39 +00:00
"""
2016-03-08 09:34:33 +00:00
Support for interfacing with the XBMC/Kodi JSON-RPC API.
2015-06-17 11:44:39 +00:00
2015-10-23 16:15:12 +00:00
For more details about this platform, please refer to the documentation at
2015-11-09 12:12:18 +00:00
https://home-assistant.io/components/media_player.kodi/
2015-06-17 11:44:39 +00:00
"""
2015-06-17 15:12:15 +00:00
import logging
2016-02-19 05:27:50 +00:00
import urllib
2015-06-17 15:12:15 +00:00
2015-06-17 11:44:39 +00:00
from homeassistant.components.media_player import (
2016-02-19 05:27:50 +00:00
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP,
MediaPlayerDevice)
2015-06-17 15:12:15 +00:00
from homeassistant.const import (
2016-02-19 05:27:50 +00:00
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
2015-06-17 15:12:15 +00:00
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['jsonrpc-requests==0.2']
2015-06-17 15:12:15 +00:00
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
SUPPORT_PLAY_MEDIA | SUPPORT_STOP
2015-06-17 11:44:39 +00:00
def setup_platform(hass, config, add_devices, discovery_info=None):
2016-03-08 09:34:33 +00:00
"""Setup the Kodi platform."""
2016-02-11 21:07:47 +00:00
url = '{}:{}'.format(config.get('host'), config.get('port', '8080'))
jsonrpc_url = config.get('url') # deprecated
if jsonrpc_url:
url = jsonrpc_url.rstrip('/jsonrpc')
2015-06-17 11:44:39 +00:00
add_devices([
KodiDevice(
config.get('name', 'Kodi'),
2016-02-11 21:07:47 +00:00
url,
2015-06-17 11:44:39 +00:00
auth=(
config.get('user', ''),
config.get('password', ''))),
])
class KodiDevice(MediaPlayerDevice):
2016-03-08 09:34:33 +00:00
"""Representation of a XBMC/Kodi device."""
2015-06-17 15:12:15 +00:00
2016-02-02 08:31:36 +00:00
# pylint: disable=too-many-public-methods, abstract-method
2015-06-17 11:44:39 +00:00
def __init__(self, name, url, auth=None):
2016-03-08 09:34:33 +00:00
"""Initialize the Kodi device."""
2015-11-29 22:04:44 +00:00
import jsonrpc_requests
2015-06-17 11:44:39 +00:00
self._name = name
self._url = url
2016-02-11 21:07:47 +00:00
self._server = jsonrpc_requests.Server(
'{}/jsonrpc'.format(self._url),
auth=auth)
self._players = list()
2015-06-17 15:12:15 +00:00
self._properties = None
self._item = None
self._app_properties = None
self.update()
2015-06-17 11:44:39 +00:00
@property
def name(self):
2016-03-08 09:34:33 +00:00
"""Return the name of the device."""
2015-06-17 11:44:39 +00:00
return self._name
def _get_players(self):
2016-03-08 09:34:33 +00:00
"""Return the active player objects or None."""
2015-11-29 22:04:44 +00:00
import jsonrpc_requests
2015-06-17 15:12:15 +00:00
try:
return self._server.Player.GetActivePlayers()
except jsonrpc_requests.jsonrpc.TransportError:
if self._players is not None:
_LOGGER.warning('Unable to fetch kodi data')
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
2015-06-17 15:12:15 +00:00
return None
2015-06-17 11:44:39 +00:00
@property
def state(self):
2016-03-08 09:34:33 +00:00
"""Return the state of the device."""
2015-06-17 15:12:15 +00:00
if self._players is None:
return STATE_OFF
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
if len(self._players) == 0:
return STATE_IDLE
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
if self._properties['speed'] == 0:
return STATE_PAUSED
else:
return STATE_PLAYING
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
def update(self):
2016-03-08 09:34:33 +00:00
"""Retrieve latest state."""
2015-06-17 15:12:15 +00:00
self._players = self._get_players()
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
if self._players is not None and len(self._players) > 0:
player_id = self._players[0]['playerid']
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
assert isinstance(player_id, int)
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
self._properties = self._server.Player.GetProperties(
player_id,
['time', 'totaltime', 'speed']
)
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
self._item = self._server.Player.GetItem(
player_id,
['title', 'file', 'uniqueid', 'thumbnail', 'artist']
)['item']
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
self._app_properties = self._server.Application.GetProperties(
['volume', 'muted']
)
2015-06-18 09:46:02 +00:00
else:
self._properties = None
self._item = None
self._app_properties = None
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
@property
def volume_level(self):
2016-03-08 09:34:33 +00:00
"""Volume level of the media player (0..1)."""
2015-06-17 15:12:15 +00:00
if self._app_properties is not None:
return self._app_properties['volume'] / 100.0
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
@property
def is_volume_muted(self):
2016-03-08 09:34:33 +00:00
"""Boolean if volume is currently muted."""
2015-06-17 15:12:15 +00:00
if self._app_properties is not None:
return self._app_properties['muted']
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
@property
def media_content_id(self):
2016-03-08 09:34:33 +00:00
"""Content ID of current playing media."""
2015-06-17 15:12:15 +00:00
if self._item is not None:
2015-10-08 11:41:58 +00:00
return self._item.get('uniqueid', None)
2015-06-17 15:12:15 +00:00
@property
def media_content_type(self):
2016-03-08 09:34:33 +00:00
"""Content type of current playing media."""
2015-06-17 15:12:15 +00:00
if self._players is not None and len(self._players) > 0:
return self._players[0]['type']
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
@property
def media_duration(self):
2016-03-08 09:34:33 +00:00
"""Duration of current playing media in seconds."""
2015-06-17 15:12:15 +00:00
if self._properties is not None:
total_time = self._properties['totaltime']
2015-06-17 11:44:39 +00:00
2015-06-17 15:12:15 +00:00
return (
2015-06-17 11:44:39 +00:00
total_time['hours'] * 3600 +
total_time['minutes'] * 60 +
2015-06-17 15:12:15 +00:00
total_time['seconds'])
@property
def media_image_url(self):
2016-03-08 09:34:33 +00:00
"""Image url of current playing media."""
2015-06-17 15:12:15 +00:00
if self._item is not None:
2016-02-10 19:48:41 +00:00
return self._get_image_url()
def _get_image_url(self):
2016-03-08 09:34:33 +00:00
"""Helper function that parses the thumbnail URLs used by Kodi."""
2016-02-10 19:48:41 +00:00
url_components = urllib.parse.urlparse(self._item['thumbnail'])
2016-02-11 21:07:47 +00:00
2016-02-10 19:48:41 +00:00
if url_components.scheme == 'image':
2016-02-11 21:07:47 +00:00
return '{}/image/{}'.format(
self._url,
urllib.parse.quote_plus(self._item['thumbnail']))
2015-06-17 15:12:15 +00:00
@property
def media_title(self):
2016-03-08 09:34:33 +00:00
"""Title of current playing media."""
2015-06-17 15:12:15 +00:00
# find a string we can use as a title
if self._item is not None:
return self._item.get(
'title',
self._item.get(
'label',
self._item.get(
'file',
'unknown')))
@property
def supported_media_commands(self):
2016-03-08 09:34:33 +00:00
"""Flag of media commands that are supported."""
2015-06-17 15:12:15 +00:00
return SUPPORT_KODI
2015-06-17 11:44:39 +00:00
def turn_off(self):
2016-03-08 09:34:33 +00:00
"""Turn off media player."""
2015-06-17 11:44:39 +00:00
self._server.System.Shutdown()
self.update_ha_state()
def volume_up(self):
2016-03-08 09:34:33 +00:00
"""Volume up the media player."""
2015-06-17 11:44:39 +00:00
assert self._server.Input.ExecuteAction('volumeup') == 'OK'
self.update_ha_state()
def volume_down(self):
2016-03-08 09:34:33 +00:00
"""Volume down the media player."""
2015-06-17 11:44:39 +00:00
assert self._server.Input.ExecuteAction('volumedown') == 'OK'
self.update_ha_state()
2015-06-17 15:12:15 +00:00
def set_volume_level(self, volume):
2016-03-08 09:34:33 +00:00
"""Set volume level, range 0..1."""
2015-06-17 11:44:39 +00:00
self._server.Application.SetVolume(int(volume * 100))
self.update_ha_state()
2015-06-17 15:12:15 +00:00
def mute_volume(self, mute):
2016-03-08 09:34:33 +00:00
"""Mute (true) or unmute (false) media player."""
2015-06-17 11:44:39 +00:00
self._server.Application.SetMute(mute)
self.update_ha_state()
def _set_play_state(self, state):
2016-03-08 09:34:33 +00:00
"""Helper method for play/pause/toggle."""
2015-06-17 11:44:39 +00:00
players = self._get_players()
if len(players) != 0:
self._server.Player.PlayPause(players[0]['playerid'], state)
self.update_ha_state()
def media_play_pause(self):
2016-03-08 09:34:33 +00:00
"""Pause media on media player."""
2015-06-17 11:44:39 +00:00
self._set_play_state('toggle')
def media_play(self):
2016-03-08 09:34:33 +00:00
"""Play media."""
2015-06-17 11:44:39 +00:00
self._set_play_state(True)
def media_pause(self):
2016-03-08 09:34:33 +00:00
"""Pause the media player."""
2015-06-17 11:44:39 +00:00
self._set_play_state(False)
def media_stop(self):
"""Stop the media player."""
players = self._get_players()
if len(players) != 0:
self._server.Player.Stop(players[0]['playerid'])
2015-06-17 15:12:15 +00:00
def _goto(self, direction):
2016-03-08 09:34:33 +00:00
"""Helper method used for previous/next track."""
2015-06-17 11:44:39 +00:00
players = self._get_players()
if len(players) != 0:
2015-06-17 15:12:15 +00:00
self._server.Player.GoTo(players[0]['playerid'], direction)
2015-06-17 11:44:39 +00:00
self.update_ha_state()
def media_next_track(self):
2016-03-08 09:34:33 +00:00
"""Send next track command."""
2015-06-17 11:44:39 +00:00
self._goto('next')
2015-06-17 15:12:15 +00:00
def media_previous_track(self):
2016-03-08 09:34:33 +00:00
"""Send next track command."""
2015-06-17 15:12:15 +00:00
# first seek to position 0, Kodi seems to go to the beginning
# of the current track current track is not at the beginning
self.media_seek(0)
self._goto('previous')
def media_seek(self, position):
2016-03-08 09:34:33 +00:00
"""Send seek command."""
2015-06-17 15:12:15 +00:00
players = self._get_players()
time = {}
time['milliseconds'] = int((position % 1) * 1000)
position = int(position)
time['seconds'] = int(position % 60)
position /= 60
time['minutes'] = int(position % 60)
position /= 60
time['hours'] = int(position)
if len(players) != 0:
self._server.Player.Seek(players[0]['playerid'], time)
self.update_ha_state()
def play_media(self, media_type, media_id):
"""Send the play_media command to the media player."""
self._server.Player.Open({media_type: media_id}, {})