scripto/mobile/lib/main.dart.old
2024-11-29 22:41:27 +01:00

600 lines
19 KiB
Dart

import 'dart:typed_data';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'dart:async';
import 'package:audio_session/audio_session.dart';
import 'package:uuid/uuid.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scriptor',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: AudioCapturePage(),
);
}
}
class AudioCapturePage extends StatefulWidget {
@override
_AudioCapturePageState createState() => _AudioCapturePageState();
}
class _AudioCapturePageState extends State<AudioCapturePage> {
FlutterSoundRecorder _audioRecorderTotal = FlutterSoundRecorder();
FlutterSoundRecorder _audioRecorderSegment = FlutterSoundRecorder();
bool _isRecording = false;
late String _filePathTotal;
late String _filePathSegment;
late Timer _segmentTimer;
late Timer _totalTimer; // Timer pour la durée totale de l'enregistrement
final String apiUrl = 'http://192.168.1.134:3003'; // Remplacer par votre URL
int _recordingDuration = 0; // Durée totale de l'enregistrement
int _segmentDuration = 0; // Durée de l'enregistrement du segment
List<String> responseApi = [];
String sessionid = Uuid().v4();
String sessionName = '';
DateTime sessionDate = DateTime.now();
bool bStartSession = false;
bool bStopSession = false;
bool directTraitement = false;
final tecSessionName = TextEditingController();
final fkSessionCreate = GlobalKey<FormState>();
String sessionEmail = '';
final fkSessionStop = GlobalKey<FormState>();
final tecSessionEmail = TextEditingController();
bool sessionSummarizeWithLLM = false;
@override
void initState() {
super.initState();
_initializeAudioSession(); // Initialisation de l'AudioSession
_requestPermissions();
}
/// Initialiser et configurer `AudioSession`
Future<void> _initializeAudioSession() async {
final session = await AudioSession.instance;
await session.configure(AudioSessionConfiguration(
avAudioSessionCategory: AVAudioSessionCategory.playAndRecord,
avAudioSessionCategoryOptions:
AVAudioSessionCategoryOptions.allowBluetooth |
AVAudioSessionCategoryOptions.defaultToSpeaker,
avAudioSessionMode: AVAudioSessionMode.spokenAudio,
androidAudioAttributes: const AndroidAudioAttributes(
contentType: AndroidAudioContentType.speech,
flags: AndroidAudioFlags.none,
usage: AndroidAudioUsage.voiceCommunication,
),
androidAudioFocusGainType: AndroidAudioFocusGainType.gain,
androidWillPauseWhenDucked: true,
));
print("AudioSession configurée avec succès !");
}
// Demande des permissions nécessaires
Future<void> _requestPermissions() async {
await Permission.microphone.request();
await Permission.storage.request();
var microphoneStatus = await Permission.microphone.status;
var storageStatus = await Permission.storage.status;
if (!microphoneStatus.isGranted || !storageStatus.isGranted) {
print("Permissions non accordées.");
return;
} else {
print("Permissions accordées. C'est tout bon !");
}
}
// Start the session on the server
Future<void> _startSession() async {
try {
var uri = Uri.parse(apiUrl + '/start');
var response = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'sessionId': sessionid}),
);
if (response.statusCode == 200) {
print('Session démarrée avec succès');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Session démarrée avec succès"),
duration: Duration(seconds: 1),
));
} else {
print(
'Erreur lors du démarrage de la session : ${response.statusCode}');
}
} catch (e) {
print('Erreur lors du démarrage de la session : $e');
}
}
Future<void> _stopSession(String sessionEmail) async {
try {
var uri = Uri.parse(apiUrl + '/stop');
var response = await http.post(
uri,
headers: {'Content-Type': 'application/json'},
body:
jsonEncode({'sessionId': sessionid, 'sessionEmail': sessionEmail}),
);
if (response.statusCode == 200) {
print('Session arrêtée avec succès');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Session arrêtée avec succès"),
duration: Duration(seconds: 1),
));
} else {
print('Erreur lors de l\'arrêt de la session : ${response.statusCode}');
}
} catch (e) {
print('Erreur lors de l\'arrêt de la session : $e');
}
}
// Démarre l'enregistrement des deux flux
Future<void> _startRecording() async {
//Clear the responseApi list
setState(() {
responseApi.clear();
});
sessionid = Uuid().v4();
sessionDate = DateTime.now();
sessionName = '';
tecSessionName.text = '';
//Dialog for name and date
bStartSession = false;
await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Nommer la session'),
content: Form(
key: fkSessionCreate,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: tecSessionName,
decoration: InputDecoration(
hintText: 'Nom de la session',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer un nom de session';
}
return null;
},
),
SizedBox(height: 20),
Text(
sessionDate.toString().substring(0, 19),
textAlign: TextAlign.center,
)
],
)),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Annuler'),
),
TextButton(
onPressed: () {
if (fkSessionCreate.currentState!.validate()) {
sessionName = tecSessionName.text;
bStartSession = true;
Navigator.pop(context);
}
},
child: Text('Valider'),
),
],
);
},
);
if (!bStartSession) {
return;
}
await _startSession();
Directory tempDir = await getTemporaryDirectory();
print(tempDir.path);
_filePathTotal = '${tempDir.path}/audioTotal.wav'; // Enregistrement complet
_filePathSegment = '${tempDir.path}/segment.wav'; // Segment de 10 secondes
// Ouvre et commence l'enregistrement pour le segment
await _audioRecorderSegment.openRecorder();
await _audioRecorderSegment.startRecorder(toFile: _filePathSegment);
// Ouvre et commence l'enregistrement pour le fichier total
await _audioRecorderTotal.openRecorder();
await _audioRecorderTotal.startRecorder(
toFile: _filePathTotal, codec: Codec.pcm16WAV);
setState(() {
_isRecording = true;
_recordingDuration = 0;
_segmentDuration = 0;
});
// Démarre un timer pour gérer l'enregistrement des segments
_segmentTimer = Timer.periodic(Duration(seconds: 1), (timer) async {
setState(() {
_segmentDuration++;
});
if (_segmentDuration == 30 && directTraitement) {
await _stopSegmentRecording(); // Arrêter l'enregistrement du segment
await _startSegmentRecording(); // Démarrer un nouvel enregistrement de segment
} else if (_segmentDuration == 30 && !directTraitement) {
_segmentDuration = 0;
}
});
// Démarre un timer pour la durée totale de l'enregistrement
_totalTimer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
_recordingDuration++;
});
});
print('Enregistrement total et segment commencé');
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Enregistrement démarré"),
duration: Duration(seconds: 1),
));
}
// Arrêter l'enregistrement des deux flux
Future<void> _stopRecording() async {
await _audioRecorderTotal.stopRecorder();
await _audioRecorderSegment.stopRecorder();
_segmentTimer.cancel();
_totalTimer.cancel();
String sessionEmail = '';
bStopSession = false;
if (kDebugMode) {
tecSessionEmail.text = 'guillaume.david@icloud.com';
}
await showDialog(
context: context,
builder: (context) {
bool summarizeWithLLM = false;
return AlertDialog(
title: Text('Terminer la session'),
content: Form(
key: fkSessionStop,
child: StatefulBuilder(
builder: (context, setState) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: tecSessionEmail,
decoration: InputDecoration(
hintText: 'Email',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Veuillez entrer un email';
}
return null;
},
),
SizedBox(height: 20),
Text('Voulez-vous vraiment arrêter la session ?'),
// Checkbox avec mise à jour de l'état
Row(
children: [
Checkbox(
value: summarizeWithLLM,
onChanged: (value) {
setState(() {
summarizeWithLLM = value!;
sessionSummarizeWithLLM = summarizeWithLLM;
});
},
),
Text('Créer un résumé'),
],
),
],
);
},
),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Annuler'),
),
TextButton(
onPressed: () {
if (fkSessionStop.currentState!.validate()) {
print('Résumé activé : $summarizeWithLLM');
bStopSession = true;
Navigator.pop(context);
}
},
child: Text('Valider'),
),
],
);
},
);
if (!bStopSession) {
return;
}
await _stopSession(sessionEmail);
setState(() {
_isRecording = false;
});
print("Enregistrement arrêté.");
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Enregistrement arrêté"),
duration: Duration(seconds: 1),
));
if (directTraitement) {
await _sendSegmentToAPI(_filePathSegment);
}
//Envoyer le fichier final
_sendFinalToAPI(_filePathTotal);
}
Future<void> _startSegmentRecording() async {
Directory tempDir = await getTemporaryDirectory();
_filePathSegment = '${tempDir.path}/segment.wav';
await _audioRecorderSegment.startRecorder(toFile: _filePathSegment);
setState(() {
_segmentDuration = 0;
});
print('Segment commencé');
}
Future<void> _stopSegmentRecording() async {
await _audioRecorderSegment.stopRecorder();
await Future.delayed(Duration(seconds: 1));
print('Segment arrêté, fichier envoyé à l\'API');
await _sendSegmentToAPI(
_filePathSegment); // Envoi du segment à l'API sans attendre
}
Future<void> _sendSegmentToAPI(String filePath) async {
try {
File file = File(filePath);
if (!await file.exists()) {
print('Le fichier n\'existe pas à l\'emplacement : $filePath');
return;
}
var uri = Uri.parse('$apiUrl/diarize');
var request = http.MultipartRequest('POST', uri);
// Ajouter le fichier
//Copie le filePath dans le dossier temporaire avec waitToSend.wav
String filePathWaitToSend =
'${(await getTemporaryDirectory()).path}/waitToSend.wav';
await file.copy(filePathWaitToSend);
File filePathWaitToSendFile = File(filePathWaitToSend);
request.files.add(await http.MultipartFile.fromPath(
'file', filePathWaitToSendFile.path));
// Ajouter le champ sessionId
request.fields['sessionId'] = sessionid;
// Lancer l'envoi de la requête sans attendre la réponse
_sendRequest(
request); // Appel de la fonction d'envoi sans attendre la réponse
//wait 1 second before continuing
// Code continue sans attendre la réponse de l'API
print("Le fichier est en cours d'envoi, le code continue...");
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Segment envoyé à l'API en arrière-plan"),
duration: Duration(seconds: 1),
));
} catch (e) {
print('Erreur lors de l\'envoi du fichier : $e');
}
}
Future<void> _sendFinalToAPI(String filePath) async {
try {
File file = File(filePath);
if (!await file.exists()) {
print('Le fichier n\'existe pas à l\'emplacement : $filePath');
return;
}
//Show taille
print('Taille du fichier : ${await file.length()} octets');
var uri = Uri.parse('$apiUrl/workfinale');
var request = http.MultipartRequest('POST', uri);
request.files.add(await http.MultipartFile.fromPath('file', file.path));
// Ajouter le champ sessionId
request.fields['sessionId'] = sessionid;
request.fields['sessionName'] = sessionName;
request.fields['sessionDate'] = sessionDate.toString();
request.fields['sessionEmail'] = tecSessionEmail.text;
request.fields['sessionSummarizeWithLLM'] =
sessionSummarizeWithLLM.toString();
// Lancer l'envoi de la requête sans attendre la réponse
await _sendRequest(
request); // Appel de la fonction d'envoi sans attendre la réponse
print("Le fichier est en cours d'envoi, le code continue...");
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Le fichier a été envoyé à l'API"),
duration: Duration(seconds: 1),
));
} catch (e) {
print('Erreur lors de l\'envoi du fichier : $e');
}
}
// Fonction pour envoyer la requête sans attendre la réponse
Future<void> _sendRequest(http.MultipartRequest request) async {
try {
var response = await request.send();
if (response.statusCode == 200) {
var responseData = await response.stream.bytesToString();
var jsonData = jsonDecode(responseData);
// Manipuler la réponse dans setState sans bloquer l'UI
setState(() {
var speakerTranscriptions = jsonData['speaker_transcriptions'] ?? [];
for (var transcription in speakerTranscriptions) {
responseApi.add("${transcription[0]}: ${transcription[1]}");
}
});
print("Réponse de l'API reçue et traitée.");
} else {
print('Erreur lors de l\'envoi à l\'API : ${response.statusCode}');
}
} catch (e) {
print('Erreur lors de l\'envoi du fichier : $e');
}
}
@override
void dispose() {
_audioRecorderTotal.closeRecorder();
_audioRecorderSegment.closeRecorder();
_segmentTimer.cancel();
_totalTimer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Scriptor',
style: TextStyle(color: Colors.black, fontSize: 32),
),
),
body: SingleChildScrollView(
child: Center(
child: Column(
children: [
sessionName != ''
? Text(
sessionName,
style: TextStyle(fontSize: 20),
)
: Text(''),
IconButton(
icon: Icon(
_isRecording ? Icons.stop : Icons.mic,
size: 100,
color: Colors.red,
),
onPressed: _isRecording ? _stopRecording : _startRecording,
),
SizedBox(height: 20),
Text(
'${(_recordingDuration ~/ 3600).toString().padLeft(2, '0')} H ${((_recordingDuration % 3600) ~/ 60).toString().padLeft(2, '0')} M ${(_recordingDuration % 60).toString().padLeft(2, '0')} S',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 10),
directTraitement
? Text('$_segmentDuration/30', style: TextStyle(fontSize: 20))
: Text(''),
//Check box
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(
value: directTraitement,
onChanged: (value) {
setState(() {
directTraitement = value!;
});
},
),
Text('Traitement en direct'),
],
),
Container(
padding: EdgeInsets.all(10),
child: Text(
"Pour annoncer un locuteur, dites 'Hey Scripto, je suis %nom% et j'ai le rôle de %rôle%.'"),
),
directTraitement
? ListView.builder(
shrinkWrap: true,
itemCount: responseApi.length,
itemBuilder: (context, index) {
// Inverser la liste et accéder à l'élément correspondant
return ListTile(
title: Text(responseApi.reversed
.toList()[index]), // Inverser la liste
);
},
)
: Container()
],
),
),
),
);
}
}