2017-12-09 05:43:06 +11:00
|
|
|
"""
|
|
|
|
Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
|
|
|
|
"""
|
2017-12-10 00:35:08 +11:00
|
|
|
from logging import getLogger
|
2017-01-03 00:07:24 +11:00
|
|
|
from threading import RLock, Thread
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-01-03 00:07:24 +11:00
|
|
|
from xbmc import sleep, Player, PlayList, PLAYLIST_MUSIC, PLAYLIST_VIDEO
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-05-17 21:55:24 +10:00
|
|
|
from utils import window, thread_methods
|
2016-12-28 03:33:52 +11:00
|
|
|
import playlist_func as PL
|
2017-03-06 03:51:58 +11:00
|
|
|
from PlexFunctions import ConvertPlexToKodiTime, GetAllPlexChildren
|
|
|
|
from PlexAPI import API
|
2017-01-10 05:56:57 +11:00
|
|
|
from playbackutils import PlaybackUtils
|
2017-12-09 05:43:06 +11:00
|
|
|
import json_rpc as js
|
2017-03-06 03:51:58 +11:00
|
|
|
import variables as v
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
###############################################################################
|
2017-12-10 00:35:08 +11:00
|
|
|
LOG = getLogger("PLEX." + __name__)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-12-09 05:43:06 +11:00
|
|
|
# lock used for playqueue manipulations
|
|
|
|
LOCK = RLock()
|
2017-05-12 03:58:21 +10:00
|
|
|
PLUGIN = 'plugin://%s' % v.ADDON_ID
|
2016-12-28 03:33:52 +11:00
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
|
2017-05-17 21:55:24 +10:00
|
|
|
@thread_methods(add_suspends=['PMS_STATUS'])
|
2016-12-28 03:33:52 +11:00
|
|
|
class Playqueue(Thread):
|
|
|
|
"""
|
|
|
|
Monitors Kodi's playqueues for changes on the Kodi side
|
|
|
|
"""
|
|
|
|
# Borg - multiple instances, shared state
|
|
|
|
__shared_state = {}
|
|
|
|
playqueues = None
|
|
|
|
|
|
|
|
def __init__(self, callback=None):
|
|
|
|
self.__dict__ = self.__shared_state
|
|
|
|
if self.playqueues is not None:
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Playqueue thread has already been initialized')
|
2017-03-06 03:59:56 +11:00
|
|
|
Thread.__init__(self)
|
2016-12-28 03:33:52 +11:00
|
|
|
return
|
|
|
|
self.mgr = callback
|
|
|
|
|
|
|
|
# Initialize Kodi playqueues
|
2017-12-09 05:43:06 +11:00
|
|
|
with LOCK:
|
2017-01-03 00:07:24 +11:00
|
|
|
self.playqueues = []
|
2017-12-09 05:43:06 +11:00
|
|
|
for queue in js.get_playlists():
|
2017-01-03 00:07:24 +11:00
|
|
|
playqueue = PL.Playqueue_Object()
|
|
|
|
playqueue.playlistid = queue['playlistid']
|
|
|
|
playqueue.type = queue['type']
|
|
|
|
# Initialize each Kodi playlist
|
|
|
|
if playqueue.type == 'audio':
|
|
|
|
playqueue.kodi_pl = PlayList(PLAYLIST_MUSIC)
|
|
|
|
elif playqueue.type == 'video':
|
|
|
|
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
|
|
|
else:
|
|
|
|
# Currently, only video or audio playqueues available
|
|
|
|
playqueue.kodi_pl = PlayList(PLAYLIST_VIDEO)
|
2017-03-09 03:06:30 +11:00
|
|
|
# Overwrite 'picture' with 'photo'
|
|
|
|
playqueue.type = v.KODI_TYPE_PHOTO
|
2017-01-03 00:07:24 +11:00
|
|
|
self.playqueues.append(playqueue)
|
|
|
|
# sort the list by their playlistid, just in case
|
|
|
|
self.playqueues = sorted(
|
|
|
|
self.playqueues, key=lambda i: i.playlistid)
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Initialized the Kodi play queues: %s' % self.playqueues)
|
2017-01-03 01:41:38 +11:00
|
|
|
Thread.__init__(self)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2016-12-28 23:14:21 +11:00
|
|
|
def get_playqueue_from_type(self, typus):
|
|
|
|
"""
|
|
|
|
Returns the playqueue according to the typus ('video', 'audio',
|
|
|
|
'picture') passed in
|
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
with LOCK:
|
2017-01-03 00:07:24 +11:00
|
|
|
for playqueue in self.playqueues:
|
|
|
|
if playqueue.type == typus:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
|
|
|
return playqueue
|
2016-12-30 01:41:14 +11:00
|
|
|
|
2017-03-06 03:51:58 +11:00
|
|
|
def init_playqueue_from_plex_children(self, plex_id):
|
|
|
|
"""
|
|
|
|
Init a new playqueue e.g. from an album. Alexa does this
|
2017-05-25 22:21:27 +10:00
|
|
|
|
|
|
|
Returns the Playlist_Object
|
2017-03-06 03:51:58 +11:00
|
|
|
"""
|
|
|
|
xml = GetAllPlexChildren(plex_id)
|
|
|
|
try:
|
|
|
|
xml[0].attrib
|
|
|
|
except (TypeError, IndexError, AttributeError):
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.error('Could not download the PMS xml for %s' % plex_id)
|
2017-03-06 03:51:58 +11:00
|
|
|
return
|
|
|
|
playqueue = self.get_playqueue_from_type(
|
|
|
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
|
|
|
|
playqueue.clear()
|
|
|
|
for i, child in enumerate(xml):
|
|
|
|
api = API(child)
|
|
|
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey())
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Firing up Kodi player')
|
2017-03-06 04:28:30 +11:00
|
|
|
Player().play(playqueue.kodi_pl, None, False, 0)
|
2017-05-25 22:21:27 +10:00
|
|
|
return playqueue
|
2017-03-06 03:51:58 +11:00
|
|
|
|
2016-12-28 23:14:21 +11:00
|
|
|
def update_playqueue_from_PMS(self,
|
|
|
|
playqueue,
|
|
|
|
playqueue_id=None,
|
2016-12-29 00:57:10 +11:00
|
|
|
repeat=None,
|
|
|
|
offset=None):
|
2016-12-28 23:14:21 +11:00
|
|
|
"""
|
|
|
|
Completely updates the Kodi playqueue with the new Plex playqueue. Pass
|
|
|
|
in playqueue_id if we need to fetch a new playqueue
|
|
|
|
|
|
|
|
repeat = 0, 1, 2
|
2017-01-03 00:07:24 +11:00
|
|
|
offset = time offset in Plextime (milliseconds)
|
2016-12-29 00:48:23 +11:00
|
|
|
"""
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.info('New playqueue %s received from Plex companion with offset '
|
2017-01-03 00:07:24 +11:00
|
|
|
'%s, repeat %s' % (playqueue_id, offset, repeat))
|
2017-12-09 05:43:06 +11:00
|
|
|
with LOCK:
|
2017-01-10 06:00:37 +11:00
|
|
|
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
2017-02-27 01:16:03 +11:00
|
|
|
playqueue.clear()
|
|
|
|
try:
|
|
|
|
PL.get_playlist_details_from_xml(playqueue, xml)
|
|
|
|
except KeyError:
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.error('Could not get playqueue ID %s' % playqueue_id)
|
2017-01-10 06:00:37 +11:00
|
|
|
return
|
|
|
|
PlaybackUtils(xml, playqueue).play_all()
|
2017-01-03 00:07:24 +11:00
|
|
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
|
|
|
window('plex_customplaylist', value="true")
|
|
|
|
if offset not in (None, "0"):
|
|
|
|
window('plex_customplaylist.seektime',
|
|
|
|
str(ConvertPlexToKodiTime(offset)))
|
|
|
|
for startpos, item in enumerate(playqueue.items):
|
2017-12-09 05:43:06 +11:00
|
|
|
if item.id == playqueue.selectedItemID:
|
2017-01-03 00:07:24 +11:00
|
|
|
break
|
|
|
|
else:
|
2017-01-10 05:56:57 +11:00
|
|
|
startpos = 0
|
2017-01-03 00:07:24 +11:00
|
|
|
# Start playback. Player does not return in time
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Playqueues after Plex Companion update are now: %s'
|
2017-01-10 05:56:57 +11:00
|
|
|
% self.playqueues)
|
|
|
|
thread = Thread(target=Player().play,
|
|
|
|
args=(playqueue.kodi_pl,
|
|
|
|
None,
|
|
|
|
False,
|
|
|
|
startpos))
|
2017-01-03 00:07:24 +11:00
|
|
|
thread.setDaemon(True)
|
|
|
|
thread.start()
|
2016-12-29 00:48:23 +11:00
|
|
|
|
2016-12-28 03:33:52 +11:00
|
|
|
def _compare_playqueues(self, playqueue, new):
|
|
|
|
"""
|
|
|
|
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
|
|
|
"""
|
2017-01-03 00:07:24 +11:00
|
|
|
old = list(playqueue.items)
|
2016-12-29 00:48:23 +11:00
|
|
|
index = list(range(0, len(old)))
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Comparing new Kodi playqueue %s with our play queue %s'
|
2017-01-03 00:07:24 +11:00
|
|
|
% (new, old))
|
2017-05-17 23:27:32 +10:00
|
|
|
if self.thread_stopped():
|
|
|
|
# Chances are that we got an empty Kodi playlist due to
|
|
|
|
# Kodi exit
|
|
|
|
return
|
2016-12-28 03:33:52 +11:00
|
|
|
for i, new_item in enumerate(new):
|
2017-05-12 03:58:21 +10:00
|
|
|
if (new_item['file'].startswith('plugin://') and
|
|
|
|
not new_item['file'].startswith(PLUGIN)):
|
|
|
|
# Ignore new media added by other addons
|
|
|
|
continue
|
2016-12-28 03:33:52 +11:00
|
|
|
for j, old_item in enumerate(old):
|
2017-05-17 23:23:39 +10:00
|
|
|
try:
|
|
|
|
if (old_item.file.startswith('plugin://') and
|
|
|
|
not old_item['file'].startswith(PLUGIN)):
|
|
|
|
# Ignore media by other addons
|
|
|
|
continue
|
2017-05-21 02:49:40 +10:00
|
|
|
except (TypeError, AttributeError):
|
2017-05-17 23:23:39 +10:00
|
|
|
# were not passed a filename; ignore
|
|
|
|
pass
|
2017-01-03 00:07:24 +11:00
|
|
|
if new_item.get('id') is None:
|
|
|
|
identical = old_item.file == new_item['file']
|
2016-12-28 03:33:52 +11:00
|
|
|
else:
|
2017-01-03 00:07:24 +11:00
|
|
|
identical = (old_item.kodi_id == new_item['id'] and
|
|
|
|
old_item.kodi_type == new_item['type'])
|
2016-12-28 03:33:52 +11:00
|
|
|
if j == 0 and identical:
|
|
|
|
del old[j], index[j]
|
|
|
|
break
|
|
|
|
elif identical:
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Detected playqueue item %s moved to position %s'
|
2017-01-03 00:07:24 +11:00
|
|
|
% (i+j, i))
|
2016-12-28 03:33:52 +11:00
|
|
|
PL.move_playlist_item(playqueue, i + j, i)
|
2016-12-29 00:48:23 +11:00
|
|
|
del old[j], index[j]
|
2016-12-28 03:33:52 +11:00
|
|
|
break
|
2017-01-03 00:07:24 +11:00
|
|
|
else:
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Detected new Kodi element at position %s: %s '
|
2017-01-10 07:25:52 +11:00
|
|
|
% (i, new_item))
|
2017-12-09 05:43:06 +11:00
|
|
|
if playqueue.id is None:
|
2017-01-03 00:07:24 +11:00
|
|
|
PL.init_Plex_playlist(playqueue,
|
|
|
|
kodi_item=new_item)
|
2016-12-28 03:33:52 +11:00
|
|
|
else:
|
2017-01-03 00:07:24 +11:00
|
|
|
PL.add_item_to_PMS_playlist(playqueue,
|
|
|
|
i,
|
|
|
|
kodi_item=new_item)
|
2017-01-13 06:21:33 +11:00
|
|
|
for j in range(i, len(index)):
|
2017-01-10 07:25:52 +11:00
|
|
|
index[j] += 1
|
2017-01-03 00:07:24 +11:00
|
|
|
for i in reversed(index):
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Detected deletion of playqueue element at pos %s' % i)
|
2017-01-03 00:07:24 +11:00
|
|
|
PL.delete_playlist_item_from_PMS(playqueue, i)
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.debug('Done comparing playqueues')
|
2016-12-29 05:38:43 +11:00
|
|
|
|
|
|
|
def run(self):
|
2017-05-17 18:09:50 +10:00
|
|
|
thread_stopped = self.thread_stopped
|
|
|
|
thread_suspended = self.thread_suspended
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.info("----===## Starting PlayQueue client ##===----")
|
2016-12-29 05:38:43 +11:00
|
|
|
# Initialize the playqueues, if Kodi already got items in them
|
2017-01-03 00:07:24 +11:00
|
|
|
for playqueue in self.playqueues:
|
2017-12-09 05:43:06 +11:00
|
|
|
for i, item in enumerate(js.playlist_get_items(
|
|
|
|
playqueue.id, ["title", "file"])):
|
2017-01-03 00:07:24 +11:00
|
|
|
if i == 0:
|
|
|
|
PL.init_Plex_playlist(playqueue, kodi_item=item)
|
|
|
|
else:
|
|
|
|
PL.add_item_to_PMS_playlist(playqueue, i, kodi_item=item)
|
2017-05-17 18:09:50 +10:00
|
|
|
while not thread_stopped():
|
|
|
|
while thread_suspended():
|
|
|
|
if thread_stopped():
|
2016-12-28 03:33:52 +11:00
|
|
|
break
|
2017-01-03 00:07:24 +11:00
|
|
|
sleep(1000)
|
2017-12-09 05:43:06 +11:00
|
|
|
with LOCK:
|
2017-01-03 00:07:24 +11:00
|
|
|
for playqueue in self.playqueues:
|
2017-12-09 05:43:06 +11:00
|
|
|
kodi_playqueue = js.playlist_get_items(playqueue.id,
|
|
|
|
["title", "file"])
|
2017-01-03 00:07:24 +11:00
|
|
|
if playqueue.old_kodi_pl != kodi_playqueue:
|
|
|
|
# compare old and new playqueue
|
|
|
|
self._compare_playqueues(playqueue, kodi_playqueue)
|
|
|
|
playqueue.old_kodi_pl = list(kodi_playqueue)
|
2017-05-07 23:08:16 +10:00
|
|
|
# Still sleep a bit so Kodi does not become
|
|
|
|
# unresponsive
|
|
|
|
sleep(10)
|
|
|
|
continue
|
|
|
|
sleep(200)
|
2017-12-09 05:43:06 +11:00
|
|
|
LOG.info("----===## PlayQueue client stopped ##===----")
|