Merge pull request #1021 from croneter/fix-resume

Fix resume not working in some cases
This commit is contained in:
croneter 2019-10-31 13:37:56 +01:00 committed by GitHub
commit 265b2dcf6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 107 additions and 168 deletions

View file

@ -17,14 +17,15 @@ from .plex_api import API
from .plex_db import PlexDB from .plex_db import PlexDB
from . import kodi_db from . import kodi_db
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import utils, timing, plex_functions as PF, playback from . import utils, timing, plex_functions as PF
from . import json_rpc as js, playqueue as PQ, playlist_func as PL from . import json_rpc as js, playqueue as PQ, playlist_func as PL
from . import backgroundthread, app, variables as v from . import backgroundthread, app, variables as v
LOG = getLogger('PLEX.kodimonitor') LOG = getLogger('PLEX.kodimonitor')
# "Start from beginning", "Play from beginning" # "Start from beginning", "Play from beginning"
STRINGS = (utils.lang(12021).encode('utf-8'), utils.lang(12023).encode('utf-8')) STRINGS = (utils.lang(12021).encode('utf-8'),
utils.lang(12023).encode('utf-8'))
class KodiMonitor(xbmc.Monitor): class KodiMonitor(xbmc.Monitor):
@ -33,7 +34,6 @@ class KodiMonitor(xbmc.Monitor):
""" """
def __init__(self): def __init__(self):
self._already_slept = False self._already_slept = False
self.hack_replay = None
xbmc.Monitor.__init__(self) xbmc.Monitor.__init__(self)
for playerid in app.PLAYSTATE.player_states: for playerid in app.PLAYSTATE.player_states:
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template) app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
@ -57,9 +57,6 @@ class KodiMonitor(xbmc.Monitor):
Monitor the PKC settings for changes made by the user Monitor the PKC settings for changes made by the user
""" """
LOG.debug('PKC settings change detected') LOG.debug('PKC settings change detected')
# Assume that the user changed something so we can try to reconnect
# app.APP.suspend = False
# app.APP.resume_threads(block=False)
def onNotification(self, sender, method, data): def onNotification(self, sender, method, data):
""" """
@ -69,28 +66,12 @@ class KodiMonitor(xbmc.Monitor):
data = loads(data, 'utf-8') data = loads(data, 'utf-8')
LOG.debug("Method: %s Data: %s", method, data) LOG.debug("Method: %s Data: %s", method, data)
# Hack
if not method == 'Player.OnStop':
self.hack_replay = None
if method == "Player.OnPlay": if method == "Player.OnPlay":
with app.APP.lock_playqueues: with app.APP.lock_playqueues:
self.PlayBackStart(data) self.PlayBackStart(data)
elif method == "Player.OnStop": elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck
# xbmc.executebuiltin('ReloadSkin()')
if (self.hack_replay and not data.get('end') and
self.hack_replay == data['item']):
# Hack for add-on paths
self.hack_replay = None
with app.APP.lock_playqueues: with app.APP.lock_playqueues:
self._hack_addon_paths_replay_video() _playback_cleanup(ended=data.get('end'))
elif data.get('end'):
with app.APP.lock_playqueues:
_playback_cleanup(ended=True)
else:
with app.APP.lock_playqueues:
_playback_cleanup()
elif method == 'Playlist.OnAdd': elif method == 'Playlist.OnAdd':
if 'item' in data and data['item'].get('type') == v.KODI_TYPE_SHOW: if 'item' in data and data['item'].get('type') == v.KODI_TYPE_SHOW:
# Hitting the "browse" button on tv show info dialog # Hitting the "browse" button on tv show info dialog
@ -126,39 +107,6 @@ class KodiMonitor(xbmc.Monitor):
elif method == 'Other.plugin.video.plexkodiconnect_play_action': elif method == 'Other.plugin.video.plexkodiconnect_play_action':
self._start_next_episode(data) self._start_next_episode(data)
@staticmethod
def _hack_addon_paths_replay_video():
"""
Hack we need for RESUMABLE items because Kodi lost the path of the
last played item that is now being replayed (see playback.py's
Player().play()) Also see playqueue.py _compare_playqueues()
Needed if user re-starts the same video from the library using addon
paths. (Video is only added to playqueue, then immediately stoppen.
There is no playback initialized by Kodi.) Log excerpts:
Method: Playlist.OnAdd Data:
{u'item': {u'type': u'movie', u'id': 4},
u'playlistid': 1,
u'position': 0}
Now we would hack!
Method: Player.OnStop Data:
{u'item': {u'type': u'movie', u'id': 4},
u'end': False}
(within the same micro-second!)
"""
LOG.info('Detected re-start of playback of last item')
old = app.PLAYSTATE.old_player_states[1]
kwargs = {
'plex_id': old['plex_id'],
'plex_type': old['plex_type'],
'path': old['file'],
'resolve': False
}
task = backgroundthread.FunctionAsTask(playback.playback_triage,
None,
**kwargs)
backgroundthread.BGThreader.addTasksToFront([task])
def _playlist_onadd(self, data): def _playlist_onadd(self, data):
""" """
Called if an item is added to a Kodi playlist. Example data dict: Called if an item is added to a Kodi playlist. Example data dict:
@ -171,15 +119,7 @@ class KodiMonitor(xbmc.Monitor):
} }
Will NOT be called if playback initiated by Kodi widgets Will NOT be called if playback initiated by Kodi widgets
""" """
if 'id' not in data['item']: pass
return
old = app.PLAYSTATE.old_player_states[data['playlistid']]
if (not app.SYNC.direct_paths and
data['position'] == 0 and data['playlistid'] == 1 and
not PQ.PLAYQUEUES[data['playlistid']].items and
data['item']['type'] == old['kodi_type'] and
data['item']['id'] == old['kodi_id']):
self.hack_replay = data['item']
def _playlist_onremove(self, data): def _playlist_onremove(self, data):
""" """
@ -451,7 +391,7 @@ def _playback_cleanup(ended=False):
app.PLAYSTATE.active_players = set() app.PLAYSTATE.active_players = set()
app.PLAYSTATE.item = None app.PLAYSTATE.item = None
utils.delete_temporary_subtitles() utils.delete_temporary_subtitles()
LOG.info('Finished PKC playback cleanup') LOG.debug('Finished PKC playback cleanup')
def _record_playstate(status, ended): def _record_playstate(status, ended):

