flutter_gemma_speech library

On-device speech-to-text and text-to-speech for flutter_gemma, via the LiteRT C API + dart:ffi.

Opt-in. Add to pubspec.yaml and pass instances to FlutterGemma.initialize(sttBackends: [LiteRtSttBackend()], ttsBackends: [LiteRtTtsBackend()]).

import 'package:flutter_gemma/flutter_gemma.dart';
import 'package:flutter_gemma_speech/flutter_gemma_speech.dart';

await FlutterGemma.initialize(
  sttBackends: [LiteRtSttBackend()],
  ttsBackends: [LiteRtTtsBackend()],
);

Classes

LiteRtSttBackend
Web stub for LiteRtSttBackend — the STT web arm is a follow-on (see the design spec's "Out of scope"). Registers cleanly so initialize doesn't break on web, but building a model throws.
LiteRtTtsBackend
Web stub for LiteRtTtsBackendflutter_gemma_speech has no web TTS arm (native-only). Registers cleanly so initialize doesn't break on web, but building a model throws.
VoiceErrorEvent
Non-fatal, per-turn error. RESERVED in v1 (never emitted): fatal errors in runTurn surface as Dart stream errors. Exists so future full-duplex mode can report a per-turn failure without tearing down the stream (§4.1 E1).
VoiceEvent
One event in a voice turn. Sealed — the vocabulary is designed to cover the streaming/barge-in endgame; later phases emit MORE of these events, never new subtypes. (Design spec §4.1.)
VoiceReplyAudioEvent
Synthesized reply audio: 16-bit LE mono PCM at sampleRate.
VoiceReplyTextEvent
A chunk of the LLM's streamed text reply (token granularity in v1).
VoiceResponder
The LLM step of a voice turn: stream the reply's tokens for a user utterance, plus a portable stop for barge-in. Injecting this (not a concrete type) keeps VoiceSession engine-agnostic and usable with a chat, an agent, or a custom (remote / one-shot) responder. (Design spec §4.2.)
VoiceSession
On-device voice turn: PCM in → VoiceEvents out. Pure orchestration — owns NO microphone, NO player, and none of the injected components' lifecycles (the caller creates + closes recognizer / chat / synthesizer). See the design spec (2026-07-29-voice-loop-design.md) for the full contract.
VoiceTranscriptEvent
Recognized user speech. v1 emits exactly one with isFinal = true.
VoiceTurnCompleteEvent
Terminal in v1: the turn finished normally.
VoiceTurnInterruptedEvent
Terminal: the turn was cut short by VoiceSession.interrupt (barge-in). Carries what was produced so the caller can reconcile app state and (for fromChat) chat history from the caller-owned chat.fullHistory — there is deliberately no historyRecorded flag (§12 B2).

Constants

languageIds → const Map<String, int>
Language id map from the model config (talker_config.codec_language_id). Ported from text_to_speech_lm/python/qwen3_tts_pipeline.py LANGUAGE_IDS.

Properties

qwen3SupportedLanguages List<String>
The full set of language values Qwen3-TTS accepts: languageIds's keys (alphabetical) plus 'auto' (automatic language detection — no language-id control token is emitted; see Qwen3Prompt.build's 'auto' branch). 'auto' is appended last since it isn't a language, it's a detection mode.
final

Functions

assertQwen3LanguageSupported(String language) → void
Throws ArgumentError if language (case-insensitive) is not in qwen3SupportedLanguages. Factored out as a standalone pure function (rather than inlined at the call site) so it is unit-testable without spawning a TtsWorker — which needs the ~1.9 GB Qwen3-TTS model bundle on disk to construct at all.
normalizeQwen3Language(String language) String
Canonicalizes an already-assertQwen3LanguageSupported-validated language to the lowercase form every downstream consumer expects.