mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Websocket push notifications for Kodi (#6063)
* Websocket push notifications for Kodi * Only create ws server if ws enabled * Fix conditional websocket server creation
This commit is contained in:
parent
799fbe42f8
commit
86a1b0a6c6
@ -18,20 +18,26 @@ from homeassistant.components.media_player import (
|
|||||||
PLATFORM_SCHEMA)
|
PLATFORM_SCHEMA)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME,
|
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME,
|
||||||
CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_PASSWORD)
|
CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_PASSWORD,
|
||||||
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
REQUIREMENTS = ['jsonrpc-async==0.2']
|
REQUIREMENTS = ['jsonrpc-async==0.4', 'jsonrpc-websocket==0.2']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONF_TCP_PORT = 'tcp_port'
|
||||||
CONF_TURN_OFF_ACTION = 'turn_off_action'
|
CONF_TURN_OFF_ACTION = 'turn_off_action'
|
||||||
|
CONF_ENABLE_WEBSOCKET = 'enable_websocket'
|
||||||
|
|
||||||
DEFAULT_NAME = 'Kodi'
|
DEFAULT_NAME = 'Kodi'
|
||||||
DEFAULT_PORT = 8080
|
DEFAULT_PORT = 8080
|
||||||
|
DEFAULT_TCP_PORT = 9090
|
||||||
DEFAULT_TIMEOUT = 5
|
DEFAULT_TIMEOUT = 5
|
||||||
DEFAULT_SSL = False
|
DEFAULT_SSL = False
|
||||||
|
DEFAULT_ENABLE_WEBSOCKET = True
|
||||||
|
|
||||||
TURN_OFF_ACTION = [None, 'quit', 'hibernate', 'suspend', 'reboot', 'shutdown']
|
TURN_OFF_ACTION = [None, 'quit', 'hibernate', 'suspend', 'reboot', 'shutdown']
|
||||||
|
|
||||||
@ -43,10 +49,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|||||||
vol.Required(CONF_HOST): cv.string,
|
vol.Required(CONF_HOST): cv.string,
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
vol.Optional(CONF_TCP_PORT, default=DEFAULT_TCP_PORT): cv.port,
|
||||||
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
||||||
vol.Optional(CONF_TURN_OFF_ACTION, default=None): vol.In(TURN_OFF_ACTION),
|
vol.Optional(CONF_TURN_OFF_ACTION, default=None): vol.In(TURN_OFF_ACTION),
|
||||||
vol.Inclusive(CONF_USERNAME, 'auth'): cv.string,
|
vol.Inclusive(CONF_USERNAME, 'auth'): cv.string,
|
||||||
vol.Inclusive(CONF_PASSWORD, 'auth'): cv.string,
|
vol.Inclusive(CONF_PASSWORD, 'auth'): cv.string,
|
||||||
|
vol.Optional(CONF_ENABLE_WEBSOCKET, default=DEFAULT_ENABLE_WEBSOCKET):
|
||||||
|
cv.boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +65,9 @@ def async_setup_platform(hass, config, async_add_entities,
|
|||||||
"""Setup the Kodi platform."""
|
"""Setup the Kodi platform."""
|
||||||
host = config.get(CONF_HOST)
|
host = config.get(CONF_HOST)
|
||||||
port = config.get(CONF_PORT)
|
port = config.get(CONF_PORT)
|
||||||
use_encryption = config.get(CONF_SSL)
|
tcp_port = config.get(CONF_TCP_PORT)
|
||||||
|
encryption = config.get(CONF_SSL)
|
||||||
|
websocket = config.get(CONF_ENABLE_WEBSOCKET)
|
||||||
|
|
||||||
if host.startswith('http://') or host.startswith('https://'):
|
if host.startswith('http://') or host.startswith('https://'):
|
||||||
host = host.lstrip('http://').lstrip('https://')
|
host = host.lstrip('http://').lstrip('https://')
|
||||||
@ -68,10 +79,10 @@ def async_setup_platform(hass, config, async_add_entities,
|
|||||||
entity = KodiDevice(
|
entity = KodiDevice(
|
||||||
hass,
|
hass,
|
||||||
name=config.get(CONF_NAME),
|
name=config.get(CONF_NAME),
|
||||||
host=host, port=port, encryption=use_encryption,
|
host=host, port=port, tcp_port=tcp_port, encryption=encryption,
|
||||||
username=config.get(CONF_USERNAME),
|
username=config.get(CONF_USERNAME),
|
||||||
password=config.get(CONF_PASSWORD),
|
password=config.get(CONF_PASSWORD),
|
||||||
turn_off_action=config.get(CONF_TURN_OFF_ACTION))
|
turn_off_action=config.get(CONF_TURN_OFF_ACTION), websocket=websocket)
|
||||||
|
|
||||||
yield from async_add_entities([entity], update_before_add=True)
|
yield from async_add_entities([entity], update_before_add=True)
|
||||||
|
|
||||||
@ -79,10 +90,12 @@ def async_setup_platform(hass, config, async_add_entities,
|
|||||||
class KodiDevice(MediaPlayerDevice):
|
class KodiDevice(MediaPlayerDevice):
|
||||||
"""Representation of a XBMC/Kodi device."""
|
"""Representation of a XBMC/Kodi device."""
|
||||||
|
|
||||||
def __init__(self, hass, name, host, port, encryption=False, username=None,
|
def __init__(self, hass, name, host, port, tcp_port, encryption=False,
|
||||||
password=None, turn_off_action=None):
|
username=None, password=None, turn_off_action=None,
|
||||||
|
websocket=True):
|
||||||
"""Initialize the Kodi device."""
|
"""Initialize the Kodi device."""
|
||||||
import jsonrpc_async
|
import jsonrpc_async
|
||||||
|
import jsonrpc_websocket
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
@ -97,32 +110,92 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
else:
|
else:
|
||||||
image_auth_string = ""
|
image_auth_string = ""
|
||||||
|
|
||||||
protocol = 'https' if encryption else 'http'
|
http_protocol = 'https' if encryption else 'http'
|
||||||
|
ws_protocol = 'wss' if encryption else 'ws'
|
||||||
|
|
||||||
self._http_url = '{}://{}:{}/jsonrpc'.format(protocol, host, port)
|
self._http_url = '{}://{}:{}/jsonrpc'.format(http_protocol, host, port)
|
||||||
self._image_url = '{}://{}{}:{}/image'.format(
|
self._image_url = '{}://{}{}:{}/image'.format(
|
||||||
protocol, image_auth_string, host, port)
|
http_protocol, image_auth_string, host, port)
|
||||||
|
self._ws_url = '{}://{}:{}/jsonrpc'.format(ws_protocol, host, tcp_port)
|
||||||
|
|
||||||
self._server = jsonrpc_async.Server(self._http_url, **kwargs)
|
self._http_server = jsonrpc_async.Server(self._http_url, **kwargs)
|
||||||
|
if websocket:
|
||||||
|
# Setup websocket connection
|
||||||
|
self._ws_server = jsonrpc_websocket.Server(self._ws_url, **kwargs)
|
||||||
|
|
||||||
|
# Register notification listeners
|
||||||
|
self._ws_server.Player.OnPause = self.async_on_speed_event
|
||||||
|
self._ws_server.Player.OnPlay = self.async_on_speed_event
|
||||||
|
self._ws_server.Player.OnSpeedChanged = self.async_on_speed_event
|
||||||
|
self._ws_server.Player.OnStop = self.async_on_stop
|
||||||
|
self._ws_server.Application.OnVolumeChanged = \
|
||||||
|
self.async_on_volume_changed
|
||||||
|
self._ws_server.System.OnQuit = self.async_on_quit
|
||||||
|
self._ws_server.System.OnRestart = self.async_on_quit
|
||||||
|
self._ws_server.System.OnSleep = self.async_on_quit
|
||||||
|
|
||||||
|
def on_hass_stop(event):
|
||||||
|
"""Close websocket connection when hass stops."""
|
||||||
|
self.hass.async_add_job(self._ws_server.close())
|
||||||
|
|
||||||
|
self.hass.bus.async_listen_once(
|
||||||
|
EVENT_HOMEASSISTANT_STOP, on_hass_stop)
|
||||||
|
else:
|
||||||
|
self._ws_server = None
|
||||||
|
|
||||||
self._turn_off_action = turn_off_action
|
self._turn_off_action = turn_off_action
|
||||||
|
self._enable_websocket = websocket
|
||||||
self._players = list()
|
self._players = list()
|
||||||
self._properties = None
|
self._properties = {}
|
||||||
self._item = None
|
self._item = {}
|
||||||
self._app_properties = None
|
self._app_properties = {}
|
||||||
|
self._ws_connected = False
|
||||||
|
|
||||||
@property
|
@callback
|
||||||
def name(self):
|
def async_on_speed_event(self, sender, data):
|
||||||
"""Return the name of the device."""
|
"""Called when player changes between playing and paused."""
|
||||||
return self._name
|
self._properties['speed'] = data['player']['speed']
|
||||||
|
|
||||||
|
# If a new item is playing, force a complete refresh
|
||||||
|
new_item = data['item']['id'] != self._item.get('id')
|
||||||
|
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state(new_item))
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_on_stop(self, sender, data):
|
||||||
|
"""Called when the player stops playback."""
|
||||||
|
# Prevent stop notifications which are sent after quit notification
|
||||||
|
if self._players is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._players = []
|
||||||
|
self._properties = {}
|
||||||
|
self._item = {}
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_on_volume_changed(self, sender, data):
|
||||||
|
"""Called when the volume is changed."""
|
||||||
|
self._app_properties['volume'] = data['volume']
|
||||||
|
self._app_properties['muted'] = data['muted']
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_on_quit(self, sender, data):
|
||||||
|
"""Called when the volume is changed."""
|
||||||
|
self._players = None
|
||||||
|
self._properties = {}
|
||||||
|
self._item = {}
|
||||||
|
self._app_properties = {}
|
||||||
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _get_players(self):
|
def _get_players(self):
|
||||||
"""Return the active player objects or None."""
|
"""Return the active player objects or None."""
|
||||||
import jsonrpc_async
|
import jsonrpc_base
|
||||||
try:
|
try:
|
||||||
return (yield from self._server.Player.GetActivePlayers())
|
return (yield from self.server.Player.GetActivePlayers())
|
||||||
except jsonrpc_async.jsonrpc.TransportError:
|
except jsonrpc_base.jsonrpc.TransportError:
|
||||||
if self._players is not None:
|
if self._players is not None:
|
||||||
_LOGGER.info('Unable to fetch kodi data')
|
_LOGGER.info('Unable to fetch kodi data')
|
||||||
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
_LOGGER.debug('Unable to fetch kodi data', exc_info=True)
|
||||||
@ -142,51 +215,105 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
else:
|
else:
|
||||||
return STATE_PLAYING
|
return STATE_PLAYING
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_ws_connect(self):
|
||||||
|
"""Connect to Kodi via websocket protocol."""
|
||||||
|
import jsonrpc_base
|
||||||
|
try:
|
||||||
|
yield from self._ws_server.ws_connect()
|
||||||
|
except jsonrpc_base.jsonrpc.TransportError:
|
||||||
|
_LOGGER.info("Unable to connect to Kodi via websocket")
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Unable to connect to Kodi via websocket", exc_info=True)
|
||||||
|
# Websocket connection is not required. Just return.
|
||||||
|
return
|
||||||
|
self.hass.loop.create_task(self.async_ws_loop())
|
||||||
|
self._ws_connected = True
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_ws_loop(self):
|
||||||
|
"""Run the websocket asyncio message loop."""
|
||||||
|
import jsonrpc_base
|
||||||
|
try:
|
||||||
|
yield from self._ws_server.ws_loop()
|
||||||
|
except jsonrpc_base.jsonrpc.TransportError:
|
||||||
|
# Kodi abruptly ends ws connection when exiting. We only need to
|
||||||
|
# know that it was closed.
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
yield from self._ws_server.close()
|
||||||
|
self._ws_connected = False
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_update(self):
|
def async_update(self):
|
||||||
"""Retrieve latest state."""
|
"""Retrieve latest state."""
|
||||||
self._players = yield from self._get_players()
|
self._players = yield from self._get_players()
|
||||||
|
|
||||||
if self._players is not None and len(self._players) > 0:
|
if self._players is None:
|
||||||
|
self._properties = {}
|
||||||
|
self._item = {}
|
||||||
|
self._app_properties = {}
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._enable_websocket and not self._ws_connected:
|
||||||
|
self.hass.loop.create_task(self.async_ws_connect())
|
||||||
|
|
||||||
|
self._app_properties = \
|
||||||
|
yield from self.server.Application.GetProperties(
|
||||||
|
['volume', 'muted']
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(self._players) > 0:
|
||||||
player_id = self._players[0]['playerid']
|
player_id = self._players[0]['playerid']
|
||||||
|
|
||||||
assert isinstance(player_id, int)
|
assert isinstance(player_id, int)
|
||||||
|
|
||||||
self._properties = yield from self._server.Player.GetProperties(
|
self._properties = yield from self.server.Player.GetProperties(
|
||||||
player_id,
|
player_id,
|
||||||
['time', 'totaltime', 'speed', 'live']
|
['time', 'totaltime', 'speed', 'live']
|
||||||
)
|
)
|
||||||
|
|
||||||
self._item = (yield from self._server.Player.GetItem(
|
self._item = (yield from self.server.Player.GetItem(
|
||||||
player_id,
|
player_id,
|
||||||
['title', 'file', 'uniqueid', 'thumbnail', 'artist']
|
['title', 'file', 'uniqueid', 'thumbnail', 'artist']
|
||||||
))['item']
|
))['item']
|
||||||
|
|
||||||
self._app_properties = \
|
|
||||||
yield from self._server.Application.GetProperties(
|
|
||||||
['volume', 'muted']
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self._properties = None
|
self._properties = {}
|
||||||
self._item = None
|
self._item = {}
|
||||||
self._app_properties = None
|
self._app_properties = {}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def server(self):
|
||||||
|
"""Active server for json-rpc requests."""
|
||||||
|
if self._ws_connected:
|
||||||
|
return self._ws_server
|
||||||
|
else:
|
||||||
|
return self._http_server
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the device."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Return True if entity has to be polled for state."""
|
||||||
|
return not self._ws_connected
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volume_level(self):
|
def volume_level(self):
|
||||||
"""Volume level of the media player (0..1)."""
|
"""Volume level of the media player (0..1)."""
|
||||||
if self._app_properties is not None:
|
if 'volume' in self._app_properties:
|
||||||
return self._app_properties['volume'] / 100.0
|
return self._app_properties['volume'] / 100.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_volume_muted(self):
|
def is_volume_muted(self):
|
||||||
"""Boolean if volume is currently muted."""
|
"""Boolean if volume is currently muted."""
|
||||||
if self._app_properties is not None:
|
return self._app_properties.get('muted')
|
||||||
return self._app_properties['muted']
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_content_id(self):
|
def media_content_id(self):
|
||||||
"""Content ID of current playing media."""
|
"""Content ID of current playing media."""
|
||||||
if self._item is not None:
|
|
||||||
return self._item.get('uniqueid', None)
|
return self._item.get('uniqueid', None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -198,8 +325,13 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def media_duration(self):
|
def media_duration(self):
|
||||||
"""Duration of current playing media in seconds."""
|
"""Duration of current playing media in seconds."""
|
||||||
if self._properties is not None and not self._properties['live']:
|
if self._properties.get('live'):
|
||||||
total_time = self._properties['totaltime']
|
return None
|
||||||
|
|
||||||
|
total_time = self._properties.get('totaltime')
|
||||||
|
|
||||||
|
if total_time is None:
|
||||||
|
return None
|
||||||
|
|
||||||
return (
|
return (
|
||||||
total_time['hours'] * 3600 +
|
total_time['hours'] * 3600 +
|
||||||
@ -209,23 +341,22 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def media_image_url(self):
|
def media_image_url(self):
|
||||||
"""Image url of current playing media."""
|
"""Image url of current playing media."""
|
||||||
if self._item is None:
|
thumbnail = self._item.get('thumbnail')
|
||||||
|
if thumbnail is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
url_components = urllib.parse.urlparse(self._item['thumbnail'])
|
url_components = urllib.parse.urlparse(thumbnail)
|
||||||
if url_components.scheme == 'image':
|
if url_components.scheme == 'image':
|
||||||
return '{}/{}'.format(
|
return '{}/{}'.format(
|
||||||
self._image_url,
|
self._image_url,
|
||||||
urllib.parse.quote_plus(self._item['thumbnail']))
|
urllib.parse.quote_plus(thumbnail))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_title(self):
|
def media_title(self):
|
||||||
"""Title of current playing media."""
|
"""Title of current playing media."""
|
||||||
# find a string we can use as a title
|
# find a string we can use as a title
|
||||||
if self._item is not None:
|
|
||||||
return self._item.get(
|
return self._item.get(
|
||||||
'title',
|
'title', self._item.get('label', self._item.get('file')))
|
||||||
self._item.get('label', self._item.get('file', 'unknown')))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
@ -241,15 +372,15 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
def async_turn_off(self):
|
def async_turn_off(self):
|
||||||
"""Execute turn_off_action to turn off media player."""
|
"""Execute turn_off_action to turn off media player."""
|
||||||
if self._turn_off_action == 'quit':
|
if self._turn_off_action == 'quit':
|
||||||
yield from self._server.Application.Quit()
|
yield from self.server.Application.Quit()
|
||||||
elif self._turn_off_action == 'hibernate':
|
elif self._turn_off_action == 'hibernate':
|
||||||
yield from self._server.System.Hibernate()
|
yield from self.server.System.Hibernate()
|
||||||
elif self._turn_off_action == 'suspend':
|
elif self._turn_off_action == 'suspend':
|
||||||
yield from self._server.System.Suspend()
|
yield from self.server.System.Suspend()
|
||||||
elif self._turn_off_action == 'reboot':
|
elif self._turn_off_action == 'reboot':
|
||||||
yield from self._server.System.Reboot()
|
yield from self.server.System.Reboot()
|
||||||
elif self._turn_off_action == 'shutdown':
|
elif self._turn_off_action == 'shutdown':
|
||||||
yield from self._server.System.Shutdown()
|
yield from self.server.System.Shutdown()
|
||||||
else:
|
else:
|
||||||
_LOGGER.warning('turn_off requested but turn_off_action is none')
|
_LOGGER.warning('turn_off requested but turn_off_action is none')
|
||||||
|
|
||||||
@ -257,27 +388,27 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
def async_volume_up(self):
|
def async_volume_up(self):
|
||||||
"""Volume up the media player."""
|
"""Volume up the media player."""
|
||||||
assert (
|
assert (
|
||||||
yield from self._server.Input.ExecuteAction('volumeup')) == 'OK'
|
yield from self.server.Input.ExecuteAction('volumeup')) == 'OK'
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_volume_down(self):
|
def async_volume_down(self):
|
||||||
"""Volume down the media player."""
|
"""Volume down the media player."""
|
||||||
assert (
|
assert (
|
||||||
yield from self._server.Input.ExecuteAction('volumedown')) == 'OK'
|
yield from self.server.Input.ExecuteAction('volumedown')) == 'OK'
|
||||||
|
|
||||||
def async_set_volume_level(self, volume):
|
def async_set_volume_level(self, volume):
|
||||||
"""Set volume level, range 0..1.
|
"""Set volume level, range 0..1.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
This method must be run in the event loop and returns a coroutine.
|
||||||
"""
|
"""
|
||||||
return self._server.Application.SetVolume(int(volume * 100))
|
return self.server.Application.SetVolume(int(volume * 100))
|
||||||
|
|
||||||
def async_mute_volume(self, mute):
|
def async_mute_volume(self, mute):
|
||||||
"""Mute (true) or unmute (false) media player.
|
"""Mute (true) or unmute (false) media player.
|
||||||
|
|
||||||
This method must be run in the event loop and returns a coroutine.
|
This method must be run in the event loop and returns a coroutine.
|
||||||
"""
|
"""
|
||||||
return self._server.Application.SetMute(mute)
|
return self.server.Application.SetMute(mute)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_set_play_state(self, state):
|
def async_set_play_state(self, state):
|
||||||
@ -285,7 +416,7 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
players = yield from self._get_players()
|
players = yield from self._get_players()
|
||||||
|
|
||||||
if len(players) != 0:
|
if len(players) != 0:
|
||||||
yield from self._server.Player.PlayPause(
|
yield from self.server.Player.PlayPause(
|
||||||
players[0]['playerid'], state)
|
players[0]['playerid'], state)
|
||||||
|
|
||||||
def async_media_play_pause(self):
|
def async_media_play_pause(self):
|
||||||
@ -315,7 +446,7 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
players = yield from self._get_players()
|
players = yield from self._get_players()
|
||||||
|
|
||||||
if len(players) != 0:
|
if len(players) != 0:
|
||||||
yield from self._server.Player.Stop(players[0]['playerid'])
|
yield from self.server.Player.Stop(players[0]['playerid'])
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _goto(self, direction):
|
def _goto(self, direction):
|
||||||
@ -326,9 +457,9 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
if direction == 'previous':
|
if direction == 'previous':
|
||||||
# first seek to position 0. Kodi goes to the beginning of the
|
# first seek to position 0. Kodi goes to the beginning of the
|
||||||
# current track if the current track is not at the beginning.
|
# current track if the current track is not at the beginning.
|
||||||
yield from self._server.Player.Seek(players[0]['playerid'], 0)
|
yield from self.server.Player.Seek(players[0]['playerid'], 0)
|
||||||
|
|
||||||
yield from self._server.Player.GoTo(
|
yield from self.server.Player.GoTo(
|
||||||
players[0]['playerid'], direction)
|
players[0]['playerid'], direction)
|
||||||
|
|
||||||
def async_media_next_track(self):
|
def async_media_next_track(self):
|
||||||
@ -364,7 +495,7 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
time['hours'] = int(position)
|
time['hours'] = int(position)
|
||||||
|
|
||||||
if len(players) != 0:
|
if len(players) != 0:
|
||||||
yield from self._server.Player.Seek(players[0]['playerid'], time)
|
yield from self.server.Player.Seek(players[0]['playerid'], time)
|
||||||
|
|
||||||
def async_play_media(self, media_type, media_id, **kwargs):
|
def async_play_media(self, media_type, media_id, **kwargs):
|
||||||
"""Send the play_media command to the media player.
|
"""Send the play_media command to the media player.
|
||||||
@ -372,8 +503,8 @@ class KodiDevice(MediaPlayerDevice):
|
|||||||
This method must be run in the event loop and returns a coroutine.
|
This method must be run in the event loop and returns a coroutine.
|
||||||
"""
|
"""
|
||||||
if media_type == "CHANNEL":
|
if media_type == "CHANNEL":
|
||||||
return self._server.Player.Open(
|
return self.server.Player.Open(
|
||||||
{"item": {"channelid": int(media_id)}})
|
{"item": {"channelid": int(media_id)}})
|
||||||
else:
|
else:
|
||||||
return self._server.Player.Open(
|
return self.server.Player.Open(
|
||||||
{"item": {"file": str(media_id)}})
|
{"item": {"file": str(media_id)}})
|
||||||
|
@ -298,11 +298,14 @@ insteon_hub==0.4.5
|
|||||||
insteonlocal==0.39
|
insteonlocal==0.39
|
||||||
|
|
||||||
# homeassistant.components.media_player.kodi
|
# homeassistant.components.media_player.kodi
|
||||||
jsonrpc-async==0.2
|
jsonrpc-async==0.4
|
||||||
|
|
||||||
# homeassistant.components.notify.kodi
|
# homeassistant.components.notify.kodi
|
||||||
jsonrpc-requests==0.3
|
jsonrpc-requests==0.3
|
||||||
|
|
||||||
|
# homeassistant.components.media_player.kodi
|
||||||
|
jsonrpc-websocket==0.2
|
||||||
|
|
||||||
# homeassistant.scripts.keyring
|
# homeassistant.scripts.keyring
|
||||||
keyring>=9.3,<10.0
|
keyring>=9.3,<10.0
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user