Revamp playback start, part 3

This commit is contained in:
croneter 2018-01-21 13:42:22 +01:00
parent fb7eafb27a
commit 7ecaa376a2
3 changed files with 90 additions and 103 deletions

View file

@ -198,6 +198,10 @@ class KodiMonitor(Monitor):
Will NOT be called if playback initiated by Kodi widgets Will NOT be called if playback initiated by Kodi widgets
""" """
playqueue = PQ.PLAYQUEUES[data['playlistid']] playqueue = PQ.PLAYQUEUES[data['playlistid']]
# Have we initiated the playqueue already? If not, ignore this
if not playqueue.items:
LOG.debug('Playqueue not initiated - ignoring')
return
# Did PKC cause this add? Then lets not do anything # Did PKC cause this add? Then lets not do anything
if playqueue.is_kodi_onadd() is False: if playqueue.is_kodi_onadd() is False:
LOG.debug('PKC added this item to the playqueue - ignoring') LOG.debug('PKC added this item to the playqueue - ignoring')

View file

@ -2,10 +2,10 @@
Used to kick off Kodi playback Used to kick off Kodi playback
""" """
from logging import getLogger from logging import getLogger
from threading import Thread, Lock from threading import Thread
from urllib import urlencode from urllib import urlencode
from xbmc import Player, getCondVisibility, sleep from xbmc import Player, sleep
from PlexAPI import API from PlexAPI import API
from PlexFunctions import GetPlexMetadata, init_plex_playqueue from PlexFunctions import GetPlexMetadata, init_plex_playqueue
@ -16,14 +16,14 @@ from playutils import PlayUtils
from PKC_listitem import PKC_ListItem from PKC_listitem import PKC_ListItem
from pickler import pickle_me, Playback_Successful from pickler import pickle_me, Playback_Successful
import json_rpc as js import json_rpc as js
from utils import window, settings, dialog, language as lang, Lock_Function from utils import window, settings, dialog, language as lang
from plexbmchelper.subscribers import LOCKER
import variables as v import variables as v
import state import state
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger("PLEX." + __name__)
LOCKER = Lock_Function(Lock())
############################################################################### ###############################################################################
@ -34,42 +34,43 @@ def playback_triage(plex_id=None, plex_type=None, path=None):
Hit this function for addon path playback, Plex trailers, etc. Hit this function for addon path playback, Plex trailers, etc.
Will setup playback first, then on second call complete playback. Will setup playback first, then on second call complete playback.
Returns Playback_Successful() with potentially a PKC_ListItem() attached Will set Playback_Successful() with potentially a PKC_ListItem() attached
(to be consumed by setResolvedURL) (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
""" """
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s', LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s',
plex_id, plex_type, path) plex_id, plex_type, path)
if not state.AUTHENTICATED: if not state.AUTHENTICATED:
LOG.error('Not yet authenticated for PMS, abort starting playback') LOG.error('Not yet authenticated for PMS, abort starting playback')
# Release default.py
pickle_me(Playback_Successful())
# "Unauthorized for PMS" # "Unauthorized for PMS"
dialog('notification', lang(29999), lang(30017)) dialog('notification', lang(29999), lang(30017))
# Don't cause second notification to appear return
return Playback_Successful()
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
pos = js.get_position(playqueue.playlistid) pos = js.get_position(playqueue.playlistid)
# Can return -1 (as in "no playlist")
pos = pos if pos != -1 else 0 pos = pos if pos != -1 else 0
LOG.info('playQueue position: %s for %s', pos, playqueue) LOG.info('playQueue position: %s for %s', pos, playqueue)
# Have we already initiated playback? # Have we already initiated playback?
init_done = True
try: try:
item = playqueue.items[pos] playqueue.items[pos]
except IndexError: except IndexError:
init_done = False playback_init(plex_id, plex_type, playqueue)
else:
init_done = item.init_done
# Either init the playback now, or - on 2nd pass - kick off playback
if init_done is False:
playback_init(plex_id, path, playqueue)
else: else:
# kick off playback on second pass
conclude_playback(playqueue, pos) conclude_playback(playqueue, pos)
def playback_init(plex_id, path, playqueue): def playback_init(plex_id, plex_type, playqueue):
""" """
Playback setup. Path is the original path PKC default.py has been called Playback setup if Kodi starts playing an item for the first time.
with
""" """
LOG.info('Initializing PKC playback')
contextmenu_play = window('plex_contextplay') == 'true' contextmenu_play = window('plex_contextplay') == 'true'
window('plex_contextplay', clear=True) window('plex_contextplay', clear=True)
xml = GetPlexMetadata(plex_id) xml = GetPlexMetadata(plex_id)
@ -77,25 +78,15 @@ def playback_init(plex_id, path, playqueue):
xml[0].attrib xml[0].attrib
except (IndexError, TypeError, AttributeError): except (IndexError, TypeError, AttributeError):
LOG.error('Could not get a PMS xml for plex id %s', plex_id) LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# Release default.py
pickle_me(Playback_Successful())
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128)) dialog('notification', lang(29999), lang(30128), icon='{error}')
return return
result = Playback_Successful()
listitem = PKC_ListItem()
# Set the original path again so Kodi will return a 2nd time to PKC
listitem.setPath(path)
api = API(xml[0]) api = API(xml[0])
plex_type = api.getType()
size_playlist = playqueue.kodi_pl.size()
# Can return -1
start_pos = max(playqueue.kodi_pl.getposition(), 0)
LOG.info("Playlist size %s", size_playlist)
LOG.info("Playlist starting position %s", start_pos)
resume, _ = api.getRuntime() resume, _ = api.getRuntime()
trailers = False trailers = False
if (plex_type == v.PLEX_TYPE_MOVIE and if (plex_type == v.PLEX_TYPE_MOVIE and not resume and
not resume and
size_playlist < 2 and
settings('enableCinema') == "true"): settings('enableCinema') == "true"):
if settings('askCinema') == "true": if settings('askCinema') == "true":
# "Play trailers?" # "Play trailers?"
@ -103,7 +94,8 @@ def playback_init(plex_id, path, playqueue):
trailers = True if trailers else False trailers = True if trailers else False
else: else:
trailers = True trailers = True
# Post to the PMS. REUSE THE PLAYQUEUE! LOG.info('Playing trailers: %s', trailers)
# Post to the PMS to create a playqueue - in any case due to Plex Companion
xml = init_plex_playqueue(plex_id, xml = init_plex_playqueue(plex_id,
xml.attrib.get('librarySectionUUID'), xml.attrib.get('librarySectionUUID'),
mediatype=plex_type, mediatype=plex_type,
@ -111,55 +103,44 @@ def playback_init(plex_id, path, playqueue):
if xml is None: if xml is None:
LOG.error('Could not get a playqueue xml for plex id %s, UUID %s', LOG.error('Could not get a playqueue xml for plex id %s, UUID %s',
plex_id, xml.attrib.get('librarySectionUUID')) plex_id, xml.attrib.get('librarySectionUUID'))
# Release default.py
pickle_me(Playback_Successful())
# "Play error" # "Play error"
dialog('notification', lang(29999), lang(30128)) dialog('notification', lang(29999), lang(30128), icon='{error}')
return return
# Should already be empty, but just in case
playqueue.clear() playqueue.clear()
PL.get_playlist_details_from_xml(playqueue, xml) PL.get_playlist_details_from_xml(playqueue, xml)
stack = _prep_playlist_stack(xml) stack = _prep_playlist_stack(xml)
force_playback = False # Release our default.py before starting our own Kodi player instance
if (not getCondVisibility('Window.IsVisible(MyVideoNav.xml)') and pickle_me(Playback_Successful())
not getCondVisibility('Window.IsVisible(VideoFullScreen.xml)')): # Sleep a bit to let setResolvedUrl do its thing - bit ugly
LOG.info("Detected playback from widget") sleep(200)
force_playback = True _process_stack(playqueue, stack)
if force_playback is False: # New thread to release this one sooner (e.g. harddisk spinning up)
# Return the listelement for setResolvedURL thread = Thread(target=Player().play,
result.listitem = listitem args=(playqueue.kodi_pl, ))
pickle_me(result) thread.setDaemon(True)
# Wait for the setResolvedUrl to have taken its course - ugly LOG.info('Done initializing PKC playback, starting Kodi player')
sleep(50) thread.start()
_process_stack(playqueue, stack)
else:
# Need to kickoff playback, not using setResolvedURL
pickle_me(result)
_process_stack(playqueue, stack)
# Need a separate thread because Player won't return in time
listitem.setProperty('StartOffset', str(resume))
thread = Thread(target=Player().play,
args=(playqueue.kodi_pl, ))
thread.setDaemon(True)
thread.start()
def _prep_playlist_stack(xml): def _prep_playlist_stack(xml):
stack = [] stack = []
for item in xml: for item in xml:
api = API(item) api = API(item)
with plexdb.Get_Plex_DB() as plex_db: if api.getType() != v.PLEX_TYPE_CLIP:
plex_dbitem = plex_db.getItem_byId(api.getRatingKey()) with plexdb.Get_Plex_DB() as plex_db:
try: plex_dbitem = plex_db.getItem_byId(api.getRatingKey())
kodi_id = plex_dbitem[0] kodi_id = plex_dbitem[0] if plex_dbitem else None
kodi_type = plex_dbitem[4] kodi_type = plex_dbitem[4] if plex_dbitem else None
except TypeError: else:
# We will never store clips (trailers) in the Kodi DB
kodi_id = None kodi_id = None
kodi_type = None kodi_type = None
for part_no, _ in enumerate(item[0]): for part, _ in enumerate(item[0]):
api.setPartNumber(part_no) api.setPartNumber(part)
if kodi_id is not None: if kodi_id is None:
# We don't need the URL, item is in the Kodi library
path = None
listitem = None
else:
# Need to redirect again to PKC to conclude playback # Need to redirect again to PKC to conclude playback
params = { params = {
'mode': 'play', 'mode': 'play',
@ -167,17 +148,20 @@ def _prep_playlist_stack(xml):
'plex_type': api.getType() 'plex_type': api.getType()
} }
path = ('plugin://plugin.video.plexkodiconnect?%s' path = ('plugin://plugin.video.plexkodiconnect?%s'
% (urlencode(params))) % urlencode(params))
listitem = api.CreateListItemFromPlexItem() listitem = api.CreateListItemFromPlexItem()
api.set_listitem_artwork(listitem)
listitem.setPath(path) listitem.setPath(path)
else:
# Will add directly via the Kodi DB
path = None
listitem = None
stack.append({ stack.append({
'kodi_id': kodi_id, 'kodi_id': kodi_id,
'kodi_type': kodi_type, 'kodi_type': kodi_type,
'file': path, 'file': path,
'xml_video_element': item, 'xml_video_element': item,
'listitem': listitem, 'listitem': listitem,
'part_no': part_no 'part': part
}) })
return stack return stack
@ -185,26 +169,28 @@ def _prep_playlist_stack(xml):
def _process_stack(playqueue, stack): def _process_stack(playqueue, stack):
""" """
Takes our stack and adds the items to the PKC and Kodi playqueues. Takes our stack and adds the items to the PKC and Kodi playqueues.
This needs to be done AFTER setResolvedURL
""" """
for i, item in enumerate(stack): # getposition() can return -1
if item['kodi_id'] is not None: pos = max(playqueue.kodi_pl.getposition(), 0) + 1
# Use Kodi id & JSON so we get full artwork for item in stack:
playlist_item = PL.add_item_to_kodi_playlist( if item['kodi_id'] is None:
playqueue,
i,
kodi_id=item['kodi_id'],
kodi_type=item['kodi_type'],
xml_video_element=item['xml_video_element'])
else:
playlist_item = PL.add_listitem_to_Kodi_playlist( playlist_item = PL.add_listitem_to_Kodi_playlist(
playqueue, playqueue,
i, pos,
item['listitem'], item['listitem'],
file=item['file'], file=item['file'],
xml_video_element=item['xml_video_element']) xml_video_element=item['xml_video_element'])
playlist_item.part = item['part_no'] 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.part = item['part']
playlist_item.init_done = True playlist_item.init_done = True
pos += 1
def conclude_playback(playqueue, pos): def conclude_playback(playqueue, pos):

View file

@ -324,7 +324,8 @@ def playlist_item_from_plex(plex_id):
return item return item
def playlist_item_from_xml(playlist, xml_video_element): def playlist_item_from_xml(playlist, xml_video_element, kodi_id=None,
kodi_type=None):
""" """
Returns a playlist element for the playqueue using the Plex xml Returns a playlist element for the playqueue using the Plex xml
@ -338,7 +339,10 @@ def playlist_item_from_xml(playlist, xml_video_element):
item.guid = xml_video_element.attrib.get('guid') item.guid = xml_video_element.attrib.get('guid')
if item.guid is not None: if item.guid is not None:
item.guid = escape_html(item.guid) item.guid = escape_html(item.guid)
if item.plex_id: if kodi_id is not None:
item.kodi_id = kodi_id
item.kodi_type = kodi_type
elif item.plex_id is not None:
with plexdb.Get_Plex_DB() as plex_db: with plexdb.Get_Plex_DB() as plex_db:
db_element = plex_db.getItem_byId(item.plex_id) db_element = plex_db.getItem_byId(item.plex_id)
try: try:
@ -369,20 +373,13 @@ def get_playlist_details_from_xml(playlist, xml):
Takes a PMS xml as input and overwrites all the playlist's details, e.g. Takes a PMS xml as input and overwrites all the playlist's details, e.g.
playlist.id with the XML's playQueueID playlist.id with the XML's playQueueID
""" """
try: playlist.id = xml.attrib['%sID' % playlist.kind]
playlist.id = xml.attrib['%sID' % playlist.kind] playlist.version = xml.attrib['%sVersion' % playlist.kind]
playlist.version = xml.attrib['%sVersion' % playlist.kind] playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind] playlist.selectedItemID = xml.attrib.get(
playlist.selectedItemID = xml.attrib.get( '%sSelectedItemID' % playlist.kind)
'%sSelectedItemID' % playlist.kind) playlist.selectedItemOffset = xml.attrib.get(
playlist.selectedItemOffset = xml.attrib.get( '%sSelectedItemOffset' % playlist.kind)
'%sSelectedItemOffset' % playlist.kind)
except:
LOG.error('Could not parse xml answer from PMS for playlist %s',
playlist)
import traceback
LOG.error(traceback.format_exc())
raise KeyError
LOG.debug('Updated playlist from xml: %s', playlist) LOG.debug('Updated playlist from xml: %s', playlist)
@ -702,7 +699,7 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
pos, playlist) pos, playlist)
# Add the item into Kodi playlist # Add the item into Kodi playlist
playlist.kodi_onadd() playlist.kodi_onadd()
playlist.kodi_pl.add(file, listitem, index=pos) playlist.kodi_pl.add(url=file, listitem=listitem, index=pos)
# We need to add this to our internal queue as well # We need to add this to our internal queue as well
if xml_video_element is not None: if xml_video_element is not None:
item = playlist_item_from_xml(playlist, xml_video_element) item = playlist_item_from_xml(playlist, xml_video_element)