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:
parent
199939c8b7
commit
3174521475
4 changed files with 99 additions and 111 deletions
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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 ##===----")
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue