Revamp playback start, part 5
This commit is contained in:
parent
2791da9f65
commit
287b888b6f
7 changed files with 109 additions and 9 deletions
|
@ -1505,6 +1505,16 @@ class API():
|
||||||
"""
|
"""
|
||||||
return self.item.attrib.get('year', None)
|
return self.item.attrib.get('year', None)
|
||||||
|
|
||||||
|
def getResume(self):
|
||||||
|
"""
|
||||||
|
Returns the resume point of time in seconds as int. 0 if not found
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
resume = float(self.item.attrib['viewOffset'])
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
resume = 0.0
|
||||||
|
return int(resume * v.PLEX_TO_KODI_TIMEFACTOR)
|
||||||
|
|
||||||
def getRuntime(self):
|
def getRuntime(self):
|
||||||
"""
|
"""
|
||||||
Resume point of time and runtime/totaltime in rounded to seconds.
|
Resume point of time and runtime/totaltime in rounded to seconds.
|
||||||
|
@ -2521,7 +2531,9 @@ class API():
|
||||||
'mpaa': self.getMpaa(),
|
'mpaa': self.getMpaa(),
|
||||||
'aired': self.getPremiereDate()
|
'aired': self.getPremiereDate()
|
||||||
}
|
}
|
||||||
listItem.setProperty('resumetime', str(userdata['Resume']))
|
# Do NOT set resumetime - otherwise Kodi always resumes at that time
|
||||||
|
# even if the user chose to start element from the beginning
|
||||||
|
# listItem.setProperty('resumetime', str(userdata['Resume']))
|
||||||
listItem.setProperty('totaltime', str(userdata['Runtime']))
|
listItem.setProperty('totaltime', str(userdata['Runtime']))
|
||||||
|
|
||||||
if typus == v.PLEX_TYPE_EPISODE:
|
if typus == v.PLEX_TYPE_EPISODE:
|
||||||
|
|
|
@ -3,11 +3,15 @@ PKC Kodi Monitoring implementation
|
||||||
"""
|
"""
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from json import loads
|
from json import loads
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
from xbmc import Monitor, Player, sleep
|
from xbmc import Monitor, Player, sleep, getCondVisibility, getInfoLabel, \
|
||||||
|
getLocalizedString
|
||||||
|
from xbmcgui import Window
|
||||||
|
|
||||||
import plexdb_functions as plexdb
|
import plexdb_functions as plexdb
|
||||||
from utils import window, settings, CatchExceptions, plex_command
|
from utils import window, settings, CatchExceptions, plex_command, \
|
||||||
|
thread_methods
|
||||||
from PlexFunctions import scrobble
|
from PlexFunctions import scrobble
|
||||||
from kodidb_functions import kodiid_from_filename
|
from kodidb_functions import kodiid_from_filename
|
||||||
from plexbmchelper.subscribers import LOCKER
|
from plexbmchelper.subscribers import LOCKER
|
||||||
|
@ -391,3 +395,31 @@ class KodiMonitor(Monitor):
|
||||||
else:
|
else:
|
||||||
window('plex_%s.playmethod' % currentFile, value="DirectPlay")
|
window('plex_%s.playmethod' % currentFile, value="DirectPlay")
|
||||||
LOG.debug('Window properties set for direct paths!')
|
LOG.debug('Window properties set for direct paths!')
|
||||||
|
|
||||||
|
|
||||||
|
@thread_methods
|
||||||
|
class SpecialMonitor(Thread):
|
||||||
|
"""
|
||||||
|
Detect the resume dialog for widgets.
|
||||||
|
Could also be used to detect external players (see Emby implementation)
|
||||||
|
"""
|
||||||
|
def run(self):
|
||||||
|
LOG.info("----====# Starting Special Monitor #====----")
|
||||||
|
player = Player()
|
||||||
|
while not self.thread_stopped():
|
||||||
|
is_playing = player.isPlaying()
|
||||||
|
|
||||||
|
if (not is_playing and
|
||||||
|
getCondVisibility('Window.IsVisible(DialogContextMenu.xml)') and
|
||||||
|
not getCondVisibility('Window.IsVisible(MyVideoNav.xml)') and
|
||||||
|
getInfoLabel('Control.GetLabel(1002)') == getLocalizedString(12021)):
|
||||||
|
control = int(Window(10106).getFocusId())
|
||||||
|
if control == 1002:
|
||||||
|
# Start from beginning
|
||||||
|
LOG.info("Resume dialog: Start from beginning selected")
|
||||||
|
state.RESUME_PLAYBACK = False
|
||||||
|
else:
|
||||||
|
LOG.info("Resume dialog: resume selected")
|
||||||
|
state.RESUME_PLAYBACK = True
|
||||||
|
sleep(200)
|
||||||
|
LOG.info("#====---- Special Monitor Stopped ----====#")
|
||||||
|
|
|
@ -32,7 +32,7 @@ def pickle_me(obj, window_var='plex_result'):
|
||||||
obj can be pretty much any Python object. However, classes and
|
obj can be pretty much any Python object. However, classes and
|
||||||
functions won't work. See the Pickle documentation
|
functions won't work. See the Pickle documentation
|
||||||
"""
|
"""
|
||||||
log('%sStart pickling: %s' % (PREFIX, obj), level=LOGDEBUG)
|
log('%sStart pickling' % PREFIX, level=LOGDEBUG)
|
||||||
pickl_window(window_var, value=dumps(obj))
|
pickl_window(window_var, value=dumps(obj))
|
||||||
log('%sSuccessfully pickled' % PREFIX, level=LOGDEBUG)
|
log('%sSuccessfully pickled' % PREFIX, level=LOGDEBUG)
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,44 @@ def playback_triage(plex_id=None, plex_type=None, path=None):
|
||||||
conclude_playback(playqueue, pos)
|
conclude_playback(playqueue, pos)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
def playback_init(plex_id, plex_type, playqueue):
|
def playback_init(plex_id, plex_type, playqueue):
|
||||||
"""
|
"""
|
||||||
Playback setup if Kodi starts playing an item for the first time.
|
Playback setup if Kodi starts playing an item for the first time.
|
||||||
|
@ -112,10 +150,16 @@ def playback_init(plex_id, plex_type, playqueue):
|
||||||
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)
|
||||||
|
# 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
|
||||||
# Release our default.py before starting our own Kodi player instance
|
# Release our default.py before starting our own Kodi player instance
|
||||||
pickle_me(Playback_Successful())
|
pickle_me(Playback_Successful())
|
||||||
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
|
# Sleep a bit to let setResolvedUrl do its thing - bit ugly
|
||||||
sleep(200)
|
sleep(100)
|
||||||
_process_stack(playqueue, stack)
|
_process_stack(playqueue, stack)
|
||||||
# New thread to release this one sooner (e.g. harddisk spinning up)
|
# New thread to release this one sooner (e.g. harddisk spinning up)
|
||||||
thread = Thread(target=Player().play,
|
thread = Thread(target=Player().play,
|
||||||
|
@ -138,7 +182,6 @@ def _prep_playlist_stack(xml):
|
||||||
# We will never store clips (trailers) in the Kodi DB
|
# We will never store clips (trailers) in the Kodi DB
|
||||||
kodi_id = None
|
kodi_id = None
|
||||||
kodi_type = None
|
kodi_type = None
|
||||||
resume, _ = api.getRuntime()
|
|
||||||
for part, _ in enumerate(item[0]):
|
for part, _ in enumerate(item[0]):
|
||||||
api.setPartNumber(part)
|
api.setPartNumber(part)
|
||||||
if kodi_id is None:
|
if kodi_id is None:
|
||||||
|
@ -164,7 +207,7 @@ def _prep_playlist_stack(xml):
|
||||||
'listitem': listitem,
|
'listitem': listitem,
|
||||||
'part': part,
|
'part': part,
|
||||||
'playcount': api.getViewCount(),
|
'playcount': api.getViewCount(),
|
||||||
'offset': resume
|
'offset': api.getResume()
|
||||||
})
|
})
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
|
@ -213,6 +256,7 @@ def conclude_playback(playqueue, pos):
|
||||||
start playback
|
start playback
|
||||||
return PKC listitem attached to result
|
return PKC listitem attached to result
|
||||||
"""
|
"""
|
||||||
|
LOG.info('Concluding playback for playqueue position %s', pos)
|
||||||
result = Playback_Successful()
|
result = Playback_Successful()
|
||||||
listitem = PKC_ListItem()
|
listitem = PKC_ListItem()
|
||||||
item = playqueue.items[pos]
|
item = playqueue.items[pos]
|
||||||
|
@ -226,10 +270,16 @@ def conclude_playback(playqueue, pos):
|
||||||
else:
|
else:
|
||||||
playurl = item.file
|
playurl = item.file
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
if item.playmethod in ("DirectStream", "DirectPlay"):
|
if item.playmethod in ('DirectStream', 'DirectPlay'):
|
||||||
listitem.setSubtitles(api.externalSubs())
|
listitem.setSubtitles(api.externalSubs())
|
||||||
else:
|
else:
|
||||||
playutils.audio_subtitle_prefs(listitem)
|
playutils.audio_subtitle_prefs(listitem)
|
||||||
listitem.setPath(playurl)
|
listitem.setPath(playurl)
|
||||||
|
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))
|
||||||
result.listitem = listitem
|
result.listitem = listitem
|
||||||
pickle_me(result)
|
pickle_me(result)
|
||||||
|
LOG.info('Done concluding playback')
|
||||||
|
|
|
@ -47,6 +47,7 @@ class PlayUtils():
|
||||||
})
|
})
|
||||||
self.item.playmethod = 'Transcode'
|
self.item.playmethod = 'Transcode'
|
||||||
LOG.info("The playurl is: %s", playurl)
|
LOG.info("The playurl is: %s", playurl)
|
||||||
|
self.item.file = playurl
|
||||||
return playurl
|
return playurl
|
||||||
|
|
||||||
def isDirectPlay(self):
|
def isDirectPlay(self):
|
||||||
|
|
|
@ -128,6 +128,9 @@ PLAYSTATE = {
|
||||||
# paths for playback (since we're not receiving a Kodi id)
|
# paths for playback (since we're not receiving a Kodi id)
|
||||||
PLEX_IDS = {}
|
PLEX_IDS = {}
|
||||||
PLAYED_INFO = {}
|
PLAYED_INFO = {}
|
||||||
|
# Set by SpecialMonitor - did user choose to resume playback or start from the
|
||||||
|
# beginning?
|
||||||
|
RESUME_PLAYBACK = False
|
||||||
|
|
||||||
# Kodi webserver details
|
# Kodi webserver details
|
||||||
WEBSERVER_PORT = 8080
|
WEBSERVER_PORT = 8080
|
||||||
|
|
|
@ -31,7 +31,7 @@ sys_path.append(_base_resource)
|
||||||
from utils import settings, window, language as lang, dialog, tryDecode
|
from utils import settings, window, language as lang, dialog, tryDecode
|
||||||
from userclient import UserClient
|
from userclient import UserClient
|
||||||
import initialsetup
|
import initialsetup
|
||||||
from kodimonitor import KodiMonitor
|
from kodimonitor import KodiMonitor, SpecialMonitor
|
||||||
from librarysync import LibrarySync
|
from librarysync import LibrarySync
|
||||||
import videonodes
|
import videonodes
|
||||||
from websocket_client import PMS_Websocket, Alexa_Websocket
|
from websocket_client import PMS_Websocket, Alexa_Websocket
|
||||||
|
@ -155,6 +155,7 @@ class Service():
|
||||||
self.alexa = Alexa_Websocket()
|
self.alexa = Alexa_Websocket()
|
||||||
self.library = LibrarySync()
|
self.library = LibrarySync()
|
||||||
self.plexCompanion = PlexCompanion()
|
self.plexCompanion = PlexCompanion()
|
||||||
|
self.specialMonitor = SpecialMonitor()
|
||||||
self.playback_starter = Playback_Starter()
|
self.playback_starter = Playback_Starter()
|
||||||
if settings('enableTextureCache') == "true":
|
if settings('enableTextureCache') == "true":
|
||||||
self.image_cache_thread = Image_Cache_Thread()
|
self.image_cache_thread = Image_Cache_Thread()
|
||||||
|
@ -197,6 +198,7 @@ class Service():
|
||||||
sound=False)
|
sound=False)
|
||||||
# Start monitoring kodi events
|
# Start monitoring kodi events
|
||||||
self.kodimonitor_running = KodiMonitor()
|
self.kodimonitor_running = KodiMonitor()
|
||||||
|
self.specialMonitor.start()
|
||||||
# Start the Websocket Client
|
# Start the Websocket Client
|
||||||
if not self.ws_running:
|
if not self.ws_running:
|
||||||
self.ws_running = True
|
self.ws_running = True
|
||||||
|
|
Loading…
Reference in a new issue