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, )