273 lines
8.2 KiB
Python
273 lines
8.2 KiB
Python
"""
|
|
homeassistant.components.media_player.kodi
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
Provides an interface to the XBMC/Kodi JSON-RPC API
|
|
|
|
For more details about this platform, please refer to the documentation at
|
|
https://home-assistant.io/components/media_player.kodi/
|
|
"""
|
|
import logging
|
|
import urllib
|
|
|
|
from homeassistant.components.media_player import (
|
|
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK,
|
|
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice)
|
|
from homeassistant.const import (
|
|
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
REQUIREMENTS = ['jsonrpc-requests==0.1']
|
|
|
|
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
|
|
|
|
|
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
|
""" Sets up the kodi platform. """
|
|
|
|
url = '{}:{}'.format(config.get('host'), config.get('port', '8080'))
|
|
|
|
jsonrpc_url = config.get('url') # deprecated
|
|
if jsonrpc_url:
|
|
url = jsonrpc_url.rstrip('/jsonrpc')
|
|
|
|
add_devices([
|
|
KodiDevice(
|
|
config.get('name', 'Kodi'),
|
|
url,
|
|
auth=(
|
|
config.get('user', ''),
|
|
config.get('password', ''))),
|
|
])
|
|
|
|
|
|
class KodiDevice(MediaPlayerDevice):
|
|
""" Represents a XBMC/Kodi device. """
|
|
|
|
# pylint: disable=too-many-public-methods, abstract-method
|
|
|
|
def __init__(self, name, url, auth=None):
|
|
import jsonrpc_requests
|
|
self._name = name
|
|
self._url = url
|
|
self._server = jsonrpc_requests.Server(
|
|
'{}/jsonrpc'.format(self._url),
|
|
auth=auth)
|
|
self._players = None
|
|
self._properties = None
|
|
self._item = None
|
|
self._app_properties = None
|
|
self.update()
|
|
|
|
@property
|
|
def name(self):
|
|
""" Returns the name of the device. """
|
|
return self._name
|
|
|
|
def _get_players(self):
|
|
""" Returns the active player objects or None """
|
|
import jsonrpc_requests
|
|
try:
|
|
return self._server.Player.GetActivePlayers()
|
|
except jsonrpc_requests.jsonrpc.TransportError:
|
|
_LOGGER.warning('Unable to fetch kodi data')
|
|
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
|
return None
|
|
|
|
@property
|
|
def state(self):
|
|
""" Returns the state of the device. """
|
|
if self._players is None:
|
|
return STATE_OFF
|
|
|
|
if len(self._players) == 0:
|
|
return STATE_IDLE
|
|
|
|
if self._properties['speed'] == 0:
|
|
return STATE_PAUSED
|
|
else:
|
|
return STATE_PLAYING
|
|
|
|
def update(self):
|
|
""" Retrieve latest state. """
|
|
self._players = self._get_players()
|
|
|
|
if self._players is not None and len(self._players) > 0:
|
|
player_id = self._players[0]['playerid']
|
|
|
|
assert isinstance(player_id, int)
|
|
|
|
self._properties = self._server.Player.GetProperties(
|
|
player_id,
|
|
['time', 'totaltime', 'speed']
|
|
)
|
|
|
|
self._item = self._server.Player.GetItem(
|
|
player_id,
|
|
['title', 'file', 'uniqueid', 'thumbnail', 'artist']
|
|
)['item']
|
|
|
|
self._app_properties = self._server.Application.GetProperties(
|
|
['volume', 'muted']
|
|
)
|
|
else:
|
|
self._properties = None
|
|
self._item = None
|
|
self._app_properties = None
|
|
|
|
@property
|
|
def volume_level(self):
|
|
""" Volume level of the media player (0..1). """
|
|
if self._app_properties is not None:
|
|
return self._app_properties['volume'] / 100.0
|
|
|
|
@property
|
|
def is_volume_muted(self):
|
|
""" Boolean if volume is currently muted. """
|
|
if self._app_properties is not None:
|
|
return self._app_properties['muted']
|
|
|
|
@property
|
|
def media_content_id(self):
|
|
""" Content ID of current playing media. """
|
|
if self._item is not None:
|
|
return self._item.get('uniqueid', None)
|
|
|
|
@property
|
|
def media_content_type(self):
|
|
""" Content type of current playing media. """
|
|
if self._players is not None and len(self._players) > 0:
|
|
return self._players[0]['type']
|
|
|
|
@property
|
|
def media_duration(self):
|
|
""" Duration of current playing media in seconds. """
|
|
if self._properties is not None:
|
|
total_time = self._properties['totaltime']
|
|
|
|
return (
|
|
total_time['hours'] * 3600 +
|
|
total_time['minutes'] * 60 +
|
|
total_time['seconds'])
|
|
|
|
@property
|
|
def media_image_url(self):
|
|
""" Image url of current playing media. """
|
|
if self._item is not None:
|
|
return self._get_image_url()
|
|
|
|
def _get_image_url(self):
|
|
""" Helper function that parses the thumbnail URLs used by Kodi. """
|
|
url_components = urllib.parse.urlparse(self._item['thumbnail'])
|
|
|
|
if url_components.scheme == 'image':
|
|
return '{}/image/{}'.format(
|
|
self._url,
|
|
urllib.parse.quote_plus(self._item['thumbnail']))
|
|
|
|
@property
|
|
def media_title(self):
|
|
""" Title of current playing media. """
|
|
# 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):
|
|
""" Flags of media commands that are supported. """
|
|
return SUPPORT_KODI
|
|
|
|
def turn_off(self):
|
|
""" turn_off media player. """
|
|
self._server.System.Shutdown()
|
|
self.update_ha_state()
|
|
|
|
def volume_up(self):
|
|
""" volume_up media player. """
|
|
assert self._server.Input.ExecuteAction('volumeup') == 'OK'
|
|
self.update_ha_state()
|
|
|
|
def volume_down(self):
|
|
""" volume_down media player. """
|
|
assert self._server.Input.ExecuteAction('volumedown') == 'OK'
|
|
self.update_ha_state()
|
|
|
|
def set_volume_level(self, volume):
|
|
""" set volume level, range 0..1. """
|
|
self._server.Application.SetVolume(int(volume * 100))
|
|
self.update_ha_state()
|
|
|
|
def mute_volume(self, mute):
|
|
""" mute (true) or unmute (false) media player. """
|
|
self._server.Application.SetMute(mute)
|
|
self.update_ha_state()
|
|
|
|
def _set_play_state(self, state):
|
|
""" Helper method for play/pause/toggle. """
|
|
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):
|
|
""" media_play_pause media player. """
|
|
self._set_play_state('toggle')
|
|
|
|
def media_play(self):
|
|
""" media_play media player. """
|
|
self._set_play_state(True)
|
|
|
|
def media_pause(self):
|
|
""" media_pause media player. """
|
|
self._set_play_state(False)
|
|
|
|
def _goto(self, direction):
|
|
""" Helper method used for previous/next track. """
|
|
players = self._get_players()
|
|
|
|
if len(players) != 0:
|
|
self._server.Player.GoTo(players[0]['playerid'], direction)
|
|
|
|
self.update_ha_state()
|
|
|
|
def media_next_track(self):
|
|
""" Send next track command. """
|
|
self._goto('next')
|
|
|
|
def media_previous_track(self):
|
|
""" Send next track command. """
|
|
# 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):
|
|
""" Send seek command. """
|
|
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()
|