Redesign playlists

This commit is contained in:
tomkat83 2016-07-20 18:36:31 +02:00
parent 5d79bcf1c2
commit 8a58c885e9
6 changed files with 276 additions and 134 deletions

View File

@ -14,7 +14,10 @@ from plexbmchelper import listener, plexgdm, subscribers, functions, \
@utils.ThreadMethodsAdditionalSuspend('plex_serverStatus')
@utils.ThreadMethods
class PlexCompanion(threading.Thread):
def __init__(self):
"""
Initialize with a Queue for callbacks
"""
def __init__(self, queue):
self.logMsg("----===## Starting PlexCompanion ##===----", 1)
self.settings = settings.getSettings()
@ -24,6 +27,8 @@ class PlexCompanion(threading.Thread):
self.logMsg("Registration string is: %s "
% self.client.getClientDetails(), 2)
self.queue = queue
threading.Thread.__init__(self)
def run(self):
@ -51,6 +56,7 @@ class PlexCompanion(threading.Thread):
subscriptionManager,
jsonClass,
self.settings,
self.queue,
('', self.settings['myport']),
listener.MyHandler)
httpd.timeout = 0.95

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from urllib import urlencode
from ast import literal_eval
from urlparse import urlparse, parse_qs
from urlparse import urlparse, parse_qsl
import re
from copy import deepcopy
@ -86,13 +86,13 @@ def GetPlexKeyNumber(plexKey):
def ParseContainerKey(containerKey):
"""
Parses e.g. /playQueues/3045?own=1&repeat=0&window=200 to:
'playQueues', '3045', {'window': ['200'], 'own': ['1'], 'repeat': ['0']}
'playQueues', '3045', {'window': '200', 'own': '1', 'repeat': '0'}
Output hence: library, key, query (query as a special dict)
Output hence: library, key, query (str, str, dict)
"""
result = urlparse(containerKey)
library, key = GetPlexKeyNumber(result.path)
query = parse_qs(result.query)
query = dict(parse_qsl(result.query))
return library, key, query

View File

