Major Plex Companion overhaul, part 6
This commit is contained in:
parent
cf15799df2
commit
5337ae5715
8 changed files with 302 additions and 271 deletions
|
@ -34,13 +34,11 @@ class PlexCompanion(Thread):
|
||||||
"""
|
"""
|
||||||
def __init__(self, callback=None):
|
def __init__(self, callback=None):
|
||||||
LOG.info("----===## Starting PlexCompanion ##===----")
|
LOG.info("----===## Starting PlexCompanion ##===----")
|
||||||
if callback is not None:
|
self.mgr = callback
|
||||||
self.mgr = callback
|
|
||||||
# Start GDM for server/client discovery
|
# Start GDM for server/client discovery
|
||||||
self.client = plexgdm.plexgdm()
|
self.client = plexgdm.plexgdm()
|
||||||
self.client.clientDetails()
|
self.client.clientDetails()
|
||||||
LOG.debug("Registration string is:\n%s",
|
LOG.debug("Registration string is:\n%s", self.client.getClientDetails())
|
||||||
self.client.getClientDetails())
|
|
||||||
# kodi player instance
|
# kodi player instance
|
||||||
self.player = player.PKC_Player()
|
self.player = player.PKC_Player()
|
||||||
self.httpd = False
|
self.httpd = False
|
||||||
|
@ -54,14 +52,13 @@ class PlexCompanion(Thread):
|
||||||
try:
|
try:
|
||||||
xml[0].attrib
|
xml[0].attrib
|
||||||
except (AttributeError, IndexError, TypeError):
|
except (AttributeError, IndexError, TypeError):
|
||||||
LOG.error('Could not download Plex metadata')
|
LOG.error('Could not download Plex metadata for: %s', data)
|
||||||
return
|
return
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
if api.getType() == v.PLEX_TYPE_ALBUM:
|
if api.getType() == v.PLEX_TYPE_ALBUM:
|
||||||
LOG.debug('Plex music album detected')
|
LOG.debug('Plex music album detected')
|
||||||
queue = self.mgr.playqueue.init_playqueue_from_plex_children(
|
self.mgr.playqueue.init_playqueue_from_plex_children(
|
||||||
api.getRatingKey())
|
api.getRatingKey(), transient_token=data.get('token'))
|
||||||
queue.plex_transient_token = data.get('token')
|
|
||||||
else:
|
else:
|
||||||
state.PLEX_TRANSIENT_TOKEN = data.get('token')
|
state.PLEX_TRANSIENT_TOKEN = data.get('token')
|
||||||
params = {
|
params = {
|
||||||
|
@ -92,13 +89,7 @@ class PlexCompanion(Thread):
|
||||||
@LOCKER.lockthis
|
@LOCKER.lockthis
|
||||||
def _process_playlist(self, data):
|
def _process_playlist(self, data):
|
||||||
# Get the playqueue ID
|
# Get the playqueue ID
|
||||||
try:
|
_, container_key, query = ParseContainerKey(data['containerKey'])
|
||||||
_, container_key, query = ParseContainerKey(data['containerKey'])
|
|
||||||
except:
|
|
||||||
LOG.error('Exception while processing')
|
|
||||||
import traceback
|
|
||||||
LOG.error("Traceback:\n%s", traceback.format_exc())
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
|
||||||
|
@ -114,16 +105,12 @@ class PlexCompanion(Thread):
|
||||||
api = API(xml[0])
|
api = API(xml[0])
|
||||||
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
playqueue = self.mgr.playqueue.get_playqueue_from_type(
|
||||||
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
|
||||||
if playqueue.id == container_key:
|
self.mgr.playqueue.update_playqueue_from_PMS(
|
||||||
# OK, really weird, this happens at least with Plex for Android
|
playqueue,
|
||||||
LOG.debug('Already know this Plex playQueue, ignoring this command')
|
playqueue_id=container_key,
|
||||||
else:
|
repeat=query.get('repeat'),
|
||||||
self.mgr.playqueue.update_playqueue_from_PMS(
|
offset=data.get('offset'),
|
||||||
playqueue,
|
transient_token=data.get('token'))
|
||||||
playqueue_id=container_key,
|
|
||||||
repeat=query.get('repeat'),
|
|
||||||
offset=data.get('offset'),
|
|
||||||
transient_token=data.get('token'))
|
|
||||||
|
|
||||||
@LOCKER.lockthis
|
@LOCKER.lockthis
|
||||||
def _process_streams(self, data):
|
def _process_streams(self, data):
|
||||||
|
@ -309,5 +296,5 @@ class PlexCompanion(Thread):
|
||||||
# Don't sleep
|
# Don't sleep
|
||||||
continue
|
continue
|
||||||
sleep(50)
|
sleep(50)
|
||||||
self.subscription_manager.signal_stop()
|
subscription_manager.signal_stop()
|
||||||
client.stop_all()
|
client.stop_all()
|
||||||
|
|
|
@ -9,6 +9,7 @@ from variables import ALEXA_TO_COMPANION
|
||||||
from playqueue import Playqueue
|
from playqueue import Playqueue
|
||||||
from PlexFunctions import GetPlexKeyNumber
|
from PlexFunctions import GetPlexKeyNumber
|
||||||
import json_rpc as js
|
import json_rpc as js
|
||||||
|
import state
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
@ -64,6 +65,7 @@ def process_command(request_path, params, queue=None):
|
||||||
convert_alexa_to_companion(params)
|
convert_alexa_to_companion(params)
|
||||||
LOG.debug('Received request_path: %s, params: %s', request_path, params)
|
LOG.debug('Received request_path: %s, params: %s', request_path, params)
|
||||||
if request_path == 'player/playback/playMedia':
|
if request_path == 'player/playback/playMedia':
|
||||||
|
state.PLAYBACK_INIT_DONE = False
|
||||||
# We need to tell service.py
|
# We need to tell service.py
|
||||||
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
|
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
|
||||||
queue.put({
|
queue.put({
|
||||||
|
@ -71,6 +73,7 @@ def process_command(request_path, params, queue=None):
|
||||||
'data': params
|
'data': params
|
||||||
})
|
})
|
||||||
elif request_path == 'player/playback/refreshPlayQueue':
|
elif request_path == 'player/playback/refreshPlayQueue':
|
||||||
|
state.PLAYBACK_INIT_DONE = False
|
||||||
queue.put({
|
queue.put({
|
||||||
'action': 'refreshPlayQueue',
|
'action': 'refreshPlayQueue',
|
||||||
'data': params
|
'data': params
|
||||||
|
@ -93,10 +96,13 @@ def process_command(request_path, params, queue=None):
|
||||||
elif request_path == "player/playback/stepBack":
|
elif request_path == "player/playback/stepBack":
|
||||||
js.smallbackward()
|
js.smallbackward()
|
||||||
elif request_path == "player/playback/skipNext":
|
elif request_path == "player/playback/skipNext":
|
||||||
|
state.PLAYBACK_INIT_DONE = False
|
||||||
js.skipnext()
|
js.skipnext()
|
||||||
elif request_path == "player/playback/skipPrevious":
|
elif request_path == "player/playback/skipPrevious":
|
||||||
|
state.PLAYBACK_INIT_DONE = False
|
||||||
js.skipprevious()
|
js.skipprevious()
|
||||||
elif request_path == "player/playback/skipTo":
|
elif request_path == "player/playback/skipTo":
|
||||||
|
state.PLAYBACK_INIT_DONE = False
|
||||||
skip_to(params)
|
skip_to(params)
|
||||||
elif request_path == "player/navigation/moveUp":
|
elif request_path == "player/navigation/moveUp":
|
||||||
js.input_up()
|
js.input_up()
|
||||||
|
|
|
@ -125,7 +125,9 @@ class KodiMonitor(Monitor):
|
||||||
LOG.debug("Method: %s Data: %s", method, data)
|
LOG.debug("Method: %s Data: %s", method, data)
|
||||||
|
|
||||||
if method == "Player.OnPlay":
|
if method == "Player.OnPlay":
|
||||||
|
state.PLAYBACK_INIT_DONE = False
|
||||||
self.PlayBackStart(data)
|
self.PlayBackStart(data)
|
||||||
|
state.PLAYBACK_INIT_DONE = True
|
||||||
elif method == "Player.OnStop":
|
elif method == "Player.OnStop":
|
||||||
# Should refresh our video nodes, e.g. on deck
|
# Should refresh our video nodes, e.g. on deck
|
||||||
# xbmc.executebuiltin('ReloadSkin()')
|
# xbmc.executebuiltin('ReloadSkin()')
|
||||||
|
@ -336,6 +338,17 @@ class KodiMonitor(Monitor):
|
||||||
kodi_item={'id': kodi_id,
|
kodi_item={'id': kodi_id,
|
||||||
'type': kodi_type,
|
'type': kodi_type,
|
||||||
'file': path})
|
'file': path})
|
||||||
|
# Set the Plex container key (e.g. using the Plex playqueue)
|
||||||
|
container_key = None
|
||||||
|
if info['playlistid'] != -1:
|
||||||
|
# -1 is Kodi's answer if there is no playlist
|
||||||
|
container_key = self.playqueue.playqueues[playerid].id
|
||||||
|
if container_key is not None:
|
||||||
|
container_key = '/playQueues/%s' % container_key
|
||||||
|
elif plex_id is not None:
|
||||||
|
container_key = '/library/metadata/%s' % plex_id
|
||||||
|
state.PLAYER_STATES[playerid]['container_key'] = container_key
|
||||||
|
LOG.debug('Set the Plex container_key to: %s', container_key)
|
||||||
|
|
||||||
def StartDirectPath(self, plex_id, type, currentFile):
|
def StartDirectPath(self, plex_id, type, currentFile):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -77,7 +77,7 @@ class Playqueue(Thread):
|
||||||
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
raise ValueError('Wrong playlist type passed in: %s' % typus)
|
||||||
return playqueue
|
return playqueue
|
||||||
|
|
||||||
def init_playqueue_from_plex_children(self, plex_id):
|
def init_playqueue_from_plex_children(self, plex_id, transient_token=None):
|
||||||
"""
|
"""
|
||||||
Init a new playqueue e.g. from an album. Alexa does this
|
Init a new playqueue e.g. from an album. Alexa does this
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ class Playqueue(Thread):
|
||||||
for i, child in enumerate(xml):
|
for i, child in enumerate(xml):
|
||||||
api = API(child)
|
api = API(child)
|
||||||
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey())
|
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey())
|
||||||
|
playqueue.plex_transient_token = transient_token
|
||||||
LOG.debug('Firing up Kodi player')
|
LOG.debug('Firing up Kodi player')
|
||||||
Player().play(playqueue.kodi_pl, None, False, 0)
|
Player().play(playqueue.kodi_pl, None, False, 0)
|
||||||
return playqueue
|
return playqueue
|
||||||
|
@ -114,6 +115,9 @@ class Playqueue(Thread):
|
||||||
"""
|
"""
|
||||||
LOG.info('New playqueue %s received from Plex companion with offset '
|
LOG.info('New playqueue %s received from Plex companion with offset '
|
||||||
'%s, repeat %s', playqueue_id, offset, repeat)
|
'%s, repeat %s', playqueue_id, offset, repeat)
|
||||||
|
# Safe transient token from being deleted
|
||||||
|
if transient_token is None:
|
||||||
|
transient_token = playqueue.plex_transient_token
|
||||||
with LOCK:
|
with LOCK:
|
||||||
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
|
||||||
playqueue.clear()
|
playqueue.clear()
|
||||||
|
@ -123,7 +127,7 @@ class Playqueue(Thread):
|
||||||
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
LOG.error('Could not get playqueue ID %s', playqueue_id)
|
||||||
return
|
return
|
||||||
playqueue.repeat = 0 if not repeat else int(repeat)
|
playqueue.repeat = 0 if not repeat else int(repeat)
|
||||||
playqueue.token = transient_token
|
playqueue.plex_transient_token = transient_token
|
||||||
PlaybackUtils(xml, playqueue).play_all()
|
PlaybackUtils(xml, playqueue).play_all()
|
||||||
window('plex_customplaylist', value="true")
|
window('plex_customplaylist', value="true")
|
||||||
if offset not in (None, "0"):
|
if offset not in (None, "0"):
|
||||||
|
|
|
@ -9,6 +9,7 @@ from urlparse import urlparse, parse_qs
|
||||||
|
|
||||||
from xbmc import sleep
|
from xbmc import sleep
|
||||||
from companion import process_command
|
from companion import process_command
|
||||||
|
from utils import window
|
||||||
import json_rpc as js
|
import json_rpc as js
|
||||||
from clientinfo import getXArgsDeviceInfo
|
from clientinfo import getXArgsDeviceInfo
|
||||||
import variables as v
|
import variables as v
|
||||||
|
@ -19,6 +20,21 @@ LOG = getLogger("PLEX." + __name__)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
RESOURCES_XML = ('%s<MediaContainer>\n'
|
||||||
|
' <Player'
|
||||||
|
' title="{title}"'
|
||||||
|
' protocol="plex"'
|
||||||
|
' protocolVersion="1"'
|
||||||
|
' protocolCapabilities="timeline,playback,navigation,playqueues"'
|
||||||
|
' machineIdentifier="{machineIdentifier}"'
|
||||||
|
' product="%s"'
|
||||||
|
' platform="%s"'
|
||||||
|
' platformVersion="%s"'
|
||||||
|
' deviceClass="pc"/>\n'
|
||||||
|
'</MediaContainer>\n') % (v.XML_HEADER,
|
||||||
|
v.ADDON_NAME,
|
||||||
|
v.PLATFORM,
|
||||||
|
v.ADDON_VERSION)
|
||||||
|
|
||||||
class MyHandler(BaseHTTPRequestHandler):
|
class MyHandler(BaseHTTPRequestHandler):
|
||||||
"""
|
"""
|
||||||
|
@ -78,94 +94,68 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
self.serverlist = self.server.client.getServerList()
|
self.serverlist = self.server.client.getServerList()
|
||||||
sub_mgr = self.server.subscription_manager
|
sub_mgr = self.server.subscription_manager
|
||||||
|
|
||||||
try:
|
request_path = self.path[1:]
|
||||||
request_path = self.path[1:]
|
request_path = sub(r"\?.*", "", request_path)
|
||||||
request_path = sub(r"\?.*", "", request_path)
|
url = urlparse(self.path)
|
||||||
url = urlparse(self.path)
|
paramarrays = parse_qs(url.query)
|
||||||
paramarrays = parse_qs(url.query)
|
params = {}
|
||||||
params = {}
|
for key in paramarrays:
|
||||||
for key in paramarrays:
|
params[key] = paramarrays[key][0]
|
||||||
params[key] = paramarrays[key][0]
|
LOG.debug("remote request_path: %s", request_path)
|
||||||
LOG.debug("remote request_path: %s", request_path)
|
LOG.debug("params received from remote: %s", params)
|
||||||
LOG.debug("params received from remote: %s", params)
|
sub_mgr.update_command_id(self.headers.get(
|
||||||
sub_mgr.update_command_id(self.headers.get(
|
'X-Plex-Client-Identifier', self.client_address[0]),
|
||||||
'X-Plex-Client-Identifier',
|
params.get('commandID'))
|
||||||
self.client_address[0]),
|
if request_path == "version":
|
||||||
params.get('commandID', False))
|
self.response(
|
||||||
if request_path == "version":
|
"PlexKodiConnect Plex Companion: Running\nVersion: %s"
|
||||||
self.response(
|
% v.ADDON_VERSION)
|
||||||
"PlexKodiConnect Plex Companion: Running\nVersion: %s"
|
elif request_path == "verify":
|
||||||
% v.ADDON_VERSION)
|
self.response("XBMC JSON connection test:\n" + js.ping())
|
||||||
elif request_path == "verify":
|
elif request_path == 'resources':
|
||||||
self.response("XBMC JSON connection test:\n" +
|
self.response(
|
||||||
js.ping())
|
RESOURCES_XML.format(
|
||||||
elif request_path == 'resources':
|
title=v.DEVICENAME,
|
||||||
resp = ('%s'
|
machineIdentifier=window('plex_machineIdentifier')),
|
||||||
'<MediaContainer>'
|
getXArgsDeviceInfo(include_token=False))
|
||||||
'<Player'
|
elif "/poll" in request_path:
|
||||||
' title="%s"'
|
if params.get('wait') == '1':
|
||||||
' protocol="plex"'
|
sleep(950)
|
||||||
' protocolVersion="1"'
|
self.response(
|
||||||
' protocolCapabilities="timeline,playback,navigation,playqueues"'
|
sub_mgr.msg(js.get_players()).format(
|
||||||
' machineIdentifier="%s"'
|
command_id=params.get('commandID', 0)),
|
||||||
' product="PlexKodiConnect"'
|
{
|
||||||
' platform="%s"'
|
'X-Plex-Client-Identifier': v.PKC_MACHINE_IDENTIFIER,
|
||||||
' platformVersion="%s"'
|
'X-Plex-Protocol': '1.0',
|
||||||
' deviceClass="pc"'
|
'Access-Control-Allow-Origin': '*',
|
||||||
'/>'
|
'Access-Control-Max-Age': '1209600',
|
||||||
'</MediaContainer>'
|
'Access-Control-Expose-Headers':
|
||||||
% (v.XML_HEADER,
|
'X-Plex-Client-Identifier',
|
||||||
v.DEVICENAME,
|
'Content-Type': 'text/xml;charset=utf-8'
|
||||||
v.PKC_MACHINE_IDENTIFIER,
|
})
|
||||||
v.PLATFORM,
|
elif "/subscribe" in request_path:
|
||||||
v.ADDON_VERSION))
|
self.response(v.COMPANION_OK_MESSAGE,
|
||||||
LOG.debug("crafted resources response: %s", resp)
|
getXArgsDeviceInfo(include_token=False))
|
||||||
self.response(resp, getXArgsDeviceInfo(include_token=False))
|
protocol = params.get('protocol')
|
||||||
elif "/poll" in request_path:
|
host = self.client_address[0]
|
||||||
if params.get('wait', False) == '1':
|
port = params.get('port')
|
||||||
sleep(950)
|
uuid = self.headers.get('X-Plex-Client-Identifier')
|
||||||
command_id = params.get('commandID', 0)
|
command_id = params.get('commandID', 0)
|
||||||
self.response(
|
sub_mgr.add_subscriber(protocol,
|
||||||
sub(r"INSERTCOMMANDID",
|
host,
|
||||||
str(command_id),
|
port,
|
||||||
sub_mgr.msg(js.get_players())),
|
uuid,
|
||||||
{
|
command_id)
|
||||||
'X-Plex-Client-Identifier': v.PKC_MACHINE_IDENTIFIER,
|
elif "/unsubscribe" in request_path:
|
||||||
'X-Plex-Protocol': '1.0',
|
self.response(v.COMPANION_OK_MESSAGE,
|
||||||
'Access-Control-Allow-Origin': '*',
|
getXArgsDeviceInfo(include_token=False))
|
||||||
'Access-Control-Max-Age': '1209600',
|
uuid = self.headers.get('X-Plex-Client-Identifier') \
|
||||||
'Access-Control-Expose-Headers':
|
or self.client_address[0]
|
||||||
'X-Plex-Client-Identifier',
|
sub_mgr.remove_subscriber(uuid)
|
||||||
'Content-Type': 'text/xml;charset=utf-8'
|
else:
|
||||||
})
|
# Throw it to companion.py
|
||||||
elif "/subscribe" in request_path:
|
process_command(request_path, params, self.server.queue)
|
||||||
self.response(v.COMPANION_OK_MESSAGE,
|
self.response('', getXArgsDeviceInfo(include_token=False))
|
||||||
getXArgsDeviceInfo(include_token=False))
|
|
||||||
protocol = params.get('protocol', False)
|
|
||||||
host = self.client_address[0]
|
|
||||||
port = params.get('port', False)
|
|
||||||
uuid = self.headers.get('X-Plex-Client-Identifier', "")
|
|
||||||
command_id = params.get('commandID', 0)
|
|
||||||
sub_mgr.add_subscriber(protocol,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
uuid,
|
|
||||||
command_id)
|
|
||||||
elif "/unsubscribe" in request_path:
|
|
||||||
self.response(v.COMPANION_OK_MESSAGE,
|
|
||||||
getXArgsDeviceInfo(include_token=False))
|
|
||||||
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
|
|
||||||
or self.client_address[0]
|
|
||||||
sub_mgr.remove_subscriber(uuid)
|
|
||||||
else:
|
|
||||||
# Throw it to companion.py
|
|
||||||
process_command(request_path, params, self.server.queue)
|
|
||||||
self.response('', getXArgsDeviceInfo(include_token=False))
|
|
||||||
sub_mgr.notify()
|
|
||||||
except:
|
|
||||||
LOG.error('Error encountered. Traceback:')
|
|
||||||
import traceback
|
|
||||||
LOG.error(traceback.print_exc())
|
|
||||||
|
|
||||||
|
|
||||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||||
|
|
|
@ -3,12 +3,10 @@ Manages getting playstate from Kodi and sending it to the PMS as well as
|
||||||
subscribed Plex Companion clients.
|
subscribed Plex Companion clients.
|
||||||
"""
|
"""
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from re import sub
|
from threading import Thread, RLock
|
||||||
from threading import Thread, Lock
|
|
||||||
|
|
||||||
from downloadutils import DownloadUtils as DU
|
from downloadutils import DownloadUtils as DU
|
||||||
from utils import window, kodi_time_to_millis, Lock_Function
|
from utils import window, kodi_time_to_millis, Lock_Function
|
||||||
from playlist_func import init_Plex_playlist
|
|
||||||
import state
|
import state
|
||||||
import variables as v
|
import variables as v
|
||||||
import json_rpc as js
|
import json_rpc as js
|
||||||
|
@ -17,19 +15,19 @@ import json_rpc as js
|
||||||
|
|
||||||
LOG = getLogger("PLEX." + __name__)
|
LOG = getLogger("PLEX." + __name__)
|
||||||
# Need to lock all methods and functions messing with subscribers or state
|
# Need to lock all methods and functions messing with subscribers or state
|
||||||
LOCK = Lock()
|
LOCK = RLock()
|
||||||
LOCKER = Lock_Function(LOCK)
|
LOCKER = Lock_Function(LOCK)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# What is Companion controllable?
|
# What is Companion controllable?
|
||||||
CONTROLLABLE = {
|
CONTROLLABLE = {
|
||||||
v.PLEX_TYPE_PHOTO: 'skipPrevious,skipNext,stop',
|
v.PLEX_PLAYLIST_TYPE_VIDEO: 'playPause,stop,volume,shuffle,audioStream,'
|
||||||
v.PLEX_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,'
|
'subtitleStream,seekTo,skipPrevious,skipNext,'
|
||||||
'skipPrevious,skipNext,stepBack,stepForward',
|
'stepBack,stepForward',
|
||||||
v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,shuffle,audioStream,'
|
v.PLEX_PLAYLIST_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,'
|
||||||
'subtitleStream,seekTo,skipPrevious,skipNext,'
|
'skipPrevious,skipNext,stepBack,stepForward',
|
||||||
'stepBack,stepForward'
|
v.PLEX_PLAYLIST_TYPE_PHOTO: 'skipPrevious,skipNext,stop'
|
||||||
}
|
}
|
||||||
|
|
||||||
STREAM_DETAILS = {
|
STREAM_DETAILS = {
|
||||||
|
@ -38,6 +36,24 @@ STREAM_DETAILS = {
|
||||||
'subtitle': 'currentsubtitle'
|
'subtitle': 'currentsubtitle'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XML = ('%s<MediaContainer commandID="{command_id}" location="{location}">\n'
|
||||||
|
' <Timeline {%s}/>\n'
|
||||||
|
' <Timeline {%s}/>\n'
|
||||||
|
' <Timeline {%s}/>\n'
|
||||||
|
'</MediaContainer>\n') % (v.XML_HEADER,
|
||||||
|
v.PLEX_PLAYLIST_TYPE_VIDEO,
|
||||||
|
v.PLEX_PLAYLIST_TYPE_AUDIO,
|
||||||
|
v.PLEX_PLAYLIST_TYPE_PHOTO)
|
||||||
|
|
||||||
|
|
||||||
|
def update_player_info(playerid):
|
||||||
|
"""
|
||||||
|
Updates all player info for playerid [int] in state.py.
|
||||||
|
"""
|
||||||
|
state.PLAYER_STATES[playerid].update(js.get_player_props(playerid))
|
||||||
|
state.PLAYER_STATES[playerid]['volume'] = js.get_volume()
|
||||||
|
state.PLAYER_STATES[playerid]['muted'] = js.get_muted()
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionMgr(object):
|
class SubscriptionMgr(object):
|
||||||
"""
|
"""
|
||||||
|
@ -47,8 +63,6 @@ class SubscriptionMgr(object):
|
||||||
self.serverlist = []
|
self.serverlist = []
|
||||||
self.subscribers = {}
|
self.subscribers = {}
|
||||||
self.info = {}
|
self.info = {}
|
||||||
self.container_key = None
|
|
||||||
self.ratingkey = None
|
|
||||||
self.server = ""
|
self.server = ""
|
||||||
self.protocol = "http"
|
self.protocol = "http"
|
||||||
self.port = ""
|
self.port = ""
|
||||||
|
@ -90,18 +104,126 @@ class SubscriptionMgr(object):
|
||||||
Returns a timeline xml as str
|
Returns a timeline xml as str
|
||||||
(xml containing video, audio, photo player state)
|
(xml containing video, audio, photo player state)
|
||||||
"""
|
"""
|
||||||
msg = v.XML_HEADER
|
self.isplaying = False
|
||||||
msg += '<MediaContainer size="3" commandID="INSERTCOMMANDID"'
|
answ = str(XML)
|
||||||
msg += ' machineIdentifier="%s">\n' % v.PKC_MACHINE_IDENTIFIER
|
timelines = {
|
||||||
msg += self._timeline_xml(players.get(v.KODI_TYPE_AUDIO),
|
v.PLEX_PLAYLIST_TYPE_VIDEO: None,
|
||||||
v.PLEX_TYPE_AUDIO)
|
v.PLEX_PLAYLIST_TYPE_AUDIO: None,
|
||||||
msg += self._timeline_xml(players.get(v.KODI_TYPE_PHOTO),
|
v.PLEX_PLAYLIST_TYPE_PHOTO: None
|
||||||
v.PLEX_TYPE_PHOTO)
|
}
|
||||||
msg += self._timeline_xml(players.get(v.KODI_TYPE_VIDEO),
|
for typus in timelines:
|
||||||
v.PLEX_TYPE_VIDEO)
|
if players.get(v.KODI_PLAYLIST_TYPE_FROM_PLEX_PLAYLIST_TYPE[typus]) is None:
|
||||||
msg += "</MediaContainer>"
|
timeline = {
|
||||||
LOG.debug('Our PKC message is: %s', msg)
|
'controllable': CONTROLLABLE[typus],
|
||||||
return msg
|
'type': typus,
|
||||||
|
'state': 'stopped'
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
timeline = self._timeline_dict(players[
|
||||||
|
v.KODI_PLAYLIST_TYPE_FROM_PLEX_PLAYLIST_TYPE[typus]], typus)
|
||||||
|
timelines[typus] = self._dict_to_xml(timeline)
|
||||||
|
location = 'fullScreenVideo' if self.isplaying else 'navigation'
|
||||||
|
timelines.update({'command_id': '{command_id}', 'location': location})
|
||||||
|
return answ.format(**timelines)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _dict_to_xml(dictionary):
|
||||||
|
"""
|
||||||
|
Returns the string 'key1="value1" key2="value2" ...' for dictionary
|
||||||
|
"""
|
||||||
|
answ = ''
|
||||||
|
for key, value in dictionary.iteritems():
|
||||||
|
answ += '%s="%s" ' % (key, value)
|
||||||
|
return answ
|
||||||
|
|
||||||
|
def _timeline_dict(self, player, ptype):
|
||||||
|
playerid = player['playerid']
|
||||||
|
info = state.PLAYER_STATES[playerid]
|
||||||
|
playqueue = self.playqueue.playqueues[playerid]
|
||||||
|
pos = info['position']
|
||||||
|
try:
|
||||||
|
playqueue.items[pos]
|
||||||
|
except IndexError:
|
||||||
|
# E.g. for direct path playback for single item
|
||||||
|
return {
|
||||||
|
'controllable': CONTROLLABLE[ptype],
|
||||||
|
'type': ptype,
|
||||||
|
'state': 'stopped'
|
||||||
|
}
|
||||||
|
pbmc_server = window('pms_server')
|
||||||
|
if pbmc_server:
|
||||||
|
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
||||||
|
self.server = self.server.replace('/', '')
|
||||||
|
status = 'paused' if info['speed'] == '0' else 'playing'
|
||||||
|
duration = kodi_time_to_millis(info['totaltime'])
|
||||||
|
shuffle = '1' if info['shuffled'] else '0'
|
||||||
|
mute = '1' if info['muted'] is True else '0'
|
||||||
|
answ = {
|
||||||
|
'location': 'fullScreenVideo',
|
||||||
|
'controllable': CONTROLLABLE[ptype],
|
||||||
|
'protocol': self.protocol,
|
||||||
|
'address': self.server,
|
||||||
|
'port': self.port,
|
||||||
|
'machineIdentifier': window('plex_machineIdentifier'),
|
||||||
|
'state': status,
|
||||||
|
'type': ptype,
|
||||||
|
'itemType': ptype,
|
||||||
|
'time': kodi_time_to_millis(info['time']),
|
||||||
|
'duration': duration,
|
||||||
|
'seekRange': '0-%s' % duration,
|
||||||
|
'shuffle': shuffle,
|
||||||
|
'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']],
|
||||||
|
'volume': info['volume'],
|
||||||
|
'mute': mute,
|
||||||
|
'mediaIndex': pos, # Still to implement from here
|
||||||
|
'partIndex':pos,
|
||||||
|
'partCount': len(playqueue.items),
|
||||||
|
'providerIdentifier': 'com.plexapp.plugins.library',
|
||||||
|
}
|
||||||
|
|
||||||
|
if info['plex_id']:
|
||||||
|
answ['key'] = '/library/metadata/%s' % info['plex_id']
|
||||||
|
answ['ratingKey'] = info['plex_id']
|
||||||
|
# PlayQueue stuff
|
||||||
|
if info['container_key']:
|
||||||
|
answ['containerKey'] = info['container_key']
|
||||||
|
if (info['container_key'] is not None and
|
||||||
|
info['container_key'].startswith('/playQueues')):
|
||||||
|
answ['playQueueID'] = playqueue.id
|
||||||
|
answ['playQueueVersion'] = playqueue.version
|
||||||
|
answ['playQueueItemID'] = playqueue.items[pos].id
|
||||||
|
if playqueue.items[pos].guid:
|
||||||
|
answ['guid'] = playqueue.items[pos].guid
|
||||||
|
# Temp. token set?
|
||||||
|
if state.PLEX_TRANSIENT_TOKEN:
|
||||||
|
answ['token'] = state.PLEX_TRANSIENT_TOKEN
|
||||||
|
elif playqueue.plex_transient_token:
|
||||||
|
answ['token'] = playqueue.plex_transient_token
|
||||||
|
# Process audio and subtitle streams
|
||||||
|
if ptype != v.PLEX_PLAYLIST_TYPE_PHOTO:
|
||||||
|
strm_id = self._plex_stream_index(playerid, 'audio')
|
||||||
|
if strm_id:
|
||||||
|
answ['audioStreamID'] = strm_id
|
||||||
|
else:
|
||||||
|
LOG.error('We could not select a Plex audiostream')
|
||||||
|
if ptype == v.PLEX_PLAYLIST_TYPE_VIDEO:
|
||||||
|
strm_id = self._plex_stream_index(playerid, 'video')
|
||||||
|
if strm_id:
|
||||||
|
answ['videoStreamID'] = strm_id
|
||||||
|
else:
|
||||||
|
LOG.error('We could not select a Plex videostream')
|
||||||
|
if info['subtitleenabled']:
|
||||||
|
try:
|
||||||
|
strm_id = self._plex_stream_index(playerid, 'subtitle')
|
||||||
|
except KeyError:
|
||||||
|
# subtitleenabled can be True while currentsubtitle can
|
||||||
|
# still be {}
|
||||||
|
strm_id = None
|
||||||
|
if strm_id is not None:
|
||||||
|
# If None, then the subtitle is only present on Kodi side
|
||||||
|
answ['subtitleStreamID'] = strm_id
|
||||||
|
self.isplaying = True
|
||||||
|
return answ
|
||||||
|
|
||||||
def signal_stop(self):
|
def signal_stop(self):
|
||||||
"""
|
"""
|
||||||
|
@ -114,23 +236,6 @@ class SubscriptionMgr(object):
|
||||||
self.last_params['state'] = 'stopped'
|
self.last_params['state'] = 'stopped'
|
||||||
self._send_pms_notification(playerid, self.last_params)
|
self._send_pms_notification(playerid, self.last_params)
|
||||||
|
|
||||||
def _get_container_key(self, playerid):
|
|
||||||
key = None
|
|
||||||
playlistid = state.PLAYER_STATES[playerid]['playlistid']
|
|
||||||
if playlistid != -1:
|
|
||||||
# -1 is Kodi's answer if there is no playlist
|
|
||||||
try:
|
|
||||||
key = self.playqueue.playqueues[playlistid].id
|
|
||||||
except (KeyError, IndexError, TypeError):
|
|
||||||
pass
|
|
||||||
if key is not None:
|
|
||||||
key = '/playQueues/%s' % key
|
|
||||||
else:
|
|
||||||
if state.PLAYER_STATES[playerid]['plex_id']:
|
|
||||||
key = '/library/metadata/%s' % \
|
|
||||||
state.PLAYER_STATES[playerid]['plex_id']
|
|
||||||
return key
|
|
||||||
|
|
||||||
def _plex_stream_index(self, playerid, stream_type):
|
def _plex_stream_index(self, playerid, stream_type):
|
||||||
"""
|
"""
|
||||||
Returns the current Plex stream index [str] for the player playerid
|
Returns the current Plex stream index [str] for the player playerid
|
||||||
|
@ -142,96 +247,6 @@ class SubscriptionMgr(object):
|
||||||
return playqueue.items[info['position']].plex_stream_index(
|
return playqueue.items[info['position']].plex_stream_index(
|
||||||
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
|
info[STREAM_DETAILS[stream_type]]['index'], stream_type)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _player_info(playerid):
|
|
||||||
"""
|
|
||||||
Grabs all player info again for playerid [int].
|
|
||||||
Returns the dict state.PLAYER_STATES[playerid]
|
|
||||||
"""
|
|
||||||
# Update our PKC state of how the player actually looks like
|
|
||||||
state.PLAYER_STATES[playerid].update(js.get_player_props(playerid))
|
|
||||||
state.PLAYER_STATES[playerid]['volume'] = js.get_volume()
|
|
||||||
state.PLAYER_STATES[playerid]['muted'] = js.get_muted()
|
|
||||||
return state.PLAYER_STATES[playerid]
|
|
||||||
|
|
||||||
def _timeline_xml(self, player, ptype):
|
|
||||||
if player is None:
|
|
||||||
return ' <Timeline state="stopped" controllable="%s" type="%s" ' \
|
|
||||||
'itemType="%s" />\n' % (CONTROLLABLE[ptype], ptype, ptype)
|
|
||||||
playerid = player['playerid']
|
|
||||||
info = self._player_info(playerid)
|
|
||||||
playqueue = self.playqueue.playqueues[playerid]
|
|
||||||
pos = info['position']
|
|
||||||
try:
|
|
||||||
playqueue.items[pos]
|
|
||||||
except IndexError:
|
|
||||||
# E.g. for direct path playback for single item
|
|
||||||
return ' <Timeline state="stopped" controllable="%s" type="%s" ' \
|
|
||||||
'itemType="%s" />\n' % (CONTROLLABLE[ptype], ptype, ptype)
|
|
||||||
LOG.debug('INFO: %s', info)
|
|
||||||
LOG.debug('playqueue: %s', playqueue)
|
|
||||||
status = 'paused' if info['speed'] == '0' else 'playing'
|
|
||||||
ret = ' <Timeline state="%s"' % status
|
|
||||||
ret += ' controllable="%s"' % CONTROLLABLE[ptype]
|
|
||||||
ret += ' type="%s" itemType="%s"' % (ptype, ptype)
|
|
||||||
ret += ' time="%s"' % kodi_time_to_millis(info['time'])
|
|
||||||
ret += ' duration="%s"' % kodi_time_to_millis(info['totaltime'])
|
|
||||||
shuffled = '1' if info['shuffled'] else '0'
|
|
||||||
ret += ' shuffle="%s"' % shuffled
|
|
||||||
ret += ' repeat="%s"' % v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']]
|
|
||||||
if ptype != v.KODI_TYPE_PHOTO:
|
|
||||||
ret += ' volume="%s"' % info['volume']
|
|
||||||
muted = '1' if info['muted'] is True else '0'
|
|
||||||
ret += ' mute="%s"' % muted
|
|
||||||
pbmc_server = window('pms_server')
|
|
||||||
server = self._server_by_host(self.server)
|
|
||||||
if pbmc_server:
|
|
||||||
(self.protocol, self.server, self.port) = pbmc_server.split(':')
|
|
||||||
self.server = self.server.replace('/', '')
|
|
||||||
if info['plex_id']:
|
|
||||||
self.ratingkey = info['plex_id']
|
|
||||||
ret += ' key="/library/metadata/%s"' % info['plex_id']
|
|
||||||
ret += ' ratingKey="%s"' % info['plex_id']
|
|
||||||
# PlayQueue stuff
|
|
||||||
key = self._get_container_key(playerid)
|
|
||||||
if key is not None and key.startswith('/playQueues'):
|
|
||||||
self.container_key = key
|
|
||||||
ret += ' containerKey="%s"' % self.container_key
|
|
||||||
ret += ' playQueueItemID="%s"' % playqueue.items[pos].id or 'null'
|
|
||||||
ret += ' playQueueID="%s"' % playqueue.id or 'null'
|
|
||||||
ret += ' playQueueVersion="%s"' % playqueue.version or 'null'
|
|
||||||
ret += ' guid="%s"' % playqueue.items[pos].guid or 'null'
|
|
||||||
elif key:
|
|
||||||
self.container_key = key
|
|
||||||
ret += ' containerKey="%s"' % self.container_key
|
|
||||||
ret += ' machineIdentifier="%s"' % server.get('uuid', "")
|
|
||||||
ret += ' protocol="%s"' % server.get('protocol', 'http')
|
|
||||||
ret += ' address="%s"' % server.get('server', self.server)
|
|
||||||
ret += ' port="%s"' % server.get('port', self.port)
|
|
||||||
# Temp. token set?
|
|
||||||
if state.PLEX_TRANSIENT_TOKEN:
|
|
||||||
ret += ' token="%s"' % state.PLEX_TRANSIENT_TOKEN
|
|
||||||
elif playqueue.plex_transient_token:
|
|
||||||
ret += ' token="%s"' % playqueue.plex_transient_token
|
|
||||||
# Process audio and subtitle streams
|
|
||||||
if ptype != v.KODI_TYPE_PHOTO:
|
|
||||||
strm_id = self._plex_stream_index(playerid, 'audio')
|
|
||||||
if strm_id is not None:
|
|
||||||
ret += ' audioStreamID="%s"' % strm_id
|
|
||||||
else:
|
|
||||||
LOG.error('We could not select a Plex audiostream')
|
|
||||||
if ptype == v.KODI_TYPE_VIDEO and info['subtitleenabled']:
|
|
||||||
try:
|
|
||||||
strm_id = self._plex_stream_index(playerid, 'subtitle')
|
|
||||||
except KeyError:
|
|
||||||
# subtitleenabled can be True while currentsubtitle can be {}
|
|
||||||
strm_id = None
|
|
||||||
if strm_id is not None:
|
|
||||||
# If None, then the subtitle is only present on Kodi side
|
|
||||||
ret += ' subtitleStreamID="%s"' % strm_id
|
|
||||||
self.isplaying = True
|
|
||||||
return ret + '/>\n'
|
|
||||||
|
|
||||||
@LOCKER.lockthis
|
@LOCKER.lockthis
|
||||||
def update_command_id(self, uuid, command_id):
|
def update_command_id(self, uuid, command_id):
|
||||||
"""
|
"""
|
||||||
|
@ -241,28 +256,27 @@ class SubscriptionMgr(object):
|
||||||
if command_id and self.subscribers.get(uuid):
|
if command_id and self.subscribers.get(uuid):
|
||||||
self.subscribers[uuid].command_id = int(command_id)
|
self.subscribers[uuid].command_id = int(command_id)
|
||||||
|
|
||||||
|
@LOCKER.lockthis
|
||||||
def notify(self):
|
def notify(self):
|
||||||
"""
|
"""
|
||||||
Causes PKC to tell the PMS and Plex Companion players to receive a
|
Causes PKC to tell the PMS and Plex Companion players to receive a
|
||||||
notification what's being played.
|
notification what's being played.
|
||||||
"""
|
"""
|
||||||
with LOCK:
|
self._cleanup()
|
||||||
self._cleanup()
|
# Get all the active/playing Kodi players (video, audio, pictures)
|
||||||
# Do we need a check to NOT tell about e.g. PVR/TV and Addon playback?
|
|
||||||
players = js.get_players()
|
players = js.get_players()
|
||||||
# fetch the message, subscribers or not, since the server will need the
|
# Update the PKC info with what's playing on the Kodi side
|
||||||
# info anyway
|
for player in players.values():
|
||||||
self.isplaying = False
|
update_player_info(player['playerid'])
|
||||||
msg = self.msg(players)
|
if self.subscribers and state.PLAYBACK_INIT_DONE is True:
|
||||||
with LOCK:
|
msg = self.msg(players)
|
||||||
if self.isplaying is True:
|
if self.isplaying is True:
|
||||||
# If we don't check here, Plex Companion devices will simply
|
# If we don't check here, Plex Companion devices will simply
|
||||||
# drop out of the Plex Companion playback screen
|
# drop out of the Plex Companion playback screen
|
||||||
for subscriber in self.subscribers.values():
|
for subscriber in self.subscribers.values():
|
||||||
subscriber.send_update(msg, not players)
|
subscriber.send_update(msg, not players)
|
||||||
self._notify_server(players)
|
self._notify_server(players)
|
||||||
self.lastplayers = players
|
self.lastplayers = players
|
||||||
return True
|
|
||||||
|
|
||||||
def _notify_server(self, players):
|
def _notify_server(self, players):
|
||||||
for typus, player in players.iteritems():
|
for typus, player in players.iteritems():
|
||||||
|
@ -273,7 +287,7 @@ class SubscriptionMgr(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
# Process the players we have left (to signal a stop)
|
# Process the players we have left (to signal a stop)
|
||||||
for _, player in self.lastplayers.iteritems():
|
for player in self.lastplayers.values():
|
||||||
self.last_params['state'] = 'stopped'
|
self.last_params['state'] = 'stopped'
|
||||||
self._send_pms_notification(player['playerid'], self.last_params)
|
self._send_pms_notification(player['playerid'], self.last_params)
|
||||||
|
|
||||||
|
@ -282,18 +296,17 @@ class SubscriptionMgr(object):
|
||||||
status = 'paused' if info['speed'] == '0' else 'playing'
|
status = 'paused' if info['speed'] == '0' else 'playing'
|
||||||
params = {
|
params = {
|
||||||
'state': status,
|
'state': status,
|
||||||
'ratingKey': self.ratingkey,
|
'ratingKey': info['plex_id'],
|
||||||
'key': '/library/metadata/%s' % self.ratingkey,
|
'key': '/library/metadata/%s' % info['plex_id'],
|
||||||
'time': kodi_time_to_millis(info['time']),
|
'time': kodi_time_to_millis(info['time']),
|
||||||
'duration': kodi_time_to_millis(info['totaltime'])
|
'duration': kodi_time_to_millis(info['totaltime'])
|
||||||
}
|
}
|
||||||
if self.container_key:
|
if info['container_key'] is not None:
|
||||||
params['containerKey'] = self.container_key
|
params['containerKey'] = info['container_key']
|
||||||
if self.container_key is not None and \
|
if info['container_key'].startswith('/playQueues/'):
|
||||||
self.container_key.startswith('/playQueues/'):
|
playqueue = self.playqueue.playqueues[playerid]
|
||||||
playqueue = self.playqueue.playqueues[playerid]
|
params['playQueueVersion'] = playqueue.version
|
||||||
params['playQueueVersion'] = playqueue.version
|
params['playQueueItemID'] = playqueue.id
|
||||||
params['playQueueItemID'] = playqueue.id
|
|
||||||
self.last_params = params
|
self.last_params = params
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
@ -384,11 +397,10 @@ class Subscriber(object):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.navlocationsent = True
|
self.navlocationsent = True
|
||||||
msg = sub(r"INSERTCOMMANDID", str(self.command_id), msg)
|
msg = msg.format(command_id=self.command_id)
|
||||||
LOG.debug("sending xml to subscriber uuid=%s,commandID=%i:\n%s",
|
LOG.debug("sending xml to subscriber uuid=%s,commandID=%i:\n%s",
|
||||||
self.uuid, self.command_id, msg)
|
self.uuid, self.command_id, msg)
|
||||||
url = self.protocol + '://' + self.host + ':' + self.port \
|
url = '%s://%s:%s/:/timeline' % (self.protocol, self.host, self.port)
|
||||||
+ "/:/timeline"
|
|
||||||
thread = Thread(target=self._threaded_send, args=(url, msg))
|
thread = Thread(target=self._threaded_send, args=(url, msg))
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,7 @@ PLAYER_STATES = {
|
||||||
'kodi_type': None,
|
'kodi_type': None,
|
||||||
'plex_id': None,
|
'plex_id': None,
|
||||||
'plex_type': None,
|
'plex_type': None,
|
||||||
|
'container_key': None,
|
||||||
'volume': 100,
|
'volume': 100,
|
||||||
'muted': False
|
'muted': False
|
||||||
},
|
},
|
||||||
|
@ -116,6 +117,10 @@ PLAYER_STATES = {
|
||||||
# paths for playback (since we're not receiving a Kodi id)
|
# paths for playback (since we're not receiving a Kodi id)
|
||||||
PLEX_IDS = {}
|
PLEX_IDS = {}
|
||||||
PLAYED_INFO = {}
|
PLAYED_INFO = {}
|
||||||
|
# Set to False after having received a Companion command to play something
|
||||||
|
# Set to True after Kodi monitor PlayBackStart is done
|
||||||
|
# This will prohibit "old" Plex Companion messages being sent
|
||||||
|
PLAYBACK_INIT_DONE = True
|
||||||
|
|
||||||
# Kodi webserver details
|
# Kodi webserver details
|
||||||
WEBSERVER_PORT = 8080
|
WEBSERVER_PORT = 8080
|
||||||
|
|
|
@ -129,6 +129,20 @@ PLEX_TYPE_MUSICVIDEO = 'musicvideo'
|
||||||
|
|
||||||
PLEX_TYPE_PHOTO = 'photo'
|
PLEX_TYPE_PHOTO = 'photo'
|
||||||
|
|
||||||
|
# Used for /:/timeline XML messages
|
||||||
|
PLEX_PLAYLIST_TYPE_VIDEO = 'video'
|
||||||
|
PLEX_PLAYLIST_TYPE_AUDIO = 'music'
|
||||||
|
PLEX_PLAYLIST_TYPE_PHOTO = 'photo'
|
||||||
|
|
||||||
|
KODI_PLAYLIST_TYPE_VIDEO = 'video'
|
||||||
|
KODI_PLAYLIST_TYPE_AUDIO = 'audio'
|
||||||
|
KODI_PLAYLIST_TYPE_PHOTO = 'picture'
|
||||||
|
|
||||||
|
KODI_PLAYLIST_TYPE_FROM_PLEX_PLAYLIST_TYPE = {
|
||||||
|
PLEX_PLAYLIST_TYPE_VIDEO: KODI_PLAYLIST_TYPE_VIDEO,
|
||||||
|
PLEX_PLAYLIST_TYPE_AUDIO: KODI_PLAYLIST_TYPE_AUDIO,
|
||||||
|
PLEX_PLAYLIST_TYPE_PHOTO: KODI_PLAYLIST_TYPE_PHOTO
|
||||||
|
}
|
||||||
|
|
||||||
# All the Kodi types as e.g. used in the JSON API
|
# All the Kodi types as e.g. used in the JSON API
|
||||||
KODI_TYPE_VIDEO = 'video'
|
KODI_TYPE_VIDEO = 'video'
|
||||||
|
|
Loading…
Reference in a new issue