PlexKodiConnect/resources/lib/plexbmchelper/subscribers.py

322 lines
12 KiB
Python
Raw Normal View History

2016-09-03 01:20:19 +10:00
import logging
2016-01-15 22:12:52 +11:00
import re
import threading
2016-02-07 22:38:50 +11:00
import downloadutils
2016-09-03 01:20:19 +10:00
from utils import window
2016-02-07 22:38:50 +11:00
import PlexFunctions as pf
from functions import *
2016-01-15 22:12:52 +11:00
2016-09-03 01:20:19 +10:00
###############################################################################
log = logging.getLogger("PLEX."+__name__)
###############################################################################
2016-01-15 22:12:52 +11:00
class SubscriptionManager:
2016-12-28 23:14:21 +11:00
def __init__(self, jsonClass, RequestMgr, player, mgr):
self.serverlist = []
2016-01-15 22:12:52 +11:00
self.subscribers = {}
self.info = {}
self.lastkey = ""
2016-01-30 06:07:21 +11:00
self.containerKey = ""
2016-08-11 03:03:37 +10:00
self.ratingkey = ""
self.lastplayers = {}
self.lastinfo = {
'video': {},
'audio': {},
'picture': {}
}
2016-01-15 22:12:52 +11:00
self.volume = 0
2016-02-07 22:38:50 +11:00
self.mute = '0'
2016-01-15 22:12:52 +11:00
self.server = ""
self.protocol = "http"
self.port = ""
self.playerprops = {}
self.doUtils = downloadutils.DownloadUtils().downloadUrl
2016-08-07 23:33:36 +10:00
self.xbmcplayer = player
2016-12-28 23:14:21 +11:00
self.playqueue = mgr.playqueue
2016-02-07 22:38:50 +11:00
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 {}
2016-01-15 22:12:52 +11:00
def getVolume(self):
self.volume, self.mute = self.js.getVolume()
2016-01-15 22:12:52 +11:00
def msg(self, players):
msg = getXMLHeader()
2017-05-07 02:42:43 +10:00
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
2017-05-07 02:48:58 +10:00
msg += ' machineIdentifier="%s">' % window('plex_client_Id')
msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
2017-02-27 02:12:39 +11:00
msg += "\n</MediaContainer>"
2016-01-15 22:12:52 +11:00
return msg
2016-08-11 03:03:37 +10:00
2016-01-15 22:12:52 +11:00
def getTimelineXML(self, playerid, ptype):
if playerid is not None:
info = self.getPlayerProperties(playerid)
2016-01-15 22:12:52 +11:00
# save this info off so the server update can use it too
self.playerprops[playerid] = info;
state = info['state']
time = info['time']
2016-01-15 22:12:52 +11:00
else:
state = "stopped"
time = 0
2017-02-27 02:12:39 +11:00
ret = "\n"+' <Timeline state="%s" time="%s" type="%s"' % (state, time, ptype)
if playerid is None:
2016-02-07 22:38:50 +11:00
ret += ' seekRange="0-0"'
ret += ' />'
return ret
# pbmc_server = str(WINDOW.getProperty('plexbmc.nowplaying.server'))
# userId = str(WINDOW.getProperty('currUserId'))
2016-03-23 01:34:59 +11:00
pbmc_server = window('pms_server')
if pbmc_server:
(self.protocol, self.server, self.port) = \
pbmc_server.split(':')
self.server = self.server.replace('/', '')
2016-02-07 22:38:50 +11:00
keyid = None
count = 0
while not keyid:
if count > 300:
break
2016-09-18 04:30:28 +10:00
keyid = window('plex_currently_playing_itemid')
xbmc.sleep(100)
count += 1
2016-02-07 22:38:50 +11:00
if keyid:
2016-08-11 03:03:37 +10:00
self.lastkey = "/library/metadata/%s" % keyid
self.ratingkey = keyid
ret += ' key="%s"' % self.lastkey
ret += ' ratingKey="%s"' % self.ratingkey
serv = self.getServerByHost(self.server)
if info.get('playQueueID'):
self.containerKey = "/playQueues/%s" % info.get('playQueueID')
ret += ' playQueueID="%s"' % info.get('playQueueID')
ret += ' playQueueVersion="%s"' % info.get('playQueueVersion')
2016-08-11 03:03:37 +10:00
ret += ' playQueueItemID="%s"' % info.get('playQueueItemID')
2016-03-17 04:01:07 +11:00
ret += ' containerKey="%s"' % self.containerKey
2016-08-12 06:11:00 +10:00
ret += ' guid="%s"' % info['guid']
elif keyid:
self.containerKey = self.lastkey
2016-08-11 03:03:37 +10:00
ret += ' containerKey="%s"' % self.containerKey
2016-02-07 22:38:50 +11:00
ret += ' duration="%s"' % info['duration']
ret += ' seekRange="0-%s"' % info['duration']
2016-02-07 22:38:50 +11:00
ret += ' controllable="%s"' % self.controllable()
ret += ' machineIdentifier="%s"' % serv.get('uuid', "")
ret += ' protocol="%s"' % serv.get('protocol', "http")
ret += ' address="%s"' % serv.get('server', self.server)
ret += ' port="%s"' % serv.get('port', self.port)
ret += ' volume="%s"' % info['volume']
ret += ' shuffle="%s"' % info['shuffle']
2016-02-07 22:38:50 +11:00
ret += ' mute="%s"' % self.mute
ret += ' repeat="%s"' % info['repeat']
ret += ' itemType="%s"' % info['itemType']
2016-02-07 22:38:50 +11:00
# Might need an update in the future
if ptype == 'video':
ret += ' subtitleStreamID="-1"'
ret += ' audioStreamID="-1"'
2016-02-07 22:38:50 +11:00
2017-05-07 02:39:38 +10:00
ret += '/>'
2016-01-15 22:12:52 +11:00
return ret
2016-02-07 22:38:50 +11:00
2016-01-15 22:12:52 +11:00
def updateCommandID(self, uuid, commandID):
if commandID and self.subscribers.get(uuid, False):
2016-08-11 03:03:37 +10:00
self.subscribers[uuid].commandID = int(commandID)
def notify(self, event=False):
2016-01-15 22:12:52 +11:00
self.cleanup()
# Don't tell anyone if we don't know a Plex ID and are still playing
# (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
2016-09-18 04:30:28 +10:00
if (not window('plex_currently_playing_itemid')
and not self.lastplayers):
return True
players = self.js.getPlayers()
2016-01-15 22:12:52 +11:00
# fetch the message, subscribers or not, since the server
# will need the info anyway
msg = self.msg(players)
if self.subscribers:
with threading.RLock():
for sub in self.subscribers.values():
2016-08-11 03:03:37 +10:00
sub.send_update(msg, len(players) == 0)
self.notifyServer(players)
self.lastplayers = players
2016-01-15 22:12:52 +11:00
return True
2016-08-11 03:03:37 +10:00
2016-01-15 22:12:52 +11:00
def notifyServer(self, players):
for typus, p in players.iteritems():
2016-01-15 22:12:52 +11:00
info = self.playerprops[p.get('playerid')]
self._sendNotification(info)
self.lastinfo[typus] = info
# Cross the one of the list
try:
del self.lastplayers[typus]
except KeyError:
pass
# Process the players we have left (to signal a stop)
for typus, p in self.lastplayers.iteritems():
self.lastinfo[typus]['state'] = 'stopped'
self._sendNotification(self.lastinfo[typus])
def _sendNotification(self, info):
params = {
'containerKey': self.containerKey or "/library/metadata/900000",
'key': self.lastkey or "/library/metadata/900000",
'ratingKey': self.ratingkey or "900000",
'state': info['state'],
'time': info['time'],
'duration': info['duration']
}
if info.get('playQueueID'):
params['containerKey'] = '/playQueues/%s' % info['playQueueID']
params['playQueueVersion'] = info['playQueueVersion']
params['playQueueItemID'] = info['playQueueItemID']
serv = self.getServerByHost(self.server)
url = '%s://%s:%s/:/timeline' % (serv.get('protocol', 'http'),
serv.get('server', 'localhost'),
serv.get('port', '32400'))
self.doUtils(url, parameters=params)
2016-09-03 01:20:19 +10:00
log.debug("Sent server notification with parameters: %s to %s"
% (params, url))
2016-01-27 22:18:54 +11:00
2016-01-15 22:12:52 +11:00
def controllable(self):
2016-01-30 06:07:21 +11:00
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
2016-01-15 22:12:52 +11:00
def addSubscriber(self, protocol, host, port, uuid, commandID):
sub = Subscriber(protocol,
host,
port,
uuid,
commandID,
self,
self.RequestMgr)
2016-01-15 22:12:52 +11:00
with threading.RLock():
self.subscribers[sub.uuid] = sub
return sub
2016-01-15 22:12:52 +11:00
def removeSubscriber(self, uuid):
with threading.RLock():
for sub in self.subscribers.values():
if sub.uuid == uuid or sub.host == uuid:
sub.cleanup()
del self.subscribers[sub.uuid]
2016-01-15 22:12:52 +11:00
def cleanup(self):
with threading.RLock():
for sub in self.subscribers.values():
if sub.age > 30:
sub.cleanup()
del self.subscribers[sub.uuid]
2016-08-12 06:11:00 +10:00
2016-01-15 22:12:52 +11:00
def getPlayerProperties(self, playerid):
try:
2016-12-28 23:14:21 +11:00
# Get the playqueue
playqueue = self.playqueue.playqueues[playerid]
2016-01-15 22:12:52 +11:00
# get info from the player
2016-08-12 06:11:00 +10:00
props = self.js.jsonrpc(
"Player.GetProperties",
{"playerid": playerid,
"properties": ["time",
"totaltime",
"speed",
"shuffled",
"repeat"]})
2016-02-07 22:38:50 +11:00
2016-08-12 06:11:00 +10:00
info = {
'time': timeToMillis(props['time']),
'duration': timeToMillis(props['totaltime']),
'state': ("paused", "playing")[int(props['speed'])],
'shuffle': ("0", "1")[props.get('shuffled', False)],
'repeat': pf.getPlexRepeat(props.get('repeat')),
}
# Get the playlist position
pos = self.js.jsonrpc(
"Player.GetProperties",
{"playerid": playerid,
"properties": ["position"]})['position']
try:
info['playQueueItemID'] = playqueue.items[pos].ID or 'null'
info['guid'] = playqueue.items[pos].guid or 'null'
info['playQueueID'] = playqueue.ID or 'null'
info['playQueueVersion'] = playqueue.version or 'null'
info['itemType'] = playqueue.items[pos].plex_type or 'null'
except:
pass
2016-01-15 22:12:52 +11:00
except:
2016-08-12 06:11:00 +10:00
import traceback
2016-09-03 01:20:19 +10:00
log.error("Traceback:\n%s" % traceback.format_exc())
2016-08-12 06:11:00 +10:00
info = {
'time': 0,
'duration': 0,
'state': 'stopped',
'shuffle': False,
'repeat': 0
}
2016-01-15 22:12:52 +11:00
# get the volume from the application
info['volume'] = self.volume
2016-02-07 22:38:50 +11:00
info['mute'] = self.mute
2016-01-15 22:12:52 +11:00
return info
2016-01-15 22:12:52 +11:00
class Subscriber:
def __init__(self, protocol, host, port, uuid, commandID,
subMgr, RequestMgr):
2016-01-15 22:12:52 +11:00
self.protocol = protocol or "http"
self.host = host
self.port = port or 32400
self.uuid = uuid or host
self.commandID = int(commandID) or 0
self.navlocationsent = False
self.age = 0
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.subMgr = subMgr
self.RequestMgr = RequestMgr
2016-01-15 22:12:52 +11:00
def __eq__(self, other):
return self.uuid == other.uuid
2016-01-15 22:12:52 +11:00
def tostr(self):
return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
2016-01-15 22:12:52 +11:00
def cleanup(self):
self.RequestMgr.closeConnection(self.protocol, self.host, self.port)
2016-01-15 22:12:52 +11:00
def send_update(self, msg, is_nav):
self.age += 1
if not is_nav:
self.navlocationsent = False
elif self.navlocationsent:
return True
else:
self.navlocationsent = True
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
2017-02-27 02:14:46 +11:00
log.debug("sending xml to subscriber %s:\n%s" % (self.tostr(), msg))
url = self.protocol + '://' + self.host + ':' + self.port \
+ "/:/timeline"
t = threading.Thread(target=self.threadedSend, args=(url, msg))
t.start()
2016-02-07 22:38:50 +11:00
def threadedSend(self, url, msg):
"""
Threaded POST request, because they stall due to PMS response missing
the Content-Length header :-(
"""
response = self.doUtils(url,
postBody=msg,
2016-04-26 22:02:19 +10:00
action_type="POST")
2016-01-30 06:07:21 +11:00
if response in [False, None, 401]:
self.subMgr.removeSubscriber(self.uuid)