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
"""
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
if playqueue.is_kodi_onadd() is False:
LOG.debug('PKC added this item to the playqueue - ignoring')

View file

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

View file

@ -324,7 +324,8 @@ def playlist_item_from_plex(plex_id):
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
@ -338,7 +339,10 @@ def playlist_item_from_xml(playlist, xml_video_element):
item.guid = xml_video_element.attrib.get('guid')
if item.guid is not None:
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:
db_element = plex_db.getItem_byId(item.plex_id)
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.
playlist.id with the XML's playQueueID
"""
try:
playlist.id = xml.attrib['%sID' % playlist.kind]
playlist.version = xml.attrib['%sVersion' % playlist.kind]
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
playlist.selectedItemID = xml.attrib.get(
'%sSelectedItemID' % playlist.kind)
playlist.selectedItemOffset = xml.attrib.get(
'%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
playlist.id = xml.attrib['%sID' % playlist.kind]
playlist.version = xml.attrib['%sVersion' % playlist.kind]
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
playlist.selectedItemID = xml.attrib.get(
'%sSelectedItemID' % playlist.kind)
playlist.selectedItemOffset = xml.attrib.get(
'%sSelectedItemOffset' % playlist.kind)
LOG.debug('Updated playlist from xml: %s', playlist)
@ -702,7 +699,7 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
pos, playlist)
# Add the item into Kodi playlist
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
if xml_video_element is not None:
item = playlist_item_from_xml(playlist, xml_video_element)