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
|
|
|
"""
|
2017-01-05 20:01:13 +00:00
|
|
|
import asyncio
|
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
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
import aiohttp
|
2016-09-05 15:46:57 +00:00
|
|
|
import voluptuous as vol
|
|
|
|
|
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,
|
2016-05-07 00:57:00 +00:00
|
|
|
SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_STOP,
|
2017-01-20 19:52:55 +00:00
|
|
|
SUPPORT_TURN_OFF, SUPPORT_PLAY, SUPPORT_VOLUME_STEP, MediaPlayerDevice,
|
|
|
|
PLATFORM_SCHEMA)
|
2015-06-17 15:12:15 +00:00
|
|
|
from homeassistant.const import (
|
2016-09-05 15:46:57 +00:00
|
|
|
STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING, CONF_HOST, CONF_NAME,
|
2017-01-24 17:20:18 +00:00
|
|
|
CONF_PORT, CONF_SSL, CONF_USERNAME, CONF_PASSWORD)
|
2017-01-05 20:01:13 +00:00
|
|
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
2016-09-05 15:46:57 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2015-06-17 15:12:15 +00:00
|
|
|
|
2017-01-13 23:16:38 +00:00
|
|
|
REQUIREMENTS = ['jsonrpc-async==0.2']
|
2015-06-17 15:12:15 +00:00
|
|
|
|
2016-09-05 15:46:57 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
CONF_TURN_OFF_ACTION = 'turn_off_action'
|
|
|
|
|
|
|
|
DEFAULT_NAME = 'Kodi'
|
|
|
|
DEFAULT_PORT = 8080
|
2017-01-05 20:01:13 +00:00
|
|
|
DEFAULT_TIMEOUT = 5
|
2017-01-24 17:20:18 +00:00
|
|
|
DEFAULT_SSL = False
|
2016-09-05 15:46:57 +00:00
|
|
|
|
|
|
|
TURN_OFF_ACTION = [None, 'quit', 'hibernate', 'suspend', 'reboot', 'shutdown']
|
|
|
|
|
2015-06-17 15:12:15 +00:00
|
|
|
SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
|
2016-04-07 15:41:41 +00:00
|
|
|
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
|
2017-01-20 19:52:55 +00:00
|
|
|
SUPPORT_PLAY_MEDIA | SUPPORT_STOP | SUPPORT_PLAY | SUPPORT_VOLUME_STEP
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2016-09-05 15:46:57 +00:00
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
|
|
vol.Required(CONF_HOST): cv.string,
|
|
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
2017-01-24 17:20:18 +00:00
|
|
|
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
|
2016-09-05 15:46:57 +00:00
|
|
|
vol.Optional(CONF_TURN_OFF_ACTION, default=None): vol.In(TURN_OFF_ACTION),
|
2016-12-06 15:43:11 +00:00
|
|
|
vol.Inclusive(CONF_USERNAME, 'auth'): cv.string,
|
|
|
|
vol.Inclusive(CONF_PASSWORD, 'auth'): cv.string,
|
2016-09-05 15:46:57 +00:00
|
|
|
})
|
|
|
|
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_setup_platform(hass, config, async_add_entities,
|
|
|
|
discovery_info=None):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Setup the Kodi platform."""
|
2017-01-03 20:41:42 +00:00
|
|
|
host = config.get(CONF_HOST)
|
|
|
|
port = config.get(CONF_PORT)
|
2017-01-24 17:20:18 +00:00
|
|
|
use_encryption = config.get(CONF_SSL)
|
2016-02-11 21:07:47 +00:00
|
|
|
|
2017-01-03 20:41:42 +00:00
|
|
|
if host.startswith('http://') or host.startswith('https://'):
|
|
|
|
host = host.lstrip('http://').lstrip('https://')
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Kodi host name should no longer conatin http:// See updated "
|
|
|
|
"definitions here: "
|
|
|
|
"https://home-assistant.io/components/media_player.kodi/")
|
2016-02-11 21:07:47 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
entity = KodiDevice(
|
|
|
|
hass,
|
|
|
|
name=config.get(CONF_NAME),
|
2017-01-24 17:20:18 +00:00
|
|
|
host=host, port=port, encryption=use_encryption,
|
2017-01-05 20:01:13 +00:00
|
|
|
username=config.get(CONF_USERNAME),
|
|
|
|
password=config.get(CONF_PASSWORD),
|
|
|
|
turn_off_action=config.get(CONF_TURN_OFF_ACTION))
|
|
|
|
|
|
|
|
yield from async_add_entities([entity], update_before_add=True)
|
2015-06-17 11:44:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
2017-01-24 17:20:18 +00:00
|
|
|
def __init__(self, hass, name, host, port, encryption=False, username=None,
|
|
|
|
password=None, turn_off_action=None):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Initialize the Kodi device."""
|
2017-01-05 20:01:13 +00:00
|
|
|
import jsonrpc_async
|
|
|
|
self.hass = hass
|
2015-06-17 11:44:39 +00:00
|
|
|
self._name = name
|
2016-12-06 15:43:11 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
kwargs = {
|
|
|
|
'timeout': DEFAULT_TIMEOUT,
|
|
|
|
'session': async_get_clientsession(hass),
|
|
|
|
}
|
2016-12-06 15:43:11 +00:00
|
|
|
|
2017-01-03 20:41:42 +00:00
|
|
|
if username is not None:
|
2017-01-05 20:01:13 +00:00
|
|
|
kwargs['auth'] = aiohttp.BasicAuth(username, password)
|
2017-01-03 20:41:42 +00:00
|
|
|
image_auth_string = "{}:{}@".format(username, password)
|
|
|
|
else:
|
|
|
|
image_auth_string = ""
|
|
|
|
|
2017-01-24 17:20:18 +00:00
|
|
|
protocol = 'https' if encryption else 'http'
|
|
|
|
|
|
|
|
self._http_url = '{}://{}:{}/jsonrpc'.format(protocol, host, port)
|
|
|
|
self._image_url = '{}://{}{}:{}/image'.format(
|
|
|
|
protocol, image_auth_string, host, port)
|
2016-12-06 15:43:11 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
self._server = jsonrpc_async.Server(self._http_url, **kwargs)
|
2016-12-06 15:43:11 +00:00
|
|
|
|
2016-06-08 02:18:25 +00:00
|
|
|
self._turn_off_action = turn_off_action
|
2016-03-18 16:29:09 +00:00
|
|
|
self._players = list()
|
2015-06-17 15:12:15 +00:00
|
|
|
self._properties = None
|
|
|
|
self._item = None
|
|
|
|
self._app_properties = None
|
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
|
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
2015-06-17 11:44:39 +00:00
|
|
|
def _get_players(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Return the active player objects or None."""
|
2017-01-05 20:01:13 +00:00
|
|
|
import jsonrpc_async
|
2015-06-17 15:12:15 +00:00
|
|
|
try:
|
2017-01-05 20:01:13 +00:00
|
|
|
return (yield from self._server.Player.GetActivePlayers())
|
|
|
|
except jsonrpc_async.jsonrpc.TransportError:
|
2016-03-18 16:29:09 +00:00
|
|
|
if self._players is not None:
|
2017-01-03 20:41:42 +00:00
|
|
|
_LOGGER.info('Unable to fetch kodi data')
|
2016-03-18 16:29:09 +00:00
|
|
|
_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
|
|
|
|
2016-11-15 04:11:22 +00:00
|
|
|
if self._properties['speed'] == 0 and not self._properties['live']:
|
2015-06-17 15:12:15 +00:00
|
|
|
return STATE_PAUSED
|
|
|
|
else:
|
|
|
|
return STATE_PLAYING
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_update(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Retrieve latest state."""
|
2017-01-05 20:01:13 +00:00
|
|
|
self._players = yield from 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
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
self._properties = yield from self._server.Player.GetProperties(
|
2015-06-17 15:12:15 +00:00
|
|
|
player_id,
|
2016-11-15 04:11:22 +00:00
|
|
|
['time', 'totaltime', 'speed', 'live']
|
2015-06-17 15:12:15 +00:00
|
|
|
)
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
self._item = (yield from self._server.Player.GetItem(
|
2015-06-17 15:12:15 +00:00
|
|
|
player_id,
|
|
|
|
['title', 'file', 'uniqueid', 'thumbnail', 'artist']
|
2017-01-05 20:01:13 +00:00
|
|
|
))['item']
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
self._app_properties = \
|
|
|
|
yield from 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."""
|
2016-11-15 04:11:22 +00:00
|
|
|
if self._properties is not None and not self._properties['live']:
|
2015-06-17 15:12:15 +00:00
|
|
|
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."""
|
2017-01-03 20:41:42 +00:00
|
|
|
if self._item is None:
|
|
|
|
return None
|
2016-02-10 19:48:41 +00:00
|
|
|
|
|
|
|
url_components = urllib.parse.urlparse(self._item['thumbnail'])
|
|
|
|
if url_components.scheme == 'image':
|
2017-01-03 20:41:42 +00:00
|
|
|
return '{}/{}'.format(
|
|
|
|
self._image_url,
|
2016-02-11 21:07:47 +00:00
|
|
|
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',
|
2016-09-05 15:46:57 +00:00
|
|
|
self._item.get('label', self._item.get('file', 'unknown')))
|
2015-06-17 15:12:15 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_media_commands(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Flag of media commands that are supported."""
|
2016-06-08 02:18:25 +00:00
|
|
|
supported_media_commands = SUPPORT_KODI
|
|
|
|
|
2016-09-05 15:46:57 +00:00
|
|
|
if self._turn_off_action in TURN_OFF_ACTION:
|
2016-06-08 02:18:25 +00:00
|
|
|
supported_media_commands |= SUPPORT_TURN_OFF
|
|
|
|
|
|
|
|
return supported_media_commands
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_turn_off(self):
|
2016-06-08 02:18:25 +00:00
|
|
|
"""Execute turn_off_action to turn off media player."""
|
|
|
|
if self._turn_off_action == 'quit':
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.Application.Quit()
|
2016-06-08 02:18:25 +00:00
|
|
|
elif self._turn_off_action == 'hibernate':
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.System.Hibernate()
|
2016-06-08 02:18:25 +00:00
|
|
|
elif self._turn_off_action == 'suspend':
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.System.Suspend()
|
2016-06-08 02:18:25 +00:00
|
|
|
elif self._turn_off_action == 'reboot':
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.System.Reboot()
|
2016-06-08 02:18:25 +00:00
|
|
|
elif self._turn_off_action == 'shutdown':
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.System.Shutdown()
|
2016-06-08 02:18:25 +00:00
|
|
|
else:
|
|
|
|
_LOGGER.warning('turn_off requested but turn_off_action is none')
|
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_volume_up(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Volume up the media player."""
|
2017-01-05 20:01:13 +00:00
|
|
|
assert (
|
|
|
|
yield from self._server.Input.ExecuteAction('volumeup')) == 'OK'
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_volume_down(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Volume down the media player."""
|
2017-01-05 20:01:13 +00:00
|
|
|
assert (
|
|
|
|
yield from self._server.Input.ExecuteAction('volumedown')) == 'OK'
|
|
|
|
|
|
|
|
def async_set_volume_level(self, volume):
|
|
|
|
"""Set volume level, range 0..1.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self._server.Application.SetVolume(int(volume * 100))
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
def async_mute_volume(self, mute):
|
|
|
|
"""Mute (true) or unmute (false) media player.
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self._server.Application.SetMute(mute)
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_set_play_state(self, state):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Helper method for play/pause/toggle."""
|
2017-01-05 20:01:13 +00:00
|
|
|
players = yield from self._get_players()
|
2015-06-17 11:44:39 +00:00
|
|
|
|
|
|
|
if len(players) != 0:
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.Player.PlayPause(
|
|
|
|
players[0]['playerid'], state)
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
def async_media_play_pause(self):
|
|
|
|
"""Pause media on media player.
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self.async_set_play_state('toggle')
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
def async_media_play(self):
|
|
|
|
"""Play media.
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self.async_set_play_state(True)
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
def async_media_pause(self):
|
|
|
|
"""Pause the media player.
|
|
|
|
|
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self.async_set_play_state(False)
|
|
|
|
|
|
|
|
@asyncio.coroutine
|
|
|
|
def async_media_stop(self):
|
2016-05-07 00:57:00 +00:00
|
|
|
"""Stop the media player."""
|
2017-01-05 20:01:13 +00:00
|
|
|
players = yield from self._get_players()
|
2016-05-07 00:57:00 +00:00
|
|
|
|
|
|
|
if len(players) != 0:
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.Player.Stop(players[0]['playerid'])
|
2016-05-07 00:57:00 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
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."""
|
2017-01-05 20:01:13 +00:00
|
|
|
players = yield from self._get_players()
|
2015-06-17 11:44:39 +00:00
|
|
|
|
|
|
|
if len(players) != 0:
|
2017-01-05 20:01:13 +00:00
|
|
|
if direction == 'previous':
|
|
|
|
# first seek to position 0. Kodi goes to the beginning of the
|
|
|
|
# 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.GoTo(
|
|
|
|
players[0]['playerid'], direction)
|
|
|
|
|
|
|
|
def async_media_next_track(self):
|
|
|
|
"""Send next track command.
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self._goto('next')
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
def async_media_previous_track(self):
|
|
|
|
"""Send next track command.
|
2015-06-17 11:44:39 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
|
|
|
return self._goto('previous')
|
2015-06-17 15:12:15 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
@asyncio.coroutine
|
|
|
|
def async_media_seek(self, position):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Send seek command."""
|
2017-01-05 20:01:13 +00:00
|
|
|
players = yield from self._get_players()
|
2015-06-17 15:12:15 +00:00
|
|
|
|
|
|
|
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:
|
2017-01-05 20:01:13 +00:00
|
|
|
yield from self._server.Player.Seek(players[0]['playerid'], time)
|
2015-06-17 15:12:15 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
def async_play_media(self, media_type, media_id, **kwargs):
|
|
|
|
"""Send the play_media command to the media player.
|
2016-04-07 15:41:41 +00:00
|
|
|
|
2017-01-05 20:01:13 +00:00
|
|
|
This method must be run in the event loop and returns a coroutine.
|
|
|
|
"""
|
2016-09-23 06:50:07 +00:00
|
|
|
if media_type == "CHANNEL":
|
2017-01-05 20:01:13 +00:00
|
|
|
return self._server.Player.Open(
|
|
|
|
{"item": {"channelid": int(media_id)}})
|
2016-09-23 06:50:07 +00:00
|
|
|
else:
|
2017-01-05 20:01:13 +00:00
|
|
|
return self._server.Player.Open(
|
|
|
|
{"item": {"file": str(media_id)}})
|