Playqueues overhaul continued

This commit is contained in:
tomkat83 2016-12-28 13:14:21 +01:00
parent 0c2d4984ab
commit 9d2902baa5
8 changed files with 216 additions and 139 deletions

View File

@ -1646,7 +1646,7 @@ class API():
If not found, empty str is returned
"""
return self.item.attrib.get('playQueueItemID', '')
return self.item.attrib.get('playQueueItemID')
def getDataFromPartOrMedia(self, key):
"""

View File

@ -9,8 +9,7 @@ from xbmc import sleep
from utils import settings, ThreadMethodsAdditionalSuspend, ThreadMethods
from plexbmchelper import listener, plexgdm, subscribers, functions, \
httppersist, plexsettings
from PlexFunctions import ParseContainerKey, GetPlayQueue, \
ConvertPlexToKodiTime
from PlexFunctions import ParseContainerKey
import player
###############################################################################
@ -29,7 +28,6 @@ class PlexCompanion(Thread):
log.info("----===## Starting PlexCompanion ##===----")
if callback is not None:
self.mgr = callback
self.playqueue = self.mgr.playqueue
self.settings = plexsettings.getSettings()
# Start GDM for server/client discovery
self.client = plexgdm.plexgdm()
@ -60,12 +58,18 @@ class PlexCompanion(Thread):
def processTasks(self, task):
"""
Processes tasks picked up e.g. by Companion listener
task = {
'action': 'playlist'
'data': as received from Plex companion
}
Processes tasks picked up e.g. by Companion listener, e.g.
{'action': 'playlist',
'data': {'address': 'xyz.plex.direct',
'commandID': '7',
'containerKey': '/playQueues/6669?own=1&repeat=0&window=200',
'key': '/library/metadata/220493',
'machineIdentifier': 'xyz',
'offset': '0',
'port': '32400',
'protocol': 'https',
'token': 'transient-cd2527d1-0484-48e0-a5f7-f5caa7d591bd',
'type': 'video'}}
"""
log.debug('Processing: %s' % task)
data = task['data']
@ -79,36 +83,11 @@ class PlexCompanion(Thread):
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
return
self.mgr.playqueue.update_playqueue_with_companion(data)
self.playqueue = self.mgr.playqueue.get_playqueue_from_plextype(
data.get('type'))
if queueId != self.playqueue.ID:
log.info('New playlist received, updating!')
xml = GetPlayQueue(queueId)
if xml in (None, 401):
log.error('Could not download Plex playlist.')
return
# Clear existing playlist on the Kodi side
self.playqueue.clear()
# Set new values
self.playqueue.QueueId(queueId)
self.playqueue.PlayQueueVersion(int(
xml.attrib.get('playQueueVersion')))
self.playqueue.Guid(xml.attrib.get('guid'))
items = []
for item in xml:
items.append({
'playQueueItemID': item.get('playQueueItemID'),
'plexId': item.get('ratingKey'),
'kodiId': None})
self.playqueue.playAll(
items,
startitem=self._getStartItem(data.get('key', '')),
offset=ConvertPlexToKodiTime(data.get('offset', 0)))
log.info('Initiated playlist no %s with version %s'
% (self.playqueue.QueueId(),
self.playqueue.PlayQueueVersion()))
playqueue = self.mgr.playqueue.get_playqueue_from_type(
data['type'])
if ID != playqueue.ID:
self.mgr.playqueue.update_playqueue_from_PMS(
playqueue, ID, int(query['repeat']))
else:
log.error('This has never happened before!')
@ -123,7 +102,7 @@ class PlexCompanion(Thread):
requestMgr = httppersist.RequestMgr()
jsonClass = functions.jsonClass(requestMgr, self.settings)
subscriptionManager = subscribers.SubscriptionManager(
jsonClass, requestMgr, self.player, self.playqueue)
jsonClass, requestMgr, self.player, self.mgr)
queue = Queue.Queue(maxsize=100)

View File

