Playqueues overhaul continued

This commit is contained in:
tomkat83 2017-01-02 14:07:24 +01:00
parent a9f59868f0
commit 146f063fc9
19 changed files with 1049 additions and 563 deletions

View file

@ -6,8 +6,6 @@
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.3.0" />
<import addon="plugin.video.plexkodiconnect.movies" version="0.01" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="0.01" />
</requires>
<extension point="xbmc.python.pluginsource"
library="default.py">

View file

@ -3,36 +3,38 @@
###############################################################################
import logging
import os
import sys
import urlparse
from os import path as os_path
from sys import path as sys_path, argv
from urlparse import parse_qsl
import xbmc
import xbmcaddon
import xbmcgui
from xbmc import translatePath, sleep, executebuiltin
from xbmcaddon import Addon
from xbmcgui import ListItem, Dialog
from xbmcplugin import setResolvedUrl
_addon = xbmcaddon.Addon(id='plugin.video.plexkodiconnect')
_addon = Addon(id='plugin.video.plexkodiconnect')
try:
_addon_path = _addon.getAddonInfo('path').decode('utf-8')
except TypeError:
_addon_path = _addon.getAddonInfo('path').decode()
try:
_base_resource = xbmc.translatePath(os.path.join(
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode('utf-8')
except TypeError:
_base_resource = xbmc.translatePath(os.path.join(
_base_resource = translatePath(os_path.join(
_addon_path,
'resources',
'lib')).decode()
sys.path.append(_base_resource)
sys_path.append(_base_resource)
###############################################################################
import entrypoint
import utils
from utils import window, pickl_window, reset, passwordsXML
from pickler import unpickle_me
from PKC_listitem import convert_PKC_to_listitem
###############################################################################
@ -43,34 +45,47 @@ log = logging.getLogger("PLEX.default")
###############################################################################
ARGV = argv
HANDLE = int(argv[1])
class Main():
# MAIN ENTRY POINT
# @utils.profiling()
def __init__(self):
log.debug("Full sys.argv received: %s" % ARGV)
# Parse parameters
log.warn("Full sys.argv received: %s" % sys.argv)
base_url = sys.argv[0]
params = urlparse.parse_qs(sys.argv[2][1:])
params = dict(parse_qsl(ARGV[2][1:]))
try:
mode = params['mode'][0]
mode = params['mode']
itemid = params.get('id', '')
if itemid:
try:
itemid = itemid[0]
except:
pass
except:
params = {}
mode = ""
itemid = ''
if mode == 'play':
# Put the request into the "queue"
while window('plex_play_new_item'):
sleep(20)
window('plex_play_new_item',
value='%s%s' % (mode, ARGV[2]))
# Wait for the result
while not pickl_window('plex_result'):
sleep(20)
result = unpickle_me()
if result is None:
log.error('Error encountered, aborting')
setResolvedUrl(HANDLE, False, ListItem())
elif result.listitem:
listitem = convert_PKC_to_listitem(result.listitem)
setResolvedUrl(HANDLE, True, listitem)
return
modes = {
'reset': utils.reset,
'reset': reset,
'resetauth': entrypoint.resetAuth,
'play': entrypoint.doPlayback,
'passwords': utils.passwordsXML,
'passwords': passwordsXML,
'channels': entrypoint.BrowseChannels,
'channelsfolder': entrypoint.BrowseChannels,
'browsecontent': entrypoint.BrowseContent,
@ -79,7 +94,6 @@ class Main():
'inprogressepisodes': entrypoint.getInProgressEpisodes,
'recentepisodes': entrypoint.getRecentEpisodes,
'refreshplaylist': entrypoint.refreshPlaylist,
'companion': entrypoint.plexCompanion,
'switchuser': entrypoint.switchPlexUser,
'deviceid': entrypoint.resetDeviceId,
'delete': entrypoint.deleteItem,
@ -92,8 +106,8 @@ class Main():
'playwatchlater': entrypoint.playWatchLater
}
if "/extrafanart" in sys.argv[0]:
plexpath = sys.argv[2][1:]
if "/extrafanart" in ARGV[0]:
plexpath = ARGV[2][1:]
plexid = params.get('id', [""])[0]
entrypoint.getExtraFanArt(plexid, plexpath)
entrypoint.getVideoFiles(plexid, plexpath)
@ -101,11 +115,11 @@ class Main():
if mode == 'fanart':
log.info('User requested fanarttv refresh')
utils.window('plex_runLibScan', value='fanart')
window('plex_runLibScan', value='fanart')
# Called by e.g. 3rd party plugin video extras
if ("/Extras" in sys.argv[0] or "/VideoFiles" in sys.argv[0] or
"/Extras" in sys.argv[2]):
if ("/Extras" in ARGV[0] or "/VideoFiles" in ARGV[0] or
"/Extras" in ARGV[2]):
plexId = params.get('id', [None])[0]
entrypoint.getVideoFiles(plexId, params)
@ -143,7 +157,7 @@ class Main():
folderid = params['folderid'][0]
modes[mode](itemid, folderid)
elif mode == "companion":
modes[mode](itemid, params=sys.argv[2])
modes[mode](itemid, params=ARGV[2])
elif mode == 'playwatchlater':
modes[mode](params.get('id')[0], params.get('viewOffset')[0])
else:
@ -151,25 +165,24 @@ class Main():
else:
# Other functions
if mode == "settings":
xbmc.executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)')
elif mode in ("manualsync", "repair"):
if utils.window('plex_online') != "true":
if window('plex_online') != "true":
# Server is not online, do not run the sync
xbmcgui.Dialog().ok(
Dialog().ok(
"PlexKodiConnect",
"Unable to run the sync, the add-on is not connected "
"to a Plex server.")
log.error("Not connected to a PMS.")
else:
if mode == 'repair':
utils.window('plex_runLibScan', value="repair")
window('plex_runLibScan', value="repair")
log.info("Requesting repair lib sync")
elif mode == 'manualsync':
log.info("Requesting full library scan")
utils.window('plex_runLibScan', value="full")
window('plex_runLibScan', value="full")
elif mode == "texturecache":
utils.window('plex_runLibScan', value='del_textures')
window('plex_runLibScan', value='del_textures')
else:
entrypoint.doMainListing()

View file

@ -0,0 +1,334 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
from xbmcgui import ListItem
###############################################################################
log = logging.getLogger("PLEX."+__name__)
###############################################################################
def convert_PKC_to_listitem(PKC_listitem):
"""
Insert a PKC_listitem and you will receive a valid XBMC listitem
"""
listitem = ListItem()
for func, args in PKC_listitem.data.items():
if isinstance(args, list):
for arg in args:
getattr(listitem, func)(*arg)
elif isinstance(args, dict):
for arg in args.items():
getattr(listitem, func)(*arg)
elif args is None:
continue
else:
getattr(listitem, func)(args)
return listitem
class PKC_ListItem(object):
"""
Imitates xbmcgui.ListItem and its functions. Pass along PKC_Listitem().data
when pickling!
WARNING: set/get path only via setPath and getPath! (not getProperty)
"""
def __init__(self, label=None, label2=None, path=None):
self.data = {
'addStreamInfo': [], # (type, values: dict { label: value })
'setArt': [], # dict: { label: value }
'setInfo': {}, # type: infoLabel (dict { label: value })
'setLabel': label, # string
'setLabel2': label2, # string
'setPath': path, # string
'setProperty': {}, # (key, value)
'setSubtitles': [], # string
}
def addContextMenuItems(self, items, replaceItems):
"""
Adds item(s) to the context menu for media lists.
items : list - [(label, action,)*] A list of tuples consisting of label
and action pairs.
- label : string or unicode - item's label.
- action : string or unicode - any built-in function to perform.
replaceItes : [opt] bool - True=only your items will show/False=your
items will be amdded to context menu(Default).
List of functions - http://kodi.wiki/view/List_of_Built_In_Functions
*Note, You can use the above as keywords for arguments and skip
certain optional arguments.
Once you use a keyword, all following arguments require the keyword.
"""
raise NotImplementedError
def addStreamInfo(self, type, values):
"""
Add a stream with details.
type : string - type of stream(video/audio/subtitle).
values : dictionary - pairs of { label: value }.
- Video Values:
- codec : string (h264)
- aspect : float (1.78)
- width : integer (1280)
- height : integer (720)
- duration : integer (seconds)
- Audio Values:
- codec : string (dts)
- language : string (en)
- channels : integer (2)
- Subtitle Values:
- language : string (en)
"""
self.data['addStreamInfo'].append((type, values))
def getLabel(self):
"""
Returns the listitem label
"""
return self.data['setLabel']
def getLabel2(self):
"""
Returns the listitem label.
"""
return self.data['setLabel2']
def getMusicInfoTag(self):
"""
returns the MusicInfoTag for this item.
"""
raise NotImplementedError
def getProperty(self, key):
"""
Returns a listitem property as a string, similar to an infolabel.
key : string - property name.
*Note, Key is NOT case sensitive.
You can use the above as keywords for arguments and skip certain
optional arguments.
Once you use a keyword, all following arguments require the keyword.
"""
return self.data['setProperty'].get(key)
def getVideoInfoTag(self):
"""
returns the VideoInfoTag for this item
"""
raise NotImplementedError
def getdescription(self):
"""
Returns the description of this PlayListItem
"""
raise NotImplementedError
def getduration(self):
"""
Returns the duration of this PlayListItem
"""
raise NotImplementedError
def getfilename(self):
"""
Returns the filename of this PlayListItem.
"""
raise NotImplementedError
def isSelected(self):
"""
Returns the listitem's selected status
"""
raise NotImplementedError
def select(self):
"""
Sets the listitem's selected status.
selected : bool - True=selected/False=not selected
"""
raise NotImplementedError
def setArt(self, values):
"""
Sets the listitem's art
values : dictionary - pairs of { label: value }.
Some default art values (any string possible):
- thumb : string - image filename
- poster : string - image filename
- banner : string - image filename
- fanart : string - image filename
- clearart : string - image filename
- clearlogo : string - image filename
- landscape : string - image filename
- icon : string - image filename
"""
self.data['setArt'].append(values)
def setContentLookup(self, enable):
"""
Enable or disable content lookup for item.
If disabled, HEAD requests to e.g determine mime type will not be sent.
enable : bool
"""
raise NotImplementedError
def setInfo(self, type, infoLabels):
"""
type : string - type of media(video/music/pictures).
infoLabels : dictionary - pairs of { label: value }. *Note, To set
pictures exif info, prepend 'exif:' to the label. Exif values must be
passed as strings, separate value pairs with a comma. (eg.
{'exif:resolution': '720,480'}
See CPictureInfoTag::TranslateString in PictureInfoTag.cpp for valid
strings. You can use the above as keywords for arguments and skip
certain optional arguments.
Once you use a keyword, all following arguments require the keyword.
- General Values that apply to all types:
- count : integer (12) - can be used to store an id for later, or
for sorting purposes
- size : long (1024) - size in bytes
- date : string (d.m.Y / 01.01.2009) - file date
- Video Values:
- genre : string (Comedy)
- year : integer (2009)
- episode : integer (4)
- season : integer (1)
- top250 : integer (192)
- tracknumber : integer (3)
- rating : float (6.4) - range is 0..10
- userrating : integer (9) - range is 1..10
- watched : depreciated - use playcount instead
- playcount : integer (2) - number of times this item has been
played
- overlay : integer (2) - range is 0..8. See GUIListItem.h for
values
- cast : list (["Michal C. Hall","Jennifer Carpenter"]) - if
provided a list of tuples cast will be interpreted as castandrole
- castandrole : list of tuples ([("Michael C.
Hall","Dexter"),("Jennifer Carpenter","Debra")])
- director : string (Dagur Kari)
- mpaa : string (PG-13)
- plot : string (Long Description)
- plotoutline : string (Short Description)
- title : string (Big Fan)
- originaltitle : string (Big Fan)
- sorttitle : string (Big Fan)
- duration : integer (245) - duration in seconds
- studio : string (Warner Bros.)
- tagline : string (An awesome movie) - short description of movie
- writer : string (Robert D. Siegel)
- tvshowtitle : string (Heroes)
- premiered : string (2005-03-04)
- status : string (Continuing) - status of a TVshow
- code : string (tt0110293) - IMDb code
- aired : string (2008-12-07)
- credits : string (Andy Kaufman) - writing credits
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
- album : string (The Joshua Tree)
- artist : list (['U2'])
- votes : string (12345 votes)
- trailer : string (/home/user/trailer.avi)
- dateadded : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
- mediatype : string - "video", "movie", "tvshow", "season",
"episode" or "musicvideo"
- Music Values:
- tracknumber : integer (8)
- discnumber : integer (2)
- duration : integer (245) - duration in seconds
- year : integer (1998)
- genre : string (Rock)
- album : string (Pulse)
- artist : string (Muse)
- title : string (American Pie)
- rating : string (3) - single character between 0 and 5
- lyrics : string (On a dark desert highway...)
- playcount : integer (2) - number of times this item has been
played
- lastplayed : string (Y-m-d h:m:s = 2009-04-05 23:16:04)
- Picture Values:
- title : string (In the last summer-1)
- picturepath : string (/home/username/pictures/img001.jpg)
- exif : string (See CPictureInfoTag::TranslateString in
PictureInfoTag.cpp for valid strings)
"""
self.data['setInfo'][type] = infoLabels
def setLabel(self, label):
"""
Sets the listitem's label.
label : string or unicode - text string.
"""
self.data['setLabel'] = label
def setLabel2(self, label):
"""
Sets the listitem's label2.
label : string or unicode - text string.
"""
self.data['setLabel2'] = label
def setMimeType(self, mimetype):
"""
Sets the listitem's mimetype if known.
mimetype : string or unicode - mimetype.
If known prehand, this can (but does not have to) avoid HEAD requests
being sent to HTTP servers to figure out file type.
"""
raise NotImplementedError
def setPath(self, path):
"""
Sets the listitem's path.
path : string or unicode - path, activated when item is clicked.
*Note, You can use the above as keywords for arguments.
"""
self.data['setPath'] = path
def setProperty(self, key, value):
"""
Sets a listitem property, similar to an infolabel.
key : string - property name.
value : string or unicode - value of property.
*Note, Key is NOT case sensitive.
You can use the above as keywords for arguments and skip certain
optional arguments. Once you use a keyword, all following arguments
require the keyword.
Some of these are treated internally by XBMC, such as the
'StartOffset' property, which is the offset in seconds at which to
start playback of an item. Others may be used in the skin to add extra
information, such as 'WatchedCount' for tvshow items
"""
self.data['setProperty'][key] = value
def setSubtitles(self, subtitles):
"""
Sets subtitles for this listitem. Pass in a list of filepaths
example:
- listitem.setSubtitles(['special://temp/example.srt',
'http://example.com/example.srt' ])
"""
self.data['setSubtitles'].extend(([subtitles],))

View file

@ -2393,7 +2393,7 @@ class API():
# listItem.setProperty('isPlayable', 'true')
# listItem.setProperty('isFolder', 'true')
# Further stuff
listItem.setIconImage('DefaultPicture.png')
listItem.setArt({'icon': 'DefaultPicture.png'})
return listItem
def _createVideoListItem(self,
@ -2456,14 +2456,14 @@ class API():
"s%.2de%.2d" % (season, episode))
if appendSxxExx is True:
title = "S%.2dE%.2d - %s" % (season, episode, title)
listItem.setIconImage('DefaultTVShows.png')
listItem.setArt({'icon': 'DefaultTVShows.png'})
if appendShowTitle is True:
title = "%s - %s " % (show, title)
elif typus == "movie":
listItem.setIconImage('DefaultMovies.png')
listItem.setArt({'icon': 'DefaultMovies.png'})
else:
# E.g. clips, trailers, ...
listItem.setIconImage('DefaultVideo.png')
listItem.setArt({'icon': 'DefaultVideo.png'})
plexId = self.getRatingKey()
listItem.setProperty('plexid', plexId)

View file

@ -85,19 +85,11 @@ class PlexCompanion(Thread):
return
playqueue = self.mgr.playqueue.get_playqueue_from_type(
data['type'])
if ID != playqueue.ID:
# playqueue changed somehow
self.mgr.playqueue.update_playqueue_from_PMS(
playqueue,
ID,
query.get('repeat'),
data.get('offset'))
else:
# No change to the playqueue
self.mgr.playqueue.start_playqueue_initiated_by_companion(
playqueue,
query.get('repeat'),
data.get('offset'))
repeat=query.get('repeat'),
offset=data.get('offset'))
def run(self):
httpd = False

View file

@ -212,6 +212,6 @@ class ContextMenu(object):
'mode': "play"
}
from urllib import urlencode
handle = ("plugin://plugin.video.plexkodiconnect.movies?%s"
handle = ("plugin://plugin.video.plexkodiconnect/movies?%s"
% urlencode(params))
xbmc.executebuiltin('RunPlugin(%s)' % handle)

View file

@ -67,7 +67,7 @@ class UsersConnect(xbmcgui.WindowXMLDialog):
if self.kodi_version > 15:
item.setArt({'Icon': user_image})
else:
item.setIconImage(user_image)
item.setArt({'icon': user_image})
return item

View file

@ -32,40 +32,6 @@ addonName = "PlexKodiConnect"
###############################################################################
def plexCompanion(fullurl, params):
params = PlexFunctions.LiteralEval(params[26:])
if params['machineIdentifier'] != window('plex_machineIdentifier'):
log.error("Command was not for us, machineIdentifier controller: %s, "
"our machineIdentifier : %s"
% (params['machineIdentifier'],
window('plex_machineIdentifier')))
return
library, key, query = PlexFunctions.ParseContainerKey(
params['containerKey'])
# Construct a container key that works always (get rid of playlist args)
window('containerKey', '/'+library+'/'+key)
if 'playQueues' in library:
log.debug("Playing a playQueue. Query was: %s" % query)
# Playing a playlist that we need to fetch from PMS
xml = PlexFunctions.GetPlayQueue(key)
if xml is None:
log.error("Error getting PMS playlist for key %s" % key)
return
else:
resume = PlexFunctions.ConvertPlexToKodiTime(
params.get('offset', 0))
itemids = []
for item in xml:
itemids.append(item.get('ratingKey'))
return playlist.Playlist().playAll(itemids, resume)
else:
log.error("Not knowing what to do for now - no playQueue sent")
def chooseServer():
"""
Lets user choose from list of PMS
@ -180,40 +146,6 @@ def playWatchLater(itemid, viewOffset):
return pbutils.PlaybackUtils(xml).play(None, 'plexnode')
def doPlayback(itemid, dbid):
"""
Called only for a SINGLE element, not playQueues
Always to return with a "setResolvedUrl"
"""
if window('plex_authenticated') != "true":
log.error('Not yet authenticated for a PMS, abort starting playback')
# Not yet connected to a PMS server
xbmcgui.Dialog().notification(
addonName,
lang(39210),
xbmcgui.NOTIFICATION_ERROR,
7000,
True)
return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, xbmcgui.ListItem())
xml = PlexFunctions.GetPlexMetadata(itemid)
if xml in (None, 401):
return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, xbmcgui.ListItem())
if xml[0].attrib.get('type') == 'photo':
# Photo
API = PlexAPI.API(xml[0])
listitem = API.CreateListItemFromPlexItem()
API.AddStreamInfo(listitem)
pbutils.PlaybackUtils(xml[0]).setArtwork(listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
else:
# Video
return pbutils.PlaybackUtils(xml).play(itemid, dbid)
##### DO RESET AUTH #####
def resetAuth():
# User tried login and failed too many times
@ -497,7 +429,7 @@ def createListItemFromEmbyItem(item,art=None,doUtils=downloadutils.DownloadUtils
li.setInfo('pictures', infoLabels={ "picturepath": img_path, "date": premieredate, "size": picture.get("Size"), "exif:width": str(picture.get("Width")), "exif:height": str(picture.get("Height")), "title": title})
li.setThumbnailImage(img_path)
li.setProperty("plot",API.getOverview())
li.setIconImage('DefaultPicture.png')
li.setArt({'icon': 'DefaultPicture.png'})
else:
#normal video items
li.setProperty('IsPlayable', 'true')
@ -541,7 +473,7 @@ def createListItemFromEmbyItem(item,art=None,doUtils=downloadutils.DownloadUtils
if allart.get('Primary'):
li.setThumbnailImage(allart.get('Primary'))
else: li.setThumbnailImage('DefaultTVShows.png')
li.setIconImage('DefaultTVShows.png')
li.setArt({'icon': 'DefaultTVShows.png'})
if not allart.get('Background'): #add image as fanart for use with skinhelper auto thumb/backgrund creation
li.setArt( {"fanart": allart.get('Primary') } )
else:
@ -663,7 +595,7 @@ def createListItem(item, appendShowTitle=False, appendSxxExx=False):
li.setProperty('totaltime', str(item['resume']['total']))
li.setArt(item['art'])
li.setThumbnailImage(item['art'].get('thumb',''))
li.setIconImage('DefaultTVShows.png')
li.setArt({'icon': 'DefaultTVShows.png'})
li.setProperty('dbid', str(item['episodeid']))
li.setProperty('fanart_image', item['art'].get('tvshow.fanart',''))
for key, value in item['streamdetails'].iteritems():
@ -1167,7 +1099,7 @@ def getOnDeck(viewid, mediatype, tagname, limit):
'id': API.getRatingKey(),
'dbid': listitem.getProperty('dbid')
}
url = "plugin://plugin.video.plexkodiconnect.tvshows/?%s" \
url = "plugin://plugin.video.plexkodiconnect/tvshows/?%s" \
% urllib.urlencode(params)
xbmcplugin.addDirectoryItem(
handle=int(sys.argv[1]),

View file

@ -407,7 +407,7 @@ class Movies(Items):
path = playurl.replace(filename, "")
if doIndirect:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.plexkodiconnect.movies/"
path = "plugin://plugin.video.plexkodiconnect/movies/"
params = {
'filename': API.getKey(),
'id': itemid,
@ -675,7 +675,7 @@ class TVShows(Items):
toplevelpath = "%s/" % dirname(dirname(path))
if doIndirect:
# Set plugin path
toplevelpath = "plugin://plugin.video.plexkodiconnect.tvshows/"
toplevelpath = "plugin://plugin.video.plexkodiconnect/tvshows/"
path = "%s%s/" % (toplevelpath, itemid)
# Add top path
@ -956,7 +956,7 @@ class TVShows(Items):
filename = playurl.rsplit('/', 1)[1]
else:
filename = 'file_not_found.mkv'
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
path = "plugin://plugin.video.plexkodiconnect/tvshows/%s/" % seriesId
params = {
'filename': tryEncode(filename),
'id': itemid,
@ -966,7 +966,7 @@ class TVShows(Items):
filename = "%s?%s" % (path, tryDecode(urlencode(params)))
playurl = filename
parentPathId = self.kodi_db.addPath(
'plugin://plugin.video.plexkodiconnect.tvshows/')
'plugin://plugin.video.plexkodiconnect/tvshows/')
# episodes table:
# c18 - playurl
@ -1093,7 +1093,7 @@ class TVShows(Items):
self.kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
if not self.directpath and resume:
# Create additional entry for widgets. This is only required for plugin/episode.
temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
temppathid = self.kodi_db.getPath("plugin://plugin.video.plexkodiconnect/tvshows/")
tempfileid = self.kodi_db.addFile(filename, temppathid)
query = ' '.join((

View file

@ -61,7 +61,7 @@ class Kodidb_Functions():
self.cursor.execute(
query, ('movies',
'metadata.local',
'plugin://plugin.video.plexkodiconnect.movies%%'))
'plugin://plugin.video.plexkodiconnect/movies%%'))
def getParentPathId(self, path):
"""

View file

@ -164,20 +164,6 @@ class KodiMonitor(xbmc.Monitor):
xbmc.sleep(5000)
window('plex_runLibScan', value="full")
elif method == "Playlist.OnClear":
pass
elif method == "Playlist.OnAdd":
# User (or PKC) manipulated Kodi playlist
# Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1,
# u'position': 0}
self.playqueue.kodi_onadd(data)
elif method == "Playlist.OnRemove":
# User (or PKC) deleted a playlist item
# Data: {u'position': 2, u'playlistid': 1}
self.playqueue.kodi_onremove(data)
def PlayBackStart(self, data):
"""
Called whenever a playback is started

View file

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
from threading import Thread
from Queue import Queue
from xbmc import sleep
from utils import window, ThreadMethods
###############################################################################
log = logging.getLogger("PLEX."+__name__)
###############################################################################
@ThreadMethods
class Monitor_Kodi_Play(Thread):
"""
Monitors for new plays initiated on the Kodi side with addon paths.
Immediately throws them into a queue to be processed by playback_starter
"""
# Borg - multiple instances, shared state
def __init__(self, callback=None):
self.mgr = callback
self.playback_queue = Queue()
Thread.__init__(self)
def run(self):
threadStopped = self.threadStopped
queue = self.playback_queue
log.info("----===## Starting Kodi_Play_Client ##===----")
while not threadStopped():
if window('plex_play_new_item'):
queue.put(window('plex_play_new_item'))
window('plex_play_new_item', clear=True)
else:
sleep(20)
# Put one last item into the queue to let playback_starter end
queue.put(None)
log.info("----===## Kodi_Play_Client stopped ##===----")

44
resources/lib/pickler.py Normal file
View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
import cPickle as Pickle
from utils import pickl_window
###############################################################################
log = logging.getLogger("PLEX."+__name__)
###############################################################################
def pickle_me(obj, window_var='plex_result'):
"""
Pickles the obj to the window variable. Use to transfer Python
objects between different PKC python instances (e.g. if default.py is
called and you'd want to use the service.py instance)
obj can be pretty much any Python object. However, classes and
functions won't work. See the Pickle documentation
"""
log.debug('Start pickling: %s' % obj)
pickl_window(window_var, value=Pickle.dumps(obj))
log.debug('Successfully pickled')
def unpickle_me(window_var='plex_result'):
"""
Unpickles a Python object from the window variable window_var.
Will then clear the window variable!
"""
result = pickl_window(window_var)
pickl_window(window_var, clear=True)
log.debug('Start unpickling')
obj = Pickle.loads(result)
log.debug('Successfully unpickled: %s' % obj)
return obj
class Playback_Successful(object):
"""
Used to communicate with another PKC Python instance
"""
listitem = None

View file

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
from threading import Thread
from urlparse import parse_qsl
from PKC_listitem import PKC_ListItem
from pickler import pickle_me, Playback_Successful
from playbackutils import PlaybackUtils
from utils import window
from PlexFunctions import GetPlexMetadata
from PlexAPI import API
from playqueue import lock
###############################################################################
log = logging.getLogger("PLEX."+__name__)
###############################################################################
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))
if window('plex_authenticated') != "true":
log.error('Not yet authenticated for PMS, abort starting playback')
# Todo: Warn user with dialog
return
xml = GetPlexMetadata(plex_id)
if xml[0].attrib.get('type') == 'photo':
# Photo
result = Playback_Successful()
listitem = PKC_ListItem()
api = API(xml[0])
listitem = api.CreateListItemFromPlexItem(listitem)
api.AddStreamInfo(listitem)
listitem = PlaybackUtils(xml[0], self.mgr).setArtwork(listitem)
result.listitem = listitem
else:
# Video and Music
with lock:
result = PlaybackUtils(xml[0], self.mgr).play(
plex_id,
kodi_id,
xml.attrib.get('librarySectionUUID'))
log.info('Done process_play, playqueues: %s'
% self.playqueue.playqueues)
return result
def triage(self, item):
mode, params = item.split('?', 1)
params = dict(parse_qsl(params))
log.debug('Received mode: %s, params: %s' % (mode, params))
try:
if mode == 'play':
result = self.process_play(params.get('id'),
params.get('dbid'))
elif mode == 'companion':
result = self.process_companion()
except:
log.error('Error encountered for mode %s, params %s'
% (mode, params))
import traceback
log.error(traceback.format_exc())
# Let default.py know!
pickle_me(None)
else:
pickle_me(result)
def run(self):
queue = self.mgr.monitor_kodi_play.playback_queue
log.info("----===## Starting Playback_Starter ##===----")
while True:
item = queue.get()
if item is None:
# Need to shutdown - initiated by monitor_kodi_play
break
else:
self.triage(item)
queue.task_done()
log.info("----===## Playback_Starter stopped ##===----")

View file

@ -3,21 +3,25 @@
###############################################################################
import logging
import sys
from urllib import urlencode
from threading import Thread
import xbmc
from xbmc import getCondVisibility, Player
import xbmcgui
import xbmcplugin
import playutils as putils
from playqueue import Playqueue
from utils import window, settings, tryEncode, tryDecode
import downloadutils
import PlexAPI
import PlexFunctions as PF
import playlist_func as PL
from PlexAPI import API
from PlexFunctions import GetPlexPlaylist, KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE, \
KODITYPE_FROM_PLEXTYPE
from PKC_listitem import PKC_ListItem as ListItem
from playlist_func import add_item_to_kodi_playlist, \
get_playlist_details_from_xml, add_listitem_to_Kodi_playlist, \
add_listitem_to_playlist, remove_from_Kodi_playlist
from playqueue import lock
from pickler import Playback_Successful
###############################################################################
@ -30,47 +34,47 @@ addonName = "PlexKodiConnect"
class PlaybackUtils():
def __init__(self, item):
def __init__(self, item, callback):
self.mgr = callback
self.item = item
self.API = PlexAPI.API(item)
self.userid = window('currUserId')
self.server = window('pms_server')
self.pl = Playqueue().get_playqueue_from_type(
PF.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[item[0].attrib.get('type')])
def play(self, itemid, dbid=None):
self.api = API(item)
self.playqueue = self.mgr.playqueue.get_playqueue_from_type(
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[self.api.getType()])
def play(self, plex_id, kodi_id=None, plex_lib_UUID=None):
"""
plex_lib_UUID: xml attribute 'librarySectionUUID', needed for posting
to the PMS
"""
log.info("Playbackutils called")
item = self.item
# Hack to get only existing entry in PMS response for THIS instance of
# playbackutils :-)
self.API = PlexAPI.API(item[0])
API = self.API
listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item[0])
log.info("Play called.")
log.debug('Playqueue: %s' % self.pl)
api = self.api
playqueue = self.playqueue
xml = None
result = Playback_Successful()
listitem = ListItem()
playutils = putils.PlayUtils(item)
playurl = playutils.getPlayUrl()
if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
log.error('No playurl found, aborting')
return
if dbid in (None, 'plextrailer', 'plexnode'):
# Item is not in Kodi database, is a trailer or plex redirect
if kodi_id in (None, 'plextrailer', 'plexnode'):
# Item is not in Kodi database, is a trailer/clip or plex redirect
# e.g. plex.tv watch later
API.CreateListItemFromPlexItem(listitem)
api.CreateListItemFromPlexItem(listitem)
self.setArtwork(listitem)
if dbid == 'plexnode':
if kodi_id == 'plexnode':
# Need to get yet another xml to get final url
window('emby_%s.playmethod' % playurl, clear=True)
xml = downloadutils.DownloadUtils().downloadUrl(
'{server}%s' % item[0][0][0].attrib.get('key'))
if xml in (None, 401):
'{server}%s' % item[0][0].attrib.get('key'))
try:
xml[0].attrib
except (TypeError, AttributeError):
log.error('Could not download %s'
% item[0][0][0].attrib.get('key'))
return xbmcplugin.setResolvedUrl(
int(sys.argv[1]), False, listitem)
% item[0][0].attrib.get('key'))
return
playurl = tryEncode(xml[0].attrib.get('key'))
window('emby_%s.playmethod' % playurl, value='DirectStream')
@ -82,20 +86,23 @@ class PlaybackUtils():
window('emby_%s.playmethod' % playurl, "Transcode")
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
result.listitem = listitem
return result
############### ORGANIZE CURRENT PLAYLIST ################
kodi_type = KODITYPE_FROM_PLEXTYPE[api.getType()]
kodi_id = int(kodi_id)
# ORGANIZE CURRENT PLAYLIST ################
contextmenu_play = window('plex_contextplay') == 'true'
window('plex_contextplay', clear=True)
homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
kodiPl = self.pl.kodi_pl
sizePlaylist = kodiPl.size()
homeScreen = getCondVisibility('Window.IsActive(home)')
sizePlaylist = len(playqueue.items)
if contextmenu_play:
# Need to start with the items we're inserting here
startPos = sizePlaylist
else:
# Can return -1
startPos = max(kodiPl.getposition(), 0)
startPos = max(playqueue.kodi_pl.getposition(), 0)
self.currentPosition = startPos
propertiesPlayback = window('plex_playbackProps') == "true"
@ -107,8 +114,8 @@ class PlaybackUtils():
log.info("Playlist plugin position: %s" % self.currentPosition)
log.info("Playlist size: %s" % sizePlaylist)
############### RESUME POINT ################
seektime, runtime = API.getRuntime()
# RESUME POINT ################
seektime, runtime = api.getRuntime()
if window('plex_customplaylist.seektime'):
# Already got seektime, e.g. from playqueue & Plex companion
seektime = int(window('plex_customplaylist.seektime'))
@ -116,64 +123,69 @@ class PlaybackUtils():
# We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop.
if not propertiesPlayback:
window('plex_playbackProps', value="true")
log.info("Setting up properties in playlist.")
# Post playQueue to PMS
# Where will the player need to start?
# Do we need to get trailers?
trailers = False
if settings('enableCinema') == "true":
if (api.getType() == 'movie' and not seektime and
settings('enableCinema') == "true"):
if settings('askCinema') == "true":
trailers = xbmcgui.Dialog().yesno(addonName,
trailers = xbmcgui.Dialog().yesno(
addonName,
"Play trailers?")
else:
trailers = True
xml = PF.GetPlexPlaylist(
itemid,
item.attrib.get('librarySectionUUID'),
mediatype=API.getType(),
# Post to the PMS. REUSE THE PLAYQUEUE!
xml = GetPlexPlaylist(
plex_id,
plex_lib_UUID,
mediatype=api.getType(),
trailers=trailers)
# Save playQueueID for other PKC python instance & kodimonitor
window('plex_playQueueID', value=xml.attrib.get('playQueueID'))
log.debug('xml: ID: %s' % xml.attrib['playQueueID'])
get_playlist_details_from_xml(playqueue, xml=xml)
log.debug('finished ')
if (not homeScreen and not seektime and
window('plex_customplaylist') != "true" and
not contextmenu_play):
# Need to add a dummy file because the first item will fail
log.debug("Adding dummy file to playlist.")
# Make sure Kodimonitor recognizes dummy
listitem.setLabel('plex_dummyfile')
dummyPlaylist = True
PL.add_listitem_to_Kodi_playlist(
self.pl,
listitem,
add_listitem_to_Kodi_playlist(
playqueue,
startPos,
xbmcgui.ListItem(),
playurl,
startPos)
xml[0])
# Remove the original item from playlist
PL.remove_from_Kodi_playlist(self.pl, startPos+1)
remove_from_Kodi_playlist(
playqueue,
startPos+1)
# Readd the original item to playlist - via jsonrpc so we have
# full metadata
PL.insert_into_Kodi_playlist(
self.pl,
add_item_to_kodi_playlist(
playqueue,
self.currentPosition+1,
dbid,
PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
kodi_id=kodi_id,
kodi_type=kodi_type,
file=playurl)
self.currentPosition += 1
############### -- CHECK FOR INTROS ################
if trailers and not seektime:
# if we have any play them when the movie/show is not being resumed
# -- ADD TRAILERS ################
if trailers:
introsPlaylist = self.AddTrailers(xml)
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
# -- ADD MAIN ITEM ONLY FOR HOMESCREEN ##############
if homeScreen and not seektime and not sizePlaylist:
# Extend our current playlist with the actual item to play
# only if there's no playlist first
log.info("Adding main item to playlist.")
PL.add_dbid_to_Kodi_playlist(
self.pl,
dbid=dbid,
mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
add_item_to_kodi_playlist(
playqueue,
self.currentPosition,
kodi_id,
kodi_type)
elif contextmenu_play:
if window('useDirectPaths') == 'true':
@ -187,17 +199,16 @@ class PlaybackUtils():
listitem, tryDecode(playurl)))
window('emby_%s.playmethod' % playurl,
value="Transcode")
api.CreateListItemFromPlexItem(listitem)
self.setProperties(playurl, listitem)
self.setArtwork(listitem)
API.CreateListItemFromPlexItem(listitem)
kodiPl.add(playurl, listitem, index=self.currentPosition+1)
else:
# Full metadata
PL.insert_into_Kodi_playlist(
self.pl,
self.pl.insertintoPlaylist(
self.currentPosition+1,
dbid=dbid,
mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()])
kodi_id,
kodi_type)
self.currentPosition += 1
if seektime:
window('plex_customplaylist.seektime', value=str(seektime))
@ -205,44 +216,50 @@ class PlaybackUtils():
# Ensure that additional parts are played after the main item
self.currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if len(item[0][0]) > 1:
# -- CHECK FOR ADDITIONAL PARTS ################
if len(item[0]) > 1:
# Only add to the playlist after intros have played
for counter, part in enumerate(item[0][0]):
for counter, part in enumerate(item[0]):
# Never add first part
if counter == 0:
continue
# Set listitem and properties for each additional parts
API.setPartNumber(counter)
api.setPartNumber(counter)
additionalListItem = xbmcgui.ListItem()
additionalPlayurl = playutils.getPlayUrl(
partNumber=counter)
log.debug("Adding additional part: %s, url: %s"
% (counter, additionalPlayurl))
api.CreateListItemFromPlexItem(additionalListItem)
self.setProperties(additionalPlayurl, additionalListItem)
self.setArtwork(additionalListItem)
# NEW to Plex
API.CreateListItemFromPlexItem(additionalListItem)
kodiPl.add(additionalPlayurl, additionalListItem,
index=self.currentPosition)
add_listitem_to_playlist(
playqueue,
self.currentPosition,
additionalListItem,
kodi_id=kodi_id,
kodi_type=kodi_type,
plex_id=plex_id,
file=additionalPlayurl)
self.currentPosition += 1
API.setPartNumber(0)
api.setPartNumber(0)
if dummyPlaylist:
# Added a dummy file to the playlist,
# because the first item is going to fail automatically.
log.info("Processed as a playlist. First item is skipped.")
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
# Delete the item that's gonna fail!
with lock:
del playqueue.items[startPos]
# Don't attach listitem
return result
# We just skipped adding properties. Reset flag for next time.
elif propertiesPlayback:
log.debug("Resetting properties playback flag.")
window('plex_playbackProps', clear=True)
#self.pl.verifyPlaylist()
########## SETUP MAIN ITEM ##########
# SETUP MAIN ITEM ##########
# For transcoding only, ask for audio/subs pref
if (window('emby_%s.playmethod' % playurl) == "Transcode" and
not contextmenu_play):
@ -254,40 +271,42 @@ class PlaybackUtils():
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
############### PLAYBACK ################
# PLAYBACK ################
if (homeScreen and seektime and window('plex_customplaylist') != "true"
and not contextmenu_play):
log.info("Play as a widget item.")
API.CreateListItemFromPlexItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
log.info("Play as a widget item")
api.CreateListItemFromPlexItem(listitem)
result.listitem = listitem
return result
elif ((introsPlaylist and window('plex_customplaylist') == "true") or
(homeScreen and not sizePlaylist) or
contextmenu_play):
# Playlist was created just now, play it.
# Contextmenu plays always need this
log.info("Play playlist.")
xbmcplugin.endOfDirectory(int(sys.argv[1]), True, False, False)
xbmc.Player().play(kodiPl, startpos=startPos)
log.info("Play playlist")
# Need a separate thread because Player won't return in time
thread = Thread(target=Player().play,
args=(playqueue.kodi_pl, None, False, startPos))
thread.setDaemon(True)
thread.start()
# Don't attach listitem
return result
else:
log.info("Play as a regular item.")
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
log.info("Play as a regular item")
result.listitem = listitem
return result
def AddTrailers(self, xml):
"""
Adds trailers to a movie, if applicable. Returns True if trailers were
added
"""
# Failure when downloading trailer playQueue
if xml in (None, 401):
return False
# Failure when getting trailers, e.g. when no plex pass
if xml.attrib.get('size') == '1':
return False
# Playurl needs to point back so we can get metadata!
path = "plugin://plugin.video.plexkodiconnect.movies/"
path = "plugin://plugin.video.plexkodiconnect/movies/"
params = {
'mode': "play",
'dbid': 'plextrailer'
@ -296,28 +315,29 @@ class PlaybackUtils():
# Don't process the last item - it's the original movie
if counter == len(xml)-1:
break
# The server randomly returns intros, process them.
# introListItem = xbmcgui.ListItem()
# introPlayurl = putils.PlayUtils(intro).getPlayUrl()
introAPI = PlexAPI.API(intro)
introAPI = API(intro)
listitem = introAPI.CreateListItemFromPlexItem()
params['id'] = introAPI.getRatingKey()
params['filename'] = introAPI.getKey()
introPlayurl = path + '?' + urlencode(params)
self.setArtwork(listitem, introAPI)
# Overwrite the Plex url
listitem.setPath(introPlayurl)
log.info("Adding Intro: %s" % introPlayurl)
PL.insert_into_Kodi_playlist(
self.pl,
add_listitem_to_Kodi_playlist(
self.playqueue,
self.currentPosition,
url=introPlayurl)
listitem,
introPlayurl,
intro)
self.currentPosition += 1
return True
def setProperties(self, playurl, listitem):
# Set all properties necessary for plugin path playback
itemid = self.API.getRatingKey()
itemtype = self.API.getType()
userdata = self.API.getUserData()
itemid = self.api.getRatingKey()
itemtype = self.api.getType()
userdata = self.api.getUserData()
embyitem = "emby_%s" % playurl
window('%s.runtime' % embyitem, value=str(userdata['Runtime']))
@ -327,20 +347,22 @@ class PlaybackUtils():
if itemtype == "episode":
window('%s.refreshid' % embyitem,
value=self.API.getParentRatingKey())
value=self.api.getParentRatingKey())
else:
window('%s.refreshid' % embyitem, value=itemid)
# Append external subtitles to stream
playmethod = window('%s.playmethod' % embyitem)
if playmethod in ("DirectStream", "DirectPlay"):
subtitles = self.API.externalSubs(playurl)
subtitles = self.api.externalSubs(playurl)
listitem.setSubtitles(subtitles)
self.setArtwork(listitem)
def setArtwork(self, listItem):
allartwork = self.API.getAllArtwork(parentInfo=True)
def setArtwork(self, listItem, api=None):
if api is None:
api = self.api
allartwork = api.getAllArtwork(parentInfo=True)
arttypes = {
'poster': "Primary",
'tvshow.poster': "Thumb",

View file

@ -3,7 +3,7 @@ from urllib import quote
import embydb_functions as embydb
from downloadutils import DownloadUtils as DU
from utils import window, JSONRPC, tryEncode
from utils import JSONRPC, tryEncode, tryDecode
from PlexAPI import API
###############################################################################
@ -12,6 +12,9 @@ log = logging.getLogger("PLEX."+__name__)
###############################################################################
# kodi_item:
# {u'type': u'movie', u'id': 3, 'file': path-to-file}
class Playlist_Object_Baseclase(object):
playlistid = None # Kodi playlist ID, [int]
@ -25,8 +28,6 @@ class Playlist_Object_Baseclase(object):
selectedItemOffset = None
shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ???
repeat = 0 # [int], 0: not repeated, 1: ??? 2: ???
# Hack to later ignore all Kodi playlist adds that PKC did (Kodimonitor)
PKC_playlist_edits = []
def __repr__(self):
answ = "<%s: " % (self.__class__.__name__)
@ -52,7 +53,6 @@ class Playlist_Object_Baseclase(object):
self.selectedItemOffset = None
self.shuffled = 0
self.repeat = 0
self.PKC_playlist_edits = []
log.debug('Playlist cleared: %s' % self)
def log_Kodi_playlist(self):
@ -84,7 +84,7 @@ class Playlist_Item(object):
return answ[:-2] + ">"
def playlist_item_from_kodi_item(kodi_item):
def playlist_item_from_kodi(kodi_item):
"""
Turns the JSON answer from Kodi into a playlist element
@ -99,14 +99,15 @@ def playlist_item_from_kodi_item(kodi_item):
kodi_item['type'])
try:
item.plex_id = emby_dbitem[0]
item.plex_UUID = emby_dbitem[0]
item.plex_UUID = emby_dbitem[0] # we dont need the uuid yet :-)
except TypeError:
pass
item.file = kodi_item.get('file') if kodi_item.get('file') else None
item.kodi_type = kodi_item.get('type') if kodi_item.get('type') else None
item.file = kodi_item.get('file')
item.kodi_type = kodi_item.get('type')
if item.plex_id is None:
item.uri = 'library://whatever/item/%s' % quote(item.file, safe='')
else:
# TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER
item.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' %
(item.plex_UUID, item.plex_id))
return item
@ -115,6 +116,8 @@ def playlist_item_from_kodi_item(kodi_item):
def playlist_item_from_plex(plex_id):
"""
Returns a playlist element providing the plex_id ("ratingKey")
Returns a Playlist_Item
"""
item = Playlist_Item()
item.plex_id = plex_id
@ -128,6 +131,26 @@ def playlist_item_from_plex(plex_id):
return item
def playlist_item_from_xml(playlist, xml_video_element):
"""
Returns a playlist element for the playqueue using the Plex xml
"""
item = Playlist_Item()
api = API(xml_video_element)
item.plex_id = api.getRatingKey()
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
item.guid = xml_video_element.attrib.get('guid')
if item.plex_id:
with embydb.GetEmbyDB() as emby_db:
db_element = emby_db.getItem_byId(item.plex_id)
try:
item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4]
except TypeError:
pass
log.debug('Created new playlist item from xml: %s' % item)
return item
def _log_xml(xml):
try:
xml.attrib
@ -154,7 +177,7 @@ def _get_playListVersion_from_xml(playlist, xml):
return True
def _get_playlist_details_from_xml(playlist, xml):
def get_playlist_details_from_xml(playlist, xml):
"""
Takes a PMS xml as input and overwrites all the playlist's details, e.g.
playlist.ID with the XML's playQueueID
@ -174,16 +197,42 @@ def _get_playlist_details_from_xml(playlist, xml):
log.error(traceback.format_exc())
_log_xml(xml)
raise KeyError
log.debug('Updated playlist from xml: %s' % playlist)
def update_playlist_from_PMS(playlist, playlist_id=None, xml=None):
"""
Updates Kodi playlist using a new PMS playlist. Pass in playlist_id if we
need to fetch a new playqueue
If an xml is passed in, the playlist will be overwritten with its info
"""
if xml is None:
xml = get_PMS_playlist(playlist, playlist_id)
try:
xml.attrib['%sVersion' % playlist.kind]
except:
log.error('Could not download Plex playlist.')
return
# Clear our existing playlist and the associated Kodi playlist
playlist.clear()
# Set new values
get_playlist_details_from_xml(playlist, xml)
for plex_item in xml:
playlist.items.append(add_to_Kodi_playlist(playlist, plex_item))
def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
"""
Supply either with a plex_id OR the data supplied by Kodi JSON-RPC
Initializes the Plex side without changing the Kodi playlists
WILL ALSO UPDATE OUR PLAYLISTS
"""
log.debug('Initializing the playlist %s on the Plex side' % playlist)
if plex_id:
item = playlist_item_from_plex(plex_id)
else:
item = playlist_item_from_kodi_item(kodi_item)
item = playlist_item_from_kodi(kodi_item)
params = {
'next': 0,
'type': playlist.type,
@ -192,22 +241,74 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
action_type="POST",
parameters=params)
_get_playlist_details_from_xml(playlist, xml)
get_playlist_details_from_xml(playlist, xml)
playlist.items.append(item)
log.debug('Initialized the playlist: %s' % playlist)
log.debug('Initialized the playlist on the Plex side: %s' % playlist)
def add_playlist_item(playlist, kodi_item, after_pos):
def add_listitem_to_playlist(playlist, pos, listitem, kodi_id=None,
kodi_type=None, plex_id=None, file=None):
"""
Adds the new kodi_item to playlist after item at position after_pos
[int]
Adds a listitem to both the Kodi and Plex playlist at position pos [int].
If file is not None, file will overrule kodi_id!
"""
item = playlist_item_from_kodi_item(kodi_item)
log.debug('add_listitem_to_playlist. Playlist before add: %s' % playlist)
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
if playlist.ID is None:
init_Plex_playlist(playlist, plex_id, kodi_item)
else:
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
if kodi_id is None and playlist.items[pos].kodi_id:
kodi_id = playlist.items[pos].kodi_id
kodi_type = playlist.items[pos].kodi_type
if file is None:
file = playlist.items[pos].file
# Otherwise we double the item!
del playlist.items[pos]
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
add_listitem_to_Kodi_playlist(playlist,
pos,
listitem,
file,
kodi_item=kodi_item)
def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
plex_id=None, file=None):
"""
Adds an item to BOTH the Kodi and Plex playlist at position pos [int]
"""
log.debug('add_item_to_playlist. Playlist before adding: %s' % playlist)
kodi_item = {'id': kodi_id, 'type': kodi_type, 'file': file}
if playlist.ID is None:
init_Plex_playlist(playlist, plex_id, kodi_item)
else:
add_item_to_PMS_playlist(playlist, pos, plex_id, kodi_item)
kodi_id = playlist.items[pos].kodi_id
kodi_type = playlist.items[pos].kodi_type
file = playlist.items[pos].file
add_item_to_kodi_playlist(playlist, pos, kodi_id, kodi_type, file)
def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
"""
Adds a new item to the playlist at position pos [int] only on the Plex
side of things (e.g. because the user changed the Kodi side)
WILL ALSO UPDATE OUR PLAYLISTS
"""
log.debug('Adding new item plex_id: %s, kodi_item: %s on the Plex side at '
'position %s for %s' % (plex_id, kodi_item, pos, playlist))
if plex_id:
item = playlist_item_from_plex(plex_id)
else:
item = playlist_item_from_kodi(kodi_item)
url = "{server}/%ss/%s?uri=%s" % (playlist.kind, playlist.ID, item.uri)
# Will always put the new item at the end of the playlist
# Will always put the new item at the end of the Plex playlist
xml = DU().downloadUrl(url, action_type="PUT")
try:
item.ID = xml.attrib['%sLastAddedItemID' % playlist.kind]
item.ID = xml[-1].attrib['%sItemID' % playlist.kind]
except (TypeError, AttributeError, KeyError):
log.error('Could not add item %s to playlist %s'
% (kodi_item, playlist))
@ -218,21 +319,48 @@ def add_playlist_item(playlist, kodi_item, after_pos):
if plex_item.attrib['%sItemID' % playlist.kind] == item.ID:
item.guid = plex_item.attrib['guid']
playlist.items.append(item)
if after_pos == len(playlist.items) - 1:
if pos == len(playlist.items) - 1:
# Item was added at the end
_get_playListVersion_from_xml(playlist, xml)
else:
# Move the new item to the correct position
move_playlist_item(playlist,
len(playlist.items) - 1,
after_pos)
pos)
log.debug('Successfully added item on the Plex side: %s' % playlist)
def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
file=None):
"""
Adds an item to the KODI playlist only
WILL ALSO UPDATE OUR PLAYLISTS
"""
log.debug('Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi '
'only at position %s for %s'
% (kodi_id, kodi_type, file, pos, playlist))
params = {
'playlistid': playlist.playlistid,
'position': pos
}
if kodi_id is not None:
params['item'] = {'%sid' % kodi_type: int(kodi_id)}
else:
params['item'] = {'file': file}
log.debug(JSONRPC('Playlist.Insert').execute(params))
playlist.items.insert(pos, playlist_item_from_kodi(
{'id': kodi_id, 'type': kodi_type, 'file': file}))
def move_playlist_item(playlist, before_pos, after_pos):
"""
Moves playlist item from before_pos [int] to after_pos [int]
Moves playlist item from before_pos [int] to after_pos [int] for Plex only.
WILL ALSO CHANGE OUR PLAYLISTS
"""
log.debug('Moving item from %s to %s' % (before_pos, after_pos))
log.debug('Moving item from %s to %s on the Plex side for %s'
% (before_pos, after_pos, playlist))
if after_pos == 0:
url = "{server}/%ss/%s/items/%s/move?after=0" % \
(playlist.kind,
@ -249,6 +377,7 @@ def move_playlist_item(playlist, before_pos, after_pos):
_get_playListVersion_from_xml(playlist, xml)
# Move our item's position in our internal playlist
playlist.items.insert(after_pos, playlist.items.pop(before_pos))
log.debug('Done moving for %s' % playlist)
def get_PMS_playlist(playlist, playlist_id=None):
@ -280,32 +409,14 @@ def refresh_playlist_from_PMS(playlist):
except:
log.error('Could not download Plex playlist.')
return
_get_playlist_details_from_xml(playlist, xml)
get_playlist_details_from_xml(playlist, xml)
def update_playlist_from_PMS(playlist, playlist_id=None):
def delete_playlist_item_from_PMS(playlist, pos):
"""
Updates Kodi playlist using a new PMS playlist. Pass in playlist_id if we
need to fetch a new playqueue
"""
xml = get_PMS_playlist(playlist, playlist_id)
try:
xml.attrib['%sVersion' % playlist.kind]
except:
log.error('Could not download Plex playlist.')
return
# Clear our existing playlist and the associated Kodi playlist
playlist.clear()
# Set new values
_get_playlist_details_from_xml(playlist, xml)
for plex_item in xml:
playlist.items.append(add_to_Kodi_playlist(playlist, plex_item))
def delete_playlist_item(playlist, pos):
"""
Delete the item at position pos [int]
Delete the item at position pos [int] on the Plex side and our playlists
"""
log.debug('Deleting position %s for %s on the Plex side' % (pos, playlist))
xml = DU().downloadUrl("{server}/%ss/%s/items/%s?repeat=%s" %
(playlist.kind,
playlist.ID,
@ -313,7 +424,7 @@ def delete_playlist_item(playlist, pos):
playlist.repeat),
action_type="DELETE")
_get_playListVersion_from_xml(playlist, xml)
del playlist.items[pos], playlist.old_kodi_pl[pos]
del playlist.items[pos]
def get_kodi_playlist_items(playlist):
@ -357,78 +468,53 @@ def add_to_Kodi_playlist(playlist, xml_video_element):
Adds a new item to the Kodi playlist via JSON (at the end of the playlist).
Pass in the PMS xml's video element (one level underneath MediaContainer).
Will return a Playlist_Item
Returns a Playlist_Item
"""
item = Playlist_Item()
api = API(xml_video_element)
item = playlist_item_from_xml(playlist, xml_video_element)
params = {
'playlistid': playlist.playlistid
}
item.plex_id = api.getRatingKey()
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
item.guid = xml_video_element.attrib.get('guid')
if item.plex_id:
with embydb.GetEmbyDB() as emby_db:
db_element = emby_db.getItem_byId(item.plex_id)
try:
item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4]
except TypeError:
pass
if item.kodi_id:
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
else:
item.file = api.getFilePath()
params['item'] = {'file': tryEncode(item.file)}
log.debug(JSONRPC('Playlist.Add').execute(params))
playlist.PKC_playlist_edits.append(
item.kodi_id if item.kodi_id else item.file)
return item
def add_listitem_to_Kodi_playlist(playlist, listitem, file, index):
def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
xml_video_element=None, kodi_item=None):
"""
Adds an xbmc listitem to the Kodi playlist. Will be ignored by kodimonitor
by settings window('plex_ignore_Playlist.OnAdd')
Adds an xbmc listitem to the Kodi playlist.xml_video_element
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
"""
playlist.kodi_pl.add(file, listitem, index=index)
def add_dbid_to_Kodi_playlist(playlist, dbid=None, mediatype=None, url=None):
params = {
'playlistid': playlist.playlistid
}
if dbid is not None:
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
log.debug('Insert listitem at position %s for Kodi only for %s'
% (pos, playlist))
# Add the item into Kodi playlist
playlist.kodi_pl.add(file, listitem, index=pos)
# We need to add this to our internal queue as well
if xml_video_element is not None:
item = playlist_item_from_xml(playlist, xml_video_element)
item.file = file
else:
params['item'] = {'file': url}
log.debug(JSONRPC('Playlist.Add').execute(params))
item = playlist_item_from_kodi(kodi_item)
playlist.items.insert(pos, item)
log.debug('Done inserting for %s' % playlist)
def remove_from_Kodi_playlist(playlist, position):
def remove_from_Kodi_playlist(playlist, pos):
"""
Removes the item at position from the Kodi playlist using JSON. Will be
ignored by kodimonitor by settings window('plex_ignore_Playlist.OnRemove')
Removes the item at position pos from the Kodi playlist using JSON.
WILL NOT UPDATE THE PLEX SIDE, BUT WILL UPDATE OUR PLAYLISTS
"""
log.debug('Removing position %s from playlist %s' % (position, playlist))
log.debug('Removing position %s from Kodi only from %s' % (pos, playlist))
log.debug(JSONRPC('Playlist.Remove').execute({
'playlistid': playlist.playlistid,
'position': position
'position': pos
}))
def insert_into_Kodi_playlist(playlist, position, dbid=None, mediatype=None,
url=None):
"""
"""
params = {
'playlistid': playlist.playlistid,
'position': position
}
if dbid is not None:
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
else:
params['item'] = {'file': url}
JSONRPC('Playlist.Insert').execute(params)
del playlist.items[pos]
# NOT YET UPDATED!!
@ -478,7 +564,7 @@ def _addtoPlaylist_xbmc(self, item):
'id': API.getRatingKey(),
'filename': API.getKey()
}
playurl = "plugin://plugin.video.plexkodiconnect.movies/?%s" \
playurl = "plugin://plugin.video.plexkodiconnect/movies/?%s" \
% urlencode(params)
listitem = API.CreateListItemFromPlexItem()

