From d397fb5b20fe0033aae2db21d134fdf1a15af053 Mon Sep 17 00:00:00 2001 From: croneter Date: Sat, 25 May 2019 13:35:14 +0200 Subject: [PATCH] Cleanup --- resources/lib/kodimonitor.py | 50 +-- resources/lib/playback.py | 551 ------------------------------ resources/lib/playback_starter.py | 108 +++++- 3 files changed, 100 insertions(+), 609 deletions(-) delete mode 100644 resources/lib/playback.py diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 6fe8b464..21e58c0f 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -14,7 +14,7 @@ import xbmcgui from .plex_db import PlexDB from . import kodi_db from .downloadutils import DownloadUtils as DU -from . import utils, timing, plex_functions as PF, playback +from . import utils, timing, plex_functions as PF from . import json_rpc as js, playqueue as PQ, playlist_func as PL from . import backgroundthread, app, variables as v @@ -38,7 +38,6 @@ class KodiMonitor(xbmc.Monitor): """ def __init__(self): self._already_slept = False - self.hack_replay = None # Info to the currently playing item self.playerid = None self.playlistid = None @@ -78,23 +77,11 @@ class KodiMonitor(xbmc.Monitor): data = loads(data, 'utf-8') LOG.debug("Method: %s Data: %s", method, data) - # Hack - if not method == 'Player.OnStop': - self.hack_replay = None - if method == "Player.OnPlay": with app.APP.lock_playqueues: self.on_play(data) elif method == "Player.OnStop": - # Should refresh our video nodes, e.g. on deck - # xbmc.executebuiltin('ReloadSkin()') - if (self.hack_replay and not data.get('end') and - self.hack_replay == data['item']): - # Hack for add-on paths - self.hack_replay = None - with app.APP.lock_playqueues: - self._hack_addon_paths_replay_video() - elif data.get('end'): + if data.get('end'): with app.APP.lock_playqueues: _playback_cleanup(ended=True) else: @@ -149,39 +136,6 @@ class KodiMonitor(xbmc.Monitor): LOG.info('Kodi OnQuit detected - shutting down') app.APP.stop_pkc = True - @staticmethod - def _hack_addon_paths_replay_video(): - """ - Hack we need for RESUMABLE items because Kodi lost the path of the - last played item that is now being replayed (see playback.py's - Player().play()) Also see playqueue.py _compare_playqueues() - - Needed if user re-starts the same video from the library using addon - paths. (Video is only added to playqueue, then immediately stoppen. - There is no playback initialized by Kodi.) Log excerpts: - Method: Playlist.OnAdd Data: - {u'item': {u'type': u'movie', u'id': 4}, - u'playlistid': 1, - u'position': 0} - Now we would hack! - Method: Player.OnStop Data: - {u'item': {u'type': u'movie', u'id': 4}, - u'end': False} - (within the same micro-second!) - """ - LOG.info('Detected re-start of playback of last item') - old = app.PLAYSTATE.old_player_states[1] - kwargs = { - 'plex_id': old['plex_id'], - 'plex_type': old['plex_type'], - 'path': old['file'], - 'resolve': False - } - task = backgroundthread.FunctionAsTask(playback.playback_triage, - None, - **kwargs) - backgroundthread.BGThreader.addTasksToFront([task]) - def _playlist_onadd(self, data): ''' Called when a new item is added to a Kodi playqueue diff --git a/resources/lib/playback.py b/resources/lib/playback.py deleted file mode 100644 index 82b42646..00000000 --- a/resources/lib/playback.py +++ /dev/null @@ -1,551 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Used to kick off Kodi playback -""" -from __future__ import absolute_import, division, unicode_literals -from logging import getLogger -from threading import Thread - -from .plex_api import API -from .plex_db import PlexDB -from . import plex_functions as PF -from . import utils -from .kodi_db import KodiVideoDB -from . import playlist_func as PL -from . import playqueue as PQ -from . import json_rpc as js -from . import transfer -from .playutils import PlayUtils -from . import variables as v -from . import app - -############################################################################### -LOG = getLogger('PLEX.playback') -# Do we need to return ultimately with a setResolvedUrl? -RESOLVE = True -############################################################################### - - -def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True): - """ - Hit this function for addon path playback, Plex trailers, etc. - Will setup playback first, then on second call complete playback. - - Will set Playback_Successful() with potentially a PKCListItem() attached - (to be consumed by setResolvedURL in default.py) - - If trailers or additional (movie-)parts are added, default.py is released - and a completely new player instance is called with a new playlist. This - circumvents most issues with Kodi & playqueues - - Set resolve to False if you do not want setResolvedUrl to be called on - the first pass - e.g. if you're calling this function from the original - service.py Python instance - """ - plex_id = utils.cast(int, plex_id) - LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s, ' - 'resolve %s', plex_id, plex_type, path, resolve) - global RESOLVE - # If started via Kodi context menu, we never resolve - RESOLVE = resolve if not app.PLAYSTATE.context_menu_play else False - if not app.CONN.online or not app.ACCOUNT.authenticated: - if not app.CONN.online: - LOG.error('PMS not online for playback') - # "{0} offline" - utils.dialog('notification', - utils.lang(29999), - utils.lang(39213).format(app.CONN.server_name), - icon='{plex}') - else: - LOG.error('Not yet authenticated for PMS, abort starting playback') - # "Unauthorized for PMS" - utils.dialog('notification', utils.lang(29999), utils.lang(30017)) - _ensure_resolve(abort=True) - return - with app.APP.lock_playqueues: - playqueue = PQ.get_playqueue_from_type( - v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]) - try: - pos = js.get_position(playqueue.playlistid) - except KeyError: - # Kodi bug - Playlist plays (not Playqueue) will ALWAYS be audio for - # add-on paths - LOG.info('No position returned from player! Assuming playlist') - playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO) - try: - pos = js.get_position(playqueue.playlistid) - except KeyError: - LOG.info('Assuming video instead of audio playlist playback') - playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO) - try: - pos = js.get_position(playqueue.playlistid) - except KeyError: - LOG.error('Still no position - abort') - # "Play error" - utils.dialog('notification', - utils.lang(29999), - utils.lang(30128), - icon='{error}') - _ensure_resolve(abort=True) - return - # HACK to detect playback of playlists for add-on paths - items = js.playlist_get_items(playqueue.playlistid) - try: - item = items[pos] - except IndexError: - LOG.info('Could not apply playlist hack! Probably Widget playback') - else: - if ('id' not in item and - item.get('type') == 'unknown' and item.get('title') == ''): - LOG.info('Kodi playlist play detected') - _playlist_playback(plex_id, plex_type) - return - - # Can return -1 (as in "no playlist") - pos = pos if pos != -1 else 0 - LOG.debug('playQueue position %s for %s', pos, playqueue) - # Have we already initiated playback? - try: - item = playqueue.items[pos] - except IndexError: - LOG.debug('PKC playqueue yet empty, need to initialize playback') - initiate = True - else: - if item.plex_id != plex_id: - LOG.debug('Received new plex_id %s, expected %s', - plex_id, item.plex_id) - initiate = True - else: - initiate = False - if initiate: - _playback_init(plex_id, plex_type, playqueue, pos) - else: - # kick off playback on second pass - _conclude_playback(playqueue, pos) - - -def _playlist_playback(plex_id, plex_type): - """ - Really annoying Kodi behavior: Kodi will throw the ENTIRE playlist some- - where, causing Playlist.onAdd to fire for each item like this: - Playlist.OnAdd Data: {u'item': {u'type': u'episode', u'id': 164}, - u'playlistid': 0, - u'position': 2} - This does NOT work for Addon paths, type and id will be unknown: - {u'item': {u'type': u'unknown'}, - u'playlistid': 0, - u'position': 7} - At the end, only the element being played actually shows up in the Kodi - playqueue. - Hence: if we fail the first addon paths call, Kodi will start playback - for the next item in line :-) - (by the way: trying to get active Kodi player id will return []) - """ - xml = PF.GetPlexMetadata(plex_id, reraise=True) - if xml in (None, 401): - _ensure_resolve(abort=True) - return - # Kodi bug: playqueue will ALWAYS be audio playqueue UNTIL playback - # has actually started. Need to tell Kodimonitor - playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO) - playqueue.clear(kodi=False) - # Set the flag for the potentially WRONG audio playlist so Kodimonitor - # can pick up on it - playqueue.kodi_playlist_playback = True - playlist_item = PL.playlist_item_from_xml(xml[0]) - playqueue.items.append(playlist_item) - _conclude_playback(playqueue, pos=0) - - -def _playback_init(plex_id, plex_type, playqueue, pos): - """ - Playback setup if Kodi starts playing an item for the first time. - """ - LOG.info('Initializing PKC playback') - xml = PF.GetPlexMetadata(plex_id, reraise=True) - if xml in (None, 401): - LOG.error('Could not get a PMS xml for plex id %s', plex_id) - _ensure_resolve(abort=True) - return - if playqueue.kodi_pl.size() > 1: - # Special case - we already got a filled Kodi playqueue - try: - _init_existing_kodi_playlist(playqueue, pos) - except PL.PlaylistError: - LOG.error('Playback_init for existing Kodi playlist failed') - # "Play error" - utils.dialog('notification', - utils.lang(29999), - utils.lang(30128), - icon='{error}') - _ensure_resolve(abort=True) - return - # Now we need to use setResolvedUrl for the item at position ZERO - # playqueue.py will pick up the missing items - _conclude_playback(playqueue, 0) - return - # "Usual" case - consider trailers and parts and build both Kodi and Plex - # playqueues - # Pass dummy PKC video with 0 length so Kodi immediately stops playback - # and we can build our own playqueue. - _ensure_resolve() - api = API(xml[0]) - trailers = False - if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and - utils.settings('enableCinema') == "true"): - if utils.settings('askCinema') == "true": - # "Play trailers?" - trailers = utils.yesno_dialog(utils.lang(29999), utils.lang(33016)) - else: - trailers = True - LOG.debug('Playing trailers: %s', trailers) - playqueue.clear() - if plex_type != v.PLEX_TYPE_CLIP: - # Post to the PMS to create a playqueue - in any case due to Companion - xml = PF.init_plex_playqueue(plex_id, - xml.attrib.get('librarySectionUUID'), - mediatype=plex_type, - trailers=trailers) - if xml is None: - LOG.error('Could not get a playqueue xml for plex id %s, UUID %s', - plex_id, xml.attrib.get('librarySectionUUID')) - # "Play error" - utils.dialog('notification', - utils.lang(29999), - utils.lang(30128), - icon='{error}') - # Do NOT use _ensure_resolve() because we resolved above already - app.PLAYSTATE.context_menu_play = False - app.PLAYSTATE.force_transcode = False - app.PLAYSTATE.resume_playback = False - return - PL.get_playlist_details_from_xml(playqueue, xml) - stack = _prep_playlist_stack(xml) - _process_stack(playqueue, stack) - # Always resume if playback initiated via PMS and there IS a resume - # point - offset = api.resume_point() * 1000 if app.PLAYSTATE.context_menu_play else None - # Reset some playback variables - app.PLAYSTATE.context_menu_play = False - app.PLAYSTATE.force_transcode = False - # New thread to release this one sooner (e.g. harddisk spinning up) - thread = Thread(target=threaded_playback, - args=(playqueue.kodi_pl, pos, offset)) - thread.setDaemon(True) - LOG.info('Done initializing playback, starting Kodi player at pos %s and ' - 'resume point %s', pos, offset) - # By design, PKC will start Kodi playback using Player().play(). Kodi - # caches paths like our plugin://pkc. If we use Player().play() between - # 2 consecutive startups of exactly the same Kodi library item, Kodi's - # cache will have been flushed for some reason. Hence the 2nd call for - # plugin://pkc will be lost; Kodi will try to startup playback for an empty - # path: log entry is "CGUIWindowVideoBase::OnPlayMedia " - thread.start() - # Ensure that PKC playqueue monitor ignores the changes we just made - playqueue.pkc_edit = True - - -def _ensure_resolve(abort=False): - """ - Will check whether RESOLVE=True and if so, fail Kodi playback startup - with the path 'PKC_Dummy_Path_Which_Fails' using setResolvedUrl (and some - pickling) - - This way we're making sure that other Python instances (calling default.py) - will be destroyed. - """ - if RESOLVE: - # Releases the other Python thread without a ListItem - transfer.send(True) - # Shows PKC error message - # transfer.send(None) - if abort: - # Reset some playback variables - app.PLAYSTATE.context_menu_play = False - app.PLAYSTATE.force_transcode = False - app.PLAYSTATE.resume_playback = False - - -def _init_existing_kodi_playlist(playqueue, pos): - """ - Will take the playqueue's kodi_pl with MORE than 1 element and initiate - playback (without adding trailers) - """ - LOG.debug('Kodi playlist size: %s', playqueue.kodi_pl.size()) - kodi_items = js.playlist_get_items(playqueue.playlistid) - if not kodi_items: - LOG.error('No Kodi items returned') - raise PL.PlaylistError('No Kodi items returned') - item = PL.init_plex_playqueue(playqueue, kodi_item=kodi_items[pos]) - item.force_transcode = app.PLAYSTATE.force_transcode - # playqueue.py will add the rest - this will likely put the PMS under - # a LOT of strain if the following Kodi setting is enabled: - # Settings -> Player -> Videos -> Play next video automatically - LOG.debug('Done init_existing_kodi_playlist') - - -def _prep_playlist_stack(xml): - stack = [] - for item in xml: - api = API(item) - if (app.PLAYSTATE.context_menu_play is False and - api.plex_type() not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)): - # If user chose to play via PMS or force transcode, do not - # use the item path stored in the Kodi DB - with PlexDB(lock=False) as plexdb: - db_item = plexdb.item_by_id(api.plex_id(), api.plex_type()) - kodi_id = db_item['kodi_id'] if db_item else None - kodi_type = db_item['kodi_type'] if db_item else None - else: - # We will never store clips (trailers) in the Kodi DB. - # Also set kodi_id to None for playback via PMS, so that we're - # using add-on paths. - # Also do NOT associate episodes with library items for addon paths - # as artwork lookup is broken (episode path does not link back to - # season and show) - kodi_id = None - kodi_type = None - for part, _ in enumerate(item[0]): - api.set_part_number(part) - if kodi_id is None: - # Need to redirect again to PKC to conclude playback - path = api.path() - listitem = api.create_listitem() - listitem.setPath(utils.try_encode(path)) - else: - # Will add directly via the Kodi DB - path = None - listitem = None - stack.append({ - 'kodi_id': kodi_id, - 'kodi_type': kodi_type, - 'file': path, - 'xml_video_element': item, - 'listitem': listitem, - 'part': part, - 'playcount': api.viewcount(), - 'offset': api.resume_point(), - 'id': api.item_id() - }) - return stack - - -def _process_stack(playqueue, stack): - """ - Takes our stack and adds the items to the PKC and Kodi playqueues. - """ - # getposition() can return -1 - pos = max(playqueue.kodi_pl.getposition(), 0) + 1 - for item in stack: - if item['kodi_id'] is None: - playlist_item = PL.add_listitem_to_Kodi_playlist( - playqueue, - pos, - item['listitem'], - file=item['file'], - xml_video_element=item['xml_video_element']) - else: - # Directly add element so we have full metadata - playlist_item = PL.add_item_to_kodi_playlist( - playqueue, - pos, - kodi_id=item['kodi_id'], - kodi_type=item['kodi_type'], - xml_video_element=item['xml_video_element']) - playlist_item.playcount = item['playcount'] - playlist_item.offset = item['offset'] - playlist_item.part = item['part'] - playlist_item.id = item['id'] - playlist_item.force_transcode = app.PLAYSTATE.force_transcode - pos += 1 - - -def _conclude_playback(playqueue, pos): - """ - ONLY if actually being played (e.g. at 5th position of a playqueue). - - Decide on direct play, direct stream, transcoding - path to - direct paths: file itself - PMS URL - Web URL - audiostream (e.g. let user choose) - subtitle stream (e.g. let user choose) - Init Kodi Playback (depending on situation): - start playback - return PKC listitem attached to result - """ - LOG.info('Concluding playback for playqueue position %s', pos) - listitem = transfer.PKCListItem() - item = playqueue.items[pos] - if item.xml is not None: - # Got a Plex element - api = API(item.xml) - api.set_part_number(item.part) - api.create_listitem(listitem) - playutils = PlayUtils(api, item) - playurl = playutils.getPlayUrl() - else: - api = None - playurl = item.file - if not playurl: - LOG.info('Did not get a playurl, aborting playback silently') - app.PLAYSTATE.resume_playback = False - transfer.send(True) - return - listitem.setPath(utils.try_encode(playurl)) - if item.playmethod == 'DirectStream': - listitem.setSubtitles(api.cache_external_subs()) - elif item.playmethod == 'Transcode': - playutils.audio_subtitle_prefs(listitem) - - if app.PLAYSTATE.resume_playback is True: - app.PLAYSTATE.resume_playback = False - if item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP): - # Do NOT use item.offset directly but get it from the DB - # (user might have initiated same video twice) - with PlexDB(lock=False) as plexdb: - db_item = plexdb.item_by_id(item.plex_id, item.plex_type) - file_id = db_item['kodi_fileid'] if db_item else None - with KodiVideoDB(lock=False) as kodidb: - item.offset = kodidb.get_resume(file_id) - LOG.info('Resuming playback at %s', item.offset) - if v.KODIVERSION >= 18 and api: - # Kodi 18 Alpha 3 broke StartOffset - try: - percent = (item.offset or api.resume_point()) / api.runtime() * 100.0 - except ZeroDivisionError: - percent = 0.0 - LOG.debug('Resuming at %s percent', percent) - listitem.setProperty('StartPercent', str(percent)) - else: - listitem.setProperty('StartOffset', str(item.offset)) - listitem.setProperty('resumetime', str(item.offset)) - elif v.KODIVERSION >= 18: - listitem.setProperty('StartPercent', '0') - # Reset the resumable flag - transfer.send(listitem) - LOG.info('Done concluding playback') - - -def process_indirect(key, offset, resolve=True): - """ - Called e.g. for Plex "Play later" - Plex items where we need to fetch an - additional xml for the actual playurl. In the PMS metadata, indirect="1" is - set. - - Will release default.py with setResolvedUrl - - Set resolve to False if playback should be kicked off directly, not via - setResolvedUrl - """ - LOG.info('process_indirect called with key: %s, offset: %s, resolve: %s', - key, offset, resolve) - global RESOLVE - RESOLVE = resolve - offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset)) if offset != '0' else None - if key.startswith('http') or key.startswith('{server}'): - xml = PF.get_playback_xml(key, app.CONN.server_name) - elif key.startswith('/system/services'): - xml = PF.get_playback_xml('http://node.plexapp.com:32400%s' % key, - 'plexapp.com', - authenticate=False, - token=app.ACCOUNT.plex_token) - else: - xml = PF.get_playback_xml('{server}%s' % key, app.CONN.server_name) - if xml is None: - _ensure_resolve(abort=True) - return - - api = API(xml[0]) - listitem = transfer.PKCListItem() - api.create_listitem(listitem) - playqueue = PQ.get_playqueue_from_type( - v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) - playqueue.clear() - item = PL.PlaylistItem() - item.xml = xml[0] - item.offset = offset - item.plex_type = v.PLEX_TYPE_CLIP - item.playmethod = 'DirectStream' - - # Need to get yet another xml to get the final playback url - try: - xml = PF.get_playback_xml('http://node.plexapp.com:32400%s' - % xml[0][0][0].attrib['key'], - 'plexapp.com', - authenticate=False, - token=app.ACCOUNT.plex_token) - except (TypeError, IndexError, AttributeError): - LOG.error('XML malformed: %s', xml.attrib) - xml = None - if xml is None: - _ensure_resolve(abort=True) - return - - try: - playurl = xml[0].attrib['key'] - except (TypeError, IndexError, AttributeError): - LOG.error('Last xml malformed: %s', xml.attrib) - _ensure_resolve(abort=True) - return - - item.file = playurl - listitem.setPath(utils.try_encode(playurl)) - playqueue.items.append(item) - if resolve is True: - transfer.send(listitem) - else: - thread = Thread(target=app.APP.player.play, - args={'item': utils.try_encode(playurl), - 'listitem': listitem}) - thread.setDaemon(True) - LOG.info('Done initializing PKC playback, starting Kodi player') - thread.start() - - -def play_xml(playqueue, xml, offset=None, start_plex_id=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 - """ - LOG.info("play_xml called with offset %s, start_plex_id %s", - offset, start_plex_id) - stack = _prep_playlist_stack(xml) - _process_stack(playqueue, stack) - LOG.debug('Playqueue after play_xml update: %s', playqueue) - if start_plex_id is not None: - for startpos, item in enumerate(playqueue.items): - if item.plex_id == start_plex_id: - break - else: - startpos = 0 - else: - for startpos, item in enumerate(playqueue.items): - if item.id == playqueue.selectedItemID: - break - else: - startpos = 0 - thread = Thread(target=threaded_playback, - args=(playqueue.kodi_pl, startpos, offset)) - LOG.info('Done play_xml, starting Kodi player at position %s', startpos) - thread.start() - - -def threaded_playback(kodi_playlist, startpos, offset): - """ - Seek immediately after kicking off playback is not reliable. - """ - app.APP.player.play(kodi_playlist, None, False, startpos) - if offset and offset != '0': - i = 0 - while not app.APP.is_playing: - app.APP.monitor.waitForAbort(0.1) - i += 1 - if i > 100: - LOG.error('Could not seek to %s', offset) - return - js.seek_to(int(offset)) diff --git a/resources/lib/playback_starter.py b/resources/lib/playback_starter.py index 5800f0eb..00a08835 100644 --- a/resources/lib/playback_starter.py +++ b/resources/lib/playback_starter.py @@ -3,7 +3,9 @@ from __future__ import absolute_import, division, unicode_literals from logging import getLogger -from . import utils, playback, context_entry, transfer, backgroundthread +from .plex_api import API +from . import utils, context_entry, transfer, backgroundthread, variables as v +from . import app, plex_functions as PF, playqueue as PQ, playlist_func as PL ############################################################################### @@ -34,16 +36,102 @@ class PlaybackTask(backgroundthread.Task): mode = params.get('mode') resolve = False if params.get('handle') == '-1' else True LOG.debug('Received mode: %s, params: %s', mode, params) - if mode == 'play': - playback.playback_triage(plex_id=params.get('plex_id'), - plex_type=params.get('plex_type'), - path=params.get('path'), - resolve=resolve) - elif mode == 'plex_node': - playback.process_indirect(params['key'], - params['offset'], - resolve=resolve) + if mode == 'plex_node': + process_indirect(params['key'], + params['offset'], + resolve=resolve) elif mode == 'context_menu': context_entry.ContextMenu(kodi_id=params.get('kodi_id'), kodi_type=params.get('kodi_type')) LOG.debug('Finished PlaybackTask') + + +def process_indirect(key, offset, resolve=True): + """ + Called e.g. for Plex "Play later" - Plex items where we need to fetch an + additional xml for the actual playurl. In the PMS metadata, indirect="1" is + set. + + Will release default.py with setResolvedUrl + + Set resolve to False if playback should be kicked off directly, not via + setResolvedUrl + """ + LOG.info('process_indirect called with key: %s, offset: %s, resolve: %s', + key, offset, resolve) + global RESOLVE + RESOLVE = resolve + offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset)) if offset != '0' else None + if key.startswith('http') or key.startswith('{server}'): + xml = PF.get_playback_xml(key, app.CONN.server_name) + elif key.startswith('/system/services'): + xml = PF.get_playback_xml('http://node.plexapp.com:32400%s' % key, + 'plexapp.com', + authenticate=False, + token=app.ACCOUNT.plex_token) + else: + xml = PF.get_playback_xml('{server}%s' % key, app.CONN.server_name) + if xml is None: + _ensure_resolve(abort=True) + return + + api = API(xml[0]) + listitem = transfer.PKCListItem() + api.create_listitem(listitem) + playqueue = PQ.get_playqueue_from_type( + v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) + playqueue.clear() + item = PL.PlaylistItem(xml_video_element=xml[0]) + item.offset = offset + item.playmethod = 'DirectStream' + + # Need to get yet another xml to get the final playback url + try: + xml = PF.get_playback_xml('http://node.plexapp.com:32400%s' + % xml[0][0][0].attrib['key'], + 'plexapp.com', + authenticate=False, + token=app.ACCOUNT.plex_token) + except (TypeError, IndexError, AttributeError): + LOG.error('XML malformed: %s', xml.attrib) + xml = None + if xml is None: + _ensure_resolve(abort=True) + return + try: + playurl = xml[0].attrib['key'] + except (TypeError, IndexError, AttributeError): + LOG.error('Last xml malformed: %s\n%s', xml.tag, xml.attrib) + _ensure_resolve(abort=True) + return + + item.file = playurl + listitem.setPath(playurl.encode('utf-8')) + playqueue.items.append(item) + if resolve is True: + transfer.send(listitem) + else: + LOG.info('Done initializing PKC playback, starting Kodi player') + app.APP.player.play(item=playurl.encode('utf-8'), + listitem=listitem) + + +def _ensure_resolve(abort=False): + """ + Will check whether RESOLVE=True and if so, fail Kodi playback startup + with the path 'PKC_Dummy_Path_Which_Fails' using setResolvedUrl (and some + pickling) + + This way we're making sure that other Python instances (calling default.py) + will be destroyed. + """ + if RESOLVE: + # Releases the other Python thread without a ListItem + transfer.send(True) + # Shows PKC error message + # transfer.send(None) + if abort: + # Reset some playback variables + app.PLAYSTATE.context_menu_play = False + app.PLAYSTATE.force_transcode = False + app.PLAYSTATE.resume_playback = False