@ -171,22 +171,6 @@ def SelectStreams(url, args):
url + '?' + urlencode(args), action_type='PUT')
def GetPlayQueue(playQueueID):
"""
Fetches the PMS playqueue with the playQueueID as an XML
Returns None if something went wrong
"""
url = "{server}/playQueues/%s" % playQueueID
args = {'Accept': 'application/xml'}
xml = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=args)
try:
xml.attrib['playQueueID']
except (AttributeError, KeyError):
return None
return xml
def GetPlexMetadata(key):
"""
Returns raw API metadata for key as an etree XML.

View File

@ -168,10 +168,10 @@ class KodiMonitor(xbmc.Monitor):
pass
elif method == "Playlist.OnAdd":
# User manipulated Kodi playlist
# User (or PKC) manipulated Kodi playlist
# Data : {u'item': {u'type': u'movie', u'id': 3}, u'playlistid': 1,
# u'position': 0}
self.playlist.kodi_onadd(data)
self.playqueue.kodi_onadd(data)
def PlayBackStart(self, data):
"""

View File

@ -37,7 +37,8 @@ class PlaybackUtils():
self.userid = window('currUserId')
self.server = window('pms_server')
self.pl = Playqueue().get_playqueue_from_plextype(self.API.getType())
self.pl = Playqueue().get_playqueue_from_type(
PF.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[self.API.getType()])
def play(self, itemid, dbid=None):

View File

