Major Plex Companion overhaul, part 2

This commit is contained in:
croneter 2017-12-13 20:14:27 +01:00
parent cc347d5654
commit 9cac51d5c9
7 changed files with 258 additions and 217 deletions

View file

@ -2690,7 +2690,8 @@ class API():
plexitem = "plex_%s" % playurl plexitem = "plex_%s" % playurl
window('%s.runtime' % plexitem, value=str(userdata['Runtime'])) window('%s.runtime' % plexitem, value=str(userdata['Runtime']))
window('%s.type' % plexitem, value=itemtype) window('%s.type' % plexitem, value=itemtype)
window('%s.itemid' % plexitem, value=self.getRatingKey()) state.PLEX_IDS[tryDecode(playurl)] = self.getRatingKey()
# window('%s.itemid' % plexitem, value=self.getRatingKey())
window('%s.playcount' % plexitem, value=str(userdata['PlayCount'])) window('%s.playcount' % plexitem, value=str(userdata['PlayCount']))
if itemtype == v.PLEX_TYPE_EPISODE: if itemtype == v.PLEX_TYPE_EPISODE:

View file

@ -362,6 +362,7 @@ def get_episodes(params):
def get_item(playerid): def get_item(playerid):
""" """
UNRELIABLE on playback startup! (as other JSON and Python Kodi functions)
Returns the following for the currently playing item: Returns the following for the currently playing item:
{ {
u'title': u'Okja', u'title': u'Okja',

View file

@ -808,7 +808,7 @@ class Kodidb_Functions():
ids.append(row[0]) ids.append(row[0])
return ids return ids
def getIdFromFilename(self, filename, path): def video_id_from_filename(self, filename, path):
""" """
Returns the tuple (itemId, type) where Returns the tuple (itemId, type) where
itemId: Kodi DB unique Id for either movie or episode itemId: Kodi DB unique Id for either movie or episode
@ -884,6 +884,34 @@ class Kodidb_Functions():
return return
return itemId, typus return itemId, typus
def music_id_from_filename(self, filename, path):
"""
Returns the Kodi song_id from the Kodi music database or None if not
found OR something went wrong.
"""
query = '''
SELECT idPath
FROM path
WHERE strPath = ?
'''
self.cursor.execute(query, (path,))
path_id = self.cursor.fetchall()
if len(path_id) != 1:
log.error('Found wrong number of path ids: %s for path %s, abort',
path_id, path)
return
query = '''
SELECT idSong
FROM song
WHERE strFileName = ? AND idPath = ?
'''
self.cursor.execute(query, (filename, path_id[0]))
song_id = self.cursor.fetchall()
if len(song_id) != 1:
log.info('Found wrong number of songs %s, abort', song_id)
return
return song_id[0]
def getUnplayedItems(self): def getUnplayedItems(self):
""" """
VIDEOS VIDEOS
@ -1522,24 +1550,29 @@ class Kodidb_Functions():
self.cursor.execute(query, (kodi_id, kodi_type)) self.cursor.execute(query, (kodi_id, kodi_type))
def get_kodiid_from_filename(file): def kodiid_from_filename(path, kodi_type):
""" """
Returns the tuple (kodiid, type) if we have a video in the database with Returns kodi_id if we have an item in the Kodi video or audio database with
said filename, or (None, None) said path. Feed with the Kodi itemtype, e.v. 'movie', 'song'
Returns None if not possible
""" """
kodiid = None kodi_id = None
typus = None
try: try:
filename = file.rsplit('/', 1)[1] filename = path.rsplit('/', 1)[1]
path = file.rsplit('/', 1)[0] + '/' path = path.rsplit('/', 1)[0] + '/'
except IndexError: except IndexError:
filename = file.rsplit('\\', 1)[1] filename = path.rsplit('\\', 1)[1]
path = file.rsplit('\\', 1)[0] + '\\' path = path.rsplit('\\', 1)[0] + '\\'
log.debug('Trying to figure out playing item from filename: %s ' if kodi_type == v.KODI_TYPE_SONG:
'and path: %s' % (filename, path)) with GetKodiDB('music') as kodi_db:
with GetKodiDB('video') as kodi_db: try:
try: kodi_id, _ = kodi_db.music_id_from_filename(filename, path)
kodiid, typus = kodi_db.getIdFromFilename(filename, path) except TypeError:
except TypeError: log.info('No Kodi audio db element found for path %s', path)
log.info('No kodi video element found with filename %s' % filename) else:
return (kodiid, typus) with GetKodiDB('video') as kodi_db:
try:
kodi_id, _ = kodi_db.video_id_from_filename(filename, path)
except TypeError:
log.info('No kodi video db element found for path %s', path)
return kodi_id

View file

@ -11,7 +11,7 @@ import plexdb_functions as plexdb
from utils import window, settings, CatchExceptions, tryDecode, tryEncode, \ from utils import window, settings, CatchExceptions, tryDecode, tryEncode, \
plex_command plex_command
from PlexFunctions import scrobble from PlexFunctions import scrobble
from kodidb_functions import get_kodiid_from_filename from kodidb_functions import kodiid_from_filename
from PlexAPI import API from PlexAPI import API
import json_rpc as js import json_rpc as js
import state import state
@ -185,68 +185,61 @@ class KodiMonitor(Monitor):
u'item': {u'type': u'movie', u'title': u''}, u'item': {u'type': u'movie', u'title': u''},
u'player': {u'playerid': 1, u'speed': 1} u'player': {u'playerid': 1, u'speed': 1}
} }
Unfortunately VERY random inputs!
E.g. when using Widgets, Kodi doesn't tell us shit
""" """
log.debug('PlayBackStart called with: %s', data)
# Get the type of media we're playing # Get the type of media we're playing
try: try:
kodi_type = data['item']['type'] kodi_type = data['item']['type']
playerid = data['player']['playerid'] playerid = data['player']['playerid']
json_data = js.get_item(playerid)
except (TypeError, KeyError): except (TypeError, KeyError):
log.info('Aborting playback report - item is invalid for updates') log.info('Aborting playback report - item invalid for updates %s',
data)
return return
json_data = js.get_item(playerid)
path = json_data.get('file')
kodi_id = json_data.get('id')
if not path and not kodi_id:
log.info('Aborting playback report - no Kodi id or file for %s',
json_data)
return
# Plex id will NOT be set with direct paths
plex_id = state.PLEX_IDS.get(path)
try: try:
kodi_id = json_data['id'] plex_type = v.PLEX_TYPE_FROM_KODI_TYPE[kodi_type]
kodi_type = json_data['type']
except KeyError: except KeyError:
log.info('Aborting playback report - no Kodi id for %s', json_data)
return
# Get Plex' item id
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type)
try:
plex_id = plex_dbitem[0]
plex_type = plex_dbitem[2]
except TypeError:
# No plex id, hence item not in the library. E.g. clips
plex_id = None
plex_type = None plex_type = None
# No Kodi id returned by Kodi, even if there is one. Ex: Widgets
if plex_id and not kodi_id:
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byId(plex_id)
try:
kodi_id = plex_dbitem[0]
except TypeError:
kodi_id = None
# If using direct paths and starting playback from a widget
if not path.startswith('http'):
if not kodi_id:
kodi_id = kodiid_from_filename(path, kodi_type)
if not plex_id and kodi_id:
with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type)
try:
plex_id = plex_dbitem[0]
plex_type = plex_dbitem[2]
except TypeError:
# No plex id, hence item not in the library. E.g. clips
pass
state.PLAYER_STATES[playerid].update(js.get_player_props(playerid)) state.PLAYER_STATES[playerid].update(js.get_player_props(playerid))
state.PLAYER_STATES[playerid]['file'] = json_data['file'] state.PLAYER_STATES[playerid]['file'] = json_data['file']
state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id
state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type
state.PLAYER_STATES[playerid]['plex_id'] = plex_id state.PLAYER_STATES[playerid]['plex_id'] = plex_id
state.PLAYER_STATES[playerid]['plex_type'] = plex_type state.PLAYER_STATES[playerid]['plex_type'] = plex_type
log.debug('Set the player state %s', state.PLAYER_STATES[playerid])
# Set other stuff like volume # Set other stuff like volume
state.PLAYER_STATES[playerid]['volume'] = js.get_volume() state.PLAYER_STATES[playerid]['volume'] = js.get_volume()
state.PLAYER_STATES[playerid]['muted'] = js.get_muted() state.PLAYER_STATES[playerid]['muted'] = js.get_muted()
return log.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
# Switch subtitle tracks if applicable
subtitle = window('plex_%s.subtitle' % tryEncode(currentFile))
if window(tryEncode('plex_%s.playmethod' % currentFile)) \
== 'Transcode' and subtitle:
if window('plex_%s.subtitle' % currentFile) == 'None':
self.xbmcplayer.showSubtitles(False)
else:
self.xbmcplayer.setSubtitleStream(int(subtitle))
# Set some stuff if Kodi initiated playback
if ((settings('useDirectPaths') == "1" and not typus == "song")
or
(typus == "song" and settings('enableMusic') == "true")):
if self.StartDirectPath(plex_id,
typus,
tryEncode(currentFile)) is False:
log.error('Could not initiate monitoring; aborting')
return
# Save currentFile for cleanup later and to be able to access refs
window('plex_lastPlayedFiled', value=currentFile)
window('plex_currently_playing_itemid', value=plex_id)
window("plex_%s.itemid" % tryEncode(currentFile), value=plex_id)
log.info('Finish playback startup')
def StartDirectPath(self, plex_id, type, currentFile): def StartDirectPath(self, plex_id, type, currentFile):
""" """

