120 lines
4.1 KiB
Dart
120 lines
4.1 KiB
Dart
import 'dart:io';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:mymuseum_visitapp/Models/visitContext.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/impl/flutter_tts_engine.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/impl/gemini_tts_engine.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/impl/myinfomate_llm_client.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/impl/native_wake_word_engine.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/impl/speech_to_text_stt.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/impl/speech_to_text_wake_word.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/engines/impl/whisper_stt_engine.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/glasses_background_service.dart';
|
|
import 'package:mymuseum_visitapp/Services/Glasses/voice_orchestrator.dart';
|
|
import 'package:mymuseum_visitapp/Services/meta_glasses_service.dart';
|
|
import 'package:mymuseum_visitapp/constants.dart';
|
|
|
|
enum VoiceMode { none, voiceOnly, glasses }
|
|
|
|
class VoiceController extends ChangeNotifier {
|
|
VoiceMode _mode = VoiceMode.none;
|
|
VoiceMode get mode => _mode;
|
|
|
|
bool _isActivating = false;
|
|
bool get isActivating => _isActivating;
|
|
|
|
VoiceController() {
|
|
MetaGlassesService.instance.state.addListener(_onGlassesStateChanged);
|
|
}
|
|
|
|
bool isFeatureAvailable(VisitAppContext ctx) =>
|
|
ctx.applicationInstanceDTO?.isAssistant == true;
|
|
|
|
bool get isGlassesConnected =>
|
|
MetaGlassesService.instance.state.value == GlassesState.connected ||
|
|
MetaGlassesService.instance.state.value == GlassesState.streaming;
|
|
|
|
/// Active le mode demandé. Si un mode est déjà actif, le stoppe d'abord.
|
|
Future<void> activate(VoiceMode targetMode, VisitAppContext ctx) async {
|
|
if (_isActivating) return;
|
|
_isActivating = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
await _stopOrchestrator();
|
|
|
|
if (targetMode == VoiceMode.glasses) {
|
|
await GlassesBackgroundService.initialize();
|
|
await GlassesBackgroundService.start();
|
|
await MetaGlassesService.instance.connect();
|
|
}
|
|
|
|
_buildOrchestrator(ctx);
|
|
await activeVoiceOrchestrator!.start();
|
|
_mode = targetMode;
|
|
} catch (e) {
|
|
debugPrint('[VoiceController] activate error: $e');
|
|
await _stopOrchestrator();
|
|
_mode = VoiceMode.none;
|
|
} finally {
|
|
_isActivating = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> deactivate() async {
|
|
await _stopOrchestrator();
|
|
_mode = VoiceMode.none;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Relance le wake word après un retour en foreground.
|
|
void restartWakeWordIfNeeded() {
|
|
final o = activeVoiceOrchestrator;
|
|
if (o != null && o.isRunning && !o.isInConversation) {
|
|
o.restartWakeWord();
|
|
}
|
|
}
|
|
|
|
Future<void> _stopOrchestrator() async {
|
|
if (activeVoiceOrchestrator != null) {
|
|
await activeVoiceOrchestrator!.stop();
|
|
activeVoiceOrchestrator = null;
|
|
}
|
|
}
|
|
|
|
void _buildOrchestrator(VisitAppContext ctx) {
|
|
activeVoiceOrchestrator = VoiceOrchestrator(
|
|
visitAppContext: ctx,
|
|
wakeWordEngine: Platform.isAndroid
|
|
? NativeWakeWordEngine(modelNames: ['hey_marco', 'hey_alba', 'hey_vasco'])
|
|
: SpeechToTextWakeWordEngine(keyword: 'visite'),
|
|
sttEngine: kWhisperApiKey.isNotEmpty
|
|
? WhisperSttEngine(apiKey: kWhisperApiKey, endpoint: kWhisperEndpoint)
|
|
: SpeechToTextSttEngine(),
|
|
ttsEngine: kGeminiApiKey.isNotEmpty
|
|
? GeminiTtsEngine(
|
|
apiKey: kGeminiApiKey,
|
|
voiceName: kGeminiTtsVoice,
|
|
voicePrompt: kGeminiTtsPrompt,
|
|
)
|
|
: FlutterTtsEngine(),
|
|
llmClient: MyInfoMateLlmClient(visitAppContext: ctx),
|
|
);
|
|
}
|
|
|
|
void _onGlassesStateChanged() {
|
|
if (_mode == VoiceMode.glasses &&
|
|
MetaGlassesService.instance.state.value == GlassesState.disconnected) {
|
|
debugPrint('[VoiceController] Glasses disconnected — deactivating');
|
|
deactivate();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
MetaGlassesService.instance.state.removeListener(_onGlassesStateChanged);
|
|
_stopOrchestrator();
|
|
super.dispose();
|
|
}
|
|
}
|