Greatly simplify handling of PKC playqueues

This commit is contained in:
tomkat83 2018-01-06 15:19:12 +01:00
parent e0f1225c21
commit e17824609a
16 changed files with 259 additions and 381 deletions

View file

@ -3,7 +3,7 @@ The Plex Companion master python file
"""
from logging import getLogger
from threading import Thread
from Queue import Queue, Empty
from Queue import Empty
from socket import SHUT_RDWR
from urllib import urlencode
@ -19,6 +19,7 @@ import json_rpc as js
import player
import variables as v
import state
import playqueue as PQ
###############################################################################
@ -32,9 +33,9 @@ class PlexCompanion(Thread):
"""
Plex Companion monitoring class. Invoke only once
"""
def __init__(self, callback=None):
def __init__(self):
LOG.info("----===## Starting PlexCompanion ##===----")
self.mgr = callback
# Init Plex Companion queue
# Start GDM for server/client discovery
self.client = plexgdm.plexgdm()
self.client.clientDetails()
@ -42,7 +43,6 @@ class PlexCompanion(Thread):
# kodi player instance
self.player = player.PKC_Player()
self.httpd = False
self.queue = None
self.subscription_manager = None
Thread.__init__(self)
@ -57,8 +57,9 @@ class PlexCompanion(Thread):
api = API(xml[0])
if api.getType() == v.PLEX_TYPE_ALBUM:
LOG.debug('Plex music album detected')
self.mgr.playqueue.init_playqueue_from_plex_children(
api.getRatingKey(), transient_token=data.get('token'))
PQ.init_playqueue_from_plex_children(
api.getRatingKey(),
transient_token=data.get('token'))
else:
state.PLEX_TRANSIENT_TOKEN = data.get('token')
params = {
@ -91,7 +92,7 @@ class PlexCompanion(Thread):
# Get the playqueue ID
_, container_key, query = ParseContainerKey(data['containerKey'])
try:
playqueue = self.mgr.playqueue.get_playqueue_from_type(
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
except KeyError:
# E.g. Plex web does not supply the media type
@ -103,13 +104,13 @@ class PlexCompanion(Thread):
LOG.error('Could not download Plex metadata')
return
api = API(xml[0])
playqueue = self.mgr.playqueue.get_playqueue_from_type(
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
if container_key == playqueue.id:
LOG.info('Already know this playqueue - ignoring')
playqueue.transient_token = data.get('token')
else:
self.mgr.playqueue.update_playqueue_from_PMS(
PQ.update_playqueue_from_PMS(
playqueue,
playqueue_id=container_key,
repeat=query.get('repeat'),
@ -121,7 +122,7 @@ class PlexCompanion(Thread):
"""
Plex Companion client adjusted audio or subtitle stream
"""
playqueue = self.mgr.playqueue.get_playqueue_from_type(
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
pos = js.get_position(playqueue.playlistid)
if 'audioStreamID' in data:
@ -151,15 +152,13 @@ class PlexCompanion(Thread):
plex_type = get_plextype_from_xml(xml)
if plex_type is None:
return
playqueue = self.mgr.playqueue.get_playqueue_from_type(
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
playqueue.clear()
return
playqueue = self.mgr.playqueue.get_playqueue_from_type(
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
self.mgr.playqueue.update_playqueue_from_PMS(
playqueue,
data['playQueueID'])
PQ.update_playqueue_from_PMS(playqueue, data['playQueueID'])
def _process_tasks(self, task):
"""
@ -217,11 +216,9 @@ class PlexCompanion(Thread):
# Start up instances
request_mgr = httppersist.RequestMgr()
subscription_manager = subscribers.SubscriptionMgr(
request_mgr, self.player, self.mgr)
subscription_manager = subscribers.SubscriptionMgr(request_mgr,
self.player)
self.subscription_manager = subscription_manager
queue = Queue(maxsize=100)
self.queue = queue
if settings('plexCompanion') == 'true':
# Start up httpd
@ -231,7 +228,6 @@ class PlexCompanion(Thread):
httpd = listener.ThreadedHTTPServer(
client,
subscription_manager,
queue,
('', v.COMPANION_PORT),
listener.MyHandler)
httpd.timeout = 0.95
@ -290,13 +286,13 @@ class PlexCompanion(Thread):
LOG.warn(traceback.format_exc())
# See if there's anything we need to process
try:
task = queue.get(block=False)
task = state.COMPANION_QUEUE.get(block=False)
except Empty:
pass
else:
# Got instructions, process them
self._process_tasks(task)
queue.task_done()
state.COMPANION_QUEUE.task_done()
# Don't sleep
continue
sleep(50)

View file

