# This file is part of PlexPy. # # PlexPy is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # PlexPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with PlexPy. If not, see . # Mostly borrowed from https://github.com/trakt/Plex-Trakt-Scrobbler from plexpy import logger, activity_pinger import threading import plexpy import json import time import websocket name = 'websocket' opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY) def start_thread(): # Check for any existing sessions on start up activity_pinger.check_active_sessions(ws_request=True) # Start the websocket listener on it's own thread threading.Thread(target=run).start() def run(): from websocket import create_connection uri = 'ws://%s:%s/:/websockets/notifications' % ( plexpy.CONFIG.PMS_IP, plexpy.CONFIG.PMS_PORT ) # Set authentication token (if one is available) if plexpy.CONFIG.PMS_TOKEN: uri += '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN ws_connected = False reconnects = 0 # Try an open the websocket connection - if it fails after 5 retries fallback to polling while not ws_connected and reconnects < 5: try: logger.info(u'PlexPy WebSocket :: Opening websocket, connection attempt %s.' % str(reconnects + 1)) ws = create_connection(uri) reconnects = 0 ws_connected = True logger.info(u'PlexPy WebSocket :: Ready') except IOError, e: logger.error(u'PlexPy WebSocket :: %s.' % e) reconnects += 1 time.sleep(5) while ws_connected: try: process(*receive(ws)) # successfully received data, reset reconnects counter reconnects = 0 except websocket.WebSocketConnectionClosedException: if reconnects <= 5: reconnects += 1 # Increasing sleep interval between reconnections if reconnects > 1: time.sleep(2 * (reconnects - 1)) logger.warn(u'PlexPy WebSocket :: Connection has closed, reconnecting...') try: ws = create_connection(uri) except IOError, e: logger.info(u'PlexPy WebSocket :: %s.' % e) else: ws_connected = False break if not ws_connected: logger.error(u'PlexPy WebSocket :: Connection unavailable, falling back to polling.') plexpy.POLLING_FAILOVER = True plexpy.initialize_scheduler() logger.debug(u'Leaving thread.') def receive(ws): frame = ws.recv_frame() if not frame: raise websocket.WebSocketException("Not a valid frame %s" % frame) elif frame.opcode in opcode_data: return frame.opcode, frame.data elif frame.opcode == websocket.ABNF.OPCODE_CLOSE: ws.send_close() return frame.opcode, None elif frame.opcode == websocket.ABNF.OPCODE_PING: ws.pong("Hi!") return None, None def process(opcode, data): from plexpy import activity_handler if opcode not in opcode_data: return False try: info = json.loads(data) except Exception as ex: logger.warn(u'PlexPy WebSocket :: Error decoding message from websocket: %s' % ex) logger.debug(data) return False type = info.get('type') if not type: return False if type == 'playing': # logger.debug('%s.playing %s' % (name, info)) try: time_line = info.get('_children') except: logger.debug(u"PlexPy WebSocket :: Session found but unable to get timeline data.") return False activity = activity_handler.ActivityHandler(timeline=time_line[0]) activity.process() return True