Fix instance chaos with Plex Companion

- Should fix PKC startup issues, among others
This commit is contained in:
tomkat83 2016-04-02 16:46:23 +02:00
parent 50b25ccf73
commit 43dc83ae57
8 changed files with 413 additions and 322 deletions

View file

@ -7,22 +7,25 @@ import xbmc
import clientinfo import clientinfo
import utils import utils
from plexbmchelper import listener, plexgdm, subscribers from plexbmchelper import listener, plexgdm, subscribers, functions, \
from plexbmchelper.settings import settings httppersist
@utils.logging @utils.logging
@utils.ThreadMethodsAdditionalSuspend('emby_serverStatus')
@utils.ThreadMethods @utils.ThreadMethods
class PlexCompanion(threading.Thread): class PlexCompanion(threading.Thread):
def __init__(self): def __init__(self):
self.port = int(utils.settings('companionPort'))
ci = clientinfo.ClientInfo() ci = clientinfo.ClientInfo()
self.clientId = ci.getDeviceId() self.clientId = ci.getDeviceId()
self.deviceName = ci.getDeviceName() self.deviceName = ci.getDeviceName()
self.logMsg("----===## Starting PlexBMC Helper ##===----", 1)
self.port = int(utils.settings('companionPort'))
self.logMsg("----===## Starting PlexCompanion ##===----", 1)
# Start GDM for server/client discovery # Start GDM for server/client discovery
self.client = plexgdm.plexgdm(debug=settings['gdm_debug']) self.client = plexgdm.plexgdm(
debug=utils.settings('companionGDMDebugging'))
self.client.clientDetails(self.clientId, # UUID self.client.clientDetails(self.clientId, # UUID
self.deviceName, # clientName self.deviceName, # clientName
self.port, self.port,
@ -34,23 +37,38 @@ class PlexCompanion(threading.Thread):
threading.Thread.__init__(self) threading.Thread.__init__(self)
def run(self): def run(self):
# Cache for quicker while loops
log = self.logMsg
client = self.client
threadStopped = self.threadStopped
threadSuspended = self.threadSuspended
start_count = 0 start_count = 0
window = utils.window
# Start up instances
requestMgr = httppersist.RequestMgr()
jsonClass = functions.jsonClass(requestMgr)
subscriptionManager = subscribers.SubscriptionManager(
jsonClass, requestMgr)
# Start up httpd
while True: while True:
try: try:
httpd = listener.ThreadedHTTPServer( httpd = listener.ThreadedHTTPServer(
client,
subscriptionManager,
jsonClass,
('', self.port), ('', self.port),
listener.MyHandler) listener.MyHandler)
httpd.timeout = 0.95 httpd.timeout = 0.95
break break
except: except:
self.logMsg("Unable to start PlexCompanion. Traceback:", -1) log("Unable to start PlexCompanion. Traceback:", -1)
self.logMsg(traceback.print_exc(), -1) log(traceback.print_exc(), -1)
xbmc.sleep(3000) xbmc.sleep(3000)
if start_count == 3: if start_count == 3:
self.logMsg("Error: Unable to start web helper.", -1) log("Error: Unable to start web helper.", -1)
httpd = False httpd = False
break break
@ -59,15 +77,15 @@ class PlexCompanion(threading.Thread):
if not httpd: if not httpd:
return return
self.client.start_all() client.start_all()
message_count = 0 message_count = 0
is_running = False while not threadStopped():
while not self.threadStopped():
# If we are not authorized, sleep # If we are not authorized, sleep
# Otherwise, we trigger a download which leads to a # Otherwise, we trigger a download which leads to a
# re-authorizations # re-authorizations
while self.threadSuspended() or window('emby_serverStatus'): while threadSuspended():
if self.threadStopped(): if threadStopped():
break break
xbmc.sleep(1000) xbmc.sleep(1000)
try: try:
@ -76,31 +94,29 @@ class PlexCompanion(threading.Thread):
message_count += 1 message_count += 1
if message_count > 100: if message_count > 100:
if self.client.check_client_registration(): if client.check_client_registration():
self.logMsg("Client is still registered", 1) log("Client is still registered", 1)
else: else:
self.logMsg("Client is no longer registered", 1) log("Client is no longer registered", 1)
self.logMsg("Plex Companion still running on port %s" log("Plex Companion still running on port %s"
% self.port, 1) % self.port, 1)
message_count = 0 message_count = 0
if not is_running: # Get and set servers
self.logMsg("Plex Companion has started", 0) subscriptionManager.serverlist = client.getServerList()
is_running = True
subscribers.subMgr.notify() subscriptionManager.notify()
settings['serverList'] = self.client.getServerList()
xbmc.sleep(50) xbmc.sleep(50)
except: except:
self.logMsg("Error in loop, continuing anyway", 1) log("Error in loop, continuing anyway", 1)
self.logMsg(traceback.format_exc(), 1) log(traceback.format_exc(), 1)
xbmc.sleep(50) xbmc.sleep(50)
self.client.stop_all() client.stop_all()
try: try:
httpd.socket.shutdown(socket.SHUT_RDWR) httpd.socket.shutdown(socket.SHUT_RDWR)
except: except:
pass pass
finally: finally:
httpd.socket.close() httpd.socket.close()
self.logMsg("----===## Plex Companion stopped ##===----", 0) log("----===## Plex Companion stopped ##===----", 0)

View file

@ -1,26 +1,35 @@
import base64 import base64
import inspect
import json import json
import string import string
import traceback
import xbmc import xbmc
from settings import settings import settings
from httppersist import requests from utils import logMsg
def xbmc_photo(): def xbmc_photo():
return "photo" return "photo"
def xbmc_video(): def xbmc_video():
return "video" return "video"
def xbmc_audio(): def xbmc_audio():
return "audio" return "audio"
def plex_photo(): def plex_photo():
return "photo" return "photo"
def plex_video(): def plex_video():
return "video" return "video"
def plex_audio(): def plex_audio():
return "music" return "music"
def xbmc_type(plex_type): def xbmc_type(plex_type):
if plex_type == plex_photo(): if plex_type == plex_photo():
return xbmc_photo() return xbmc_photo()
@ -28,7 +37,8 @@ def xbmc_type(plex_type):
return xbmc_video() return xbmc_video()
elif plex_type == plex_audio(): elif plex_type == plex_audio():
return xbmc_audio() return xbmc_audio()
def plex_type(xbmc_type): def plex_type(xbmc_type):
if xbmc_type == xbmc_photo(): if xbmc_type == xbmc_photo():
return plex_photo() return plex_photo()
@ -37,6 +47,7 @@ def plex_type(xbmc_type):
elif xbmc_type == xbmc_audio(): elif xbmc_type == xbmc_audio():
return plex_audio() return plex_audio()
def getPlatform(): def getPlatform():
if xbmc.getCondVisibility('system.platform.osx'): if xbmc.getCondVisibility('system.platform.osx'):
return "MacOSX" return "MacOSX"
@ -50,140 +61,23 @@ def getPlatform():
return "RaspberryPi" return "RaspberryPi"
elif xbmc.getCondVisibility('system.platform.linux'): elif xbmc.getCondVisibility('system.platform.linux'):
return "Linux" return "Linux"
elif xbmc.getCondVisibility('system.platform.android'): elif xbmc.getCondVisibility('system.platform.android'):
return "Android" return "Android"
return "Unknown" return "Unknown"
def printDebug( msg, functionname=True ):
if settings['debug']:
if functionname is False:
print str(msg)
else:
print "PleXBMC Helper -> " + inspect.stack()[1][3] + ": " + str(msg)
""" communicate with XBMC """
def jsonrpc(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" : self.arguments[0], "done" : False }} )
elif action.lower() == "ping":
request=json.dumps({ "jsonrpc" : "2.0",
"id" : 1 ,
"method" : "JSONRPC.Ping" })
elif action.lower() == "playmedia":
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
"?mode=companion&arguments=%s"
% arguments)
return True
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})
printDebug("Sending request to XBMC without network stack: %s" % request)
result = parseJSONRPC(xbmc.executeJSONRPC(request))
if not result and 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 = parseJSONRPC(requests.post(
"127.0.0.1",
settings['port'],
"/jsonrpc",
request,
{ 'Content-Type' : 'application/json',
'Authorization' : 'Basic ' + string.strip(base64.encodestring(settings['user'] + ':' + settings['passwd'])) }))
return result
def parseJSONRPC(jsonraw):
if not jsonraw:
printDebug("Empty response from XBMC")
return {}
else:
printDebug("Response from XBMC: %s" % jsonraw)
parsed=json.loads(jsonraw)
if parsed.get('error', False):
print "XBMC returned an error: %s" % parsed.get('error')
return parsed.get('result', {})
def getXMLHeader(): def getXMLHeader():
return '<?xml version="1.0" encoding="utf-8" ?>'+"\r\n" return '<?xml version="1.0" encoding="utf-8" ?>'+"\r\n"
def getOKMsg(): def getOKMsg():
return getXMLHeader() + '<Response code="200" status="OK" />' return getXMLHeader() + '<Response code="200" status="OK" />'
def getPlexHeaders():
h = {
"Content-type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Origin": "*",
"X-Plex-Version": settings['version'],
"X-Plex-Client-Identifier": settings['uuid'],
"X-Plex-Provides": "player",
"X-Plex-Product": "PlexKodiConnect",
"X-Plex-Device-Name": settings['client_name'],
"X-Plex-Platform": "XBMC",
"X-Plex-Model": getPlatform(),
"X-Plex-Device": "PC",
}
if settings['myplex_user']:
h["X-Plex-Username"] = settings['myplex_user']
return h
def getServerByHost(host):
list = settings['serverList']
if len(list) == 1:
return list[0]
for server in list:
if server.get('serverName') in host or server.get('server') in host:
return server
return {}
def getPlayers():
info = jsonrpc("Player.GetActivePlayers") or []
ret = {}
for player in info:
player['playerid'] = int(player['playerid'])
ret[player['type']] = player
return ret
def getPlayerIds():
ret = []
for player in getPlayers().values():
ret.append(player['playerid'])
return ret
def getVideoPlayerId(players = False):
if players is None:
players = getPlayers()
return players.get(xbmc_video(), {}).get('playerid', None)
def getAudioPlayerId(players = False):
if players is None:
players = getPlayers()
return players.get(xbmc_audio(), {}).get('playerid', None)
def getPhotoPlayerId(players = False):
if players is None:
players = getPlayers()
return players.get(xbmc_photo(), {}).get('playerid', None)
def getVolume():
answ = jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] })
vol = str(answ.get('volume', 100))
mute = ("0", "1")[answ.get('muted', False)]
return (vol, mute)
def timeToMillis(time): def timeToMillis(time):
return (time['hours']*3600 + time['minutes']*60 + time['seconds'])*1000 + time['milliseconds'] return (time['hours']*3600 + time['minutes']*60 + time['seconds'])*1000 + time['milliseconds']
def millisToTime(t): def millisToTime(t):
millis = int(t) millis = int(t)
seconds = millis / 1000 seconds = millis / 1000
@ -194,5 +88,118 @@ def millisToTime(t):
millis = millis % 1000 millis = millis % 1000
return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis} return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis}
def textFromXml(element): def textFromXml(element):
return element.firstChild.data return element.firstChild.data
class jsonClass():
def __init__(self, requestMgr):
self.settings = settings.getSettings()
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 action.lower() == "playmedia":
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
"?mode=companion&arguments=%s"
% arguments)
return True
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})
logMsg("PlexCompanion",
"Sending request to XBMC without network stack: %s"
% request, 2)
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 ' + string.strip(base64.encodestring(self.settings['user'] + ':' + self.settings['passwd'])) }))
return result
def getPlexHeaders(self):
h = {
"Content-type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Origin": "*",
"X-Plex-Version": self.settings['version'],
"X-Plex-Client-Identifier": self.settings['uuid'],
"X-Plex-Provides": "player",
"X-Plex-Product": "PlexKodiConnect",
"X-Plex-Device-Name": self.settings['client_name'],
"X-Plex-Platform": "Kodi",
"X-Plex-Model": getPlatform(),
"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:
logMsg("PlexCompanion", "Empty response from XBMC", 1)
return {}
else:
logMsg("PlexCompanion", "Response from XBMC: %s" % jsonraw, 2)
parsed=json.loads(jsonraw)
if parsed.get('error', False):
logMsg("PlexCompanion", "XBMC returned an error: %s"
% parsed.get('error'), -1)
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 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)

