Commit graph

40 commits

Author SHA1 Message Date
SergeantPanda
d676903825 Enhancement: Cache channel names to reduce repeated DB queries in StreamManager and StreamGenerator. This improves performance by avoiding unnecessary database calls during retries and logging events. (Fixes #1138)
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
2026-04-17 10:55:41 -05:00
SergeantPanda
6d06422ddd Enhancement: client_connect and client_disconnect system events now include the **username** of the connected user. The username is stored alongside the client metadata in Redis and included in the event payload for log_system_event calls (making it available to webhook and script integrations). 2026-03-29 16:06:14 -05:00
dekzter
90a600b48f merged in dev 2026-03-26 12:17:43 -04:00
None
40d0bc9567 fix: cap keepalive mode duration to prevent indefinite client hold
- Add MAX_KEEPALIVE_DURATION = 300s to TSConfig
- Track keepalive_start_time in _stream_data_generator; disconnect after cap is exceeded with a warning log
- Reset timer on real data so independent stalls don't accumulate
- Fix pre-existing test helper missing throttle attributes in NonOwnerWorkerKeepaliveTests
2026-03-15 14:22:57 -05:00
cmcpherson274
c8b665dc98
Update last_active timestamp in keepalive logic
Update last active timestamp for clients during failover.
2026-03-15 14:08:43 -04:00
cmcpherson274
f7085e5dc2
Add 'last_active' field to stats in stream_generator 2026-03-15 12:10:38 -04:00
dekzter
f69a462253 merged in dev 2026-03-13 08:22:44 -04:00
SergeantPanda
9805732582 Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/CodeBormen/1023 2026-03-05 11:16:20 -06:00
SergeantPanda
06f864ad6d fix(stream_generator): optimize resource and health checks with throttling 2026-03-04 17:37:42 -06:00
None
e5206545dd fix(dvr): add stream reconnection resilience, fix comskip exit code handling
- Add reconnection loop to DVR streaming: on transient connectivity loss (ReadTimeout, ConnectionError, ChunkedEncodingError), the recording task retries the same TS proxy base up to 5 times with a 2-second delay, appending to the existing file. Counter resets on successful data receipt. TS container format tolerates brief discontinuities; MKV remux normalises timestamps.
- Extract _check_recording_cancelled() helper to consolidate repeated stop/delete DB checks across the streaming and reconnection paths.
- Fix comskip treating exit code 1 (no commercials detected) as a fatal error.
  Use check=False and handle return codes explicitly: 0 = commercials found,
  1 = no commercials (skipped), negative = signal, other = real error.
- Add human-readable reason messages for comskip 'skipped' notifications in
  WebSocket handler.
- Remove redundant conditional Channel import in stream_generator._cleanup() that shadowed the module-level import, causing UnboundLocalError when the conditional branch did not execute.
2026-03-04 10:21:02 -06:00
None
f28530a47b fix(dvr): add recording extend, fix cross-recording interference, artwork race, and keepalive gaps
File changes:

Backend

apps/channels/api_views.py — Added extend() action that uses queryset .update() to bypass signals, letting the running task pick up the new end_time via polling. Moved WS recording_stopped notification to synchronous (before HTTP response). Refactored destroy() to delete DB row first, send WS event immediately, defer cleanup to background thread.

apps/channels/signals.py — Added pre_save guard to skip task revocation when status is "recording". Added post_save reentrancy guard to skip artwork prefetch for internal field-only saves (custom_properties, task_id, end_time).

apps/channels/tasks.py — Added 2-second polling loop in streaming that checks for stop, deletion, and extended end_time. Added refresh_from_db() + merge before metadata save to prevent overwriting concurrent artwork prefetch. Replaced inline PeriodicTask cleanup with revoke_task()/_dvr_task_name(). Consolidated redundant DB queries in error path. Fixed TOCTOU os.path.exists + os.remove. Preserved "stopped" status in final-status logic instead of overwriting with "completed".

core/utils.py — Made send_websocket_update gevent-safe by detecting monkey-patching and offloading async_to_sync to gevent's native threadpool, fixing the event loop deadlock that caused cross-recording interference.

apps/proxy/ts_proxy/client_manager.py — Moved _trigger_stats_update to a background thread so remove_client() returns immediately.

apps/proxy/ts_proxy/stream_generator.py — Added Redis-based health check for non-owner workers in _should_send_keepalive(), so keepalives fire correctly even when stream_manager is None.

Frontend

frontend/src/WebSocket.jsx — Added 400ms debounced scheduleRecordingFetch() replacing all inline fetchRecordings() calls. Added recording_extended and recording_updated event handlers. recording_cancelled now does surgical removeRecording() by ID when available.

frontend/src/api.js — Added extendRecording(id, extraMinutes).

frontend/src/components/cards/RecordingCard.jsx — Added Extend menu (+15m/+30m/+1h) for in-progress recordings. Widened Comskip button to stopped/interrupted statuses.

frontend/src/components/forms/ProgramRecordingModal.jsx — Removed manual fetchRecordings() calls, relying on WS-driven debounced refresh.

frontend/src/components/forms/RecordingDetailsModal.jsx — Added stopped to playable statuses. Widened Comskip button. Removed manual fetchRecordings().

frontend/src/components/forms/RecurringRuleModal.jsx — Removed all manual fetchRecordings() calls, relying on WS events.

frontend/src/pages/Guide.jsx — Removed isLoading subscription that caused loading flash on debounced refresh. Removed manual fetchRecordings() after saving a series rule.

frontend/src/store/channels.jsx — Stripped isLoading/error state from fetchRecordings to prevent full-page loading indicators. Added no-op guards in removeRecording.

frontend/src/utils/cards/RecordingCardUtils.js — Added extendRecordingById wrapper.

Tests

apps/channels/tests/test_recording_extend.py (new) — 14 tests: extend endpoint validation, stacked extensions, signal bypass, pre_save guard behavior.

apps/channels/tests/test_recording_stop_cancel.py (new) — 18 tests: stop endpoint, DVR client teardown, cancel was_in_progress flag, run_recording race guards, signal reentrancy guards.

apps/channels/tests/test_ts_proxy_keepalive.py (new) — 21 tests: keepalive owner/non-owner logic, stats update error handling, client removal non-blocking, timing invariants.

apps/channels/tests/test_recording_scheduling.py — Updated mock assertion to match new _stop_dvr_clients call signature.

Summary:

Adds an Extend action for in-progress recordings that bypasses Django signals and lets the running task dynamically pick up the new end_time via its 2-second DB polling loop. Fixes cross-recording interference caused by send_websocket_update deadlocking the gevent event loop. Fixes an artwork race condition where run_recording overwrote concurrent prefetch data. Adds Redis-based keepalive health checks for non-owner workers to prevent DVR read timeouts during source transitions. Replaces manual fetchRecordings() calls throughout the frontend with a debounced WS-driven refresh. 53 new tests.
2026-03-04 10:21:02 -06:00
SergeantPanda
fc75a68195 Enhancement: new clients joining an active channel are now positioned a configurable number of seconds behind live rather than a fixed chunk count. The start position is determined by wall-clock chunk receive time (stored as a Redis sorted set alongside the buffer), so the buffer depth is consistent in seconds regardless of stream bitrate. Setting the value to 0 starts clients at live with no buffer. Defaults to 2 seconds. Existing chunk-count gating for the first client connecting to a channel is unchanged. The setting is exposed in Settings → Proxy as "New Client Buffer (seconds)". 2026-03-03 18:12:32 -06:00
SergeantPanda
d70450b7fc Merge branch 'dev' of https://github.com/Dispatcharr/Dispatcharr into pr/CodeBormen/949 2026-03-03 16:16:25 -06:00
SergeantPanda
e78c24aad7 Enhance Redis chunk management in StreamBuffer and StreamGenerator
- Register Lua scripts for optimized chunk retrieval in StreamBuffer.
- Implement atomic Lua binary search to find the oldest available chunk in Redis.
- Update StreamGenerator to handle expired chunks more efficiently, reducing TOCTOU race conditions.
2026-03-03 15:13:46 -06:00
None
20ce45f7ad fix(ts-proxy): Add Stream fallback in cleanup path to prevent counter leak on stream preview
When previewing a stream directly (via stream hash), the cleanup in stream_generator.py tried Channel.objects.get(uuid=stream_hash) which failed with ValidationError since the hash is not a UUID. This
prevented release_stream() from running, permanently leaking the
connection counter. With max_streams=1, a single preview permanently blocked all subsequent streams.

Added Stream.objects.get(stream_hash=...) fallback so the cleanup
path works for both Channel (UUID) and Stream (hash) identifiers.
Also added Stream import to stream_generator.py.
2026-02-12 00:02:40 -06:00
None
8bb8a0fca9 fix(ts-proxy): Remove redundant local import causing UnboundLocalError
The local `from apps.channels.models import Channel` inside a
conditional block in _handle_client_disconnect() shadowed the top-level import. When the condition was False, Python's function-level scoping caused line 462's Channel.objects.get() to raise UnboundLocalError. Channel is already imported at module level (line 11), making the local import unnecessary.

Discovered when testing proxy changes
2026-02-11 20:52:30 -06:00
None
fe17d0d585 Check release_stream() boolean return at all call sites
All 12 call sites for Channel.release_stream() and Stream.release_stream() now check the boolean return value and log warnings when release fails.

Files updated:
- server.py: 4 call sites (zombie cleanup + _clean_redis_keys)
- channel_service.py: 2 call sites (stop_channel)
- stream_generator.py: 1 call site (last client disconnect)
- views.py: 4 call sites (failed init, redirect, init failure, exception)
- url_utils.py: 1 call site (URL generation error)

Also fixed bare except clause in _clean_redis_keys and uncaught
Stream.DoesNotExist in channel_service.stop_channel.

Based on PR #838 by patchy8736
2026-02-11 19:49:11 -06:00
None
16309cd9b4 Remove if logic from scheduled shutdown
- stream_generator.py: _schedule_channel_shutdown_if_needed() already guards itself, the if logic created conflict between release_stream() and stop_channel(), so if the stream was already released the shutdown would not be scheduled
2026-02-10 18:15:48 -06:00
dekzter
4a23883d0c merged in upstream 2025-12-05 08:34:29 -05:00
SergeantPanda
89a23164ff Enhancement: Add system event logging and viewer with M3U/EPG endpoint caching
System Event Logging:
- Add SystemEvent model with 15 event types tracking channel operations, client connections, M3U/EPG activities, and buffering events
- Log detailed metrics for M3U/EPG refresh operations (streams/programs created/updated/deleted)
- Track M3U/EPG downloads with client information (IP address, user agent, profile, channel count)
- Record channel lifecycle events (start, stop, reconnect) with stream and client details
- Monitor client connections/disconnections and buffering events with stream metadata

Event Viewer UI:
- Add SystemEvents component with real-time updates via WebSocket
- Implement pagination, filtering by event type, and configurable auto-refresh
- Display events with color-coded badges and type-specific icons
- Integrate event viewer into Stats page with modal display
- Add event management settings (retention period, refresh rate)

M3U/EPG Endpoint Optimizations:
- Implement content caching with 5-minute TTL to reduce duplicate processing
- Add client-based event deduplication (2-second window) using IP and user agent hashing
- Support HEAD requests for efficient preflight checks
- Cache streamed EPG responses while maintaining streaming behavior for first request
2025-11-20 17:41:06 -06:00
dekzter
cd35e671e5 byte to string fix 2025-11-12 17:52:48 -05:00
SergeantPanda
6715bc7c5c Enhancement: Update TTL settings for client records and implement periodic refresh during active streaming 2025-10-31 11:53:16 -05:00
SergeantPanda
74280baa85 Changed read timeout for http connection for the proxy server to 10 secounds to avoid unnecessary timeouts.
Improved try_next_stream to not fail if the returned stream is the same. It will now try a different stream.
Force a client to jump ahead in the buffer if they fall to far behind.
2025-10-11 19:45:21 -05:00
SergeantPanda
50048518a9 Fixes bug where mulitiple channel initializations can occur which leads to choppy streams and zombie channels. 2025-05-27 19:05:26 -05:00
SergeantPanda
898224dc72 Fix potential issue during stream switches. 2025-05-05 17:02:28 -05:00
SergeantPanda
423020861c Replace time.sleep with gevent.sleep for improved concurrency 2025-04-30 13:32:16 -05:00
SergeantPanda
b7c543b5f5 Use gevent sleep instead of sleep. 2025-04-30 12:48:50 -05:00
SergeantPanda
bdb8d326a5 Add better logging for which channel clients are getting chunks from. 2025-04-30 12:17:11 -05:00
SergeantPanda
b439eb810c Cleanup channel lock instead of stream lock. 2025-04-28 15:05:58 -05:00
SergeantPanda
07485f87a3 Add stream health recovery and reconnection logic 2025-04-10 10:14:22 -05:00
SergeantPanda
5c74aec790 Fixes channel switching issue (release lock faster) 2025-04-10 07:38:29 -05:00
dekzter
7351264e8a centralized and lazy-loaded redis client singleton, check for manage.py commands so we don't init proxyservers (redis connection), put manage commmands before starting uwsgi 2025-04-04 16:18:12 -04:00
SergeantPanda
560345f7b1 Changed profile to stream_profile and utilized constants more for consistency. 2025-03-25 12:13:06 -05:00
SergeantPanda
2b42838bfe Added current bitrate for client and renamed transfer_rate_KBps to avg_rate_KBps for clarity. 2025-03-24 17:08:17 -05:00
SergeantPanda
b9d0b07d69 Added current bitrate for client and renamed transfer_rate_KBps to avg_rate_KBps for clarity. 2025-03-24 17:06:01 -05:00
SergeantPanda
77002beaac Improved logging to include current component name. 2025-03-22 07:42:46 -05:00
SergeantPanda
d04ba07d10 Added client speed statistics to client metadata in redis. 2025-03-21 19:59:54 -05:00
SergeantPanda
4738d301d1 Fixed regression with buffer checks when clients should be disconnecting due to failure. 2025-03-20 21:03:01 -05:00
SergeantPanda
f03b885d71 Lots more refactoring. 2025-03-19 19:03:33 -05:00
SergeantPanda
8f1c233d64 Moved stream generator out of stream_ts and into stream_generator.py 2025-03-19 16:36:01 -05:00