Revamp playback start, part 2

This commit is contained in:
croneter 2018-01-10 20:14:05 +01:00
parent 24f2f60209
commit fb7eafb27a
6 changed files with 310 additions and 135 deletions

View file

@ -291,12 +291,11 @@ class Movies(Items):
path = playurl.replace(filename, "")
if doIndirect:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.plexkodiconnect/movies/"
path = "plugin://plugin.video.plexkodiconnect"
params = {
'filename': API.getKey(),
'id': itemid,
'dbid': movieid,
'mode': "play"
'mode': 'play',
'plex_id': itemid,
'plex_type': v.PLEX_TYPE_MOVIE
}
filename = "%s?%s" % (path, urlencode(params))
playurl = filename

View file

@ -12,11 +12,11 @@ from PlexFunctions import scrobble
from kodidb_functions import kodiid_from_filename
from plexbmchelper.subscribers import LOCKER
from PlexAPI import API
import playqueue as PQ
import json_rpc as js
import playlist_func as PL
import state
import variables as v
import playqueue as PQ
###############################################################################
@ -254,31 +254,16 @@ class KodiMonitor(Monitor):
return
playqueue.clear()
@LOCKER.lockthis
def PlayBackStart(self, data):
def _get_ids(self, json_item):
"""
Called whenever playback is started. Example data:
{
u'item': {u'type': u'movie', u'title': u''},
u'player': {u'playerid': 1, u'speed': 1}
}
Unfortunately when using Widgets, Kodi doesn't tell us shit
"""
# Get the type of media we're playing
try:
kodi_type = data['item']['type']
playerid = data['player']['playerid']
except (TypeError, KeyError):
LOG.info('Aborting playback report - item invalid for updates %s',
data)
return
json_data = js.get_item(playerid)
path = json_data.get('file')
kodi_id = json_data.get('id')
kodi_id = json_item.get('id')
kodi_type = json_item.get('type')
path = json_item.get('file')
if not path and not kodi_id:
LOG.info('Aborting playback report - no Kodi id or file for %s',
json_data)
return
json_item)
raise RuntimeError
# Plex id will NOT be set with direct paths
plex_id = state.PLEX_IDS.get(path)
try:
@ -306,28 +291,49 @@ class KodiMonitor(Monitor):
except TypeError:
# No plex id, hence item not in the library. E.g. clips
pass
info = js.get_player_props(playerid)
state.PLAYER_STATES[playerid].update(info)
state.PLAYER_STATES[playerid]['file'] = path
state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id
state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type
state.PLAYER_STATES[playerid]['plex_id'] = plex_id
state.PLAYER_STATES[playerid]['plex_type'] = plex_type
LOG.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
# Check whether we need to init our playqueues (e.g. direct play)
init = False
playqueue = PQ.PLAYQUEUES[playerid]
return kodi_id, kodi_type, plex_id, plex_type
@LOCKER.lockthis
def PlayBackStart(self, data):
"""
Called whenever playback is started. Example data:
{
u'item': {u'type': u'movie', u'title': u''},
u'player': {u'playerid': 1, u'speed': 1}
}
Unfortunately when using Widgets, Kodi doesn't tell us shit
"""
# Get the type of media we're playing
try:
playqueue.items[info['position']]
kodi_type = data['item']['type']
playerid = data['player']['playerid']
except (TypeError, KeyError):
LOG.info('Aborting playback report - item invalid for updates %s',
data)
return
playqueue = PQ.PLAYQUEUES[playerid]
info = js.get_player_props(playerid)
json_item = js.get_item(playerid)
path = json_item.get('file')
pos = info['position'] if info['position'] != -1 else 0
LOG.info('Detected position %s for %s', pos, playqueue)
try:
item = playqueue.items[pos]
# See if playback.py already initiated playback
init_done = item.init_done
except IndexError:
init = True
if init is False and plex_id is not None:
if plex_id != playqueue.items[info['position']].plex_id:
init = True
elif init is False and path != playqueue.items[info['position']].file:
init = True
if init is True:
LOG.debug('Need to initialize Plex and PKC playqueue')
init_done = False
if init_done is True:
kodi_id = item.kodi_id
kodi_type = item.kodi_type
plex_id = item.plex_id
plex_type = item.plex_type
else:
try:
kodi_id, kodi_type, plex_id, plex_type = self._get_ids(json_item)
except RuntimeError:
return
LOG.info('Need to initialize Plex and PKC playqueue')
if plex_id:
PL.init_Plex_playlist(playqueue, plex_id=plex_id)
else:
@ -335,17 +341,25 @@ class KodiMonitor(Monitor):
kodi_item={'id': kodi_id,
'type': kodi_type,
'file': path})
# Set the Plex container key (e.g. using the Plex playqueue)
container_key = None
if info['playlistid'] != -1:
# -1 is Kodi's answer if there is no playlist
container_key = PQ.PLAYQUEUES[playerid].id
if container_key is not None:
container_key = '/playQueues/%s' % container_key
elif plex_id is not None:
container_key = '/library/metadata/%s' % plex_id
state.PLAYER_STATES[playerid]['container_key'] = container_key
LOG.debug('Set the Plex container_key to: %s', container_key)
# Set the Plex container key (e.g. using the Plex playqueue)
container_key = None
if info['playlistid'] != -1:
# -1 is Kodi's answer if there is no playlist
container_key = PQ.PLAYQUEUES[playerid].id
if container_key is not None:
container_key = '/playQueues/%s' % container_key
elif plex_id is not None:
container_key = '/library/metadata/%s' % plex_id
state.PLAYER_STATES[playerid]['container_key'] = container_key
LOG.debug('Set the Plex container_key to: %s', container_key)
state.PLAYER_STATES[playerid].update(info)
state.PLAYER_STATES[playerid]['file'] = path
state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id
state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type
state.PLAYER_STATES[playerid]['plex_id'] = plex_id
state.PLAYER_STATES[playerid]['plex_type'] = plex_type
LOG.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
def StartDirectPath(self, plex_id, type, currentFile):
"""

View file

@ -1,52 +1,213 @@
"""
Used to kick off Kodi playback
"""
from logging import getLogger
from threading import Thread, Lock
from urllib import urlencode
from xbmc import Player, getCondVisibility, sleep
from PlexAPI import API
from PlexFunctions import GetPlexMetadata, init_plex_playqueue
import plexdb_functions as plexdb
import playlist_func as PL
import playqueue as PQ
from playutils import PlayUtils
from PKC_listitem import PKC_ListItem, convert_PKC_to_listitem
from pickler import Playback_Successful
from utils import settings, dialog, language as lang
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
import variables as v
import state
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOCKER = Lock_Function(Lock())
###############################################################################
def playback_setup(plex_id, kodi_id, kodi_type, path):
@LOCKER.lockthis
def playback_triage(plex_id=None, plex_type=None, path=None):
"""
Get XML
For the single element, e.g. including trailers and parts
For playQueue (init by Companion or Alexa)
Set up
PKC/Kodi/Plex Playqueue
Trailers
Clips
Several parts
companion playqueue
Alexa music album
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)
"""
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')
# "Unauthorized for PMS"
dialog('notification', lang(29999), lang(30017))
# Don't cause second notification to appear
return Playback_Successful()
playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
pos = js.get_position(playqueue.playlistid)
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]
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)
else:
conclude_playback(playqueue, pos)
def playback_init(plex_id, path, playqueue):
"""
Playback setup. Path is the original path PKC default.py has been called
with
"""
contextmenu_play = window('plex_contextplay') == 'true'
window('plex_contextplay', clear=True)
xml = GetPlexMetadata(plex_id)
try:
xml[0].attrib
except (IndexError, TypeError, AttributeError):
LOG.error('Could not get a PMS xml for plex id %s', plex_id)
# "Play error"
dialog('notification', lang(29999), lang(30128))
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 (api.getType() == v.PLEX_TYPE_MOVIE and
not seektime and
sizePlaylist < 2 and
if (plex_type == v.PLEX_TYPE_MOVIE and
not resume and
size_playlist < 2 and
settings('enableCinema') == "true"):
if settings('askCinema') == "true":
trailers = dialog('yesno', lang(29999), "Play trailers?")
# "Play trailers?"
trailers = dialog('yesno', lang(29999), lang(33016))
trailers = True if trailers else False
else:
trailers = True
# Post to the PMS. REUSE THE PLAYQUEUE!
xml = init_plex_playqueue(plex_id,
plex_lib_UUID,
mediatype=api.getType(),
xml.attrib.get('librarySectionUUID'),
mediatype=plex_type,
trailers=trailers)
pass
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"
dialog('notification', lang(29999), lang(30128))
return
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()
def conclude_playback_startup(playqueue_no,
pos,
plex_id=None,
kodi_id=None,
kodi_type=None,
path=None):
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:
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:
# Need to redirect again to PKC to conclude playback
params = {
'mode': 'play',
'plex_id': api.getRatingKey(),
'plex_type': api.getType()
}
path = ('plugin://plugin.video.plexkodiconnect?%s'
% (urlencode(params)))
listitem = api.CreateListItemFromPlexItem()
api.set_listitem_artwork(listitem)
listitem.setPath(path)
stack.append({
'kodi_id': kodi_id,
'kodi_type': kodi_type,
'file': path,
'xml_video_element': item,
'listitem': listitem,
'part_no': part_no
})
return stack
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:
playlist_item = PL.add_listitem_to_Kodi_playlist(
playqueue,
i,
item['listitem'],
file=item['file'],
xml_video_element=item['xml_video_element'])
playlist_item.part = item['part_no']
playlist_item.init_done = True
def conclude_playback(playqueue, pos):
"""
ONLY if actually being played (e.g. at 5th position of a playqueue).
@ -63,17 +224,16 @@ def conclude_playback_startup(playqueue_no,
"""
result = Playback_Successful()
listitem = PKC_ListItem()
playqueue = PQ.PLAYQUEUES[playqueue_no]
item = playqueue.items[pos]
api = API(item.xml)
api.setPartNumber(item.part)
api.CreateListItemFromPlexItem(listitem)
if plex_id is not None:
if item.xml is not None:
# Got a Plex element
api = API(item.xml)
api.setPartNumber(item.part)
api.CreateListItemFromPlexItem(listitem)
playutils = PlayUtils(api, item)
playurl = playutils.getPlayUrl()
elif path is not None:
playurl = path
item.playmethod = 'DirectStream'
else:
playurl = item.file
listitem.setPath(playurl)
if item.playmethod in ("DirectStream", "DirectPlay"):
listitem.setSubtitles(api.externalSubs())
@ -81,4 +241,4 @@ def conclude_playback_startup(playqueue_no,
playutils.audio_subtitle_prefs(listitem)
listitem.setPath(playurl)
result.listitem = listitem
return result
pickle_me(result)

View file

@ -9,6 +9,7 @@ from xbmc import Player
from PKC_listitem import PKC_ListItem
from pickler import pickle_me, Playback_Successful
from playbackutils import PlaybackUtils
import playback
from utils import window
from PlexFunctions import GetPlexMetadata
from PlexAPI import API
@ -126,30 +127,23 @@ class Playback_Starter(Thread):
params = dict(parse_qsl(params))
mode = params.get('mode')
LOG.debug('Received mode: %s, params: %s', mode, params)
try:
if mode == 'play':
result = self.process_play(params.get('id'),
params.get('dbid'))
elif mode == 'companion':
result = self.process_companion()
elif mode == 'plex_node':
result = self.process_plex_node(
params.get('key'),
params.get('view_offset'),
directplay=True if params.get('play_directly') else False,
node=False if params.get('node') == 'false' else True)
elif mode == 'context_menu':
ContextMenu()
result = Playback_Successful()
except:
LOG.error('Error encountered for mode %s, params %s',
mode, params)
import traceback
LOG.error(traceback.format_exc())
# Let default.py know!
pickle_me(None)
else:
pickle_me(result)
if mode == 'play':
result = playback.playback_triage(plex_id=params.get('plex_id'),
plex_type=params.get('plex_type'),
path=params.get('path'))
elif mode == 'companion':
result = self.process_companion()
elif mode == 'plex_node':
result = self.process_plex_node(
params.get('key'),
params.get('view_offset'),
directplay=True if params.get('play_directly') else False,
node=False if params.get('node') == 'false' else True)
elif mode == 'context_menu':
ContextMenu()
result = Playback_Successful()
# Let default.py know!
# pickle_me(result)
def run(self):
queue = state.COMMAND_PIPELINE_QUEUE

View file

@ -190,6 +190,8 @@ class Playlist_Item(object):
guid = None [str] Weird Plex guid
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
part = 0 [int] part number if Plex video consists of mult. parts
init_done = False Set to True only if run through playback init
"""
def __init__(self):
self.id = None
@ -203,8 +205,9 @@ class Playlist_Item(object):
self.guid = None
self.xml = None
self.playmethod = None
# Yet to be implemented: handling of a movie with several parts
# If Plex video consists of several parts; part number
self.part = 0
self.init_done = False
def __repr__(self):
"""
@ -550,11 +553,11 @@ def add_item_to_PMS_playlist(playlist, pos, plex_id=None, kodi_item=None):
def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
file=None):
file=None, xml_video_element=None):
"""
Adds an item to the KODI playlist only. WILL ALSO UPDATE OUR PLAYLISTS
Returns False if unsuccessful
Returns the playlist item that was just added or None
file: str!
"""
@ -574,17 +577,20 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
if reply.get('error') is not None:
LOG.error('Could not add item to playlist. Kodi reply. %s', reply)
playlist.is_kodi_onadd()
return False
item = playlist_item_from_kodi(
{'id': kodi_id, 'type': kodi_type, 'file': file})
if item.plex_id is not None:
xml = GetPlexMetadata(item.plex_id)
try:
return
if xml_video_element is not None:
item = playlist_item_from_xml(playlist, xml_video_element)
item.kodi_id = kodi_id
item.kodi_type = kodi_type
item.file = file
elif kodi_id is not None:
item = playlist_item_from_kodi(
{'id': kodi_id, 'type': kodi_type, 'file': file})
if item.plex_id is not None:
xml = GetPlexMetadata(item.plex_id)
item.xml = xml[-1]
except (TypeError, IndexError):
LOG.error('Could not get metadata for playlist item %s', item)
playlist.items.insert(pos, item)
return True
return item
def move_playlist_item(playlist, before_pos, after_pos):
@ -706,6 +712,7 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
item.file = file
playlist.items.insert(pos, item)
LOG.debug('Done inserting for %s', playlist)
return item
def remove_from_kodi_playlist(playlist, pos):

View file

@ -58,17 +58,18 @@ def init_playqueues():
LOG.debug('Initialized the Kodi playqueues: %s', PLAYQUEUES)
def get_playqueue_from_type(typus):
def get_playqueue_from_type(kodi_playlist_type):
"""
Returns the playqueue according to the typus ('video', 'audio',
'picture') passed in
Returns the playqueue according to the kodi_playlist_type ('video',
'audio', 'picture') passed in
"""
with LOCK:
for playqueue in PLAYQUEUES:
if playqueue.type == typus:
if playqueue.type == kodi_playlist_type:
break
else:
raise ValueError('Wrong playlist type passed in: %s' % typus)
raise ValueError('Wrong playlist type passed in: %s',
kodi_playlist_type)
return playqueue