104 lines
3.1 KiB
Dart
104 lines
3.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_sound/flutter_sound.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/stt_engine.dart';
|
|
|
|
/// STT via faster-whisper hébergé sur Home Assistant.
|
|
///
|
|
/// POST http://{haUrl}/api/stt/{providerId}
|
|
/// Header : Authorization: Bearer {token}
|
|
/// Body : bytes audio WAV 16kHz mono
|
|
class HomeAssistantSttEngine implements SttEngine {
|
|
final String haUrl;
|
|
final String haToken;
|
|
final String providerId;
|
|
|
|
final FlutterSoundRecorder _recorder = FlutterSoundRecorder();
|
|
bool _initialized = false;
|
|
|
|
HomeAssistantSttEngine({
|
|
required this.haUrl,
|
|
required this.haToken,
|
|
this.providerId = 'faster_whisper',
|
|
});
|
|
|
|
Future<void> _ensureInitialized() async {
|
|
if (_initialized) return;
|
|
await Permission.microphone.request();
|
|
await _recorder.openRecorder();
|
|
_initialized = true;
|
|
}
|
|
|
|
@override
|
|
Future<String> transcribeOnce({
|
|
required String languageCode,
|
|
Duration timeout = const Duration(seconds: 8),
|
|
}) async {
|
|
await _ensureInitialized();
|
|
|
|
final dir = await getTemporaryDirectory();
|
|
final path = '${dir.path}/ha_stt_${DateTime.now().millisecondsSinceEpoch}.wav';
|
|
|
|
try {
|
|
await _recorder.startRecorder(
|
|
toFile: path,
|
|
codec: Codec.pcm16WAV,
|
|
sampleRate: 16000,
|
|
numChannels: 1,
|
|
);
|
|
|
|
await _waitForSilenceOrTimeout(timeout);
|
|
await _recorder.stopRecorder();
|
|
|
|
return await _transcribe(path, languageCode);
|
|
} catch (e) {
|
|
debugPrint('[HomeAssistantSttEngine] error: $e');
|
|
try { await _recorder.stopRecorder(); } catch (_) {}
|
|
return '';
|
|
} finally {
|
|
try { File(path).deleteSync(); } catch (_) {}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> cancel() async {
|
|
try { await _recorder.stopRecorder(); } catch (_) {}
|
|
}
|
|
|
|
Future<void> _waitForSilenceOrTimeout(Duration timeout) async {
|
|
// Attend la durée complète — l'utilisateur a le temps de parler
|
|
// flutter_sound ne fournit pas d'accès simple au niveau audio en temps réel
|
|
await Future.delayed(timeout);
|
|
}
|
|
|
|
Future<String> _transcribe(String wavPath, String languageCode) async {
|
|
final bytes = await File(wavPath).readAsBytes();
|
|
final lang = languageCode.split('-').first;
|
|
|
|
final response = await http.post(
|
|
Uri.parse('$haUrl/api/stt/$providerId'),
|
|
headers: {
|
|
'Authorization': 'Bearer $haToken',
|
|
'Content-Type': 'audio/wav',
|
|
'X-Speech-Content': 'language=$lang',
|
|
},
|
|
body: bytes,
|
|
).timeout(const Duration(seconds: 10));
|
|
|
|
if (response.statusCode != 200) {
|
|
debugPrint('[HomeAssistantSttEngine] HTTP ${response.statusCode}: ${response.body}');
|
|
return '';
|
|
}
|
|
|
|
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
|
final text = (json['text'] as String? ?? '').trim();
|
|
debugPrint('[HomeAssistantSttEngine] "$text"');
|
|
return text;
|
|
}
|
|
}
|