@ -2,7 +2,6 @@
###############################################################################
import logging
from threading import Thread
from Queue import Queue
from xbmc import sleep
@ -10,8 +9,7 @@ from utils import window, thread_methods
import state
###############################################################################
log = logging.getLogger("PLEX."+__name__)
LOG = logging.getLogger("PLEX." + __name__)
###############################################################################
@ -23,16 +21,10 @@ class Monitor_Window(Thread):
Adjusts state.py accordingly
"""
# Borg - multiple instances, shared state
def __init__(self, callback=None):
self.mgr = callback
self.playback_queue = Queue()
Thread.__init__(self)
def run(self):
thread_stopped = self.thread_stopped
queue = self.playback_queue
log.info("----===## Starting Kodi_Play_Client ##===----")
queue = state.COMMAND_PIPELINE_QUEUE
LOG.info("----===## Starting Kodi_Play_Client ##===----")
while not thread_stopped():
if window('plex_command'):
value = window('plex_command')
@ -70,4 +62,4 @@ class Monitor_Window(Thread):
sleep(50)
# Put one last item into the queue to let playback_starter end
queue.put(None)
log.info("----===## Kodi_Play_Client stopped ##===----")
LOG.info("----===## Kodi_Play_Client stopped ##===----")

View file

@ -6,7 +6,7 @@ from logging import getLogger
from xbmc import Player
from variables import ALEXA_TO_COMPANION
from playqueue import Playqueue
import playqueue as PQ
from PlexFunctions import GetPlexKeyNumber
import json_rpc as js
import state
@ -29,9 +29,8 @@ def skip_to(params):
LOG.debug('Skipping to playQueueItemID %s, plex_id %s',
playqueue_item_id, plex_id)
found = True
playqueues = Playqueue()
for player in js.get_players().values():
playqueue = playqueues.playqueues[player['playerid']]
playqueue = PQ.PLAYQUEUES[player['playerid']]
for i, item in enumerate(playqueue.items):
if item.id == playqueue_item_id:
found = True
@ -57,7 +56,7 @@ def convert_alexa_to_companion(dictionary):
del dictionary[key]
def process_command(request_path, params, queue=None):
def process_command(request_path, params):
"""
queue: Queue() of PlexCompanion.py
"""
@ -67,12 +66,12 @@ def process_command(request_path, params, queue=None):
if request_path == 'player/playback/playMedia':
# We need to tell service.py
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
queue.put({
state.COMPANION_QUEUE.put({
'action': action,
'data': params
})
elif request_path == 'player/playback/refreshPlayQueue':
queue.put({
state.COMPANION_QUEUE.put({
'action': 'refreshPlayQueue',
'data': params
})
@ -114,7 +113,7 @@ def process_command(request_path, params, queue=None):
elif request_path == "player/navigation/back":
js.input_back()
elif request_path == "player/playback/setStreams":
queue.put({
state.COMPANION_QUEUE.put({
'action': 'setStreams',
'data': params
})

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
###############################################################################
from logging import getLogger
from Queue import Queue
import xbmc
import xbmcgui
@ -15,10 +15,11 @@ from PlexAPI import PlexAPI
from PlexFunctions import GetMachineIdentifier, get_PMS_settings
import state
from migration import check_migration
import playqueue as PQ
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
###############################################################################
@ -26,7 +27,7 @@ log = getLogger("PLEX."+__name__)
class InitialSetup():
def __init__(self):
log.debug('Entering initialsetup class')
LOG.debug('Entering initialsetup class')
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.plx = PlexAPI()
self.dialog = xbmcgui.Dialog()
@ -42,7 +43,7 @@ class InitialSetup():
# Token for the PMS, not plex.tv
self.pms_token = settings('accessToken')
if self.plexToken:
log.debug('Found a plex.tv token in the settings')
LOG.debug('Found a plex.tv token in the settings')
def PlexTVSignIn(self):
"""
@ -68,7 +69,7 @@ class InitialSetup():
chk = self.plx.CheckConnection('plex.tv', token=self.plexToken)
if chk in (401, 403):
# HTTP Error: unauthorized. Token is no longer valid
log.info('plex.tv connection returned HTTP %s' % str(chk))
LOG.info('plex.tv connection returned HTTP %s', str(chk))
# Delete token in the settings
settings('plexToken', value='')
settings('plexLogin', value='')
@ -77,12 +78,12 @@ class InitialSetup():
answer = self.PlexTVSignIn()
elif chk is False or chk >= 400:
# Problems connecting to plex.tv. Network or internet issue?
log.info('Problems connecting to plex.tv; connection returned '
'HTTP %s' % str(chk))
LOG.info('Problems connecting to plex.tv; connection returned '
'HTTP %s', str(chk))
self.dialog.ok(lang(29999), lang(39010))
answer = False
else:
log.info('plex.tv connection with token successful')
LOG.info('plex.tv connection with token successful')
settings('plex_status', value=lang(39227))
# Refresh the info from Plex.tv
xml = self.doUtils('https://plex.tv/users/account',
@ -91,14 +92,14 @@ class InitialSetup():
try:
self.plexLogin = xml.attrib['title']
except (AttributeError, KeyError):
log.error('Failed to update Plex info from plex.tv')
LOG.error('Failed to update Plex info from plex.tv')
else:
settings('plexLogin', value=self.plexLogin)
home = 'true' if xml.attrib.get('home') == '1' else 'false'
settings('plexhome', value=home)
settings('plexAvatar', value=xml.attrib.get('thumb'))
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1'))
log.info('Updated Plex info from plex.tv')
LOG.info('Updated Plex info from plex.tv')
return answer
def CheckPMS(self):
@ -114,24 +115,24 @@ class InitialSetup():
answer = True
chk = self.plx.CheckConnection(self.server, verifySSL=False)
if chk is False:
log.warn('Could not reach PMS %s' % self.server)
LOG.warn('Could not reach PMS %s', self.server)
answer = False
if answer is True and not self.serverid:
log.info('No PMS machineIdentifier found for %s. Trying to '
'get the PMS unique ID' % self.server)
LOG.info('No PMS machineIdentifier found for %s. Trying to '
'get the PMS unique ID', self.server)
self.serverid = GetMachineIdentifier(self.server)
if self.serverid is None:
log.warn('Could not retrieve machineIdentifier')
LOG.warn('Could not retrieve machineIdentifier')
answer = False
else:
settings('plex_machineIdentifier', value=self.serverid)
elif answer is True:
tempServerid = GetMachineIdentifier(self.server)
if tempServerid != self.serverid:
log.warn('The current PMS %s was expected to have a '
LOG.warn('The current PMS %s was expected to have a '
'unique machineIdentifier of %s. But we got '
'%s. Pick a new server to be sure'
% (self.server, self.serverid, tempServerid))
'%s. Pick a new server to be sure',
self.server, self.serverid, tempServerid)
answer = False
return answer
@ -142,7 +143,7 @@ class InitialSetup():
self.plx.discoverPMS(xbmc.getIPAddress(),
plexToken=self.plexToken)
serverlist = self.plx.returnServerList(self.plx.g_PMS)
log.debug('PMS serverlist: %s' % serverlist)
LOG.debug('PMS serverlist: %s', serverlist)
return serverlist
def _checkServerCon(self, server):
@ -209,7 +210,7 @@ class InitialSetup():
try:
xml.attrib
except AttributeError:
log.error('Could not get PMS settings for %s' % url)
LOG.error('Could not get PMS settings for %s', url)
return
for entry in xml:
if entry.attrib.get('id', '') == 'allowMediaDeletion':
@ -236,9 +237,9 @@ class InitialSetup():
server = item
if server is None:
name = settings('plex_servername')
log.warn('The PMS you have used before with a unique '
LOG.warn('The PMS you have used before with a unique '
'machineIdentifier of %s and name %s is '
'offline' % (self.serverid, name))
'offline', self.serverid, name)
return
chk = self._checkServerCon(server)
if chk == 504 and httpsUpdated is False:
@ -247,8 +248,8 @@ class InitialSetup():
httpsUpdated = True
continue
if chk == 401:
log.warn('Not yet authorized for Plex server %s'
% server['name'])
LOG.warn('Not yet authorized for Plex server %s',
server['name'])
if self.CheckPlexTVSignIn() is True:
if checkedPlexTV is False:
# Try again
@ -256,7 +257,7 @@ class InitialSetup():
httpsUpdated = False
continue
else:
log.warn('Not authorized even though we are signed '
LOG.warn('Not authorized even though we are signed '
' in to plex.tv correctly')
self.dialog.ok(lang(29999), '%s %s'
% (lang(39214),
@ -266,11 +267,11 @@ class InitialSetup():
return
# Problems connecting
elif chk >= 400 or chk is False:
log.warn('Problems connecting to server %s. chk is %s'
% (server['name'], chk))
LOG.warn('Problems connecting to server %s. chk is %s',
server['name'], chk)
return
log.info('We found a server to automatically connect to: %s'
% server['name'])
LOG.info('We found a server to automatically connect to: %s',
server['name'])
return server
def _UserPickPMS(self):
@ -285,7 +286,7 @@ class InitialSetup():
serverlist = self._getServerList()
# Exit if no servers found
if len(serverlist) == 0:
log.warn('No plex media servers found!')
LOG.warn('No plex media servers found!')
self.dialog.ok(lang(29999), lang(39011))
return
# Get a nicer list
@ -322,8 +323,8 @@ class InitialSetup():
continue
httpsUpdated = False
if chk == 401:
log.warn('Not yet authorized for Plex server %s'
% server['name'])
LOG.warn('Not yet authorized for Plex server %s',
server['name'])
# Please sign in to plex.tv
self.dialog.ok(lang(29999),
lang(39013) + server['name'],
@ -370,7 +371,7 @@ class InitialSetup():
scheme = server['scheme']
settings('ipaddress', server['ip'])
settings('port', server['port'])
log.debug("Setting SSL verify to false, because server is "
LOG.debug("Setting SSL verify to false, because server is "
"local")
settings('sslverify', 'false')
else:
@ -378,7 +379,7 @@ class InitialSetup():
scheme = baseURL[0]
settings('ipaddress', baseURL[1].replace('//', ''))
settings('port', baseURL[2])
log.debug("Setting SSL verify to true, because server is not "
LOG.debug("Setting SSL verify to true, because server is not "
"local")
settings('sslverify', 'true')
@ -387,10 +388,10 @@ class InitialSetup():
else:
settings('https', 'false')
# And finally do some logging
log.debug("Writing to Kodi user settings file")
log.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
% (server['machineIdentifier'], server['ip'],
server['port'], server['scheme']))
LOG.debug("Writing to Kodi user settings file")
LOG.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s ",
server['machineIdentifier'], server['ip'], server['port'],
server['scheme'])
def setup(self):
"""
@ -399,14 +400,14 @@ class InitialSetup():
Check server, user, direct paths, music, direct stream if not direct
path.
"""
log.info("Initial setup called.")
LOG.info("Initial setup called.")
dialog = self.dialog
# Get current Kodi video cache setting
cache, _ = advancedsettings_xml(['cache', 'memorysize'])
# Kodi default cache if no setting is set
cache = str(cache.text) if cache is not None else '20971520'
log.info('Current Kodi video memory cache in bytes: %s', cache)
LOG.info('Current Kodi video memory cache in bytes: %s', cache)
settings('kodi_video_cache', value=cache)
# Disable foreground "Loading media information from files"
# (still used by Kodi, even though the Wiki says otherwise)
@ -420,13 +421,20 @@ class InitialSetup():
if self.plexToken and self.myplexlogin:
self.CheckPlexTVSignIn()
# Initialize the PKC playqueues
PQ.init_playqueues()
# Init some Queues()
state.COMMAND_PIPELINE_QUEUE = Queue()
state.COMPANION_QUEUE = Queue(maxsize=100)
state.WEBSOCKET_QUEUE = Queue()
# If a Plex server IP has already been set
# return only if the right machine identifier is found
if self.server:
log.info("PMS is already set: %s. Checking now..." % self.server)
LOG.info("PMS is already set: %s. Checking now...", self.server)
if self.CheckPMS():
log.info("Using PMS %s with machineIdentifier %s"
% (self.server, self.serverid))
LOG.info("Using PMS %s with machineIdentifier %s",
self.server, self.serverid)
self._write_PMS_settings(self.server, self.pms_token)
return
@ -452,19 +460,19 @@ class InitialSetup():
lang(39028),
nolabel="Addon (Default)",
yeslabel="Native (Direct Paths)"):
log.debug("User opted to use direct paths.")
LOG.debug("User opted to use direct paths.")
settings('useDirectPaths', value="1")
state.DIRECT_PATHS = True
# Are you on a system where you would like to replace paths
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
if dialog.yesno(heading=lang(29999), line1=lang(39033)):
log.debug("User chose to replace paths with smb")
LOG.debug("User chose to replace paths with smb")
else:
settings('replaceSMB', value="false")
# complete replace all original Plex library paths with custom SMB
if dialog.yesno(heading=lang(29999), line1=lang(39043)):
log.debug("User chose custom smb paths")
LOG.debug("User chose custom smb paths")
settings('remapSMB', value="true")
# Please enter your custom smb paths in the settings under
# "Sync Options" and then restart Kodi
@ -475,22 +483,22 @@ class InitialSetup():
if dialog.yesno(heading=lang(29999),
line1=lang(39029),
line2=lang(39030)):
log.debug("Presenting network credentials dialog.")
LOG.debug("Presenting network credentials dialog.")
from utils import passwordsXML
passwordsXML()
# Disable Plex music?
if dialog.yesno(heading=lang(29999), line1=lang(39016)):
log.debug("User opted to disable Plex music library.")
LOG.debug("User opted to disable Plex music library.")
settings('enableMusic', value="false")
# Download additional art from FanArtTV
if dialog.yesno(heading=lang(29999), line1=lang(39061)):
log.debug("User opted to use FanArtTV")
LOG.debug("User opted to use FanArtTV")
settings('FanartTV', value="true")
# Do you want to replace your custom user ratings with an indicator of
# how many versions of a media item you posses?
if dialog.yesno(heading=lang(29999), line1=lang(39718)):
log.debug("User opted to replace user ratings with version number")
LOG.debug("User opted to replace user ratings with version number")
settings('indicate_media_versions', value="true")
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and

View file

@ -16,6 +16,7 @@ import json_rpc as js
import playlist_func as PL
import state
import variables as v
import playqueue as PQ
###############################################################################
@ -51,10 +52,8 @@ class KodiMonitor(Monitor):
"""
PKC implementation of the Kodi Monitor class. Invoke only once.
"""
def __init__(self, callback):
self.mgr = callback
def __init__(self):
self.xbmcplayer = Player()
self.playqueue = self.mgr.playqueue
Monitor.__init__(self)
LOG.info("Kodi monitor started.")
@ -198,7 +197,7 @@ class KodiMonitor(Monitor):
}
Will NOT be called if playback initiated by Kodi widgets
"""
playqueue = self.playqueue.playqueues[data['playlistid']]
playqueue = PQ.PLAYQUEUES[data['playlistid']]
# Did PKC cause this add? Then lets not do anything
if playqueue.is_kodi_onadd() is False:
LOG.debug('PKC added this item to the playqueue - ignoring')
@ -228,7 +227,7 @@ class KodiMonitor(Monitor):
u'position': 0
}
"""
playqueue = self.playqueue.playqueues[data['playlistid']]
playqueue = PQ.PLAYQUEUES[data['playlistid']]
# Did PKC cause this add? Then lets not do anything
if playqueue.is_kodi_onremove() is False:
LOG.debug('PKC removed this item already from playqueue - ignoring')
@ -249,7 +248,7 @@ class KodiMonitor(Monitor):
u'playlistid': 1,
}
"""
playqueue = self.playqueue.playqueues[data['playlistid']]
playqueue = PQ.PLAYQUEUES[data['playlistid']]
if playqueue.is_kodi_onclear() is False:
LOG.debug('PKC already cleared the playqueue - ignoring')
return
@ -317,7 +316,7 @@ class KodiMonitor(Monitor):
LOG.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
# Check whether we need to init our playqueues (e.g. direct play)
init = False
playqueue = self.playqueue.playqueues[playerid]
playqueue = PQ.PLAYQUEUES[playerid]
try:
playqueue.items[info['position']]
except IndexError:
@ -340,7 +339,7 @@ class KodiMonitor(Monitor):
container_key = None
if info['playlistid'] != -1:
# -1 is Kodi's answer if there is no playlist
container_key = self.playqueue.playqueues[playerid].id
container_key = PQ.PLAYQUEUES[playerid].id
if container_key is not None:
container_key = '/playQueues/%s' % container_key
elif plex_id is not None:

View file

@ -43,9 +43,7 @@ log = getLogger("PLEX."+__name__)
class LibrarySync(Thread):
"""
"""
def __init__(self, callback=None):
self.mgr = callback
def __init__(self):
self.itemsToProcess = []
self.sessionKeys = []
self.fanartqueue = Queue.Queue()
@ -1527,7 +1525,7 @@ class LibrarySync(Thread):
oneDay = 60*60*24
# Link to Websocket queue
queue = self.mgr.ws.queue
queue = state.WEBSOCKET_QUEUE
startupComplete = False
self.views = []

View file

@ -12,7 +12,7 @@ from playbackutils import PlaybackUtils
from utils import window
from PlexFunctions import GetPlexMetadata
from PlexAPI import API
from playqueue import LOCK
import playqueue as PQ
import variables as v
from downloadutils import DownloadUtils
from PKC_listitem import convert_PKC_to_listitem
@ -21,7 +21,8 @@ from context_entry import ContextMenu
import state
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
###############################################################################
@ -30,26 +31,21 @@ class Playback_Starter(Thread):
"""
Processes new plays
"""
def __init__(self, callback=None):
self.mgr = callback
self.playqueue = self.mgr.playqueue
Thread.__init__(self)
def process_play(self, plex_id, kodi_id=None):
"""
Processes Kodi playback init for ONE item
"""
log.info("Process_play called with plex_id %s, kodi_id %s"
% (plex_id, kodi_id))
LOG.info("Process_play called with plex_id %s, kodi_id %s",
plex_id, kodi_id)
if not state.AUTHENTICATED:
log.error('Not yet authenticated for PMS, abort starting playback')
LOG.error('Not yet authenticated for PMS, abort starting playback')
# Todo: Warn user with dialog
return
xml = GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (IndexError, TypeError, AttributeError):
log.error('Could not get a PMS xml for plex id %s' % plex_id)
LOG.error('Could not get a PMS xml for plex id %s', plex_id)
return
api = API(xml[0])
if api.getType() == v.PLEX_TYPE_PHOTO:
@ -60,15 +56,14 @@ class Playback_Starter(Thread):
result.listitem = listitem
else:
# Video and Music
playqueue = self.playqueue.get_playqueue_from_type(
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
with LOCK:
with PQ.LOCK:
result = PlaybackUtils(xml, playqueue).play(
plex_id,
kodi_id,
xml.attrib.get('librarySectionUUID'))
log.info('Done process_play, playqueues: %s'
% self.playqueue.playqueues)
LOG.info('Done process_play, playqueues: %s', PQ.PLAYQUEUES)
return result
def process_plex_node(self, url, viewOffset, directplay=False,
@ -77,8 +72,8 @@ class Playback_Starter(Thread):
Called for Plex directories or redirect for playback (e.g. trailers,
clips, watchlater)
"""
log.info('process_plex_node called with url: %s, viewOffset: %s'
% (url, viewOffset))
LOG.info('process_plex_node called with url: %s, viewOffset: %s',
url, viewOffset)
# Plex redirect, e.g. watch later. Need to get actual URLs
if url.startswith('http') or url.startswith('{server}'):
xml = DownloadUtils().downloadUrl(url)
@ -87,7 +82,7 @@ class Playback_Starter(Thread):
try:
xml[0].attrib
except:
log.error('Could not download PMS metadata')
LOG.error('Could not download PMS metadata')
return
if viewOffset != '0':
try:
@ -96,7 +91,7 @@ class Playback_Starter(Thread):
pass
else:
window('plex_customplaylist.seektime', value=str(viewOffset))
log.info('Set resume point to %s' % str(viewOffset))
LOG.info('Set resume point to %s', viewOffset)
api = API(xml[0])
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]
if node is True:
@ -110,10 +105,10 @@ class Playback_Starter(Thread):
try:
kodi_id = plexdb_item[0]
except TypeError:
log.info('Couldnt find item %s in Kodi db'
% api.getRatingKey())
playqueue = self.playqueue.get_playqueue_from_type(typus)
with LOCK:
LOG.info('Couldnt find item %s in Kodi db',
api.getRatingKey())
playqueue = PQ.get_playqueue_from_type(typus)
with PQ.LOCK:
result = PlaybackUtils(xml, playqueue).play(
plex_id,
kodi_id=kodi_id,
@ -130,7 +125,7 @@ class Playback_Starter(Thread):
_, params = item.split('?', 1)
params = dict(parse_qsl(params))
mode = params.get('mode')
log.debug('Received mode: %s, params: %s' % (mode, params))
LOG.debug('Received mode: %s, params: %s', mode, params)
try:
if mode == 'play':
result = self.process_play(params.get('id'),
@ -147,18 +142,18 @@ class Playback_Starter(Thread):
ContextMenu()
result = Playback_Successful()
except:
log.error('Error encountered for mode %s, params %s'
% (mode, params))
LOG.error('Error encountered for mode %s, params %s',
mode, params)
import traceback
log.error(traceback.format_exc())
LOG.error(traceback.format_exc())
# Let default.py know!
pickle_me(None)
else:
pickle_me(result)
def run(self):
queue = self.mgr.command_pipeline.playback_queue
log.info("----===## Starting Playback_Starter ##===----")
queue = state.COMMAND_PIPELINE_QUEUE
LOG.info("----===## Starting Playback_Starter ##===----")
while True:
item = queue.get()
if item is None:
@ -167,4 +162,4 @@ class Playback_Starter(Thread):
else:
self.triage(item)
queue.task_done()
log.info("----===## Playback_Starter stopped ##===----")
LOG.info("----===## Playback_Starter stopped ##===----")

View file

@ -97,6 +97,7 @@ class PlaybackUtils():
startPos = max(playqueue.kodi_pl.getposition(), 0)
self.currentPosition = startPos
propertiesPlayback = window('plex_playbackProps') == "true"
introsPlaylist = False
dummyPlaylist = False
@ -113,8 +114,8 @@ class PlaybackUtils():
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not state.PLAYBACK_SETUP_DONE:
state.PLAYBACK_SETUP_DONE = True
if not propertiesPlayback:
window('plex_playbackProps', value="true")
LOG.info("Setting up properties in playlist.")
# Where will the player need to start?
# Do we need to get trailers?
@ -139,7 +140,6 @@ class PlaybackUtils():
get_playlist_details_from_xml(playqueue, xml=xml)
except KeyError:
return
if (not homeScreen and not seektime and sizePlaylist < 2 and
window('plex_customplaylist') != "true" and
not contextmenu_play):
@ -198,7 +198,7 @@ class PlaybackUtils():
api.set_listitem_artwork(listitem)
add_listitem_to_Kodi_playlist(
playqueue,
self.currentPosition,
self.currentPosition+1,
convert_PKC_to_listitem(listitem),
file=playurl,
kodi_item={'id': kodi_id, 'type': kodi_type})
@ -206,7 +206,7 @@ class PlaybackUtils():
# Full metadata$
add_item_to_kodi_playlist(
playqueue,
self.currentPosition,
self.currentPosition+1,
kodi_id,
kodi_type)
self.currentPosition += 1
@ -230,9 +230,9 @@ class PlaybackUtils():
return result
# We just skipped adding properties. Reset flag for next time.
elif state.PLAYBACK_SETUP_DONE:
elif propertiesPlayback:
LOG.debug("Resetting properties playback flag.")
state.PLAYBACK_SETUP_DONE = False
window('plex_playbackProps', clear=True)
# SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
@ -275,7 +275,7 @@ class PlaybackUtils():
Play all items contained in the xml passed in. Called by Plex Companion
"""
LOG.info("Playbackutils play_all called")
state.PLAYBACK_SETUP_DONE = True
window('plex_playbackProps', value="true")
self.currentPosition = 0
for item in self.xml:
api = API(item)

View file

@ -268,7 +268,6 @@ class PKC_Player(Player):
# We might have saved a transient token from a user flinging media via
# Companion (if we could not use the playqueue to store the token)
state.PLEX_TRANSIENT_TOKEN = None
state.PLAYBACK_SETUP_DONE = False
LOG.debug("Cleared playlist properties.")
def onPlayBackEnded(self):

View file

@ -4,9 +4,9 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
from logging import getLogger
from threading import RLock, Thread
from xbmc import sleep, Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
from xbmc import Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
from utils import window, thread_methods
from utils import window
import playlist_func as PL
from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren
from PlexAPI import API
@ -20,64 +20,59 @@ LOG = getLogger("PLEX." + __name__)
# lock used for playqueue manipulations
LOCK = RLock()
PLUGIN = 'plugin://%s' % v.ADDON_ID
# Our PKC playqueues (3 instances of Playqueue_Object())
PLAYQUEUES = []
###############################################################################
@thread_methods(add_suspends=['PMS_STATUS'])
class Playqueue(Thread):
def init_playqueues():
"""
Monitors Kodi's playqueues for changes on the Kodi side
Call this once on startup to initialize the PKC playqueue objects in
the list PLAYQUEUES
"""
# Borg - multiple instances, shared state
__shared_state = {}
playqueues = None
def __init__(self, callback=None):
self.__dict__ = self.__shared_state
if self.playqueues is not None:
LOG.debug('Playqueue thread has already been initialized')
Thread.__init__(self)
if PLAYQUEUES:
LOG.debug('Playqueues have already been initialized')
return
self.mgr = callback
# Initialize Kodi playqueues
with LOCK:
self.playqueues = []
for i in (0, 1, 2):
# Just in case the Kodi response is not sorted correctly
for queue in js.get_playlists():
if queue['playlistid'] != i:
continue
playqueue = PL.Playqueue_Object()
playqueue.playlistid = queue['playlistid']
playqueue.playlistid = i
playqueue.type = queue['type']
# Initialize each Kodi playlist
if playqueue.type == 'audio':
if playqueue.type == v.KODI_TYPE_AUDIO:
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
elif playqueue.type == 'video':
elif playqueue.type == v.KODI_TYPE_VIDEO:
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
else:
# Currently, only video or audio playqueues available
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
# Overwrite 'picture' with 'photo'
playqueue.type = v.KODI_TYPE_PHOTO
self.playqueues.append(playqueue)
# sort the list by their playlistid, just in case
self.playqueues = sorted(
self.playqueues, key=lambda i: i.playlistid)
LOG.debug('Initialized the Kodi play queues: %s', self.playqueues)
Thread.__init__(self)
PLAYQUEUES.append(playqueue)
LOG.debug('Initialized the Kodi playqueues: %s', PLAYQUEUES)
def get_playqueue_from_type(self, typus):
def get_playqueue_from_type(typus):
"""
Returns the playqueue according to the typus ('video', 'audio',
'picture') passed in
"""
with LOCK:
for playqueue in self.playqueues:
for playqueue in PLAYQUEUES:
if playqueue.type == typus:
break
else:
raise ValueError('Wrong playlist type passed in: %s' % typus)
return playqueue
def init_playqueue_from_plex_children(self, plex_id, transient_token=None):
def init_playqueue_from_plex_children(plex_id, transient_token=None):
"""
Init a new playqueue e.g. from an album. Alexa does this
@ -89,7 +84,7 @@ class Playqueue(Thread):
except (TypeError, IndexError, AttributeError):
LOG.error('Could not download the PMS xml for %s', plex_id)
return
playqueue = self.get_playqueue_from_type(
playqueue = get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
playqueue.clear()
for i, child in enumerate(xml):
@ -100,8 +95,8 @@ class Playqueue(Thread):
Player().play(playqueue.kodi_pl, None, False, 0)
return playqueue
def update_playqueue_from_PMS(self,
playqueue,
def update_playqueue_from_PMS(playqueue,
playqueue_id=None,
repeat=None,
offset=None,
@ -140,7 +135,7 @@ class Playqueue(Thread):
startpos = 0
# Start playback. Player does not return in time
LOG.debug('Playqueues after Plex Companion update are now: %s',
self.playqueues)
PLAYQUEUES)
thread = Thread(target=Player().play,
args=(playqueue.kodi_pl,
None,
@ -148,90 +143,3 @@ class Playqueue(Thread):
startpos))
thread.setDaemon(True)
thread.start()
def _compare_playqueues(self, playqueue, new):
"""
Used to poll the Kodi playqueue and update the Plex playqueue if needed
"""
old = list(playqueue.items)
index = list(range(0, len(old)))
LOG.debug('Comparing new Kodi playqueue %s with our play queue %s',
new, old)
if self.thread_stopped():
# Chances are that we got an empty Kodi playlist due to
# Kodi exit
return
for i, new_item in enumerate(new):
if (new_item['file'].startswith('plugin://') and
not new_item['file'].startswith(PLUGIN)):
# Ignore new media added by other addons
continue
for j, old_item in enumerate(old):
try:
if (old_item.file.startswith('plugin://') and
not old_item['file'].startswith(PLUGIN)):
# Ignore media by other addons
continue
except (TypeError, AttributeError):
# were not passed a filename; ignore
pass
if new_item.get('id') is None:
identical = old_item.file == new_item['file']
else:
identical = (old_item.kodi_id == new_item['id'] and
old_item.kodi_type == new_item['type'])
if j == 0 and identical:
del old[j], index[j]
break
elif identical:
LOG.debug('Detected playqueue item %s moved to position %s',
i+j, i)
PL.move_playlist_item(playqueue, i + j, i)
del old[j], index[j]
break
else:
LOG.debug('Detected new Kodi element at position %s: %s ',
i, new_item)
if playqueue.id is None:
PL.init_Plex_playlist(playqueue,
kodi_item=new_item)
else:
PL.add_item_to_PMS_playlist(playqueue,
i,
kodi_item=new_item)
for j in range(i, len(index)):
index[j] += 1
for i in reversed(index):
LOG.debug('Detected deletion of playqueue element at pos %s', i)
PL.delete_playlist_item_from_PMS(playqueue, i)
LOG.debug('Done comparing playqueues')
def run(self):
thread_stopped = self.thread_stopped
thread_suspended = self.thread_suspended
LOG.info("----===## Starting PlayQueue client ##===----")
# Initialize the playqueues, if Kodi already got items in them
for playqueue in self.playqueues:
for i, item in enumerate(js.playlist_get_items(playqueue.id)):
if i == 0:
PL.init_Plex_playlist(playqueue, kodi_item=item)
else:
PL.add_item_to_PMS_playlist(playqueue, i, kodi_item=item)
while not thread_stopped():
while thread_suspended():
if thread_stopped():
break
sleep(1000)
# with LOCK:
# for playqueue in self.playqueues:
# kodi_playqueue = js.playlist_get_items(playqueue.id)
# if playqueue.old_kodi_pl != kodi_playqueue:
# # compare old and new playqueue
# self._compare_playqueues(playqueue, kodi_playqueue)
# playqueue.old_kodi_pl = list(kodi_playqueue)
# # Still sleep a bit so Kodi does not become
# # unresponsive
# sleep(10)
# continue
sleep(200)
LOG.info("----===## PlayQueue client stopped ##===----")

View file

@ -9,7 +9,6 @@ from urlparse import urlparse, parse_qs
from xbmc import sleep
from companion import process_command
from utils import window
import json_rpc as js
from clientinfo import getXArgsDeviceInfo
import variables as v
@ -154,7 +153,7 @@ class MyHandler(BaseHTTPRequestHandler):
sub_mgr.remove_subscriber(uuid)
else:
# Throw it to companion.py
process_command(request_path, params, self.server.queue)
process_command(request_path, params)
self.response('', getXArgsDeviceInfo(include_token=False))
@ -164,7 +163,7 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""
daemon_threads = True
def __init__(self, client, subscription_manager, queue, *args, **kwargs):
def __init__(self, client, subscription_manager, *args, **kwargs):
"""
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
date serverlist without instantiating anything
@ -173,5 +172,4 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
"""
self.client = client
self.subscription_manager = subscription_manager
self.queue = queue
HTTPServer.__init__(self, *args, **kwargs)

View file

@ -10,6 +10,7 @@ from utils import window, kodi_time_to_millis, Lock_Function
import state
import variables as v
import json_rpc as js
import playqueue as PQ
###############################################################################
@ -111,7 +112,7 @@ class SubscriptionMgr(object):
"""
Manages Plex companion subscriptions
"""
def __init__(self, request_mgr, player, mgr):
def __init__(self, request_mgr, player):
self.serverlist = []
self.subscribers = {}
self.info = {}
@ -124,11 +125,8 @@ class SubscriptionMgr(object):
self.lastplayers = {}
self.xbmcplayer = player
self.playqueue = mgr.playqueue
self.request_mgr = request_mgr
def _server_by_host(self, host):
if len(self.serverlist) == 1:
return self.serverlist[0]
@ -180,7 +178,7 @@ class SubscriptionMgr(object):
def _timeline_dict(self, player, ptype):
playerid = player['playerid']
info = state.PLAYER_STATES[playerid]
playqueue = self.playqueue.playqueues[playerid]
playqueue = PQ.PLAYQUEUES[playerid]
pos = info['position']
try:
item = playqueue.items[pos]
@ -284,7 +282,7 @@ class SubscriptionMgr(object):
stream_type: 'video', 'audio', 'subtitle'
"""
playqueue = self.playqueue.playqueues[playerid]
playqueue = PQ.PLAYQUEUES[playerid]
info = state.PLAYER_STATES[playerid]
return playqueue.items[info['position']].plex_stream_index(
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
@ -306,7 +304,7 @@ class SubscriptionMgr(object):
"""
for player in players.values():
info = state.PLAYER_STATES[player['playerid']]
playqueue = self.playqueue.playqueues[player['playerid']]
playqueue = PQ.PLAYQUEUES[player['playerid']]
try:
item = playqueue.items[info['position']]
except IndexError:
@ -362,7 +360,7 @@ class SubscriptionMgr(object):
def _get_pms_params(self, playerid):
info = state.PLAYER_STATES[playerid]
playqueue = self.playqueue.playqueues[playerid]
playqueue = PQ.PLAYQUEUES[playerid]
try:
item = playqueue.items[info['position']]
except IndexError:
@ -386,7 +384,7 @@ class SubscriptionMgr(object):
def _send_pms_notification(self, playerid, params):
serv = self._server_by_host(self.server)
playqueue = self.playqueue.playqueues[playerid]
playqueue = PQ.PLAYQUEUES[playerid]
xargs = params_pms()
xargs.update(params)
if state.PLEX_TRANSIENT_TOKEN:

View file

@ -75,6 +75,13 @@ PLEX_USER_ID = None
# another user playing something! Token identifies user
PLEX_TRANSIENT_TOKEN = None
# Plex Companion Queue()
COMPANION_QUEUE = None
# Command Pipeline Queue()
COMMAND_PIPELINE_QUEUE = None
# Websocket_client queue to communicate with librarysync
WEBSOCKET_QUEUE = None
# Kodi player states - here, initial values are set
PLAYER_STATES = {
1: {
@ -117,10 +124,6 @@ PLAYER_STATES = {
# paths for playback (since we're not receiving a Kodi id)
PLEX_IDS = {}
PLAYED_INFO = {}
# Former playbackProps; used by playbackutils.py and set to True if initial
# playback setup has been done (and playbackutils will be called again
# subsequently)
PLAYBACK_SETUP_DONE = False
# Kodi webserver details
WEBSERVER_PORT = 8080

View file

@ -30,10 +30,8 @@ class UserClient(Thread):
# Borg - multiple instances, shared state
__shared_state = {}
def __init__(self, callback=None):
def __init__(self):
self.__dict__ = self.__shared_state
if callback is not None:
self.mgr = callback
self.auth = True
self.retry = 0

View file

@ -6,7 +6,6 @@ import websocket
from json import loads
import xml.etree.ElementTree as etree
from threading import Thread
from Queue import Queue
from ssl import CERT_NONE
from xbmc import sleep
@ -165,9 +164,6 @@ class PMS_Websocket(WebSocket):
"""
Websocket connection with the PMS for Plex Companion
"""
# Communication with librarysync
queue = Queue()
def getUri(self):
server = window('pms_server')
# Get the appropriate prefix for the websocket
@ -221,7 +217,7 @@ class PMS_Websocket(WebSocket):
% self.__class__.__name__)
else:
# Put PMS message on queue and let libsync take care of it
self.queue.put(message)
state.WEBSOCKET_QUEUE.put(message)
def IOError_response(self):
log.warn("Repeatedly could not connect to PMS, "
@ -271,8 +267,7 @@ class Alexa_Websocket(WebSocket):
% self.__class__.__name__)
return
process_command(message.attrib['path'][1:],
message.attrib,
queue=self.mgr.plexCompanion.queue)
message.attrib)
def IOError_response(self):
pass

View file

@ -36,7 +36,6 @@ from librarysync import LibrarySync
import videonodes
from websocket_client import PMS_Websocket, Alexa_Websocket
import downloadutils
from playqueue import Playqueue
import clientinfo
import PlexAPI
@ -80,14 +79,12 @@ class Service():
ws = None
library = None
plexCompanion = None
playqueue = None
user_running = False
ws_running = False
alexa_running = False
library_running = False
plexCompanion_running = False
playqueue_running = False
kodimonitor_running = False
playback_starter_running = False
image_cache_thread_running = False
@ -145,21 +142,20 @@ class Service():
monitor = self.monitor
kodiProfile = v.KODI_PROFILE
# Detect playback start early on
self.command_pipeline = Monitor_Window(self)
self.command_pipeline.start()
# Server auto-detect
initialsetup.InitialSetup().setup()
# Detect playback start early on
self.command_pipeline = Monitor_Window()
self.command_pipeline.start()
# Initialize important threads, handing over self for callback purposes
self.user = UserClient(self)
self.ws = PMS_Websocket(self)
self.alexa = Alexa_Websocket(self)
self.library = LibrarySync(self)
self.plexCompanion = PlexCompanion(self)
self.playqueue = Playqueue(self)
self.playback_starter = Playback_Starter(self)
self.user = UserClient()
self.ws = PMS_Websocket()
self.alexa = Alexa_Websocket()
self.library = LibrarySync()
self.plexCompanion = PlexCompanion()
self.playback_starter = Playback_Starter()
if settings('enableTextureCache') == "true":
self.image_cache_thread = Image_Cache_Thread()
@ -200,11 +196,7 @@ class Service():
time=2000,
sound=False)
# Start monitoring kodi events
self.kodimonitor_running = KodiMonitor(self)
# Start playqueue client
if not self.playqueue_running:
self.playqueue_running = True
self.playqueue.start()
self.kodimonitor_running = KodiMonitor()
# Start the Websocket Client
if not self.ws_running:
self.ws_running = True