""" 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, RLock from downloadutils import DownloadUtils as DU from utils import window, kodi_time_to_millis import state import variables as v import json_rpc as js ############################################################################### LOG = getLogger("PLEX." + __name__) ############################################################################### # 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' } STREAM_DETAILS = { 'video': 'currentvideostream', 'audio': 'currentaudiostream', 'subtitle': 'currentsubtitle' } class SubscriptionMgr(object): """ Manages Plex companion subscriptions """ def __init__(self, request_mgr, player, mgr): self.serverlist = [] self.subscribers = {} self.info = {} self.container_key = None self.ratingkey = None self.server = "" self.protocol = "http" self.port = "" # In order to be able to signal a stop at the end self.last_params = {} self.lastplayers = {} self.xbmcplayer = player self.playqueue = mgr.playqueue self.request_mgr = request_mgr @staticmethod def _headers(): """ Headers are different for Plex Companion! """ return { 'Content-type': 'text/plain', 'Connection': 'Keep-Alive', 'Keep-Alive': 'timeout=20', 'X-Plex-Client-Identifier': v.PKC_MACHINE_IDENTIFIER, 'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier', 'X-Plex-Protocol': "1.0" } def _server_by_host(self, host): if len(self.serverlist) == 1: return self.serverlist[0] for server in self.serverlist: if (server.get('serverName') in host or server.get('server') in host): return server return {} def msg(self, players): """ Returns a timeline xml as str (xml containing video, audio, photo player state) """ LOG.debug('players: %s', players) msg = v.XML_HEADER msg += '\n' % (CONTROLLABLE[ptype], ptype, ptype) playerid = player['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() # Get the message together to send to Plex info = state.PLAYER_STATES[playerid] status = 'paused' if info['speed'] == '0' else 'playing' ret = ' 30: subscriber.cleanup() del self.subscribers[subscriber.uuid] class Subscriber(object): """ Plex Companion subscribing device """ def __init__(self, protocol, host, port, uuid, command_id, sub_mgr, request_mgr): self.protocol = protocol or "http" self.host = host self.port = port or 32400 self.uuid = uuid or host self.command_id = int(command_id) or 0 self.navlocationsent = False self.age = 0 self.sub_mgr = sub_mgr self.request_mgr = request_mgr def __eq__(self, other): return self.uuid == other.uuid def cleanup(self): """ Closes the connection to the Plex Companion client """ self.request_mgr.closeConnection(self.protocol, self.host, self.port) def send_update(self, msg, is_nav): """ Sends msg to the Plex Companion client (via .../:/timeline) """ self.age += 1 if not is_nav: self.navlocationsent = False elif self.navlocationsent: return True else: self.navlocationsent = True msg = sub(r"INSERTCOMMANDID", str(self.command_id), msg) 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" thread = Thread(target=self._threaded_send, args=(url, msg)) thread.start() def _threaded_send(self, url, msg): """ Threaded POST request, because they stall due to PMS response missing the Content-Length header :-( """ response = DU().downloadUrl(url, postBody=msg, action_type="POST") if response in (False, None, 401): self.sub_mgr.remove_subscriber(self.uuid)