@ -24,22 +24,37 @@ class Playlist():
def __init__(self, typus=None):
self.userid = utils.window('currUserId')
self.server = utils.window('pms_server')
# Construct the Kodi playlist instance
if typus == 'video':
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
self.typus = 'video'
self.logMsg('Initiated video playlist', 1)
elif typus == 'music':
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
self.typus = 'music'
self.logMsg('Initiated music playlist', 1)
else:
self.playlist = None
self.typus = None
if self.playlist is not None:
self.playlistId = self.playlist.getPlayListId()
# "interal" PKC playlist
self.items = []
def _initiatePlaylist(self, itemids):
def clear(self):
"""
Empties current Kodi playlist and internal self.items list
"""
self.logMsg('Clearing playlist', 1)
self.playlist.clear()
self.items = []
def _initiatePlaylist(self):
self.logMsg('Initiating playlist', 1)
playlist = None
with embydb.GetEmbyDB() as emby_db:
for itemid in itemids:
for item in self.items:
itemid = item['plexId']
embydb_item = emby_db.getItem_byId(itemid)
try:
mediatype = embydb_item[4]
@ -54,63 +69,95 @@ class Playlist():
if PlexAPI.API(item[0]).getType() == 'track':
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
self.logMsg('Music playlist initiated', 1)
self.typus = 'music'
else:
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
self.logMsg('Video playlist initiated', 1)
self.typus = 'video'
else:
if mediatype == 'song':
playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
self.logMsg('Music playlist initiated', 1)
self.typus = 'music'
else:
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
self.logMsg('Video playlist initiated', 1)
self.typus = 'video'
break
self.playlist = playlist
if self.playlist is not None:
self.playlistId = self.playlist.getPlayListId()
def _addToPlaylist(self, itemids, startPlayer=False):
def _addToPlaylist(self, startitem, startPlayer=False):
started = False
with embydb.GetEmbyDB() as emby_db:
for itemid in itemids:
embydb_item = emby_db.getItem_byId(itemid)
for pos, item in enumerate(self.items):
kodiId = None
plexId = item['plexId']
embydb_item = emby_db.getItem_byId(plexId)
try:
dbid = embydb_item[0]
kodiId = embydb_item[0]
mediatype = embydb_item[4]
except TypeError:
self.logMsg('Couldnt find item %s in Kodi db' % itemid, 1)
item = PlexFunctions.GetPlexMetadata(itemid)
if item in (None, 401):
self.logMsg('Could not download itemid %s'
% itemid, -1)
self.logMsg('Couldnt find item %s in Kodi db' % plexId, 1)
xml = PlexFunctions.GetPlexMetadata(plexId)
if xml in (None, 401):
self.logMsg('Could not download plexId %s'
% plexId, -1)
else:
self.logMsg('Downloaded item metadata, adding now', 1)
self._addtoPlaylist_xbmc(item[0])
self.logMsg('Downloaded xml metadata, adding now', 1)
self._addtoPlaylist_xbmc(xml[0])
else:
# Add to playlist
self.logMsg("Adding %s PlexId %s, KodiId %s to playlist."
% (mediatype, itemid, dbid), 1)
self.addtoPlaylist(dbid, mediatype)
if started is False and startPlayer is True:
% (mediatype, plexId, kodiId), 1)
self.addtoPlaylist(kodiId, mediatype)
# Add the kodiId
if kodiId is not None:
item['kodiId'] = str(kodiId)
if (started is False and
startPlayer is True and
startitem[1] == item[startitem[0]]):
started = True
xbmc.Player().play(self.playlist)
xbmc.Player().play(self.playlist, startpos=pos)
if (started is False and
startPlayer is True and
len(self.playlist) > 0):
self.logMsg('Never received a starting item for playlist, '
'starting with the first entry', 1)
xbmc.Player().play(self.playlist)
def playAll(self, itemids, startat):
def playAll(self, items, startitem, offset):
"""
items: list of dicts of the form
{
'queueId': Plex playQueueItemID, e.g. '29175'
'plexId': Plex ratingKey, e.g. '125'
'kodiId': Kodi's db id of the same item
}
startitem: tuple (typus, id), where typus is either 'queueId' or
'plexId' and id is the corresponding id as a string
offset: First item's time offset to play in Kodi time (an int)
"""
self.logMsg("---*** PLAY ALL ***---", 1)
self.logMsg("Items: %s and start at: %s" % (itemids, startat), 1)
self.logMsg('Startitem: %s, offset: %s, items: %s'
% (startitem, offset, items), 1)
self.items = items
if self.playlist is None:
self._initiatePlaylist(itemids)
self._initiatePlaylist()
if self.playlist is None:
self.logMsg('Could not create playlist, abort', -1)
return
utils.window('plex_customplaylist', value="true")
if startat != 0:
if offset != 0:
# Seek to the starting position
utils.window('plex_customplaylist.seektime', str(startat))
self._addToPlaylist(itemids, startPlayer=True)
utils.window('plex_customplaylist.seektime', str(offset))
self._addToPlaylist(startitem, startPlayer=True)
# Log playlist
self.verifyPlaylist()
self.logMsg('Internal playlist: %s' % self.items, 2)
def modifyPlaylist(self, itemids):
self.logMsg("---*** ADD TO PLAYLIST ***---", 1)
@ -126,7 +173,6 @@ class Playlist():
mediatype: Kodi type: 'movie', 'episode', 'musicvideo', 'artist',
'album', 'song', 'genre'
"""
pl = {
'jsonrpc': "2.0",
'id': 1,

View File

@ -106,11 +106,6 @@ class jsonClass():
"id": 1,
"method": "JSONRPC.Ping"
})
elif action.lower() == "playmedia":
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
"?mode=companion&arguments=%s"
% arguments)
return True
elif arguments:
request = json.dumps({
"id": 1,

View File

@ -73,9 +73,10 @@ class MyHandler(BaseHTTPRequestHandler):
def answer_request(self, sendData):
self.serverlist = self.server.client.getServerList()
self.subMgr = self.server.subscriptionManager
self.js = self.server.jsonClass
self.settings = self.server.settings
subMgr = self.server.subscriptionManager
js = self.server.jsonClass
settings = self.server.settings
queue = self.server.queue
try:
request_path = self.path[1:]
@ -87,17 +88,17 @@ class MyHandler(BaseHTTPRequestHandler):
params[key] = paramarrays[key][0]
self.logMsg("remote request_path: %s" % request_path, 2)
self.logMsg("params received from remote: %s" % params, 2)
self.subMgr.updateCommandID(self.headers.get(
subMgr.updateCommandID(self.headers.get(
'X-Plex-Client-Identifier',
self.client_address[0]),
params.get('commandID', False))
if request_path == "version":
self.response(
"PlexKodiConnect Plex Companion: Running\r\nVersion: %s"
% self.settings['version'])
% settings['version'])
elif request_path == "verify":
self.response("XBMC JSON connection test:\r\n" +
self.js.jsonrpc("ping"))
js.jsonrpc("ping"))
elif "resources" == request_path:
resp = ('%s'
'<MediaContainer>'
@ -114,24 +115,24 @@ class MyHandler(BaseHTTPRequestHandler):
'/>'
'</MediaContainer>'
% (getXMLHeader(),
self.settings['client_name'],
self.settings['uuid'],
self.settings['platform'],
self.settings['plexbmc_version']))
settings['client_name'],
settings['uuid'],
settings['platform'],
settings['plexbmc_version']))
self.logMsg("crafted resources response: %s" % resp, 2)
self.response(resp, self.js.getPlexHeaders())
self.response(resp, js.getPlexHeaders())
elif "/subscribe" in request_path:
self.response(getOKMsg(), self.js.getPlexHeaders())
self.response(getOKMsg(), js.getPlexHeaders())
protocol = params.get('protocol', False)
host = self.client_address[0]
port = params.get('port', False)
uuid = self.headers.get('X-Plex-Client-Identifier', "")
commandID = params.get('commandID', 0)
self.subMgr.addSubscriber(protocol,
host,
port,
uuid,
commandID)
subMgr.addSubscriber(protocol,
host,
port,
uuid,
commandID)
elif "/poll" in request_path:
if params.get('wait', False) == '1':
sleep(950)
@ -139,28 +140,28 @@ class MyHandler(BaseHTTPRequestHandler):
self.response(
re.sub(r"INSERTCOMMANDID",
str(commandID),
self.subMgr.msg(self.js.getPlayers())),
subMgr.msg(js.getPlayers())),
{
'X-Plex-Client-Identifier': self.settings['uuid'],
'X-Plex-Client-Identifier': settings['uuid'],
'Access-Control-Expose-Headers':
'X-Plex-Client-Identifier',
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/xml'
})
elif "/unsubscribe" in request_path:
self.response(getOKMsg(), self.js.getPlexHeaders())
self.response(getOKMsg(), js.getPlexHeaders())
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
or self.client_address[0]
self.subMgr.removeSubscriber(uuid)
subMgr.removeSubscriber(uuid)
elif request_path == "player/playback/setParameters":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.response(getOKMsg(), js.getPlexHeaders())
if 'volume' in params:
volume = int(params['volume'])
self.logMsg("adjusting the volume to %s%%" % volume, 2)
self.js.jsonrpc("Application.SetVolume",
{"volume": volume})
js.jsonrpc("Application.SetVolume",
{"volume": volume})
elif "/playMedia" in request_path:
self.response(getOKMsg(), self.js.getPlexHeaders())
self.response(getOKMsg(), js.getPlexHeaders())
offset = params.get('viewOffset', params.get('offset', "0"))
protocol = params.get('protocol', "http")
address = params.get('address', self.client_address[0])
@ -174,90 +175,93 @@ class MyHandler(BaseHTTPRequestHandler):
playQueueID = self.regex.findall(containerKey)[0]
except IndexError:
playQueueID = ''
self.js.jsonrpc("playmedia", params)
self.subMgr.lastkey = params['key']
self.subMgr.containerKey = containerKey
self.subMgr.playQueueID = playQueueID
self.subMgr.server = server.get('server', 'localhost')
self.subMgr.port = port
self.subMgr.protocol = protocol
self.subMgr.notify()
# We need to tell service.py
queue.put({
'action': 'playlist',
'data': params
})
subMgr.lastkey = params['key']
subMgr.containerKey = containerKey
subMgr.playQueueID = playQueueID
subMgr.server = server.get('server', 'localhost')
subMgr.port = port
subMgr.protocol = protocol
subMgr.notify()
elif request_path == "player/playback/play":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.PlayPause",
{"playerid": playerid, "play": True})
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.PlayPause",
{"playerid": playerid, "play": True})
elif request_path == "player/playback/pause":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.PlayPause",
{"playerid": playerid, "play": False})
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.PlayPause",
{"playerid": playerid, "play": False})
elif request_path == "player/playback/stop":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.Stop", {"playerid": playerid})
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.Stop", {"playerid": playerid})
elif request_path == "player/playback/seekTo":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.Seek",
{"playerid": playerid,
"value": millisToTime(
params.get('offset', 0))})
self.subMgr.notify()
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.Seek",
{"playerid": playerid,
"value": millisToTime(
params.get('offset', 0))})
subMgr.notify()
elif request_path == "player/playback/stepForward":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.Seek",
{"playerid": playerid,
"value": "smallforward"})
self.subMgr.notify()
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.Seek",
{"playerid": playerid,
"value": "smallforward"})
subMgr.notify()
elif request_path == "player/playback/stepBack":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.Seek",
{"playerid": playerid,
"value": "smallbackward"})
self.subMgr.notify()
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.Seek",
{"playerid": playerid,
"value": "smallbackward"})
subMgr.notify()
elif request_path == "player/playback/skipNext":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.GoTo",
{"playerid": playerid,
"to": "next"})
self.subMgr.notify()
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.GoTo",
{"playerid": playerid,
"to": "next"})
subMgr.notify()
elif request_path == "player/playback/skipPrevious":
self.response(getOKMsg(), self.js.getPlexHeaders())
for playerid in self.js.getPlayerIds():
self.js.jsonrpc("Player.GoTo",
{"playerid": playerid,
"to": "previous"})
self.subMgr.notify()
self.response(getOKMsg(), js.getPlexHeaders())
for playerid in js.getPlayerIds():
js.jsonrpc("Player.GoTo",
{"playerid": playerid,
"to": "previous"})
subMgr.notify()
elif request_path == "player/playback/skipTo":
self.js.skipTo(params.get('key').rsplit('/', 1)[1],
params.get('type'))
self.subMgr.notify()
js.skipTo(params.get('key').rsplit('/', 1)[1],
params.get('type'))
subMgr.notify()
elif request_path == "player/navigation/moveUp":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.js.jsonrpc("Input.Up")
self.response(getOKMsg(), js.getPlexHeaders())
js.jsonrpc("Input.Up")
elif request_path == "player/navigation/moveDown":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.js.jsonrpc("Input.Down")
self.response(getOKMsg(), js.getPlexHeaders())
js.jsonrpc("Input.Down")
elif request_path == "player/navigation/moveLeft":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.js.jsonrpc("Input.Left")
self.response(getOKMsg(), js.getPlexHeaders())
js.jsonrpc("Input.Left")
elif request_path == "player/navigation/moveRight":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.js.jsonrpc("Input.Right")
self.response(getOKMsg(), js.getPlexHeaders())
js.jsonrpc("Input.Right")
elif request_path == "player/navigation/select":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.js.jsonrpc("Input.Select")
self.response(getOKMsg(), js.getPlexHeaders())
js.jsonrpc("Input.Select")
elif request_path == "player/navigation/home":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.js.jsonrpc("Input.Home")
self.response(getOKMsg(), js.getPlexHeaders())
js.jsonrpc("Input.Home")
elif request_path == "player/navigation/back":
self.response(getOKMsg(), self.js.getPlexHeaders())
self.js.jsonrpc("Input.Back")
self.response(getOKMsg(), js.getPlexHeaders())
js.jsonrpc("Input.Back")
else:
self.logMsg('Unknown request path: %s' % request_path, -1)
# elif 'player/mirror/details' in request_path:
@ -290,7 +294,7 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True
def __init__(self, client, subscriptionManager, jsonClass, settings,
*args, **kwargs):
queue, *args, **kwargs):
"""
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
date serverlist without instantiating anything
@ -301,4 +305,5 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
self.subscriptionManager = subscriptionManager
self.jsonClass = jsonClass
self.settings = settings
self.queue = queue
HTTPServer.__init__(self, *args, **kwargs)

