Reintroduce Kodi playlist polling

There is no way around it - Kodi does not tell if the user swaps items in the Kodi playlist, unfortunately
This commit is contained in:
croneter 2018-02-04 12:06:39 +01:00
parent 199939c8b7
commit 3174521475
4 changed files with 99 additions and 111 deletions

View file

@ -69,7 +69,7 @@ class KodiMonitor(Monitor):
""" """
Will be called when Kodi starts scanning the library Will be called when Kodi starts scanning the library
""" """
LOG.debug("Kodi library scan %s running." % library) LOG.debug("Kodi library scan %s running.", library)
if library == "video": if library == "video":
window('plex_kodiScan', value="true") window('plex_kodiScan', value="true")
@ -77,7 +77,7 @@ class KodiMonitor(Monitor):
""" """
Will be called when Kodi finished scanning the library Will be called when Kodi finished scanning the library
""" """
LOG.debug("Kodi library scan %s finished." % library) LOG.debug("Kodi library scan %s finished.", library)
if library == "video": if library == "video":
window('plex_kodiScan', clear=True) window('plex_kodiScan', clear=True)
@ -209,11 +209,6 @@ class KodiMonitor(Monitor):
} }
Will NOT be called if playback initiated by Kodi widgets Will NOT be called if playback initiated by Kodi widgets
""" """
playqueue = PQ.PLAYQUEUES[data['playlistid']]
# Did PKC cause this add? Then lets not do anything
if playqueue.is_kodi_onadd() is False:
LOG.debug('PKC added this item to the playqueue - ignoring')
return
kodi_item = js.get_item(data['playlistid']) kodi_item = js.get_item(data['playlistid'])
if (state.RESUMABLE is True and not kodi_item['file'] and if (state.RESUMABLE is True and not kodi_item['file'] and
data['position'] == 0 and data['position'] == 0 and
@ -233,22 +228,7 @@ class KodiMonitor(Monitor):
thread = Thread(target=playback_triage, kwargs=kwargs) thread = Thread(target=playback_triage, kwargs=kwargs)
thread.start() thread.start()
return return
# Have we initiated the playqueue already? If not, ignore this
if not playqueue.items:
LOG.debug('Playqueue not initiated - ignoring')
return
# Playlist has been updated; need to tell Plex about it
try:
if playqueue.id is None:
PL.init_Plex_playlist(playqueue, kodi_item=data['item'])
else:
PL.add_item_to_PMS_playlist(playqueue,
data['position'],
kodi_item=data['item'])
except PL.PlaylistError:
pass
@LOCKER.lockthis
def _playlist_onremove(self, data): def _playlist_onremove(self, data):
""" """
Called if an item is removed from a Kodi playlist. Example data dict: Called if an item is removed from a Kodi playlist. Example data dict:
@ -257,14 +237,8 @@ class KodiMonitor(Monitor):
u'position': 0 u'position': 0
} }
""" """
playqueue = PQ.PLAYQUEUES[data['playlistid']] pass
# Did PKC cause this add? Then lets not do anything
if playqueue.is_kodi_onremove() is False:
LOG.debug('PKC removed this item already from playqueue - ignoring')
return
PL.delete_playlist_item_from_PMS(playqueue, data['position'])
@LOCKER.lockthis
def _playlist_onclear(self, data): def _playlist_onclear(self, data):
""" """
Called if a Kodi playlist is cleared. Example data dict: Called if a Kodi playlist is cleared. Example data dict:
@ -272,11 +246,7 @@ class KodiMonitor(Monitor):
u'playlistid': 1, u'playlistid': 1,
} }
""" """
playqueue = PQ.PLAYQUEUES[data['playlistid']] pass
if playqueue.is_kodi_onclear() is False:
LOG.debug('PKC already cleared the playqueue - ignoring')
return
playqueue.clear(kodi=False)
def _get_ids(self, json_item): def _get_ids(self, json_item):
""" """
@ -399,9 +369,6 @@ class KodiMonitor(Monitor):
LOG.info('Could not initialize our playlist') LOG.info('Could not initialize our playlist')
# Avoid errors # Avoid errors
item = PL.Playlist_Item() item = PL.Playlist_Item()
# Make sure we've added all items of the Kodi playqueue
if playqueue.kodi_pl.size() > 1:
self._add_remaining_items_to_playlist(playqueue)
# Set the Plex container key (e.g. using the Plex playqueue) # Set the Plex container key (e.g. using the Plex playqueue)
container_key = None container_key = None
if info['playlistid'] != -1: if info['playlistid'] != -1:

