diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py
index 15a33f77..08ec3d7b 100644
--- a/resources/lib/PlexCompanion.py
+++ b/resources/lib/PlexCompanion.py
@@ -34,13 +34,11 @@ class PlexCompanion(Thread):
"""
def __init__(self, callback=None):
LOG.info("----===## Starting PlexCompanion ##===----")
- if callback is not None:
- self.mgr = callback
+ self.mgr = callback
# Start GDM for server/client discovery
self.client = plexgdm.plexgdm()
self.client.clientDetails()
- LOG.debug("Registration string is:\n%s",
- self.client.getClientDetails())
+ LOG.debug("Registration string is:\n%s", self.client.getClientDetails())
# kodi player instance
self.player = player.PKC_Player()
self.httpd = False
@@ -54,14 +52,13 @@ class PlexCompanion(Thread):
try:
xml[0].attrib
except (AttributeError, IndexError, TypeError):
- LOG.error('Could not download Plex metadata')
+ LOG.error('Could not download Plex metadata for: %s', data)
return
api = API(xml[0])
if api.getType() == v.PLEX_TYPE_ALBUM:
LOG.debug('Plex music album detected')
- queue = self.mgr.playqueue.init_playqueue_from_plex_children(
- api.getRatingKey())
- queue.plex_transient_token = data.get('token')
+ self.mgr.playqueue.init_playqueue_from_plex_children(
+ api.getRatingKey(), transient_token=data.get('token'))
else:
state.PLEX_TRANSIENT_TOKEN = data.get('token')
params = {
@@ -92,13 +89,7 @@ class PlexCompanion(Thread):
@LOCKER.lockthis
def _process_playlist(self, data):
# Get the playqueue ID
- try:
- _, container_key, query = ParseContainerKey(data['containerKey'])
- except:
- LOG.error('Exception while processing')
- import traceback
- LOG.error("Traceback:\n%s", traceback.format_exc())
- return
+ _, container_key, query = ParseContainerKey(data['containerKey'])
try:
playqueue = self.mgr.playqueue.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']])
@@ -114,16 +105,12 @@ class PlexCompanion(Thread):
api = API(xml[0])
playqueue = self.mgr.playqueue.get_playqueue_from_type(
v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()])
- if playqueue.id == container_key:
- # OK, really weird, this happens at least with Plex for Android
- LOG.debug('Already know this Plex playQueue, ignoring this command')
- else:
- self.mgr.playqueue.update_playqueue_from_PMS(
- playqueue,
- playqueue_id=container_key,
- repeat=query.get('repeat'),
- offset=data.get('offset'),
- transient_token=data.get('token'))
+ self.mgr.playqueue.update_playqueue_from_PMS(
+ playqueue,
+ playqueue_id=container_key,
+ repeat=query.get('repeat'),
+ offset=data.get('offset'),
+ transient_token=data.get('token'))
@LOCKER.lockthis
def _process_streams(self, data):
@@ -309,5 +296,5 @@ class PlexCompanion(Thread):
# Don't sleep
continue
sleep(50)
- self.subscription_manager.signal_stop()
+ subscription_manager.signal_stop()
client.stop_all()
diff --git a/resources/lib/companion.py b/resources/lib/companion.py
index eccc60c2..7004ff06 100644
--- a/resources/lib/companion.py
+++ b/resources/lib/companion.py
@@ -9,6 +9,7 @@ from variables import ALEXA_TO_COMPANION
from playqueue import Playqueue
from PlexFunctions import GetPlexKeyNumber
import json_rpc as js
+import state
###############################################################################
@@ -64,6 +65,7 @@ def process_command(request_path, params, queue=None):
convert_alexa_to_companion(params)
LOG.debug('Received request_path: %s, params: %s', request_path, params)
if request_path == 'player/playback/playMedia':
+ state.PLAYBACK_INIT_DONE = False
# We need to tell service.py
action = 'alexa' if params.get('deviceName') == 'Alexa' else 'playlist'
queue.put({
@@ -71,6 +73,7 @@ def process_command(request_path, params, queue=None):
'data': params
})
elif request_path == 'player/playback/refreshPlayQueue':
+ state.PLAYBACK_INIT_DONE = False
queue.put({
'action': 'refreshPlayQueue',
'data': params
@@ -93,10 +96,13 @@ def process_command(request_path, params, queue=None):
elif request_path == "player/playback/stepBack":
js.smallbackward()
elif request_path == "player/playback/skipNext":
+ state.PLAYBACK_INIT_DONE = False
js.skipnext()
elif request_path == "player/playback/skipPrevious":
+ state.PLAYBACK_INIT_DONE = False
js.skipprevious()
elif request_path == "player/playback/skipTo":
+ state.PLAYBACK_INIT_DONE = False
skip_to(params)
elif request_path == "player/navigation/moveUp":
js.input_up()
diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py
index c796f74a..51d18680 100644
--- a/resources/lib/kodimonitor.py
+++ b/resources/lib/kodimonitor.py
@@ -125,7 +125,9 @@ class KodiMonitor(Monitor):
LOG.debug("Method: %s Data: %s", method, data)
if method == "Player.OnPlay":
+ state.PLAYBACK_INIT_DONE = False
self.PlayBackStart(data)
+ state.PLAYBACK_INIT_DONE = True
elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck
# xbmc.executebuiltin('ReloadSkin()')
@@ -336,6 +338,17 @@ class KodiMonitor(Monitor):
kodi_item={'id': kodi_id,
'type': kodi_type,
'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):
"""
diff --git a/resources/lib/playqueue.py b/resources/lib/playqueue.py
index cdb23cf6..416078ca 100644
--- a/resources/lib/playqueue.py
+++ b/resources/lib/playqueue.py
@@ -77,7 +77,7 @@ class Playqueue(Thread):
raise ValueError('Wrong playlist type passed in: %s' % typus)
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
@@ -95,6 +95,7 @@ class Playqueue(Thread):
for i, child in enumerate(xml):
api = API(child)
PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey())
+ playqueue.plex_transient_token = transient_token
LOG.debug('Firing up Kodi player')
Player().play(playqueue.kodi_pl, None, False, 0)
return playqueue
@@ -114,6 +115,9 @@ class Playqueue(Thread):
"""
LOG.info('New playqueue %s received from Plex companion with offset '
'%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:
xml = PL.get_PMS_playlist(playqueue, playqueue_id)
playqueue.clear()
@@ -123,7 +127,7 @@ class Playqueue(Thread):
LOG.error('Could not get playqueue ID %s', playqueue_id)
return
playqueue.repeat = 0 if not repeat else int(repeat)
- playqueue.token = transient_token
+ playqueue.plex_transient_token = transient_token
PlaybackUtils(xml, playqueue).play_all()
window('plex_customplaylist', value="true")
if offset not in (None, "0"):
diff --git a/resources/lib/plexbmchelper/listener.py b/resources/lib/plexbmchelper/listener.py
index b3735a39..a39c9531 100644
--- a/resources/lib/plexbmchelper/listener.py
+++ b/resources/lib/plexbmchelper/listener.py
@@ -9,6 +9,7 @@ from urlparse import urlparse, parse_qs
from xbmc import sleep
from companion import process_command
+from utils import window
import json_rpc as js
from clientinfo import getXArgsDeviceInfo
import variables as v
@@ -19,6 +20,21 @@ LOG = getLogger("PLEX." + __name__)
###############################################################################
+RESOURCES_XML = ('%s\n'
+ ' \n'
+ '\n') % (v.XML_HEADER,
+ v.ADDON_NAME,
+ v.PLATFORM,
+ v.ADDON_VERSION)
class MyHandler(BaseHTTPRequestHandler):
"""
@@ -78,94 +94,68 @@ class MyHandler(BaseHTTPRequestHandler):
self.serverlist = self.server.client.getServerList()
sub_mgr = self.server.subscription_manager
- try:
- request_path = self.path[1:]
- request_path = sub(r"\?.*", "", request_path)
- url = urlparse(self.path)
- paramarrays = parse_qs(url.query)
- params = {}
- for key in paramarrays:
- params[key] = paramarrays[key][0]
- LOG.debug("remote request_path: %s", request_path)
- LOG.debug("params received from remote: %s", params)
- sub_mgr.update_command_id(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\nVersion: %s"
- % v.ADDON_VERSION)
- elif request_path == "verify":
- self.response("XBMC JSON connection test:\n" +
- js.ping())
- elif request_path == 'resources':
- resp = ('%s'
- ''
- ''
- ''
- % (v.XML_HEADER,
- v.DEVICENAME,
- v.PKC_MACHINE_IDENTIFIER,
- v.PLATFORM,
- v.ADDON_VERSION))
- LOG.debug("crafted resources response: %s", resp)
- self.response(resp, getXArgsDeviceInfo(include_token=False))
- elif "/poll" in request_path:
- if params.get('wait', False) == '1':
- sleep(950)
- command_id = params.get('commandID', 0)
- self.response(
- sub(r"INSERTCOMMANDID",
- str(command_id),
- sub_mgr.msg(js.get_players())),
- {
- 'X-Plex-Client-Identifier': v.PKC_MACHINE_IDENTIFIER,
- 'X-Plex-Protocol': '1.0',
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Max-Age': '1209600',
- 'Access-Control-Expose-Headers':
- 'X-Plex-Client-Identifier',
- 'Content-Type': 'text/xml;charset=utf-8'
- })
- elif "/subscribe" in request_path:
- self.response(v.COMPANION_OK_MESSAGE,
- 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())
+ request_path = self.path[1:]
+ request_path = sub(r"\?.*", "", request_path)
+ url = urlparse(self.path)
+ paramarrays = parse_qs(url.query)
+ params = {}
+ for key in paramarrays:
+ params[key] = paramarrays[key][0]
+ LOG.debug("remote request_path: %s", request_path)
+ LOG.debug("params received from remote: %s", params)
+ sub_mgr.update_command_id(self.headers.get(
+ 'X-Plex-Client-Identifier', self.client_address[0]),
+ params.get('commandID'))
+ if request_path == "version":
+ self.response(
+ "PlexKodiConnect Plex Companion: Running\nVersion: %s"
+ % v.ADDON_VERSION)
+ elif request_path == "verify":
+ self.response("XBMC JSON connection test:\n" + js.ping())
+ elif request_path == 'resources':
+ self.response(
+ RESOURCES_XML.format(
+ title=v.DEVICENAME,
+ machineIdentifier=window('plex_machineIdentifier')),
+ getXArgsDeviceInfo(include_token=False))
+ elif "/poll" in request_path:
+ if params.get('wait') == '1':
+ sleep(950)
+ self.response(
+ sub_mgr.msg(js.get_players()).format(
+ command_id=params.get('commandID', 0)),
+ {
+ 'X-Plex-Client-Identifier': v.PKC_MACHINE_IDENTIFIER,
+ 'X-Plex-Protocol': '1.0',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Max-Age': '1209600',
+ 'Access-Control-Expose-Headers':
+ 'X-Plex-Client-Identifier',
+ 'Content-Type': 'text/xml;charset=utf-8'
+ })
+ elif "/subscribe" in request_path:
+ self.response(v.COMPANION_OK_MESSAGE,
+ getXArgsDeviceInfo(include_token=False))
+ protocol = params.get('protocol')
+ host = self.client_address[0]
+ port = params.get('port')
+ 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') \
+ 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))
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py
index 3876d73d..afab85c3 100644
--- a/resources/lib/plexbmchelper/subscribers.py
+++ b/resources/lib/plexbmchelper/subscribers.py
@@ -3,12 +3,10 @@ Manages getting playstate from Kodi and sending it to the PMS as well as
subscribed Plex Companion clients.
"""
from logging import getLogger
-from re import sub
-from threading import Thread, Lock
+from threading import Thread, RLock
from downloadutils import DownloadUtils as DU
from utils import window, kodi_time_to_millis, Lock_Function
-from playlist_func import init_Plex_playlist
import state
import variables as v
import json_rpc as js
@@ -17,19 +15,19 @@ import json_rpc as js
LOG = getLogger("PLEX." + __name__)
# Need to lock all methods and functions messing with subscribers or state
-LOCK = Lock()
+LOCK = RLock()
LOCKER = Lock_Function(LOCK)
###############################################################################
# What is Companion controllable?
CONTROLLABLE = {
- v.PLEX_TYPE_PHOTO: 'skipPrevious,skipNext,stop',
- v.PLEX_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,'
- 'skipPrevious,skipNext,stepBack,stepForward',
- v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,shuffle,audioStream,'
- 'subtitleStream,seekTo,skipPrevious,skipNext,'
- 'stepBack,stepForward'
+ v.PLEX_PLAYLIST_TYPE_VIDEO: 'playPause,stop,volume,shuffle,audioStream,'
+ 'subtitleStream,seekTo,skipPrevious,skipNext,'
+ 'stepBack,stepForward',
+ v.PLEX_PLAYLIST_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,'
+ 'skipPrevious,skipNext,stepBack,stepForward',
+ v.PLEX_PLAYLIST_TYPE_PHOTO: 'skipPrevious,skipNext,stop'
}
STREAM_DETAILS = {
@@ -38,6 +36,24 @@ STREAM_DETAILS = {
'subtitle': 'currentsubtitle'
}
+XML = ('%s\n'
+ ' \n'
+ ' \n'
+ ' \n'
+ '\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):
"""
@@ -47,8 +63,6 @@ class SubscriptionMgr(object):
self.serverlist = []
self.subscribers = {}
self.info = {}
- self.container_key = None
- self.ratingkey = None
self.server = ""
self.protocol = "http"
self.port = ""
@@ -90,18 +104,126 @@ class SubscriptionMgr(object):
Returns a timeline xml as str
(xml containing video, audio, photo player state)
"""
- msg = v.XML_HEADER
- msg += '\n' % v.PKC_MACHINE_IDENTIFIER
- msg += self._timeline_xml(players.get(v.KODI_TYPE_AUDIO),
- v.PLEX_TYPE_AUDIO)
- msg += self._timeline_xml(players.get(v.KODI_TYPE_PHOTO),
- v.PLEX_TYPE_PHOTO)
- msg += self._timeline_xml(players.get(v.KODI_TYPE_VIDEO),
- v.PLEX_TYPE_VIDEO)
- msg += ""
- LOG.debug('Our PKC message is: %s', msg)
- return msg
+ self.isplaying = False
+ answ = str(XML)
+ timelines = {
+ v.PLEX_PLAYLIST_TYPE_VIDEO: None,
+ v.PLEX_PLAYLIST_TYPE_AUDIO: None,
+ v.PLEX_PLAYLIST_TYPE_PHOTO: None
+ }
+ for typus in timelines:
+ if players.get(v.KODI_PLAYLIST_TYPE_FROM_PLEX_PLAYLIST_TYPE[typus]) is None:
+ timeline = {
+ 'controllable': CONTROLLABLE[typus],
+ '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):
"""
@@ -114,23 +236,6 @@ class SubscriptionMgr(object):
self.last_params['state'] = 'stopped'
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):
"""
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(
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 ' \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 ' \n' % (CONTROLLABLE[ptype], ptype, ptype)
- LOG.debug('INFO: %s', info)
- LOG.debug('playqueue: %s', playqueue)
- status = 'paused' if info['speed'] == '0' else 'playing'
- ret = ' \n'
-
@LOCKER.lockthis
def update_command_id(self, uuid, command_id):
"""
@@ -241,28 +256,27 @@ class SubscriptionMgr(object):
if command_id and self.subscribers.get(uuid):
self.subscribers[uuid].command_id = int(command_id)
+ @LOCKER.lockthis
def notify(self):
"""
Causes PKC to tell the PMS and Plex Companion players to receive a
notification what's being played.
"""
- with LOCK:
- self._cleanup()
- # Do we need a check to NOT tell about e.g. PVR/TV and Addon playback?
+ self._cleanup()
+ # Get all the active/playing Kodi players (video, audio, pictures)
players = js.get_players()
- # fetch the message, subscribers or not, since the server will need the
- # info anyway
- self.isplaying = False
- msg = self.msg(players)
- with LOCK:
+ # Update the PKC info with what's playing on the Kodi side
+ for player in players.values():
+ update_player_info(player['playerid'])
+ if self.subscribers and state.PLAYBACK_INIT_DONE is True:
+ msg = self.msg(players)
if self.isplaying is True:
# If we don't check here, Plex Companion devices will simply
# drop out of the Plex Companion playback screen
for subscriber in self.subscribers.values():
subscriber.send_update(msg, not players)
- self._notify_server(players)
- self.lastplayers = players
- return True
+ self._notify_server(players)
+ self.lastplayers = players
def _notify_server(self, players):
for typus, player in players.iteritems():
@@ -273,7 +287,7 @@ class SubscriptionMgr(object):
except KeyError:
pass
# 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._send_pms_notification(player['playerid'], self.last_params)
@@ -282,18 +296,17 @@ class SubscriptionMgr(object):
status = 'paused' if info['speed'] == '0' else 'playing'
params = {
'state': status,
- 'ratingKey': self.ratingkey,
- 'key': '/library/metadata/%s' % self.ratingkey,
+ 'ratingKey': info['plex_id'],
+ 'key': '/library/metadata/%s' % info['plex_id'],
'time': kodi_time_to_millis(info['time']),
'duration': kodi_time_to_millis(info['totaltime'])
}
- if self.container_key:
- params['containerKey'] = self.container_key
- if self.container_key is not None and \
- self.container_key.startswith('/playQueues/'):
- playqueue = self.playqueue.playqueues[playerid]
- params['playQueueVersion'] = playqueue.version
- params['playQueueItemID'] = playqueue.id
+ if info['container_key'] is not None:
+ params['containerKey'] = info['container_key']
+ if info['container_key'].startswith('/playQueues/'):
+ playqueue = self.playqueue.playqueues[playerid]
+ params['playQueueVersion'] = playqueue.version
+ params['playQueueItemID'] = playqueue.id
self.last_params = params
return params
@@ -384,11 +397,10 @@ class Subscriber(object):
return True
else:
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",
self.uuid, self.command_id, msg)
- url = self.protocol + '://' + self.host + ':' + self.port \
- + "/:/timeline"
+ url = '%s://%s:%s/:/timeline' % (self.protocol, self.host, self.port)
thread = Thread(target=self._threaded_send, args=(url, msg))
thread.start()
diff --git a/resources/lib/state.py b/resources/lib/state.py
index a115f5bc..4c675e8d 100644
--- a/resources/lib/state.py
+++ b/resources/lib/state.py
@@ -106,6 +106,7 @@ PLAYER_STATES = {
'kodi_type': None,
'plex_id': None,
'plex_type': None,
+ 'container_key': None,
'volume': 100,
'muted': False
},
@@ -116,6 +117,10 @@ PLAYER_STATES = {
# paths for playback (since we're not receiving a Kodi id)
PLEX_IDS = {}
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
WEBSERVER_PORT = 8080
diff --git a/resources/lib/variables.py b/resources/lib/variables.py
index 2284f9c0..086a3ec0 100644
--- a/resources/lib/variables.py
+++ b/resources/lib/variables.py
@@ -129,6 +129,20 @@ PLEX_TYPE_MUSICVIDEO = 'musicvideo'
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
KODI_TYPE_VIDEO = 'video'