View File

@ -43,9 +43,11 @@ import player
import videonodes
import websocket_client as wsc
import downloadutils
import playlist
import PlexAPI
import PlexCompanion
import PlexFunctions as PF
###############################################################################
@ -105,10 +107,14 @@ class Service():
# Clear video nodes properties
videonodes.VideoNodes().clearProperties()
# Set the minimum database version
window('plex_minDBVersion', value="1.1.5")
# Initialize playlist/queue stuff
self.queueId = None
self.playlist = None
def getLogLevel(self):
try:
logLevel = int(utils.settings('logLevel'))
@ -116,6 +122,79 @@ class Service():
logLevel = 0
return logLevel
def _getStartItem(self, string):
"""
Grabs the Plex id from e.g. '/library/metadata/12987'
and returns the tuple (typus, id) where typus is either 'queueId' or
'plexId' and id is the corresponding id as a string
"""
typus = 'plexId'
if string.startswith('/library/metadata'):
try:
string = string.split('/')[3]
except IndexError:
string = ''
else:
self.logMsg('Unknown string! %s' % string, -1)
return typus, string
def processTasks(self, task):
"""
Processes tasks picked up e.g. by Companion listener
task = {
'action': 'playlist'
'data': as received from Plex companion
}
"""
self.logMsg('Processing: %s' % task, 2)
data = task['data']
if task['action'] == 'playlist':
try:
_, queueId, query = PF.ParseContainerKey(data['containerKey'])
except Exception as e:
self.logMsg('Exception while processing: %s' % e, -1)
import traceback
self.logMsg("Traceback:\n%s" % traceback.format_exc(), -1)
return
if self.playlist is not None:
if self.playlist.typus != data.get('type'):
self.logMsg('Switching to Kodi playlist of type %s'
% data.get('type'), 1)
self.playlist = None
self.queueId = None
if self.playlist is None:
if data.get('type') == 'music':
self.playlist = playlist.Playlist('music')
elif data.get('type') == 'video':
self.playlist = playlist.Playlist('video')
else:
self.playlist = playlist.Playlist()
if queueId != self.queueId:
self.logMsg('New playlist received, updating!', 1)
self.queueId = queueId
xml = PF.GetPlayQueue(queueId)
if xml in (None, 401):
self.logMsg('Could not download Plex playlist.', -1)
return
# Clear existing playlist on the Kodi side
self.playlist.clear()
items = []
for item in xml:
items.append({
'queueId': item.get('playQueueItemID'),
'plexId': item.get('ratingKey'),
'kodiId': None
})
self.playlist.playAll(
items,
startitem=self._getStartItem(data.get('key', '')),
offset=PF.ConvertPlexToKodiTime(data.get('offset', 0)))
else:
self.logMsg('This has never happened before!', -1)
def ServiceEntryPoint(self):
log = self.logMsg
@ -132,6 +211,8 @@ class Service():
# Queue for background sync
queue = Queue.Queue(maxsize=200)
# Queue for PlexCompanion listener
companionQueue = Queue.Queue(maxsize=100)
connectMsg = True if utils.settings('connectMsg') == 'true' else False
@ -195,7 +276,15 @@ class Service():
except Exception as e:
log("Exception in Playback Monitor Service: %s" % e, 1)
pass
try:
task = companionQueue.get(block=False)
except Queue.Empty:
pass
else:
# Got instructions from Plex Companions, process them
self.processTasks(task)
companionQueue.task_done()
if not self.kodimonitor_running:
# Start up events
self.warn_auth = True
if connectMsg and self.welcome_msg:
@ -208,8 +297,8 @@ class Service():
time=2000,
sound=False)
# Start monitoring kodi events
if not self.kodimonitor_running:
self.kodimonitor_running = kodimonitor.KodiMonitor()
self.kodimonitor_running = True
kodimonitor.KodiMonitor()
# Start the Websocket Client
if not self.websocket_running:
@ -222,7 +311,8 @@ class Service():
# Start the Plex Companion thread
if not self.plexCompanion_running:
self.plexCompanion_running = True
plexCompanion = PlexCompanion.PlexCompanion()
plexCompanion = PlexCompanion.PlexCompanion(
companionQueue)
plexCompanion.start()
else:
if (user.currUser is None) and self.warn_auth:
@ -313,7 +403,7 @@ class Service():
# Abort was requested while waiting.
break
if monitor.waitForAbort(1):
if monitor.waitForAbort(0.05):
# Abort was requested while waiting. We should exit
break