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

View file

@ -2,7 +2,6 @@
############################################################################### ###############################################################################
import logging import logging
from threading import Thread from threading import Thread
from Queue import Queue
from xbmc import sleep from xbmc import sleep
@ -10,8 +9,7 @@ from utils import window, thread_methods
import state import state
############################################################################### ###############################################################################
log = logging.getLogger("PLEX."+__name__) LOG = logging.getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@ -23,16 +21,10 @@ class Monitor_Window(Thread):
Adjusts state.py accordingly 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): def run(self):
thread_stopped = self.thread_stopped thread_stopped = self.thread_stopped
queue = self.playback_queue queue = state.COMMAND_PIPELINE_QUEUE
log.info("----===## Starting Kodi_Play_Client ##===----") LOG.info("----===## Starting Kodi_Play_Client ##===----")
while not thread_stopped(): while not thread_stopped():
if window('plex_command'): if window('plex_command'):
value = window('plex_command') value = window('plex_command')
@ -70,4 +62,4 @@ class Monitor_Window(Thread):
sleep(50) sleep(50)
# Put one last item into the queue to let playback_starter end # Put one last item into the queue to let playback_starter end
queue.put(None) 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 xbmc import Player
from variables import ALEXA_TO_COMPANION from variables import ALEXA_TO_COMPANION
from playqueue import Playqueue import playqueue as PQ
from PlexFunctions import GetPlexKeyNumber from PlexFunctions import GetPlexKeyNumber
import json_rpc as js import json_rpc as js
import state import state
@ -29,9 +29,8 @@ def skip_to(params):
LOG.debug('Skipping to playQueueItemID %s, plex_id %s', LOG.debug('Skipping to playQueueItemID %s, plex_id %s',
playqueue_item_id, plex_id) playqueue_item_id, plex_id)
found = True found = True
playqueues = Playqueue()
for player in js.get_players().values(): for player in js.get_players().values():
playqueue = playqueues.playqueues[player['playerid']] playqueue = PQ.PLAYQUEUES[player['playerid']]
for i, item in enumerate(playqueue.items): for i, item in enumerate(playqueue.items):
if item.id == playqueue_item_id: if item.id == playqueue_item_id:
found = True found = True
@ -57,7 +56,7 @@ def convert_alexa_to_companion(dictionary):
del dictionary[key] del dictionary[key]
def process_command(request_path, params, queue=None): def process_command(request_path, params):
""" """
queue: Queue() of PlexCompanion.py queue: Queue() of PlexCompanion.py
""" """
@ -67,12 +66,12 @@ def process_command(request_path, params, queue=None):
if request_path == 'player/playback/playMedia': if request_path == 'player/playback/playMedia':
# We need to tell service.py # We need to tell service.py
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist' action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
queue.put({ state.COMPANION_QUEUE.put({
'action': action, 'action': action,
'data': params 'data': params
}) })
elif request_path == 'player/playback/refreshPlayQueue': elif request_path == 'player/playback/refreshPlayQueue':
queue.put({ state.COMPANION_QUEUE.put({
'action': 'refreshPlayQueue', 'action': 'refreshPlayQueue',
'data': params 'data': params
}) })
@ -114,7 +113,7 @@ def process_command(request_path, params, queue=None):
elif request_path == "player/navigation/back": elif request_path == "player/navigation/back":
js.input_back() js.input_back()
elif request_path == "player/playback/setStreams": elif request_path == "player/playback/setStreams":
queue.put({ state.COMPANION_QUEUE.put({
'action': 'setStreams', 'action': 'setStreams',
'data': params 'data': params
}) })

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################### ###############################################################################
from logging import getLogger from logging import getLogger
from Queue import Queue
import xbmc import xbmc
import xbmcgui import xbmcgui
@ -15,10 +15,11 @@ from PlexAPI import PlexAPI
from PlexFunctions import GetMachineIdentifier, get_PMS_settings from PlexFunctions import GetMachineIdentifier, get_PMS_settings
import state import state
from migration import check_migration 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(): class InitialSetup():
def __init__(self): def __init__(self):
log.debug('Entering initialsetup class') LOG.debug('Entering initialsetup class')
self.doUtils = downloadutils.DownloadUtils().downloadUrl self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.plx = PlexAPI() self.plx = PlexAPI()
self.dialog = xbmcgui.Dialog() self.dialog = xbmcgui.Dialog()
@ -42,7 +43,7 @@ class InitialSetup():
# Token for the PMS, not plex.tv # Token for the PMS, not plex.tv
self.pms_token = settings('accessToken') self.pms_token = settings('accessToken')
if self.plexToken: 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): def PlexTVSignIn(self):
""" """
@ -68,7 +69,7 @@ class InitialSetup():
chk = self.plx.CheckConnection('plex.tv', token=self.plexToken) chk = self.plx.CheckConnection('plex.tv', token=self.plexToken)
if chk in (401, 403): if chk in (401, 403):
# HTTP Error: unauthorized. Token is no longer valid # 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 # Delete token in the settings
settings('plexToken', value='') settings('plexToken', value='')
settings('plexLogin', value='') settings('plexLogin', value='')
@ -77,12 +78,12 @@ class InitialSetup():
answer = self.PlexTVSignIn() answer = self.PlexTVSignIn()
elif chk is False or chk >= 400: elif chk is False or chk >= 400:
# Problems connecting to plex.tv. Network or internet issue? # Problems connecting to plex.tv. Network or internet issue?
log.info('Problems connecting to plex.tv; connection returned ' LOG.info('Problems connecting to plex.tv; connection returned '
'HTTP %s' % str(chk)) 'HTTP %s', str(chk))
self.dialog.ok(lang(29999), lang(39010)) self.dialog.ok(lang(29999), lang(39010))
answer = False answer = False
else: else:
log.info('plex.tv connection with token successful') LOG.info('plex.tv connection with token successful')
settings('plex_status', value=lang(39227)) settings('plex_status', value=lang(39227))
# Refresh the info from Plex.tv # Refresh the info from Plex.tv
xml = self.doUtils('https://plex.tv/users/account', xml = self.doUtils('https://plex.tv/users/account',
@ -91,14 +92,14 @@ class InitialSetup():
try: try:
self.plexLogin = xml.attrib['title'] self.plexLogin = xml.attrib['title']
except (AttributeError, KeyError): except (AttributeError, KeyError):
log.error('Failed to update Plex info from plex.tv') LOG.error('Failed to update Plex info from plex.tv')
else: else:
settings('plexLogin', value=self.plexLogin) settings('plexLogin', value=self.plexLogin)
home = 'true' if xml.attrib.get('home') == '1' else 'false' home = 'true' if xml.attrib.get('home') == '1' else 'false'
settings('plexhome', value=home) settings('plexhome', value=home)
settings('plexAvatar', value=xml.attrib.get('thumb')) settings('plexAvatar', value=xml.attrib.get('thumb'))
settings('plexHomeSize', value=xml.attrib.get('homeSize', '1')) 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 return answer
def CheckPMS(self): def CheckPMS(self):
@ -114,24 +115,24 @@ class InitialSetup():
answer = True answer = True
chk = self.plx.CheckConnection(self.server, verifySSL=False) chk = self.plx.CheckConnection(self.server, verifySSL=False)
if chk is 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 answer = False
if answer is True and not self.serverid: if answer is True and not self.serverid:
log.info('No PMS machineIdentifier found for %s. Trying to ' LOG.info('No PMS machineIdentifier found for %s. Trying to '
'get the PMS unique ID' % self.server) 'get the PMS unique ID', self.server)
self.serverid = GetMachineIdentifier(self.server) self.serverid = GetMachineIdentifier(self.server)
if self.serverid is None: if self.serverid is None:
log.warn('Could not retrieve machineIdentifier') LOG.warn('Could not retrieve machineIdentifier')
answer = False answer = False
else: else:
settings('plex_machineIdentifier', value=self.serverid) settings('plex_machineIdentifier', value=self.serverid)
elif answer is True: elif answer is True:
tempServerid = GetMachineIdentifier(self.server) tempServerid = GetMachineIdentifier(self.server)
if tempServerid != self.serverid: 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 ' 'unique machineIdentifier of %s. But we got '
'%s. Pick a new server to be sure' '%s. Pick a new server to be sure',
% (self.server, self.serverid, tempServerid)) self.server, self.serverid, tempServerid)
answer = False answer = False
return answer return answer
@ -142,7 +143,7 @@ class InitialSetup():
self.plx.discoverPMS(xbmc.getIPAddress(), self.plx.discoverPMS(xbmc.getIPAddress(),
plexToken=self.plexToken) plexToken=self.plexToken)
serverlist = self.plx.returnServerList(self.plx.g_PMS) serverlist = self.plx.returnServerList(self.plx.g_PMS)
log.debug('PMS serverlist: %s' % serverlist) LOG.debug('PMS serverlist: %s', serverlist)
return serverlist return serverlist
def _checkServerCon(self, server): def _checkServerCon(self, server):
@ -209,7 +210,7 @@ class InitialSetup():
try: try:
xml.attrib xml.attrib
except AttributeError: except AttributeError:
log.error('Could not get PMS settings for %s' % url) LOG.error('Could not get PMS settings for %s', url)
return return
for entry in xml: for entry in xml:
if entry.attrib.get('id', '') == 'allowMediaDeletion': if entry.attrib.get('id', '') == 'allowMediaDeletion':
@ -236,9 +237,9 @@ class InitialSetup():
server = item server = item
if server is None: if server is None:
name = settings('plex_servername') 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 ' 'machineIdentifier of %s and name %s is '
'offline' % (self.serverid, name)) 'offline', self.serverid, name)
return return
chk = self._checkServerCon(server) chk = self._checkServerCon(server)
if chk == 504 and httpsUpdated is False: if chk == 504 and httpsUpdated is False:
@ -247,8 +248,8 @@ class InitialSetup():
httpsUpdated = True httpsUpdated = True
continue continue
if chk == 401: if chk == 401:
log.warn('Not yet authorized for Plex server %s' LOG.warn('Not yet authorized for Plex server %s',
% server['name']) server['name'])
if self.CheckPlexTVSignIn() is True: if self.CheckPlexTVSignIn() is True:
if checkedPlexTV is False: if checkedPlexTV is False:
# Try again # Try again
@ -256,7 +257,7 @@ class InitialSetup():
httpsUpdated = False httpsUpdated = False
continue continue
else: else:
log.warn('Not authorized even though we are signed ' LOG.warn('Not authorized even though we are signed '
' in to plex.tv correctly') ' in to plex.tv correctly')
self.dialog.ok(lang(29999), '%s %s' self.dialog.ok(lang(29999), '%s %s'
% (lang(39214), % (lang(39214),
@ -266,11 +267,11 @@ class InitialSetup():
return return
# Problems connecting # Problems connecting
elif chk >= 400 or chk is False: elif chk >= 400 or chk is False:
log.warn('Problems connecting to server %s. chk is %s' LOG.warn('Problems connecting to server %s. chk is %s',
% (server['name'], chk)) server['name'], chk)
return return
log.info('We found a server to automatically connect to: %s' LOG.info('We found a server to automatically connect to: %s',
% server['name']) server['name'])
return server return server
def _UserPickPMS(self): def _UserPickPMS(self):
@ -285,7 +286,7 @@ class InitialSetup():
serverlist = self._getServerList() serverlist = self._getServerList()
# Exit if no servers found # Exit if no servers found
if len(serverlist) == 0: 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)) self.dialog.ok(lang(29999), lang(39011))
return return
# Get a nicer list # Get a nicer list
@ -322,8 +323,8 @@ class InitialSetup():
continue continue
httpsUpdated = False httpsUpdated = False
if chk == 401: if chk == 401:
log.warn('Not yet authorized for Plex server %s' LOG.warn('Not yet authorized for Plex server %s',
% server['name']) server['name'])
# Please sign in to plex.tv # Please sign in to plex.tv
self.dialog.ok(lang(29999), self.dialog.ok(lang(29999),
lang(39013) + server['name'], lang(39013) + server['name'],
@ -370,7 +371,7 @@ class InitialSetup():
scheme = server['scheme'] scheme = server['scheme']
settings('ipaddress', server['ip']) settings('ipaddress', server['ip'])
settings('port', server['port']) 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") "local")
settings('sslverify', 'false') settings('sslverify', 'false')
else: else:
@ -378,7 +379,7 @@ class InitialSetup():
scheme = baseURL[0] scheme = baseURL[0]
settings('ipaddress', baseURL[1].replace('//', '')) settings('ipaddress', baseURL[1].replace('//', ''))
settings('port', baseURL[2]) 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") "local")
settings('sslverify', 'true') settings('sslverify', 'true')
@ -387,10 +388,10 @@ class InitialSetup():
else: else:
settings('https', 'false') settings('https', 'false')
# And finally do some logging # And finally do some logging
log.debug("Writing to Kodi user settings file") LOG.debug("Writing to Kodi user settings file")
log.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s " LOG.debug("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s ",
% (server['machineIdentifier'], server['ip'], server['machineIdentifier'], server['ip'], server['port'],
server['port'], server['scheme'])) server['scheme'])
def setup(self): def setup(self):
""" """
@ -399,14 +400,14 @@ class InitialSetup():
Check server, user, direct paths, music, direct stream if not direct Check server, user, direct paths, music, direct stream if not direct
path. path.
""" """
log.info("Initial setup called.") LOG.info("Initial setup called.")
dialog = self.dialog dialog = self.dialog
# Get current Kodi video cache setting # Get current Kodi video cache setting
cache, _ = advancedsettings_xml(['cache', 'memorysize']) cache, _ = advancedsettings_xml(['cache', 'memorysize'])
# Kodi default cache if no setting is set # Kodi default cache if no setting is set
cache = str(cache.text) if cache is not None else '20971520' 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) settings('kodi_video_cache', value=cache)
# Disable foreground "Loading media information from files" # Disable foreground "Loading media information from files"
# (still used by Kodi, even though the Wiki says otherwise) # (still used by Kodi, even though the Wiki says otherwise)
@ -420,13 +421,20 @@ class InitialSetup():
if self.plexToken and self.myplexlogin: if self.plexToken and self.myplexlogin:
self.CheckPlexTVSignIn() 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 # If a Plex server IP has already been set
# return only if the right machine identifier is found # return only if the right machine identifier is found
if self.server: 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(): if self.CheckPMS():
log.info("Using PMS %s with machineIdentifier %s" LOG.info("Using PMS %s with machineIdentifier %s",
% (self.server, self.serverid)) self.server, self.serverid)
self._write_PMS_settings(self.server, self.pms_token) self._write_PMS_settings(self.server, self.pms_token)
return return
@ -452,19 +460,19 @@ class InitialSetup():
lang(39028), lang(39028),
nolabel="Addon (Default)", nolabel="Addon (Default)",
yeslabel="Native (Direct Paths)"): yeslabel="Native (Direct Paths)"):
log.debug("User opted to use direct paths.") LOG.debug("User opted to use direct paths.")
settings('useDirectPaths', value="1") settings('useDirectPaths', value="1")
state.DIRECT_PATHS = True state.DIRECT_PATHS = True
# Are you on a system where you would like to replace paths # Are you on a system where you would like to replace paths
# \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows) # \\NAS\mymovie.mkv with smb://NAS/mymovie.mkv? (e.g. Windows)
if dialog.yesno(heading=lang(29999), line1=lang(39033)): 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: else:
settings('replaceSMB', value="false") settings('replaceSMB', value="false")
# complete replace all original Plex library paths with custom SMB # complete replace all original Plex library paths with custom SMB
if dialog.yesno(heading=lang(29999), line1=lang(39043)): 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") settings('remapSMB', value="true")
# Please enter your custom smb paths in the settings under # Please enter your custom smb paths in the settings under
# "Sync Options" and then restart Kodi # "Sync Options" and then restart Kodi
@ -475,22 +483,22 @@ class InitialSetup():
if dialog.yesno(heading=lang(29999), if dialog.yesno(heading=lang(29999),
line1=lang(39029), line1=lang(39029),
line2=lang(39030)): line2=lang(39030)):
log.debug("Presenting network credentials dialog.") LOG.debug("Presenting network credentials dialog.")
from utils import passwordsXML from utils import passwordsXML
passwordsXML() passwordsXML()
# Disable Plex music? # Disable Plex music?
if dialog.yesno(heading=lang(29999), line1=lang(39016)): 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") settings('enableMusic', value="false")
# Download additional art from FanArtTV # Download additional art from FanArtTV
if dialog.yesno(heading=lang(29999), line1=lang(39061)): 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") settings('FanartTV', value="true")
# Do you want to replace your custom user ratings with an indicator of # Do you want to replace your custom user ratings with an indicator of
# how many versions of a media item you posses? # how many versions of a media item you posses?
if dialog.yesno(heading=lang(29999), line1=lang(39718)): 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") settings('indicate_media_versions', value="true")
# If you use several Plex libraries of one kind, e.g. "Kids Movies" and # 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 playlist_func as PL
import state import state
import variables as v 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. PKC implementation of the Kodi Monitor class. Invoke only once.
""" """
def __init__(self, callback): def __init__(self):
self.mgr = callback
self.xbmcplayer = Player() self.xbmcplayer = Player()
self.playqueue = self.mgr.playqueue
Monitor.__init__(self) Monitor.__init__(self)
LOG.info("Kodi monitor started.") LOG.info("Kodi monitor started.")
@ -198,7 +197,7 @@ class KodiMonitor(Monitor):
} }
Will NOT be called if playback initiated by Kodi widgets 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 # Did PKC cause this add? Then lets not do anything
if playqueue.is_kodi_onadd() is False: if playqueue.is_kodi_onadd() is False:
LOG.debug('PKC added this item to the playqueue - ignoring') LOG.debug('PKC added this item to the playqueue - ignoring')
@ -228,7 +227,7 @@ class KodiMonitor(Monitor):
u'position': 0 u'position': 0
} }
""" """
playqueue = self.playqueue.playqueues[data['playlistid']] playqueue = PQ.PLAYQUEUES[data['playlistid']]
# Did PKC cause this add? Then lets not do anything # Did PKC cause this add? Then lets not do anything
if playqueue.is_kodi_onremove() is False: if playqueue.is_kodi_onremove() is False:
LOG.debug('PKC removed this item already from playqueue - ignoring') LOG.debug('PKC removed this item already from playqueue - ignoring')
@ -249,7 +248,7 @@ class KodiMonitor(Monitor):
u'playlistid': 1, u'playlistid': 1,
} }
""" """
playqueue = self.playqueue.playqueues[data['playlistid']] playqueue = PQ.PLAYQUEUES[data['playlistid']]
if playqueue.is_kodi_onclear() is False: if playqueue.is_kodi_onclear() is False:
LOG.debug('PKC already cleared the playqueue - ignoring') LOG.debug('PKC already cleared the playqueue - ignoring')
return return
@ -317,7 +316,7 @@ class KodiMonitor(Monitor):
LOG.debug('Set the player state: %s', state.PLAYER_STATES[playerid]) LOG.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
# Check whether we need to init our playqueues (e.g. direct play) # Check whether we need to init our playqueues (e.g. direct play)
init = False init = False
playqueue = self.playqueue.playqueues[playerid] playqueue = PQ.PLAYQUEUES[playerid]
try: try:
playqueue.items[info['position']] playqueue.items[info['position']]
except IndexError: except IndexError:
@ -340,7 +339,7 @@ class KodiMonitor(Monitor):
container_key = None container_key = None
if info['playlistid'] != -1: if info['playlistid'] != -1:
# -1 is Kodi's answer if there is no playlist # -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: if container_key is not None:
container_key = '/playQueues/%s' % container_key container_key = '/playQueues/%s' % container_key
elif plex_id is not None: elif plex_id is not None:

