2018-01-08 03:50:30 +11:00
|
|
|
"""
|
|
|
|
Used to kick off Kodi playback
|
|
|
|
"""
|
2018-01-11 06:14:05 +11:00
|
|
|
from logging import getLogger
|
2018-01-21 23:42:22 +11:00
|
|
|
from threading import Thread
|
2018-01-11 06:14:05 +11:00
|
|
|
from urllib import urlencode
|
|
|
|
|
2018-01-21 23:42:22 +11:00
|
|
|
from xbmc import Player, sleep
|
2018-01-11 06:14:05 +11:00
|
|
|
|
2018-01-08 03:50:30 +11:00
|
|
|
from PlexAPI import API
|
2018-01-11 06:14:05 +11:00
|
|
|
from PlexFunctions import GetPlexMetadata, init_plex_playqueue
|
|
|
|
import plexdb_functions as plexdb
|
|
|
|
import playlist_func as PL
|
2018-01-08 03:50:30 +11:00
|
|
|
import playqueue as PQ
|
|
|
|
from playutils import PlayUtils
|
2018-01-11 06:14:05 +11:00
|
|
|
from PKC_listitem import PKC_ListItem
|
|
|
|
from pickler import pickle_me, Playback_Successful
|
|
|
|
import json_rpc as js
|
2018-01-21 23:42:22 +11:00
|
|
|
from utils import window, settings, dialog, language as lang
|
|
|
|
from plexbmchelper.subscribers import LOCKER
|
2018-01-11 06:14:05 +11:00
|
|
|
import variables as v
|
|
|
|
import state
|
|
|
|
|
|
|
|
###############################################################################
|
2018-01-08 03:50:30 +11:00
|
|
|
|
2018-01-11 06:14:05 +11:00
|
|
|
LOG = getLogger("PLEX." + __name__)
|
2018-01-08 03:50:30 +11:00
|
|
|
|
2018-01-11 06:14:05 +11:00
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
|
|
|
|
@LOCKER.lockthis
|
2018-01-26 03:15:38 +11:00
|
|
|
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
|
2018-01-08 03:50:30 +11:00
|
|
|
"""
|
2018-01-11 06:14:05 +11:00
|
|
|
Hit this function for addon path playback, Plex trailers, etc.
|
|
|
|
Will setup playback first, then on second call complete playback.
|
2018-01-08 03:50:30 +11:00
|
|
|
|
2018-01-21 23:42:22 +11:00
|
|
|
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
|
2018-01-26 03:15:38 +11:00
|
|
|
|
|
|
|
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
|
2018-01-11 06:14:05 +11:00
|
|
|
"""
|
|
|
|
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')
|
2018-01-28 23:26:25 +11:00
|
|
|
if resolve is True:
|
|
|
|
# Release default.py
|
|
|
|
pickle_me(Playback_Successful())
|
2018-01-11 06:14:05 +11:00
|
|
|
# "Unauthorized for PMS"
|
|
|
|
dialog('notification', lang(29999), lang(30017))
|
2018-01-21 23:42:22 +11:00
|
|
|
return
|
2018-01-11 06:14:05 +11:00
|
|
|
playqueue = PQ.get_playqueue_from_type(
|
|
|
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
|
|
|
|
pos = js.get_position(playqueue.playlistid)
|
2018-01-21 23:42:22 +11:00
|
|
|
# Can return -1 (as in "no playlist")
|
2018-01-11 06:14:05 +11:00
|
|
|
pos = pos if pos != -1 else 0
|
|
|
|
LOG.info('playQueue position: %s for %s', pos, playqueue)
|
|
|
|
# Have we already initiated playback?
|
|
|
|
try:
|
2018-01-21 23:42:22 +11:00
|
|
|
playqueue.items[pos]
|
2018-01-11 06:14:05 +11:00
|
|
|
except IndexError:
|
2018-01-24 06:38:50 +11:00
|
|
|
# Release our default.py before starting our own Kodi player instance
|
2018-01-26 03:15:38 +11:00
|
|
|
if resolve is True:
|
|
|
|
result = Playback_Successful()
|
2018-01-28 23:24:42 +11:00
|
|
|
result.listitem = PKC_ListItem(path='PKC_Dummy_Path_Which_Fails')
|
2018-01-26 03:15:38 +11:00
|
|
|
pickle_me(result)
|
2018-01-21 23:42:22 +11:00
|
|
|
playback_init(plex_id, plex_type, playqueue)
|
2018-01-11 06:14:05 +11:00
|
|
|
else:
|
2018-01-21 23:42:22 +11:00
|
|
|
# kick off playback on second pass
|
2018-01-11 06:14:05 +11:00
|
|
|
conclude_playback(playqueue, pos)
|
|
|
|
|
|
|
|
|
2018-01-22 21:20:37 +11:00
|
|
|
def play_resume(playqueue, xml, stack):
|
|
|
|
"""
|
|
|
|
If there exists a resume point, Kodi will ask the user whether to continue
|
|
|
|
playback. We thus need to use setResolvedUrl "correctly". Mind that there
|
|
|
|
might be several parts!
|
|
|
|
"""
|
|
|
|
result = Playback_Successful()
|
|
|
|
listitem = PKC_ListItem()
|
|
|
|
# Only get the very first item of our playqueue (i.e. the very first part)
|
|
|
|
stack_item = stack.pop(0)
|
|
|
|
api = API(xml[0])
|
|
|
|
item = PL.playlist_item_from_xml(playqueue,
|
|
|
|
xml[0],
|
|
|
|
kodi_id=stack_item['kodi_id'],
|
|
|
|
kodi_type=stack_item['kodi_type'])
|
|
|
|
api.setPartNumber(item.part)
|
|
|
|
item.playcount = stack_item['playcount']
|
|
|
|
item.offset = stack_item['offset']
|
|
|
|
item.part = stack_item['part']
|
|
|
|
item.init_done = True
|
|
|
|
api.CreateListItemFromPlexItem(listitem)
|
|
|
|
playutils = PlayUtils(api, item)
|
|
|
|
playurl = playutils.getPlayUrl()
|
|
|
|
listitem.setPath(playurl)
|
|
|
|
if item.playmethod in ('DirectStream', 'DirectPlay'):
|
|
|
|
listitem.setSubtitles(api.externalSubs())
|
|
|
|
else:
|
|
|
|
playutils.audio_subtitle_prefs(listitem)
|
|
|
|
result.listitem = listitem
|
|
|
|
# Add to our playlist
|
|
|
|
playqueue.items.append(item)
|
|
|
|
# This will release default.py with setResolvedUrl
|
|
|
|
pickle_me(result)
|
|
|
|
# Add remaining parts to the playlist, if any
|
|
|
|
if stack:
|
|
|
|
_process_stack(playqueue, stack)
|
|
|
|
|
|
|
|
|
2018-01-21 23:42:22 +11:00
|
|
|
def playback_init(plex_id, plex_type, playqueue):
|
2018-01-11 06:14:05 +11:00
|
|
|
"""
|
2018-01-21 23:42:22 +11:00
|
|
|
Playback setup if Kodi starts playing an item for the first time.
|
2018-01-08 03:50:30 +11:00
|
|
|
"""
|
2018-01-21 23:42:22 +11:00
|
|
|
LOG.info('Initializing PKC playback')
|
2018-01-11 06:14:05 +11:00
|
|
|
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"
|
2018-01-21 23:42:22 +11:00
|
|
|
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
2018-01-11 06:14:05 +11:00
|
|
|
return
|
2018-01-08 03:50:30 +11:00
|
|
|
trailers = False
|
2018-01-28 23:29:27 +11:00
|
|
|
if (plex_type == v.PLEX_TYPE_MOVIE and not state.RESUMABLE and
|
2018-01-08 03:50:30 +11:00
|
|
|
settings('enableCinema') == "true"):
|
|
|
|
if settings('askCinema') == "true":
|
2018-01-11 06:14:05 +11:00
|
|
|
# "Play trailers?"
|
|
|
|
trailers = dialog('yesno', lang(29999), lang(33016))
|
2018-01-08 03:50:30 +11:00
|
|
|
trailers = True if trailers else False
|
|
|
|
else:
|
|
|
|
trailers = True
|
2018-01-21 23:42:22 +11:00
|
|
|
LOG.info('Playing trailers: %s', trailers)
|
|
|
|
# Post to the PMS to create a playqueue - in any case due to Plex Companion
|
2018-01-08 03:50:30 +11:00
|
|
|
xml = init_plex_playqueue(plex_id,
|
2018-01-11 06:14:05 +11:00
|
|
|
xml.attrib.get('librarySectionUUID'),
|
|
|
|
mediatype=plex_type,
|
2018-01-08 03:50:30 +11:00
|
|
|
trailers=trailers)
|
2018-01-11 06:14:05 +11:00
|
|
|
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"
|
2018-01-21 23:42:22 +11:00
|
|
|
dialog('notification', lang(29999), lang(30128), icon='{error}')
|
2018-01-11 06:14:05 +11:00
|
|
|
return
|
2018-01-21 23:42:22 +11:00
|
|
|
# Should already be empty, but just in case
|
2018-01-11 06:14:05 +11:00
|
|
|
playqueue.clear()
|
|
|
|
PL.get_playlist_details_from_xml(playqueue, xml)
|
|
|
|
stack = _prep_playlist_stack(xml)
|
2018-01-22 21:20:37 +11:00
|
|
|
# if resume:
|
|
|
|
# # Need to handle this differently so only 1 dialog is displayed whether
|
|
|
|
# # user wants to resume to start at the beginning
|
|
|
|
# LOG.info('Resume detected')
|
|
|
|
# play_resume(playqueue, xml, stack)
|
|
|
|
# return
|
2018-01-21 23:42:22 +11:00
|
|
|
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
|
2018-01-28 23:31:35 +11:00
|
|
|
sleep(200)
|
2018-01-21 23:42:22 +11:00
|
|
|
_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')
|
2018-01-26 19:47:58 +11:00
|
|
|
# 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 <missing path>"
|
2018-01-21 23:42:22 +11:00
|
|
|
thread.start()
|
2018-01-08 03:50:30 +11:00
|
|
|
|
|
|
|
|
2018-01-11 06:14:05 +11:00
|
|
|
def _prep_playlist_stack(xml):
|
|
|
|
stack = []
|
|
|
|
for item in xml:
|
|
|
|
api = API(item)
|
2018-01-21 23:42:22 +11:00
|
|
|
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
|
2018-01-11 06:14:05 +11:00
|
|
|
kodi_id = None
|
|
|
|
kodi_type = None
|
2018-01-21 23:42:22 +11:00
|
|
|
for part, _ in enumerate(item[0]):
|
|
|
|
api.setPartNumber(part)
|
|
|
|
if kodi_id is None:
|
2018-01-11 06:14:05 +11:00
|
|
|
# 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'
|
2018-01-21 23:42:22 +11:00
|
|
|
% urlencode(params))
|
2018-01-11 06:14:05 +11:00
|
|
|
listitem = api.CreateListItemFromPlexItem()
|
|
|
|
listitem.setPath(path)
|
2018-01-21 23:42:22 +11:00
|
|
|
else:
|
|
|
|
# Will add directly via the Kodi DB
|
|
|
|
path = None
|
|
|
|
listitem = None
|
2018-01-11 06:14:05 +11:00
|
|
|
stack.append({
|
|
|
|
'kodi_id': kodi_id,
|
|
|
|
'kodi_type': kodi_type,
|
|
|
|
'file': path,
|
|
|
|
'xml_video_element': item,
|
|
|
|
'listitem': listitem,
|
2018-01-22 04:31:49 +11:00
|
|
|
'part': part,
|
|
|
|
'playcount': api.getViewCount(),
|
2018-01-22 21:20:37 +11:00
|
|
|
'offset': api.getResume()
|
2018-01-11 06:14:05 +11:00
|
|
|
})
|
|
|
|
return stack
|
|
|
|
|
|
|
|
|
|
|
|
def _process_stack(playqueue, stack):
|
|
|
|
"""
|
|
|
|
Takes our stack and adds the items to the PKC and Kodi playqueues.
|
|
|
|
"""
|
2018-01-21 23:42:22 +11:00
|
|
|
# getposition() can return -1
|
|
|
|
pos = max(playqueue.kodi_pl.getposition(), 0) + 1
|
|
|
|
for item in stack:
|
|
|
|
if item['kodi_id'] is None:
|
2018-01-11 06:14:05 +11:00
|
|
|
playlist_item = PL.add_listitem_to_Kodi_playlist(
|
|
|
|
playqueue,
|
2018-01-21 23:42:22 +11:00
|
|
|
pos,
|
2018-01-11 06:14:05 +11:00
|
|
|
item['listitem'],
|
|
|
|
file=item['file'],
|
|
|
|
xml_video_element=item['xml_video_element'])
|
2018-01-21 23:42:22 +11:00
|
|
|
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'])
|
2018-01-22 04:31:49 +11:00
|
|
|
playlist_item.playcount = item['playcount']
|
|
|
|
playlist_item.offset = item['offset']
|
2018-01-21 23:42:22 +11:00
|
|
|
playlist_item.part = item['part']
|
2018-01-11 06:14:05 +11:00
|
|
|
playlist_item.init_done = True
|
2018-01-21 23:42:22 +11:00
|
|
|
pos += 1
|
2018-01-11 06:14:05 +11:00
|
|
|
|
|
|
|
|
|
|
|
def conclude_playback(playqueue, pos):
|
2018-01-08 03:50:30 +11:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
"""
|
2018-01-22 21:20:37 +11:00
|
|
|
LOG.info('Concluding playback for playqueue position %s', pos)
|
2018-01-08 03:50:30 +11:00
|
|
|
result = Playback_Successful()
|
|
|
|
listitem = PKC_ListItem()
|
|
|
|
item = playqueue.items[pos]
|
2018-01-11 06:14:05 +11:00
|
|
|
if item.xml is not None:
|
|
|
|
# Got a Plex element
|
|
|
|
api = API(item.xml)
|
|
|
|
api.setPartNumber(item.part)
|
|
|
|
api.CreateListItemFromPlexItem(listitem)
|
2018-01-08 03:50:30 +11:00
|
|
|
playutils = PlayUtils(api, item)
|
|
|
|
playurl = playutils.getPlayUrl()
|
2018-01-11 06:14:05 +11:00
|
|
|
else:
|
|
|
|
playurl = item.file
|
2018-01-08 03:50:30 +11:00
|
|
|
listitem.setPath(playurl)
|
2018-01-22 21:20:37 +11:00
|
|
|
if item.playmethod in ('DirectStream', 'DirectPlay'):
|
2018-01-08 03:50:30 +11:00
|
|
|
listitem.setSubtitles(api.externalSubs())
|
|
|
|
else:
|
|
|
|
playutils.audio_subtitle_prefs(listitem)
|
|
|
|
listitem.setPath(playurl)
|
2018-01-22 21:20:37 +11:00
|
|
|
if state.RESUME_PLAYBACK is True:
|
|
|
|
state.RESUME_PLAYBACK = False
|
|
|
|
LOG.info('Resuming playback at %s', item.offset)
|
|
|
|
listitem.setProperty('StartOffset', str(item.offset))
|
|
|
|
listitem.setProperty('resumetime', str(item.offset))
|
2018-01-26 19:47:58 +11:00
|
|
|
# Reset the resumable flag
|
|
|
|
state.RESUMABLE = False
|
2018-01-08 03:50:30 +11:00
|
|
|
result.listitem = listitem
|
2018-01-11 06:14:05 +11:00
|
|
|
pickle_me(result)
|
2018-01-22 21:20:37 +11:00
|
|
|
LOG.info('Done concluding playback')
|