mymuseum-visitapp/lib/Services/Glasses/engines/impl/flutter_tts_engine.dart

82 lines
2.0 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:mymuseum_visitapp/Services/Glasses/engines/tts_engine.dart';
/// TTS on-device via Google (Android) / AVSpeechSynthesizer (iOS).
/// Gratuit, pas de latence réseau, supporte FR/NL/EN/DE.
class FlutterTtsEngine implements TtsEngine {
final FlutterTts _tts = FlutterTts();
bool _speaking = false;
String? _lastText;
String? _lastLang;
Completer<void>? _completer;
FlutterTtsEngine() {
_tts.setStartHandler(() {
_speaking = true;
});
_tts.setCompletionHandler(() {
_speaking = false;
_completer?.complete();
_completer = null;
});
_tts.setCancelHandler(() {
_speaking = false;
_completer?.complete();
_completer = null;
});
_tts.setErrorHandler((msg) {
_speaking = false;
_completer?.completeError(msg);
_completer = null;
});
}
@override
bool get isSpeaking => _speaking;
@override
Future<void> speak(String text, {String languageCode = 'fr-FR'}) async {
if (text.isEmpty) return;
_lastText = text;
_lastLang = languageCode;
try {
await _tts.stop();
await _tts.setLanguage(languageCode);
await _tts.setSpeechRate(0.45);
await _tts.setVolume(1.0);
await _tts.setPitch(1.0);
_completer = Completer<void>();
await _tts.speak(text);
// Attend la vraie fin de la synthèse
await _completer!.future.timeout(
const Duration(seconds: 30),
onTimeout: () {},
);
} catch (e) {
debugPrint('[FlutterTtsEngine] speak error: $e');
} finally {
_speaking = false;
}
}
@override
Future<void> stop() async {
await _tts.stop();
_speaking = false;
_completer?.complete();
_completer = null;
}
@override
Future<void> replay() async {
if (_lastText != null) {
await speak(_lastText!, languageCode: _lastLang ?? 'fr-FR');
}
}
}