import 'dart:async'; import 'dart:convert'; import 'package:vosk_flutter_service/vosk_flutter.dart'; class VoskVoiceService { VoskVoiceService._(); static final VoskVoiceService instance = VoskVoiceService._(); static const String _modelAsset = 'assets/models/vosk-model-small-cn-0.22.zip'; static const int _sampleRate = 16000; final VoskFlutterPlugin _vosk = VoskFlutterPlugin.instance(); Model? _model; Recognizer? _recognizer; SpeechService? _speechService; bool _initializing = false; bool _listening = false; final StreamController _partialController = StreamController.broadcast(); final StreamController _resultController = StreamController.broadcast(); final StreamController _errorController = StreamController.broadcast(); StreamSubscription? _partialSub; StreamSubscription? _resultSub; Stream get partialStream => _partialController.stream; Stream get resultStream => _resultController.stream; Stream get errorStream => _errorController.stream; bool get isListening => _listening; bool get isReady => _speechService != null; Future ensureReady() async { if (_speechService != null) return true; if (_initializing) { while (_initializing) { await Future.delayed(const Duration(milliseconds: 80)); } return _speechService != null; } _initializing = true; try { final modelPath = await ModelLoader().loadFromAssets(_modelAsset); _model = await _vosk.createModel(modelPath); _recognizer = await _vosk.createRecognizer( model: _model!, sampleRate: _sampleRate, ); _speechService = await _vosk.initSpeechService(_recognizer!); _partialSub = _speechService!.onPartial().listen((raw) { final text = _extractField(raw, 'partial'); if (text != null) _partialController.add(text); }); _resultSub = _speechService!.onResult().listen((raw) { final text = _extractField(raw, 'text'); if (text != null) _resultController.add(text); }); return true; } catch (e) { _errorController.add(e.toString()); return false; } finally { _initializing = false; } } Future start() async { if (!await ensureReady()) return false; if (_listening) return true; final ok = await _speechService!.start( onRecognitionError: (err) => _errorController.add(err.toString()), ); _listening = ok ?? false; return _listening; } Future stop() async { if (!_listening || _speechService == null) return; await _speechService!.stop(); _listening = false; } Future cancel() async { if (_speechService == null) return; await _speechService!.cancel(); _listening = false; } Future dispose() async { await _partialSub?.cancel(); await _resultSub?.cancel(); await _speechService?.dispose(); await _recognizer?.dispose(); _speechService = null; _recognizer = null; _model = null; _listening = false; } String? _extractField(String raw, String key) { try { final map = jsonDecode(raw); if (map is Map && map[key] is String) { final v = (map[key] as String).trim(); return v; } } catch (_) {} return null; } }