mymuseum-visitapp/lib/Services/Glasses/engines/impl/openwakeword_engine.dart
2026-07-17 15:23:12 +02:00

81 lines
2.8 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:mymuseum_visitapp/Services/Glasses/engines/wake_word_engine.dart';
/// Wake word via OpenWakeWord — service Android natif (foreground service).
/// Supporte plusieurs modèles en parallèle : un seul AudioRecord + pipeline
/// mel→embedding partagée, N classifieurs TFLite tournant simultanément.
///
/// Événements reçus : "detected:hey_marco", "detected:hey_alba", etc.
/// → onDetectedWithCommand("hey_marco") si fourni, sinon onDetected()
///
/// iOS non supporté — fallback sur SpeechToTextWakeWordEngine.
class OpenWakeWordEngine implements WakeWordEngine {
static const _methodChannel = MethodChannel('be.unov.mymuseum/wake_word');
static const _eventChannel = EventChannel('be.unov.mymuseum/wake_word_events');
/// Un ou plusieurs modèles .tflite dans assets/files (sans extension).
/// Ex: ['hey_marco'] ou ['hey_alba', 'hey_marco', 'hey_vasco']
final List<String> modelNames;
StreamSubscription<dynamic>? _subscription;
void Function()? _onDetected;
void Function(String modelName)? _onModelDetected;
bool _running = false;
OpenWakeWordEngine({
List<String>? modelNames,
String? modelName,
}) : modelNames = modelNames ?? (modelName != null ? [modelName] : ['hey_visit']);
@override
Future<void> start({
required void Function() onDetected,
void Function(String command)? onDetectedWithCommand,
}) async {
if (_running) return;
if (!_isAndroid) {
debugPrint('[OpenWakeWordEngine] Android only — no-op on this platform');
return;
}
_onDetected = onDetected;
_onModelDetected = onDetectedWithCommand;
_running = true;
_subscription = _eventChannel.receiveBroadcastStream().listen(
(event) {
if (!_running || event is! String) return;
if (event.startsWith('detected:')) {
final name = event.substring('detected:'.length);
debugPrint('[OpenWakeWordEngine] Wake word detected: $name');
if (_onModelDetected != null) {
_onModelDetected!(name);
} else {
_onDetected?.call();
}
}
},
onError: (e) => debugPrint('[OpenWakeWordEngine] Event error: $e'),
);
await _methodChannel.invokeMethod('start', {
'modelNames': modelNames.join(','),
});
debugPrint('[OpenWakeWordEngine] Service started (models: $modelNames)');
}
@override
Future<void> stop() async {
if (!_running) return;
_running = false;
await _subscription?.cancel();
_subscription = null;
if (_isAndroid) await _methodChannel.invokeMethod('stop');
debugPrint('[OpenWakeWordEngine] Service stopped');
}
bool get _isAndroid => defaultTargetPlatform == TargetPlatform.android;
}