0
# Real-time Server Monitoring
1
2
WebSocket-based real-time monitoring of server events, activities, and notifications for building responsive Plex applications that react immediately to server changes.
3
4
## Capabilities
5
6
### AlertListener Setup
7
8
Primary class for establishing WebSocket connections and receiving real-time server notifications.
9
10
```python { .api }
11
class AlertListener:
12
def __init__(self, server, callback, ws_url=None, **kwargs):
13
"""
14
Create real-time alert listener for server events.
15
16
Args:
17
server (PlexServer): Connected Plex server
18
callback (function): Function to handle incoming alerts
19
ws_url (str, optional): Custom WebSocket URL
20
**kwargs: Additional WebSocket connection parameters
21
"""
22
23
def start(self):
24
"""Start listening for server alerts in background thread."""
25
26
def stop(self):
27
"""Stop listening and close WebSocket connection."""
28
29
def is_running(self):
30
"""
31
Check if alert listener is actively running.
32
33
Returns:
34
bool: True if listener is active
35
"""
36
```
37
38
### Alert Event Types
39
40
Server events that can be monitored through the AlertListener system.
41
42
```python { .api }
43
# Common alert event types received via callback
44
class AlertEvent:
45
"""Alert event data structure."""
46
47
@property
48
def type(self):
49
"""str: Event type identifier."""
50
51
@property
52
def data(self):
53
"""dict: Event-specific data payload."""
54
55
@property
56
def timestamp(self):
57
"""datetime: When the event occurred."""
58
59
# Event type constants
60
ALERT_TYPES = {
61
'library.refresh.started': 'Library refresh started',
62
'library.refresh.progress': 'Library refresh progress update',
63
'library.refresh.finished': 'Library refresh completed',
64
'media.scrobble': 'Media playback scrobble event',
65
'media.play': 'Media playback started',
66
'media.pause': 'Media playback paused',
67
'media.resume': 'Media playback resumed',
68
'media.stop': 'Media playback stopped',
69
'admin.database.backup': 'Database backup event',
70
'admin.database.corrupted': 'Database corruption detected',
71
'transcoder.progress': 'Transcoding progress update',
72
'activity': 'Background activity update'
73
}
74
```
75
76
### Callback Function Interface
77
78
Structure for handling incoming alert events.
79
80
```python { .api }
81
def alert_callback(alert_data):
82
"""
83
Callback function for processing server alerts.
84
85
Args:
86
alert_data (dict): Raw alert data from server containing:
87
- type (str): Alert event type
88
- NotificationContainer (dict): Event details
89
- size (int): Number of notifications
90
- data (list): List of notification objects
91
"""
92
93
# Example callback implementation structure
94
def process_alert(alert_data):
95
alert_type = alert_data.get('type')
96
container = alert_data.get('NotificationContainer', {})
97
98
if alert_type == 'playing':
99
# Handle playback events
100
sessions = container.get('PlaySessionStateNotification', [])
101
for session in sessions:
102
handle_playback_event(session)
103
104
elif alert_type == 'timeline':
105
# Handle timeline updates
106
entries = container.get('TimelineEntry', [])
107
for entry in entries:
108
handle_timeline_update(entry)
109
110
elif alert_type == 'activity':
111
# Handle background activities
112
activities = container.get('ActivityNotification', [])
113
for activity in activities:
114
handle_activity_update(activity)
115
```
116
117
## Usage Examples
118
119
### Basic Alert Monitoring
120
121
```python
122
from plexapi.server import PlexServer
123
from plexapi.alert import AlertListener
124
125
plex = PlexServer('http://localhost:32400', token='your-token')
126
127
def my_callback(alert_data):
128
"""Handle server alerts."""
129
alert_type = alert_data.get('type', 'unknown')
130
print(f"Alert received: {alert_type}")
131
132
if alert_type == 'playing':
133
# Handle playback state changes
134
container = alert_data.get('NotificationContainer', {})
135
sessions = container.get('PlaySessionStateNotification', [])
136
137
for session in sessions:
138
state = session.get('state', '')
139
session_key = session.get('sessionKey', '')
140
print(f"Session {session_key}: {state}")
141
142
# Create and start listener
143
listener = AlertListener(plex, my_callback)
144
listener.start()
145
146
# Keep program running to receive alerts
147
try:
148
while True:
149
time.sleep(1)
150
except KeyboardInterrupt:
151
listener.stop()
152
```
153
154
### Playback Monitoring
155
156
```python
157
def playback_monitor(alert_data):
158
"""Monitor media playback events."""
159
if alert_data.get('type') == 'playing':
160
container = alert_data.get('NotificationContainer', {})
161
sessions = container.get('PlaySessionStateNotification', [])
162
163
for session in sessions:
164
user = session.get('username', 'Unknown')
165
state = session.get('state', '')
166
title = session.get('title', 'Unknown')
167
168
if state == 'playing':
169
print(f"{user} started playing: {title}")
170
elif state == 'paused':
171
print(f"{user} paused: {title}")
172
elif state == 'stopped':
173
print(f"{user} stopped: {title}")
174
175
listener = AlertListener(plex, playback_monitor)
176
listener.start()
177
```
178
179
### Library Activity Monitoring
180
181
```python
182
def library_monitor(alert_data):
183
"""Monitor library scanning and updates."""
184
alert_type = alert_data.get('type', '')
185
186
if alert_type == 'activity':
187
container = alert_data.get('NotificationContainer', {})
188
activities = container.get('ActivityNotification', [])
189
190
for activity in activities:
191
activity_type = activity.get('type', '')
192
title = activity.get('title', '')
193
progress = activity.get('progress', 0)
194
195
if activity_type == 'library.refresh':
196
print(f"Library scan: {title} - {progress}% complete")
197
elif activity_type == 'transcoder':
198
print(f"Transcoding: {title} - {progress}% complete")
199
200
listener = AlertListener(plex, library_monitor)
201
listener.start()
202
```
203
204
### Timeline Event Monitoring
205
206
```python
207
def timeline_monitor(alert_data):
208
"""Monitor media timeline updates."""
209
if alert_data.get('type') == 'timeline':
210
container = alert_data.get('NotificationContainer', {})
211
entries = container.get('TimelineEntry', [])
212
213
for entry in entries:
214
item_type = entry.get('type', '')
215
state = entry.get('state', '')
216
title = entry.get('title', 'Unknown')
217
218
if state == 'created':
219
print(f"New {item_type} added: {title}")
220
elif state == 'updated':
221
print(f"{item_type} updated: {title}")
222
elif state == 'deleted':
223
print(f"{item_type} deleted: {title}")
224
225
listener = AlertListener(plex, timeline_monitor)
226
listener.start()
227
```
228
229
### Multi-Event Handler
230
231
```python
232
class PlexEventHandler:
233
"""Comprehensive event handler for multiple alert types."""
234
235
def __init__(self, plex_server):
236
self.plex = plex_server
237
self.listener = AlertListener(plex_server, self.handle_alert)
238
239
def handle_alert(self, alert_data):
240
"""Route alerts to specific handlers."""
241
alert_type = alert_data.get('type', '')
242
243
if alert_type == 'playing':
244
self.handle_playback(alert_data)
245
elif alert_type == 'timeline':
246
self.handle_timeline(alert_data)
247
elif alert_type == 'activity':
248
self.handle_activity(alert_data)
249
else:
250
print(f"Unhandled alert type: {alert_type}")
251
252
def handle_playback(self, alert_data):
253
"""Handle playback state changes."""
254
container = alert_data.get('NotificationContainer', {})
255
sessions = container.get('PlaySessionStateNotification', [])
256
257
for session in sessions:
258
self.log_playback_event(session)
259
260
def handle_timeline(self, alert_data):
261
"""Handle library timeline updates."""
262
container = alert_data.get('NotificationContainer', {})
263
entries = container.get('TimelineEntry', [])
264
265
for entry in entries:
266
self.log_timeline_event(entry)
267
268
def handle_activity(self, alert_data):
269
"""Handle background activity updates."""
270
container = alert_data.get('NotificationContainer', {})
271
activities = container.get('ActivityNotification', [])
272
273
for activity in activities:
274
self.log_activity_event(activity)
275
276
def log_playback_event(self, session):
277
"""Log playback events to file or database."""
278
timestamp = datetime.now()
279
user = session.get('username', 'Unknown')
280
state = session.get('state', '')
281
title = session.get('title', 'Unknown')
282
283
log_entry = f"{timestamp}: {user} {state} {title}"
284
print(log_entry)
285
# Save to database or log file
286
287
def log_timeline_event(self, entry):
288
"""Log timeline events."""
289
# Implementation for timeline logging
290
pass
291
292
def log_activity_event(self, activity):
293
"""Log activity events."""
294
# Implementation for activity logging
295
pass
296
297
def start(self):
298
"""Start monitoring."""
299
self.listener.start()
300
print("Event monitoring started")
301
302
def stop(self):
303
"""Stop monitoring."""
304
self.listener.stop()
305
print("Event monitoring stopped")
306
307
# Usage
308
handler = PlexEventHandler(plex)
309
handler.start()
310
311
# Run until interrupted
312
try:
313
while True:
314
time.sleep(1)
315
except KeyboardInterrupt:
316
handler.stop()
317
```
318
319
### Error Handling and Reconnection
320
321
```python
322
import time
323
import logging
324
from plexapi.alert import AlertListener
325
from plexapi.exceptions import PlexApiException
326
327
class RobustAlertListener:
328
"""Alert listener with automatic reconnection."""
329
330
def __init__(self, server, callback, max_retries=5):
331
self.server = server
332
self.callback = callback
333
self.max_retries = max_retries
334
self.retry_count = 0
335
self.listener = None
336
self.running = False
337
338
def start(self):
339
"""Start listening with automatic retry on failure."""
340
self.running = True
341
while self.running and self.retry_count < self.max_retries:
342
try:
343
self.listener = AlertListener(self.server, self.callback)
344
self.listener.start()
345
self.retry_count = 0 # Reset on successful connection
346
break
347
348
except PlexApiException as e:
349
self.retry_count += 1
350
wait_time = min(2 ** self.retry_count, 60) # Exponential backoff
351
logging.error(f"Alert listener failed: {e}. Retrying in {wait_time}s...")
352
time.sleep(wait_time)
353
354
def stop(self):
355
"""Stop listening."""
356
self.running = False
357
if self.listener:
358
self.listener.stop()
359
360
# Usage with robust error handling
361
robust_listener = RobustAlertListener(plex, my_callback)
362
robust_listener.start()
363
```
364
365
### Performance Considerations
366
367
```python
368
def efficient_callback(alert_data):
369
"""Efficiently handle high-volume alerts."""
370
# Process alerts quickly to avoid blocking
371
alert_type = alert_data.get('type', '')
372
373
# Queue heavy processing for background thread
374
if alert_type == 'playing':
375
# Quick processing only
376
session_count = len(alert_data.get('NotificationContainer', {}).get('PlaySessionStateNotification', []))
377
print(f"Active sessions: {session_count}")
378
379
# For complex processing, consider using a queue
380
# alert_queue.put(alert_data)
381
382
# Optional: Use threading for heavy processing
383
import queue
384
import threading
385
386
alert_queue = queue.Queue()
387
388
def background_processor():
389
"""Process alerts in background thread."""
390
while True:
391
try:
392
alert_data = alert_queue.get(timeout=1)
393
# Perform heavy processing here
394
process_complex_alert(alert_data)
395
alert_queue.task_done()
396
except queue.Empty:
397
continue
398
399
# Start background processor
400
processor_thread = threading.Thread(target=background_processor, daemon=True)
401
processor_thread.start()
402
```
403
404
## Installation Requirements
405
406
The AlertListener functionality requires the optional WebSocket dependency:
407
408
```bash
409
pip install PlexAPI[alert]
410
```
411
412
This installs the `websocket-client` package required for real-time WebSocket connections to the Plex server.