fix: soundcard capture detects a dead arecord instead of silently recording nothing

ALSAStream._read_loop just returned when arecord died or hit EOF, and
__exit__ never checked the exit status. SoundcardRecorder.record's inner
loop only slept and flushed, so nothing ever noticed: the recorder stayed
"running", kept rolling over to a new split file on schedule, and wrote
zero bytes into every one of them. The max_retries/reconnect path below
it was unreachable. Worst kind of failure for an unattended recorder —
files keep appearing, at the right times, with the right names.

ALSAStream now tracks when audio last arrived and exposes check_alive(),
which raises if the process exited or nothing has been captured for
SILENCE_TIMEOUT (15 s). The record loop calls it every 0.5 s tick, so a
dead device unwinds into the existing retry handler.

retry_count resets only when the stream actually delivered audio —
resetting on every healthy tick would let a device that opens but never
captures retry forever, which is the same silent failure by another
route.

Also: join the stderr thread in __exit__, and break _read_loop on EOF
rather than looping until poll() catches up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 20:21:05 +02:00
co-authored by Claude Opus 5
parent 24569efe20
commit 46af41b12d
4 changed files with 145 additions and 6 deletions
+2
View File
@@ -208,6 +208,8 @@ Analysis, clips, cut, and delete return `409` for files that are still being rec
Both recorder types run in separate threads and retry independently up to `max_retries`.
Soundcard capture is health-checked twice a second: if `arecord` exits, or delivers no audio for 15 seconds while still running (device unplugged, a `dsnoop` device that went away), the source is torn down and reopened as a normal retry. Only a stream that actually delivered audio resets the retry counter, so a device that opens but never captures stops after `max_retries` instead of producing an endless series of empty split files.
---
## Docker notes