View file

@ -1,7 +1,12 @@
import httplib import httplib
import traceback import traceback
import string import string
import errno
from socket import error as socket_error
from utils import logging
@logging
class RequestMgr: class RequestMgr:
def __init__(self): def __init__(self):
self.conns = {} self.conns = {}
@ -9,24 +14,24 @@ class RequestMgr:
def getConnection(self, protocol, host, port): def getConnection(self, protocol, host, port):
conn = self.conns.get(protocol+host+str(port), False) conn = self.conns.get(protocol+host+str(port), False)
if not conn: if not conn:
if protocol=="https": if protocol == "https":
conn = httplib.HTTPSConnection(host, port) conn = httplib.HTTPSConnection(host, port)
else: else:
conn = httplib.HTTPConnection(host, port) conn = httplib.HTTPConnection(host, port)
self.conns[protocol+host+str(port)] = conn self.conns[protocol+host+str(port)] = conn
return conn return conn
def closeConnection(self, protocol, host, port): def closeConnection(self, protocol, host, port):
conn = self.conns.get(protocol+host+str(port), False) conn = self.conns.get(protocol+host+str(port), False)
if conn: if conn:
conn.close() conn.close()
self.conns.pop(protocol+host+str(port), None) self.conns.pop(protocol+host+str(port), None)
def dumpConnections(self): def dumpConnections(self):
for conn in self.conns.values(): for conn in self.conns.values():
conn.close() conn.close()
self.conns = {} self.conns = {}
def post(self, host, port, path, body, header={}, protocol="http"): def post(self, host, port, path, body, header={}, protocol="http"):
conn = None conn = None
try: try:
@ -35,27 +40,33 @@ class RequestMgr:
conn.request("POST", path, body, header) conn.request("POST", path, body, header)
data = conn.getresponse() data = conn.getresponse()
if int(data.status) >= 400: if int(data.status) >= 400:
print "HTTP response error: " + str(data.status) self.logMsg("HTTP response error: %s" % str(data.status), -1)
# this should return false, but I'm hacking it since iOS returns 404 no matter what # this should return false, but I'm hacking it since iOS
# returns 404 no matter what
return data.read() or True return data.read() or True
else: else:
return data.read() or True return data.read() or True
except: except socket_error as serr:
print "Unable to connect to %s\nReason:" % host # Ignore remote close and connection refused (e.g. shutdown PKC)
traceback.print_exc() if serr.errno in (errno.WSAECONNABORTED, errno.WSAECONNREFUSED):
pass
else:
self.logMsg("Unable to connect to %s\nReason:" % host, -1)
self.logMsg(traceback.print_exc(), -1)
self.conns.pop(protocol+host+str(port), None) self.conns.pop(protocol+host+str(port), None)
if conn: if conn:
conn.close() conn.close()
return False return False
def getwithparams(self, host, port, path, params, header={}, protocol="http"): def getwithparams(self, host, port, path, params, header={},
protocol="http"):
newpath = path + '?' newpath = path + '?'
pairs = [] pairs = []
for key in params: for key in params:
pairs.append(str(key)+'='+str(params[key])) pairs.append(str(key)+'='+str(params[key]))
newpath += string.join(pairs, '&') newpath += string.join(pairs, '&')
return self.get(host, port, newpath, header, protocol) return self.get(host, port, newpath, header, protocol)
def get(self, host, port, path, header={}, protocol="http"): def get(self, host, port, path, header={}, protocol="http"):
try: try:
conn = self.getConnection(protocol, host, port) conn = self.getConnection(protocol, host, port)
@ -63,14 +74,13 @@ class RequestMgr:
conn.request("GET", path, headers=header) conn.request("GET", path, headers=header)
data = conn.getresponse() data = conn.getresponse()
if int(data.status) >= 400: if int(data.status) >= 400:
print "HTTP response error: " + str(data.status) self.logMsg("HTTP response error: %s" % str(data.status), -1)
return False return False
else: else:
return data.read() or True return data.read() or True
except: except:
print "Unable to connect to %s\nReason: %s" % (host, traceback.print_exc()) self.logMsg("Unable to connect to %s\nReason:" % host, -1)
self.logMsg(traceback.print_exc(), -1)
self.conns.pop(protocol+host+str(port), None) self.conns.pop(protocol+host+str(port), None)
conn.close() conn.close()
return False return False
requests = RequestMgr()

