112 lines
3.3 KiB
Dart
112 lines
3.3 KiB
Dart
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<String> _partialController = StreamController<String>.broadcast();
|
|
final StreamController<String> _resultController = StreamController<String>.broadcast();
|
|
final StreamController<String> _errorController = StreamController<String>.broadcast();
|
|
|
|
StreamSubscription<String>? _partialSub;
|
|
StreamSubscription<String>? _resultSub;
|
|
|
|
Stream<String> get partialStream => _partialController.stream;
|
|
Stream<String> get resultStream => _resultController.stream;
|
|
Stream<String> get errorStream => _errorController.stream;
|
|
bool get isListening => _listening;
|
|
bool get isReady => _speechService != null;
|
|
|
|
Future<bool> ensureReady() async {
|
|
if (_speechService != null) return true;
|
|
if (_initializing) {
|
|
while (_initializing) {
|
|
await Future<void>.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<bool> 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<void> stop() async {
|
|
if (!_listening || _speechService == null) return;
|
|
await _speechService!.stop();
|
|
_listening = false;
|
|
}
|
|
|
|
Future<void> cancel() async {
|
|
if (_speechService == null) return;
|
|
await _speechService!.cancel();
|
|
_listening = false;
|
|
}
|
|
|
|
Future<void> 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;
|
|
}
|
|
}
|