View file

@ -30,7 +30,8 @@ RESOLVE = True
############################################################################### ###############################################################################
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True): def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True,
resume=False):
""" """
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.
@ -47,7 +48,7 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
service.py Python instance service.py Python instance
""" """
try: try:
_playback_triage(plex_id, plex_type, path, resolve) _playback_triage(plex_id, plex_type, path, resolve, resume)
finally: finally:
# Reset some playback variables the user potentially set to init # Reset some playback variables the user potentially set to init
# playback # playback
@ -56,10 +57,10 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
app.PLAYSTATE.resume_playback = None app.PLAYSTATE.resume_playback = None
def _playback_triage(plex_id, plex_type, path, resolve): def _playback_triage(plex_id, plex_type, path, resolve, resume):
plex_id = utils.cast(int, plex_id) plex_id = utils.cast(int, plex_id)
LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s, ' LOG.debug('playback_triage called with plex_id %s, plex_type %s, path %s, '
'resolve %s', plex_id, plex_type, path, resolve) 'resolve %s, resume %s', plex_id, plex_type, path, resolve, resume)
global RESOLVE global RESOLVE
# If started via Kodi context menu, we never resolve # If started via Kodi context menu, we never resolve
RESOLVE = resolve if not app.PLAYSTATE.context_menu_play else False RESOLVE = resolve if not app.PLAYSTATE.context_menu_play else False
@ -85,12 +86,12 @@ def _playback_triage(plex_id, plex_type, path, resolve):
except KeyError: except KeyError:
# Kodi bug - Playlist plays (not Playqueue) will ALWAYS be audio for # Kodi bug - Playlist plays (not Playqueue) will ALWAYS be audio for
# add-on paths # add-on paths
LOG.info('No position returned from player! Assuming playlist') LOG.debug('No position returned from player! Assuming playlist')
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO) playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO)
try: try:
pos = js.get_position(playqueue.playlistid) pos = js.get_position(playqueue.playlistid)
except KeyError: except KeyError:
LOG.info('Assuming video instead of audio playlist playback') LOG.debug('Assuming video instead of audio playlist playback')
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO) playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO)
try: try:
pos = js.get_position(playqueue.playlistid) pos = js.get_position(playqueue.playlistid)
@ -108,12 +109,12 @@ def _playback_triage(plex_id, plex_type, path, resolve):
try: try:
item = items[pos] item = items[pos]
except IndexError: except IndexError:
LOG.info('Could not apply playlist hack! Probably Widget playback') LOG.debug('Could not apply playlist hack! Probably Widget playback')
else: else:
if ('id' not in item and if ('id' not in item and
item.get('type') == 'unknown' and item.get('title') == ''): item.get('type') == 'unknown' and item.get('title') == ''):
LOG.info('Kodi playlist play detected') LOG.debug('Kodi playlist play detected')
_playlist_playback(plex_id, plex_type) _playlist_playback(plex_id)
return return
# Can return -1 (as in "no playlist") # Can return -1 (as in "no playlist")
@ -136,13 +137,14 @@ def _playback_triage(plex_id, plex_type, path, resolve):
LOG.debug('Detected re-playing of the same item') LOG.debug('Detected re-playing of the same item')
initiate = True initiate = True
if initiate: if initiate:
_playback_init(plex_id, plex_type, playqueue, pos) _playback_init(plex_id, plex_type, playqueue, pos, resume)
else: else:
# kick off playback on second pass # kick off playback on second pass, resume was already set on first
# pass (threaded_playback will seek to resume)
_conclude_playback(playqueue, pos) _conclude_playback(playqueue, pos)
def _playlist_playback(plex_id, plex_type): def _playlist_playback(plex_id):
""" """
Really annoying Kodi behavior: Kodi will throw the ENTIRE playlist some- Really annoying Kodi behavior: Kodi will throw the ENTIRE playlist some-
where, causing Playlist.onAdd to fire for each item like this: where, causing Playlist.onAdd to fire for each item like this:
@ -175,11 +177,11 @@ def _playlist_playback(plex_id, plex_type):
_conclude_playback(playqueue, pos=0) _conclude_playback(playqueue, pos=0)
def _playback_init(plex_id, plex_type, playqueue, pos): def _playback_init(plex_id, plex_type, playqueue, pos, force_resume):
""" """
Playback setup if Kodi starts playing an item for the first time. Playback setup if Kodi starts playing an item for the first time.
""" """
LOG.info('Initializing PKC playback') LOG.debug('Initializing PKC playback')
# Stop playback so we don't get an error message that the last item of the # Stop playback so we don't get an error message that the last item of the
# queue failed to play # queue failed to play
app.APP.player.stop() app.APP.player.stop()
@ -210,19 +212,8 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
# playqueues # playqueues
# Release default.py # Release default.py
_ensure_resolve() _ensure_resolve()
api = API(xml[0]) LOG.debug('Using force_resume %s', force_resume)
if api.resume_point() and (app.SYNC.direct_paths or resume = force_resume or False
app.PLAYSTATE.context_menu_play):
# Since Kodi won't ask if user wants to resume playback -
# we need to ask ourselves
resume = resume_dialog(int(api.resume_point()))
if resume is None:
LOG.info('User cancelled resume dialog')
return
elif app.SYNC.direct_paths:
resume = False
else:
resume = app.PLAYSTATE.resume_playback or False
trailers = False trailers = False
if (not resume and plex_type == v.PLEX_TYPE_MOVIE and if (not resume and plex_type == v.PLEX_TYPE_MOVIE and
utils.settings('enableCinema') == "true"): utils.settings('enableCinema') == "true"):
@ -251,11 +242,17 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
PL.get_playlist_details_from_xml(playqueue, xml) PL.get_playlist_details_from_xml(playqueue, xml)
stack = _prep_playlist_stack(xml, resume) stack = _prep_playlist_stack(xml, resume)
_process_stack(playqueue, stack) _process_stack(playqueue, stack)
offset = _use_kodi_db_offset(playqueue.items[pos].plex_id,
playqueue.items[pos].plex_type,
playqueue.items[pos].offset) if resume else 0
# 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=threaded_playback, thread = Thread(target=threaded_playback,
args=(playqueue.kodi_pl, pos, None)) args=(playqueue.kodi_pl, pos, offset))
thread.setDaemon(True) thread.setDaemon(True)
LOG.info('Done initializing playback, starting Kodi player at pos %s', pos) LOG.debug('Done initializing playback, starting Kodi player at pos %s and '
'offset %s', pos, offset)
# Ensure that PKC playqueue monitor ignores the changes we just made
playqueue.pkc_edit = True
# By design, PKC will start Kodi playback using Player().play(). Kodi # By design, PKC will start Kodi playback using Player().play(). Kodi
# caches paths like our plugin://pkc. If we use Player().play() between # caches paths like our plugin://pkc. If we use Player().play() between
# 2 consecutive startups of exactly the same Kodi library item, Kodi's # 2 consecutive startups of exactly the same Kodi library item, Kodi's
@ -263,8 +260,6 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
# plugin://pkc will be lost; Kodi will try to startup playback for an empty # plugin://pkc will be lost; Kodi will try to startup playback for an empty
# path: log entry is "CGUIWindowVideoBase::OnPlayMedia <missing path>" # path: log entry is "CGUIWindowVideoBase::OnPlayMedia <missing path>"
thread.start() thread.start()
# Ensure that PKC playqueue monitor ignores the changes we just made
playqueue.pkc_edit = True
def _ensure_resolve(abort=False): def _ensure_resolve(abort=False):
@ -376,7 +371,7 @@ def _prep_playlist_stack(xml, resume):
'part': part, 'part': part,
'playcount': api.viewcount(), 'playcount': api.viewcount(),
'offset': api.resume_point(), 'offset': api.resume_point(),
'resume': resume if i + 1 == len(xml) and part == 0 else False, 'resume': resume if part == 0 and i + 1 == len(xml) else None,
'id': api.item_id() 'id': api.item_id()
}) })
return stack return stack
@ -413,33 +408,20 @@ def _process_stack(playqueue, stack):
pos += 1 pos += 1
def _set_resume(listitem, item, api): def _use_kodi_db_offset(plex_id, plex_type, plex_offset):
if item.plex_type in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP): """
return Do NOT use item.offset directly but get it from the Kodi DB (Plex might not
if item.resume is True: have gotten the last resume point)
# Do NOT use item.offset directly but get it from the DB """
# (user might have initiated same video twice) if plex_type not in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_EPISODE):
return plex_offset
with PlexDB(lock=False) as plexdb: with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(item.plex_id, item.plex_type) db_item = plexdb.item_by_id(plex_id, plex_type)
if db_item: if db_item:
file_id = db_item['kodi_fileid']
with KodiVideoDB(lock=False) as kodidb: with KodiVideoDB(lock=False) as kodidb:
item.offset = kodidb.get_resume(file_id) return kodidb.get_resume(db_item['kodi_fileid'])
LOG.info('Resuming playback at %s', item.offset)
if v.KODIVERSION >= 18 and api:
# Kodi 18 Alpha 3 broke StartOffset
try:
percent = (item.offset or api.resume_point()) / api.runtime() * 100.0
except ZeroDivisionError:
percent = 0.0
LOG.debug('Resuming at %s percent', percent)
listitem.setProperty('StartPercent', str(percent))
else: else:
listitem.setProperty('StartOffset', str(item.offset)) return plex_offset
listitem.setProperty('resumetime', str(item.offset))
elif v.KODIVERSION >= 18:
# Make sure that the video starts from the beginning
listitem.setProperty('StartPercent', '0')
def _conclude_playback(playqueue, pos): def _conclude_playback(playqueue, pos):
@ -457,19 +439,14 @@ 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) LOG.debug('Concluding playback for playqueue position %s', pos)
item = playqueue.items[pos] item = playqueue.items[pos]
if item.xml is not None:
# Got a Plex element
api = API(item.xml) api = API(item.xml)
api.part = item.part or 0 api.part = item.part or 0
listitem = api.listitem(listitem=transfer.PKCListItem) listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
set_playurl(api, item) set_playurl(api, item)
else:
listitem = transfer.PKCListItem()
api = None
if not item.file: if not item.file:
LOG.info('Did not get a playurl, aborting playback silently') LOG.debug('Did not get a playurl, aborting playback silently')
_ensure_resolve() _ensure_resolve()
return return
listitem.setPath(item.file.encode('utf-8')) listitem.setPath(item.file.encode('utf-8'))
@ -478,9 +455,8 @@ def _conclude_playback(playqueue, pos):
elif item.playmethod in (v.PLAYBACK_METHOD_DIRECT_STREAM, elif item.playmethod in (v.PLAYBACK_METHOD_DIRECT_STREAM,
v.PLAYBACK_METHOD_TRANSCODE): v.PLAYBACK_METHOD_TRANSCODE):
audio_subtitle_prefs(api, listitem) audio_subtitle_prefs(api, listitem)
_set_resume(listitem, item, api)
transfer.send(listitem) transfer.send(listitem)
LOG.info('Done concluding playback') LOG.debug('Done concluding playback')
def process_indirect(key, offset, resolve=True): def process_indirect(key, offset, resolve=True):
@ -494,7 +470,7 @@ def process_indirect(key, offset, resolve=True):
Set resolve to False if playback should be kicked off directly, not via Set resolve to False if playback should be kicked off directly, not via
setResolvedUrl setResolvedUrl
""" """
LOG.info('process_indirect called with key: %s, offset: %s, resolve: %s', LOG.debug('process_indirect called with key: %s, offset: %s, resolve: %s',
key, offset, resolve) key, offset, resolve)
global RESOLVE global RESOLVE
RESOLVE = resolve RESOLVE = resolve
@ -513,7 +489,7 @@ def process_indirect(key, offset, resolve=True):
return return
api = API(xml[0]) api = API(xml[0])
listitem = api.listitem(listitem=transfer.PKCListItem) listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
playqueue = PQ.get_playqueue_from_type( playqueue = PQ.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type]) v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
playqueue.clear() playqueue.clear()
@ -552,7 +528,7 @@ def process_indirect(key, offset, resolve=True):
args={'item': utils.try_encode(playurl), args={'item': utils.try_encode(playurl),
'listitem': listitem}) 'listitem': listitem})
thread.setDaemon(True) thread.setDaemon(True)
LOG.info('Done initializing PKC playback, starting Kodi player') LOG.debug('Done initializing PKC playback, starting Kodi player')
thread.start() thread.start()
@ -563,8 +539,8 @@ def play_xml(playqueue, xml, offset=None, start_plex_id=None):
Either supply the ratingKey of the starting Plex element. Or set Either supply the ratingKey of the starting Plex element. Or set
playqueue.selectedItemID playqueue.selectedItemID
""" """
offset = int(offset) if offset else None offset = int(offset) / 1000 if offset else None
LOG.info("play_xml called with offset %s, start_plex_id %s", LOG.debug("play_xml called with offset %s, start_plex_id %s",
offset, start_plex_id) offset, start_plex_id)
start_item = start_plex_id if start_plex_id is not None \ start_item = start_plex_id if start_plex_id is not None \
else playqueue.selectedItemID else playqueue.selectedItemID
@ -581,21 +557,38 @@ def play_xml(playqueue, xml, offset=None, start_plex_id=None):
LOG.debug('Playqueue after play_xml update: %s', playqueue) LOG.debug('Playqueue after play_xml update: %s', playqueue)
thread = Thread(target=threaded_playback, thread = Thread(target=threaded_playback,
args=(playqueue.kodi_pl, startpos, offset)) args=(playqueue.kodi_pl, startpos, offset))
LOG.info('Done play_xml, starting Kodi player at position %s', startpos) LOG.debug('Done play_xml, starting Kodi player at position %s', startpos)
thread.start() thread.start()
def threaded_playback(kodi_playlist, startpos, offset): def threaded_playback(kodi_playlist, startpos, offset):
""" """
Seek immediately after kicking off playback is not reliable. Seek immediately after kicking off playback is not reliable. We even seek
to 0 (starting position) in case Kodi wants to resume but we want to start
over.
offset: resume position in seconds [int/float]
""" """
LOG.debug('threaded_playback with startpos %s, offset %s',
startpos, offset)
app.APP.player.play(kodi_playlist, None, False, startpos) app.APP.player.play(kodi_playlist, None, False, startpos)
if offset and offset != '0': if offset:
i = 0 i = 0
while not app.APP.is_playing or not js.get_player_ids(): while not app.APP.is_playing or not js.get_player_ids():
app.APP.monitor.waitForAbort(0.1) app.APP.monitor.waitForAbort(0.1)
i += 1 i += 1
if i > 100: if i > 200:
LOG.error('Could not seek to %s', offset) LOG.error('Could not seek to %s', offset)
return return
js.seek_to(int(offset)) i = 0
answ = js.seek_to(offset * 1000)
while 'error' in answ:
# Kodi sometimes returns {u'message': u'Failed to execute method.',
# u'code': -32100} if user quickly switches videos
i += 1
if i > 10:
LOG.error('Failed to seek to %s', offset)
return
app.APP.monitor.waitForAbort(0.1)
answ = js.seek_to(offset * 1000)
LOG.debug('Seek to offset %s successful', offset)

