mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
feat: add stop recording button and POST /api/recordings/stop/<id> endpoint to kill FFmpeg process
This commit is contained in:
1 parent
0d2ad322fe
commit
4c2e3ce600
4 files changed
+68
-2
No files matched your search
@@ -101,4 +101,29 @@ class RecordingsApi {
|
|||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// POST /api/recordings/stop/<id> — Arrête un enregistrement FFmpeg en cours
|
||||||
|
Future<Response> handleStop(Request request, String id) async {
|
||||||
|
final recording = _db.getRecordingById(id);
|
||||||
|
if (recording == null) {
|
||||||
|
return Response.notFound(
|
||||||
|
json.encode({'error': 'Enregistrement non trouvé'}),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final stopped = await _scheduler.stopRecording(id);
|
||||||
|
if (stopped) {
|
||||||
|
return Response.ok(
|
||||||
|
json.encode({'message': 'Enregistrement arrêté'}),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Pas de processus FFmpeg actif pour cet ID → marquer comme complété quand même
|
||||||
|
_db.updateRecordingStatus(id, 'completed');
|
||||||
|
return Response.ok(
|
||||||
|
json.encode({'message': 'Enregistrement marqué comme terminé'}),
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +122,7 @@ void main(List<String> args) async {
|
|||||||
..get('/api/recordings', recordingsApi.handleGetAll)
|
..get('/api/recordings', recordingsApi.handleGetAll)
|
||||||
..post('/api/recordings', recordingsApi.handlePost)
|
..post('/api/recordings', recordingsApi.handlePost)
|
||||||
..delete('/api/recordings/<id>', recordingsApi.handleDelete)
|
..delete('/api/recordings/<id>', recordingsApi.handleDelete)
|
||||||
|
..post('/api/recordings/stop/<id>', recordingsApi.handleStop)
|
||||||
..get('/api/recordings/logs/<id>', recordingsApi.getLogHandler);
|
..get('/api/recordings/logs/<id>', recordingsApi.getLogHandler);
|
||||||
// NOTE: /api/xtream is handled by proxyHandler in the Cascade below
|
// NOTE: /api/xtream is handled by proxyHandler in the Cascade below
|
||||||
// Do NOT mount here as it would intercept and block the actual proxy
|
// Do NOT mount here as it would intercept and block the actual proxy
|
||||||
|
|||||||
@@ -186,10 +186,23 @@ class RecordingScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Arrêter un enregistrement en cours (appelé depuis l'API)
|
||||||
|
Future<bool> stopRecording(String id) async {
|
||||||
|
if (_ffmpegProcess != null && _currentRecording?.id == id) {
|
||||||
|
print('[RecordingScheduler] Arrêt demandé pour: ${_currentRecording!.title}');
|
||||||
|
_ffmpegProcess!.kill(ProcessSignal.sigterm);
|
||||||
|
_db.updateRecordingStatus(id, 'completed');
|
||||||
|
_ffmpegProcess = null;
|
||||||
|
_currentRecording = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false; // Pas d'enregistrement actif avec cet ID
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _stopCurrentRecording({String? reason}) async {
|
Future<void> _stopCurrentRecording({String? reason}) async {
|
||||||
if (_ffmpegProcess != null && _currentRecording != null) {
|
if (_ffmpegProcess != null && _currentRecording != null) {
|
||||||
print('[RecordingScheduler] Arrêt forcé du processus FFmpeg. Raison: ${reason ?? "Fin programmée"}');
|
print('[RecordingScheduler] Arrêt auto: ${reason ?? "Fin programmée"}');
|
||||||
_ffmpegProcess!.kill(ProcessSignal.sigterm); // Envoyer un signal d'arrêt propre
|
_ffmpegProcess!.kill(ProcessSignal.sigterm);
|
||||||
_db.updateRecordingStatus(_currentRecording!.id, 'completed');
|
_db.updateRecordingStatus(_currentRecording!.id, 'completed');
|
||||||
_ffmpegProcess = null;
|
_ffmpegProcess = null;
|
||||||
_currentRecording = null;
|
_currentRecording = null;
|
||||||
|
|||||||
@@ -71,6 +71,26 @@ class _RecordingsTabState extends State<RecordingsTab> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _stopRecording(String id, String title) async {
|
||||||
|
try {
|
||||||
|
final response = await http.post(Uri.parse('/api/recordings/stop/$id'));
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
_fetchRecordings();
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('⏹ Enregistrement "$title" arrêté')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('Erreur lors de l\'arrêt de l\'enregistrement: $e'), backgroundColor: Colors.red),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _showLogs(String id, String title) async {
|
Future<void> _showLogs(String id, String title) async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(Uri.parse('/api/recordings/logs/$id'));
|
final response = await http.get(Uri.parse('/api/recordings/logs/$id'));
|
||||||
@@ -268,6 +288,13 @@ class _RecordingsTabState extends State<RecordingsTab> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
// Bouton Stop (uniquement pour les enregistrements en cours)
|
||||||
|
if (rec['status'] == 'recording')
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.stop_circle, color: Colors.redAccent),
|
||||||
|
tooltip: 'Arrêter l\'enregistrement',
|
||||||
|
onPressed: () => _stopRecording(rec['id'], rec['title'] ?? 'Inconnu'),
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.description_outlined, color: Colors.blueAccent),
|
icon: const Icon(Icons.description_outlined, color: Colors.blueAccent),
|
||||||
tooltip: 'Voir les logs',
|
tooltip: 'Voir les logs',
|
||||||
|
|||||||
Reference in new issue
Block a user