Major Plex Companion overhaul, part 2
This commit is contained in:
parent
cc347d5654
commit
9cac51d5c9
7 changed files with 258 additions and 217 deletions
|
@ -2690,7 +2690,8 @@ class API():
|
|||
plexitem = "plex_%s" % playurl
|
||||
window('%s.runtime' % plexitem, value=str(userdata['Runtime']))
|
||||
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']))
|
||||
|
||||
if itemtype == v.PLEX_TYPE_EPISODE:
|
||||
|
|
|
@ -362,6 +362,7 @@ def get_episodes(params):
|
|||
|
||||
def get_item(playerid):
|
||||
"""
|
||||
UNRELIABLE on playback startup! (as other JSON and Python Kodi functions)
|
||||
Returns the following for the currently playing item:
|
||||
{
|
||||
u'title': u'Okja',
|
||||
|
|
|
@ -808,7 +808,7 @@ class Kodidb_Functions():
|
|||
ids.append(row[0])
|
||||
return ids
|
||||
|
||||
def getIdFromFilename(self, filename, path):
|
||||
def video_id_from_filename(self, filename, path):
|
||||
"""
|
||||
Returns the tuple (itemId, type) where
|
||||
itemId: Kodi DB unique Id for either movie or episode
|
||||
|
@ -884,6 +884,34 @@ class Kodidb_Functions():
|
|||
return
|
||||
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):
|
||||
"""
|
||||
VIDEOS
|
||||
|
@ -1522,24 +1550,29 @@ class Kodidb_Functions():
|
|||
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
|
||||
said filename, or (None, None)
|
||||
Returns kodi_id if we have an item in the Kodi video or audio database with
|
||||
said path. Feed with the Kodi itemtype, e.v. 'movie', 'song'
|
||||
Returns None if not possible
|
||||
"""
|
||||
kodiid = None
|
||||
typus = None
|
||||
kodi_id = None
|
||||
try:
|
||||
filename = file.rsplit('/', 1)[1]
|
||||
path = file.rsplit('/', 1)[0] + '/'
|
||||
filename = path.rsplit('/', 1)[1]
|
||||
path = path.rsplit('/', 1)[0] + '/'
|
||||
except IndexError:
|
||||
filename = file.rsplit('\\', 1)[1]
|
||||
path = file.rsplit('\\', 1)[0] + '\\'
|
||||
log.debug('Trying to figure out playing item from filename: %s '
|
||||
'and path: %s' % (filename, path))
|
||||
with GetKodiDB('video') as kodi_db:
|
||||
try:
|
||||
kodiid, typus = kodi_db.getIdFromFilename(filename, path)
|
||||
except TypeError:
|
||||
log.info('No kodi video element found with filename %s' % filename)
|
||||
return (kodiid, typus)
|
||||
filename = path.rsplit('\\', 1)[1]
|
||||
path = path.rsplit('\\', 1)[0] + '\\'
|
||||
if kodi_type == v.KODI_TYPE_SONG:
|
||||
with GetKodiDB('music') as kodi_db:
|
||||
try:
|
||||
kodi_id, _ = kodi_db.music_id_from_filename(filename, path)
|
||||
except TypeError:
|
||||
log.info('No Kodi audio db element found for path %s', path)
|
||||
else:
|
||||
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
|
||||
|
|
|
@ -11,7 +11,7 @@ import plexdb_functions as plexdb
|
|||
from utils import window, settings, CatchExceptions, tryDecode, tryEncode, \
|
||||
plex_command
|
||||
from PlexFunctions import scrobble
|
||||
from kodidb_functions import get_kodiid_from_filename
|
||||
from kodidb_functions import kodiid_from_filename
|
||||
from PlexAPI import API
|
||||
import json_rpc as js
|
||||
import state
|
||||
|
@ -185,68 +185,61 @@ class KodiMonitor(Monitor):
|
|||
u'item': {u'type': u'movie', u'title': u''},
|
||||
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
|
||||
try:
|
||||
kodi_type = data['item']['type']
|
||||
playerid = data['player']['playerid']
|
||||
json_data = js.get_item(playerid)
|
||||
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
|
||||
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:
|
||||
kodi_id = json_data['id']
|
||||
kodi_type = json_data['type']
|
||||
plex_type = v.PLEX_TYPE_FROM_KODI_TYPE[kodi_type]
|
||||
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
|
||||
# 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]['file'] = json_data['file']
|
||||
state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id
|
||||
state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type
|
||||
state.PLAYER_STATES[playerid]['plex_id'] = plex_id
|
||||
state.PLAYER_STATES[playerid]['plex_type'] = plex_type
|
||||
log.debug('Set the player state %s', state.PLAYER_STATES[playerid])
|
||||
# Set other stuff like volume
|
||||
state.PLAYER_STATES[playerid]['volume'] = js.get_volume()
|
||||
state.PLAYER_STATES[playerid]['muted'] = js.get_muted()
|
||||
return
|
||||
|
||||
# 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')
|
||||
log.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
|
||||
|
||||
def StartDirectPath(self, plex_id, type, currentFile):
|
||||
"""
|
||||
|
|
|
@ -1,56 +1,68 @@
|
|||
import logging
|
||||
import re
|
||||
import threading
|
||||
|
||||
from xbmc import sleep
|
||||
"""
|
||||
Manages getting playstate from Kodi and sending it to the PMS as well as
|
||||
subscribed Plex Companion clients.
|
||||
"""
|
||||
from logging import getLogger
|
||||
from re import sub
|
||||
from threading import Thread, RLock
|
||||
|
||||
import downloadutils
|
||||
from clientinfo import getXArgsDeviceInfo
|
||||
from utils import window, kodi_time_to_millis
|
||||
import PlexFunctions as pf
|
||||
import state
|
||||
import variables as v
|
||||
import json_rpc as js
|
||||
|
||||
###############################################################################
|
||||
|
||||
log = logging.getLogger("PLEX."+__name__)
|
||||
LOG = getLogger("PLEX." + __name__)
|
||||
|
||||
###############################################################################
|
||||
|
||||
# What is Companion controllable?
|
||||
CONTROLLABLE = {
|
||||
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',
|
||||
v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,audioStream,subtitleStream,' \
|
||||
'seekTo,skipPrevious,skipNext,stepBack,stepForward'
|
||||
v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,shuffle,audioStream,'
|
||||
'subtitleStream,seekTo,skipPrevious,skipNext,stepBack,stepForward'
|
||||
}
|
||||
|
||||
class SubscriptionManager:
|
||||
"""
|
||||
Manages Plex companion subscriptions
|
||||
"""
|
||||
def __init__(self, RequestMgr, player, mgr):
|
||||
self.serverlist = []
|
||||
self.subscribers = {}
|
||||
self.info = {}
|
||||
self.lastkey = ""
|
||||
self.containerKey = ""
|
||||
self.ratingkey = ""
|
||||
self.lastplayers = {}
|
||||
self.lastinfo = {
|
||||
'video': {},
|
||||
'audio': {},
|
||||
'picture': {}
|
||||
}
|
||||
self.containerKey = None
|
||||
self.ratingkey = None
|
||||
self.server = ""
|
||||
self.protocol = "http"
|
||||
self.port = ""
|
||||
self.playerprops = {}
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
# In order to be able to signal a stop at the end
|
||||
self.last_params = {}
|
||||
self.lastplayers = {}
|
||||
|
||||
self.doUtils = downloadutils.DownloadUtils
|
||||
self.xbmcplayer = player
|
||||
self.playqueue = mgr.playqueue
|
||||
|
||||
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):
|
||||
if len(self.serverlist) == 1:
|
||||
return self.serverlist[0]
|
||||
|
@ -61,64 +73,80 @@ class SubscriptionManager:
|
|||
return {}
|
||||
|
||||
def msg(self, players):
|
||||
log.debug('players: %s', players)
|
||||
LOG.debug('players: %s', players)
|
||||
msg = v.XML_HEADER
|
||||
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
|
||||
msg += ' machineIdentifier="%s">' % v.PKC_MACHINE_IDENTIFIER
|
||||
msg += self.getTimelineXML(players.get(v.KODI_TYPE_AUDIO),
|
||||
v.PLEX_TYPE_AUDIO)
|
||||
msg += self.getTimelineXML(players.get(v.KODI_TYPE_PHOTO),
|
||||
v.PLEX_TYPE_PHOTO)
|
||||
msg += self.getTimelineXML(players.get(v.KODI_TYPE_VIDEO),
|
||||
v.PLEX_TYPE_VIDEO)
|
||||
msg += "\n</MediaContainer>"
|
||||
log.debug('msg is: %s', msg)
|
||||
msg += ' machineIdentifier="%s">\n' % v.PKC_MACHINE_IDENTIFIER
|
||||
msg += self.get_timeline_xml(players.get(v.KODI_TYPE_AUDIO),
|
||||
v.PLEX_TYPE_AUDIO)
|
||||
msg += self.get_timeline_xml(players.get(v.KODI_TYPE_PHOTO),
|
||||
v.PLEX_TYPE_PHOTO)
|
||||
msg += self.get_timeline_xml(players.get(v.KODI_TYPE_VIDEO),
|
||||
v.PLEX_TYPE_VIDEO)
|
||||
msg += "</MediaContainer>"
|
||||
LOG.debug('msg is: %s', msg)
|
||||
return msg
|
||||
|
||||
def getTimelineXML(self, player, ptype):
|
||||
if player is None:
|
||||
status = 'stopped'
|
||||
def _get_container_key(self, playerid):
|
||||
key = None
|
||||
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:
|
||||
playerid = player['playerid']
|
||||
info = state.PLAYER_STATES[playerid]
|
||||
# save this info off so the server update can use it too
|
||||
# self.playerprops[playerid] = info
|
||||
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
|
||||
if state.PLAYER_STATES[playerid]['plex_id']:
|
||||
key = '/library/metadata/%s' % \
|
||||
state.PLAYER_STATES[playerid]['plex_id']
|
||||
return key
|
||||
|
||||
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 += ' 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']]
|
||||
if ptype != v.KODI_TYPE_PHOTO:
|
||||
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')
|
||||
server = self.getServerByHost(self.server)
|
||||
if pbmc_server:
|
||||
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
||||
self.server = self.server.replace('/', '')
|
||||
if info['plex_id']:
|
||||
self.lastkey = "/library/metadata/%s" % info['plex_id']
|
||||
self.ratingkey = info['plex_id']
|
||||
ret += ' key="/library/metadata/%s"' % info['plex_id']
|
||||
ret += ' ratingKey="%s"' % info['plex_id']
|
||||
# PlayQueue stuff
|
||||
playqueue = self.playqueue.playqueues[playerid]
|
||||
pos = info['position']
|
||||
try:
|
||||
ret += ' playQueueItemID="%s"' % playqueue.items[pos].ID or 'null'
|
||||
self.containerKey = "/playQueues/%s" % playqueue.ID or 'null'
|
||||
ret += ' playQueueID="%s"' % playqueue.ID or 'null'
|
||||
ret += ' playQueueVersion="%s"' % playqueue.version or 'null'
|
||||
key = self._get_container_key(playerid)
|
||||
if key is not None and key.startswith('/playQueues'):
|
||||
self.containerKey = key
|
||||
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'
|
||||
except IndexError:
|
||||
pass
|
||||
elif key:
|
||||
self.containerKey = key
|
||||
ret += ' containerKey="%s"' % self.containerKey
|
||||
ret += ' machineIdentifier="%s"' % server.get('uuid', "")
|
||||
ret += ' protocol="%s"' % server.get('protocol', 'http')
|
||||
ret += ' address="%s"' % server.get('server', self.server)
|
||||
|
@ -132,8 +160,7 @@ class SubscriptionManager:
|
|||
if ptype == 'video':
|
||||
ret += ' subtitleStreamID="-1"'
|
||||
ret += ' audioStreamID="-1"'
|
||||
|
||||
ret += '/>'
|
||||
ret += '/>\n'
|
||||
return ret
|
||||
|
||||
def updateCommandID(self, uuid, commandID):
|
||||
|
@ -142,120 +169,92 @@ class SubscriptionManager:
|
|||
|
||||
def notify(self, event=False):
|
||||
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
|
||||
if (not window('plex_currently_playing_itemid')
|
||||
and not self.lastplayers):
|
||||
return True
|
||||
# Do we need a check to NOT tell about e.g. PVR/TV and Addon playback?
|
||||
players = js.get_players()
|
||||
# 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():
|
||||
sub.send_update(msg, len(players) == 0)
|
||||
with RLock():
|
||||
for subscriber in self.subscribers.values():
|
||||
subscriber.send_update(msg, len(players) == 0)
|
||||
self.notifyServer(players)
|
||||
self.lastplayers = players
|
||||
return True
|
||||
|
||||
def notifyServer(self, players):
|
||||
for typus, p in players.iteritems():
|
||||
info = self.playerprops[p.get('playerid')]
|
||||
self._sendNotification(info, int(p['playerid']))
|
||||
self.lastinfo[typus] = info
|
||||
# Cross the one of the list
|
||||
for typus, player in players.iteritems():
|
||||
self._send_pms_notification(
|
||||
player['playerid'], self._get_pms_params(player['playerid']))
|
||||
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], int(p['playerid']))
|
||||
for typus, player in self.lastplayers.iteritems():
|
||||
self.last_params['state'] = 'stopped'
|
||||
self._send_pms_notification(player['playerid'], self.last_params)
|
||||
|
||||
def _sendNotification(self, info, playerid):
|
||||
playqueue = self.playqueue.playqueues[playerid]
|
||||
xargs = getXArgsDeviceInfo(include_token=False)
|
||||
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']
|
||||
def _get_pms_params(self, playerid):
|
||||
info = state.PLAYER_STATES[playerid]
|
||||
status = 'paused' if info['speed'] == '0' else 'playing'
|
||||
params = {'state': status,
|
||||
'ratingKey': self.ratingkey,
|
||||
'key': '/library/metadata/%s' % self.ratingkey,
|
||||
'time': kodi_time_to_millis(info['time']),
|
||||
'duration': kodi_time_to_millis(info['totaltime'])
|
||||
}
|
||||
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:
|
||||
xargs['X-Plex-Token'] = state.PLEX_TRANSIENT_TOKEN
|
||||
elif 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'),
|
||||
serv.get('server', 'localhost'),
|
||||
serv.get('port', '32400'))
|
||||
self.doUtils(url, parameters=params, headerOptions=xargs)
|
||||
log.debug("Sent server notification with parameters: %s to %s"
|
||||
% (params, url))
|
||||
self.doUtils().downloadUrl(
|
||||
url, parameters=params, headerOptions=xargs)
|
||||
# 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):
|
||||
sub = Subscriber(protocol,
|
||||
host,
|
||||
port,
|
||||
uuid,
|
||||
commandID,
|
||||
self,
|
||||
self.RequestMgr)
|
||||
with threading.RLock():
|
||||
self.subscribers[sub.uuid] = sub
|
||||
return sub
|
||||
subscriber = Subscriber(protocol,
|
||||
host,
|
||||
port,
|
||||
uuid,
|
||||
commandID,
|
||||
self,
|
||||
self.RequestMgr)
|
||||
with RLock():
|
||||
self.subscribers[subscriber.uuid] = subscriber
|
||||
return subscriber
|
||||
|
||||
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]
|
||||
with RLock():
|
||||
for subscriber in self.subscribers.values():
|
||||
if subscriber.uuid == uuid or subscriber.host == uuid:
|
||||
subscriber.cleanup()
|
||||
del self.subscribers[subscriber.uuid]
|
||||
|
||||
def cleanup(self):
|
||||
with threading.RLock():
|
||||
for sub in self.subscribers.values():
|
||||
if sub.age > 30:
|
||||
sub.cleanup()
|
||||
del self.subscribers[sub.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
|
||||
with RLock():
|
||||
for subscriber in self.subscribers.values():
|
||||
if subscriber.age > 30:
|
||||
subscriber.cleanup()
|
||||
del self.subscribers[subscriber.uuid]
|
||||
|
||||
|
||||
class Subscriber:
|
||||
|
@ -268,16 +267,13 @@ class Subscriber:
|
|||
self.commandID = int(commandID) or 0
|
||||
self.navlocationsent = False
|
||||
self.age = 0
|
||||
self.doUtils = downloadutils.DownloadUtils().downloadUrl
|
||||
self.doUtils = downloadutils.DownloadUtils
|
||||
self.subMgr = subMgr
|
||||
self.RequestMgr = RequestMgr
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.uuid == other.uuid
|
||||
|
||||
def tostr(self):
|
||||
return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
|
||||
|
||||
def cleanup(self):
|
||||
self.RequestMgr.closeConnection(self.protocol, self.host, self.port)
|
||||
|
||||
|
@ -289,11 +285,12 @@ class Subscriber:
|
|||
return True
|
||||
else:
|
||||
self.navlocationsent = True
|
||||
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
|
||||
log.debug("sending xml to subscriber %s:\n%s" % (self.tostr(), msg))
|
||||
msg = sub(r"INSERTCOMMANDID", str(self.commandID), 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 \
|
||||
+ "/:/timeline"
|
||||
t = threading.Thread(target=self.threadedSend, args=(url, msg))
|
||||
t = Thread(target=self.threadedSend, args=(url, msg))
|
||||
t.start()
|
||||
|
||||
def threadedSend(self, url, msg):
|
||||
|
@ -301,9 +298,8 @@ class Subscriber:
|
|||
Threaded POST request, because they stall due to PMS response missing
|
||||
the Content-Length header :-(
|
||||
"""
|
||||
response = self.doUtils(url,
|
||||
postBody=msg,
|
||||
action_type="POST")
|
||||
log.debug('response is: %s', response)
|
||||
response = self.doUtils().downloadUrl(url,
|
||||
postBody=msg,
|
||||
action_type="POST")
|
||||
if response in [False, None, 401]:
|
||||
self.subMgr.removeSubscriber(self.uuid)
|
||||
|
|
|
@ -112,6 +112,9 @@ PLAYER_STATES = {
|
|||
2: {},
|
||||
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 = {}
|
||||
|
||||
# Kodi webserver details
|
||||
|
|
|
@ -199,6 +199,20 @@ KODITYPE_FROM_PLEXTYPE = {
|
|||
'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 = {
|
||||
PLEX_TYPE_VIDEO: KODI_TYPE_VIDEO,
|
||||
PLEX_TYPE_MOVIE: KODI_TYPE_VIDEO,
|
||||
|
|
Loading…
Reference in a new issue