Fix WebSocket session flapping with ForceKeepAlive protocol
Implements Jellyfin's ForceKeepAlive/KeepAlive mechanism to prevent constant reconnects every 2 minutes. WebSocketApp is now recreated for each connection attempt to avoid memory leaks. Removed problematic ping_timeout and reconnect parameters that caused instability.
This commit is contained in:
@@ -46,7 +46,12 @@ class WebSocketClient(threading.Thread):
|
|||||||
result = json.loads(message)
|
result = json.loads(message)
|
||||||
message_type = result['MessageType']
|
message_type = result['MessageType']
|
||||||
|
|
||||||
if message_type == 'Play':
|
if message_type == 'ForceKeepAlive':
|
||||||
|
timeout_seconds = result.get('Data', 60)
|
||||||
|
log.debug("Received ForceKeepAlive with timeout: {0}s".format(timeout_seconds))
|
||||||
|
self._send_keep_alive()
|
||||||
|
|
||||||
|
elif message_type == 'Play':
|
||||||
data = result['Data']
|
data = result['Data']
|
||||||
self._play(data)
|
self._play(data)
|
||||||
|
|
||||||
@@ -237,6 +242,9 @@ class WebSocketClient(threading.Thread):
|
|||||||
def on_error(self, ws, error):
|
def on_error(self, ws, error):
|
||||||
log.debug("Error: {0}".format(error))
|
log.debug("Error: {0}".format(error))
|
||||||
|
|
||||||
|
def on_close(self, ws, close_status_code, close_msg):
|
||||||
|
log.debug("WebSocket closed. Code: {0}, Message: {1}".format(close_status_code, close_msg))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
token = None
|
token = None
|
||||||
@@ -259,17 +267,23 @@ class WebSocketClient(threading.Thread):
|
|||||||
)
|
)
|
||||||
log.debug("websocket url: {0}".format(websocket_url))
|
log.debug("websocket url: {0}".format(websocket_url))
|
||||||
|
|
||||||
self._client = websocket.WebSocketApp(
|
|
||||||
websocket_url,
|
|
||||||
on_open=lambda ws: self.on_open(ws),
|
|
||||||
on_message=lambda ws, message: self.on_message(ws, message),
|
|
||||||
on_error=lambda ws, error: self.on_error(ws, error))
|
|
||||||
|
|
||||||
log.debug("Starting WebSocketClient")
|
log.debug("Starting WebSocketClient")
|
||||||
|
|
||||||
while not self.monitor.abortRequested():
|
while not self.monitor.abortRequested():
|
||||||
|
|
||||||
self._client.run_forever(ping_interval=5, reconnect=13, ping_timeout=2)
|
# Create a new WebSocketApp for each connection attempt to avoid
|
||||||
|
# memory leaks from reusing a potentially dirty connection object
|
||||||
|
self._client = websocket.WebSocketApp(
|
||||||
|
websocket_url,
|
||||||
|
on_open=lambda ws: self.on_open(ws),
|
||||||
|
on_message=lambda ws, message: self.on_message(ws, message),
|
||||||
|
on_error=lambda ws, error: self.on_error(ws, error),
|
||||||
|
on_close=lambda ws, status, msg: self.on_close(ws, status, msg))
|
||||||
|
|
||||||
|
# Use ping_interval without ping_timeout to keep connection alive
|
||||||
|
# without forcing disconnection. The server's ForceKeepAlive/KeepAlive
|
||||||
|
# mechanism handles the actual keepalive logic.
|
||||||
|
self._client.run_forever(ping_interval=10)
|
||||||
|
|
||||||
if self._stop_websocket:
|
if self._stop_websocket:
|
||||||
break
|
break
|
||||||
@@ -291,6 +305,17 @@ class WebSocketClient(threading.Thread):
|
|||||||
self._client.close()
|
self._client.close()
|
||||||
log.debug("Stopping WebSocket (stop_client called)")
|
log.debug("Stopping WebSocket (stop_client called)")
|
||||||
|
|
||||||
|
def _send_keep_alive(self):
|
||||||
|
"""Send a KeepAlive message to the server to maintain the connection."""
|
||||||
|
try:
|
||||||
|
keep_alive_message = json.dumps({
|
||||||
|
'MessageType': 'KeepAlive'
|
||||||
|
})
|
||||||
|
self._client.send(keep_alive_message)
|
||||||
|
log.debug("Sent KeepAlive message")
|
||||||
|
except Exception as error:
|
||||||
|
log.debug("Error sending KeepAlive: {0}".format(error))
|
||||||
|
|
||||||
def post_capabilities(self):
|
def post_capabilities(self):
|
||||||
|
|
||||||
settings = xbmcaddon.Addon()
|
settings = xbmcaddon.Addon()
|
||||||
|
|||||||
Reference in New Issue
Block a user