2018-07-13 02:46:02 +10:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
2017-12-09 05:43:06 +11:00
|
|
|
"""
|
|
|
|
Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
|
|
|
|
"""
|
2018-07-13 02:46:02 +10:00
|
|
|
from __future__ import absolute_import, division, unicode_literals
|
2017-12-10 00:35:08 +11:00
|
|
|
from logging import getLogger
|
2018-06-22 03:24:37 +10:00
|
|
|
import xbmc
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2018-06-22 03:24:37 +10:00
|
|
|
from .plex_api import API
|
2018-11-19 00:59:17 +11:00
|
|
|
from . import playlist_func as PL, plex_functions as PF
|
|
|
|
from . import backgroundthread, utils, json_rpc as js, app, variables as v
|
2016-12-28 03:33:52 +11:00
|
|
|
|
|
|
|
###############################################################################
|
2018-06-22 03:24:37 +10:00
|
|
|
LOG = getLogger('PLEX.playqueue')
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2017-05-12 03:58:21 +10:00
|
|
|
PLUGIN = 'plugin://%s' % v.ADDON_ID
|
2018-01-07 01:19:12 +11:00
|
|
|
|
|
|
|
# Our PKC playqueues (3 instances of Playqueue_Object())
|
|
|
|
PLAYQUEUES = []
|
2016-12-28 03:33:52 +11:00
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
|
2018-01-07 01:19:12 +11:00
|
|
|
def init_playqueues():
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2018-01-07 01:19:12 +11:00
|
|
|
Call this once on startup to initialize the PKC playqueue objects in
|
|
|
|
the list PLAYQUEUES
|
2016-12-28 03:33:52 +11:00
|
|
|
"""
|
2018-01-07 01:19:12 +11:00
|
|
|
if PLAYQUEUES:
|
|
|
|
LOG.debug('Playqueues have already been initialized')
|
|
|
|
return
|
|
|
|
# Initialize Kodi playqueues
|
2018-11-19 00:59:17 +11:00
|
|
|
with app.APP.lock_playqueues:
|
2018-01-07 01:19:12 +11:00
|
|
|
for i in (0, 1, 2):
|
|
|
|
# Just in case the Kodi response is not sorted correctly
|
2017-12-09 05:43:06 +11:00
|
|
|
for queue in js.get_playlists():
|
2018-01-07 01:19:12 +11:00
|
|
|
if queue['playlistid'] != i:
|
|
|
|
continue
|
2017-01-03 00:07:24 +11:00
|
|
|
playqueue = PL.Playqueue_Object()
|
2018-01-07 01:19:12 +11:00
|
|
|
playqueue.playlistid = i
|
2017-01-03 00:07:24 +11:00
|
|
|
playqueue.type = queue['type']
|
|
|
|
# Initialize each Kodi playlist
|
2018-01-07 01:19:12 +11:00
|
|
|
if playqueue.type == v.KODI_TYPE_AUDIO:
|
2018-06-22 03:24:37 +10:00
|
|
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
2018-01-07 01:19:12 +11:00
|
|
|
elif playqueue.type == v.KODI_TYPE_VIDEO:
|
2018-06-22 03:24:37 +10:00
|
|
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
2017-01-03 00:07:24 +11:00
|
|
|
else:
|
|
|
|
# Currently, only video or audio playqueues available
|
2018-06-22 03:24:37 +10:00
|
|
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
2017-03-09 03:06:30 +11:00
|
|
|
# Overwrite 'picture' with 'photo'
|
|
|
|
playqueue.type = v.KODI_TYPE_PHOTO
|
2018-01-07 01:19:12 +11:00
|
|
|
PLAYQUEUES.append(playqueue)
|
|
|
|
LOG.debug('Initialized the Kodi playqueues: %s', PLAYQUEUES)
|
2016-12-28 03:33:52 +11:00
|
|
|
|
2016-12-30 01:41:14 +11:00
|
|
|
|
2018-01-11 06:14:05 +11:00
|
|
|
def get_playqueue_from_type(kodi_playlist_type):
|
2018-01-07 01:19:12 +11:00
|
|
|
"""
|
2018-01-11 06:14:05 +11:00
|
|
|
Returns the playqueue according to the kodi_playlist_type ('video',
|
|
|
|
'audio', 'picture') passed in
|
2018-01-07 01:19:12 +11:00
|
|
|
"""
|
2018-06-22 04:43:39 +10:00
|
|
|
for playqueue in PLAYQUEUES:
|
|
|
|
if playqueue.type == kodi_playlist_type:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise ValueError('Wrong playlist type passed in: %s',
|
|
|
|
kodi_playlist_type)
|
|
|
|
return playqueue
|
2017-03-06 03:51:58 +11:00
|
|
|
|
2016-12-28 23:14:21 +11:00
|
|
|
|
2018-01-07 01:19:12 +11:00
|
|
|
def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
|
|
|
"""
|
|
|
|
Init a new playqueue e.g. from an album. Alexa does this
|
2016-12-29 00:48:23 +11:00
|
|
|
|
2018-07-12 05:24:27 +10:00
|
|
|
Returns the playqueue
|
2018-01-07 01:19:12 +11:00
|
|
|
"""
|
2018-06-22 03:24:37 +10:00
|
|
|
xml = PF.GetAllPlexChildren(plex_id)
|
2018-01-07 01:19:12 +11:00
|
|
|
try:
|
|
|
|
xml[0].attrib
|
|
|
|
except (TypeError, IndexError, AttributeError):
|
|
|
|
LOG.error('Could not download the PMS xml for %s', plex_id)
|
|
|
|
return
|
|
|
|
playqueue = 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)
|
2018-02-12 00:42:49 +11:00
|
|
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id())
|
2018-01-07 01:19:12 +11:00
|
|
|
playqueue.plex_transient_token = transient_token
|
|
|
|
LOG.debug('Firing up Kodi player')
|
2018-11-23 18:41:05 +11:00
|
|
|
app.APP.player.play(playqueue.kodi_pl, None, False, 0)
|
2018-01-07 01:19:12 +11:00
|
|
|
return playqueue
|
2016-12-29 05:38:43 +11:00
|
|
|
|
2018-01-07 01:19:12 +11:00
|
|
|
|
2018-11-19 00:59:17 +11:00
|
|
|
class PlayqueueMonitor(backgroundthread.KillableThread):
|
2018-02-04 22:06:39 +11:00
|
|
|
"""
|
|
|
|
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
|
|
|
|
"""
|
2018-11-19 00:59:17 +11:00
|
|
|
def isSuspended(self):
|
|
|
|
"""
|
|
|
|
Returns True if the thread is suspended
|
|
|
|
"""
|
|
|
|
return self._suspended or app.CONN.pms_status
|
|
|
|
|
2018-02-04 22:06:39 +11:00
|
|
|
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):
|
2018-11-19 00:59:17 +11:00
|
|
|
if self.isCanceled():
|
2018-02-04 22:06:39 +11:00
|
|
|
# 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
|
2018-02-08 00:57:36 +11:00
|
|
|
if 'id' in new_item:
|
2018-02-04 22:06:39 +11:00
|
|
|
identical = (old_item.kodi_id == new_item['id'] and
|
|
|
|
old_item.kodi_type == new_item['type'])
|
2018-02-08 00:57:36 +11:00
|
|
|
else:
|
|
|
|
try:
|
2018-06-24 02:25:18 +10:00
|
|
|
plex_id = utils.REGEX_PLEX_ID.findall(new_item['file'])[0]
|
2018-02-08 00:57:36 +11:00
|
|
|
except IndexError:
|
|
|
|
LOG.debug('Comparing paths directly as a fallback')
|
|
|
|
identical = old_item.file == new_item['file']
|
|
|
|
else:
|
|
|
|
identical = plex_id == old_item.plex_id
|
2018-02-04 22:06:39 +11:00
|
|
|
if j == 0 and identical:
|
|
|
|
del old[j], index[j]
|
|
|
|
break
|
|
|
|
elif identical:
|
2018-07-08 19:08:30 +10:00
|
|
|
LOG.debug('Playqueue item %s moved to position %s',
|
2018-02-04 22:06:39 +11:00
|
|
|
i + j, i)
|
2018-07-08 19:08:30 +10:00
|
|
|
try:
|
|
|
|
PL.move_playlist_item(playqueue, i + j, i)
|
|
|
|
except PL.PlaylistError:
|
|
|
|
LOG.error('Could not modify playqueue positions')
|
|
|
|
LOG.error('This is likely caused by mixing audio and '
|
|
|
|
'video tracks in the Kodi playqueue')
|
2018-02-04 22:06:39 +11:00
|
|
|
del old[j], index[j]
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
LOG.debug('Detected new Kodi element at position %s: %s ',
|
|
|
|
i, new_item)
|
2018-06-03 01:31:57 +10:00
|
|
|
try:
|
|
|
|
if playqueue.id is None:
|
2018-06-03 22:26:55 +10:00
|
|
|
PL.init_plex_playqueue(playqueue, kodi_item=new_item)
|
2018-02-21 17:59:19 +11:00
|
|
|
else:
|
2018-06-03 22:26:55 +10:00
|
|
|
PL.add_item_to_plex_playqueue(playqueue,
|
|
|
|
i,
|
|
|
|
kodi_item=new_item)
|
2018-06-03 01:31:57 +10:00
|
|
|
except PL.PlaylistError:
|
|
|
|
# Could not add the element
|
|
|
|
pass
|
|
|
|
except IndexError:
|
|
|
|
# This is really a hack - happens when using Addon Paths
|
|
|
|
# and repeatedly starting the same element. Kodi will then
|
|
|
|
# not pass kodi id nor file path AND will also not
|
|
|
|
# start-up playback. Hence kodimonitor kicks off playback.
|
|
|
|
# Also see kodimonitor.py - _playlist_onadd()
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
for j in range(i, len(index)):
|
|
|
|
index[j] += 1
|
2018-02-04 22:06:39 +11:00
|
|
|
for i in reversed(index):
|
2018-11-19 00:59:17 +11:00
|
|
|
if self.isCanceled():
|
2018-02-04 22:17:05 +11:00
|
|
|
# Chances are that we got an empty Kodi playlist due to
|
|
|
|
# Kodi exit
|
|
|
|
return
|
2018-02-04 22:06:39 +11:00
|
|
|
LOG.debug('Detected deletion of playqueue element at pos %s', i)
|
2018-07-08 19:08:30 +10:00
|
|
|
try:
|
|
|
|
PL.delete_playlist_item_from_PMS(playqueue, i)
|
|
|
|
except PL.PlaylistError:
|
|
|
|
LOG.error('Could not delete PMS element from position %s', i)
|
|
|
|
LOG.error('This is likely caused by mixing audio and '
|
|
|
|
'video tracks in the Kodi playqueue')
|
2018-02-04 22:06:39 +11:00
|
|
|
LOG.debug('Done comparing playqueues')
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
LOG.info("----===## Starting PlayqueueMonitor ##===----")
|
2018-11-19 00:59:17 +11:00
|
|
|
while not self.isCanceled():
|
|
|
|
while self.isSuspended():
|
|
|
|
if self.isCanceled():
|
2018-02-04 22:06:39 +11:00
|
|
|
break
|
2018-11-21 02:58:25 +11:00
|
|
|
app.APP.monitor.waitForAbort(1)
|
2018-11-19 00:59:17 +11:00
|
|
|
with app.APP.lock_playqueues:
|
2018-04-11 03:23:37 +10:00
|
|
|
for playqueue in PLAYQUEUES:
|
|
|
|
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
|
|
|
if playqueue.old_kodi_pl != kodi_pl:
|
2018-11-21 04:39:18 +11:00
|
|
|
if playqueue.id is None and (not app.SYNC.direct_paths or
|
2018-11-19 00:59:17 +11:00
|
|
|
app.PLAYSTATE.context_menu_play):
|
2018-04-11 03:23:37 +10:00
|
|
|
# Only initialize if directly fired up using direct
|
|
|
|
# paths. Otherwise let default.py do its magic
|
|
|
|
LOG.debug('Not yet initiating playback')
|
|
|
|
else:
|
2018-06-03 01:31:57 +10:00
|
|
|
# compare old and new playqueue
|
|
|
|
self._compare_playqueues(playqueue, kodi_pl)
|
|
|
|
playqueue.old_kodi_pl = list(kodi_pl)
|
2018-11-21 02:58:25 +11:00
|
|
|
app.APP.monitor.waitForAbort(0.2)
|
2018-02-04 22:06:39 +11:00
|
|
|
LOG.info("----===## PlayqueueMonitor stopped ##===----")
|