createSttModel method
Future<SpeechRecognizer>
createSttModel({
- String? modelPath,
- String? tokenizerPath,
- PreferredBackend? preferredBackend,
override
Creates and returns a new SpeechRecognizer instance.
Modern API: If paths are not provided, uses the active STT model set via
FlutterGemma.installStt() or modelManager.setActiveModel().
modelPath — path to the STT model file (optional if active model set).
tokenizerPath — path to the tokenizer file (optional if active model set).
preferredBackend — backend preference (e.g., CPU, GPU).
Implementation
@override
Future<SpeechRecognizer> createSttModel({
String? modelPath,
String? tokenizerPath,
PreferredBackend? preferredBackend,
}) async {
// Check if active STT model changed
final currentActiveModel = _modelManager.activeSttModel;
if (_initSttCompleter != null &&
_initializedSttModel != null &&
_lastActiveSttModelName != null) {
final modelChanged =
currentActiveModel == null ||
currentActiveModel.name != _lastActiveSttModelName;
if (modelChanged) {
await _initializedSttModel?.close();
_initSttCompleter = null;
_initializedSttModel = null;
_lastActiveSttModelName = null;
} else {
return _initSttCompleter!.future;
}
}
// Return existing if initialization in progress
if (_initSttCompleter case Completer<SpeechRecognizer> completer) {
return completer.future;
}
final completer = _initSttCompleter = Completer<SpeechRecognizer>();
try {
// Resolve model and tokenizer paths from active STT model
if (modelPath == null || tokenizerPath == null) {
final activeModel = _modelManager.activeSttModel;
if (activeModel == null) {
throw StateError(
'No active STT model set. '
'Use `FlutterGemma.installStt()` first.',
);
}
final filePaths = await _modelManager.getModelFilePaths(activeModel);
if (filePaths == null || filePaths.isEmpty) {
throw StateError('STT model file paths not found');
}
modelPath ??= filePaths[PreferencesKeys.sttModelFile];
tokenizerPath ??= filePaths[PreferencesKeys.sttTokenizerFile];
}
if (modelPath == null) {
throw StateError('STT model path is required');
}
gemmaLog('[FlutterGemmaDesktop] Loading STT model: $modelPath');
if (tokenizerPath == null) {
throw StateError('Tokenizer path is required for desktop STT');
}
// Dispatches construction through the SttRegistry (probe-chain, mirrors
// EmbeddingRegistry). The backend reads spec.sttModelType to select its
// runtime profile, and ONLY config.modelPath/config.tokenizerPath for
// path resolution.
final activeSpec = currentActiveModel is SttModelSpec
? currentActiveModel
: null;
final SttBackendProvider? backend = activeSpec != null
? SttRegistry.instance.findFor(activeSpec)
: (SttRegistry.instance.registered.isNotEmpty
? SttRegistry.instance.registered.first
: null);
if (backend == null) {
throw StateError(
'No STT backend registered. Add flutter_gemma_speech to '
'pubspec.yaml and pass it in sttBackends: of '
'FlutterGemma.initialize(...). Registered backends: '
'${SttRegistry.instance.registered.map((b) => b.name).join(", ")}.',
);
}
// modelPath/tokenizerPath are non-null here (resolved in the preamble).
// maxTokens is unused by STT.
final sttConfig = RuntimeConfig(
maxTokens: 0,
modelPath: modelPath,
tokenizerPath: tokenizerPath,
preferredBackend: preferredBackend,
);
// The backend's createModel(spec, config) signature requires a non-null
// spec, but it resolves paths exclusively from config. On the legacy
// explicit-paths path there is no active spec, so synthesize one from the
// resolved file paths (FileSource) purely to satisfy the signature.
// sttModelType defaults to moonshine — the only shipped profile — for
// this legacy-path fallback.
final specForBackend =
activeSpec ??
SttModelSpec(
name: 'legacy:${path.basename(modelPath)}',
modelSource: ModelSource.file(modelPath),
tokenizerSource: ModelSource.file(tokenizerPath),
sttModelType: SttModelType.moonshine,
);
final model = await backend.createModel(specForBackend, sttConfig);
// Core owns the singleton lifecycle: track it + reset on close. The
// package-built model fires this via CloseNotifier (addCloseListener).
_initializedSttModel = model;
model.addCloseListener(() {
_initializedSttModel = null;
_initSttCompleter = null;
_lastActiveSttModelName = null;
});
_lastActiveSttModelName = currentActiveModel?.name;
completer.complete(model);
return model;
} catch (e, st) {
completer.completeError(e, st);
_initSttCompleter = null;
_initializedSttModel = null;
_lastActiveSttModelName = null;
rethrow;
}
}