View file

@ -37,10 +37,15 @@ class PlaybackTask(backgroundthread.Task):
resolve = False if params.get('handle') == '-1' else True resolve = False if params.get('handle') == '-1' else True
LOG.debug('Received mode: %s, params: %s', mode, params) LOG.debug('Received mode: %s, params: %s', mode, params)
if mode == 'play': if mode == 'play':
if params.get('resume'):
resume = params.get('resume') == '1'
else:
resume = None
playback.playback_triage(plex_id=params.get('plex_id'), playback.playback_triage(plex_id=params.get('plex_id'),
plex_type=params.get('plex_type'), plex_type=params.get('plex_type'),
path=params.get('path'), path=params.get('path'),
resolve=resolve) resolve=resolve,
resume=resume)
elif mode == 'plex_node': elif mode == 'plex_node':
playback.process_indirect(params['key'], playback.process_indirect(params['key'],
params['offset'], params['offset'],

View file

@ -213,6 +213,7 @@ class PlaylistItem(object):
"'guid': '{self.guid}', " "'guid': '{self.guid}', "
"'playmethod': '{self.playmethod}', " "'playmethod': '{self.playmethod}', "
"'playcount': {self.playcount}, " "'playcount': {self.playcount}, "
"'resume': {self.resume},"
"'offset': {self.offset}, " "'offset': {self.offset}, "
"'force_transcode': {self.force_transcode}, " "'force_transcode': {self.force_transcode}, "
"'part': {self.part}".format(self=self)) "'part': {self.part}".format(self=self))

View file

@ -605,11 +605,16 @@ class Base(object):
% (v.ADDON_ID, url, v.PLEX_TYPE_CLIP)) % (v.ADDON_ID, url, v.PLEX_TYPE_CLIP))
return url return url
def listitem(self, listitem=xbmcgui.ListItem): def listitem(self, listitem=xbmcgui.ListItem, resume=True):
""" """
Returns a xbmcgui.ListItem() (or PKCListItem) for this Plex element Returns a xbmcgui.ListItem() (or PKCListItem) for this Plex element
Pass resume=False in order to NOT set a resume point (but let Kodi
automatically handle it)
""" """
item = widgets.generate_item(self) item = widgets.generate_item(self)
if not resume and 'resume' in item:
del item['resume']
item = widgets.prepare_listitem(item) item = widgets.prepare_listitem(item)
return widgets.create_listitem(item, as_tuple=False, listitem=listitem) return widgets.create_listitem(item, as_tuple=False, listitem=listitem)

View file

@ -151,11 +151,10 @@ class PlexCompanion(backgroundthread.KillableThread):
playback.play_xml(playqueue, xml, offset) playback.play_xml(playqueue, xml, offset)
else: else:
app.CONN.plex_transient_token = data.get('token') app.CONN.plex_transient_token = data.get('token')
if data.get('offset') != '0':
app.PLAYSTATE.resume_playback = True
playback.playback_triage(api.plex_id, playback.playback_triage(api.plex_id,
api.plex_type, api.plex_type,
resolve=False) resolve=False,
resume=data.get('offset') not in ('0', None))
@staticmethod @staticmethod
def _process_node(data): def _process_node(data):

View file

@ -63,25 +63,21 @@ def kodi_now():
def millis_to_kodi_time(milliseconds): def millis_to_kodi_time(milliseconds):
""" """
Converts time in milliseconds to the time dict used by the Kodi JSON RPC: Converts time in milliseconds [int or float] to the time dict used by the
Kodi JSON RPC:
{ {
'hours': [int], 'hours': [int],
'minutes': [int], 'minutes': [int],
'seconds'[int], 'seconds'[int],
'milliseconds': [int] 'milliseconds': [int]
} }
Pass in the time in milliseconds as an int
""" """
seconds = int(milliseconds / 1000) seconds = int(milliseconds / 1000)
minutes = int(seconds / 60) minutes = int(seconds / 60)
seconds = seconds % 60 return {'hours': int(minutes / 60),
hours = int(minutes / 60) 'minutes': int(minutes % 60),
minutes = minutes % 60 'seconds': int(seconds % 60),
milliseconds = milliseconds % 1000 'milliseconds': int(milliseconds % 1000)}
return {'hours': hours,
'minutes': minutes,
'seconds': seconds,
'milliseconds': milliseconds}
def kodi_time_to_millis(time): def kodi_time_to_millis(time):