From 1218cde0a2f41f991e1a8c571c8e8195d23bc9cf Mon Sep 17 00:00:00 2001 From: croneter Date: Sun, 28 Apr 2019 18:03:20 +0200 Subject: [PATCH] Big update --- resources/lib/kodimonitor.py | 51 ++- resources/lib/playback.py | 2 +- resources/lib/playlist_func.py | 598 +++++++++++++++++++++++------ resources/lib/playqueue.py | 7 +- resources/lib/playstrm.py | 252 ++---------- resources/lib/playutils.py | 6 +- resources/lib/plex_db/playlists.py | 2 +- resources/lib/plex_functions.py | 6 +- resources/lib/utils.py | 10 + resources/lib/webservice.py | 103 +++-- 10 files changed, 634 insertions(+), 403 deletions(-) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 0f15bd38..d6c436e7 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -219,16 +219,20 @@ class KodiMonitor(xbmc.Monitor): { u'playlistid': 1, } + Let's NOT use this as Kodi's responses when e.g. playing an entire + folder are NOT threadsafe: Playlist.OnAdd might be added first, then + Playlist.OnClear might be received LATER """ if self.playlistid == data['playlistid']: LOG.debug('Resetting autoplay') app.PLAYSTATE.autoplay = False - playqueue = PQ.PLAYQUEUES[data['playlistid']] - if not playqueue.is_pkc_clear(): - playqueue.pkc_edit = True - playqueue.clear(kodi=False) - else: - LOG.debug('Detected PKC clear - ignoring') + return + # playqueue = PQ.PLAYQUEUES[data['playlistid']] + # if not playqueue.is_pkc_clear(): + # playqueue.pkc_edit = True + # playqueue.clear(kodi=False) + # else: + # LOG.debug('Detected PKC clear - ignoring') @staticmethod def _get_ids(kodi_id, kodi_type, path): @@ -311,7 +315,7 @@ class KodiMonitor(xbmc.Monitor): def _check_playing_item(self, data): """ - Returns a PF.Playlist_Item() for the currently playing item + Returns a PF.PlaylistItem() for the currently playing item Raises MonitorError or IndexError if we need to init the PKC playqueue """ info = js.get_player_props(self.playerid) @@ -320,26 +324,13 @@ class KodiMonitor(xbmc.Monitor): kodi_playlist = js.playlist_get_items(self.playerid) LOG.debug('Current Kodi playlist: %s', kodi_playlist) kodi_item = PL.playlist_item_from_kodi(kodi_playlist[position]) - if (position == 1 and - len(kodi_playlist) == len(self.playqueue.items) + 1 and - kodi_playlist[0].get('type') == 'unknown' and - kodi_playlist[0].get('file') and - kodi_playlist[0].get('file').startswith('http://127.0.0.1')): - if kodi_item == self.playqueue.items[0]: - # Delete the very first item that we used to start playback: - # { - # u'title': u'', - # u'type': u'unknown', - # u'file': u'http://127.0.0.1:57578/plex/kodi/....', - # u'label': u'' - # } - LOG.debug('Deleting the very first playqueue item') - js.playlist_remove(self.playqueue.playlistid, 0) - position = 0 - else: - LOG.debug('Different item in PKC playlist: %s vs. %s', - self.playqueue.items[0], kodi_item) - raise MonitorError() + if isinstance(self.playqueue.items[0], PL.PlaylistItemDummy): + # Get rid of the very first element in the queue that Kodi marked + # as unplayed (the one to init the queue) + LOG.debug('Deleting the very first playqueue item') + js.playlist_remove(self.playqueue.playlistid, 0) + del self.playqueue.items[0] + position = 0 elif kodi_item != self.playqueue.items[position]: LOG.debug('Different playqueue items: %s vs. %s ', kodi_item, self.playqueue.items[position]) @@ -349,7 +340,7 @@ class KodiMonitor(xbmc.Monitor): def _load_playerstate(self, item): """ - Pass in a PF.Playlist_Item(). Will then set the currently playing + Pass in a PF.PlaylistItem(). Will then set the currently playing state with app.PLAYSTATE.player_states[self.playerid] """ if self.playqueue.id: @@ -431,6 +422,7 @@ def _playback_cleanup(ended=False): # 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) app.CONN.plex_transient_token = None + LOG.debug('Playstate is: %s', app.PLAYSTATE.player_states) for playerid in app.PLAYSTATE.active_players: status = app.PLAYSTATE.player_states[playerid] # Remember the last played item later @@ -498,6 +490,9 @@ def _record_playstate(status, ended): playcount += 1 time = 0 with kodi_db.KodiVideoDB() as kodidb: + LOG.error('Setting file_id %s, time %s, totaltime %s, playcount %s, ' + 'last_played %s', + db_item['kodi_fileid'], time, totaltime, playcount, last_played) kodidb.set_resume(db_item['kodi_fileid'], time, totaltime, diff --git a/resources/lib/playback.py b/resources/lib/playback.py index 0873b9c6..82b42646 100644 --- a/resources/lib/playback.py +++ b/resources/lib/playback.py @@ -464,7 +464,7 @@ def process_indirect(key, offset, resolve=True): playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) playqueue.clear() - item = PL.Playlist_Item() + item = PL.PlaylistItem() item.xml = xml[0] item.offset = offset item.plex_type = v.PLEX_TYPE_CLIP diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py index e571e5a1..c0ad1f19 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -6,15 +6,16 @@ Collection of functions associated with Kodi and Plex playlists and playqueues from __future__ import absolute_import, division, unicode_literals from logging import getLogger +import xbmc + from .plex_api import API from .plex_db import PlexDB from . import plex_functions as PF -from .kodi_db import kodiid_from_filename +from .playutils import PlayUtils +from .kodi_db import kodiid_from_filename, KodiVideoDB from .downloadutils import DownloadUtils as DU -from . import utils -from . import json_rpc as js -from . import variables as v -from . import app +from . import utils, json_rpc as js, variables as v, app, widgets +from .windows.resume import resume_dialog ############################################################################### @@ -30,14 +31,14 @@ class PlaylistError(Exception): pass -class Playqueue_Object(object): +class PlayQueue(object): """ PKC object to represent PMS playQueues and Kodi playlist for queueing playlistid = None [int] Kodi playlist id (0, 1, 2) type = None [str] Kodi type: 'audio', 'video', 'picture' kodi_pl = None Kodi xbmc.PlayList object - items = [] [list] of Playlist_Items + items = [] [list] of PlaylistItem id = None [str] Plex playQueueID, unique Plex identifier version = None [int] Plex version of the playQueue selectedItemID = None @@ -74,8 +75,11 @@ class Playqueue_Object(object): # To keep track if Kodi playback was initiated from a Kodi playlist # There are a couple of pitfalls, unfortunately... self.kodi_playlist_playback = False + # Playlist position/index used when initiating the playqueue + self.index = None + self.force_transcode = None - def __repr__(self): + def __unicode__(self): return ("{{" "'playlistid': {self.playlistid}, " "'id': {self.id}, " @@ -89,10 +93,14 @@ class Playqueue_Object(object): "'kodi_playlist_playback': {self.kodi_playlist_playback}, " "'pkc_edit': {self.pkc_edit}, " "}}").format(**{ - 'items': [x.plex_id for x in self.items or []], + 'items': ['%s/%s: %s' % (x.plex_id, x.id, x.name) + for x in self.items], 'self': self - }).encode('utf-8') - __str__ = __repr__ + }) + + def __str__(self): + return unicode(self).encode('utf-8') + __repr__ = __str__ def is_pkc_clear(self): """ @@ -127,10 +135,319 @@ class Playqueue_Object(object): self.plex_transient_token = None self.old_kodi_pl = [] self.kodi_playlist_playback = False + self.index = None + self.force_transcode = None LOG.debug('Playlist cleared: %s', self) + def init(self, plex_id, plex_type=None, position=None, synched=True, + force_transcode=None): + """ + Initializes the playQueue with e.g. trailers and additional file parts + Pass synched=False if you're sure that this item has not been synched + to Kodi + """ + LOG.error('Current Kodi playlist: %s', + js.playlist_get_items(self.playlistid)) + if position is not None: + self.index = position + else: + # Do NOT use kodi_pl.getposition() as that appears to be buggy + self.index = max(js.get_position(self.playlistid), 0) + LOG.debug('Initializing with plex_id %s, plex_type %s, position %s, ' + 'synched %s, force_transcode %s, index %s', plex_id, + plex_type, position, synched, force_transcode, self.index) + LOG.error('Actual start: %s', js.get_position(self.playlistid)) + if self.kodi_pl.size() != len(self.items): + # The original item that Kodi put into the playlist, e.g. + # { + # u'title': u'', + # u'type': u'unknown', + # u'file': u'http://127.0.0.1:57578/plex/kodi/....', + # u'label': u'' + # } + # We CANNOT delete that item right now - so let's add a dummy + # on the PKC side + LOG.debug('Detected Kodi playlist size %s to be off for PKC: %s', + self.kodi_pl.size(), len(self.items)) + while len(self.items) < self.kodi_pl.size(): + LOG.debug('Adding a dummy item to our playqueue') + playlistitem = PlaylistItemDummy() + self.items.insert(0, playlistitem) + self.force_transcode = force_transcode + if synched: + with PlexDB(lock=False) as plexdb: + db_item = plexdb.item_by_id(plex_id, plex_type) + else: + db_item = None + if db_item: + xml = None + section_uuid = db_item['section_uuid'] + plex_type = db_item['plex_type'] + else: + xml = PF.GetPlexMetadata(plex_id) + if xml in (None, 401): + raise PlaylistError('Could not get Plex metadata %s', plex_id) + section_uuid = xml.get('librarySectionUUID') + api = API(xml[0]) + plex_type = api.plex_type() + resume = self._resume_playback(db_item, xml) + trailers = False + if (not resume and plex_type == v.PLEX_TYPE_MOVIE and + utils.settings('enableCinema') == 'true'): + if utils.settings('askCinema') == "true": + # "Play trailers?" + trailers = utils.yesno_dialog(utils.lang(29999), + utils.lang(33016)) or False + else: + trailers = True + LOG.debug('Playing trailers: %s', trailers) + xml = PF.init_plex_playqueue(plex_id, + section_uuid, + plex_type=plex_type, + trailers=trailers) + if xml is None: + LOG.error('Could not get playqueue for plex_id %s UUID %s for %s', + plex_id, section_uuid, self) + raise PlaylistError('Could not get playqueue') + # See that we add trailers, if they exist in the xml return + self._add_intros(xml) + # Add the main item after the trailers + # Look at the LAST item + api = API(xml[-1]) + self._kodi_add_xml(xml[-1], api, resume) + # Add additional file parts, if any exist + self._add_additional_parts(xml) + self.update_details_from_xml(xml) -class Playlist_Item(object): + @staticmethod + def _resume_playback(db_item=None, xml=None): + ''' + Pass in either db_item or xml + Resume item if available. Returns bool or raise an PlayStrmException if + resume was cancelled by user. + ''' + resume = app.PLAYSTATE.resume_playback + app.PLAYSTATE.resume_playback = None + if app.PLAYSTATE.autoplay: + resume = False + LOG.info('Skip resume for autoplay') + elif resume is None: + if db_item: + with KodiVideoDB(lock=False) as kodidb: + resume = kodidb.get_resume(db_item['kodi_fileid']) + else: + api = API(xml) + resume = api.resume_point() + if resume: + resume = resume_dialog(resume) + LOG.info('User chose resume: %s', resume) + if resume is None: + raise PlaylistError('User backed out of resume dialog') + app.PLAYSTATE.autoplay = True + return resume + + def _add_intros(self, xml): + ''' + if we have any play them when the movie/show is not being resumed. + ''' + if not len(xml) > 1: + LOG.debug('No trailers returned from the PMS') + return + for i, intro in enumerate(xml): + if i + 1 == len(xml): + # The main item we're looking at - skip! + break + api = API(intro) + LOG.debug('Adding trailer: %s', api.title()) + self._kodi_add_xml(intro, api) + + def _add_additional_parts(self, xml): + ''' Create listitems and add them to the stack of playlist. + ''' + api = API(xml[0]) + for part, _ in enumerate(xml[0][0]): + if part == 0: + # The first part that we've already added + continue + api.set_part_number(part) + LOG.debug('Adding addional part for %s: %s', api.title(), part) + self._kodi_add_xml(xml[0], api) + + def _kodi_add_xml(self, xml, api, resume=False): + playlistitem = PlaylistItem(xml_video_element=xml) + playlistitem.part = api.part + playlistitem.force_transcode = self.force_transcode + listitem = widgets.get_listitem(xml, resume=True) + listitem.setSubtitles(api.cache_external_subs()) + play = PlayUtils(api, playlistitem) + url = play.getPlayUrl() + listitem.setPath(url.encode('utf-8')) + self.kodi_add_item(playlistitem, self.index, listitem) + self.items.insert(self.index, playlistitem) + self.index += 1 + + def update_details_from_xml(self, xml): + """ + Updates the playlist details from the xml provided + """ + self.id = utils.cast(int, xml.get('%sID' % self.kind)) + self.version = utils.cast(int, xml.get('%sVersion' % self.kind)) + self.shuffled = utils.cast(int, xml.get('%sShuffled' % self.kind)) + self.selectedItemID = utils.cast(int, + xml.get('%sSelectedItemID' % self.kind)) + self.selectedItemOffset = utils.cast(int, + xml.get('%sSelectedItemOffset' + % self.kind)) + LOG.debug('Updated playlist from xml: %s', self) + + def add_item(self, item, pos, listitem=None): + """ + Adds a PlaylistItem to both Kodi and Plex at position pos [int] + Also changes self.items + Raises PlaylistError + """ + self.kodi_add_item(item, pos, listitem) + self.plex_add_item(item, pos) + + def kodi_add_item(self, item, pos, listitem=None): + """ + Adds a PlaylistItem to Kodi only. Will not change self.items + Raises PlaylistError + """ + if not isinstance(item, PlaylistItem): + raise PlaylistError('Wrong item %s of type %s received' + % (item, type(item))) + if pos > len(self.items): + raise PlaylistError('Position %s too large for playlist length %s' + % (pos, len(self.items))) + LOG.debug('Adding item to Kodi playlist at position %s: %s', pos, item) + if item.kodi_id is not None and item.kodi_type is not None: + # This method ensures we have full Kodi metadata, potentially + # with more artwork, for example, than Plex provides + if pos == len(self.items): + answ = js.playlist_add(self.playlistid, + {'%sid' % item.kodi_type: item.kodi_id}) + else: + answ = js.playlist_insert({'playlistid': self.playlistid, + 'position': pos, + 'item': {'%sid' % item.kodi_type: item.kodi_id}}) + if 'error' in answ: + raise PlaylistError('Kodi did not add item to playlist: %s', + answ) + else: + if not listitem: + if item.xml is None: + LOG.debug('Need to get metadata for item %s', item) + item.xml = PF.GetPlexMetadata(item.plex_id) + if item.xml in (None, 401): + raise PlaylistError('Could not get metadata for %s', item) + listitem = widgets.get_listitem(item.xml, resume=True) + url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT + args = { + 'plex_id': self.plex_id, + 'plex_type': self.plex_type + } + if item.force_transcode: + args['transcode'] = 'true' + url = utils.extend_url(url, args) + item.file = url + listitem.setPath(url.encode('utf-8')) + self.kodi_pl.add(url=listitem.getPath(), + listitem=listitem, + index=pos) + + def plex_add_item(self, item, pos): + """ + Adds a new PlaylistItem to the playlist at position pos [int] only on + the Plex side of things. Also changes self.items + Raises PlaylistError + """ + if not isinstance(item, PlaylistItem) or not item.uri: + raise PlaylistError('Wrong item %s of type %s received' + % (item, type(item))) + if pos > len(self.items): + raise PlaylistError('Position %s too large for playlist length %s' + % (pos, len(self.items))) + LOG.debug('Adding item to Plex playlist at position %s: %s', pos, item) + url = '{server}/%ss/%s?uri=%s' % (self.kind, self.id, item.uri) + # Will usually put the new item at the end of the Plex playlist + xml = DU().downloadUrl(url, action_type='PUT') + try: + xml[0].attrib + except (TypeError, AttributeError, KeyError, IndexError): + raise PlaylistError('Could not add item %s to playlist %s' + % (item, self)) + if len(xml) != len(self.items) + 1: + raise PlaylistError('Could not add item %s to playlist %s - wrong' + ' length received' % (item, self)) + for actual_pos, xml_video_element in enumerate(xml): + api = API(xml_video_element) + if api.plex_id() == item.plex_id: + break + else: + raise PlaylistError('Something went wrong - Plex id not found') + item.from_xml(xml[actual_pos]) + self.items.insert(actual_pos, item) + self.update_details_from_xml(xml) + if actual_pos != pos: + self.plex_move_item(actual_pos, pos) + LOG.debug('Added item %s on Plex side: %s', item, self) + + def kodi_remove_item(self, pos): + """ + Only manipulates the Kodi playlist. Won't change self.items + """ + LOG.debug('Removing position %s on the Kodi side for %s', pos, self) + answ = js.playlist_remove(self.playlistid, pos) + if 'error' in answ: + raise PlaylistError('Could not remove item: %s' % answ) + + def plex_move_item(self, before, after): + """ + Moves playlist item from before [int] to after [int] for Plex only. + + Will also change self.items + """ + if before > len(self.items): + raise PlaylistError('Original position %s larger than current ' + 'playlist length %s', + before, len(self.items)) + elif after > len(self.items): + raise PlaylistError('Desired position %s larger than current ' + 'playlist length %s', + after, len(self.items)) + elif after == before: + raise PlaylistError('Desired position and original position are ' + 'identical: %s', after) + LOG.debug('Moving item from %s to %s on the Plex side for %s', + before, after, self) + if after == 0: + url = "{server}/%ss/%s/items/%s/move?after=0" % \ + (self.kind, + self.id, + self.items[before].id) + else: + url = "{server}/%ss/%s/items/%s/move?after=%s" % \ + (self.kind, + self.id, + self.items[before].id, + self.items[after - 1].id) + xml = DU().downloadUrl(url, action_type="PUT") + try: + xml[0].attrib + except (TypeError, IndexError, AttributeError): + raise PlaylistError('Could not move playlist item from %s to %s ' + 'for %s' % (before, after, self)) + self.update_details_from_xml(xml) + self.items.insert(after, self.items.pop(before)) + LOG.debug('Done moving items for %s', self) + + def start_playback(self, pos=0): + LOG.info('Starting playback at %s for %s', pos, self) + xbmc.Player().play(self.kodi_pl, startpos=pos, windowed=False) + + +class PlaylistItem(object): """ Object to fill our playqueues and playlists with. @@ -150,110 +467,75 @@ class Playlist_Item(object): part = 0 [int] part number if Plex video consists of mult. parts force_transcode [bool] defaults to False - Playlist_items compare as equal, if they + PlaylistItem compare as equal, if they - have the same plex_id - OR: have the same kodi_id AND kodi_type - OR: have the same file """ - def __init__(self): - self._id = None - self._plex_id = None - self.plex_type = None + def __init__(self, plex_id=None, plex_type=None, xml_video_element=None, + kodi_id=None, kodi_type=None, grab_xml=False, + lookup_kodi=True): + """ + Pass grab_xml=True in order to get Plex metadata from the PMS while + passing a plex_id. + Pass lookup_kodi=False to NOT check the plex.db for kodi_id and + kodi_type if they're missing (won't be done for clips anyway) + """ + self.name = None + self.id = None + self.plex_id = plex_id + self.plex_type = plex_type self.plex_uuid = None - self._kodi_id = None - self.kodi_type = None + self.kodi_id = kodi_id + self.kodi_type = kodi_type self.file = None self.uri = None self.guid = None self.xml = None self.playmethod = None - self._playcount = None - self._offset = None - # If Plex video consists of several parts; part number - self._part = 0 + self.playcount = None + self.offset = None + self.part = 0 self.force_transcode = False + if grab_xml and plex_id is not None and xml_video_element is None: + xml_video_element = PF.GetPlexMetadata(plex_id) + try: + xml_video_element = xml_video_element[0] + except (TypeError, IndexError): + xml_video_element = None + if xml_video_element is not None: + self.from_xml(xml_video_element) + if (lookup_kodi and (kodi_id is None or kodi_type is None) and + self.plex_type != v.PLEX_TYPE_CLIP): + with PlexDB(lock=False) as plexdb: + db_item = plexdb.item_by_id(plex_id, plex_type) + if db_item is not None: + self.kodi_id = db_item['kodi_id'] + self.kodi_type = db_item['kodi_type'] + self.plex_uuid = db_item['section_uuid'] + self.set_uri() - def __eq__(self, item): - if self.plex_id is not None and item.plex_id is not None: - return self.plex_id == item.plex_id - elif (self.kodi_id is not None and item.kodi_id is not None and - self.kodi_type and item.kodi_type): - return (self.kodi_id == item.kodi_id and - self.kodi_type == item.kodi_type) - elif self.file and item.file: - return self.file == item.file - raise RuntimeError('playlist items not fully defined: %s, %s' % - (self, item)) + def __eq__(self, other): + if self.plex_id is not None and other.plex_id is not None: + return self.plex_id == other.plex_id + elif (self.kodi_id is not None and other.kodi_id is not None and + self.kodi_type and other.kodi_type): + return (self.kodi_id == other.kodi_id and + self.kodi_type == other.kodi_type) + elif self.file and other.file: + return self.file == other.file + raise RuntimeError('PlaylistItems not fully defined: %s, %s' % + (self, other)) - def __ne__(self, item): - return not self == item + def __ne__(self, other): + return not self == other - @property - def plex_id(self): - return self._plex_id - - @plex_id.setter - def plex_id(self, value): - if not isinstance(value, int) and value is not None: - raise TypeError('Passed %s instead of int!' % type(value)) - self._plex_id = value - - @property - def id(self): - return self._id - - @id.setter - def id(self, value): - if not isinstance(value, int) and value is not None: - raise TypeError('Passed %s instead of int!' % type(value)) - self._id = value - - @property - def kodi_id(self): - return self._kodi_id - - @kodi_id.setter - def kodi_id(self, value): - if not isinstance(value, int) and value is not None: - raise TypeError('Passed %s instead of int!' % type(value)) - self._kodi_id = value - - @property - def playcount(self): - return self._playcount - - @playcount.setter - def playcount(self, value): - if not isinstance(value, int) and value is not None: - raise TypeError('Passed %s instead of int!' % type(value)) - self._playcount = value - - @property - def offset(self): - return self._offset - - @offset.setter - def offset(self, value): - if not isinstance(value, (int, float)) and value is not None: - raise TypeError('Passed %s instead of int!' % type(value)) - self._offset = value - - @property - def part(self): - return self._part - - @part.setter - def part(self, value): - if not isinstance(value, int) and value is not None: - raise TypeError('Passed %s instead of int!' % type(value)) - self._part = value - - def __repr__(self): - answ = ("{{" + def __unicode__(self): + return ("{{" + "'name': '{self.name}', " "'id': {self.id}, " "'plex_id': {self.plex_id}, " "'plex_type': '{self.plex_type}', " - "'plex_uuid': '{self.plex_uuid}', " "'kodi_id': {self.kodi_id}, " "'kodi_type': '{self.kodi_type}', " "'file': '{self.file}', " @@ -263,13 +545,71 @@ class Playlist_Item(object): "'playcount': {self.playcount}, " "'offset': {self.offset}, " "'force_transcode': {self.force_transcode}, " - "'part': {self.part}, ".format(self=self)) - answ = answ.encode('utf-8') - # etree xml.__repr__() could return string, not unicode - return answ + b"'xml': \"{self.xml}\"}}".format(self=self) + "'part': {self.part}" + "}}".format(self=self)) def __str__(self): - return self.__repr__() + return unicode(self).encode('utf-8') + __repr__ = __str__ + + def from_xml(self, xml_video_element): + """ + xml_video_element: etree xml piece 1 level underneath + item.id will only be set if you passed in an xml_video_element from + e.g. a playQueue + """ + api = API(xml_video_element) + self.name = api.title() + self.plex_id = api.plex_id() + self.plex_type = api.plex_type() + self.id = api.item_id() + self.guid = api.guid_html_escaped() + self.playcount = api.viewcount() + self.offset = api.resume_point() + self.xml = xml_video_element + + def from_kodi(self, playlist_item): + """ + playlist_item: dict contains keys 'id', 'type', 'file' (if applicable) + + Will thus set the attributes kodi_id, kodi_type, file, if applicable + If kodi_id & kodi_type are provided, plex_id and plex_type will be + looked up (if not already set) + """ + self.kodi_id = playlist_item.get('id') + self.kodi_type = playlist_item.get('type') + self.file = playlist_item.get('file') + if self.plex_id is None and self.kodi_id is not None and self.kodi_type: + with PlexDB(lock=False) as plexdb: + db_item = plexdb.item_by_kodi_id(self.kodi_id, self.kodi_type) + if db_item: + self.plex_id = db_item['plex_id'] + self.plex_type = db_item['plex_type'] + self.plex_uuid = db_item['section_uuid'] + if self.plex_id is None and self.file is not None: + try: + query = self.file.split('?', 1)[1] + except IndexError: + query = '' + query = dict(utils.parse_qsl(query)) + self.plex_id = utils.cast(int, query.get('plex_id')) + self.plex_type = query.get('itemType') + self.set_uri() + LOG.debug('Made playlist item from Kodi: %s', self) + + def set_uri(self): + if self.plex_id is None and self.file is not None: + self.uri = ('library://whatever/item/%s' + % utils.quote(self.file, safe='')) + elif self.plex_id is not None and self.plex_uuid is not None: + # TO BE VERIFIED - PLEX DOESN'T LIKE PLAYLIST ADDS IN THIS MANNER + self.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % + (self.plex_uuid, self.plex_id)) + elif self.plex_id is not None: + self.uri = ('library://%s/item/library%%2Fmetadata%%2F%s' % + (self.plex_id, self.plex_id)) + else: + self.uri = None def plex_stream_index(self, kodi_stream_index, stream_type): """ @@ -327,6 +667,17 @@ class Playlist_Item(object): count += 1 +class PlaylistItemDummy(PlaylistItem): + """ + Let e.g. Kodimonitor detect that this is a dummy item + """ + def __init__(self, *args, **kwargs): + super(PlaylistItemDummy, self).__init__(*args, **kwargs) + self.name = 'dummy item' + self.id = 0 + self.plex_id = 0 + + def playlist_item_from_kodi(kodi_item): """ Turns the JSON answer from Kodi into a playlist element @@ -334,7 +685,7 @@ def playlist_item_from_kodi(kodi_item): Supply with data['item'] as returned from Kodi JSON-RPC interface. kodi_item dict contains keys 'id', 'type', 'file' (if applicable) """ - item = Playlist_Item() + item = PlaylistItem() item.kodi_id = kodi_item.get('id') item.kodi_type = kodi_item.get('type') if item.kodi_id: @@ -343,7 +694,7 @@ def playlist_item_from_kodi(kodi_item): if db_item: item.plex_id = db_item['plex_id'] item.plex_type = db_item['plex_type'] - item.plex_uuid = db_item['plex_id'] # we dont need the uuid yet :-) + item.plex_uuid = db_item['section_uuid'] item.file = kodi_item.get('file') if item.plex_id is None and item.file is not None: try: @@ -413,7 +764,7 @@ def playlist_item_from_plex(plex_id): Returns a Playlist_Item """ - item = Playlist_Item() + item = PlaylistItem() item.plex_id = plex_id with PlexDB(lock=False) as plexdb: db_item = plexdb.item_by_id(plex_id) @@ -436,7 +787,7 @@ def playlist_item_from_xml(xml_video_element, kodi_id=None, kodi_type=None): xml_video_element: etree xml piece 1 level underneath """ - item = Playlist_Item() + item = PlaylistItem() api = API(xml_video_element) item.plex_id = api.plex_id() item.plex_type = api.plex_type() @@ -612,12 +963,14 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None): Returns the PKC PlayList item or raises PlaylistError """ + LOG.debug('Adding item to Plex playqueue with plex id %s, kodi_item %s at ' + 'position %s', plex_id, kodi_item, pos) verify_kodi_item(plex_id, kodi_item) 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) + url = '{server}/%ss/%s?uri=%s' % (playlist.kind, playlist.id, item.uri) # Will always put the new item at the end of the Plex playlist xml = DU().downloadUrl(url, action_type="PUT") try: @@ -625,21 +978,27 @@ def add_item_to_plex_playqueue(playlist, pos, plex_id=None, kodi_item=None): except (TypeError, AttributeError, KeyError, IndexError): raise PlaylistError('Could not add item %s to playlist %s' % (kodi_item, playlist)) - api = API(xml[-1]) - item.xml = xml[-1] + if len(xml) != len(playlist.items) + 1: + raise PlaylistError('Couldnt add item %s to playlist %s - wrong length' + % (kodi_item, playlist)) + for actual_pos, xml_video_element in enumerate(xml): + api = API(xml_video_element) + if api.plex_id() == item.plex_id: + break + else: + raise PlaylistError('Something went terribly wrong!') + utils.dump_xml(xml) + LOG.debug('Plex added the new item at position %s', actual_pos) + item.xml = xml[actual_pos] item.id = api.item_id() item.guid = api.guid_html_escaped() item.offset = api.resume_point() item.playcount = api.viewcount() - playlist.items.append(item) - if pos == len(playlist.items) - 1: - # Item was added at the end - _get_playListVersion_from_xml(playlist, xml) - else: + playlist.items.insert(actual_pos, item) + _get_playListVersion_from_xml(playlist, xml) + if actual_pos != pos: # Move the new item to the correct position - move_playlist_item(playlist, - len(playlist.items) - 1, - pos) + move_playlist_item(playlist, actual_pos, pos) LOG.debug('Successfully added item on the Plex side: %s', playlist) return item @@ -710,6 +1069,7 @@ def move_playlist_item(playlist, before_pos, after_pos): LOG.error('Could not move playlist item') return _get_playListVersion_from_xml(playlist, xml) + utils.dump_xml(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) diff --git a/resources/lib/playqueue.py b/resources/lib/playqueue.py index 929ccc75..dde6921b 100644 --- a/resources/lib/playqueue.py +++ b/resources/lib/playqueue.py @@ -18,7 +18,7 @@ LOG = getLogger('PLEX.playqueue') PLUGIN = 'plugin://%s' % v.ADDON_ID -# Our PKC playqueues (3 instances of Playqueue_Object()) +# Our PKC playqueues (3 instances PlayQueue()) PLAYQUEUES = [] ############################################################################### @@ -38,7 +38,7 @@ def init_playqueues(): for queue in js.get_playlists(): if queue['playlistid'] != i: continue - playqueue = PL.Playqueue_Object() + playqueue = PL.PlayQueue() playqueue.playlistid = i playqueue.type = queue['type'] # Initialize each Kodi playlist @@ -206,6 +206,8 @@ class PlayqueueMonitor(backgroundthread.KillableThread): with app.APP.lock_playqueues: for playqueue in PLAYQUEUES: kodi_pl = js.playlist_get_items(playqueue.playlistid) + playqueue.old_kodi_pl = list(kodi_pl) + continue if playqueue.old_kodi_pl != kodi_pl: if playqueue.id is None and (not app.SYNC.direct_paths or app.PLAYSTATE.context_menu_play): @@ -215,5 +217,4 @@ class PlayqueueMonitor(backgroundthread.KillableThread): else: # compare old and new playqueue self._compare_playqueues(playqueue, kodi_pl) - playqueue.old_kodi_pl = list(kodi_pl) app.APP.monitor.waitForAbort(0.2) diff --git a/resources/lib/playstrm.py b/resources/lib/playstrm.py index adab3150..55b7f338 100644 --- a/resources/lib/playstrm.py +++ b/resources/lib/playstrm.py @@ -2,13 +2,8 @@ from __future__ import absolute_import, division, unicode_literals from logging import getLogger -import xbmc - -from .plex_api import API -from .playutils import PlayUtils -from .windows.resume import resume_dialog -from . import app, plex_functions as PF, utils, json_rpc, variables as v, \ - widgets, playlist_func as PL, playqueue as PQ +from . import app, utils, json_rpc, variables as v, playlist_func as PL, \ + playqueue as PQ LOG = getLogger('PLEX.playstrm') @@ -27,15 +22,8 @@ class PlayStrm(object): webserivce returns a dummy file to play. Meanwhile, PlayStrm adds the real listitems for items to play to the playlist. ''' - def __init__(self, params, server_id=None): - LOG.debug('Starting PlayStrm with server_id %s, params: %s', - server_id, params) - self.xml = None - self.playqueue_item = None - self.api = None - self.start_index = None - self.index = None - self.server_id = server_id + def __init__(self, params): + LOG.debug('Starting PlayStrm with params: %s', params) self.plex_id = utils.cast(int, params['plex_id']) self.plex_type = params.get('plex_type') if params.get('synched') and params['synched'].lower() == 'false': @@ -44,97 +32,46 @@ class PlayStrm(object): self.synched = True self.kodi_id = utils.cast(int, params.get('kodi_id')) self.kodi_type = params.get('kodi_type') - self._get_xml() - self.name = self.api.title() - if ((self.kodi_id is None or self.kodi_type is None) and - self.xml[0].get('pkc_db_item')): - self.kodi_id = self.xml[0].get('pkc_db_item')['kodi_id'] - self.kodi_type = self.xml[0].get('pkc_db_item')['kodi_type'] - self.transcode = params.get('transcode') - if self.transcode is None: - self.transcode = utils.settings('playFromTranscode.bool') if utils.settings('playFromStream.bool') else None + self.force_transcode = params.get('transcode') == 'true' if app.PLAYSTATE.audioplaylist: LOG.debug('Audio playlist detected') self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO) else: LOG.debug('Video playlist detected') self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO) - self.kodi_playlist = self.playqueue.kodi_pl - def __repr__(self): + def __unicode__(self): return ("{{" - "'name': '{self.name}', " "'plex_id': {self.plex_id}, " "'plex_type': '{self.plex_type}', " "'kodi_id': {self.kodi_id}, " "'kodi_type': '{self.kodi_type}', " - "'server_id': '{self.server_id}', " - "'transcode': {self.transcode}, " - "'start_index': {self.start_index}, " - "'index': {self.index}" - "}}").format(self=self).encode('utf-8') - __str__ = __repr__ + "}}").format(self=self) - def playlist_add_json(self): - playlistid = self.kodi_playlist.getPlayListId() - LOG.debug('Adding kodi_id %s, kodi_type %s to playlist %s at index %s', - self.kodi_id, self.kodi_type, playlistid, self.index) - if self.index is None: - json_rpc.playlist_add(playlistid, - {'%sid' % self.kodi_type: self.kodi_id}) - else: - json_rpc.playlist_insert({'playlistid': playlistid, - 'position': self.index, - 'item': {'%sid' % self.kodi_type: self.kodi_id}}) - - def playlist_add(self, url, listitem): - self.kodi_playlist.add(url=url, listitem=listitem, index=self.index) - self.playqueue_item.file = url.decode('utf-8') - self.playqueue.items.insert(self.index, self.playqueue_item) - self.index += 1 - - def remove_from_playlist(self, index): - LOG.debug('Removing playlist item number %s from %s', index, self) - json_rpc.playlist_remove(self.kodi_playlist.getPlayListId(), - index) - - def _get_xml(self): - self.xml = PF.GetPlexMetadata(self.plex_id) - if self.xml in (None, 401): - raise PlayStrmException('No xml received from the PMS') - if self.synched: - # Adds a new key 'pkc_db_item' to self.xml[0].attrib - widgets.attach_kodi_ids(self.xml) - else: - self.xml[0].set('pkc_db_item', None) - self.api = API(self.xml[0]) - - def set_playqueue_item(self, xml, kodi_id, kodi_type): - self.playqueue_item = PL.playlist_item_from_xml(xml, - kodi_id=kodi_id, - kodi_type=kodi_type) - self.playqueue_item.force_transcode = self.transcode - - def start_playback(self, index=0): - LOG.debug('Starting playback at %s', index) - xbmc.Player().play(self.kodi_playlist, startpos=index, windowed=False) + def __str__(self): + return unicode(self).encode('utf-8') + __repr__ = __str__ def play(self, start_position=None, delayed=True): ''' - Create and add listitems to the Kodi playlist. + Create and add a single listitem to the Kodi playlist, potentially + with trailers and different file-parts ''' LOG.debug('play called with start_position %s, delayed %s', start_position, delayed) - if start_position is not None: - self.start_index = start_position - else: - self.start_index = max(self.kodi_playlist.getposition(), 0) - self.index = self.start_index - self._set_playlist() + LOG.debug('Kodi playlist BEFORE: %s', + json_rpc.playlist_get_items(self.playqueue.playlistid)) + self.playqueue.init(self.plex_id, + plex_type=self.plex_type, + position=start_position, + synched=self.synched, + force_transcode=self.force_transcode) LOG.info('Initiating play for %s', self) + LOG.debug('Kodi playlist AFTER: %s', + json_rpc.playlist_get_items(self.playqueue.playlistid)) if not delayed: - self.start_playback(self.start_index) - return self.index + self.playqueue.start_playback(start_position) + return self.playqueue.index def play_folder(self, position=None): ''' @@ -142,136 +79,13 @@ class PlayStrm(object): provided, add as Kodi would, otherwise queue playlist items using strm links to setup playback later. ''' - self.start_index = position or max(self.kodi_playlist.size(), 0) - self.index = self.start_index + 1 - LOG.info('Play folder plex_id %s, index: %s', self.plex_id, self.index) - if self.kodi_id and self.kodi_type: - self.playlist_add_json() - self.index += 1 - else: - listitem = widgets.get_listitem(self.xml[0], resume=True) - url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT - args = { - 'plex_id': self.plex_id, - 'plex_type': self.api.plex_type() - } - if self.kodi_id: - args['kodi_id'] = self.kodi_id - if self.kodi_type: - args['kodi_type'] = self.kodi_type - if self.server_id: - args['server_id'] = self.server_id - if self.transcode: - args['transcode'] = 'true' - url = utils.extend_url(url, args).encode('utf-8') - listitem.setPath(url) - self.playlist_add(url, listitem) - return self.index - 1 - - def _set_playlist(self): - ''' - Verify seektime, set intros, set main item and set additional parts. - Detect the seektime for video type content. Verify the default video - action set in Kodi for accurate resume behavior. - ''' - seektime = self._resume() - trailers = False - if (not seektime and self.plex_type == v.PLEX_TYPE_MOVIE and - utils.settings('enableCinema') == 'true'): - if utils.settings('askCinema') == "true": - # "Play trailers?" - trailers = utils.yesno_dialog(utils.lang(29999), - utils.lang(33016)) or False - else: - trailers = True - LOG.debug('Playing trailers: %s', trailers) - xml = PF.init_plex_playqueue(self.plex_id, - self.xml.get('librarySectionUUID'), - mediatype=self.plex_type, - trailers=trailers) - if xml is None: - LOG.error('Could not get playqueue for UUID %s for %s', - self.xml.get('librarySectionUUID'), self) - # "Play error" - utils.dialog('notification', - utils.lang(29999), - utils.lang(30128), - icon='{error}') - app.PLAYSTATE.context_menu_play = False - app.PLAYSTATE.resume_playback = False - return - PL.get_playlist_details_from_xml(self.playqueue, xml) - # See that we add trailers, if they exist in the xml return - self._add_intros(xml) - # Add the main item - if seektime: - listitem = widgets.get_listitem(self.xml[0], resume=True) - else: - listitem = widgets.get_listitem(self.xml[0], resume=False) - listitem.setSubtitles(self.api.cache_external_subs()) - self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type) - play = PlayUtils(self.api, self.playqueue_item) - url = play.getPlayUrl().encode('utf-8') - listitem.setPath(url) - self.playlist_add(url, listitem) - # Add additional file parts, if any exist - self._add_additional_parts() - - def _resume(self): - ''' - Resume item if available. Returns bool or raise an PlayStrmException if - resume was cancelled by user. - ''' - seektime = app.PLAYSTATE.resume_playback - app.PLAYSTATE.resume_playback = None - if app.PLAYSTATE.autoplay: - seektime = False - LOG.info('Skip resume for autoplay') - elif seektime is None: - resume = self.api.resume_point() - if resume: - seektime = resume_dialog(resume) - LOG.info('User chose resume: %s', seektime) - if seektime is None: - raise PlayStrmException('User backed out of resume dialog.') - app.PLAYSTATE.autoplay = True - return seektime - - def _add_intros(self, xml): - ''' - if we have any play them when the movie/show is not being resumed. - ''' - if not len(xml) > 1: - LOG.debug('No trailers returned from the PMS') - return - for intro in xml: - api = API(intro) - if not api.plex_type() == v.PLEX_TYPE_CLIP: - # E.g. the main item we're looking at - skip! - continue - LOG.debug('Adding trailer: %s', api.title()) - listitem = widgets.get_listitem(intro, resume=False) - self.set_playqueue_item(intro, None, None) - play = PlayUtils(api, self.playqueue_item) - url = play.getPlayUrl().encode('utf-8') - listitem.setPath(url) - self.playlist_add(url, listitem) - - def _add_additional_parts(self): - ''' Create listitems and add them to the stack of playlist. - ''' - for part, _ in enumerate(self.xml[0][0]): - if part == 0: - # The first part that we've already added - continue - self.api.set_part_number(part) - LOG.debug('Adding addional part %s', part) - self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type) - self.playqueue_item.part = part - listitem = widgets.get_listitem(self.xml[0], resume=False) - listitem.setSubtitles(self.api.cache_external_subs()) - playqueue_item = PL.playlist_item_from_xml(self.xml[0]) - play = PlayUtils(self.api, playqueue_item) - url = play.getPlayUrl().encode('utf-8') - listitem.setPath(url) - self.playlist_add(url, listitem) + start_position = position or max(self.playqueue.kodi_pl.size(), 0) + index = start_position + 1 + LOG.info('Play folder plex_id %s, index: %s', self.plex_id, index) + item = PL.PlaylistItem(plex_id=self.plex_id, + plex_type=self.plex_type, + kodi_id=self.kodi_id, + kodi_type=self.kodi_type) + self.playqueue.add_item(item, index) + index += 1 + return index - 1 diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index 773f9fb8..96a22ea0 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -14,13 +14,13 @@ LOG = getLogger('PLEX.playutils') class PlayUtils(): - def __init__(self, api, playqueue_item): + def __init__(self, api, playlistitem): """ init with api (PlexAPI wrapper of the PMS xml element) and - playqueue_item (Playlist_Item()) + playlistitem [PlaylistItem()] """ self.api = api - self.item = playqueue_item + self.item = playlistitem def getPlayUrl(self): """ diff --git a/resources/lib/plex_db/playlists.py b/resources/lib/plex_db/playlists.py index 3c14f259..37d4f7c8 100644 --- a/resources/lib/plex_db/playlists.py +++ b/resources/lib/plex_db/playlists.py @@ -20,7 +20,7 @@ class Playlists(object): def delete_playlist(self, playlist): """ - Removes the entry for playlist [Playqueue_Object] from the Plex + Removes the entry for playlist [PlayQueue] from the Plex playlists table. Be sure to either set playlist.id or playlist.kodi_path """ diff --git a/resources/lib/plex_functions.py b/resources/lib/plex_functions.py index b70efe8c..a2e412cd 100644 --- a/resources/lib/plex_functions.py +++ b/resources/lib/plex_functions.py @@ -820,14 +820,14 @@ def get_plex_sections(): return xml -def init_plex_playqueue(plex_id, librarySectionUUID, mediatype='movie', +def init_plex_playqueue(plex_id, librarySectionUUID, plex_type='movie', trailers=False): """ Returns raw API metadata XML dump for a playlist with e.g. trailers. - """ + """ url = "{server}/playQueues" args = { - 'type': mediatype, + 'type': plex_type, 'uri': ('library://{0}/item/%2Flibrary%2Fmetadata%2F{1}'.format( librarySectionUUID, plex_id)), 'includeChapters': '1', diff --git a/resources/lib/utils.py b/resources/lib/utils.py index afcafdb1..ae5baa58 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -69,6 +69,16 @@ def getGlobalProperty(key): 'Window(10000).Property(plugin.video.plexkodiconnect.{0})'.format(key)) +def dump_xml(xml): + tree = etree.ElementTree(xml) + i = 0 + while path_ops.exists(path_ops.path.join(v.ADDON_PROFILE, 'xml%s.xml' % i)): + i += 1 + tree.write(path_ops.path.join(v.ADDON_PROFILE, 'xml%s.xml' % i), + encoding='utf-8') + LOG.debug('Dumped to xml: %s', 'xml%s.xml' % i) + + def reboot_kodi(message=None): """ Displays an OK prompt with 'Kodi will now restart to apply the changes' diff --git a/resources/lib/webservice.py b/resources/lib/webservice.py index 2c336f7e..aef3af38 100644 --- a/resources/lib/webservice.py +++ b/resources/lib/webservice.py @@ -13,8 +13,8 @@ import Queue import xbmc import xbmcvfs -from . import backgroundthread, utils, variables as v, app -from .playstrm import PlayStrm +from . import backgroundthread, utils, variables as v, app, playqueue as PQ +from . import playlist_func as PL, json_rpc as js LOG = getLogger('PLEX.webservice') @@ -270,58 +270,110 @@ class QueuePlay(backgroundthread.KillableThread): def __init__(self, server): self.server = server + self.plex_id = None + self.plex_type = None + self.kodi_id = None + self.kodi_type = None + self.synched = None + self.force_transcode = None super(QueuePlay, self).__init__() + def load_params(self, params): + self.plex_id = utils.cast(int, params['plex_id']) + self.plex_type = params.get('plex_type') + self.kodi_id = utils.cast(int, params.get('kodi_id')) + self.kodi_type = params.get('kodi_type') + if params.get('synched') and params['synched'].lower() == 'false': + self.synched = False + else: + self.synched = True + if params.get('transcode') and params['transcode'].lower() == 'true': + self.force_transcode = True + else: + self.force_transcode = False + def run(self): - LOG.info('##===---- Starting QueuePlay ----===##') + LOG.debug('##===---- Starting QueuePlay ----===##') + if app.PLAYSTATE.audioplaylist: + LOG.debug('Audio playlist detected') + playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO) + else: + LOG.debug('Video playlist detected') + playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO) + abort = False play_folder = False - play = None - start_position = None - position = None - - # Let Kodi catch up + # Position to start playback from (!!) + # Do NOT use kodi_pl.getposition() as that appears to be buggy + start_position = max(js.get_position(playqueue.playlistid), 0) + # Position to add next element to queue - we're doing this at the end + # of our playqueue + position = playqueue.kodi_pl.size() + LOG.debug('start %s, position %s for current playqueue: %s', + start_position, position, playqueue) + # Make sure we got at least 2 items in the queue - ugly + # TODO: find a better solution xbmc.sleep(200) - while True: - try: try: params = self.server.queue.get(timeout=0.1) except Queue.Empty: - count = 20 + count = 50 while not utils.window('plex.playlist.ready'): xbmc.sleep(50) if not count: LOG.info('Playback aborted') - raise Exception('PlaybackAborted') + raise Exception('Playback aborted') count -= 1 - LOG.info('Starting playback at position: %s', start_position) if play_folder: LOG.info('Start playing folder') xbmc.executebuiltin('Dialog.Close(busydialognocancel)') - play.start_playback() + playqueue.start_playback(start_position) else: + # TODO - do we need to do anything here? + # Originally, 1st failable item should have been removed utils.window('plex.playlist.play', value='true') - # xbmc.sleep(1000) - play.remove_from_playlist(start_position) + # playqueue.kodi_remove_item(start_position) break - play = PlayStrm(params, params.get('ServerId')) - - if start_position is None: - start_position = max(play.kodi_playlist.getposition(), 0) - position = start_position + 1 + self.load_params(params) if play_folder: - position = play.play_folder(position) + # position = play.play_folder(position) + item = PL.PlaylistItem(plex_id=self.plex_id, + plex_type=self.plex_type, + kodi_id=self.kodi_id, + kodi_type=self.kodi_type) + item.force_transcode = self.force_transcode + playqueue.add_item(item, position) + position += 1 else: if self.server.pending.count(params['plex_id']) != len(self.server.pending): + LOG.debug('Folder playback detected') play_folder = True utils.window('plex.playlist.start', str(start_position)) - position = play.play(position) + playqueue.init(self.plex_id, + plex_type=self.plex_type, + position=position, + synched=self.synched, + force_transcode=self.force_transcode) + # Do NOT start playback here - because Kodi already started + # it! + # playqueue.start_playback(position) + position = playqueue.index if play_folder: xbmc.executebuiltin('Activateutils.window(busydialognocancel)') + except PL.PlaylistError as error: + abort = True + LOG.warn('Not playing due to the following: %s', error) except Exception: + abort = True utils.ERROR() - play.kodi_playlist.clear() + try: + self.server.queue.task_done() + except ValueError: + # "task_done() called too many times" + pass + if abort: + playqueue.clear() xbmc.Player().stop() self.server.queue.queue.clear() if play_folder: @@ -329,11 +381,10 @@ class QueuePlay(backgroundthread.KillableThread): else: utils.window('plex.playlist.aborted', value='true') break - self.server.queue.task_done() utils.window('plex.playlist.ready', clear=True) utils.window('plex.playlist.start', clear=True) app.PLAYSTATE.audioplaylist = None self.server.threads.remove(self) self.server.pending = [] - LOG.info('##===---- QueuePlay Stopped ----===##') + LOG.debug('##===---- QueuePlay Stopped ----===##')