View file

@ -4,38 +4,56 @@ import xbmc
from SocketServer import ThreadingMixIn from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from urlparse import urlparse, parse_qs from urlparse import urlparse, parse_qs
from settings import settings import settings
from functions import * from functions import *
from subscribers import subMgr from utils import logging
@logging
class MyHandler(BaseHTTPRequestHandler): class MyHandler(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1' protocol_version = 'HTTP/1.1'
def log_message(s, format, *args):
# I have my own logging, suppressing BaseHTTPRequestHandler's def __init__(self, *args, **kwargs):
#printDebug(format % args) self.serverlist = []
return True self.settings = settings.getSettings()
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def getServerByHost(self, host):
if len(self.serverlist) == 1:
return self.serverlist[0]
for server in self.serverlist:
if (server.get('serverName') in host or
server.get('server') in host):
return server
return {}
def do_HEAD(s): def do_HEAD(s):
printDebug( "Serving HEAD request..." ) s.logMsg("Serving HEAD request...", 2)
s.answer_request(0) s.answer_request(0)
def do_GET(s): def do_GET(s):
printDebug( "Serving GET request..." ) s.logMsg("Serving GET request...", 2)
s.answer_request(1) s.answer_request(1)
def do_OPTIONS(s): def do_OPTIONS(s):
s.send_response(200) s.send_response(200)
s.send_header('Content-Length', '0') s.send_header('Content-Length', '0')
s.send_header('X-Plex-Client-Identifier', settings['uuid']) s.send_header('X-Plex-Client-Identifier', s.settings['uuid'])
s.send_header('Content-Type', 'text/plain') s.send_header('Content-Type', 'text/plain')
s.send_header('Connection', 'close') s.send_header('Connection', 'close')
s.send_header('Access-Control-Max-Age', '1209600') s.send_header('Access-Control-Max-Age', '1209600')
s.send_header('Access-Control-Allow-Origin', '*') s.send_header('Access-Control-Allow-Origin', '*')
s.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, HEAD') s.send_header('Access-Control-Allow-Methods',
s.send_header('Access-Control-Allow-Headers', 'x-plex-version, x-plex-platform-version, x-plex-username, x-plex-client-identifier, x-plex-target-client-identifier, x-plex-device-name, x-plex-platform, x-plex-product, accept, x-plex-device') 'POST, GET, OPTIONS, DELETE, PUT, HEAD')
s.send_header('Access-Control-Allow-Headers',
'x-plex-version, x-plex-platform-version, '
'x-plex-username, x-plex-client-identifier, '
'x-plex-target-client-identifier, x-plex-device-name, '
'x-plex-platform, x-plex-product, accept, x-plex-device')
s.end_headers() s.end_headers()
s.wfile.close() s.wfile.close()
def response(s, body, headers = {}, code = 200): def response(s, body, headers={}, code=200):
try: try:
s.send_response(code) s.send_response(code)
for key in headers: for key in headers:
@ -49,73 +67,76 @@ class MyHandler(BaseHTTPRequestHandler):
pass pass
def answer_request(s, sendData): def answer_request(s, sendData):
s.serverlist = s.server.client.getServerList()
s.subMgr = s.server.subscriptionManager
s.js = s.server.jsonClass
try: try:
request_path=s.path[1:] request_path = s.path[1:]
request_path=re.sub(r"\?.*","",request_path) request_path = re.sub(r"\?.*", "", request_path)
url = urlparse(s.path) url = urlparse(s.path)
paramarrays = parse_qs(url.query) paramarrays = parse_qs(url.query)
params = {} params = {}
for key in paramarrays: for key in paramarrays:
params[key] = paramarrays[key][0] params[key] = paramarrays[key][0]
printDebug ( "request path is: [%s]" % ( request_path,) ) s.logMsg("request path is: [%s]" % (request_path,), 2)
printDebug ( "params are: %s" % params ) s.logMsg("params are: %s" % params, 2)
subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False)) s.subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False))
if request_path=="version": if request_path=="version":
s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % settings['version']) s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % s.settings['version'])
elif request_path=="verify": elif request_path=="verify":
result=jsonrpc("ping") result=s.js.jsonrpc("ping")
s.response("XBMC JSON connection test:\r\n"+result) s.response("XBMC JSON connection test:\r\n"+result)
elif "resources" == request_path: elif "resources" == request_path:
resp = getXMLHeader() resp = getXMLHeader()
resp += "<MediaContainer>" resp += "<MediaContainer>"
resp += "<Player" resp += "<Player"
resp += ' title="%s"' % settings['client_name'] resp += ' title="%s"' % s.settings['client_name']
resp += ' protocol="plex"' resp += ' protocol="plex"'
resp += ' protocolVersion="1"' resp += ' protocolVersion="1"'
resp += ' protocolCapabilities="navigation,playback,timeline"' resp += ' protocolCapabilities="navigation,playback,timeline"'
resp += ' machineIdentifier="%s"' % settings['uuid'] resp += ' machineIdentifier="%s"' % s.settings['uuid']
resp += ' product="PlexKodiConnect"' resp += ' product="PlexKodiConnect"'
resp += ' platform="%s"' % getPlatform() resp += ' platform="%s"' % getPlatform()
resp += ' platformVersion="%s"' % settings['plexbmc_version'] resp += ' platformVersion="%s"' % s.settings['plexbmc_version']
resp += ' deviceClass="pc"' resp += ' deviceClass="pc"'
resp += "/>" resp += "/>"
resp += "</MediaContainer>" resp += "</MediaContainer>"
printDebug("crafted resources response: %s" % resp) s.logMsg("crafted resources response: %s" % resp, 2)
s.response(resp, getPlexHeaders()) s.response(resp, s.js.getPlexHeaders())
elif "/subscribe" in request_path: elif "/subscribe" in request_path:
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
protocol = params.get('protocol', False) protocol = params.get('protocol', False)
host = s.client_address[0] host = s.client_address[0]
port = params.get('port', False) port = params.get('port', False)
uuid = s.headers.get('X-Plex-Client-Identifier', "") uuid = s.headers.get('X-Plex-Client-Identifier', "")
commandID = params.get('commandID', 0) commandID = params.get('commandID', 0)
subMgr.addSubscriber(protocol, host, port, uuid, commandID) s.subMgr.addSubscriber(protocol, host, port, uuid, commandID)
elif "/poll" in request_path: elif "/poll" in request_path:
if params.get('wait', False) == '1': if params.get('wait', False) == '1':
xbmc.sleep(950) xbmc.sleep(950)
commandID = params.get('commandID', 0) commandID = params.get('commandID', 0)
s.response(re.sub(r"INSERTCOMMANDID", str(commandID), subMgr.msg(getPlayers())), { s.response(re.sub(r"INSERTCOMMANDID", str(commandID), s.subMgr.msg(s.js.getPlayers())), {
'X-Plex-Client-Identifier': settings['uuid'], 'X-Plex-Client-Identifier': s.settings['uuid'],
'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier', 'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/xml' 'Content-Type': 'text/xml'
}) })
elif "/unsubscribe" in request_path: elif "/unsubscribe" in request_path:
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
uuid = s.headers.get('X-Plex-Client-Identifier', False) or s.client_address[0] uuid = s.headers.get('X-Plex-Client-Identifier', False) or s.client_address[0]
subMgr.removeSubscriber(uuid) s.subMgr.removeSubscriber(uuid)
elif request_path == "player/playback/setParameters": elif request_path == "player/playback/setParameters":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
if 'volume' in params: if 'volume' in params:
volume = int(params['volume']) volume = int(params['volume'])
printDebug("adjusting the volume to %s%%" % volume) s.logMsg("adjusting the volume to %s%%" % volume, 2)
jsonrpc("Application.SetVolume", {"volume": volume}) s.js.jsonrpc("Application.SetVolume", {"volume": volume})
elif "/playMedia" in request_path: elif "/playMedia" in request_path:
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
offset = params.get('viewOffset', params.get('offset', "0")) offset = params.get('viewOffset', params.get('offset', "0"))
protocol = params.get('protocol', "http") protocol = params.get('protocol', "http")
address = params.get('address', s.client_address[0]) address = params.get('address', s.client_address[0])
server = getServerByHost(address) server = s.getServerByHost(address)
port = params.get('port', server.get('port', '32400')) port = params.get('port', server.get('port', '32400'))
try: try:
containerKey = urlparse(params.get('containerKey')).path containerKey = urlparse(params.get('containerKey')).path
@ -127,74 +148,88 @@ class MyHandler(BaseHTTPRequestHandler):
except IndexError: except IndexError:
playQueueID = '' playQueueID = ''
jsonrpc("playmedia", params) s.js.jsonrpc("playmedia", params)
subMgr.lastkey = params['key'] s.subMgr.lastkey = params['key']
subMgr.containerKey = containerKey s.subMgr.containerKey = containerKey
subMgr.playQueueID = playQueueID s.subMgr.playQueueID = playQueueID
subMgr.server = server.get('server', 'localhost') s.subMgr.server = server.get('server', 'localhost')
subMgr.port = port s.subMgr.port = port
subMgr.protocol = protocol s.subMgr.protocol = protocol
subMgr.notify() s.subMgr.notify()
elif request_path == "player/playback/play": elif request_path == "player/playback/play":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True}) s.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True})
elif request_path == "player/playback/pause": elif request_path == "player/playback/pause":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False}) s.js.jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False})
elif request_path == "player/playback/stop": elif request_path == "player/playback/stop":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.Stop", {"playerid" : playerid}) s.js.jsonrpc("Player.Stop", {"playerid" : playerid})
elif request_path == "player/playback/seekTo": elif request_path == "player/playback/seekTo":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))}) s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))})
subMgr.notify() s.subMgr.notify()
elif request_path == "player/playback/stepForward": elif request_path == "player/playback/stepForward":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"}) s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"})
subMgr.notify() s.subMgr.notify()
elif request_path == "player/playback/stepBack": elif request_path == "player/playback/stepBack":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"}) s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"})
subMgr.notify() s.subMgr.notify()
elif request_path == "player/playback/skipNext": elif request_path == "player/playback/skipNext":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"}) s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"})
subMgr.notify() s.subMgr.notify()
elif request_path == "player/playback/skipPrevious": elif request_path == "player/playback/skipPrevious":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
for playerid in getPlayerIds(): for playerid in s.js.getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"}) s.js.jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"})
subMgr.notify() s.subMgr.notify()
elif request_path == "player/navigation/moveUp": elif request_path == "player/navigation/moveUp":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
jsonrpc("Input.Up") s.js.jsonrpc("Input.Up")
elif request_path == "player/navigation/moveDown": elif request_path == "player/navigation/moveDown":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
jsonrpc("Input.Down") s.js.jsonrpc("Input.Down")
elif request_path == "player/navigation/moveLeft": elif request_path == "player/navigation/moveLeft":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
jsonrpc("Input.Left") s.js.jsonrpc("Input.Left")
elif request_path == "player/navigation/moveRight": elif request_path == "player/navigation/moveRight":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
jsonrpc("Input.Right") s.js.jsonrpc("Input.Right")
elif request_path == "player/navigation/select": elif request_path == "player/navigation/select":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
jsonrpc("Input.Select") s.js.jsonrpc("Input.Select")
elif request_path == "player/navigation/home": elif request_path == "player/navigation/home":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
jsonrpc("Input.Home") s.js.jsonrpc("Input.Home")
elif request_path == "player/navigation/back": elif request_path == "player/navigation/back":
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), s.js.getPlexHeaders())
jsonrpc("Input.Back") s.js.jsonrpc("Input.Back")
except: except:
traceback.print_exc() traceback.print_exc()
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True daemon_threads = True
def __init__(self, client, subscriptionManager, jsonClass,
*args, **kwargs):
"""
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
date serverlist without instantiating anything
same for SubscriptionManager and jsonClass
"""
self.client = client
self.subscriptionManager = subscriptionManager
self.jsonClass = jsonClass
HTTPServer.__init__(self, *args, **kwargs)