View file

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

View file

@ -12,7 +12,7 @@ from playbackutils import PlaybackUtils
from utils import window from utils import window
from PlexFunctions import GetPlexMetadata from PlexFunctions import GetPlexMetadata
from PlexAPI import API from PlexAPI import API
from playqueue import LOCK import playqueue as PQ
import variables as v import variables as v
from downloadutils import DownloadUtils from downloadutils import DownloadUtils
from PKC_listitem import convert_PKC_to_listitem from PKC_listitem import convert_PKC_to_listitem
@ -21,7 +21,8 @@ from context_entry import ContextMenu
import state import state
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@ -30,26 +31,21 @@ class Playback_Starter(Thread):
""" """
Processes new plays 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): def process_play(self, plex_id, kodi_id=None):
""" """
Processes Kodi playback init for ONE item Processes Kodi playback init for ONE item
""" """
log.info("Process_play called with plex_id %s, kodi_id %s" LOG.info("Process_play called with plex_id %s, kodi_id %s",
% (plex_id, kodi_id)) plex_id, kodi_id)
if not state.AUTHENTICATED: 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 # Todo: Warn user with dialog
return return
xml = GetPlexMetadata(plex_id) xml = GetPlexMetadata(plex_id)
try: try:
xml[0].attrib xml[0].attrib
except (IndexError, TypeError, AttributeError): 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 return
api = API(xml[0]) api = API(xml[0])
if api.getType() == v.PLEX_TYPE_PHOTO: if api.getType() == v.PLEX_TYPE_PHOTO:
@ -60,15 +56,14 @@ class Playback_Starter(Thread):
result.listitem = listitem result.listitem = listitem
else: else:
# Video and Music # 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()]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
with LOCK: with PQ.LOCK:
result = PlaybackUtils(xml, playqueue).play( result = PlaybackUtils(xml, playqueue).play(
plex_id, plex_id,
kodi_id, kodi_id,
xml.attrib.get('librarySectionUUID')) xml.attrib.get('librarySectionUUID'))
log.info('Done process_play, playqueues: %s' LOG.info('Done process_play, playqueues: %s', PQ.PLAYQUEUES)
% self.playqueue.playqueues)
return result return result
def process_plex_node(self, url, viewOffset, directplay=False, 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, Called for Plex directories or redirect for playback (e.g. trailers,
clips, watchlater) clips, watchlater)
""" """
log.info('process_plex_node called with url: %s, viewOffset: %s' LOG.info('process_plex_node called with url: %s, viewOffset: %s',
% (url, viewOffset)) url, viewOffset)
# Plex redirect, e.g. watch later. Need to get actual URLs # Plex redirect, e.g. watch later. Need to get actual URLs
if url.startswith('http') or url.startswith('{server}'): if url.startswith('http') or url.startswith('{server}'):
xml = DownloadUtils().downloadUrl(url) xml = DownloadUtils().downloadUrl(url)
@ -87,7 +82,7 @@ class Playback_Starter(Thread):
try: try:
xml[0].attrib xml[0].attrib
except: except:
log.error('Could not download PMS metadata') LOG.error('Could not download PMS metadata')
return return
if viewOffset != '0': if viewOffset != '0':
try: try:
@ -96,7 +91,7 @@ class Playback_Starter(Thread):
pass pass
else: else:
window('plex_customplaylist.seektime', value=str(viewOffset)) 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]) api = API(xml[0])
typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()] typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]
if node is True: if node is True:
@ -110,10 +105,10 @@ class Playback_Starter(Thread):
try: try:
kodi_id = plexdb_item[0] kodi_id = plexdb_item[0]
except TypeError: except TypeError:
log.info('Couldnt find item %s in Kodi db' LOG.info('Couldnt find item %s in Kodi db',
% api.getRatingKey()) api.getRatingKey())
playqueue = self.playqueue.get_playqueue_from_type(typus) playqueue = PQ.get_playqueue_from_type(typus)
with LOCK: with PQ.LOCK:
result = PlaybackUtils(xml, playqueue).play( result = PlaybackUtils(xml, playqueue).play(
plex_id, plex_id,
kodi_id=kodi_id, kodi_id=kodi_id,
@ -130,7 +125,7 @@ class Playback_Starter(Thread):
_, params = item.split('?', 1) _, params = item.split('?', 1)
params = dict(parse_qsl(params)) params = dict(parse_qsl(params))
mode = params.get('mode') mode = params.get('mode')
log.debug('Received mode: %s, params: %s' % (mode, params)) LOG.debug('Received mode: %s, params: %s', mode, params)
try: try:
if mode == 'play': if mode == 'play':
result = self.process_play(params.get('id'), result = self.process_play(params.get('id'),
@ -147,18 +142,18 @@ class Playback_Starter(Thread):
ContextMenu() ContextMenu()
result = Playback_Successful() result = Playback_Successful()
except: except:
log.error('Error encountered for mode %s, params %s' LOG.error('Error encountered for mode %s, params %s',
% (mode, params)) mode, params)
import traceback import traceback
log.error(traceback.format_exc()) LOG.error(traceback.format_exc())
# Let default.py know! # Let default.py know!
pickle_me(None) pickle_me(None)
else: else:
pickle_me(result) pickle_me(result)
def run(self): def run(self):
queue = self.mgr.command_pipeline.playback_queue queue = state.COMMAND_PIPELINE_QUEUE
log.info("----===## Starting Playback_Starter ##===----") LOG.info("----===## Starting Playback_Starter ##===----")
while True: while True:
item = queue.get() item = queue.get()
if item is None: if item is None:
@ -167,4 +162,4 @@ class Playback_Starter(Thread):
else: else:
self.triage(item) self.triage(item)
queue.task_done() 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) startPos = max(playqueue.kodi_pl.getposition(), 0)
self.currentPosition = startPos self.currentPosition = startPos
propertiesPlayback = window('plex_playbackProps') == "true"
introsPlaylist = False introsPlaylist = False
dummyPlaylist = False dummyPlaylist = False
@ -113,8 +114,8 @@ class PlaybackUtils():
# We need to ensure we add the intro and additional parts only once. # We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop. # Otherwise we get a loop.
if not state.PLAYBACK_SETUP_DONE: if not propertiesPlayback:
state.PLAYBACK_SETUP_DONE = True window('plex_playbackProps', value="true")
LOG.info("Setting up properties in playlist.") LOG.info("Setting up properties in playlist.")
# Where will the player need to start? # Where will the player need to start?
# Do we need to get trailers? # Do we need to get trailers?
@ -139,7 +140,6 @@ class PlaybackUtils():
get_playlist_details_from_xml(playqueue, xml=xml) get_playlist_details_from_xml(playqueue, xml=xml)
except KeyError: except KeyError:
return return
if (not homeScreen and not seektime and sizePlaylist < 2 and if (not homeScreen and not seektime and sizePlaylist < 2 and
window('plex_customplaylist') != "true" and window('plex_customplaylist') != "true" and
not contextmenu_play): not contextmenu_play):
@ -198,7 +198,7 @@ class PlaybackUtils():
api.set_listitem_artwork(listitem) api.set_listitem_artwork(listitem)
add_listitem_to_Kodi_playlist( add_listitem_to_Kodi_playlist(
playqueue, playqueue,
self.currentPosition, self.currentPosition+1,
convert_PKC_to_listitem(listitem), convert_PKC_to_listitem(listitem),
file=playurl, file=playurl,
kodi_item={'id': kodi_id, 'type': kodi_type}) kodi_item={'id': kodi_id, 'type': kodi_type})
@ -206,7 +206,7 @@ class PlaybackUtils():
# Full metadata$ # Full metadata$
add_item_to_kodi_playlist( add_item_to_kodi_playlist(
playqueue, playqueue,
self.currentPosition, self.currentPosition+1,
kodi_id, kodi_id,
kodi_type) kodi_type)
self.currentPosition += 1 self.currentPosition += 1
@ -230,9 +230,9 @@ class PlaybackUtils():
return result return result
# We just skipped adding properties. Reset flag for next time. # We just skipped adding properties. Reset flag for next time.
elif state.PLAYBACK_SETUP_DONE: elif propertiesPlayback:
LOG.debug("Resetting properties playback flag.") LOG.debug("Resetting properties playback flag.")
state.PLAYBACK_SETUP_DONE = False window('plex_playbackProps', clear=True)
# SETUP MAIN ITEM ########## # SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref # 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 Play all items contained in the xml passed in. Called by Plex Companion
""" """
LOG.info("Playbackutils play_all called") LOG.info("Playbackutils play_all called")
state.PLAYBACK_SETUP_DONE = True window('plex_playbackProps', value="true")
self.currentPosition = 0 self.currentPosition = 0
for item in self.xml: for item in self.xml:
api = API(item) 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 # 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) # Companion (if we could not use the playqueue to store the token)
state.PLEX_TRANSIENT_TOKEN = None state.PLEX_TRANSIENT_TOKEN = None
state.PLAYBACK_SETUP_DONE = False
LOG.debug("Cleared playlist properties.") LOG.debug("Cleared playlist properties.")
def onPlayBackEnded(self): def onPlayBackEnded(self):

View file

@ -4,9 +4,9 @@ Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
from logging import getLogger from logging import getLogger
from threading import RLock, Thread 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 import playlist_func as PL
from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren
from PlexAPI import API from PlexAPI import API
@ -20,218 +20,126 @@ LOG = getLogger("PLEX." + __name__)
# lock used for playqueue manipulations # lock used for playqueue manipulations
LOCK = RLock() LOCK = RLock()
PLUGIN = 'plugin://%s' % v.ADDON_ID PLUGIN = 'plugin://%s' % v.ADDON_ID
# Our PKC playqueues (3 instances of Playqueue_Object())
PLAYQUEUES = []
############################################################################### ###############################################################################
@thread_methods(add_suspends=['PMS_STATUS']) def init_playqueues():
class Playqueue(Thread):
""" """
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 if PLAYQUEUES:
__shared_state = {} LOG.debug('Playqueues have already been initialized')
playqueues = None return
# Initialize Kodi playqueues
def __init__(self, callback=None): with LOCK:
self.__dict__ = self.__shared_state for i in (0, 1, 2):
if self.playqueues is not None: # Just in case the Kodi response is not sorted correctly
LOG.debug('Playqueue thread has already been initialized')
Thread.__init__(self)
return
self.mgr = callback
# Initialize Kodi playqueues
with LOCK:
self.playqueues = []
for queue in js.get_playlists(): for queue in js.get_playlists():
if queue['playlistid'] != i:
continue
playqueue = PL.Playqueue_Object() playqueue = PL.Playqueue_Object()
playqueue.playlistid = queue['playlistid'] playqueue.playlistid = i
playqueue.type = queue['type'] playqueue.type = queue['type']
# Initialize each Kodi playlist # Initialize each Kodi playlist
if playqueue.type == 'audio': if playqueue.type == v.KODI_TYPE_AUDIO:
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC) playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
elif playqueue.type == 'video': elif playqueue.type == v.KODI_TYPE_VIDEO:
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO) playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
else: else:
# Currently, only video or audio playqueues available # Currently, only video or audio playqueues available
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO) playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
# Overwrite 'picture' with 'photo' # Overwrite 'picture' with 'photo'
playqueue.type = v.KODI_TYPE_PHOTO playqueue.type = v.KODI_TYPE_PHOTO
self.playqueues.append(playqueue) PLAYQUEUES.append(playqueue)
# sort the list by their playlistid, just in case LOG.debug('Initialized the Kodi playqueues: %s', PLAYQUEUES)
self.playqueues = sorted(
self.playqueues, key=lambda i: i.playlistid)
LOG.debug('Initialized the Kodi play queues: %s', self.playqueues)
Thread.__init__(self)
def get_playqueue_from_type(self, typus):
"""
Returns the playqueue according to the typus ('video', 'audio',
'picture') passed in
"""
with LOCK:
for playqueue in self.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 get_playqueue_from_type(typus):
""" """
Init a new playqueue e.g. from an album. Alexa does this Returns the playqueue according to the typus ('video', 'audio',
'picture') passed in
Returns the Playlist_Object """
""" with LOCK:
xml = GetAllPlexChildren(plex_id) for playqueue in PLAYQUEUES:
try: if playqueue.type == typus:
xml[0].attrib break
except (TypeError, IndexError, AttributeError): else:
LOG.error('Could not download the PMS xml for %s', plex_id) raise ValueError('Wrong playlist type passed in: %s' % typus)
return
playqueue = self.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
playqueue.clear()
for i, child in enumerate(xml):
api = API(child)
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey())
playqueue.plex_transient_token = transient_token
LOG.debug('Firing up Kodi player')
Player().play(playqueue.kodi_pl, None, False, 0)
return playqueue return playqueue
def update_playqueue_from_PMS(self,
playqueue,
playqueue_id=None,
repeat=None,
offset=None,
transient_token=None):
"""
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
in playqueue_id if we need to fetch a new playqueue
repeat = 0, 1, 2 def init_playqueue_from_plex_children(plex_id, transient_token=None):
offset = time offset in Plextime (milliseconds) """
""" Init a new playqueue e.g. from an album. Alexa does this
LOG.info('New playqueue %s received from Plex companion with offset '
'%s, repeat %s', playqueue_id, offset, repeat)
# Safe transient token from being deleted
if transient_token is None:
transient_token = playqueue.plex_transient_token
with LOCK:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
playqueue.clear()
try:
PL.get_playlist_details_from_xml(playqueue, xml)
except KeyError:
LOG.error('Could not get playqueue ID %s', playqueue_id)
return
playqueue.repeat = 0 if not repeat else int(repeat)
playqueue.plex_transient_token = transient_token
PlaybackUtils(xml, playqueue).play_all()
window('plex_customplaylist', value="true")
if offset not in (None, "0"):
window('plex_customplaylist.seektime',
str(ConvertPlexToKodiTime(offset)))
for startpos, item in enumerate(playqueue.items):
if item.id == playqueue.selectedItemID:
break
else:
startpos = 0
# Start playback. Player does not return in time
LOG.debug('Playqueues after Plex Companion update are now: %s',
self.playqueues)
thread = Thread(target=Player().play,
args=(playqueue.kodi_pl,
None,
False,
startpos))
thread.setDaemon(True)
thread.start()
def _compare_playqueues(self, playqueue, new): Returns the Playlist_Object
""" """
Used to poll the Kodi playqueue and update the Plex playqueue if needed xml = GetAllPlexChildren(plex_id)
""" try:
old = list(playqueue.items) xml[0].attrib
index = list(range(0, len(old))) except (TypeError, IndexError, AttributeError):
LOG.debug('Comparing new Kodi playqueue %s with our play queue %s', LOG.error('Could not download the PMS xml for %s', plex_id)
new, old) return
if self.thread_stopped(): playqueue = get_playqueue_from_type(
# Chances are that we got an empty Kodi playlist due to v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
# Kodi exit playqueue.clear()
for i, child in enumerate(xml):
api = API(child)
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey())
playqueue.plex_transient_token = transient_token
LOG.debug('Firing up Kodi player')
Player().play(playqueue.kodi_pl, None, False, 0)
return playqueue
def update_playqueue_from_PMS(playqueue,
playqueue_id=None,
repeat=None,
offset=None,
transient_token=None):
"""
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
in playqueue_id if we need to fetch a new playqueue
repeat = 0, 1, 2
offset = time offset in Plextime (milliseconds)
"""
LOG.info('New playqueue %s received from Plex companion with offset '
'%s, repeat %s', playqueue_id, offset, repeat)
# Safe transient token from being deleted
if transient_token is None:
transient_token = playqueue.plex_transient_token
with LOCK:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
playqueue.clear()
try:
PL.get_playlist_details_from_xml(playqueue, xml)
except KeyError:
LOG.error('Could not get playqueue ID %s', playqueue_id)
return return
for i, new_item in enumerate(new): playqueue.repeat = 0 if not repeat else int(repeat)
if (new_item['file'].startswith('plugin://') and playqueue.plex_transient_token = transient_token
not new_item['file'].startswith(PLUGIN)): PlaybackUtils(xml, playqueue).play_all()
# Ignore new media added by other addons window('plex_customplaylist', value="true")
continue if offset not in (None, "0"):
for j, old_item in enumerate(old): window('plex_customplaylist.seektime',
try: str(ConvertPlexToKodiTime(offset)))
if (old_item.file.startswith('plugin://') and for startpos, item in enumerate(playqueue.items):
not old_item['file'].startswith(PLUGIN)): if item.id == playqueue.selectedItemID:
# Ignore media by other addons break
continue else:
except (TypeError, AttributeError): startpos = 0
# were not passed a filename; ignore # Start playback. Player does not return in time
pass LOG.debug('Playqueues after Plex Companion update are now: %s',
if new_item.get('id') is None: PLAYQUEUES)
identical = old_item.file == new_item['file'] thread = Thread(target=Player().play,
else: args=(playqueue.kodi_pl,
identical = (old_item.kodi_id == new_item['id'] and None,
old_item.kodi_type == new_item['type']) False,
if j == 0 and identical: startpos))
del old[j], index[j] thread.setDaemon(True)
break thread.start()
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 xbmc import sleep
from companion import process_command from companion import process_command
from utils import window
import json_rpc as js import json_rpc as js
from clientinfo import getXArgsDeviceInfo from clientinfo import getXArgsDeviceInfo
import variables as v import variables as v
@ -154,7 +153,7 @@ class MyHandler(BaseHTTPRequestHandler):
sub_mgr.remove_subscriber(uuid) sub_mgr.remove_subscriber(uuid)
else: else:
# Throw it to companion.py # Throw it to companion.py
process_command(request_path, params, self.server.queue) process_command(request_path, params)
self.response('', getXArgsDeviceInfo(include_token=False)) self.response('', getXArgsDeviceInfo(include_token=False))
@ -164,7 +163,7 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
""" """
daemon_threads = True 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- client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
date serverlist without instantiating anything date serverlist without instantiating anything
@ -173,5 +172,4 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
""" """
self.client = client self.client = client
self.subscription_manager = subscription_manager self.subscription_manager = subscription_manager
self.queue = queue
HTTPServer.__init__(self, *args, **kwargs) HTTPServer.__init__(self, *args, **kwargs)

View file

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

View file

@ -75,6 +75,13 @@ PLEX_USER_ID = None
# another user playing something! Token identifies user # another user playing something! Token identifies user
PLEX_TRANSIENT_TOKEN = None 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 # Kodi player states - here, initial values are set
PLAYER_STATES = { PLAYER_STATES = {
1: { 1: {
@ -117,10 +124,6 @@ PLAYER_STATES = {
# paths for playback (since we're not receiving a Kodi id) # paths for playback (since we're not receiving a Kodi id)
PLEX_IDS = {} PLEX_IDS = {}
PLAYED_INFO = {} 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 # Kodi webserver details
WEBSERVER_PORT = 8080 WEBSERVER_PORT = 8080

View file

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

View file

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

View file

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