Switch Companion to use json_rpc.py
This commit is contained in:
parent
cceb110354
commit
843bedbee6
7 changed files with 119 additions and 309 deletions
|
@ -8,8 +8,8 @@ from urllib import urlencode
|
||||||
from xbmc import sleep, executebuiltin
|
from xbmc import sleep, executebuiltin
|
||||||
|
|
||||||
from utils import settings, thread_methods
|
from utils import settings, thread_methods
|
||||||
from plexbmchelper import listener, plexgdm, subscribers, functions, \
|
from plexbmchelper import listener, plexgdm, subscribers, httppersist, \
|
||||||
httppersist, plexsettings
|
plexsettings
|
||||||
from PlexFunctions import ParseContainerKey, GetPlexMetadata
|
from PlexFunctions import ParseContainerKey, GetPlexMetadata
|
||||||
from PlexAPI import API
|
from PlexAPI import API
|
||||||
from playlist_func import get_pms_playqueue, get_plextype_from_xml
|
from playlist_func import get_pms_playqueue, get_plextype_from_xml
|
||||||
|
@ -196,9 +196,8 @@ class PlexCompanion(Thread):
|
||||||
|
|
||||||
# Start up instances
|
# Start up instances
|
||||||
requestMgr = httppersist.RequestMgr()
|
requestMgr = httppersist.RequestMgr()
|
||||||
jsonClass = functions.jsonClass(requestMgr, self.settings)
|
|
||||||
subscriptionManager = subscribers.SubscriptionManager(
|
subscriptionManager = subscribers.SubscriptionManager(
|
||||||
jsonClass, requestMgr, self.player, self.mgr)
|
requestMgr, self.player, self.mgr)
|
||||||
|
|
||||||
queue = Queue.Queue(maxsize=100)
|
queue = Queue.Queue(maxsize=100)
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
@ -211,7 +210,6 @@ class PlexCompanion(Thread):
|
||||||
httpd = listener.ThreadedHTTPServer(
|
httpd = listener.ThreadedHTTPServer(
|
||||||
client,
|
client,
|
||||||
subscriptionManager,
|
subscriptionManager,
|
||||||
jsonClass,
|
|
||||||
self.settings,
|
self.settings,
|
||||||
queue,
|
queue,
|
||||||
('', self.settings['myport']),
|
('', self.settings['myport']),
|
||||||
|
|
|
@ -3,7 +3,7 @@ Collection of functions using the Kodi JSON RPC interface.
|
||||||
See http://kodi.wiki/view/JSON-RPC_API
|
See http://kodi.wiki/view/JSON-RPC_API
|
||||||
"""
|
"""
|
||||||
from json import loads, dumps
|
from json import loads, dumps
|
||||||
from utils import milliseconds_to_kodi_time
|
from utils import millis_to_kodi_time
|
||||||
from xbmc import executeJSONRPC
|
from xbmc import executeJSONRPC
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ def get_players():
|
||||||
'picture': ...
|
'picture': ...
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
info = jsonrpc("Player.GetActivePlayers").execute()['result'] or []
|
info = jsonrpc("Player.GetActivePlayers").execute()['result']
|
||||||
ret = {}
|
ret = {}
|
||||||
for player in info:
|
for player in info:
|
||||||
player['playerid'] = int(player['playerid'])
|
player['playerid'] = int(player['playerid'])
|
||||||
|
@ -152,7 +152,7 @@ def seek_to(offset):
|
||||||
for playerid in get_player_ids():
|
for playerid in get_player_ids():
|
||||||
jsonrpc("Player.Seek").execute(
|
jsonrpc("Player.Seek").execute(
|
||||||
{"playerid": playerid,
|
{"playerid": playerid,
|
||||||
"value": milliseconds_to_kodi_time(offset)})
|
"value": millis_to_kodi_time(offset)})
|
||||||
|
|
||||||
|
|
||||||
def smallforward():
|
def smallforward():
|
||||||
|
@ -240,6 +240,13 @@ def input_back():
|
||||||
return jsonrpc("Input.Back").execute()
|
return jsonrpc("Input.Back").execute()
|
||||||
|
|
||||||
|
|
||||||
|
def input_sendtext(text):
|
||||||
|
"""
|
||||||
|
Tells Kodi the user sent text [unicode]
|
||||||
|
"""
|
||||||
|
return jsonrpc("Input.SendText").execute({'test': text, 'done': False})
|
||||||
|
|
||||||
|
|
||||||
def playlist_get_items(playlistid, properties):
|
def playlist_get_items(playlistid, properties):
|
||||||
"""
|
"""
|
||||||
playlistid: [int] id of the Kodi playlist
|
playlistid: [int] id of the Kodi playlist
|
||||||
|
@ -350,6 +357,33 @@ def get_episodes(params):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def get_player_props(playerid):
|
||||||
|
"""
|
||||||
|
Returns a dict for the active Kodi player with the following values:
|
||||||
|
{
|
||||||
|
'type' [str] the Kodi player type, e.g. 'video'
|
||||||
|
'time' The current item's time in Kodi time
|
||||||
|
'totaltime' The current item's total length in Kodi time
|
||||||
|
'speed' [int] playback speed, defaults to 0
|
||||||
|
'shuffled' [bool] True if shuffled
|
||||||
|
'repeat' [str] 'off', 'one', 'all'
|
||||||
|
'position' [int] position in playlist (or -1)
|
||||||
|
'playlistid' [int] the Kodi playlist id (or -1)
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
ret = jsonrpc('Player.GetProperties').execute({
|
||||||
|
'playerid': playerid,
|
||||||
|
'properties': ['type',
|
||||||
|
'time',
|
||||||
|
'totaltime',
|
||||||
|
'speed',
|
||||||
|
'shuffled',
|
||||||
|
'repeat',
|
||||||
|
'position',
|
||||||
|
'playlistid']})
|
||||||
|
return ret['result']
|
||||||
|
|
||||||
|
|
||||||
def current_audiostream(playerid):
|
def current_audiostream(playerid):
|
||||||
"""
|
"""
|
||||||
Returns a dict of the active audiostream for playerid [int]:
|
Returns a dict of the active audiostream for playerid [int]:
|
||||||
|
@ -402,3 +436,10 @@ def subtitle_enabled(playerid):
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
ret = False
|
ret = False
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def ping():
|
||||||
|
"""
|
||||||
|
Pings the JSON RPC interface
|
||||||
|
"""
|
||||||
|
return jsonrpc('JSONRPC.Ping').execute()
|
||||||
|
|
|
@ -1,244 +0,0 @@
|
||||||
import logging
|
|
||||||
import base64
|
|
||||||
import json
|
|
||||||
import string
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
|
|
||||||
import plexdb_functions as plexdb
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
log = logging.getLogger("PLEX."+__name__)
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
def xbmc_photo():
|
|
||||||
return "photo"
|
|
||||||
|
|
||||||
|
|
||||||
def xbmc_video():
|
|
||||||
return "video"
|
|
||||||
|
|
||||||
|
|
||||||
def xbmc_audio():
|
|
||||||
return "audio"
|
|
||||||
|
|
||||||
|
|
||||||
def plex_photo():
|
|
||||||
return "photo"
|
|
||||||
|
|
||||||
|
|
||||||
def plex_video():
|
|
||||||
return "video"
|
|
||||||
|
|
||||||
|
|
||||||
def plex_audio():
|
|
||||||
return "music"
|
|
||||||
|
|
||||||
|
|
||||||
def xbmc_type(plex_type):
|
|
||||||
if plex_type == plex_photo():
|
|
||||||
return xbmc_photo()
|
|
||||||
elif plex_type == plex_video():
|
|
||||||
return xbmc_video()
|
|
||||||
elif plex_type == plex_audio():
|
|
||||||
return xbmc_audio()
|
|
||||||
|
|
||||||
|
|
||||||
def plex_type(xbmc_type):
|
|
||||||
if xbmc_type == xbmc_photo():
|
|
||||||
return plex_photo()
|
|
||||||
elif xbmc_type == xbmc_video():
|
|
||||||
return plex_video()
|
|
||||||
elif xbmc_type == xbmc_audio():
|
|
||||||
return plex_audio()
|
|
||||||
|
|
||||||
|
|
||||||
def getXMLHeader():
|
|
||||||
return '<?xml version="1.0" encoding="UTF-8"?>\n'
|
|
||||||
|
|
||||||
|
|
||||||
def getOKMsg():
|
|
||||||
return getXMLHeader() + '<Response code="200" status="OK" />'
|
|
||||||
|
|
||||||
|
|
||||||
def timeToMillis(time):
|
|
||||||
return (time['hours']*3600 +
|
|
||||||
time['minutes']*60 +
|
|
||||||
time['seconds'])*1000 + time['milliseconds']
|
|
||||||
|
|
||||||
|
|
||||||
def millisToTime(t):
|
|
||||||
millis = int(t)
|
|
||||||
seconds = millis / 1000
|
|
||||||
minutes = seconds / 60
|
|
||||||
hours = minutes / 60
|
|
||||||
seconds = seconds % 60
|
|
||||||
minutes = minutes % 60
|
|
||||||
millis = millis % 1000
|
|
||||||
return {'hours': hours,
|
|
||||||
'minutes': minutes,
|
|
||||||
'seconds': seconds,
|
|
||||||
'milliseconds': millis}
|
|
||||||
|
|
||||||
|
|
||||||
def textFromXml(element):
|
|
||||||
return element.firstChild.data
|
|
||||||
|
|
||||||
|
|
||||||
class jsonClass():
|
|
||||||
|
|
||||||
def __init__(self, requestMgr, settings):
|
|
||||||
self.settings = settings
|
|
||||||
self.requestMgr = requestMgr
|
|
||||||
|
|
||||||
def jsonrpc(self, action, arguments={}):
|
|
||||||
""" put some JSON together for the JSON-RPC APIv6 """
|
|
||||||
if action.lower() == "sendkey":
|
|
||||||
request = json.dumps({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"method": "Input.SendText",
|
|
||||||
"params": {
|
|
||||||
"text": arguments[0],
|
|
||||||
"done": False
|
|
||||||
}
|
|
||||||
})
|
|
||||||
elif action.lower() == "ping":
|
|
||||||
request = json.dumps({
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": 1,
|
|
||||||
"method": "JSONRPC.Ping"
|
|
||||||
})
|
|
||||||
elif arguments:
|
|
||||||
request = json.dumps({
|
|
||||||
"id": 1,
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"method": action,
|
|
||||||
"params": arguments})
|
|
||||||
else:
|
|
||||||
request = json.dumps({
|
|
||||||
"id": 1,
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"method": action
|
|
||||||
})
|
|
||||||
|
|
||||||
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
|
|
||||||
|
|
||||||
if not result and self.settings['webserver_enabled']:
|
|
||||||
# xbmc.executeJSONRPC appears to fail on the login screen, but
|
|
||||||
# going through the network stack works, so let's try the request
|
|
||||||
# again
|
|
||||||
result = self.parseJSONRPC(self.requestMgr.post(
|
|
||||||
"127.0.0.1",
|
|
||||||
self.settings['port'],
|
|
||||||
"/jsonrpc",
|
|
||||||
request,
|
|
||||||
{'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Basic %s' % string.strip(
|
|
||||||
base64.encodestring('%s:%s'
|
|
||||||
% (self.settings['user'],
|
|
||||||
self.settings['passwd'])))
|
|
||||||
}))
|
|
||||||
return result
|
|
||||||
|
|
||||||
def skipTo(self, plexId, typus):
|
|
||||||
# playlistId = self.getPlaylistId(tryDecode(xbmc_type(typus)))
|
|
||||||
# playerId = self.
|
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
|
||||||
plexdb_item = plex_db.getItem_byId(plexId)
|
|
||||||
try:
|
|
||||||
dbid = plexdb_item[0]
|
|
||||||
mediatype = plexdb_item[4]
|
|
||||||
except TypeError:
|
|
||||||
log.info('Couldnt find item %s in Kodi db' % plexId)
|
|
||||||
return
|
|
||||||
log.debug('plexid: %s, kodi id: %s, type: %s'
|
|
||||||
% (plexId, dbid, mediatype))
|
|
||||||
|
|
||||||
def getPlexHeaders(self):
|
|
||||||
h = {
|
|
||||||
"Content-type": "text/xml",
|
|
||||||
"Access-Control-Allow-Origin": "*",
|
|
||||||
"X-Plex-Version": self.settings['version'],
|
|
||||||
"X-Plex-Client-Identifier": self.settings['uuid'],
|
|
||||||
"X-Plex-Provides": "client,controller,player",
|
|
||||||
"X-Plex-Product": "PlexKodiConnect",
|
|
||||||
"X-Plex-Device-Name": self.settings['client_name'],
|
|
||||||
"X-Plex-Platform": "Kodi",
|
|
||||||
"X-Plex-Model": self.settings['platform'],
|
|
||||||
"X-Plex-Device": "PC",
|
|
||||||
}
|
|
||||||
if self.settings['myplex_user']:
|
|
||||||
h["X-Plex-Username"] = self.settings['myplex_user']
|
|
||||||
return h
|
|
||||||
|
|
||||||
def parseJSONRPC(self, jsonraw):
|
|
||||||
if not jsonraw:
|
|
||||||
log.debug("Empty response from Kodi")
|
|
||||||
return {}
|
|
||||||
else:
|
|
||||||
parsed = json.loads(jsonraw)
|
|
||||||
if parsed.get('error', False):
|
|
||||||
log.error("Kodi returned an error: %s" % parsed.get('error'))
|
|
||||||
return parsed.get('result', {})
|
|
||||||
|
|
||||||
def getPlayers(self):
|
|
||||||
info = self.jsonrpc("Player.GetActivePlayers") or []
|
|
||||||
ret = {}
|
|
||||||
for player in info:
|
|
||||||
player['playerid'] = int(player['playerid'])
|
|
||||||
ret[player['type']] = player
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def getPlaylistId(self, typus):
|
|
||||||
"""
|
|
||||||
typus: one of the Kodi types, e.g. audio or video
|
|
||||||
|
|
||||||
Returns None if nothing was found
|
|
||||||
"""
|
|
||||||
for playlist in self.getPlaylists():
|
|
||||||
if playlist.get('type') == typus:
|
|
||||||
return playlist.get('playlistid')
|
|
||||||
|
|
||||||
def getPlaylists(self):
|
|
||||||
"""
|
|
||||||
Returns a list, e.g.
|
|
||||||
[
|
|
||||||
{u'playlistid': 0, u'type': u'audio'},
|
|
||||||
{u'playlistid': 1, u'type': u'video'},
|
|
||||||
{u'playlistid': 2, u'type': u'picture'}
|
|
||||||
]
|
|
||||||
"""
|
|
||||||
return self.jsonrpc('Playlist.GetPlaylists')
|
|
||||||
|
|
||||||
def getPlayerIds(self):
|
|
||||||
ret = []
|
|
||||||
for player in self.getPlayers().values():
|
|
||||||
ret.append(player['playerid'])
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def getVideoPlayerId(self, players=False):
|
|
||||||
if players is None:
|
|
||||||
players = self.getPlayers()
|
|
||||||
return players.get(xbmc_video(), {}).get('playerid', None)
|
|
||||||
|
|
||||||
def getAudioPlayerId(self, players=False):
|
|
||||||
if players is None:
|
|
||||||
players = self.getPlayers()
|
|
||||||
return players.get(xbmc_audio(), {}).get('playerid', None)
|
|
||||||
|
|
||||||
def getPhotoPlayerId(self, players=False):
|
|
||||||
if players is None:
|
|
||||||
players = self.getPlayers()
|
|
||||||
return players.get(xbmc_photo(), {}).get('playerid', None)
|
|
||||||
|
|
||||||
def getVolume(self):
|
|
||||||
answ = self.jsonrpc('Application.GetProperties',
|
|
||||||
{
|
|
||||||
"properties": ["volume", 'muted']
|
|
||||||
})
|
|
||||||
vol = str(answ.get('volume', 100))
|
|
||||||
mute = ("0", "1")[answ.get('muted', False)]
|
|
||||||
return (vol, mute)
|
|
|
@ -8,9 +8,9 @@ from urlparse import urlparse, parse_qs
|
||||||
from xbmc import sleep
|
from xbmc import sleep
|
||||||
from companion import process_command
|
from companion import process_command
|
||||||
from utils import window
|
from utils import window
|
||||||
|
import json_rpc as js
|
||||||
from functions import *
|
from clientinfo import getXArgsDeviceInfo
|
||||||
|
import variables as v
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -82,7 +82,6 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
def answer_request(self, sendData):
|
def answer_request(self, sendData):
|
||||||
self.serverlist = self.server.client.getServerList()
|
self.serverlist = self.server.client.getServerList()
|
||||||
subMgr = self.server.subscriptionManager
|
subMgr = self.server.subscriptionManager
|
||||||
js = self.server.jsonClass
|
|
||||||
settings = self.server.settings
|
settings = self.server.settings
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -105,7 +104,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
% settings['version'])
|
% settings['version'])
|
||||||
elif request_path == "verify":
|
elif request_path == "verify":
|
||||||
self.response("XBMC JSON connection test:\n" +
|
self.response("XBMC JSON connection test:\n" +
|
||||||
js.jsonrpc("ping"))
|
js.ping())
|
||||||
elif "resources" == request_path:
|
elif "resources" == request_path:
|
||||||
resp = ('%s'
|
resp = ('%s'
|
||||||
'<MediaContainer>'
|
'<MediaContainer>'
|
||||||
|
@ -121,15 +120,15 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
' deviceClass="pc"'
|
' deviceClass="pc"'
|
||||||
'/>'
|
'/>'
|
||||||
'</MediaContainer>'
|
'</MediaContainer>'
|
||||||
% (getXMLHeader(),
|
% (v.XML_HEADER,
|
||||||
settings['client_name'],
|
settings['client_name'],
|
||||||
settings['uuid'],
|
settings['uuid'],
|
||||||
settings['platform'],
|
settings['platform'],
|
||||||
settings['plexbmc_version']))
|
settings['plexbmc_version']))
|
||||||
log.debug("crafted resources response: %s" % resp)
|
log.debug("crafted resources response: %s" % resp)
|
||||||
self.response(resp, js.getPlexHeaders())
|
self.response(resp, getXArgsDeviceInfo())
|
||||||
elif "/subscribe" in request_path:
|
elif "/subscribe" in request_path:
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
self.response(v.COMPANION_OK_MESSAGE, getXArgsDeviceInfo())
|
||||||
protocol = params.get('protocol', False)
|
protocol = params.get('protocol', False)
|
||||||
host = self.client_address[0]
|
host = self.client_address[0]
|
||||||
port = params.get('port', False)
|
port = params.get('port', False)
|
||||||
|
@ -147,7 +146,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
self.response(
|
self.response(
|
||||||
sub(r"INSERTCOMMANDID",
|
sub(r"INSERTCOMMANDID",
|
||||||
str(commandID),
|
str(commandID),
|
||||||
subMgr.msg(js.getPlayers())),
|
subMgr.msg(js.get_players())),
|
||||||
{
|
{
|
||||||
'X-Plex-Client-Identifier': settings['uuid'],
|
'X-Plex-Client-Identifier': settings['uuid'],
|
||||||
'Access-Control-Expose-Headers':
|
'Access-Control-Expose-Headers':
|
||||||
|
@ -156,14 +155,14 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
'Content-Type': 'text/xml'
|
'Content-Type': 'text/xml'
|
||||||
})
|
})
|
||||||
elif "/unsubscribe" in request_path:
|
elif "/unsubscribe" in request_path:
|
||||||
self.response(getOKMsg(), js.getPlexHeaders())
|
self.response(v.COMPANION_OK_MESSAGE, getXArgsDeviceInfo())
|
||||||
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
|
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
|
||||||
or self.client_address[0]
|
or self.client_address[0]
|
||||||
subMgr.removeSubscriber(uuid)
|
subMgr.removeSubscriber(uuid)
|
||||||
else:
|
else:
|
||||||
# Throw it to companion.py
|
# Throw it to companion.py
|
||||||
process_command(request_path, params, self.server.queue)
|
process_command(request_path, params, self.server.queue)
|
||||||
self.response('', js.getPlexHeaders())
|
self.response('', getXArgsDeviceInfo())
|
||||||
subMgr.notify()
|
subMgr.notify()
|
||||||
except:
|
except:
|
||||||
log.error('Error encountered. Traceback:')
|
log.error('Error encountered. Traceback:')
|
||||||
|
@ -174,17 +173,16 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
daemon_threads = True
|
daemon_threads = True
|
||||||
|
|
||||||
def __init__(self, client, subscriptionManager, jsonClass, settings,
|
def __init__(self, client, subscriptionManager, settings,
|
||||||
queue, *args, **kwargs):
|
queue, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
|
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
|
||||||
date serverlist without instantiating anything
|
date serverlist without instantiating anything
|
||||||
|
|
||||||
same for SubscriptionManager and jsonClass
|
same for SubscriptionManager
|
||||||
"""
|
"""
|
||||||
self.client = client
|
self.client = client
|
||||||
self.subscriptionManager = subscriptionManager
|
self.subscriptionManager = subscriptionManager
|
||||||
self.jsonClass = jsonClass
|
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
HTTPServer.__init__(self, *args, **kwargs)
|
HTTPServer.__init__(self, *args, **kwargs)
|
||||||
|
|
|
@ -2,12 +2,15 @@ import logging
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
from xbmc import sleep
|
||||||
|
|
||||||
import downloadutils
|
import downloadutils
|
||||||
from clientinfo import getXArgsDeviceInfo
|
from clientinfo import getXArgsDeviceInfo
|
||||||
from utils import window
|
from utils import window, kodi_time_to_millis
|
||||||
import PlexFunctions as pf
|
import PlexFunctions as pf
|
||||||
import state
|
import state
|
||||||
from functions import *
|
import variables as v
|
||||||
|
import json_rpc as js
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -17,7 +20,7 @@ log = logging.getLogger("PLEX."+__name__)
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionManager:
|
class SubscriptionManager:
|
||||||
def __init__(self, jsonClass, RequestMgr, player, mgr):
|
def __init__(self, RequestMgr, player, mgr):
|
||||||
self.serverlist = []
|
self.serverlist = []
|
||||||
self.subscribers = {}
|
self.subscribers = {}
|
||||||
self.info = {}
|
self.info = {}
|
||||||
|
@ -30,8 +33,6 @@ class SubscriptionManager:
|
||||||
'audio': {},
|
'audio': {},
|
||||||
'picture': {}
|
'picture': {}
|
||||||
}
|
}
|
||||||
self.volume = 0
|
|
||||||
self.mute = '0'
|
|
||||||
self.server = ""
|
self.server = ""
|
||||||
self.protocol = "http"
|
self.protocol = "http"
|
||||||
self.port = ""
|
self.port = ""
|
||||||
|
@ -40,7 +41,6 @@ class SubscriptionManager:
|
||||||
self.xbmcplayer = player
|
self.xbmcplayer = player
|
||||||
self.playqueue = mgr.playqueue
|
self.playqueue = mgr.playqueue
|
||||||
|
|
||||||
self.js = jsonClass
|
|
||||||
self.RequestMgr = RequestMgr
|
self.RequestMgr = RequestMgr
|
||||||
|
|
||||||
def getServerByHost(self, host):
|
def getServerByHost(self, host):
|
||||||
|
@ -52,32 +52,34 @@ class SubscriptionManager:
|
||||||
return server
|
return server
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def getVolume(self):
|
|
||||||
self.volume, self.mute = self.js.getVolume()
|
|
||||||
|
|
||||||
def msg(self, players):
|
def msg(self, players):
|
||||||
msg = getXMLHeader()
|
log.debug('players: %s', players)
|
||||||
|
msg = v.XML_HEADER
|
||||||
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
|
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
|
||||||
msg += ' machineIdentifier="%s" location="fullScreenVideo">' % window('plex_client_Id')
|
msg += ' machineIdentifier="%s" location="fullScreenVideo">' % window('plex_client_Id')
|
||||||
msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
|
msg += self.getTimelineXML(players.get(v.KODI_TYPE_AUDIO),
|
||||||
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
|
v.PLEX_TYPE_AUDIO)
|
||||||
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
|
msg += self.getTimelineXML(players.get(v.KODI_TYPE_PHOTO),
|
||||||
|
v.PLEX_TYPE_PHOTO)
|
||||||
|
msg += self.getTimelineXML(players.get(v.KODI_TYPE_VIDEO),
|
||||||
|
v.PLEX_TYPE_VIDEO)
|
||||||
msg += "\n</MediaContainer>"
|
msg += "\n</MediaContainer>"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def getTimelineXML(self, playerid, ptype):
|
def getTimelineXML(self, player, ptype):
|
||||||
if playerid is not None:
|
if player is None:
|
||||||
|
status = 'stopped'
|
||||||
|
time = 0
|
||||||
|
else:
|
||||||
|
playerid = player['playerid']
|
||||||
info = self.getPlayerProperties(playerid)
|
info = self.getPlayerProperties(playerid)
|
||||||
# save this info off so the server update can use it too
|
# save this info off so the server update can use it too
|
||||||
self.playerprops[playerid] = info
|
self.playerprops[playerid] = info
|
||||||
status = info['state']
|
status = info['state']
|
||||||
time = info['time']
|
time = info['time']
|
||||||
else:
|
|
||||||
status = "stopped"
|
|
||||||
time = 0
|
|
||||||
ret = ('\n <Timeline state="%s" time="%s" type="%s"'
|
ret = ('\n <Timeline state="%s" time="%s" type="%s"'
|
||||||
% (status, time, ptype))
|
% (status, time, ptype))
|
||||||
if playerid is None:
|
if player is None:
|
||||||
ret += ' />'
|
ret += ' />'
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -89,10 +91,10 @@ class SubscriptionManager:
|
||||||
keyid = None
|
keyid = None
|
||||||
count = 0
|
count = 0
|
||||||
while not keyid:
|
while not keyid:
|
||||||
if count > 300:
|
if count > 30:
|
||||||
break
|
break
|
||||||
keyid = window('plex_currently_playing_itemid')
|
keyid = window('plex_currently_playing_itemid')
|
||||||
xbmc.sleep(100)
|
sleep(100)
|
||||||
count += 1
|
count += 1
|
||||||
if keyid:
|
if keyid:
|
||||||
self.lastkey = "/library/metadata/%s" % keyid
|
self.lastkey = "/library/metadata/%s" % keyid
|
||||||
|
@ -119,7 +121,7 @@ class SubscriptionManager:
|
||||||
ret += ' port="%s"' % serv.get('port', self.port)
|
ret += ' port="%s"' % serv.get('port', self.port)
|
||||||
ret += ' volume="%s"' % info['volume']
|
ret += ' volume="%s"' % info['volume']
|
||||||
ret += ' shuffle="%s"' % info['shuffle']
|
ret += ' shuffle="%s"' % info['shuffle']
|
||||||
ret += ' mute="%s"' % self.mute
|
ret += ' mute="%s"' % info['mute']
|
||||||
ret += ' repeat="%s"' % info['repeat']
|
ret += ' repeat="%s"' % info['repeat']
|
||||||
ret += ' itemType="%s"' % ptype
|
ret += ' itemType="%s"' % ptype
|
||||||
if state.PLEX_TRANSIENT_TOKEN:
|
if state.PLEX_TRANSIENT_TOKEN:
|
||||||
|
@ -145,7 +147,7 @@ class SubscriptionManager:
|
||||||
if (not window('plex_currently_playing_itemid')
|
if (not window('plex_currently_playing_itemid')
|
||||||
and not self.lastplayers):
|
and not self.lastplayers):
|
||||||
return True
|
return True
|
||||||
players = self.js.getPlayers()
|
players = js.get_players()
|
||||||
# fetch the message, subscribers or not, since the server
|
# fetch the message, subscribers or not, since the server
|
||||||
# will need the info anyway
|
# will need the info anyway
|
||||||
msg = self.msg(players)
|
msg = self.msg(players)
|
||||||
|
@ -233,27 +235,15 @@ class SubscriptionManager:
|
||||||
# Get the playqueue
|
# Get the playqueue
|
||||||
playqueue = self.playqueue.playqueues[playerid]
|
playqueue = self.playqueue.playqueues[playerid]
|
||||||
# get info from the player
|
# get info from the player
|
||||||
props = self.js.jsonrpc(
|
props = js.get_player_props(playerid)
|
||||||
"Player.GetProperties",
|
|
||||||
{"playerid": playerid,
|
|
||||||
"properties": ["type",
|
|
||||||
"time",
|
|
||||||
"totaltime",
|
|
||||||
"speed",
|
|
||||||
"shuffled",
|
|
||||||
"repeat"]})
|
|
||||||
info = {
|
info = {
|
||||||
'time': timeToMillis(props['time']),
|
'time': kodi_time_to_millis(props['time']),
|
||||||
'duration': timeToMillis(props['totaltime']),
|
'duration': kodi_time_to_millis(props['totaltime']),
|
||||||
'state': ("paused", "playing")[int(props['speed'])],
|
'state': ("paused", "playing")[int(props['speed'])],
|
||||||
'shuffle': ("0", "1")[props.get('shuffled', False)],
|
'shuffle': ("0", "1")[props.get('shuffled', False)],
|
||||||
'repeat': pf.getPlexRepeat(props.get('repeat')),
|
'repeat': pf.getPlexRepeat(props.get('repeat')),
|
||||||
}
|
}
|
||||||
# Get the playlist position
|
pos = props['position']
|
||||||
pos = self.js.jsonrpc(
|
|
||||||
"Player.GetProperties",
|
|
||||||
{"playerid": playerid,
|
|
||||||
"properties": ["position"]})['position']
|
|
||||||
try:
|
try:
|
||||||
info['playQueueItemID'] = playqueue.items[pos].ID or 'null'
|
info['playQueueItemID'] = playqueue.items[pos].ID or 'null'
|
||||||
info['guid'] = playqueue.items[pos].guid or 'null'
|
info['guid'] = playqueue.items[pos].guid or 'null'
|
||||||
|
@ -274,8 +264,8 @@ class SubscriptionManager:
|
||||||
}
|
}
|
||||||
|
|
||||||
# get the volume from the application
|
# get the volume from the application
|
||||||
info['volume'] = self.volume
|
info['volume'] = js.get_volume()
|
||||||
info['mute'] = self.mute
|
info['mute'] = js.get_muted()
|
||||||
|
|
||||||
info['plex_transient_token'] = playqueue.plex_transient_token
|
info['plex_transient_token'] = playqueue.plex_transient_token
|
||||||
|
|
||||||
|
|
|
@ -179,9 +179,15 @@ def dialog(typus, *args, **kwargs):
|
||||||
return types[typus](*args, **kwargs)
|
return types[typus](*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def milliseconds_to_kodi_time(milliseconds):
|
def millis_to_kodi_time(milliseconds):
|
||||||
"""
|
"""
|
||||||
Converts time in milliseconds to the time dict used by the Kodi JSON RPC
|
Converts time in milliseconds to the time dict used by the Kodi JSON RPC:
|
||||||
|
{
|
||||||
|
'hours': [int],
|
||||||
|
'minutes': [int],
|
||||||
|
'seconds'[int],
|
||||||
|
'milliseconds': [int]
|
||||||
|
}
|
||||||
Pass in the time in milliseconds as an int
|
Pass in the time in milliseconds as an int
|
||||||
"""
|
"""
|
||||||
seconds = milliseconds / 1000
|
seconds = milliseconds / 1000
|
||||||
|
@ -196,6 +202,22 @@ def milliseconds_to_kodi_time(milliseconds):
|
||||||
'milliseconds': milliseconds}
|
'milliseconds': milliseconds}
|
||||||
|
|
||||||
|
|
||||||
|
def kodi_time_to_millis(time):
|
||||||
|
"""
|
||||||
|
Converts the Kodi time dict
|
||||||
|
{
|
||||||
|
'hours': [int],
|
||||||
|
'minutes': [int],
|
||||||
|
'seconds'[int],
|
||||||
|
'milliseconds': [int]
|
||||||
|
}
|
||||||
|
to milliseconds [int]
|
||||||
|
"""
|
||||||
|
return (time['hours']*3600 +
|
||||||
|
time['minutes']*60 +
|
||||||
|
time['seconds'])*1000 + time['milliseconds']
|
||||||
|
|
||||||
|
|
||||||
def tryEncode(uniString, encoding='utf-8'):
|
def tryEncode(uniString, encoding='utf-8'):
|
||||||
"""
|
"""
|
||||||
Will try to encode uniString (in unicode) to encoding. This possibly
|
Will try to encode uniString (in unicode) to encoding. This possibly
|
||||||
|
|
|
@ -361,3 +361,8 @@ SORT_METHODS_ALBUMS = (
|
||||||
'SORT_METHOD_ARTIST',
|
'SORT_METHOD_ARTIST',
|
||||||
'SORT_METHOD_ALBUM',
|
'SORT_METHOD_ALBUM',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||||
|
|
||||||
|
COMPANION_OK_MESSAGE = XML_HEADER + '<Response code="200" status="OK" />'
|
||||||
|
|
Loading…
Reference in a new issue