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()
@ -29,6 +38,7 @@ def xbmc_type(plex_type):
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"
@ -54,18 +65,44 @@ def getPlatform():
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 getXMLHeader():
def jsonrpc(action, arguments = {}): return '<?xml version="1.0" encoding="utf-8" ?>'+"\r\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):
self.settings = settings.getSettings()
self.requestMgr = requestMgr
def jsonrpc(self, action, arguments={}):
""" put some JSON together for the JSON-RPC APIv6 """ """ put some JSON together for the JSON-RPC APIv6 """
if action.lower() == "sendkey": if action.lower() == "sendkey":
request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : self.arguments[0], "done" : False }} ) request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : arguments[0], "done" : False }} )
elif action.lower() == "ping": elif action.lower() == "ping":
request=json.dumps({ "jsonrpc" : "2.0", request=json.dumps({ "jsonrpc" : "2.0",
"id" : 1 , "id" : 1 ,
@ -85,114 +122,84 @@ def jsonrpc(action, arguments = {}):
"jsonrpc" : "2.0", "jsonrpc" : "2.0",
"method" : action}) "method" : action})
printDebug("Sending request to XBMC without network stack: %s" % request) logMsg("PlexCompanion",
result = parseJSONRPC(xbmc.executeJSONRPC(request)) "Sending request to XBMC without network stack: %s"
% request, 2)
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
if not result and settings['webserver_enabled']: if not result and self.settings['webserver_enabled']:
# xbmc.executeJSONRPC appears to fail on the login screen, but going # xbmc.executeJSONRPC appears to fail on the login screen, but going
# through the network stack works, so let's try the request again # through the network stack works, so let's try the request again
result = parseJSONRPC(requests.post( result = self.parseJSONRPC(self.requestMgr.post(
"127.0.0.1", "127.0.0.1",
settings['port'], self.settings['port'],
"/jsonrpc", "/jsonrpc",
request, request,
{ 'Content-Type' : 'application/json', { 'Content-Type' : 'application/json',
'Authorization' : 'Basic ' + string.strip(base64.encodestring(settings['user'] + ':' + settings['passwd'])) })) 'Authorization' : 'Basic ' + string.strip(base64.encodestring(self.settings['user'] + ':' + self.settings['passwd'])) }))
return result return result
def getPlexHeaders(self):
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():
return '<?xml version="1.0" encoding="utf-8" ?>'+"\r\n"
def getOKMsg():
return getXMLHeader() + '<Response code="200" status="OK" />'
def getPlexHeaders():
h = { h = {
"Content-type": "application/x-www-form-urlencoded", "Content-type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
"X-Plex-Version": settings['version'], "X-Plex-Version": self.settings['version'],
"X-Plex-Client-Identifier": settings['uuid'], "X-Plex-Client-Identifier": self.settings['uuid'],
"X-Plex-Provides": "player", "X-Plex-Provides": "player",
"X-Plex-Product": "PlexKodiConnect", "X-Plex-Product": "PlexKodiConnect",
"X-Plex-Device-Name": settings['client_name'], "X-Plex-Device-Name": self.settings['client_name'],
"X-Plex-Platform": "XBMC", "X-Plex-Platform": "Kodi",
"X-Plex-Model": getPlatform(), "X-Plex-Model": getPlatform(),
"X-Plex-Device": "PC", "X-Plex-Device": "PC",
} }
if settings['myplex_user']: if self.settings['myplex_user']:
h["X-Plex-Username"] = settings['myplex_user'] h["X-Plex-Username"] = self.settings['myplex_user']
return h return h
def getServerByHost(host): def parseJSONRPC(self, jsonraw):
list = settings['serverList'] if not jsonraw:
if len(list) == 1: logMsg("PlexCompanion", "Empty response from XBMC", 1)
return list[0]
for server in list:
if server.get('serverName') in host or server.get('server') in host:
return server
return {} 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(): def getPlayers(self):
info = jsonrpc("Player.GetActivePlayers") or [] info = self.jsonrpc("Player.GetActivePlayers") or []
ret = {} ret = {}
for player in info: for player in info:
player['playerid'] = int(player['playerid']) player['playerid'] = int(player['playerid'])
ret[player['type']] = player ret[player['type']] = player
return ret return ret
def getPlayerIds(): def getPlayerIds(self):
ret = [] ret = []
for player in getPlayers().values(): for player in self.getPlayers().values():
ret.append(player['playerid']) ret.append(player['playerid'])
return ret return ret
def getVideoPlayerId(players = False): def getVideoPlayerId(self, players=False):
if players is None: if players is None:
players = getPlayers() players = self.getPlayers()
return players.get(xbmc_video(), {}).get('playerid', None) return players.get(xbmc_video(), {}).get('playerid', None)
def getAudioPlayerId(players = False): def getAudioPlayerId(self, players=False):
if players is None: if players is None:
players = getPlayers() players = self.getPlayers()
return players.get(xbmc_audio(), {}).get('playerid', None) return players.get(xbmc_audio(), {}).get('playerid', None)
def getPhotoPlayerId(players = False): def getPhotoPlayerId(self, players=False):
if players is None: if players is None:
players = getPlayers() players = self.getPlayers()
return players.get(xbmc_photo(), {}).get('playerid', None) return players.get(xbmc_photo(), {}).get('playerid', None)
def getVolume(): def getVolume(self):
answ = jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] }) answ = self.jsonrpc('Application.GetProperties', { "properties": [ "volume", 'muted' ] })
vol = str(answ.get('volume', 100)) vol = str(answ.get('volume', 100))
mute = ("0", "1")[answ.get('muted', False)] mute = ("0", "1")[answ.get('muted', False)]
return (vol, mute) return (vol, mute)
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

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 = {}
@ -35,20 +40,26 @@ 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:
@ -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,34 +4,52 @@ 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()
@ -49,6 +67,9 @@ 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)
@ -57,65 +78,65 @@ class MyHandler(BaseHTTPRequestHandler):
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,21 +1,20 @@
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
def getSettings():
settings = {}
addon = xbmcaddon.Addon() addon = xbmcaddon.Addon()
plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect') plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect')
@ -33,7 +32,6 @@ for entry in kodiSettingsList:
settings['client_name'] = plexbmc.getSetting('deviceName') settings['client_name'] = plexbmc.getSetting('deviceName')
# XBMC web server settings # XBMC web server settings
xbmc.sleep(5000)
settings['webserver_enabled'] = (getGUI('webserver') == "true") settings['webserver_enabled'] = (getGUI('webserver') == "true")
settings['port'] = int(getGUI('webserverport')) settings['port'] = int(getGUI('webserverport'))
settings['user'] = getGUI('webserverusername') settings['user'] = getGUI('webserverusername')
@ -44,5 +42,5 @@ settings['uuid'] = plexbmc.getSetting('plex_client_Id')
settings['version'] = plexbmc.getAddonInfo('version') settings['version'] = plexbmc.getAddonInfo('version')
settings['plexbmc_version'] = plexbmc.getAddonInfo('version') settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
settings['myplex_user'] = plexbmc.getSetting('username') settings['myplex_user'] = plexbmc.getSetting('username')
settings['serverList'] = []
settings['myport'] = addon.getSetting('companionPort') 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,21 +163,28 @@ 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
@ -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: