Merge pull request #868 from croneter/beta-version

Bump master
This commit is contained in:
croneter 2019-06-04 20:18:24 +02:00 committed by GitHub
commit 00f69d8568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 235 additions and 110 deletions

View file

@ -1,5 +1,5 @@
[![stable version](https://img.shields.io/badge/stable_version-2.7.14-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) [![stable version](https://img.shields.io/badge/stable_version-2.8.0-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip)
[![beta version](https://img.shields.io/badge/beta_version-2.7.14-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) [![beta version](https://img.shields.io/badge/beta_version-2.8.0-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip)
[![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation)
[![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq)

View file

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.7.14" provider-name="croneter"> <addon id="plugin.video.plexkodiconnect" name="PlexKodiConnect" version="2.8.0" provider-name="croneter">
<requires> <requires>
<import addon="xbmc.python" version="2.1.0"/> <import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="2.9.1" /> <import addon="script.module.requests" version="2.9.1" />
<import addon="script.module.defusedxml" version="0.5.0"/> <import addon="script.module.defusedxml" version="0.5.0"/>
<import addon="plugin.video.plexkodiconnect.movies" version="2.0.9" /> <import addon="plugin.video.plexkodiconnect.movies" version="2.1.1" />
<import addon="plugin.video.plexkodiconnect.tvshows" version="2.0.10" /> <import addon="plugin.video.plexkodiconnect.tvshows" version="2.1.1" />
</requires> </requires>
<extension point="xbmc.python.pluginsource" library="default.py"> <extension point="xbmc.python.pluginsource" library="default.py">
<provides>video audio image</provides> <provides>video audio image</provides>
@ -77,7 +77,28 @@
<summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary> <summary lang="uk_UA">Нативна інтеграція Plex в Kodi</summary>
<description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description> <description lang="uk_UA">Підключає Kodi до серверу Plex. Цей плагін передбачає, що ви керуєте всіма своїми відео за допомогою Plex (і ніяк не Kodi). Ви можете втратити дані, які вже зберігаються у відео та музичних БД Kodi (оскільки цей плагін безпосередньо їх змінює). Використовуйте на свій страх і ризик!</description>
<disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer> <disclaimer lang="uk_UA">Використовуйте на свій ризик</disclaimer>
<news>version 2.7.14: <news>version 2.8.0:
- Finally fix Kodi crashing on playback startup for add-on paths!
- All the good stuff from 2.7.15-2.7.18 for everyone
version 2.7.18 (beta only):
- Fix Kodi always playing the same file version of a video if several are present
- Also play trailers if user chose to resume movie from the beginning
- Ask user whether to resume if using Direct Paths and user initiated playback via PMS
- Fix video thrown by Plex Companion not resuming
version 2.7.17 (beta only):
- Another attempt to keep Kodi from crashing on playback startup
version 2.7.16 (beta only):
- Hopefully fix Kodi crashing on playback startup for good
version 2.7.15 (beta only):
- Hopefully fix Kodi crashing on playback startup
- Refresh widgets only on homescreen to prevent cursor from jumping within libraries
- Don't refresh container when user chose to delete or refresh an item from the context menu
version 2.7.14:
- Correctly clear window variables e.g. on user switch - Correctly clear window variables e.g. on user switch
- Reload skin on resetting PKC video nodes - Reload skin on resetting PKC video nodes
- Fix last-played node value to ensure a playcount greater than zero - Fix last-played node value to ensure a playcount greater than zero

View file

@ -1,3 +1,24 @@
version 2.8.0:
- Finally fix Kodi crashing on playback startup for add-on paths!
- All the good stuff from 2.7.15-2.7.18 for everyone
version 2.7.18 (beta only):
- Fix Kodi always playing the same file version of a video if several are present
- Also play trailers if user chose to resume movie from the beginning
- Ask user whether to resume if using Direct Paths and user initiated playback via PMS
- Fix video thrown by Plex Companion not resuming
version 2.7.17 (beta only):
- Another attempt to keep Kodi from crashing on playback startup
version 2.7.16 (beta only):
- Hopefully fix Kodi crashing on playback startup for good
version 2.7.15 (beta only):
- Hopefully fix Kodi crashing on playback startup
- Refresh widgets only on homescreen to prevent cursor from jumping within libraries
- Don't refresh container when user chose to delete or refresh an item from the context menu
version 2.7.14: version 2.7.14:
- Correctly clear window variables e.g. on user switch - Correctly clear window variables e.g. on user switch
- Reload skin on resetting PKC video nodes - Reload skin on resetting PKC video nodes

View file

@ -157,17 +157,11 @@ class Main():
# Handle -1 received, not waiting for main thread # Handle -1 received, not waiting for main thread
return return
# Wait for the result from the main PKC thread # Wait for the result from the main PKC thread
result = transfer.wait_for_transfer() result = transfer.wait_for_transfer(source='main')
if result is None: if result is True:
LOG.error('Error encountered, aborting')
utils.dialog('notification',
heading='{plex}',
message=utils.lang(30128),
icon='{error}',
time=3000)
xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem()) xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem())
elif result is True: # Tell main thread that we're done
pass transfer.send(True, target='main')
else: else:
# Received a xbmcgui.ListItem() # Received a xbmcgui.ListItem()
xbmcplugin.setResolvedUrl(HANDLE, True, result) xbmcplugin.setResolvedUrl(HANDLE, True, result)

View file

@ -27,6 +27,8 @@ class App(object):
self.stop_pkc = False self.stop_pkc = False
# This will suspend the main thread also # This will suspend the main thread also
self.suspend = False self.suspend = False
# Update Kodi widgets
self.update_widgets = False
# Need to lock all methods and functions messing with Plex Companion subscribers # Need to lock all methods and functions messing with Plex Companion subscribers
self.lock_subscriber = RLock() self.lock_subscriber = RLock()
# Need to lock everything messing with Kodi/PKC playqueues # Need to lock everything messing with Kodi/PKC playqueues

View file

@ -55,7 +55,10 @@ class PlayState(object):
# Set by SpecialMonitor - did user choose to resume playback or start from the # Set by SpecialMonitor - did user choose to resume playback or start from the
# beginning? # beginning?
self.resume_playback = False # Set to None if resume dialog has not been shown
# True if dialog has been shown and user selected to resume
# False if dialog has been shown and user chose to start from beginning
self.resume_playback = None
# Was the playback initiated by the user using the Kodi context menu? # Was the playback initiated by the user using the Kodi context menu?
self.context_menu_play = False self.context_menu_play = False
# Set by context menu - shall we force-transcode the next playing item? # Set by context menu - shall we force-transcode the next playing item?

View file

@ -60,11 +60,6 @@ class ContextMenu(object):
self.api = API(xml[0]) self.api = API(xml[0])
if self._select_menu(): if self._select_menu():
self._action_menu() self._action_menu()
if self._selected_option in (OPTIONS['Delete'],
OPTIONS['Refresh']):
LOG.info("refreshing container")
app.APP.monitor.waitForAbort(0.5)
xbmc.executebuiltin('Container.Refresh')
@staticmethod @staticmethod
def _get_plex_id(kodi_id, kodi_type): def _get_plex_id(kodi_id, kodi_type):
@ -147,7 +142,7 @@ class ContextMenu(object):
v.KODI_PLAYLIST_TYPE_FROM_KODI_TYPE[self.kodi_type]) v.KODI_PLAYLIST_TYPE_FROM_KODI_TYPE[self.kodi_type])
playqueue.clear() playqueue.clear()
app.PLAYSTATE.context_menu_play = True app.PLAYSTATE.context_menu_play = True
handle = self.api.path(force_first_media=False, force_addon=True) handle = self.api.path(force_addon=True)
handle = 'RunPlugin(%s)' % handle handle = 'RunPlugin(%s)' % handle
xbmc.executebuiltin(handle.encode('utf-8')) xbmc.executebuiltin(handle.encode('utf-8'))

View file

@ -170,10 +170,10 @@ def stop():
def seek_to(offset): def seek_to(offset):
""" """
Seeks all Kodi players to offset [int] Seeks all Kodi players to offset [int] in milliseconds
""" """
for playerid in get_player_ids(): for playerid in get_player_ids():
JsonRPC("Player.Seek").execute( return JsonRPC("Player.Seek").execute(
{"playerid": playerid, {"playerid": playerid,
"value": timing.millis_to_kodi_time(offset)}) "value": timing.millis_to_kodi_time(offset)})

View file

@ -3,7 +3,7 @@
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
import xbmc import xbmc
from .. import utils, variables as v from .. import utils, app, variables as v
PLAYLIST_SYNC_ENABLED = (v.DEVICE != 'Microsoft UWP' and PLAYLIST_SYNC_ENABLED = (v.DEVICE != 'Microsoft UWP' and
utils.settings('enablePlaylistSync') == 'true') utils.settings('enablePlaylistSync') == 'true')
@ -42,7 +42,12 @@ def update_kodi_library(video=True, music=True):
Updates the Kodi library and thus refreshes the Kodi views and widgets Updates the Kodi library and thus refreshes the Kodi views and widgets
""" """
if video: if video:
xbmc.executebuiltin('UpdateLibrary(video)') if not xbmc.getCondVisibility('Window.IsMedia'):
xbmc.executebuiltin('UpdateLibrary(video)')
else:
# Prevent cursor from moving - refresh later
xbmc.executebuiltin('Container.Refresh')
app.APP.update_widgets = True
if music: if music:
xbmc.executebuiltin('UpdateLibrary(music)') xbmc.executebuiltin('UpdateLibrary(music)')

View file

@ -6,6 +6,9 @@ Used to kick off Kodi playback
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from threading import Thread from threading import Thread
import datetime
import xbmc
from .plex_api import API from .plex_api import API
from .plex_db import PlexDB from .plex_db import PlexDB
@ -43,6 +46,17 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
the first pass - e.g. if you're calling this function from the original the first pass - e.g. if you're calling this function from the original
service.py Python instance service.py Python instance
""" """
try:
_playback_triage(plex_id, plex_type, path, resolve)
finally:
# Reset some playback variables the user potentially set to init
# playback
app.PLAYSTATE.context_menu_play = False
app.PLAYSTATE.force_transcode = False
app.PLAYSTATE.resume_playback = None
def _playback_triage(plex_id, plex_type, path, resolve):
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.info('playback_triage called with plex_id %s, plex_type %s, path %s, '
'resolve %s', plex_id, plex_type, path, resolve) 'resolve %s', plex_id, plex_type, path, resolve)
@ -118,6 +132,9 @@ def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True):
initiate = True initiate = True
else: else:
initiate = False initiate = False
if not initiate and app.PLAYSTATE.resume_playback is not None:
LOG.debug('Detected re-playing of the same item')
initiate = True
if initiate: if initiate:
_playback_init(plex_id, plex_type, playqueue, pos) _playback_init(plex_id, plex_type, playqueue, pos)
else: else:
@ -163,22 +180,26 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
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.info('Initializing PKC playback')
# Stop playback so we don't get an error message that the last item of the
# queue failed to play
app.APP.player.stop()
xml = PF.GetPlexMetadata(plex_id, reraise=True) xml = PF.GetPlexMetadata(plex_id, reraise=True)
if xml in (None, 401): if xml in (None, 401):
LOG.error('Could not get a PMS xml for plex id %s', plex_id) LOG.error('Could not get a PMS xml for plex id %s', plex_id)
_ensure_resolve(abort=True) _ensure_resolve(abort=True)
return return
if playqueue.kodi_pl.size() > 1: if (xbmc.getCondVisibility('Window.IsVisible(Home.xml)') and
plex_type in v.PLEX_VIDEOTYPES and
playqueue.kodi_pl.size() > 1):
# playqueue.kodi_pl.size() could return more than one - since playback
# was initiated from the audio queue!
LOG.debug('Detected widget playback for videos')
elif playqueue.kodi_pl.size() > 1:
# Special case - we already got a filled Kodi playqueue # Special case - we already got a filled Kodi playqueue
try: try:
_init_existing_kodi_playlist(playqueue, pos) _init_existing_kodi_playlist(playqueue, pos)
except PL.PlaylistError: except PL.PlaylistError:
LOG.error('Playback_init for existing Kodi playlist failed') LOG.error('Playback_init for existing Kodi playlist failed')
# "Play error"
utils.dialog('notification',
utils.lang(29999),
utils.lang(30128),
icon='{error}')
_ensure_resolve(abort=True) _ensure_resolve(abort=True)
return return
# Now we need to use setResolvedUrl for the item at position ZERO # Now we need to use setResolvedUrl for the item at position ZERO
@ -187,19 +208,29 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
return return
# "Usual" case - consider trailers and parts and build both Kodi and Plex # "Usual" case - consider trailers and parts and build both Kodi and Plex
# playqueues # playqueues
# Pass dummy PKC video with 0 length so Kodi immediately stops playback # Release default.py
# and we can build our own playqueue.
_ensure_resolve() _ensure_resolve()
api = API(xml[0]) api = API(xml[0])
if app.SYNC.direct_paths and api.resume_point():
# 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 (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and if (not resume and plex_type == v.PLEX_TYPE_MOVIE and
utils.settings('enableCinema') == "true"): utils.settings('enableCinema') == "true"):
if utils.settings('askCinema') == "true": if utils.settings('askCinema') == "true":
# "Play trailers?" # "Play trailers?"
trailers = utils.yesno_dialog(utils.lang(29999), utils.lang(33016)) trailers = utils.yesno_dialog(utils.lang(29999), utils.lang(33016))
else: else:
trailers = True trailers = True
LOG.debug('Playing trailers: %s', trailers) LOG.debug('Resuming: %s. Playing trailers: %s', resume, trailers)
playqueue.clear() playqueue.clear()
if plex_type != v.PLEX_TYPE_CLIP: if plex_type != v.PLEX_TYPE_CLIP:
# Post to the PMS to create a playqueue - in any case due to Companion # Post to the PMS to create a playqueue - in any case due to Companion
@ -216,25 +247,15 @@ def _playback_init(plex_id, plex_type, playqueue, pos):
utils.lang(30128), utils.lang(30128),
icon='{error}') icon='{error}')
# Do NOT use _ensure_resolve() because we resolved above already # Do NOT use _ensure_resolve() because we resolved above already
app.PLAYSTATE.context_menu_play = False
app.PLAYSTATE.force_transcode = False
app.PLAYSTATE.resume_playback = False
return return
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, resume)
_process_stack(playqueue, stack) _process_stack(playqueue, stack)
# Always resume if playback initiated via PMS and there IS a resume
# point
offset = api.resume_point() * 1000 if app.PLAYSTATE.context_menu_play else None
# Reset some playback variables
app.PLAYSTATE.context_menu_play = False
app.PLAYSTATE.force_transcode = False
# 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, offset)) args=(playqueue.kodi_pl, pos, None))
thread.setDaemon(True) thread.setDaemon(True)
LOG.info('Done initializing playback, starting Kodi player at pos %s and ' LOG.info('Done initializing playback, starting Kodi player at pos %s', pos)
'resume point %s', pos, offset)
# 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
@ -258,13 +279,30 @@ def _ensure_resolve(abort=False):
if RESOLVE: if RESOLVE:
# Releases the other Python thread without a ListItem # Releases the other Python thread without a ListItem
transfer.send(True) transfer.send(True)
# Shows PKC error message # Wait for default.py to have completed xbmcplugin.setResolvedUrl()
# transfer.send(None) transfer.wait_for_transfer(source='default')
if abort: if abort:
# Reset some playback variables utils.dialog('notification',
app.PLAYSTATE.context_menu_play = False heading='{plex}',
app.PLAYSTATE.force_transcode = False message=utils.lang(30128),
app.PLAYSTATE.resume_playback = False icon='{error}',
time=3000)
def resume_dialog(resume):
"""
Pass the resume [int] point in seconds. Returns True if user chose to
resume. Returns None if user cancelled
"""
# "Resume from {0:s}"
# "Start from beginning"
resume = datetime.timedelta(seconds=resume)
answ = utils.dialog('contextmenu',
[utils.lang(12022).replace('{0:s}', '{0}').format(unicode(resume)),
utils.lang(12021)])
if answ == -1:
return
return answ == 0
def _init_existing_kodi_playlist(playqueue, pos): def _init_existing_kodi_playlist(playqueue, pos):
@ -285,9 +323,13 @@ def _init_existing_kodi_playlist(playqueue, pos):
LOG.debug('Done init_existing_kodi_playlist') LOG.debug('Done init_existing_kodi_playlist')
def _prep_playlist_stack(xml): def _prep_playlist_stack(xml, resume):
"""
resume [bool] will set the resume point of the LAST item of the stack, for
part 1 only
"""
stack = [] stack = []
for item in xml: for i, item in enumerate(xml):
api = API(item) api = API(item)
if (app.PLAYSTATE.context_menu_play is False and if (app.PLAYSTATE.context_menu_play is False and
api.plex_type() not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)): api.plex_type() not in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_EPISODE)):
@ -310,9 +352,17 @@ def _prep_playlist_stack(xml):
api.set_part_number(part) api.set_part_number(part)
if kodi_id is None: if kodi_id is None:
# Need to redirect again to PKC to conclude playback # Need to redirect again to PKC to conclude playback
path = api.path() path = api.path(force_addon=True, force_first_media=True)
# Using different paths than the ones saved in the Kodi DB
# fixes Kodi immediately resuming the video if one restarts
# the same video again after playback
# WARNING: This fixes startup, but renders Kodi unstable
# path = path.replace('plugin.video.plexkodiconnect.tvshows',
# 'plugin.video.plexkodiconnect', 1)
# path = path.replace('plugin.video.plexkodiconnect.movies',
# 'plugin.video.plexkodiconnect', 1)
listitem = api.create_listitem() listitem = api.create_listitem()
listitem.setPath(utils.try_encode(path)) listitem.setPath(path.encode('utf-8'))
else: else:
# Will add directly via the Kodi DB # Will add directly via the Kodi DB
path = None path = None
@ -326,6 +376,7 @@ def _prep_playlist_stack(xml):
'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,
'id': api.item_id() 'id': api.item_id()
}) })
return stack return stack
@ -358,9 +409,39 @@ def _process_stack(playqueue, stack):
playlist_item.part = item['part'] playlist_item.part = item['part']
playlist_item.id = item['id'] playlist_item.id = item['id']
playlist_item.force_transcode = app.PLAYSTATE.force_transcode playlist_item.force_transcode = app.PLAYSTATE.force_transcode
playlist_item.resume = item['resume']
pos += 1 pos += 1
def _set_resume(listitem, item, api):
if item.plex_type in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP):
return
if item.resume is True:
# Do NOT use item.offset directly but get it from the DB
# (user might have initiated same video twice)
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(item.plex_id, item.plex_type)
if db_item:
file_id = db_item['kodi_fileid']
with KodiVideoDB(lock=False) as kodidb:
item.offset = kodidb.get_resume(file_id)
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:
listitem.setProperty('StartOffset', str(item.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):
""" """
ONLY if actually being played (e.g. at 5th position of a playqueue). ONLY if actually being played (e.g. at 5th position of a playqueue).
@ -391,40 +472,14 @@ def _conclude_playback(playqueue, pos):
playurl = item.file playurl = item.file
if not playurl: if not playurl:
LOG.info('Did not get a playurl, aborting playback silently') LOG.info('Did not get a playurl, aborting playback silently')
app.PLAYSTATE.resume_playback = False _ensure_resolve(abort=True)
transfer.send(True)
return return
listitem.setPath(utils.try_encode(playurl)) listitem.setPath(playurl.encode('utf-8'))
if item.playmethod == 'DirectStream': if item.playmethod == 'DirectStream':
listitem.setSubtitles(api.cache_external_subs()) listitem.setSubtitles(api.cache_external_subs())
elif item.playmethod == 'Transcode': elif item.playmethod == 'Transcode':
playutils.audio_subtitle_prefs(listitem) playutils.audio_subtitle_prefs(listitem)
_set_resume(listitem, item, api)
if app.PLAYSTATE.resume_playback is True:
app.PLAYSTATE.resume_playback = False
if item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP):
# Do NOT use item.offset directly but get it from the DB
# (user might have initiated same video twice)
with PlexDB(lock=False) as plexdb:
db_item = plexdb.item_by_id(item.plex_id, item.plex_type)
file_id = db_item['kodi_fileid'] if db_item else None
with KodiVideoDB(lock=False) as kodidb:
item.offset = kodidb.get_resume(file_id)
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:
listitem.setProperty('StartOffset', str(item.offset))
listitem.setProperty('resumetime', str(item.offset))
elif v.KODIVERSION >= 18:
listitem.setProperty('StartPercent', '0')
# Reset the resumable flag
transfer.send(listitem) transfer.send(listitem)
LOG.info('Done concluding playback') LOG.info('Done concluding playback')
@ -512,23 +567,22 @@ 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
LOG.info("play_xml called with offset %s, start_plex_id %s", LOG.info("play_xml called with offset %s, start_plex_id %s",
offset, start_plex_id) offset, start_plex_id)
stack = _prep_playlist_stack(xml) start_item = start_plex_id if start_plex_id is not None \
else playqueue.selectedItemID
for startpos, video in enumerate(xml):
api = API(video)
if api.plex_id() == start_item:
break
else:
startpos = 0
stack = _prep_playlist_stack(xml, resume=False)
if offset:
stack[startpos]['resume'] = True
_process_stack(playqueue, stack) _process_stack(playqueue, stack)
LOG.debug('Playqueue after play_xml update: %s', playqueue) LOG.debug('Playqueue after play_xml update: %s', playqueue)
if start_plex_id is not None:
for startpos, item in enumerate(playqueue.items):
if item.plex_id == start_plex_id:
break
else:
startpos = 0
else:
for startpos, item in enumerate(playqueue.items):
if item.id == playqueue.selectedItemID:
break
else:
startpos = 0
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.info('Done play_xml, starting Kodi player at position %s', startpos)
@ -542,7 +596,7 @@ def threaded_playback(kodi_playlist, 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 and offset != '0':
i = 0 i = 0
while not app.APP.is_playing: 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 > 100:

View file

@ -29,6 +29,8 @@ class PlaybackTask(backgroundthread.Task):
# E.g. other add-ons scanning for Extras folder # E.g. other add-ons scanning for Extras folder
LOG.debug('Detected 3rd party add-on call - ignoring') LOG.debug('Detected 3rd party add-on call - ignoring')
transfer.send(True) transfer.send(True)
# Wait for default.py to have completed xbmcplugin.setResolvedUrl()
transfer.wait_for_transfer(source='default')
return return
params = dict(utils.parse_qsl(params)) params = dict(utils.parse_qsl(params))
mode = params.get('mode') mode = params.get('mode')

View file

@ -167,6 +167,11 @@ class Playlist_Item(object):
# If Plex video consists of several parts; part number # If Plex video consists of several parts; part number
self._part = 0 self._part = 0
self.force_transcode = False self.force_transcode = False
# Shall we ask user to resume this item?
# None: ask user to resume
# False: do NOT resume, don't ask user
# True: do resume, don't ask user
self.resume = None
@property @property
def plex_id(self): def plex_id(self):

View file

@ -1388,6 +1388,7 @@ class API(object):
option = cast(str, option.strip()) option = cast(str, option.strip())
dialoglist.append(option) dialoglist.append(option)
media = utils.dialog('select', 'Select stream', dialoglist) media = utils.dialog('select', 'Select stream', dialoglist)
LOG.info('User chose media stream number: %s', media)
if media == -1: if media == -1:
LOG.info('User cancelled media stream selection') LOG.info('User cancelled media stream selection')
return return

View file

@ -486,6 +486,8 @@ class Service(object):
elif plex_command == 'EXIT-PKC': elif plex_command == 'EXIT-PKC':
LOG.info('Received command from another instance to quit') LOG.info('Received command from another instance to quit')
app.APP.stop_pkc = True app.APP.stop_pkc = True
else:
raise RuntimeError('Unknown command: %s', plex_command)
if task: if task:
backgroundthread.BGThreader.addTasksToFront([task]) backgroundthread.BGThreader.addTasksToFront([task])
continue continue
@ -494,6 +496,15 @@ class Service(object):
xbmc.sleep(100) xbmc.sleep(100)
continue continue
if app.APP.update_widgets and not xbmc.getCondVisibility('Window.IsMedia'):
'''
In case an update happened but we were not on the homescreen
and now we are, force widgets to update. Prevents cursor from
moving/jumping in libraries
'''
app.APP.update_widgets = False
xbmc.executebuiltin('UpdateLibrary(video)')
# Before proceeding, need to make sure: # Before proceeding, need to make sure:
# 1. Server is online # 1. Server is online
# 2. User is set # 2. User is set

View file

@ -14,7 +14,8 @@ import xbmcgui
LOG = getLogger('PLEX.transfer') LOG = getLogger('PLEX.transfer')
MONITOR = xbmc.Monitor() MONITOR = xbmc.Monitor()
WINDOW = xbmcgui.Window(10000) WINDOW = xbmcgui.Window(10000)
WINDOW_RESULT = 'plexkodiconnect.result'.encode('utf-8') WINDOW_UPSTREAM = 'plexkodiconnect.result.upstream'.encode('utf-8')
WINDOW_DOWNSTREAM = 'plexkodiconnect.result.downstream'.encode('utf-8')
WINDOW_COMMAND = 'plexkodiconnect.command'.encode('utf-8') WINDOW_COMMAND = 'plexkodiconnect.command'.encode('utf-8')
@ -90,7 +91,7 @@ def de_serialize(answ):
raise NotImplementedError('Not implemented: %s' % answ) raise NotImplementedError('Not implemented: %s' % answ)
def send(pkc_listitem): def send(pkc_listitem, target='default'):
""" """
Pickles the obj to the window variable. Use to transfer Python Pickles the obj to the window variable. Use to transfer Python
objects between different PKC python instances (e.g. if default.py is objects between different PKC python instances (e.g. if default.py is
@ -98,18 +99,27 @@ def send(pkc_listitem):
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
Set target='default' if you send data TO another Python default.py
instance, 'main' if your default.py needs to send to the main thread
""" """
window = WINDOW_DOWNSTREAM if target == 'default' else WINDOW_UPSTREAM
LOG.debug('Sending: %s', pkc_listitem) LOG.debug('Sending: %s', pkc_listitem)
kodi_window(WINDOW_RESULT, kodi_window(window,
value=json.dumps(serialize(pkc_listitem))) value=json.dumps(serialize(pkc_listitem)))
def wait_for_transfer(): def wait_for_transfer(source='main'):
"""
Set source='default' if you wait for data FROM another Python default.py
instance, 'main' if your default.py needs to wait for the main thread
"""
window = WINDOW_DOWNSTREAM if source == 'main' else WINDOW_UPSTREAM
result = '' result = ''
while not result: while not result:
result = kodi_window(WINDOW_RESULT) result = kodi_window(window)
if result: if result:
kodi_window(WINDOW_RESULT, clear=True) kodi_window(window, clear=True)
LOG.debug('Received') LOG.debug('Received')
result = json.loads(result) result = json.loads(result)
return de_serialize(result) return de_serialize(result)

View file

@ -211,7 +211,8 @@ def dialog(typus, *args, **kwargs):
'notification': dia.notification, 'notification': dia.notification,
'input': dia.input, 'input': dia.input,
'select': dia.select, 'select': dia.select,
'numeric': dia.numeric 'numeric': dia.numeric,
'contextmenu': dia.contextmenu
} }
return types[typus](*args, **kwargs) return types[typus](*args, **kwargs)