View file

@ -1,21 +1,19 @@
# -*- coding: utf-8 -*-
###############################################################################
import logging
from threading import Lock, Thread
from threading import RLock, Thread
import xbmc
from xbmc import sleep, Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend, \
Lock_Function
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend
import playlist_func as PL
from PlexFunctions import ConvertPlexToKodiTime
###############################################################################
log = logging.getLogger("PLEX."+__name__)
# Lock used to lock methods
lock = Lock()
lockmethod = Lock_Function(lock)
# Lock used for playqueue manipulations
lock = RLock()
###############################################################################
@ -29,16 +27,15 @@ class Playqueue(Thread):
__shared_state = {}
playqueues = None
@lockmethod.lockthis
def __init__(self, callback=None):
self.__dict__ = self.__shared_state
Thread.__init__(self)
if self.playqueues is not None:
return
self.mgr = callback
self.player = xbmc.Player()
# Initialize Kodi playqueues
with lock:
self.playqueues = []
for queue in PL.get_kodi_playqueues():
playqueue = PL.Playqueue_Object()
@ -46,12 +43,12 @@ class Playqueue(Thread):
playqueue.type = queue['type']
# Initialize each Kodi playlist
if playqueue.type == 'audio':
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
elif playqueue.type == 'video':
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
else:
# Currently, only video or audio playqueues available
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
self.playqueues.append(playqueue)
# sort the list by their playlistid, just in case
self.playqueues = sorted(
@ -63,30 +60,14 @@ class Playqueue(Thread):
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 type was passed in: %s' % typus)
raise ValueError('Wrong playlist type passed in: %s' % typus)
return playqueue
def get_playqueue_from_playerid(self, kodi_player_id):
for playqueue in self.playqueues:
if playqueue.playlistid == kodi_player_id:
break
else:
raise ValueError('Wrong kodi_player_id passed was passed in: %s'
% kodi_player_id)
return playqueue
def _grab_PMS_playqueue(self, playqueue, playqueue_id=None, repeat=None):
"""
For initiating out playqueues from the PMS because another PKC Python
instance already is setting up the Kodi playlists
"""
PL.grab_PMS_playqueue(playqueue, playqueue_id)
@lockmethod.lockthis
def update_playqueue_from_PMS(self,
playqueue,
playqueue_id=None,
@ -97,38 +78,16 @@ class Playqueue(Thread):
in playqueue_id if we need to fetch a new playqueue
repeat = 0, 1, 2
offset = time offset in Plextime
offset = time offset in Plextime (milliseconds)
"""
log.info('New playqueue received from the PMS, updating!')
log.info('New playqueue %s received from Plex companion with offset '
'%s, repeat %s' % (playqueue_id, offset, repeat))
with lock:
if playqueue_id != playqueue.ID:
log.debug('Need to fetch new playQueue from the PMS')
PL.update_playlist_from_PMS(playqueue, playqueue_id)
playqueue.repeat = 0 if not repeat else int(repeat)
log.debug('Updated playqueue: %s' % playqueue)
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 = None
# Start playback
if startpos:
self.player.play(playqueue.kodi_pl, startpos=startpos)
else:
self.player.play(playqueue.kodi_pl)
playqueue.log_Kodi_playlist()
@lockmethod.lockthis
def start_playqueue_initiated_by_companion(self,
playqueue,
playqueue_id=None,
repeat=None,
offset=None):
log.info('Plex companion wants to restart playback of playqueue %s'
% playqueue)
# Still need to get new playQueue from the server - don't know what has
# been selected
log.debug('Restarting existing playQueue')
PL.refresh_playlist_from_PMS(playqueue)
playqueue.repeat = 0 if not repeat else int(repeat)
window('plex_customplaylist', value="true")
@ -140,126 +99,83 @@ class Playqueue(Thread):
break
else:
startpos = None
# Start playback
# Start playback. Player does not return in time
if startpos:
self.player.play(playqueue.kodi_pl, startpos=startpos)
thread = Thread(target=Player().play,
args=(playqueue.kodi_pl,
None,
False,
startpos))
else:
self.player.play(playqueue.kodi_pl)
playqueue.log_Kodi_playlist()
thread = Thread(target=Player().play,
args=(playqueue.kodi_pl,))
thread.setDaemon(True)
thread.start()
@lockmethod.lockthis
def kodi_onadd(self, data):
"""
Called if an item is added to a Kodi playqueue. Data is Kodi JSON-RPC
output, e.g.
{
u'item': {u'type': u'movie', u'id': 3},
u'playlistid': 1,
u'position': 0
}
"""
playqueue = self.playqueues[data['playlistid']]
if window('plex_playbackProps') == 'true':
log.debug('kodi_onadd called during PKC playback setup')
if window('plex_playQueueID'):
self._grab_PMS_playqueue(playqueue, window('plex_playQueueID'))
window('plex_playQueueID', clear=True)
log.debug('Done setting up playQueue')
return
if playqueue.PKC_playlist_edits:
old = (data['item'].get('id') if data['item'].get('id')
else data['item'].get('file'))
for i, item in enumerate(playqueue.PKC_playlist_edits):
if old == item:
log.debug('kodimonitor told us of a PKC edit - ignore')
del playqueue.PKC_playlist_edits[i]
return
if playqueue.ID is None:
# Need to initialize the queue for the first time
PL.init_Plex_playlist(playqueue, kodi_item=data['item'])
else:
PL.add_playlist_item(playqueue, data['item'], data['position'])
log.debug('Added a new item to the playqueue: %s' % playqueue)
@lockmethod.lockthis
def kodi_onremove(self, data):
"""
Called if an item is removed from a Kodi playqueue. Data is Kodi JSON-
RPC output, e.g.
{u'position': 2, u'playlistid': 1}
"""
if window('plex_playbackProps') == 'true':
log.debug('kodi_onremove called during PKC playback setup')
return
playqueue = self.playqueues[data['playlistid']]
PL.delete_playlist_item(playqueue, data['position'])
log.debug('Deleted item at position %s. New playqueue: %s'
% (data['position'], playqueue))
@lockmethod.lockthis
def _compare_playqueues(self, playqueue, new):
"""
Used to poll the Kodi playqueue and update the Plex playqueue if needed
"""
if self.threadStopped():
# Chances are that we got an empty Kodi playlist due to Kodi exit
return
old = playqueue.old_kodi_pl
old = list(playqueue.items)
index = list(range(0, len(old)))
log.debug('Comparing new Kodi playqueue %s with our play queue %s'
% (new, playqueue))
% (new, old))
for i, new_item in enumerate(new):
for j, old_item in enumerate(old):
if old_item.get('id') is None:
identical = old_item['file'] == new_item['file']
if self.threadStopped():
# Chances are that we got an empty Kodi playlist due to
# Kodi exit
return
if new_item.get('id') is None:
identical = old_item.file == new_item['file']
else:
identical = (old_item['id'] == new_item['id'] and
old_item['type'] == new_item['type'])
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:
# item now at pos i has been moved from original pos i+j
log.debug('Detected playqueue item %s moved to position %s'
% (i+j, i))
PL.move_playlist_item(playqueue, i + j, i)
# Delete the item we just found
del old[j], index[j]
break
# New elements and left-over elements will be taken care of by the kodi
# monitor!
log.debug('New playqueue: %s' % playqueue)
def init_playlists(self):
"""
Initializes the playqueues with already existing items.
Called on startup AND for addon paths!
"""
for playqueue in self.playqueues:
for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)):
if i == 0:
PL.init_Plex_playlist(playqueue, kodi_item=item)
else:
PL.add_playlist_item(playqueue, item, i)
log.debug('Detected new Kodi element: %s' % 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 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):
threadStopped = self.threadStopped
threadSuspended = self.threadSuspended
log.info("----===## Starting PlayQueue client ##===----")
# Initialize the playqueues, if Kodi already got items in them
self.init_playlists()
for playqueue in self.playqueues:
for i, item in enumerate(PL.get_kodi_playlist_items(playqueue)):
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 threadStopped():
while threadSuspended():
if threadStopped():
break
xbmc.sleep(1000)
sleep(1000)
with lock:
for playqueue in self.playqueues:
if not playqueue.items:
# Skip empty playqueues as items can't be modified
continue
kodi_playqueue = PL.get_kodi_playlist_items(playqueue)
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)
xbmc.sleep(1000)
sleep(50)
log.info("----===## PlayQueue client stopped ##===----")

View file

@ -56,6 +56,24 @@ def window(property, value=None, clear=False, windowid=10000):
return tryDecode(win.getProperty(property))
def pickl_window(property, value=None, clear=False, windowid=10000):
"""
Get or set window property - thread safe! For use with Pickle
Property and value must be string
"""
if windowid != 10000:
win = xbmcgui.Window(windowid)
else:
win = WINDOW
if clear:
win.clearProperty(property)
elif value is not None:
win.setProperty(property, value)
else:
return win.getProperty(property)
def settings(setting, value=None):
"""
Get or add addon setting. Returns unicode

View file

@ -44,6 +44,8 @@ from playqueue import Playqueue
import PlexAPI
from PlexCompanion import PlexCompanion
from monitor_kodi_play import Monitor_Kodi_Play
from playback_starter import Playback_Starter
###############################################################################
@ -73,6 +75,7 @@ class Service():
plexCompanion_running = False
playqueue_running = False
kodimonitor_running = False
playback_starter_running = False
def __init__(self):
@ -109,7 +112,8 @@ class Service():
"plex_authenticated", "PlexUserImage", "useDirectPaths",
"suspend_LibraryThread", "plex_terminateNow",
"kodiplextimeoffset", "countError", "countUnauthorized",
"plex_restricteduser", "plex_allows_mediaDeletion"
"plex_restricteduser", "plex_allows_mediaDeletion",
"plex_play_new_item", "plex_result"
]
for prop in properties:
window(prop, clear=True)
@ -133,6 +137,10 @@ class Service():
monitor = self.monitor
kodiProfile = xbmc.translatePath("special://profile")
# Detect playback start early on
self.monitor_kodi_play = Monitor_Kodi_Play(self)
self.monitor_kodi_play.start()
# Server auto-detect
initialsetup.InitialSetup().setup()
@ -142,6 +150,7 @@ class Service():
self.library = LibrarySync(self)
self.plexCompanion = PlexCompanion(self)
self.playqueue = Playqueue(self)
self.playback_starter = Playback_Starter(self)
plx = PlexAPI.PlexAPI()
@ -197,6 +206,9 @@ class Service():
if not self.plexCompanion_running:
self.plexCompanion_running = True
self.plexCompanion.start()
if not self.playback_starter_running:
self.playback_starter_running = True
self.playback_starter.start()
else:
if (self.user.currUser is None) and self.warn_auth:
# Alert user is not authenticated and suppress future