@ -4,6 +4,7 @@ from urllib import quote
import embydb_functions as embydb
from downloadutils import DownloadUtils as DU
from utils import JSONRPC, tryEncode
from PlexAPI import API
###############################################################################
@ -24,13 +25,35 @@ class Playlist_Object_Baseclase(object):
selectedItemOffset = None
shuffled = 0 # [int], 0: not shuffled, 1: ??? 2: ???
repeat = 0 # [int], 0: not repeated, 1: ??? 2: ???
# Hack to later ignore all Kodi playlist adds that PKC did (Kodimonitor)
PKC_playlist_edits = []
def __repr__(self):
answ = "<%s object: " % (self.__class__.__name__)
answ = "<%s: " % (self.__class__.__name__)
for key in self.__dict__:
answ += '%s: %s, ' % (key, getattr(self, key))
return answ[:-2] + ">"
def clear(self):
"""
Resets the playlist object to an empty playlist
"""
# Clear Kodi playlist object
self.kodi_pl.clear()
self.items = []
self.old_kodi_pl = []
self.ID = None
self.version = None
self.selectedItemID = None
self.selectedItemOffset = None
self.shuffled = 0
self.repeat = 0
self.PKC_playlist_edits = []
log.debug('Playlist cleared: %s' % self)
def log_Kodi_playlist(self):
log.debug('Current Kodi playlist: %s' % get_kodi_playlist_items(self))
class Playlist_Object(Playlist_Object_Baseclase):
kind = 'playList'
@ -48,6 +71,13 @@ class Playlist_Item(object):
kodi_type = None # Kodi type: 'movie'
file = None # Path to the item's file
uri = None # Weird Plex uri path involving plex_UUID
guid = None # Weird Plex guid
def __repr__(self):
answ = "<%s: " % (self.__class__.__name__)
for key in self.__dict__:
answ += '%s: %s, ' % (key, getattr(self, key))
return answ[:-2] + ">"
def playlist_item_from_kodi_item(kodi_item):
@ -127,8 +157,10 @@ def _get_playlist_details_from_xml(playlist, xml):
try:
playlist.ID = xml.attrib['%sID' % playlist.kind]
playlist.version = xml.attrib['%sVersion' % playlist.kind]
playlist.selectedItemID = xml.attrib['%sSelectedItemID' % playlist.kind]
playlist.selectedItemOffset = xml.attrib['%sSelectedItemOffset' % playlist.kind]
playlist.selectedItemID = xml.attrib['%sSelectedItemID'
% playlist.kind]
playlist.selectedItemOffset = xml.attrib['%sSelectedItemOffset'
% playlist.kind]
playlist.shuffled = xml.attrib['%sShuffled' % playlist.kind]
except:
log.error('Could not parse xml answer from PMS for playlist %s'
@ -141,9 +173,9 @@ def _get_playlist_details_from_xml(playlist, xml):
def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
"""
Supply either plex_id or the data supplied by Kodi JSON-RPC
Supply either with a plex_id OR the data supplied by Kodi JSON-RPC
"""
if plex_id is not None:
if plex_id:
item = playlist_item_from_plex(plex_id)
else:
item = playlist_item_from_kodi_item(kodi_item)
@ -155,7 +187,7 @@ def init_Plex_playlist(playlist, plex_id=None, kodi_item=None):
xml = DU().downloadUrl(url="{server}/%ss" % playlist.kind,
action_type="POST",
parameters=params)
_get_playlist_details_from_xml(xml)
_get_playlist_details_from_xml(playlist, xml)
playlist.items.append(item)
log.debug('Initialized the playlist: %s' % playlist)
@ -176,6 +208,10 @@ def add_playlist_item(playlist, kodi_item, after_pos):
% (kodi_item, playlist))
_log_xml(xml)
return
# Get the guid for this item
for plex_item in xml:
if plex_item.attrib['%sItemID' % playlist.kind] == item.ID:
item.guid = plex_item.attrib['guid']
playlist.items.append(item)
if after_pos == len(playlist.items) - 1:
# Item was added at the end
@ -259,6 +295,39 @@ def get_kodi_playqueues():
# Functions operating on the Kodi playlist objects ##########
def add_to_Kodi_playlist(playlist, xml_video_element):
"""
Adds a new item to the Kodi playlist via JSON (at the end of the playlist).
Pass in the PMS xml's video element (one level underneath MediaContainer).
Will return a Playlist_Item
"""
item = Playlist_Item()
api = API(xml_video_element)
params = {
'playlistid': playlist.playlistid
}
item.plex_id = api.getRatingKey()
item.ID = xml_video_element.attrib['%sItemID' % playlist.kind]
item.guid = xml_video_element.attrib.get('guid')
if item.plex_id:
with embydb.GetEmbyDB() as emby_db:
db_element = emby_db.getItem_byId(item.plex_id)
try:
item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4]
except TypeError:
pass
if item.kodi_id:
params['item'] = {'%sid' % item.kodi_type: item.kodi_id}
else:
item.file = api.getFilePath()
params['item'] = {'file': tryEncode(item.file)}
log.debug(JSONRPC('Playlist.Add').execute(params))
playlist.PKC_playlist_edits.append(
item.kodi_id if item.kodi_id else item.file)
return item
def insertintoPlaylist(self,
position,
dbid=None,
@ -275,57 +344,51 @@ def insertintoPlaylist(self,
JSONRPC('Playlist.Insert').execute(params)
def addtoPlaylist(self, dbid=None, mediatype=None, url=None):
params = {
'playlistid': self.playlistId
}
if dbid is not None:
params['item'] = {'%sid' % tryEncode(mediatype): int(dbid)}
else:
params['item'] = {'file': url}
JSONRPC('Playlist.Add').execute(params)
def removefromPlaylist(self, position):
params = {
'playlistid': self.playlistId,
'position': position
}
JSONRPC('Playlist.Remove').execute(params)
log.debug(JSONRPC('Playlist.Remove').execute(params))
def playAll(self, items, startitem, offset):
def get_PMS_playlist(playlist, playlist_id=None):
"""
items: list of dicts of the form
{
'playQueueItemID': Plex playQueueItemID, e.g. '29175'
'plexId': Plex ratingKey, e.g. '125'
'kodiId': Kodi's db id of the same item
}
Fetches the PMS playlist/playqueue as an XML. Pass in playlist_id if we
need to fetch a new playlist
startitem: tuple (typus, id), where typus is either
'playQueueItemID' or 'plexId' and id is the corresponding
id as a string
offset: First item's time offset to play in Kodi time (an int)
Returns None if something went wrong
"""
log.info("---*** PLAY ALL ***---")
log.debug('Startitem: %s, offset: %s, items: %s'
% (startitem, offset, items))
self.items = items
if self.playlist is None:
self._initiatePlaylist()
if self.playlist is None:
log.error('Could not create playlist, abort')
playlist_id = playlist_id if playlist_id else playlist.ID
xml = DU().downloadUrl(
"{server}/%ss/%s" % (playlist.kind, playlist_id),
headerOptions={'Accept': 'application/xml'})
try:
xml.attrib['%sID' % playlist.kind]
except (AttributeError, KeyError):
xml = None
return xml
def update_playlist_from_PMS(playlist, playlist_id=None, repeat=None):
"""
Updates Kodi playlist using a new PMS playlist. Pass in playlist_id if we
need to fetch a new playqueue
"""
xml = get_PMS_playlist(playlist, playlist_id)
try:
xml.attrib['%sVersion' % playlist.kind]
except:
log.error('Could not download Plex playlist.')
return
window('plex_customplaylist', value="true")
if offset != 0:
# Seek to the starting position
window('plex_customplaylist.seektime', str(offset))
self._processItems(startitem, startPlayer=True)
# Log playlist
self._verifyPlaylist()
log.debug('Internal playlist: %s' % self.items)
# Clear our existing playlist and the associated Kodi playlist
playlist.clear()
# Set new values
_get_playlist_details_from_xml(playlist, xml)
if repeat:
playlist.repeat = repeat
for plex_item in xml:
playlist.items.append(add_to_Kodi_playlist(playlist, plex_item))
def _processItems(self, startitem, startPlayer=False):
@ -380,15 +443,3 @@ def _addtoPlaylist_xbmc(self, item):
playbackutils.PlaybackUtils(item).setArtwork(listitem)
self.playlist.add(playurl, listitem)
def clear(self):
"""
Empties current Kodi playlist and associated variables
"""
self.playlist.clear()
self.items = []
self.queueId = None
self.playQueueVersion = None
self.guid = None
log.info('Playlist cleared')

View File

@ -5,10 +5,10 @@ from threading import Lock, Thread
import xbmc
from utils import ThreadMethods, ThreadMethodsAdditionalSuspend, Lock_Function
from utils import window, ThreadMethods, ThreadMethodsAdditionalSuspend, \
Lock_Function
import playlist_func as PL
from PlexFunctions import KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE, GetPlayQueue, \
ParseContainerKey
from PlexFunctions import ConvertPlexToKodiTime
###############################################################################
log = logging.getLogger("PLEX."+__name__)
@ -36,6 +36,7 @@ class Playqueue(Thread):
if self.playqueues is not None:
return
self.mgr = callback
self.player = xbmc.Player()
# Initialize Kodi playqueues
self.playqueues = []
@ -52,8 +53,64 @@ class Playqueue(Thread):
# Currently, only video or audio playqueues available
playqueue.kodi_pl = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
self.playqueues.append(playqueue)
# sort the list by their playlistid, just in case
self.playqueues = sorted(
self.playqueues, key=lambda i: i.playlistid)
log.debug('Initialized the Kodi play queues: %s' % self.playqueues)
def get_playqueue_from_type(self, typus):
"""
Returns the playqueue according to the typus ('video', 'audio',
'picture') passed in
"""
for playqueue in self.playqueues:
if playqueue.type == typus:
break
else:
raise ValueError('Wrong type was passed in: %s' % typus)
return playqueue
def get_playqueue_from_playerid(self, kodi_player_id):
for playqueue in self.playqueues:
if playqueue.playlistid == kodi_player_id:
break
else:
raise ValueError('Wrong kodi_player_id passed was passed in: %s'
% kodi_player_id)
return playqueue
@lockmethod.lockthis
def update_playqueue_from_PMS(self,
playqueue,
playqueue_id=None,
repeat=None):
"""
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
"""
log.info('New playqueue received, updating!')
PL.update_playlist_from_PMS(playqueue, playqueue_id, repeat)
log.debug('Updated playqueue: %s' % playqueue)
window('plex_customplaylist', value="true")
if playqueue.selectedItemOffset not in (None, "0"):
window('plex_customplaylist.seektime',
str(ConvertPlexToKodiTime(playqueue.selectedItemOffset)))
for startpos, item in enumerate(playqueue.items):
if item.ID == playqueue.selectedItemID:
break
else:
startpos = None
# Start playback
if startpos:
self.player.play(playqueue.kodi_pl, startpos=startpos)
else:
self.player.play(playqueue.kodi_pl)
log.debug('Playqueue at the end: %s' % playqueue)
playqueue.log_Kodi_playlist()
@lockmethod.lockthis
def update_playqueue_with_companion(self, data):
"""
@ -61,10 +118,6 @@ class Playqueue(Thread):
"""
# Get the correct queue
for playqueue in self.playqueues:
if playqueue.type == KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[
data['type']]:
break
@lockmethod.lockthis
def kodi_onadd(self, data):
@ -80,11 +133,20 @@ class Playqueue(Thread):
for playqueue in self.playqueues:
if playqueue.playlistid == data['playlistid']:
break
if playqueue.PKC_playlist_edits:
old = (data['item'].get('id') if data['item'].get('id')
else data['item'].get('file'))
for i, item in enumerate(playqueue.PKC_playlist_edits):
if old == item:
log.debug('kodimonitor told us of a PKC edit - ignore.')
del playqueue.PKC_playlist_edits[i]
return
if playqueue.ID is None:
# Need to initialize the queue for the first time
PL.init_Plex_playlist(playqueue, kodi_item=data['item'])
else:
PL.add_playlist_item(playqueue, data['item'], data['position'])
log.debug('Added a new item to the playqueue: %s' % playqueue)
@lockmethod.lockthis
def _compare_playqueues(self, playqueue, new):

View File

@ -15,7 +15,7 @@ log = logging.getLogger("PLEX."+__name__)
class SubscriptionManager:
def __init__(self, jsonClass, RequestMgr, player, playlist):
def __init__(self, jsonClass, RequestMgr, player, mgr):
self.serverlist = []
self.subscribers = {}
self.info = {}
@ -36,7 +36,7 @@ class SubscriptionManager:
self.playerprops = {}
self.doUtils = downloadutils.DownloadUtils().downloadUrl
self.xbmcplayer = player
self.playlist = playlist
self.playqueue = mgr.playqueue
self.js = jsonClass
self.RequestMgr = RequestMgr
@ -231,6 +231,8 @@ class SubscriptionManager:
def getPlayerProperties(self, playerid):
try:
# Get the playqueue
playqueue = self.playqueue.playqueues[playerid]
# get info from the player
props = self.js.jsonrpc(
"Player.GetProperties",
@ -248,18 +250,16 @@ class SubscriptionManager:
'shuffle': ("0", "1")[props.get('shuffled', False)],
'repeat': pf.getPlexRepeat(props.get('repeat')),
}
if self.playlist is not None:
if self.playlist.QueueId() is not None:
info['playQueueID'] = self.playlist.QueueId()
info['playQueueVersion'] = self.playlist.PlayQueueVersion()
info['guid'] = self.playlist.Guid()
# Get the playlist position
pos = self.js.jsonrpc(
"Player.GetProperties",
{"playerid": playerid,
"properties": ["position"]})
info['playQueueItemID'] = \
self.playlist.getQueueIdFromPosition(pos['position'])
if playqueue.ID is not None:
info['playQueueID'] = playqueue.ID
info['playQueueVersion'] = playqueue.version
# Get the playlist position
pos = self.js.jsonrpc(
"Player.GetProperties",
{"playerid": playerid,
"properties": ["position"]})['position']
info['playQueueItemID'] = playqueue.items[pos].ID
info['guid'] = playqueue.items[pos].guid
except:
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())