Skip to content

fix(dvr,reverb): JSON error responses, Reverb secret persistence, and one-shot rule cleanup - #1355

Open
Grimothy wants to merge 2 commits into
m3ue:devfrom
Grimothy:fix/dvr-and-reverb-reliability
Open

fix(dvr,reverb): JSON error responses, Reverb secret persistence, and one-shot rule cleanup#1355
Grimothy wants to merge 2 commits into
m3ue:devfrom
Grimothy:fix/dvr-and-reverb-reliability

Conversation

@Grimothy

@Grimothy Grimothy commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • schedule_dvr could return an HTML error page instead of JSON when the immediate-match path
    (recording a program that's already airing) threw — DvrSchedulerService::scheduleRuleImmediately()
    now catches/logs matchRule() failures, matching the existing try/catch already used by the periodic
    scheduler tick (runMatchAndSchedule).
  • delete_dvr_recording could do the same for a completed recording with an associated VOD
    channel/episode — DvrRecording's deleting hook now guards broadcastDeleted() and the VOD cascade
    block with try/catch, matching the two blocks already guarded right next to them.
  • Added a shouldRenderJsonWhen rule in bootstrap/app.php scoped to the Xtream API routes
    (player_api.php, get.php) so any other uncaught exception on these endpoints renders as JSON
    instead of Laravel's default HTML error page — these routes are consumed by TV/IPTV clients that always
    parse the response as JSON.
  • start-container: REDIS_PASSWORD, M3U_PROXY_TOKEN, REVERB_APP_ID, and REVERB_APP_SECRET are
    now loaded from the persisted config volume before the generate-if-unset checks run. Previously,
    since docker-compose doesn't set these and the persisted .env wasn't read until later in the script,
    a restarted (not recreated) container would silently regenerate a fresh Reverb secret on every boot.
    Any already-connected or cached client would then fail private-channel subscription auth ("Connection
    is unauthorized") with no visible error — dvr.status (and other) pushes would silently stop arriving
    until the app was fully restarted to re-fetch state from scratch.
  • New: Once/Manual DVR rules are now cleaned up as soon as their recording reaches a terminal
    status (Completed/Failed/Cancelled), not just when the recording row is explicitly deleted. Previously,
    choosing "Keep recording" to stop an in-progress recording — a common, legitimate choice — left the
    spent one-shot rule enabled and orphaned in the editor's Rules list forever, since it would never match
    anything again. Series rules are untouched by this change (they still only clean up on deletion, since
    a completed episode doesn't mean the season is over).

Test plan

  • DvrRecordingCascadeTest, DvrSchedulerServiceTest, XtreamScheduleDvrTest,
    XtreamCancelDeleteDvrTest pass
  • Reproduced schedule_dvr on an already-airing program and delete_dvr_recording on a completed
    recording with a VOD channel via a local test container — both now return clean JSON instead of an HTML
    error page
  • Verified the Reverb fix by doing a full container recreate (not just restart) and replaying the
    WebSocket private-channel handshake end-to-end — subscription succeeds on the very first boot
  • Verified the rule-cleanup fix live: scheduled a recording, cancelled with keep-footage, confirmed
    the associated rule was deleted once the recording settled to Completed (previously stayed enabled
    indefinitely)

…; persist Reverb secrets across restarts

- DvrSchedulerService::scheduleRuleImmediately now catches/logs matchRule()
  failures instead of letting them propagate out of schedule_dvr, matching
  the existing try/catch pattern already used by the periodic scheduler tick.
- DvrRecording's deleting hook now guards broadcastDeleted() and the VOD
  channel/episode cascade with try/catch, matching the two blocks around
  them that were already guarded. An uncaught exception in either path
  previously surfaced as delete_dvr_recording's HTML error page instead of
  a clean JSON response.
- bootstrap/app.php: force JSON rendering for any uncaught exception on the
  Xtream API routes (player_api.php, get.php), since these endpoints are
  consumed by TV/IPTV clients that always parse the response as JSON
  regardless of the request's Accept header.
- start-container: load previously-generated secrets (REDIS_PASSWORD,
  M3U_PROXY_TOKEN, REVERB_APP_ID, REVERB_APP_SECRET) from the persistent
  config volume before the generate-if-unset checks run. Previously these
  regenerated fresh on every boot since docker-compose doesn't set them and
  the checks ran before the persisted .env file was read. A restarted (not
  recreated) container would silently rotate its Reverb secret, causing
  already-connected or cached clients to fail private-channel subscription
  auth ("Connection is unauthorized") with no visible error — recording
  status pushes (dvr.status) would silently stop arriving until the app
  was fully restarted to re-fetch state from scratch.
@Grimothy
Grimothy requested a review from sparkison July 31, 2026 20:51
…st deletion

Once/Manual rules are one-shot — they'll never match another programme —
so there's no reason to wait for the recording row itself to be deleted
before cleaning up the spent rule. Previously this cascade only ran in the
deleting() hook; a recording that was merely cancelled (keep footage) or
completed normally left its rule enabled and orphaned indefinitely.

Added the same cleanup to the updated() hook, gated on the new status
being Completed/Failed/Cancelled, scoped to Once/Manual only (Series rules
must stay alive for future episodes not yet matched into rows, so the
siblings-based heuristic used on deletion doesn't apply here). Extracted
the duplicated rule-delete try/catch into a shared deleteRuleQuietly()
helper used by both hooks.

Verified: DvrRecordingCascadeTest, DvrSchedulerServiceTest,
XtreamScheduleDvrTest, XtreamCancelDeleteDvrTest pass (166 assertions, 0
failures). Also reproduced live against a running instance: scheduled a
recording, cancelled with keep-footage, confirmed the rule was deleted
once the recording settled to Completed.
@Grimothy Grimothy changed the title fix(dvr,reverb): stop uncaught exceptions from masking JSON responses; persist Reverb secrets across restarts fix(dvr,reverb): JSON error responses, Reverb secret persistence, and one-shot rule cleanup Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant