diff --git a/resources/lib/app/playstate.py b/resources/lib/app/playstate.py index 7736ed17..e0b82960 100644 --- a/resources/lib/app/playstate.py +++ b/resources/lib/app/playstate.py @@ -63,3 +63,6 @@ class PlayState(object): self.context_menu_play = False # Which Kodi player is/has been active? (either int 1, 2 or 3) self.active_players = set() + # Have we initiated playback via Plex Companion or Alexa - so from the + # Plex side of things? + self.initiated_by_plex = False diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 44fbff92..c7604985 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -322,12 +322,10 @@ class KodiMonitor(xbmc.Monitor): LOG.debug('Current Kodi playlist: %s', kodi_playlist) kodi_item = PL.playlist_item_from_kodi(kodi_playlist[position]) 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 + # This dummy item will be deleted by webservice soon - it won't + # play + LOG.debug('Dummy item detected') + position = 1 elif kodi_item != self.playqueue.items[position]: LOG.debug('Different playqueue items: %s vs. %s ', kodi_item, self.playqueue.items[position]) @@ -487,9 +485,6 @@ 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/playlist_func.py b/resources/lib/playlist_func.py index 9512d1c6..48442e32 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -5,8 +5,7 @@ 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 +import threading from .plex_api import API from .plex_db import PlexDB @@ -139,20 +138,59 @@ class PlayQueue(object): 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): + def play(self, plex_id, plex_type=None, startpos=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 + + Or resolves webservice paths to actual paths + """ + LOG.debug('Play called with plex_id %s, plex_type %s, position %s, ' + 'synched %s, force_transcode %s, startpos %s', plex_id, + plex_type, position, synched, force_transcode, startpos) + resolve = False + try: + if plex_id == self.items[startpos].plex_id: + resolve = True + except IndexError: + pass + if resolve: + LOG.info('Resolving playback') + self._resolve(plex_id, startpos) + else: + LOG.info('Initializing playback') + self.init(plex_id, + plex_type, + startpos, + position, + synched, + force_transcode) + + def _resolve(self, plex_id, startpos): + """ + The Plex playqueue has already been initialized. We resolve the path + from original webservice http://127.0.0.1 to the "correct" Plex one + """ + self.index = startpos + 1 + xml = PF.GetPlexMetadata(plex_id) + if xml in (None, 401): + raise PlaylistError('Could not get Plex metadata %s for %s', + plex_id, self.items[startpos]) + api = API(xml[0]) + resume = self._resume_playback(None, xml[0]) + self._kodi_add_xml(xml[0], api, resume) + # Add additional file parts, if any exist + self._add_additional_parts(xml) + + def init(self, plex_id, plex_type=None, startpos=None, position=None, + synched=True, force_transcode=None): + """ + Initializes the Plex and PKC playqueue for playback """ - LOG.error('Current Kodi playlist: %s', - js.playlist_get_items(self.playlistid)) - 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) self.index = position - if self.kodi_pl.size() != len(self.items): + while len(self.items) < self.kodi_pl.size(): # The original item that Kodi put into the playlist, e.g. # { # u'title': u'', @@ -161,13 +199,10 @@ class PlayQueue(object): # 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) + # on the PKC side to keep all indicees lined up. + # The failing item will be deleted in webservice.py + LOG.debug('Adding a dummy item to our playqueue') + self.items.insert(0, PlaylistItemDummy()) self.force_transcode = force_transcode if synched: with PlexDB(lock=False) as plexdb: @@ -316,7 +351,11 @@ class PlayQueue(object): 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: + if listitem: + self.kodi_pl.add(url=listitem.getPath(), + listitem=listitem, + index=pos) + elif 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): @@ -330,24 +369,24 @@ class PlayQueue(object): 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(), + 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) + api = API(item.xml[0]) + 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': item.plex_id, + 'plex_type': api.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=url.encode('utf-8'), listitem=listitem, index=pos) @@ -372,9 +411,6 @@ class PlayQueue(object): 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: @@ -395,7 +431,7 @@ class PlayQueue(object): 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) + raise PlaylistError('Could not remove item: %s' % answ['error']) def plex_move_item(self, before, after): """ @@ -436,9 +472,71 @@ class PlayQueue(object): 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) + def init_from_xml(self, xml, offset=None, start_plex_id=None, repeat=None, + transient_token=None): + """ + Play all items contained in the xml passed in. Called by Plex Companion. + Either supply the ratingKey of the starting Plex element. Or set + playqueue.selectedItemID + + offset [float]: will seek to position offset after playback start + start_plex_id [int]: the plex_id of the element that should be + played + repeat [int]: 0: don't repear + 1: repeat item + 2: repeat everything + transient_token [unicode]: temporary token received from the PMS + + Will stop current playback and start playback at the end + """ + LOG.debug("init_from_xml called with offset %s, start_plex_id %s", + offset, start_plex_id) + app.APP.player.stop() + self.clear() + self.update_details_from_xml(xml) + self.repeat = 0 if not repeat else repeat + self.plex_transient_token = transient_token + for pos, xml_video_element in enumerate(xml): + playlistitem = PlaylistItem(xml_video_element=xml_video_element) + self.kodi_add_item(playlistitem, pos) + self.items.append(playlistitem) + # Where do we start playback? + if start_plex_id is not None: + for startpos, item in enumerate(self.items): + if item.plex_id == start_plex_id: + break + else: + startpos = 0 + else: + for startpos, item in enumerate(self.items): + if item.id == self.selectedItemID: + break + else: + startpos = 0 + self.start_playback(pos=startpos, offset=offset) + + def start_playback(self, pos=0, offset=0): + """ + Seek immediately after kicking off playback is not reliable. + Threaded, since we need to return BEFORE seeking + """ + LOG.info('Starting playback at %s offset %s for %s', pos, offset, self) + thread = threading.Thread(target=self._threaded_playback, + args=(self.kodi_pl, pos, offset)) + thread.start() + + @staticmethod + def _threaded_playback(kodi_playlist, pos, offset): + app.APP.player.play(kodi_playlist, startpos=pos, windowed=False) + if offset: + i = 0 + while not app.APP.is_playing: + app.APP.monitor.waitForAbort(0.1) + i += 1 + if i > 50: + LOG.warn('Could not seek to %s', offset) + return + js.seek_to(offset) class PlaylistItem(object): @@ -1069,7 +1167,7 @@ def move_playlist_item(playlist, before_pos, after_pos): LOG.debug('Done moving for %s', playlist) -def get_PMS_playlist(playlist, playlist_id=None): +def get_PMS_playlist(playlist=None, playlist_id=None): """ Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we need to fetch a new playlist @@ -1077,7 +1175,7 @@ def get_PMS_playlist(playlist, playlist_id=None): Returns None if something went wrong """ playlist_id = playlist_id if playlist_id else playlist.id - if playlist.kind == 'playList': + if playlist and playlist.kind == 'playList': xml = DU().downloadUrl("{server}/playlists/%s/items" % playlist_id) else: xml = DU().downloadUrl("{server}/playQueues/%s" % playlist_id) diff --git a/resources/lib/plex_companion.py b/resources/lib/plex_companion.py index 267e42e0..285e4773 100644 --- a/resources/lib/plex_companion.py +++ b/resources/lib/plex_companion.py @@ -15,7 +15,6 @@ from .plex_api import API from . import utils from . import plex_functions as PF from . import playlist_func as PL -from . import playback from . import json_rpc as js from . import playqueue as PQ from . import variables as v @@ -40,6 +39,8 @@ def update_playqueue_from_PMS(playqueue, repeat = 0, 1, 2 offset = time offset in Plextime (milliseconds) + + Will (re)start playback """ LOG.info('New playqueue %s received from Plex companion with offset ' '%s, repeat %s', playqueue_id, offset, repeat) @@ -47,21 +48,15 @@ def update_playqueue_from_PMS(playqueue, if transient_token is None: transient_token = playqueue.plex_transient_token with app.APP.lock_playqueues: - xml = PL.get_PMS_playlist(playqueue, playqueue_id) - try: - xml.attrib - except AttributeError: + xml = PL.get_PMS_playlist(playlist_id=playqueue_id) + if xml is None: LOG.error('Could now download playqueue %s', playqueue_id) - return - playqueue.clear() - try: - PL.get_playlist_details_from_xml(playqueue, xml) - except PL.PlaylistError: - LOG.error('Could not get playqueue ID %s', playqueue_id) - return - playqueue.repeat = 0 if not repeat else int(repeat) - playqueue.plex_transient_token = transient_token - playback.play_xml(playqueue, xml, offset) + raise PL.PlaylistError() + app.PLAYSTATE.initiated_by_plex = True + playqueue.init_from_xml(xml, + offset=offset, + repeat=0 if not repeat else int(repeat), + transient_token=transient_token) class PlexCompanion(backgroundthread.KillableThread): @@ -81,45 +76,48 @@ class PlexCompanion(backgroundthread.KillableThread): @staticmethod def _process_alexa(data): + app.PLAYSTATE.initiated_by_plex = True xml = PF.GetPlexMetadata(data['key']) try: xml[0].attrib except (AttributeError, IndexError, TypeError): LOG.error('Could not download Plex metadata for: %s', data) - return + raise PL.PlaylistError() api = API(xml[0]) if api.plex_type() == v.PLEX_TYPE_ALBUM: LOG.debug('Plex music album detected') - PQ.init_playqueue_from_plex_children( - api.plex_id(), - transient_token=data.get('token')) + xml = PF.GetAllPlexChildren(api.plex_id()) + try: + xml[0].attrib + except (TypeError, IndexError, AttributeError): + LOG.error('Could not download the album xml for %s', data) + raise PL.PlaylistError() + playqueue = PQ.get_playqueue_from_type('audio') + playqueue.init_from_xml(xml, + transient_token=data.get('token')) elif data['containerKey'].startswith('/playQueues/'): _, container_key, _ = PF.ParseContainerKey(data['containerKey']) xml = PF.DownloadChunks('{server}/playQueues/%s' % container_key) if xml is None: - # "Play error" - utils.dialog('notification', - utils.lang(29999), - utils.lang(30128), - icon='{error}') - return + LOG.error('Could not get playqueue for %s', data) + raise PL.PlaylistError() playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) - playqueue.clear() - PL.get_playlist_details_from_xml(playqueue, xml) - playqueue.plex_transient_token = data.get('token') - if data.get('offset') != '0': + if data.get('offset') not in ('0', None): offset = float(data['offset']) / 1000.0 else: offset = None - playback.play_xml(playqueue, xml, offset) + playqueue.init_from_xml(xml, + offset=offset, + transient_token=data.get('token')) else: app.CONN.plex_transient_token = data.get('token') - if data.get('offset') != '0': + if data.get('offset') not in (None, '0'): app.PLAYSTATE.resume_playback = True - playback.playback_triage(api.plex_id(), - api.plex_type(), - resolve=False) + path = ('http://127.0.0.1:%s/plex/play/file.strm?plex_id=%s' + % (v.WEBSERVICE_PORT, api.plex_id())) + path += '&plex_type=%s' % api.plex_type() + executebuiltin(('PlayMedia(%s)' % path).encode('utf-8')) @staticmethod def _process_node(data): @@ -150,7 +148,7 @@ class PlexCompanion(backgroundthread.KillableThread): xml[0].attrib except (AttributeError, IndexError, TypeError): LOG.error('Could not download Plex metadata') - return + raise PL.PlaylistError() api = API(xml[0]) playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) @@ -167,20 +165,23 @@ class PlexCompanion(backgroundthread.KillableThread): """ playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']]) - pos = js.get_position(playqueue.playlistid) - if 'audioStreamID' in data: - index = playqueue.items[pos].kodi_stream_index( - data['audioStreamID'], 'audio') - app.APP.player.setAudioStream(index) - elif 'subtitleStreamID' in data: - if data['subtitleStreamID'] == '0': - app.APP.player.showSubtitles(False) - else: + try: + pos = js.get_position(playqueue.playlistid) + if 'audioStreamID' in data: index = playqueue.items[pos].kodi_stream_index( - data['subtitleStreamID'], 'subtitle') - app.APP.player.setSubtitleStream(index) - else: - LOG.error('Unknown setStreams command: %s', data) + data['audioStreamID'], 'audio') + app.APP.player.setAudioStream(index) + elif 'subtitleStreamID' in data: + if data['subtitleStreamID'] == '0': + app.APP.player.showSubtitles(False) + else: + index = playqueue.items[pos].kodi_stream_index( + data['subtitleStreamID'], 'subtitle') + app.APP.player.setSubtitleStream(index) + else: + LOG.error('Unknown setStreams command: %s', data) + except KeyError: + LOG.warn('Could not process stream data: %s', data) @staticmethod def _process_refresh(data): @@ -220,23 +221,29 @@ class PlexCompanion(backgroundthread.KillableThread): """ LOG.debug('Processing: %s', task) data = task['data'] - if task['action'] == 'alexa': - with app.APP.lock_playqueues: - self._process_alexa(data) - elif (task['action'] == 'playlist' and - data.get('address') == 'node.plexapp.com'): - self._process_node(data) - elif task['action'] == 'playlist': - with app.APP.lock_playqueues: - self._process_playlist(data) - elif task['action'] == 'refreshPlayQueue': - with app.APP.lock_playqueues: - self._process_refresh(data) - elif task['action'] == 'setStreams': - try: + try: + if task['action'] == 'alexa': + with app.APP.lock_playqueues: + self._process_alexa(data) + elif (task['action'] == 'playlist' and + data.get('address') == 'node.plexapp.com'): + self._process_node(data) + elif task['action'] == 'playlist': + with app.APP.lock_playqueues: + self._process_playlist(data) + elif task['action'] == 'refreshPlayQueue': + with app.APP.lock_playqueues: + self._process_refresh(data) + elif task['action'] == 'setStreams': self._process_streams(data) - except KeyError: - pass + except PL.PlaylistError: + LOG.error('Could not process companion data: %s', data) + # "Play Error" + utils.dialog('notification', + utils.lang(29999), + utils.lang(30128), + icon='{error}') + app.PLAYSTATE.initiated_by_plex = False def run(self): """ diff --git a/resources/lib/webservice.py b/resources/lib/webservice.py index ff3b2893..400bd9b7 100644 --- a/resources/lib/webservice.py +++ b/resources/lib/webservice.py @@ -13,16 +13,16 @@ import Queue import xbmc import xbmcvfs +from .plex_api import API from .plex_db import PlexDB from . import backgroundthread, utils, variables as v, app, playqueue as PQ -from . import playlist_func as PL, json_rpc as js +from . import playlist_func as PL, json_rpc as js, plex_functions as PF LOG = getLogger('PLEX.webservice') class WebService(backgroundthread.KillableThread): - ''' Run a webservice to trigger playback. ''' def is_alive(self): @@ -48,7 +48,7 @@ class WebService(backgroundthread.KillableThread): conn.request('QUIT', '/') conn.getresponse() except Exception as error: - xbmc.log('Plex.WebService abort error: %s' % error, xbmc.LOGWARNING) + xbmc.log('PLEX.webservice abort error: %s' % error, xbmc.LOGWARNING) def suspend(self): """ @@ -124,7 +124,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): # Silence "[Errno 10054] An existing connection was forcibly # closed by the remote host" return - xbmc.log('Plex.WebService handle error: %s' % error, xbmc.LOGWARNING) + xbmc.log('PLEX.webservice handle error: %s' % error, xbmc.LOGWARNING) def do_QUIT(self): ''' send 200 OK response, and set server.stop to True @@ -144,7 +144,12 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): if '?' in path: path = path.split('?', 1)[1] params = dict(utils.parse_qsl(path)) + if 'plex_id' not in params: + LOG.error('No plex_id received for path %s', path) + return + if 'plex_type' in params and params['plex_type'].lower() == 'none': + del params['plex_type'] if 'plex_type' not in params: LOG.debug('Need to look-up plex_type') with PlexDB(lock=False) as plexdb: @@ -154,9 +159,20 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): else: LOG.debug('No plex_type found, using Kodi player id') players = js.get_players() - params['plex_type'] = v.PLEX_TYPE_CLIP if 'video' in players \ - else v.PLEX_TYPE_SONG - + if players: + params['plex_type'] = v.PLEX_TYPE_CLIP if 'video' in players \ + else v.PLEX_TYPE_SONG + LOG.debug('Using the following plex_type: %s', + params['plex_type']) + else: + xml = PF.GetPlexMetadata(params['plex_id']) + if xml in (None, 401): + LOG.error('Could not get metadata for %s', params) + return + api = API(xml[0]) + params['plex_type'] = api.plex_type() + LOG.debug('Got metadata, using plex_type %s', + params['plex_type']) return params def do_HEAD(self): @@ -172,7 +188,7 @@ class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): def handle_request(self, headers_only=False): '''Send headers and reponse ''' - xbmc.log('Plex.WebService handle_request called. headers %s, path: %s' + xbmc.log('PLEX.webservice handle_request called. headers %s, path: %s' % (headers_only, self.path), xbmc.LOGDEBUG) try: if b'extrafanart' in self.path or b'extrathumbs' in self.path: @@ -311,11 +327,13 @@ class QueuePlay(backgroundthread.KillableThread): self.synched = not params['synched'].lower() == 'false' def _get_playqueue(self): - if (self.plex_type in v.PLEX_VIDEOTYPES and - xbmc.getCondVisibility('Window.IsVisible(Home.xml)')): + playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO) + if ((self.plex_type in v.PLEX_VIDEOTYPES and + not app.PLAYSTATE.initiated_by_plex and + xbmc.getCondVisibility('Window.IsVisible(Home.xml)'))): # Video launched from a widget - which starts a Kodi AUDIO playlist # We will empty everything and start with a fresh VIDEO playlist - LOG.debug('Widget video playback detected; relaunching') + LOG.debug('Widget video playback detected') video_widget_playback = True # Release default.py utils.window('plex.playlist.ready', value='true') @@ -335,14 +353,9 @@ class QueuePlay(backgroundthread.KillableThread): else: LOG.debug('Audio playback detected') playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO) - playqueue.clear(kodi=False) return playqueue, video_widget_playback def run(self): - """ - We cannot use js.get_players() to reliably get the active player - Use Kodimonitor's OnNotification and OnAdd - """ LOG.debug('##===---- Starting QueuePlay ----===##') abort = False play_folder = False @@ -358,6 +371,8 @@ class QueuePlay(backgroundthread.KillableThread): # Position to add next element to queue - we're doing this at the end # of our current playqueue position = playqueue.kodi_pl.size() + # Set to start_position + 1 because first item will fail + utils.window('plex.playlist.start', str(start_position + 1)) LOG.debug('start_position %s, position %s for current playqueue: %s', start_position, position, playqueue) while True: @@ -370,7 +385,7 @@ class QueuePlay(backgroundthread.KillableThread): LOG.debug('Wrapping up') if xbmc.getCondVisibility('VideoPlayer.Content(livetv)'): # avoid issues with ongoing Live TV playback - xbmc.Player().stop() + app.APP.player.stop() count = 50 while not utils.window('plex.playlist.ready'): xbmc.sleep(50) @@ -392,48 +407,47 @@ class QueuePlay(backgroundthread.KillableThread): LOG.info('Start normal playback') # Release default.py utils.window('plex.playlist.play', value='true') + # Remove the playlist element we just added with the + # right path + xbmc.sleep(1000) + playqueue.kodi_remove_item(start_position) + del playqueue.items[start_position] LOG.debug('Done wrapping up') break self.load_params(params) if play_folder: - # 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) + playlistitem = PL.PlaylistItem(plex_id=self.plex_id, + plex_type=self.plex_type, + kodi_id=self.kodi_id, + kodi_type=self.kodi_type) + playlistitem.force_transcode = self.force_transcode + playqueue.add_item(playlistitem, position) position += 1 else: if self.server.pending.count(params['plex_id']) != len(self.server.pending): + # E.g. when selecting "play" for an entire video genre LOG.debug('Folder playback detected') play_folder = True - # Set to start_position + 1 because first item will fail - utils.window('plex.playlist.start', str(start_position + 1)) - playqueue.init(self.plex_id, + xbmc.executebuiltin('Activateutils.window(busydialognocancel)') + playqueue.play(self.plex_id, plex_type=self.plex_type, + startpos=start_position, 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() + utils.ERROR(notify=True) try: self.server.queue.task_done() except ValueError: - # "task_done() called too many times" + # "task_done() called too many times" when aborting pass if abort: - xbmc.Player().stop() + app.APP.player.stop() playqueue.clear() self.server.queue.queue.clear() if play_folder: @@ -444,6 +458,7 @@ class QueuePlay(backgroundthread.KillableThread): utils.window('plex.playlist.ready', clear=True) utils.window('plex.playlist.start', clear=True) + app.PLAYSTATE.initiated_by_plex = False self.server.threads.remove(self) self.server.pending = [] LOG.debug('##===---- QueuePlay Stopped ----===##')