diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py index 6a4b3b7d..130cbb22 100644 --- a/resources/lib/PlexCompanion.py +++ b/resources/lib/PlexCompanion.py @@ -88,11 +88,16 @@ class PlexCompanion(Thread): if ID != playqueue.ID: # playqueue changed somehow self.mgr.playqueue.update_playqueue_from_PMS( - playqueue, ID, int(query['repeat']), data['offset']) + playqueue, + ID, + query.get('repeat'), + data.get('offset')) else: # No change to the playqueue self.mgr.playqueue.start_playqueue_initiated_by_companion( - playqueue, int(query['repeat']), data['offset']) + playqueue, + query.get('repeat'), + data.get('offset')) def run(self): httpd = False diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index 17342df7..af542ea6 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -17,6 +17,7 @@ import downloadutils import PlexAPI import PlexFunctions as PF +import playlist_func as PL ############################################################################### @@ -36,9 +37,12 @@ class PlaybackUtils(): self.userid = window('currUserId') self.server = window('pms_server') - - self.pl = Playqueue().get_playqueue_from_type( - PF.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[self.API.getType()]) + playqueue = Playqueue() + # We need to initialize already existing items as we have a completely + # different Python instance! + playqueue.init_playlists() + 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): @@ -51,6 +55,7 @@ class PlaybackUtils(): playutils = putils.PlayUtils(item[0]) log.info("Play called.") + log.debug('Playqueue: %s' % self.pl) playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) @@ -108,6 +113,9 @@ class PlaybackUtils(): ############### 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')) # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. @@ -120,12 +128,20 @@ class PlaybackUtils(): window('plex_customplaylist') != "true" and not contextmenu_play): log.debug("Adding dummy file to playlist.") + # Make sure Kodimonitor recognizes dummy + listitem.setLabel('plex_dummyfile') dummyPlaylist = True - kodiPl.add(playurl, listitem, index=startPos) + PL.add_listitem_to_Kodi_playlist( + self.pl, + listitem, + playurl, + startPos) # Remove the original item from playlist - self.pl.removefromPlaylist(startPos+1) - # Readd the original item to playlist - via jsonrpc so we have full metadata - self.pl.insertintoPlaylist( + PL.remove_from_Kodi_playlist(self.pl, startPos+1) + # Readd the original item to playlist - via jsonrpc so we have + # full metadata + PL.insert_into_Kodi_playlist( + self.pl, self.currentPosition+1, dbid, PF.KODITYPE_FROM_PLEXTYPE[API.getType()]) @@ -146,9 +162,10 @@ class PlaybackUtils(): # Extend our current playlist with the actual item to play # only if there's no playlist first log.info("Adding main item to playlist.") - self.pl.addtoPlaylist( - dbid, - PF.KODITYPE_FROM_PLEXTYPE[API.getType()]) + PL.add_dbid_to_Kodi_playlist( + self.pl, + dbid=dbid, + mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()]) elif contextmenu_play: if window('useDirectPaths') == 'true': @@ -168,10 +185,11 @@ class PlaybackUtils(): kodiPl.add(playurl, listitem, index=self.currentPosition+1) else: # Full metadata - self.pl.insertintoPlaylist( + PL.insert_into_Kodi_playlist( + self.pl, self.currentPosition+1, - dbid, - PF.KODITYPE_FROM_PLEXTYPE[API.getType()]) + dbid=dbid, + mediatype=PF.KODITYPE_FROM_PLEXTYPE[API.getType()]) self.currentPosition += 1 if seektime: window('plex_customplaylist.seektime', value=str(seektime)) @@ -201,7 +219,6 @@ class PlaybackUtils(): kodiPl.add(additionalPlayurl, additionalListItem, index=self.currentPosition) - self.pl.verifyPlaylist() self.currentPosition += 1 API.setPartNumber(0) @@ -287,7 +304,10 @@ class PlaybackUtils(): introPlayurl = path + '?' + urlencode(params) log.info("Adding Intro: %s" % introPlayurl) - self.pl.insertintoPlaylist(self.currentPosition, url=introPlayurl) + PL.insert_into_Kodi_playlist( + self.pl, + self.currentPosition, + url=introPlayurl) self.currentPosition += 1 return True diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py index e4a887a0..d9b756d3 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -3,7 +3,7 @@ from urllib import quote import embydb_functions as embydb from downloadutils import DownloadUtils as DU -from utils import JSONRPC, tryEncode +from utils import window, JSONRPC, tryEncode from PlexAPI import API ############################################################################### @@ -250,6 +250,57 @@ def move_playlist_item(playlist, before_pos, after_pos): playlist.items.insert(after_pos, playlist.items.pop(before_pos)) +def get_PMS_playlist(playlist, playlist_id=None): + """ + Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we + need to fetch a new playlist + + Returns None if something went wrong + """ + playlist_id = playlist_id if playlist_id else playlist.ID + xml = DU().downloadUrl( + "{server}/%ss/%s" % (playlist.kind, playlist_id), + headerOptions={'Accept': 'application/xml'}) + try: + xml.attrib['%sID' % playlist.kind] + except (AttributeError, KeyError): + xml = None + return xml + + +def refresh_playlist_from_PMS(playlist): + """ + Only updates the selected item from the PMS side (e.g. + playQueueSelectedItemID). Will NOT check whether items still make sense. + """ + xml = get_PMS_playlist(playlist) + try: + xml.attrib['%sVersion' % playlist.kind] + except: + log.error('Could not download Plex playlist.') + return + _get_playlist_details_from_xml(playlist, xml) + + +def update_playlist_from_PMS(playlist, playlist_id=None): + """ + 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] @@ -333,13 +384,43 @@ def add_to_Kodi_playlist(playlist, xml_video_element): return item -def insertintoPlaylist(self, - position, - dbid=None, - mediatype=None, - url=None): +def add_listitem_to_Kodi_playlist(playlist, listitem, file, index): + """ + Adds an xbmc listitem to the Kodi playlist. Will be ignored by kodimonitor + by settings window('plex_ignore_Playlist.OnAdd') + """ + playlist.kodi_pl.add(file, listitem, index=index) + + +def add_dbid_to_Kodi_playlist(playlist, dbid=None, mediatype=None, url=None): params = { - 'playlistid': self.playlistId, + 'playlistid': playlist.playlistid + } + if dbid is not None: + params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)} + else: + params['item'] = {'file': url} + log.debug(JSONRPC('Playlist.Add').execute(params)) + + +def remove_from_Kodi_playlist(playlist, position): + """ + Removes the item at position from the Kodi playlist using JSON. Will be + ignored by kodimonitor by settings window('plex_ignore_Playlist.OnRemove') + """ + log.debug('Removing position %s from playlist %s' % (position, playlist)) + log.debug(JSONRPC('Playlist.Remove').execute({ + 'playlistid': playlist.playlistid, + 'position': position + })) + + +def insert_into_Kodi_playlist(playlist, position, dbid=None, mediatype=None, + url=None): + """ + """ + params = { + 'playlistid': playlist.playlistid, 'position': position } if dbid is not None: @@ -349,64 +430,7 @@ def insertintoPlaylist(self, JSONRPC('Playlist.Insert').execute(params) -def removefromPlaylist(self, position): - params = { - 'playlistid': self.playlistId, - 'position': position - } - log.debug(JSONRPC('Playlist.Remove').execute(params)) - - -def get_PMS_playlist(playlist, playlist_id=None): - """ - Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we - need to fetch a new playlist - - Returns None if something went wrong - """ - playlist_id = playlist_id if playlist_id else playlist.ID - xml = DU().downloadUrl( - "{server}/%ss/%s" % (playlist.kind, playlist_id), - headerOptions={'Accept': 'application/xml'}) - try: - xml.attrib['%sID' % playlist.kind] - except (AttributeError, KeyError): - xml = None - return xml - - -def refresh_playlist_from_PMS(playlist): - """ - Only updates the selected item from the PMS side (e.g. - playQueueSelectedItemID). Will NOT check whether items still make sense. - """ - xml = get_PMS_playlist(playlist) - try: - xml.attrib['%sVersion' % playlist.kind] - except: - log.error('Could not download Plex playlist.') - return - _get_playlist_details_from_xml(playlist, xml) - - -def update_playlist_from_PMS(playlist, playlist_id=None): - """ - 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)) - +# NOT YET UPDATED!! def _processItems(self, startitem, startPlayer=False): startpos = None diff --git a/resources/lib/playqueue.py b/resources/lib/playqueue.py index bc8eafbb..f88c1fcb 100644 --- a/resources/lib/playqueue.py +++ b/resources/lib/playqueue.py @@ -94,7 +94,7 @@ class Playqueue(Thread): """ log.info('New playqueue received from the PMS, updating!') PL.update_playlist_from_PMS(playqueue, playqueue_id) - playqueue.repeat = repeat + playqueue.repeat = 0 if not repeat else int(repeat) log.debug('Updated playqueue: %s' % playqueue) window('plex_customplaylist', value="true") @@ -124,7 +124,7 @@ class Playqueue(Thread): # Still need to get new playQueue from the server - don't know what has # been selected PL.refresh_playlist_from_PMS(playqueue) - playqueue.repeat = repeat + playqueue.repeat = 0 if not repeat else int(repeat) window('plex_customplaylist', value="true") if offset not in (None, "0"): window('plex_customplaylist.seektime', @@ -153,6 +153,13 @@ class Playqueue(Thread): } """ playqueue = self.playqueues[data['playlistid']] + if data['item'].get('id') is None and data['item'].get('file') is None: + # Kodi screwed up. Let's try to get the data anyway + items = PL.get_kodi_playlist_items(playqueue) + if items[data['position']].get('id') is not None: + data['item']['id'] = items[data['position']].get('id') + else: + data['item']['file'] = items[data['position']].get('file') if playqueue.PKC_playlist_edits: old = (data['item'].get('id') if data['item'].get('id') else data['item'].get('file')) @@ -212,17 +219,24 @@ class Playqueue(Thread): # monitor! log.debug('New playqueue: %s' % playqueue) - def run(self): - threadStopped = self.threadStopped - threadSuspended = self.threadSuspended - log.info("----===## Starting PlayQueue client ##===----") - # Initialize the playqueues, if Kodi already got items in them + 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) + + 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() while not threadStopped(): while threadSuspended(): if threadStopped():