View file

@ -333,7 +333,7 @@ class plexgdm:
if discovery_count > self.discovery_interval: if discovery_count > self.discovery_interval:
self.discover() self.discover()
discovery_count=0 discovery_count=0
xbmc.sleep(1000) xbmc.sleep(500)
def start_discovery(self, daemon = False): def start_discovery(self, daemon = False):
if not self._discovery_is_running: if not self._discovery_is_running:

View file

@ -1,48 +1,46 @@
import xbmc
import xbmcaddon import xbmcaddon
import utils import utils
settings = {}
guisettingsXML = utils.guisettingsXML()
def getGUI(name): def getGUI(name):
guisettingsXML = utils.guisettingsXML()
try: try:
ans = list(guisettingsXML.iter(name))[0].text ans = list(guisettingsXML.iter(name))[0].text
if ans is None: if ans is None:
ans = '' ans = ''
return ans
except: except:
return "" ans = ''
return ans
addon = xbmcaddon.Addon()
plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect')
settings['debug'] = utils.settings('companionDebugging') def getSettings():
settings['gdm_debug'] = utils.settings('companionGDMDebugging') settings = {}
addon = xbmcaddon.Addon()
plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect')
# Transform 'true' into True because of the way Kodi's file settings work settings['debug'] = utils.settings('companionDebugging')
kodiSettingsList = ['debug', 'gdm_debug'] settings['gdm_debug'] = utils.settings('companionGDMDebugging')
for entry in kodiSettingsList:
if settings[entry] == 'true':
settings[entry] = True
else:
settings[entry] = False
settings['client_name'] = plexbmc.getSetting('deviceName') # Transform 'true' into True because of the way Kodi's file settings work
kodiSettingsList = ['debug', 'gdm_debug']
for entry in kodiSettingsList:
if settings[entry] == 'true':
settings[entry] = True
else:
settings[entry] = False
# XBMC web server settings settings['client_name'] = plexbmc.getSetting('deviceName')
xbmc.sleep(5000)
settings['webserver_enabled'] = (getGUI('webserver') == "true")
settings['port'] = int(getGUI('webserverport'))
settings['user'] = getGUI('webserverusername')
settings['passwd'] = getGUI('webserverpassword')
settings['uuid'] = plexbmc.getSetting('plex_client_Id') # XBMC web server settings
settings['webserver_enabled'] = (getGUI('webserver') == "true")
settings['port'] = int(getGUI('webserverport'))
settings['user'] = getGUI('webserverusername')
settings['passwd'] = getGUI('webserverpassword')
settings['version'] = plexbmc.getAddonInfo('version') settings['uuid'] = plexbmc.getSetting('plex_client_Id')
settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
settings['myplex_user'] = plexbmc.getSetting('username') settings['version'] = plexbmc.getAddonInfo('version')
settings['serverList'] = [] settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
settings['myport'] = addon.getSetting('companionPort') settings['myplex_user'] = plexbmc.getSetting('username')
settings['myport'] = addon.getSetting('companionPort')
return settings

