From fcc760363f1806681afdcea19ef7d6671c84d07b Mon Sep 17 00:00:00 2001 From: cclohmar Date: Sat, 12 Sep 2026 20:27:48 +0100 Subject: [PATCH] fix: add ffmpeg socket I/O timeout to prevent hung recordings A stalled RTSP stream (TCP connection alive but no data) made the recorder's ffmpeg processes block forever on read(). The -t 300 flag only bounds by input timestamps, not wall-clock, so it never fired and the recorder loop never started the next segment. Add -rw_timeout (10s socket I/O timeout) so ffmpeg exits on a stalled stream and the existing retry loop reconnects automatically. --- recorder.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/recorder.go b/recorder.go index c8ad720..d5ffd35 100644 --- a/recorder.go +++ b/recorder.go @@ -79,8 +79,9 @@ func (rm *RecorderManager) StopAll() { // d. On failure: .part file stays (picked up by cleaner), wait 5s, retry // // Each segment is exactly 5 minutes. Gaps between segments are ~1-2s -// (FFmpeg restart overhead). The -t 300 flag provides a hard timeout -// preventing hung FFmpeg processes. +// (FFmpeg restart overhead). The -t 300 flag bounds the segment by input +// duration, while -rw_timeout makes ffmpeg exit (rather than hang) when the +// RTSP stream stalls, so the retry loop below can reconnect. func (rm *RecorderManager) startRecorder(cam CameraConfig) { rtspURL := cam.RTSPMain if rtspURL == "" { @@ -125,14 +126,16 @@ func (rm *RecorderManager) startRecorder(cam CameraConfig) { partFile := filepath.Join(outDir, "rec_"+timeStr+".part.mp4") finalFile := filepath.Join(outDir, "rec_"+timeStr+".mp4") - // Build FFmpeg command with hard 5-minute timeout. + // Build FFmpeg command: 5-minute segment, with a socket I/O timeout so a + // stalled stream exits ffmpeg instead of hanging the recorder loop. cmd := exec.Command("ffmpeg", "-hide_banner", "-loglevel", "error", "-rtsp_transport", "tcp", + "-rw_timeout", "10000000", // 10s socket I/O timeout — exit on stalled stream "-i", rtspURL, "-an", // drop audio (pcm_mulaw not supported in MP4) "-c:v", "copy", // stream-copy video: zero transcoding, near-zero CPU - "-t", "300", // hard stop after 300 seconds (5 minutes) + "-t", "300", // stop after 300 seconds (5 minutes) of input "-y", // overwrite output without asking partFile, )