View file

@ -45,11 +45,8 @@ class PlaylistObjectBaseclase(object):
self.shuffled = 0 self.shuffled = 0
self.repeat = 0 self.repeat = 0
self.plex_transient_token = None self.plex_transient_token = None
# Needed to not add an item twice (first through PKC, then the kodi # Need a hack for detecting swaps of elements
# monitor) self.old_kodi_pl = []
self._onadd_queue = []
self._onremove_queue = []
self._onclear_queue = []
def __repr__(self): def __repr__(self):
""" """
@ -69,63 +66,6 @@ class PlaylistObjectBaseclase(object):
answ += '\'%s\': %s, ' % (key, str(getattr(self, key))) answ += '\'%s\': %s, ' % (key, str(getattr(self, key)))
return answ + '\'items\': %s}}' % self.items return answ + '\'items\': %s}}' % self.items
def kodi_onadd(self):
"""
Call this before adding an item to the Kodi playqueue
"""
self._onadd_queue.append(None)
def is_kodi_onadd(self):
"""
Returns False if the last kodimonitor on_add was caused by PKC - so that
we are not adding a playlist item twice.
Calling this function will remove the item from our "checklist"
"""
try:
self._onadd_queue.pop()
except IndexError:
return True
return False
def kodi_onremove(self):
"""
Call this before removing an item from the Kodi playqueue
"""
self._onremove_queue.append(None)
def is_kodi_onremove(self):
"""
Returns False if the last kodimonitor on_remove was caused by PKC - so
that we are not adding a playlist item twice.
Calling this function will remove the item from our "checklist"
"""
try:
self._onremove_queue.pop()
except IndexError:
return True
return False
def kodi_onclear(self):
"""
Call this before clearing the Kodi playqueue IF it was not empty
"""
self._onclear_queue.append(None)
def is_kodi_onclear(self):
"""
Returns False if the last kodimonitor on_remove was caused by PKC - so
that we are not clearing the playlist twice.
Calling this function will remove the item from our "checklist"
"""
try:
self._onclear_queue.pop()
except IndexError:
return True
return False
def clear(self, kodi=True): def clear(self, kodi=True):
""" """
Resets the playlist object to an empty playlist. Resets the playlist object to an empty playlist.
@ -135,7 +75,6 @@ class PlaylistObjectBaseclase(object):
# kodi monitor's on_clear method will only be called if there were some # kodi monitor's on_clear method will only be called if there were some
# items to begin with # items to begin with
if kodi and self.kodi_pl.size() != 0: if kodi and self.kodi_pl.size() != 0:
self.kodi_onclear()
self.kodi_pl.clear() # Clear Kodi playlist object self.kodi_pl.clear() # Clear Kodi playlist object
self.items = [] self.items = []
self.id = None self.id = None
@ -145,6 +84,7 @@ class PlaylistObjectBaseclase(object):
self.shuffled = 0 self.shuffled = 0
self.repeat = 0 self.repeat = 0
self.plex_transient_token = None self.plex_transient_token = None
self.old_kodi_pl = []
LOG.debug('Playlist cleared: %s', self) LOG.debug('Playlist cleared: %s', self)
@ -503,10 +443,8 @@ def add_item_to_playlist(playlist, pos, kodi_id=None, kodi_type=None,
params['item'] = {'%sid' % item.kodi_type: int(item.kodi_id)} params['item'] = {'%sid' % item.kodi_type: int(item.kodi_id)}
else: else:
params['item'] = {'file': item.file} params['item'] = {'file': item.file}
playlist.kodi_onadd()
reply = js.playlist_insert(params) reply = js.playlist_insert(params)
if reply.get('error') is not None: if reply.get('error') is not None:
playlist.is_kodi_onadd()
raise PlaylistError('Could not add item to playlist. Kodi reply. %s', raise PlaylistError('Could not add item to playlist. Kodi reply. %s',
reply) reply)
return item return item
@ -572,10 +510,8 @@ def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None,
params['item'] = {'%sid' % kodi_type: int(kodi_id)} params['item'] = {'%sid' % kodi_type: int(kodi_id)}
else: else:
params['item'] = {'file': file} params['item'] = {'file': file}
playlist.kodi_onadd()
reply = js.playlist_insert(params) reply = js.playlist_insert(params)
if reply.get('error') is not None: if reply.get('error') is not None:
playlist.is_kodi_onadd()
raise PlaylistError('Could not add item to playlist. Kodi reply. %s', raise PlaylistError('Could not add item to playlist. Kodi reply. %s',
reply) reply)
if xml_video_element is not None: if xml_video_element is not None:
@ -694,7 +630,6 @@ def add_listitem_to_Kodi_playlist(playlist, pos, listitem, file,
LOG.debug('Insert listitem at position %s for Kodi only for %s', LOG.debug('Insert listitem at position %s for Kodi only for %s',
pos, playlist) pos, playlist)
# Add the item into Kodi playlist # Add the item into Kodi playlist
playlist.kodi_onadd()
playlist.kodi_pl.add(url=file, listitem=listitem, index=pos) playlist.kodi_pl.add(url=file, listitem=listitem, index=pos)
# We need to add this to our internal queue as well # We need to add this to our internal queue as well
if xml_video_element is not None: if xml_video_element is not None:

View file

@ -2,14 +2,15 @@
Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
""" """
from logging import getLogger from logging import getLogger
from threading import RLock, Thread from threading import Thread
from xbmc import Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO from xbmc import Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO, sleep
from utils import window from utils import thread_methods
import playlist_func as PL import playlist_func as PL
from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren from PlexFunctions import GetAllPlexChildren
from PlexAPI import API from PlexAPI import API
from plexbmchelper.subscribers import LOCK
from playback import play_xml from playback import play_xml
import json_rpc as js import json_rpc as js
import variables as v import variables as v
@ -17,8 +18,6 @@ import variables as v
############################################################################### ###############################################################################
LOG = getLogger("PLEX." + __name__) LOG = getLogger("PLEX." + __name__)
# lock used for playqueue manipulations
LOCK = RLock()
PLUGIN = 'plugin://%s' % v.ADDON_ID PLUGIN = 'plugin://%s' % v.ADDON_ID
# Our PKC playqueues (3 instances of Playqueue_Object()) # Our PKC playqueues (3 instances of Playqueue_Object())
@ -125,3 +124,87 @@ def update_playqueue_from_PMS(playqueue,
playqueue.repeat = 0 if not repeat else int(repeat) playqueue.repeat = 0 if not repeat else int(repeat)
playqueue.plex_transient_token = transient_token playqueue.plex_transient_token = transient_token
play_xml(playqueue, xml, offset) play_xml(playqueue, xml, offset)
@thread_methods(add_suspends=['PMS_STATUS'])
class PlayqueueMonitor(Thread):
"""
Unfortunately, Kodi does not tell if items within a Kodi playqueue
(playlist) are swapped. This is what this monitor is for. Don't replace
this mechanism till Kodi's implementation of playlists has improved
"""
def _compare_playqueues(self, playqueue, new):
"""
Used to poll the Kodi playqueue and update the Plex playqueue if needed
"""
old = list(playqueue.items)
index = list(range(0, len(old)))
LOG.debug('Comparing new Kodi playqueue %s with our play queue %s',
new, old)
for i, new_item in enumerate(new):
if (new_item['file'].startswith('plugin://') and
not new_item['file'].startswith(PLUGIN)):
# Ignore new media added by other addons
continue
for j, old_item in enumerate(old):
if self.thread_stopped():
# Chances are that we got an empty Kodi playlist due to
# Kodi exit
return
try:
if (old_item.file.startswith('plugin://') and
not old_item.file.startswith(PLUGIN)):
# Ignore media by other addons
continue
except AttributeError:
# were not passed a filename; ignore
pass
if new_item.get('id') is None:
identical = old_item.file == new_item['file']
else:
identical = (old_item.kodi_id == new_item['id'] and
old_item.kodi_type == new_item['type'])
if j == 0 and identical:
del old[j], index[j]
break
elif identical:
LOG.debug('Detected playqueue item %s moved to position %s',
i + j, i)
PL.move_playlist_item(playqueue, i + j, i)
del old[j], index[j]
break
else:
LOG.debug('Detected new Kodi element at position %s: %s ',
i, new_item)
if playqueue.id is None:
PL.init_Plex_playlist(playqueue,
kodi_item=new_item)
else:
PL.add_item_to_PMS_playlist(playqueue,
i,
kodi_item=new_item)
for j in range(i, len(index)):
index[j] += 1
for i in reversed(index):
LOG.debug('Detected deletion of playqueue element at pos %s', i)
PL.delete_playlist_item_from_PMS(playqueue, i)
LOG.debug('Done comparing playqueues')
def run(self):
thread_stopped = self.thread_stopped
thread_suspended = self.thread_suspended
LOG.info("----===## Starting PlayqueueMonitor ##===----")
while not thread_stopped():
while thread_suspended():
if thread_stopped():
break
sleep(1000)
with LOCK:
for playqueue in PLAYQUEUES:
kodi_pl = js.playlist_get_items(playqueue.playlistid)
if playqueue.old_kodi_pl != kodi_pl:
# compare old and new playqueue
self._compare_playqueues(playqueue, kodi_pl)
playqueue.old_kodi_pl = list(kodi_pl)
sleep(200)
LOG.info("----===## PlayqueueMonitor stopped ##===----")

View file

@ -43,6 +43,7 @@ import PlexAPI
from PlexCompanion import PlexCompanion from PlexCompanion import PlexCompanion
from command_pipeline import Monitor_Window from command_pipeline import Monitor_Window
from playback_starter import Playback_Starter from playback_starter import Playback_Starter
from playqueue import PlayqueueMonitor
from artwork import Image_Cache_Thread from artwork import Image_Cache_Thread
from json_rpc import get_setting, set_setting from json_rpc import get_setting, set_setting
import variables as v import variables as v
@ -174,6 +175,7 @@ class Service():
self.plexCompanion = PlexCompanion() self.plexCompanion = PlexCompanion()
self.specialMonitor = SpecialMonitor() self.specialMonitor = SpecialMonitor()
self.playback_starter = Playback_Starter() self.playback_starter = Playback_Starter()
self.playqueue = PlayqueueMonitor()
if settings('enableTextureCache') == "true": if settings('enableTextureCache') == "true":
self.image_cache_thread = Image_Cache_Thread() self.image_cache_thread = Image_Cache_Thread()
@ -236,6 +238,7 @@ class Service():
if not self.playback_starter_running: if not self.playback_starter_running:
self.playback_starter_running = True self.playback_starter_running = True
self.playback_starter.start() self.playback_starter.start()
self.playqueue.start()
if (not self.image_cache_thread_running and if (not self.image_cache_thread_running and
settings('enableTextureCache') == "true"): settings('enableTextureCache') == "true"):
self.image_cache_thread_running = True self.image_cache_thread_running = True