View file

@ -1,18 +1,17 @@
import re import re
import threading import threading
# from xml.dom.minidom import parseString
from functions import * from functions import *
from settings import settings
from httppersist import requests
from xbmc import Player from xbmc import Player
# import xbmcgui
import downloadutils import downloadutils
from utils import window from utils import window, logging
import PlexFunctions as pf import PlexFunctions as pf
@logging
class SubscriptionManager: class SubscriptionManager:
def __init__(self): def __init__(self, jsonClass, RequestMgr):
self.serverlist = []
self.subscribers = {} self.subscribers = {}
self.info = {} self.info = {}
self.lastkey = "" self.lastkey = ""
@ -27,8 +26,20 @@ class SubscriptionManager:
self.download = downloadutils.DownloadUtils() self.download = downloadutils.DownloadUtils()
self.xbmcplayer = Player() self.xbmcplayer = Player()
self.js = jsonClass
self.RequestMgr = RequestMgr
def getServerByHost(self, host):
if len(self.serverlist) == 1:
return self.serverlist[0]
for server in self.serverlist:
if (server.get('serverName') in host or
server.get('server') in host):
return server
return {}
def getVolume(self): def getVolume(self):
self.volume, self.mute = getVolume() self.volume, self.mute = self.js.getVolume()
def msg(self, players): def msg(self, players):
msg = getXMLHeader() msg = getXMLHeader()
@ -45,9 +56,9 @@ class SubscriptionManager:
else: else:
self.mainlocation = "navigation" self.mainlocation = "navigation"
msg += ' location="%s">' % self.mainlocation msg += ' location="%s">' % self.mainlocation
msg += self.getTimelineXML(getAudioPlayerId(players), plex_audio()) msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
msg += self.getTimelineXML(getPhotoPlayerId(players), plex_photo()) msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
msg += self.getTimelineXML(getVideoPlayerId(players), plex_video()) msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
msg += "\r\n</MediaContainer>" msg += "\r\n</MediaContainer>"
return msg return msg
@ -88,7 +99,7 @@ class SubscriptionManager:
ret += ' location="%s"' % (self.mainlocation) ret += ' location="%s"' % (self.mainlocation)
ret += ' key="%s"' % (self.lastkey) ret += ' key="%s"' % (self.lastkey)
ret += ' ratingKey="%s"' % (self.lastratingkey) ret += ' ratingKey="%s"' % (self.lastratingkey)
serv = getServerByHost(self.server) serv = self.getServerByHost(self.server)
if info.get('playQueueID'): if info.get('playQueueID'):
self.containerKey = "/playQueues/%s" % info.get('playQueueID') self.containerKey = "/playQueues/%s" % info.get('playQueueID')
ret += ' playQueueID="%s"' % info.get('playQueueID') ret += ' playQueueID="%s"' % info.get('playQueueID')
@ -124,7 +135,7 @@ class SubscriptionManager:
def notify(self, event = False): def notify(self, event = False):
self.cleanup() self.cleanup()
players = getPlayers() players = self.js.getPlayers()
# 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)
@ -152,32 +163,39 @@ class SubscriptionManager:
params['state'] = info['state'] params['state'] = info['state']
params['time'] = info['time'] params['time'] = info['time']
params['duration'] = info['duration'] params['duration'] = info['duration']
serv = getServerByHost(self.server) serv = self.getServerByHost(self.server)
url = serv.get('protocol', 'http') + '://' \ url = serv.get('protocol', 'http') + '://' \
+ serv.get('server', 'localhost') + ':' \ + serv.get('server', 'localhost') + ':' \
+ serv.get('port', '32400') + "/:/timeline" + serv.get('port', '32400') + "/:/timeline"
self.download.downloadUrl(url, type="GET", parameters=params) self.download.downloadUrl(url, type="GET", parameters=params)
# requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http')) # requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
printDebug("params: %s" % params) self.logMsg("params: %s" % params, 2)
printDebug("players: %s" % players) self.logMsg("players: %s" % players, 2)
printDebug("sent server notification with state = %s" % params['state']) self.logMsg("sent server notification with state = %s"
% params['state'], 2)
def controllable(self): def controllable(self):
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause" return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
def addSubscriber(self, protocol, host, port, uuid, commandID): def addSubscriber(self, protocol, host, port, uuid, commandID):
sub = Subscriber(protocol, host, port, uuid, commandID) sub = Subscriber(protocol,
host,
port,
uuid,
commandID,
self,
self.RequestMgr)
with threading.RLock(): with threading.RLock():
self.subscribers[sub.uuid] = sub self.subscribers[sub.uuid] = sub
return sub return sub
def removeSubscriber(self, uuid): def removeSubscriber(self, uuid):
with threading.RLock(): with threading.RLock():
for sub in self.subscribers.values(): for sub in self.subscribers.values():
if sub.uuid == uuid or sub.host == uuid: if sub.uuid == uuid or sub.host == uuid:
sub.cleanup() sub.cleanup()
del self.subscribers[sub.uuid] del self.subscribers[sub.uuid]
def cleanup(self): def cleanup(self):
with threading.RLock(): with threading.RLock():
for sub in self.subscribers.values(): for sub in self.subscribers.values():
@ -189,8 +207,8 @@ class SubscriptionManager:
info = {} info = {}
try: try:
# get info from the player # get info from the player
props = jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]}) props = self.js.jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]})
printDebug(jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]})) self.logMsg(self.js.jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}), 2)
info['time'] = timeToMillis(props['time']) info['time'] = timeToMillis(props['time'])
info['duration'] = timeToMillis(props['totaltime']) info['duration'] = timeToMillis(props['totaltime'])
info['state'] = ("paused", "playing")[int(props['speed'])] info['state'] = ("paused", "playing")[int(props['speed'])]
@ -214,8 +232,11 @@ class SubscriptionManager:
return info return info
@logging
class Subscriber: class Subscriber:
def __init__(self, protocol, host, port, uuid, commandID): def __init__(self, protocol, host, port, uuid, commandID,
subMgr, RequestMgr):
self.protocol = protocol or "http" self.protocol = protocol or "http"
self.host = host self.host = host
self.port = port or 32400 self.port = port or 32400
@ -224,12 +245,18 @@ class Subscriber:
self.navlocationsent = False self.navlocationsent = False
self.age = 0 self.age = 0
self.download = downloadutils.DownloadUtils() self.download = downloadutils.DownloadUtils()
self.subMgr = subMgr
self.RequestMgr = RequestMgr
def __eq__(self, other): def __eq__(self, other):
return self.uuid == other.uuid return self.uuid == other.uuid
def tostr(self): def tostr(self):
return "uuid=%s,commandID=%i" % (self.uuid, self.commandID) return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
def cleanup(self): def cleanup(self):
requests.closeConnection(self.protocol, self.host, self.port) self.RequestMgr.closeConnection(self.protocol, self.host, self.port)
def send_update(self, msg, is_nav): def send_update(self, msg, is_nav):
self.age += 1 self.age += 1
if not is_nav: if not is_nav:
@ -239,7 +266,8 @@ class Subscriber:
else: else:
self.navlocationsent = True self.navlocationsent = True
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg) msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
printDebug("sending xml to subscriber %s: %s" % (self.tostr(), msg)) self.logMsg("sending xml to subscriber %s: %s"
% (self.tostr(), msg), 2)
url = self.protocol + '://' + self.host + ':' + self.port \ url = self.protocol + '://' + self.host + ':' + self.port \
+ "/:/timeline" + "/:/timeline"
t = threading.Thread(target=self.threadedSend, args=(url, msg)) t = threading.Thread(target=self.threadedSend, args=(url, msg))
@ -255,6 +283,4 @@ class Subscriber:
postBody=msg, postBody=msg,
type="POSTXML") type="POSTXML")
if response in [False, None, 401]: if response in [False, None, 401]:
subMgr.removeSubscriber(self.uuid) self.subMgr.removeSubscriber(self.uuid)
subMgr = SubscriptionManager()

View file

@ -322,8 +322,7 @@ class Service():
delay = int(utils.settings('startupDelay')) delay = int(utils.settings('startupDelay'))
xbmc.log("Delaying Plex startup by: %s sec..." % delay) xbmc.log("Delaying Plex startup by: %s sec..." % delay)
# Plex: add 10 seconds just for good measure if delay and xbmc.Monitor().waitForAbort(delay):
if delay and xbmc.Monitor().waitForAbort(delay+10):
# Start the service # Start the service
xbmc.log("Abort requested while waiting. Emby for kodi not started.") xbmc.log("Abort requested while waiting. Emby for kodi not started.")
else: else: