From 1b4321d84ade9d08941ccf09d2ace22cc35988d6 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 2 Apr 2026 07:22:24 +0200 Subject: [PATCH] feat: implement SimpleRecorder service for FFmpeg-based stream recording and scheduling --- bin/services/simple_recorder.dart | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bin/services/simple_recorder.dart b/bin/services/simple_recorder.dart index 852c73f..8ab8e52 100644 --- a/bin/services/simple_recorder.dart +++ b/bin/services/simple_recorder.dart @@ -55,6 +55,7 @@ class SimpleRecorder { try { final ffmpeg = await Process.start('ffmpeg', [ '-y', + '-fflags', '+genpts+igndts', // FORCE generation of missing timestamps for MKV/MP4 compatibility '-i', streamUrl, '-c', @@ -75,10 +76,18 @@ class SimpleRecorder { db.updateRecordingStatus(recordingId, 'recording', filePath: filepath); // Auto-stop when time is up or process ends - ffmpeg.exitCode.then((_) { - db.updateRecordingStatus(recordingId, 'completed'); + ffmpeg.exitCode.then((code) { + if (code == 0 || code == 255) { // 255 is usually returned when gracefully stopped via q/SIGTERM + db.updateRecordingStatus(recordingId, 'completed'); + } else { + db.updateRecordingStatus( + recordingId, + 'failed', + errorReason: 'FFmpeg crashed with exit code $code', + ); + } _active.remove(channelId); - print('✅ Recording done: $title'); + print('✅ Recording finished: $title (Code: $code)'); }).catchError((e) { db.updateRecordingStatus( recordingId, @@ -203,7 +212,7 @@ class SimpleRecorder { final safe = title.replaceAll(RegExp(r'[^a-zA-Z0-9_-]'), '_'); final timestamp = DateTime.now().toIso8601String().replaceAll(':', '').split('.')[0]; - return '${safe}_$timestamp.mkv'; + return '${safe}_$timestamp.mkv'; // The user explicitly mandated MKV or MP4. } void dispose() {