View file

@ -1,56 +1,68 @@
import logging """
import re Manages getting playstate from Kodi and sending it to the PMS as well as
import threading subscribed Plex Companion clients.
"""
from xbmc import sleep from logging import getLogger
from re import sub
from threading import Thread, RLock
import downloadutils import downloadutils
from clientinfo import getXArgsDeviceInfo
from utils import window, kodi_time_to_millis from utils import window, kodi_time_to_millis
import PlexFunctions as pf
import state import state
import variables as v import variables as v
import json_rpc as js import json_rpc as js
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
# What is Companion controllable? # What is Companion controllable?
CONTROLLABLE = { CONTROLLABLE = {
v.PLEX_TYPE_PHOTO: 'skipPrevious,skipNext,stop', v.PLEX_TYPE_PHOTO: 'skipPrevious,skipNext,stop',
v.PLEX_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,' \ v.PLEX_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,'
'skipPrevious,skipNext,stepBack,stepForward', 'skipPrevious,skipNext,stepBack,stepForward',
v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,audioStream,subtitleStream,' \ v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,shuffle,audioStream,'
'seekTo,skipPrevious,skipNext,stepBack,stepForward' 'subtitleStream,seekTo,skipPrevious,skipNext,stepBack,stepForward'
} }
class SubscriptionManager: class SubscriptionManager:
"""
Manages Plex companion subscriptions
"""
def __init__(self, RequestMgr, player, mgr): def __init__(self, RequestMgr, player, mgr):
self.serverlist = [] self.serverlist = []
self.subscribers = {} self.subscribers = {}
self.info = {} self.info = {}
self.lastkey = "" self.containerKey = None
self.containerKey = "" self.ratingkey = None
self.ratingkey = ""
self.lastplayers = {}
self.lastinfo = {
'video': {},
'audio': {},
'picture': {}
}
self.server = "" self.server = ""
self.protocol = "http" self.protocol = "http"
self.port = "" self.port = ""
self.playerprops = {} # In order to be able to signal a stop at the end
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.last_params = {}
self.lastplayers = {}
self.doUtils = downloadutils.DownloadUtils
self.xbmcplayer = player self.xbmcplayer = player
self.playqueue = mgr.playqueue self.playqueue = mgr.playqueue
self.RequestMgr = RequestMgr self.RequestMgr = RequestMgr
@staticmethod
def _headers():
"""
Headers are different for Plex Companion!
"""
return {
'Content-type': 'text/plain',
'Connection': 'Keep-Alive',
'Keep-Alive': 'timeout=20',
'X-Plex-Client-Identifier': v.PKC_MACHINE_IDENTIFIER,
'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
'X-Plex-Protocol': "1.0"
}
def getServerByHost(self, host): def getServerByHost(self, host):
if len(self.serverlist) == 1: if len(self.serverlist) == 1:
return self.serverlist[0] return self.serverlist[0]
@ -61,64 +73,80 @@ class SubscriptionManager:
return {} return {}
def msg(self, players): def msg(self, players):
log.debug('players: %s', players) LOG.debug('players: %s', players)
msg = v.XML_HEADER msg = v.XML_HEADER
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"' msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
msg += ' machineIdentifier="%s">' % v.PKC_MACHINE_IDENTIFIER msg += ' machineIdentifier="%s">\n' % v.PKC_MACHINE_IDENTIFIER
msg += self.getTimelineXML(players.get(v.KODI_TYPE_AUDIO), msg += self.get_timeline_xml(players.get(v.KODI_TYPE_AUDIO),
v.PLEX_TYPE_AUDIO) v.PLEX_TYPE_AUDIO)
msg += self.getTimelineXML(players.get(v.KODI_TYPE_PHOTO), msg += self.get_timeline_xml(players.get(v.KODI_TYPE_PHOTO),
v.PLEX_TYPE_PHOTO) v.PLEX_TYPE_PHOTO)
msg += self.getTimelineXML(players.get(v.KODI_TYPE_VIDEO), msg += self.get_timeline_xml(players.get(v.KODI_TYPE_VIDEO),
v.PLEX_TYPE_VIDEO) v.PLEX_TYPE_VIDEO)
msg += "\n</MediaContainer>" msg += "</MediaContainer>"
log.debug('msg is: %s', msg) LOG.debug('msg is: %s', msg)
return msg return msg
def getTimelineXML(self, player, ptype): def _get_container_key(self, playerid):
if player is None: key = None
status = 'stopped' playlistid = state.PLAYER_STATES[playerid]['playlistid']
LOG.debug('type: %s, playlistid: %s', type(playlistid), playlistid)
if playlistid != -1:
# -1 is Kodi's answer if there is no playlist
try:
key = self.playqueue.playqueues[playlistid].id
except (KeyError, IndexError, TypeError):
pass
if key is not None:
key = '/playQueues/%s' % key
else: else:
playerid = player['playerid'] if state.PLAYER_STATES[playerid]['plex_id']:
info = state.PLAYER_STATES[playerid] key = '/library/metadata/%s' % \
# save this info off so the server update can use it too state.PLAYER_STATES[playerid]['plex_id']
# self.playerprops[playerid] = info return key
status = ("paused", "playing")[info['speed']]
ret = ('\n <Timeline state="%s" controllable="%s" type="%s" '
'itemType="%s"' % (status, CONTROLLABLE[ptype], ptype, ptype))
if player is None:
ret += ' />'
return ret
def get_timeline_xml(self, player, ptype):
if player is None:
return ' <Timeline state="stopped" controllable="%s" type="%s" ' \
'itemType="%s" />\n' % (CONTROLLABLE[ptype], ptype, ptype)
playerid = player['playerid']
info = state.PLAYER_STATES[playerid]
status = 'paused' if info['speed'] == '0' else 'playing'
ret = ' <Timeline state="%s"' % status
ret += ' controllable="%s"' % CONTROLLABLE[ptype]
ret += ' type="%s" itemType="%s"' % (ptype, ptype)
ret += ' time="%s"' % kodi_time_to_millis(info['time']) ret += ' time="%s"' % kodi_time_to_millis(info['time'])
ret += ' duration="%s"' % kodi_time_to_millis(info['totaltime']) ret += ' duration="%s"' % kodi_time_to_millis(info['totaltime'])
ret += ' shuffle="%s"' % ("0", "1")[info['shuffled']] shuffled = '1' if info['shuffled'] else '0'
ret += ' shuffle="%s"' % shuffled
ret += ' repeat="%s"' % v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']] ret += ' repeat="%s"' % v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']]
if ptype != v.KODI_TYPE_PHOTO: if ptype != v.KODI_TYPE_PHOTO:
ret += ' volume="%s"' % info['volume'] ret += ' volume="%s"' % info['volume']
ret += ' mute="%s"' % ("0", "1")[info['muted']] muted = '1' if info['muted'] is True else '0'
ret += ' mute="%s"' % muted
pbmc_server = window('pms_server') pbmc_server = window('pms_server')
server = self.getServerByHost(self.server) server = self.getServerByHost(self.server)
if pbmc_server: if pbmc_server:
(self.protocol, self.server, self.port) = pbmc_server.split(':') (self.protocol, self.server, self.port) = pbmc_server.split(':')
self.server = self.server.replace('/', '') self.server = self.server.replace('/', '')
if info['plex_id']: if info['plex_id']:
self.lastkey = "/library/metadata/%s" % info['plex_id']
self.ratingkey = info['plex_id'] self.ratingkey = info['plex_id']
ret += ' key="/library/metadata/%s"' % info['plex_id'] ret += ' key="/library/metadata/%s"' % info['plex_id']
ret += ' ratingKey="%s"' % info['plex_id'] ret += ' ratingKey="%s"' % info['plex_id']
# PlayQueue stuff # PlayQueue stuff
playqueue = self.playqueue.playqueues[playerid] playqueue = self.playqueue.playqueues[playerid]
pos = info['position'] key = self._get_container_key(playerid)
try: if key is not None and key.startswith('/playQueues'):
ret += ' playQueueItemID="%s"' % playqueue.items[pos].ID or 'null' self.containerKey = key
self.containerKey = "/playQueues/%s" % playqueue.ID or 'null'
ret += ' playQueueID="%s"' % playqueue.ID or 'null'
ret += ' playQueueVersion="%s"' % playqueue.version or 'null'
ret += ' containerKey="%s"' % self.containerKey ret += ' containerKey="%s"' % self.containerKey
pos = info['position']
ret += ' playQueueItemID="%s"' % playqueue.items[pos].id or 'null'
ret += ' playQueueID="%s"' % playqueue.id or 'null'
ret += ' playQueueVersion="%s"' % playqueue.version or 'null'
ret += ' guid="%s"' % playqueue.items[pos].guid or 'null' ret += ' guid="%s"' % playqueue.items[pos].guid or 'null'
except IndexError: elif key:
pass self.containerKey = key
ret += ' containerKey="%s"' % self.containerKey
ret += ' machineIdentifier="%s"' % server.get('uuid', "") ret += ' machineIdentifier="%s"' % server.get('uuid', "")
ret += ' protocol="%s"' % server.get('protocol', 'http') ret += ' protocol="%s"' % server.get('protocol', 'http')
ret += ' address="%s"' % server.get('server', self.server) ret += ' address="%s"' % server.get('server', self.server)
@ -132,8 +160,7 @@ class SubscriptionManager:
if ptype == 'video': if ptype == 'video':
ret += ' subtitleStreamID="-1"' ret += ' subtitleStreamID="-1"'
ret += ' audioStreamID="-1"' ret += ' audioStreamID="-1"'
ret += '/>\n'
ret += '/>'
return ret return ret
def updateCommandID(self, uuid, commandID): def updateCommandID(self, uuid, commandID):
@ -142,120 +169,92 @@ class SubscriptionManager:
def notify(self, event=False): def notify(self, event=False):
self.cleanup() self.cleanup()
# Don't tell anyone if we don't know a Plex ID and are still playing # Do we need a check to NOT tell about e.g. PVR/TV and Addon playback?
# (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
if (not window('plex_currently_playing_itemid')
and not self.lastplayers):
return True
players = js.get_players() players = js.get_players()
# fetch the message, subscribers or not, since the server # fetch the message, subscribers or not, since the server
# will need the info anyway # will need the info anyway
msg = self.msg(players) msg = self.msg(players)
if self.subscribers: if self.subscribers:
with threading.RLock(): with RLock():
for sub in self.subscribers.values(): for subscriber in self.subscribers.values():
sub.send_update(msg, len(players) == 0) subscriber.send_update(msg, len(players) == 0)
self.notifyServer(players) self.notifyServer(players)
self.lastplayers = players self.lastplayers = players
return True return True
def notifyServer(self, players): def notifyServer(self, players):
for typus, p in players.iteritems(): for typus, player in players.iteritems():
info = self.playerprops[p.get('playerid')] self._send_pms_notification(
self._sendNotification(info, int(p['playerid'])) player['playerid'], self._get_pms_params(player['playerid']))
self.lastinfo[typus] = info
# Cross the one of the list
try: try:
del self.lastplayers[typus] del self.lastplayers[typus]
except KeyError: except KeyError:
pass pass
# Process the players we have left (to signal a stop) # Process the players we have left (to signal a stop)
for typus, p in self.lastplayers.iteritems(): for typus, player in self.lastplayers.iteritems():
self.lastinfo[typus]['state'] = 'stopped' self.last_params['state'] = 'stopped'
self._sendNotification(self.lastinfo[typus], int(p['playerid'])) self._send_pms_notification(player['playerid'], self.last_params)
def _sendNotification(self, info, playerid): def _get_pms_params(self, playerid):
playqueue = self.playqueue.playqueues[playerid] info = state.PLAYER_STATES[playerid]
xargs = getXArgsDeviceInfo(include_token=False) status = 'paused' if info['speed'] == '0' else 'playing'
params = { params = {'state': status,
'containerKey': self.containerKey or "/library/metadata/900000", 'ratingKey': self.ratingkey,
'key': self.lastkey or "/library/metadata/900000", 'key': '/library/metadata/%s' % self.ratingkey,
'ratingKey': self.ratingkey or "900000", 'time': kodi_time_to_millis(info['time']),
'state': info['state'], 'duration': kodi_time_to_millis(info['totaltime'])
'time': info['time'],
'duration': info['duration']
} }
if self.containerKey:
params['containerKey'] = self.containerKey
if self.containerKey is not None and \
self.containerKey.startswith('/playQueues/'):
params['playQueueVersion'] = info['playQueueVersion']
params['playQueueItemID'] = info['playQueueItemID']
self.last_params = params
return params
def _send_pms_notification(self, playerid, params):
serv = self.getServerByHost(self.server)
xargs = self._headers()
playqueue = self.playqueue.playqueues[playerid]
if state.PLEX_TRANSIENT_TOKEN: if state.PLEX_TRANSIENT_TOKEN:
xargs['X-Plex-Token'] = state.PLEX_TRANSIENT_TOKEN xargs['X-Plex-Token'] = state.PLEX_TRANSIENT_TOKEN
elif playqueue.plex_transient_token: elif playqueue.plex_transient_token:
xargs['X-Plex-Token'] = playqueue.plex_transient_token xargs['X-Plex-Token'] = playqueue.plex_transient_token
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'), url = '%s://%s:%s/:/timeline' % (serv.get('protocol', 'http'),
serv.get('server', 'localhost'), serv.get('server', 'localhost'),
serv.get('port', '32400')) serv.get('port', '32400'))
self.doUtils(url, parameters=params, headerOptions=xargs) self.doUtils().downloadUrl(
log.debug("Sent server notification with parameters: %s to %s" url, parameters=params, headerOptions=xargs)
% (params, url)) # Save to be able to signal a stop at the end
LOG.debug("Sent server notification with parameters: %s to %s",
params, url)
def addSubscriber(self, protocol, host, port, uuid, commandID): def addSubscriber(self, protocol, host, port, uuid, commandID):
sub = Subscriber(protocol, subscriber = Subscriber(protocol,
host, host,
port, port,
uuid, uuid,
commandID, commandID,
self, self,
self.RequestMgr) self.RequestMgr)
with threading.RLock(): with RLock():
self.subscribers[sub.uuid] = sub self.subscribers[subscriber.uuid] = subscriber
return sub return subscriber
def removeSubscriber(self, uuid): def removeSubscriber(self, uuid):
with threading.RLock(): with RLock():
for sub in self.subscribers.values(): for subscriber in self.subscribers.values():
if sub.uuid == uuid or sub.host == uuid: if subscriber.uuid == uuid or subscriber.host == uuid:
sub.cleanup() subscriber.cleanup()
del self.subscribers[sub.uuid] del self.subscribers[subscriber.uuid]
def cleanup(self): def cleanup(self):
with threading.RLock(): with RLock():
for sub in self.subscribers.values(): for subscriber in self.subscribers.values():
if sub.age > 30: if subscriber.age > 30:
sub.cleanup() subscriber.cleanup()
del self.subscribers[sub.uuid] del self.subscribers[subscriber.uuid]
def getPlayerProperties(self, playerid):
# Get the playqueue
playqueue = self.playqueue.playqueues[playerid]
# get info from the player
props = state.PLAYER_STATES[playerid]
info = {
'time': kodi_time_to_millis(props['time']),
'duration': kodi_time_to_millis(props['totaltime']),
'state': ("paused", "playing")[int(props['speed'])],
'shuffle': ("0", "1")[props.get('shuffled', False)],
'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[props.get('repeat')]
}
pos = props['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:
info['itemType'] = props.get('type') or 'null'
# get the volume from the application
info['volume'] = js.get_volume()
info['mute'] = js.get_muted()
info['plex_transient_token'] = playqueue.plex_transient_token
return info
class Subscriber: class Subscriber:
@ -268,16 +267,13 @@ class Subscriber:
self.commandID = int(commandID) or 0 self.commandID = int(commandID) or 0
self.navlocationsent = False self.navlocationsent = False
self.age = 0 self.age = 0
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils
self.subMgr = subMgr self.subMgr = subMgr
self.RequestMgr = RequestMgr self.RequestMgr = RequestMgr
def __eq__(self, other): def __eq__(self, other):
return self.uuid == other.uuid return self.uuid == other.uuid
def tostr(self):
return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
def cleanup(self): def cleanup(self):
self.RequestMgr.closeConnection(self.protocol, self.host, self.port) self.RequestMgr.closeConnection(self.protocol, self.host, self.port)
@ -289,11 +285,12 @@ class Subscriber:
return True return True
else: else:
self.navlocationsent = True self.navlocationsent = True
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg) msg = sub(r"INSERTCOMMANDID", str(self.commandID), msg)
log.debug("sending xml to subscriber %s:\n%s" % (self.tostr(), msg)) LOG.debug("sending xml to subscriber uuid=%s,commandID=%i:\n%s",
self.uuid, self.commandID, msg)
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 = Thread(target=self.threadedSend, args=(url, msg))
t.start() t.start()
def threadedSend(self, url, msg): def threadedSend(self, url, msg):
@ -301,9 +298,8 @@ class Subscriber:
Threaded POST request, because they stall due to PMS response missing Threaded POST request, because they stall due to PMS response missing
the Content-Length header :-( the Content-Length header :-(
""" """
response = self.doUtils(url, response = self.doUtils().downloadUrl(url,
postBody=msg, postBody=msg,
action_type="POST") action_type="POST")
log.debug('response is: %s', response)
if response in [False, None, 401]: if response in [False, None, 401]:
self.subMgr.removeSubscriber(self.uuid) self.subMgr.removeSubscriber(self.uuid)

View file

@ -112,6 +112,9 @@ PLAYER_STATES = {
2: {}, 2: {},
3: {} 3: {}
} }
# Dict containing all filenames as keys with plex id as values - used for addon
# paths for playback (since we're not receiving a Kodi id)
PLEX_IDS = {}
PLAYED_INFO = {} PLAYED_INFO = {}
# Kodi webserver details # Kodi webserver details

View file

@ -199,6 +199,20 @@ KODITYPE_FROM_PLEXTYPE = {
'XXXXXXX': 'genre' 'XXXXXXX': 'genre'
} }
PLEX_TYPE_FROM_KODI_TYPE = {
KODI_TYPE_VIDEO: PLEX_TYPE_VIDEO,
KODI_TYPE_MOVIE: PLEX_TYPE_MOVIE,
KODI_TYPE_EPISODE: PLEX_TYPE_EPISODE,
KODI_TYPE_SEASON: PLEX_TYPE_SEASON,
KODI_TYPE_SHOW: PLEX_TYPE_SHOW,
KODI_TYPE_CLIP: PLEX_TYPE_CLIP,
KODI_TYPE_ARTIST: PLEX_TYPE_ARTIST,
KODI_TYPE_ALBUM: PLEX_TYPE_ALBUM,
KODI_TYPE_SONG: PLEX_TYPE_SONG,
KODI_TYPE_AUDIO: PLEX_TYPE_AUDIO,
KODI_TYPE_PHOTO: PLEX_TYPE_PHOTO
}
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = { KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = {
PLEX_TYPE_VIDEO: KODI_TYPE_VIDEO, PLEX_TYPE_VIDEO: KODI_TYPE_VIDEO,
PLEX_TYPE_MOVIE: KODI_TYPE_VIDEO, PLEX_TYPE_MOVIE: KODI_TYPE_VIDEO,