Refactor playqueues
This commit is contained in:
parent
edf0cd9a54
commit
f2cd4d68ea
13 changed files with 433 additions and 389 deletions
|
@ -9,12 +9,14 @@ from .application import App
|
||||||
from .connection import Connection
|
from .connection import Connection
|
||||||
from .libsync import Sync
|
from .libsync import Sync
|
||||||
from .playstate import PlayState
|
from .playstate import PlayState
|
||||||
|
from .playqueues import Playqueues
|
||||||
|
|
||||||
ACCOUNT = None
|
ACCOUNT = None
|
||||||
APP = None
|
APP = None
|
||||||
CONN = None
|
CONN = None
|
||||||
SYNC = None
|
SYNC = None
|
||||||
PLAYSTATE = None
|
PLAYSTATE = None
|
||||||
|
PLAYQUEUES = None
|
||||||
|
|
||||||
|
|
||||||
def init(entrypoint=False):
|
def init(entrypoint=False):
|
||||||
|
@ -22,13 +24,15 @@ def init(entrypoint=False):
|
||||||
entrypoint=True initiates only the bare minimum - for other PKC python
|
entrypoint=True initiates only the bare minimum - for other PKC python
|
||||||
instances
|
instances
|
||||||
"""
|
"""
|
||||||
global ACCOUNT, APP, CONN, SYNC, PLAYSTATE
|
global ACCOUNT, APP, CONN, SYNC, PLAYSTATE, PLAYQUEUES
|
||||||
APP = App(entrypoint)
|
APP = App(entrypoint)
|
||||||
CONN = Connection(entrypoint)
|
CONN = Connection(entrypoint)
|
||||||
ACCOUNT = Account(entrypoint)
|
ACCOUNT = Account(entrypoint)
|
||||||
SYNC = Sync(entrypoint)
|
SYNC = Sync(entrypoint)
|
||||||
if not entrypoint:
|
if not entrypoint:
|
||||||
PLAYSTATE = PlayState()
|
PLAYSTATE = PlayState()
|
||||||
|
PLAYQUEUES = Playqueues()
|
||||||
|
|
||||||
|
|
||||||
def reload():
|
def reload():
|
||||||
"""
|
"""
|
||||||
|
|
230
resources/lib/app/playqueues.py
Normal file
230
resources/lib/app/playqueues.py
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from logging import getLogger
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
|
||||||
|
from .. import variables as v
|
||||||
|
|
||||||
|
|
||||||
|
LOG = getLogger('PLEX.playqueue')
|
||||||
|
|
||||||
|
|
||||||
|
class Playqueue(object):
|
||||||
|
"""
|
||||||
|
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
||||||
|
|
||||||
|
playlistid = None [int] Kodi playlist id (0, 1, 2)
|
||||||
|
type = None [str] Kodi type: 'audio', 'video', 'picture'
|
||||||
|
kodi_pl = None Kodi xbmc.PlayList object
|
||||||
|
items = [] [list] of Playlist_Items
|
||||||
|
id = None [str] Plex playQueueID, unique Plex identifier
|
||||||
|
version = None [int] Plex version of the playQueue
|
||||||
|
selectedItemID = None
|
||||||
|
[str] Plex selectedItemID, playing element in queue
|
||||||
|
selectedItemOffset = None
|
||||||
|
[str] Offset of the playing element in queue
|
||||||
|
shuffled = 0 [int] 0: not shuffled, 1: ??? 2: ???
|
||||||
|
repeat = 0 [int] 0: not repeated, 1: ??? 2: ???
|
||||||
|
|
||||||
|
If Companion playback is initiated by another user:
|
||||||
|
plex_transient_token = None
|
||||||
|
"""
|
||||||
|
kind = 'playQueue'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.id = None
|
||||||
|
self.type = None
|
||||||
|
self.playlistid = None
|
||||||
|
self.kodi_pl = None
|
||||||
|
self.items = []
|
||||||
|
self.version = None
|
||||||
|
self.selectedItemID = None
|
||||||
|
self.selectedItemOffset = None
|
||||||
|
self.shuffled = 0
|
||||||
|
self.repeat = 0
|
||||||
|
self.plex_transient_token = None
|
||||||
|
# Need a hack for detecting swaps of elements
|
||||||
|
self.old_kodi_pl = []
|
||||||
|
# Did PKC itself just change the playqueue so the PKC playqueue monitor
|
||||||
|
# should not pick up any changes?
|
||||||
|
self.pkc_edit = False
|
||||||
|
# Workaround to avoid endless loops of detecting PL clears
|
||||||
|
self._clear_list = []
|
||||||
|
# To keep track if Kodi playback was initiated from a Kodi playlist
|
||||||
|
# There are a couple of pitfalls, unfortunately...
|
||||||
|
self.kodi_playlist_playback = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
answ = ("{{"
|
||||||
|
"'playlistid': {self.playlistid}, "
|
||||||
|
"'id': {self.id}, "
|
||||||
|
"'version': {self.version}, "
|
||||||
|
"'type': '{self.type}', "
|
||||||
|
"'selectedItemID': {self.selectedItemID}, "
|
||||||
|
"'selectedItemOffset': {self.selectedItemOffset}, "
|
||||||
|
"'shuffled': {self.shuffled}, "
|
||||||
|
"'repeat': {self.repeat}, "
|
||||||
|
"'kodi_playlist_playback': {self.kodi_playlist_playback}, "
|
||||||
|
"'pkc_edit': {self.pkc_edit}, ".format(self=self))
|
||||||
|
# Since list.__repr__ will return string, not unicode
|
||||||
|
return answ + "'items': {self.items}}}".format(self=self)
|
||||||
|
|
||||||
|
def is_pkc_clear(self):
|
||||||
|
"""
|
||||||
|
Returns True if PKC has cleared the Kodi playqueue just recently.
|
||||||
|
Then this clear will be ignored from now on
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._clear_list.pop()
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def clear(self, kodi=True):
|
||||||
|
"""
|
||||||
|
Resets the playlist object to an empty playlist.
|
||||||
|
|
||||||
|
Pass kodi=False in order to NOT clear the Kodi playqueue
|
||||||
|
"""
|
||||||
|
# kodi monitor's on_clear method will only be called if there were some
|
||||||
|
# items to begin with
|
||||||
|
if kodi and self.kodi_pl.size() != 0:
|
||||||
|
self._clear_list.append(None)
|
||||||
|
self.kodi_pl.clear() # Clear Kodi playlist object
|
||||||
|
self.items = []
|
||||||
|
self.id = None
|
||||||
|
self.version = None
|
||||||
|
self.selectedItemID = None
|
||||||
|
self.selectedItemOffset = None
|
||||||
|
self.shuffled = 0
|
||||||
|
self.repeat = 0
|
||||||
|
self.plex_transient_token = None
|
||||||
|
self.old_kodi_pl = []
|
||||||
|
self.kodi_playlist_playback = False
|
||||||
|
LOG.debug('Playlist cleared: %s', self)
|
||||||
|
|
||||||
|
def position_from_plex_id(self, plex_id):
|
||||||
|
"""
|
||||||
|
Returns the position [int] for the very first item with plex_id [int]
|
||||||
|
(Plex seems uncapable of adding the same element multiple times to a
|
||||||
|
playqueue or playlist)
|
||||||
|
|
||||||
|
Raises KeyError if not found
|
||||||
|
"""
|
||||||
|
for position, item in enumerate(self.items):
|
||||||
|
if item.plex_id == plex_id:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise KeyError('Did not find plex_id %s in %s', plex_id, self)
|
||||||
|
return position
|
||||||
|
|
||||||
|
|
||||||
|
class Playqueues(list):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
for i, typus in enumerate((v.KODI_PLAYLIST_TYPE_AUDIO,
|
||||||
|
v.KODI_PLAYLIST_TYPE_VIDEO,
|
||||||
|
v.KODI_PLAYLIST_TYPE_PHOTO)):
|
||||||
|
playqueue = Playqueue()
|
||||||
|
playqueue.playlistid = i
|
||||||
|
playqueue.type = typus
|
||||||
|
# Initialize each Kodi playlist
|
||||||
|
if typus == v.KODI_PLAYLIST_TYPE_AUDIO:
|
||||||
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
||||||
|
elif typus == v.KODI_PLAYLIST_TYPE_VIDEO:
|
||||||
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
else:
|
||||||
|
# Currently, only video or audio playqueues available
|
||||||
|
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
# Overwrite 'picture' with 'photo'
|
||||||
|
playqueue.type = v.KODI_TYPE_PHOTO
|
||||||
|
self.append(playqueue)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def audio(self):
|
||||||
|
return self[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def video(self):
|
||||||
|
return self[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def photo(self):
|
||||||
|
return self[2]
|
||||||
|
|
||||||
|
def from_kodi_playlist_type(self, kodi_playlist_type):
|
||||||
|
"""
|
||||||
|
Returns the playqueue according to the kodi_playlist_type ('video',
|
||||||
|
'audio', 'picture') passed in
|
||||||
|
"""
|
||||||
|
if kodi_playlist_type == v.KODI_PLAYLIST_TYPE_AUDIO:
|
||||||
|
return self[0]
|
||||||
|
elif kodi_playlist_type == v.KODI_PLAYLIST_TYPE_VIDEO:
|
||||||
|
return self[1]
|
||||||
|
elif kodi_playlist_type == v.KODI_PLAYLIST_TYPE_PHOTO:
|
||||||
|
return self[2]
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown kodi_playlist_type: %s' % kodi_playlist_type)
|
||||||
|
|
||||||
|
def from_kodi_type(self, kodi_type):
|
||||||
|
"""
|
||||||
|
Pass in the kodi_type (e.g. the string 'movie') to get the correct
|
||||||
|
playqueue (either video, audio or picture)
|
||||||
|
"""
|
||||||
|
if kodi_type == v.KODI_TYPE_VIDEO:
|
||||||
|
return self[1]
|
||||||
|
elif kodi_type == v.KODI_TYPE_MOVIE:
|
||||||
|
return self[1]
|
||||||
|
elif kodi_type == v.KODI_TYPE_EPISODE:
|
||||||
|
return self[1]
|
||||||
|
elif kodi_type == v.KODI_TYPE_SEASON:
|
||||||
|
return self[1]
|
||||||
|
elif kodi_type == v.KODI_TYPE_SHOW:
|
||||||
|
return self[1]
|
||||||
|
elif kodi_type == v.KODI_TYPE_CLIP:
|
||||||
|
return self[1]
|
||||||
|
elif kodi_type == v.KODI_TYPE_SONG:
|
||||||
|
return self[0]
|
||||||
|
elif kodi_type == v.KODI_TYPE_ALBUM:
|
||||||
|
return self[0]
|
||||||
|
elif kodi_type == v.KODI_TYPE_ARTIST:
|
||||||
|
return self[0]
|
||||||
|
elif kodi_type == v.KODI_TYPE_AUDIO:
|
||||||
|
return self[0]
|
||||||
|
elif kodi_type == v.KODI_TYPE_PHOTO:
|
||||||
|
return self[2]
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown kodi_type: %s' % kodi_type)
|
||||||
|
|
||||||
|
def from_plex_type(self, plex_type):
|
||||||
|
"""
|
||||||
|
Pass in the plex_type (e.g. the string 'movie') to get the correct
|
||||||
|
playqueue (either video, audio or picture)
|
||||||
|
"""
|
||||||
|
if plex_type == v.PLEX_TYPE_VIDEO:
|
||||||
|
return self[1]
|
||||||
|
elif plex_type == v.PLEX_TYPE_MOVIE:
|
||||||
|
return self[1]
|
||||||
|
elif plex_type == v.PLEX_TYPE_EPISODE:
|
||||||
|
return self[1]
|
||||||
|
elif plex_type == v.PLEX_TYPE_SEASON:
|
||||||
|
return self[1]
|
||||||
|
elif plex_type == v.PLEX_TYPE_SHOW:
|
||||||
|
return self[1]
|
||||||
|
elif plex_type == v.PLEX_TYPE_CLIP:
|
||||||
|
return self[1]
|
||||||
|
elif plex_type == v.PLEX_TYPE_SONG:
|
||||||
|
return self[0]
|
||||||
|
elif plex_type == v.PLEX_TYPE_ALBUM:
|
||||||
|
return self[0]
|
||||||
|
elif plex_type == v.PLEX_TYPE_ARTIST:
|
||||||
|
return self[0]
|
||||||
|
elif plex_type == v.PLEX_TYPE_AUDIO:
|
||||||
|
return self[0]
|
||||||
|
elif plex_type == v.PLEX_TYPE_PHOTO:
|
||||||
|
return self[2]
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown plex_type: %s' % plex_type)
|
|
@ -6,8 +6,10 @@ Processes Plex companion inputs from the plexbmchelper to Kodi commands
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from xbmc import Player
|
from xbmc import Player
|
||||||
|
|
||||||
from . import playqueue as PQ, plex_functions as PF
|
from . import plex_functions as PF
|
||||||
from . import json_rpc as js, variables as v, app
|
from . import json_rpc as js
|
||||||
|
from . import variables as v
|
||||||
|
from . import app
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -28,7 +30,7 @@ def skip_to(params):
|
||||||
playqueue_item_id, plex_id)
|
playqueue_item_id, plex_id)
|
||||||
found = True
|
found = True
|
||||||
for player in list(js.get_players().values()):
|
for player in list(js.get_players().values()):
|
||||||
playqueue = PQ.PLAYQUEUES[player['playerid']]
|
playqueue = app.PLAYQUEUES[player['playerid']]
|
||||||
for i, item in enumerate(playqueue.items):
|
for i, item in enumerate(playqueue.items):
|
||||||
if item.id == playqueue_item_id:
|
if item.id == playqueue_item_id:
|
||||||
found = True
|
found = True
|
||||||
|
|
|
@ -6,8 +6,11 @@ import xbmcgui
|
||||||
|
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from .plex_db import PlexDB
|
from .plex_db import PlexDB
|
||||||
from . import context, plex_functions as PF, playqueue as PQ
|
from . import context
|
||||||
from . import utils, variables as v, app
|
from . import plex_functions as PF
|
||||||
|
from . import utils
|
||||||
|
from . import variables as v
|
||||||
|
from . import app
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -137,8 +140,7 @@ class ContextMenu(object):
|
||||||
"""
|
"""
|
||||||
For using direct paths: Initiates playback using the PMS
|
For using direct paths: Initiates playback using the PMS
|
||||||
"""
|
"""
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.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.fullpath(force_addon=True)[0]
|
handle = self.api.fullpath(force_addon=True)[0]
|
||||||
|
|
|
@ -17,7 +17,7 @@ from .kodi_db import KodiVideoDB
|
||||||
from . import kodi_db
|
from . import kodi_db
|
||||||
from .downloadutils import DownloadUtils as DU
|
from .downloadutils import DownloadUtils as DU
|
||||||
from . import utils, timing, plex_functions as PF
|
from . import utils, timing, plex_functions as PF
|
||||||
from . import json_rpc as js, playqueue as PQ, playlist_func as PL
|
from . import json_rpc as js, playlist_func as PL
|
||||||
from . import backgroundthread, app, variables as v
|
from . import backgroundthread, app, variables as v
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
u'playlistid': 1,
|
u'playlistid': 1,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
playqueue = PQ.PLAYQUEUES[data['playlistid']]
|
playqueue = app.PLAYQUEUES[data['playlistid']]
|
||||||
if not playqueue.is_pkc_clear():
|
if not playqueue.is_pkc_clear():
|
||||||
playqueue.pkc_edit = True
|
playqueue.pkc_edit = True
|
||||||
playqueue.clear(kodi=False)
|
playqueue.clear(kodi=False)
|
||||||
|
@ -256,7 +256,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
if not playerid:
|
if not playerid:
|
||||||
LOG.error('Coud not get playerid for data %s', data)
|
LOG.error('Coud not get playerid for data %s', data)
|
||||||
return
|
return
|
||||||
playqueue = PQ.PLAYQUEUES[playerid]
|
playqueue = app.PLAYQUEUES[playerid]
|
||||||
info = js.get_player_props(playerid)
|
info = js.get_player_props(playerid)
|
||||||
if playqueue.kodi_playlist_playback:
|
if playqueue.kodi_playlist_playback:
|
||||||
# Kodi will tell us the wrong position - of the playlist, not the
|
# Kodi will tell us the wrong position - of the playlist, not the
|
||||||
|
@ -326,7 +326,7 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
container_key = None
|
container_key = None
|
||||||
if info['playlistid'] != -1:
|
if info['playlistid'] != -1:
|
||||||
# -1 is Kodi's answer if there is no playlist
|
# -1 is Kodi's answer if there is no playlist
|
||||||
container_key = PQ.PLAYQUEUES[playerid].id
|
container_key = app.PLAYQUEUES[playerid].id
|
||||||
if container_key is not None:
|
if container_key is not None:
|
||||||
container_key = '/playQueues/%s' % container_key
|
container_key = '/playQueues/%s' % container_key
|
||||||
elif plex_id is not None:
|
elif plex_id is not None:
|
||||||
|
|
|
@ -12,8 +12,12 @@ import xbmc
|
||||||
from .plex_api import API
|
from .plex_api import API
|
||||||
from .plex_db import PlexDB
|
from .plex_db import PlexDB
|
||||||
from .kodi_db import KodiVideoDB
|
from .kodi_db import KodiVideoDB
|
||||||
from . import plex_functions as PF, playlist_func as PL, playqueue as PQ
|
from . import plex_functions as PF
|
||||||
from . import json_rpc as js, variables as v, utils, transfer
|
from . import playlist_func as PL
|
||||||
|
from . import json_rpc as js
|
||||||
|
from . import variables as v
|
||||||
|
from . import utils
|
||||||
|
from . import transfer
|
||||||
from . import playback_decision, app
|
from . import playback_decision, app
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
|
|
||||||
|
@ -74,20 +78,19 @@ def _playback_triage(plex_id, plex_type, path, resolve, resume):
|
||||||
_ensure_resolve(abort=True)
|
_ensure_resolve(abort=True)
|
||||||
return
|
return
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.from_plex_type(plex_type)
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
|
|
||||||
try:
|
try:
|
||||||
pos = js.get_position(playqueue.playlistid)
|
pos = js.get_position(playqueue.playlistid)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Kodi bug - Playlist plays (not Playqueue) will ALWAYS be audio for
|
# Kodi bug - Playlist plays (not Playqueue) will ALWAYS be audio for
|
||||||
# add-on paths
|
# add-on paths
|
||||||
LOG.debug('No position returned from player! Assuming playlist')
|
LOG.debug('No position returned from player! Assuming playlist')
|
||||||
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO)
|
playqueue = app.PLAYQUEUES.audio
|
||||||
try:
|
try:
|
||||||
pos = js.get_position(playqueue.playlistid)
|
pos = js.get_position(playqueue.playlistid)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
LOG.debug('Assuming video instead of audio playlist playback')
|
LOG.debug('Assuming video instead of audio playlist playback')
|
||||||
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_VIDEO)
|
playqueue = app.PLAYQUEUES.video
|
||||||
try:
|
try:
|
||||||
pos = js.get_position(playqueue.playlistid)
|
pos = js.get_position(playqueue.playlistid)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -159,7 +162,7 @@ def _playlist_playback(plex_id):
|
||||||
return
|
return
|
||||||
# Kodi bug: playqueue will ALWAYS be audio playqueue UNTIL playback
|
# Kodi bug: playqueue will ALWAYS be audio playqueue UNTIL playback
|
||||||
# has actually started. Need to tell Kodimonitor
|
# has actually started. Need to tell Kodimonitor
|
||||||
playqueue = PQ.get_playqueue_from_type(v.KODI_PLAYLIST_TYPE_AUDIO)
|
playqueue = app.PLAYQUEUES.audio
|
||||||
playqueue.clear(kodi=False)
|
playqueue.clear(kodi=False)
|
||||||
# Set the flag for the potentially WRONG audio playlist so Kodimonitor
|
# Set the flag for the potentially WRONG audio playlist so Kodimonitor
|
||||||
# can pick up on it
|
# can pick up on it
|
||||||
|
@ -499,8 +502,7 @@ def process_indirect(key, offset, resolve=True):
|
||||||
|
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
|
listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.from_plex_type(api.plex_type)
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
|
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
item = PL.playlist_item_from_xml(xml[0])
|
item = PL.playlist_item_from_xml(xml[0])
|
||||||
item.offset = offset
|
item.offset = offset
|
||||||
|
|
|
@ -22,117 +22,6 @@ from .subtitles import accessible_plex_subtitles
|
||||||
LOG = getLogger('PLEX.playlist_func')
|
LOG = getLogger('PLEX.playlist_func')
|
||||||
|
|
||||||
|
|
||||||
class Playqueue_Object(object):
|
|
||||||
"""
|
|
||||||
PKC object to represent PMS playQueues and Kodi playlist for queueing
|
|
||||||
|
|
||||||
playlistid = None [int] Kodi playlist id (0, 1, 2)
|
|
||||||
type = None [str] Kodi type: 'audio', 'video', 'picture'
|
|
||||||
kodi_pl = None Kodi xbmc.PlayList object
|
|
||||||
items = [] [list] of Playlist_Items
|
|
||||||
id = None [str] Plex playQueueID, unique Plex identifier
|
|
||||||
version = None [int] Plex version of the playQueue
|
|
||||||
selectedItemID = None
|
|
||||||
[str] Plex selectedItemID, playing element in queue
|
|
||||||
selectedItemOffset = None
|
|
||||||
[str] Offset of the playing element in queue
|
|
||||||
shuffled = 0 [int] 0: not shuffled, 1: ??? 2: ???
|
|
||||||
repeat = 0 [int] 0: not repeated, 1: ??? 2: ???
|
|
||||||
|
|
||||||
If Companion playback is initiated by another user:
|
|
||||||
plex_transient_token = None
|
|
||||||
"""
|
|
||||||
kind = 'playQueue'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.id = None
|
|
||||||
self.type = None
|
|
||||||
self.playlistid = None
|
|
||||||
self.kodi_pl = None
|
|
||||||
self.items = []
|
|
||||||
self.version = None
|
|
||||||
self.selectedItemID = None
|
|
||||||
self.selectedItemOffset = None
|
|
||||||
self.shuffled = 0
|
|
||||||
self.repeat = 0
|
|
||||||
self.plex_transient_token = None
|
|
||||||
# Need a hack for detecting swaps of elements
|
|
||||||
self.old_kodi_pl = []
|
|
||||||
# Did PKC itself just change the playqueue so the PKC playqueue monitor
|
|
||||||
# should not pick up any changes?
|
|
||||||
self.pkc_edit = False
|
|
||||||
# Workaround to avoid endless loops of detecting PL clears
|
|
||||||
self._clear_list = []
|
|
||||||
# To keep track if Kodi playback was initiated from a Kodi playlist
|
|
||||||
# There are a couple of pitfalls, unfortunately...
|
|
||||||
self.kodi_playlist_playback = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
answ = ("{{"
|
|
||||||
"'playlistid': {self.playlistid}, "
|
|
||||||
"'id': {self.id}, "
|
|
||||||
"'version': {self.version}, "
|
|
||||||
"'type': '{self.type}', "
|
|
||||||
"'selectedItemID': {self.selectedItemID}, "
|
|
||||||
"'selectedItemOffset': {self.selectedItemOffset}, "
|
|
||||||
"'shuffled': {self.shuffled}, "
|
|
||||||
"'repeat': {self.repeat}, "
|
|
||||||
"'kodi_playlist_playback': {self.kodi_playlist_playback}, "
|
|
||||||
"'pkc_edit': {self.pkc_edit}, ".format(self=self))
|
|
||||||
# Since list.__repr__ will return string, not unicode
|
|
||||||
return answ + "'items': {self.items}}}".format(self=self)
|
|
||||||
|
|
||||||
def is_pkc_clear(self):
|
|
||||||
"""
|
|
||||||
Returns True if PKC has cleared the Kodi playqueue just recently.
|
|
||||||
Then this clear will be ignored from now on
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self._clear_list.pop()
|
|
||||||
except IndexError:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def clear(self, kodi=True):
|
|
||||||
"""
|
|
||||||
Resets the playlist object to an empty playlist.
|
|
||||||
|
|
||||||
Pass kodi=False in order to NOT clear the Kodi playqueue
|
|
||||||
"""
|
|
||||||
# kodi monitor's on_clear method will only be called if there were some
|
|
||||||
# items to begin with
|
|
||||||
if kodi and self.kodi_pl.size() != 0:
|
|
||||||
self._clear_list.append(None)
|
|
||||||
self.kodi_pl.clear() # Clear Kodi playlist object
|
|
||||||
self.items = []
|
|
||||||
self.id = None
|
|
||||||
self.version = None
|
|
||||||
self.selectedItemID = None
|
|
||||||
self.selectedItemOffset = None
|
|
||||||
self.shuffled = 0
|
|
||||||
self.repeat = 0
|
|
||||||
self.plex_transient_token = None
|
|
||||||
self.old_kodi_pl = []
|
|
||||||
self.kodi_playlist_playback = False
|
|
||||||
LOG.debug('Playlist cleared: %s', self)
|
|
||||||
|
|
||||||
def position_from_plex_id(self, plex_id):
|
|
||||||
"""
|
|
||||||
Returns the position [int] for the very first item with plex_id [int]
|
|
||||||
(Plex seems uncapable of adding the same element multiple times to a
|
|
||||||
playqueue or playlist)
|
|
||||||
|
|
||||||
Raises KeyError if not found
|
|
||||||
"""
|
|
||||||
for position, item in enumerate(self.items):
|
|
||||||
if item.plex_id == plex_id:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise KeyError('Did not find plex_id %s in %s', plex_id, self)
|
|
||||||
return position
|
|
||||||
|
|
||||||
|
|
||||||
class PlaylistItem(object):
|
class PlaylistItem(object):
|
||||||
"""
|
"""
|
||||||
Object to fill our playqueues and playlists with.
|
Object to fill our playqueues and playlists with.
|
||||||
|
|
|
@ -1,232 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Monitors the Kodi playqueue and adjusts the Plex playqueue accordingly
|
|
||||||
"""
|
|
||||||
from logging import getLogger
|
|
||||||
import copy
|
|
||||||
|
|
||||||
import xbmc
|
|
||||||
|
|
||||||
from .plex_api import API
|
|
||||||
from . import playlist_func as PL, plex_functions as PF
|
|
||||||
from . import backgroundthread, utils, json_rpc as js, app, variables as v
|
|
||||||
from . import exceptions
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
LOG = getLogger('PLEX.playqueue')
|
|
||||||
|
|
||||||
PLUGIN = 'plugin://%s' % v.ADDON_ID
|
|
||||||
|
|
||||||
# Our PKC playqueues (3 instances of Playqueue_Object())
|
|
||||||
PLAYQUEUES = []
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
def init_playqueues():
|
|
||||||
"""
|
|
||||||
Call this once on startup to initialize the PKC playqueue objects in
|
|
||||||
the list PLAYQUEUES
|
|
||||||
"""
|
|
||||||
if PLAYQUEUES:
|
|
||||||
LOG.debug('Playqueues have already been initialized')
|
|
||||||
return
|
|
||||||
# Initialize Kodi playqueues
|
|
||||||
with app.APP.lock_playqueues:
|
|
||||||
for i in (0, 1, 2):
|
|
||||||
# Just in case the Kodi response is not sorted correctly
|
|
||||||
for queue in js.get_playlists():
|
|
||||||
if queue['playlistid'] != i:
|
|
||||||
continue
|
|
||||||
playqueue = PL.Playqueue_Object()
|
|
||||||
playqueue.playlistid = i
|
|
||||||
playqueue.type = queue['type']
|
|
||||||
# Initialize each Kodi playlist
|
|
||||||
if playqueue.type == v.KODI_TYPE_AUDIO:
|
|
||||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
|
|
||||||
elif playqueue.type == v.KODI_TYPE_VIDEO:
|
|
||||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
||||||
else:
|
|
||||||
# Currently, only video or audio playqueues available
|
|
||||||
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
|
||||||
# Overwrite 'picture' with 'photo'
|
|
||||||
playqueue.type = v.KODI_TYPE_PHOTO
|
|
||||||
PLAYQUEUES.append(playqueue)
|
|
||||||
LOG.debug('Initialized the Kodi playqueues: %s', PLAYQUEUES)
|
|
||||||
|
|
||||||
|
|
||||||
def get_playqueue_from_type(kodi_playlist_type):
|
|
||||||
"""
|
|
||||||
Returns the playqueue according to the kodi_playlist_type ('video',
|
|
||||||
'audio', 'picture') passed in
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
|
||||||
"""
|
|
||||||
Init a new playqueue e.g. from an album. Alexa does this
|
|
||||||
|
|
||||||
Returns the playqueue
|
|
||||||
"""
|
|
||||||
xml = PF.GetAllPlexChildren(plex_id)
|
|
||||||
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)
|
|
||||||
try:
|
|
||||||
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
|
||||||
except exceptions.PlaylistError:
|
|
||||||
LOG.error('Could not add Plex item to our playlist: %s, %s',
|
|
||||||
child.tag, child.attrib)
|
|
||||||
playqueue.plex_transient_token = transient_token
|
|
||||||
LOG.debug('Firing up Kodi player')
|
|
||||||
app.APP.player.play(playqueue.kodi_pl, None, False, 0)
|
|
||||||
return playqueue
|
|
||||||
|
|
||||||
|
|
||||||
class PlayqueueMonitor(backgroundthread.KillableThread):
|
|
||||||
"""
|
|
||||||
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_kodi_playqueue):
|
|
||||||
"""
|
|
||||||
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
|
||||||
"""
|
|
||||||
old = list(playqueue.items)
|
|
||||||
# We might append to new_kodi_playqueue but will need the original
|
|
||||||
# still back in the main loop
|
|
||||||
new = copy.deepcopy(new_kodi_playqueue)
|
|
||||||
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.should_suspend() or self.should_cancel():
|
|
||||||
# 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 'id' in new_item:
|
|
||||||
identical = (old_item.kodi_id == new_item['id'] and
|
|
||||||
old_item.kodi_type == new_item['type'])
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
plex_id = int(utils.REGEX_PLEX_ID.findall(new_item['file'])[0])
|
|
||||||
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
|
|
||||||
if j == 0 and identical:
|
|
||||||
del old[j], index[j]
|
|
||||||
break
|
|
||||||
elif identical:
|
|
||||||
LOG.debug('Playqueue item %s moved to position %s',
|
|
||||||
i + j, i)
|
|
||||||
try:
|
|
||||||
PL.move_playlist_item(playqueue, i + j, i)
|
|
||||||
except exceptions.PlaylistError:
|
|
||||||
LOG.error('Could not modify playqueue positions')
|
|
||||||
LOG.error('This is likely caused by mixing audio and '
|
|
||||||
'video tracks in the Kodi playqueue')
|
|
||||||
del old[j], index[j]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
LOG.debug('Detected new Kodi element at position %s: %s ',
|
|
||||||
i, new_item)
|
|
||||||
try:
|
|
||||||
if playqueue.id is None:
|
|
||||||
PL.init_plex_playqueue(playqueue, kodi_item=new_item)
|
|
||||||
else:
|
|
||||||
PL.add_item_to_plex_playqueue(playqueue,
|
|
||||||
i,
|
|
||||||
kodi_item=new_item)
|
|
||||||
except exceptions.PlaylistError:
|
|
||||||
# Could not add the element
|
|
||||||
pass
|
|
||||||
except KeyError:
|
|
||||||
# Catches KeyError from PL.verify_kodi_item()
|
|
||||||
# Hack: Kodi already started playback of a new item and we
|
|
||||||
# started playback already using kodimonitors
|
|
||||||
# PlayBackStart(), but the Kodi playlist STILL only shows
|
|
||||||
# the old element. Hence ignore playlist difference here
|
|
||||||
LOG.debug('Detected an outdated Kodi playlist - ignoring')
|
|
||||||
return
|
|
||||||
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
|
|
||||||
for i in reversed(index):
|
|
||||||
if self.should_suspend() or self.should_cancel():
|
|
||||||
# Chances are that we got an empty Kodi playlist due to
|
|
||||||
# Kodi exit
|
|
||||||
return
|
|
||||||
LOG.debug('Detected deletion of playqueue element at pos %s', i)
|
|
||||||
try:
|
|
||||||
PL.delete_playlist_item_from_PMS(playqueue, i)
|
|
||||||
except exceptions.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')
|
|
||||||
LOG.debug('Done comparing playqueues')
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
LOG.info("----===## Starting PlayqueueMonitor ##===----")
|
|
||||||
app.APP.register_thread(self)
|
|
||||||
try:
|
|
||||||
self._run()
|
|
||||||
finally:
|
|
||||||
app.APP.deregister_thread(self)
|
|
||||||
LOG.info("----===## PlayqueueMonitor stopped ##===----")
|
|
||||||
|
|
||||||
def _run(self):
|
|
||||||
while not self.should_cancel():
|
|
||||||
if self.should_suspend():
|
|
||||||
if self.wait_while_suspended():
|
|
||||||
return
|
|
||||||
with app.APP.lock_playqueues:
|
|
||||||
for playqueue in PLAYQUEUES:
|
|
||||||
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
|
||||||
if playqueue.old_kodi_pl != kodi_pl:
|
|
||||||
if playqueue.id is None and (not app.SYNC.direct_paths or
|
|
||||||
app.PLAYSTATE.context_menu_play):
|
|
||||||
# Only initialize if directly fired up using direct
|
|
||||||
# paths. Otherwise let default.py do its magic
|
|
||||||
LOG.debug('Not yet initiating playback')
|
|
||||||
else:
|
|
||||||
# compare old and new playqueue
|
|
||||||
self._compare_playqueues(playqueue, kodi_pl)
|
|
||||||
playqueue.old_kodi_pl = list(kodi_pl)
|
|
||||||
self.sleep(0.2)
|
|
144
resources/lib/plex_companion/playqueue.py
Normal file
144
resources/lib/plex_companion/playqueue.py
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from logging import getLogger
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from ..plex_api import API
|
||||||
|
from .. import variables as v
|
||||||
|
from .. import app
|
||||||
|
from .. import utils
|
||||||
|
from .. import plex_functions as PF
|
||||||
|
from .. import playlist_func as PL
|
||||||
|
from .. import exceptions
|
||||||
|
|
||||||
|
log = getLogger('PLEX.companion.playqueue')
|
||||||
|
|
||||||
|
PLUGIN = 'plugin://%s' % v.ADDON_ID
|
||||||
|
|
||||||
|
|
||||||
|
def init_playqueue_from_plex_children(plex_id, transient_token=None):
|
||||||
|
"""
|
||||||
|
Init a new playqueue e.g. from an album. Alexa does this
|
||||||
|
|
||||||
|
Returns the playqueue
|
||||||
|
"""
|
||||||
|
xml = PF.GetAllPlexChildren(plex_id)
|
||||||
|
try:
|
||||||
|
xml[0].attrib
|
||||||
|
except (TypeError, IndexError, AttributeError):
|
||||||
|
log.error('Could not download the PMS xml for %s', plex_id)
|
||||||
|
return
|
||||||
|
playqueue = app.PLAYQUEUES.from_plex_type(xml[0].attrib['type'])
|
||||||
|
playqueue.clear()
|
||||||
|
for i, child in enumerate(xml):
|
||||||
|
api = API(child)
|
||||||
|
try:
|
||||||
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.plex_id)
|
||||||
|
except exceptions.PlaylistError:
|
||||||
|
log.error('Could not add Plex item to our playlist: %s, %s',
|
||||||
|
child.tag, child.attrib)
|
||||||
|
playqueue.plex_transient_token = transient_token
|
||||||
|
log.debug('Firing up Kodi player')
|
||||||
|
app.APP.player.play(playqueue.kodi_pl, None, False, 0)
|
||||||
|
return playqueue
|
||||||
|
|
||||||
|
|
||||||
|
def compare_playqueues(playqueue, new_kodi_playqueue):
|
||||||
|
"""
|
||||||
|
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
||||||
|
"""
|
||||||
|
old = list(playqueue.items)
|
||||||
|
# We might append to new_kodi_playqueue but will need the original
|
||||||
|
# still back in the main loop
|
||||||
|
new = copy.deepcopy(new_kodi_playqueue)
|
||||||
|
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 app.APP.stop_pkc:
|
||||||
|
# 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 'id' in new_item:
|
||||||
|
identical = (old_item.kodi_id == new_item['id'] and
|
||||||
|
old_item.kodi_type == new_item['type'])
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
plex_id = int(utils.REGEX_PLEX_ID.findall(new_item['file'])[0])
|
||||||
|
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
|
||||||
|
if j == 0 and identical:
|
||||||
|
del old[j], index[j]
|
||||||
|
break
|
||||||
|
elif identical:
|
||||||
|
log.debug('Playqueue item %s moved to position %s',
|
||||||
|
i + j, i)
|
||||||
|
try:
|
||||||
|
PL.move_playlist_item(playqueue, i + j, i)
|
||||||
|
except exceptions.PlaylistError:
|
||||||
|
log.error('Could not modify playqueue positions')
|
||||||
|
log.error('This is likely caused by mixing audio and '
|
||||||
|
'video tracks in the Kodi playqueue')
|
||||||
|
del old[j], index[j]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
log.debug('Detected new Kodi element at position %s: %s ',
|
||||||
|
i, new_item)
|
||||||
|
try:
|
||||||
|
if playqueue.id is None:
|
||||||
|
PL.init_plex_playqueue(playqueue, kodi_item=new_item)
|
||||||
|
else:
|
||||||
|
PL.add_item_to_plex_playqueue(playqueue,
|
||||||
|
i,
|
||||||
|
kodi_item=new_item)
|
||||||
|
except exceptions.PlaylistError:
|
||||||
|
# Could not add the element
|
||||||
|
pass
|
||||||
|
except KeyError:
|
||||||
|
# Catches KeyError from PL.verify_kodi_item()
|
||||||
|
# Hack: Kodi already started playback of a new item and we
|
||||||
|
# started playback already using kodimonitors
|
||||||
|
# PlayBackStart(), but the Kodi playlist STILL only shows
|
||||||
|
# the old element. Hence ignore playlist difference here
|
||||||
|
log.debug('Detected an outdated Kodi playlist - ignoring')
|
||||||
|
return
|
||||||
|
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
|
||||||
|
for i in reversed(index):
|
||||||
|
if app.APP.stop_pkc:
|
||||||
|
# Chances are that we got an empty Kodi playlist due to
|
||||||
|
# Kodi exit
|
||||||
|
return
|
||||||
|
log.debug('Detected deletion of playqueue element at pos %s', i)
|
||||||
|
try:
|
||||||
|
PL.delete_playlist_item_from_PMS(playqueue, i)
|
||||||
|
except exceptions.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')
|
||||||
|
log.debug('Done comparing playqueues')
|
|
@ -5,13 +5,13 @@ import requests
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
from .common import proxy_headers, proxy_params, log_error
|
from .common import proxy_headers, proxy_params, log_error
|
||||||
|
from .playqueue import compare_playqueues
|
||||||
|
|
||||||
from .. import json_rpc as js
|
from .. import json_rpc as js
|
||||||
from .. import variables as v
|
from .. import variables as v
|
||||||
from .. import backgroundthread
|
from .. import backgroundthread
|
||||||
from .. import app
|
from .. import app
|
||||||
from .. import timing
|
from .. import timing
|
||||||
from .. import playqueue as PQ
|
|
||||||
from .. import skip_plex_intro
|
from .. import skip_plex_intro
|
||||||
|
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ def get_correct_position(info, playqueue):
|
||||||
def timeline_dict(playerid, typus):
|
def timeline_dict(playerid, typus):
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
info = app.PLAYSTATE.player_states[playerid]
|
info = app.PLAYSTATE.player_states[playerid]
|
||||||
playqueue = PQ.PLAYQUEUES[playerid]
|
playqueue = app.PLAYQUEUES[playerid]
|
||||||
position = get_correct_position(info, playqueue)
|
position = get_correct_position(info, playqueue)
|
||||||
try:
|
try:
|
||||||
item = playqueue.items[position]
|
item = playqueue.items[position]
|
||||||
|
@ -354,7 +354,21 @@ class PlaystateMgr(backgroundthread.KillableThread):
|
||||||
self.close_requests_session()
|
self.close_requests_session()
|
||||||
if self.wait_while_suspended():
|
if self.wait_while_suspended():
|
||||||
break
|
break
|
||||||
# We will only become active if there's Kodi playback going on
|
# Check for Kodi playlist changes first
|
||||||
|
with app.APP.lock_playqueues:
|
||||||
|
for playqueue in app.PLAYQUEUES:
|
||||||
|
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
||||||
|
if playqueue.old_kodi_pl != kodi_pl:
|
||||||
|
if playqueue.id is None and (not app.SYNC.direct_paths or
|
||||||
|
app.PLAYSTATE.context_menu_play):
|
||||||
|
# Only initialize if directly fired up using direct
|
||||||
|
# paths. Otherwise let default.py do its magic
|
||||||
|
log.debug('Not yet initiating playback')
|
||||||
|
else:
|
||||||
|
# compare old and new playqueue
|
||||||
|
compare_playqueues(playqueue, kodi_pl)
|
||||||
|
playqueue.old_kodi_pl = list(kodi_pl)
|
||||||
|
# Then check for Kodi playback
|
||||||
players = js.get_players()
|
players = js.get_players()
|
||||||
if not players and signaled_playback_stop:
|
if not players and signaled_playback_stop:
|
||||||
self.sleep(1)
|
self.sleep(1)
|
||||||
|
|
|
@ -14,7 +14,6 @@ from .. import plex_functions as PF
|
||||||
from .. import playlist_func as PL
|
from .. import playlist_func as PL
|
||||||
from .. import playback
|
from .. import playback
|
||||||
from .. import json_rpc as js
|
from .. import json_rpc as js
|
||||||
from .. import playqueue as PQ
|
|
||||||
from .. import variables as v
|
from .. import variables as v
|
||||||
from .. import app
|
from .. import app
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
|
@ -87,9 +86,8 @@ def process_playlist(containerKey, typus, key, offset, token):
|
||||||
# Get the playqueue ID
|
# Get the playqueue ID
|
||||||
_, container_key, query = PF.ParseContainerKey(containerKey)
|
_, container_key, query = PF.ParseContainerKey(containerKey)
|
||||||
try:
|
try:
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.from_plex_type(typus)
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[typus])
|
except ValueError:
|
||||||
except KeyError:
|
|
||||||
# E.g. Plex web does not supply the media type
|
# E.g. Plex web does not supply the media type
|
||||||
# Still need to figure out the type (video vs. music vs. pix)
|
# Still need to figure out the type (video vs. music vs. pix)
|
||||||
xml = PF.GetPlexMetadata(key)
|
xml = PF.GetPlexMetadata(key)
|
||||||
|
@ -99,8 +97,7 @@ def process_playlist(containerKey, typus, key, offset, token):
|
||||||
log.error('Could not download Plex metadata')
|
log.error('Could not download Plex metadata')
|
||||||
return
|
return
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.from_plex_type(api.plex_type)
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type])
|
|
||||||
if key:
|
if key:
|
||||||
_, key, _ = PF.ParseContainerKey(key)
|
_, key, _ = PF.ParseContainerKey(key)
|
||||||
update_playqueue_from_PMS(playqueue,
|
update_playqueue_from_PMS(playqueue,
|
||||||
|
@ -111,12 +108,12 @@ def process_playlist(containerKey, typus, key, offset, token):
|
||||||
start_plex_id=key)
|
start_plex_id=key)
|
||||||
|
|
||||||
|
|
||||||
def process_streams(typus, video_stream_id, audio_stream_id, subtitle_stream_id):
|
def process_streams(plex_type, video_stream_id, audio_stream_id,
|
||||||
|
subtitle_stream_id):
|
||||||
"""
|
"""
|
||||||
Plex Companion client adjusted audio or subtitle stream
|
Plex Companion client adjusted audio or subtitle stream
|
||||||
"""
|
"""
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.from_plex_type(plex_type)
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[typus])
|
|
||||||
pos = js.get_position(playqueue.playlistid)
|
pos = js.get_position(playqueue.playlistid)
|
||||||
playqueue.items[pos].on_plex_stream_change(video_stream_id,
|
playqueue.items[pos].on_plex_stream_change(video_stream_id,
|
||||||
audio_stream_id,
|
audio_stream_id,
|
||||||
|
@ -135,12 +132,10 @@ def process_refresh(playqueue_id):
|
||||||
plex_type = PL.get_plextype_from_xml(xml)
|
plex_type = PL.get_plextype_from_xml(xml)
|
||||||
if plex_type is None:
|
if plex_type is None:
|
||||||
return
|
return
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.from_plex_type(plex_type)
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type])
|
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
return
|
return
|
||||||
playqueue = PQ.get_playqueue_from_type(
|
playqueue = app.PLAYQUEUES.from_plex_type(xml[0].attrib['type'])
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']])
|
|
||||||
update_playqueue_from_PMS(playqueue, playqueue_id)
|
update_playqueue_from_PMS(playqueue, playqueue_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -155,7 +150,7 @@ def skip_to(playqueue_item_id, key):
|
||||||
playqueue_item_id, plex_id)
|
playqueue_item_id, plex_id)
|
||||||
found = True
|
found = True
|
||||||
for player in list(js.get_players().values()):
|
for player in list(js.get_players().values()):
|
||||||
playqueue = PQ.PLAYQUEUES[player['playerid']]
|
playqueue = app.PLAYQUEUES[player['playerid']]
|
||||||
for i, item in enumerate(playqueue.items):
|
for i, item in enumerate(playqueue.items):
|
||||||
if item.id == playqueue_item_id:
|
if item.id == playqueue_item_id:
|
||||||
found = True
|
found = True
|
||||||
|
|
|
@ -18,7 +18,7 @@ class Playlists(object):
|
||||||
|
|
||||||
def delete_playlist(self, playlist):
|
def delete_playlist(self, playlist):
|
||||||
"""
|
"""
|
||||||
Removes the entry for playlist [Playqueue_Object] from the Plex
|
Removes the entry for playlist [Playqueue()] from the Plex
|
||||||
playlists table.
|
playlists table.
|
||||||
Be sure to either set playlist.id or playlist.kodi_path
|
Be sure to either set playlist.id or playlist.kodi_path
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,9 +12,8 @@ from . import kodimonitor
|
||||||
from . import sync, library_sync
|
from . import sync, library_sync
|
||||||
from . import websocket_client
|
from . import websocket_client
|
||||||
from . import plex_companion
|
from . import plex_companion
|
||||||
from . import plex_functions as PF, playqueue as PQ
|
from . import plex_functions as PF
|
||||||
from . import playback_starter
|
from . import playback_starter
|
||||||
from . import playqueue
|
|
||||||
from . import variables as v
|
from . import variables as v
|
||||||
from . import app
|
from . import app
|
||||||
from . import loghandler
|
from . import loghandler
|
||||||
|
@ -99,7 +98,6 @@ class Service(object):
|
||||||
self.setup = None
|
self.setup = None
|
||||||
self.pms_ws = None
|
self.pms_ws = None
|
||||||
self.alexa_ws = None
|
self.alexa_ws = None
|
||||||
self.playqueue = None
|
|
||||||
# Flags for other threads
|
# Flags for other threads
|
||||||
self.connection_check_running = False
|
self.connection_check_running = False
|
||||||
self.auth_running = False
|
self.auth_running = False
|
||||||
|
@ -436,8 +434,6 @@ class Service(object):
|
||||||
app.init()
|
app.init()
|
||||||
app.APP.monitor = kodimonitor.KodiMonitor()
|
app.APP.monitor = kodimonitor.KodiMonitor()
|
||||||
app.APP.player = xbmc.Player()
|
app.APP.player = xbmc.Player()
|
||||||
# Initialize the PKC playqueues
|
|
||||||
PQ.init_playqueues()
|
|
||||||
|
|
||||||
# Server auto-detect
|
# Server auto-detect
|
||||||
self.setup = initialsetup.InitialSetup()
|
self.setup = initialsetup.InitialSetup()
|
||||||
|
@ -452,7 +448,6 @@ class Service(object):
|
||||||
self.companion_listener = plex_companion.Listener(self.companion_playstate_mgr)
|
self.companion_listener = plex_companion.Listener(self.companion_playstate_mgr)
|
||||||
else:
|
else:
|
||||||
self.companion_listener = None
|
self.companion_listener = None
|
||||||
self.playqueue = playqueue.PlayqueueMonitor()
|
|
||||||
|
|
||||||
# Main PKC program loop
|
# Main PKC program loop
|
||||||
while not self.should_cancel():
|
while not self.should_cancel():
|
||||||
|
@ -554,7 +549,6 @@ class Service(object):
|
||||||
self.companion_playstate_mgr.start()
|
self.companion_playstate_mgr.start()
|
||||||
if self.companion_listener is not None:
|
if self.companion_listener is not None:
|
||||||
self.companion_listener.start()
|
self.companion_listener.start()
|
||||||
self.playqueue.start()
|
|
||||||
self.alexa_ws.start()
|
self.alexa_ws.start()
|
||||||
|
|
||||||
xbmc.sleep(200)
|
xbmc.sleep(200)
|
||||||
|
|
Loading…
Reference in a new issue