flutter_davoice 0.0.4
flutter_davoice: ^0.0.4 copied to clipboard
Flutter voice AI plugin for on-device STT, neural TTS, speaker verification, audio playback, and native iOS/Android voice flows.
flutter_davoice #
Flutter voice AI plugin for Davoice on iOS and Android.
Core Capabilities #
- Speaker verification / speaker identification: speaker onboarding and live verification for speaker-gated voice experiences.
- Wake-word speaker JSON support: accepts the speaker enrollment JSON or saved enrollment JSON path created by the Davoice wake-word library: https://github.com/frymanofer/Flutter_WakeWordDetection/
- Multi-language STT: speech-to-text for conversational apps, including speaker identification and speaker-isolated recognition flows.
- On-device TTS: low-latency Davoice text-to-speech with local voice models.
- VAD / voice activity detection flows: voice activity detection is used in the Davoice voice pipeline and in the companion wake-word package for always-listening and conversational microphone control.
See STT with speaker isolation in action: https://www.youtube.com/watch?v=uYpaCXAvjew
Full Example App #
The runnable Flutter example app is distributed separately because it includes large voice, wake-word, and speaker-verification model assets.
Example app: https://github.com/frymanofer/Flutter_DaVoice
The example app demonstrates:
- voice model selection
- license setup
- speaker verification onboarding
- saved speaker signature reuse
- live speaker verification before wake-word/STT activation
- wake-word detection through
flutter_wake_word - speaker-isolated STT initialization
- on-device TTS
- safe STT pause / TTS speak / STT resume sequencing
- manual TTS testing
- speech echo testing
- Full AI Chat style turn taking
Installation #
dependencies:
flutter_davoice: ^0.0.1
For wake word, speaker onboarding, speaker verification mic flows, and VAD / wake-word front-end behavior, also add the companion package:
dependencies:
flutter_wake_word: ^0.0.41
Companion package: https://github.com/frymanofer/Flutter_WakeWordDetection/
Platform Setup #
Android #
The plugin requires Android minSdk 29 or newer.
Add permissions used by voice apps:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.INTERNET" />
iOS #
Add usage descriptions:
<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone for voice features.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>This app uses speech recognition to convert speech to text.</string>
The plugin supports iOS 13.0 or newer.
Model Assets #
Add Davoice voice models to your Flutter app assets:
flutter:
assets:
- assets/models/model_ex_ariana_fast.dm
- assets/models/model_ex_rich_fast.dm
Then pass the same asset key to initAll or initTTS:
const arianaFastModel = 'assets/models/model_ex_ariana_fast.dm';
const richFastModel = 'assets/models/model_ex_rich_fast.dm';
Create The Speech API #
import 'dart:async';
import 'package:flutter_davoice/flutter_davoice.dart';
final speech = FlutterDavoice();
final subscriptions = <StreamSubscription<dynamic>>[];
License #
Call setLicense before initializing STT/TTS.
final licensed = await speech.setLicense('YOUR_DAVOICE_LICENSE_KEY');
if (!licensed) {
throw StateError('Davoice license is not valid.');
}
You can also validate a key explicitly:
final valid = await speech.isLicenseValid('YOUR_DAVOICE_LICENSE_KEY');
Listen To STT/TTS Events #
Register event listeners before starting the voice flow:
subscriptions.addAll([
speech.onSpeechStart.listen((_) {
print('STT started');
}),
speech.onSpeechPartialResults.listen((event) {
final text = event.value.join(' ').trim();
print('partial: $text');
}),
speech.onSpeechResults.listen((event) {
final text = event.value.join(' ').trim();
print('final: $text');
}),
speech.onSpeechError.listen((event) {
print('speech error: ${event.code} ${event.message}');
}),
speech.onSpeechVolumeChanged.listen((event) {
print('volume: ${event.value}');
}),
speech.onFinishedSpeaking.listen((_) {
print('TTS or marked playback finished');
}),
speech.onNewSpeechWAV.listen((event) {
print('new speech wav: ${event.path}');
}),
]);
Dispose subscriptions and native resources when your screen/app shuts down:
for (final subscription in subscriptions) {
await subscription.cancel();
}
await speech.destroyAll();
Initialize STT + TTS #
Use initAll when your app needs both STT and TTS in the same flow.
await speech.initAll(
const DavoiceInitAllOptions(
locale: 'en-US',
model: 'assets/models/model_ex_ariana_fast.dm',
timeoutMs: 30000,
),
);
For speaker-isolated STT, pass the saved speaker enrollment JSON file path:
await speech.initAll(
DavoiceInitAllOptions(
locale: 'en-US',
model: 'assets/models/model_ex_ariana_fast.dm',
timeoutMs: 30000,
onboardingJsonPath: savedSpeakerEnrollmentJsonPath,
),
);
The onboardingJsonPath value is the enrollment JSON path produced by the
Davoice wake-word package speaker verification onboarding flow.
Start, Pause, And Resume STT #
Start recognition:
await speech.start(
'en-US',
options: const DavoiceStartOptions(
extraPartialResults: true,
extraMaxResults: 5,
),
);
Pause STT:
await speech.pauseSpeechRecognition();
Resume STT:
await speech.unPauseSpeechRecognition(-1);
unPauseSpeechRecognition parameters:
times: how many recognition cycles to resume for. Use-1for continuous.preFetchMs: optional prefetch window used by native flows.timeoutMs: native resume timeout.
Stop or cancel recognition:
await speech.stop();
await speech.cancel();
Check availability:
final available = await speech.isAvailable();
final recognizing = await speech.isRecognizing();
Correct STT/TTS Sequencing #
When your app speaks, pause STT first. Resume STT only after speak returns.
Future<void> speakReply(String text) async {
await speech.pauseSpeechRecognition();
await speech.speak(
text,
speakerId: 0,
speed: 0.88,
);
await speech.unPauseSpeechRecognition(-1);
}
This sequence matters in real voice apps because it prevents STT from hearing the app's own TTS output.
On-Device TTS #
If you only need TTS, initialize the TTS model directly:
await speech.setLicense('YOUR_DAVOICE_LICENSE_KEY');
await speech.initTTS('assets/models/model_ex_ariana_fast.dm');
await speech.speak(
'Hello from Davoice.',
speakerId: 0,
speed: 0.88,
);
Stop current TTS playback:
await speech.stopSpeaking();
Speaker Verification + Speaker-Isolated STT #
Speaker onboarding and live verification are handled by the Davoice wake-word package. This package consumes the enrollment JSON path to initialize speaker-aware STT.
Typical production flow:
- Use
flutter_wake_wordto onboard the speaker. - Save the returned enrollment JSON to a file, for example
sv_enrollment.json. - On next launch, offer:
- use saved signature
- create new signature
- skip speaker verification
- If speaker verification is enabled, verify the speaker first.
- Pass the saved enrollment JSON path to
flutter_davoice. - Initialize wake word and STT with the same saved enrollment path.
The Davoice STT side:
await speech.initAll(
DavoiceInitAllOptions(
locale: 'en-US',
model: 'assets/models/model_ex_ariana_fast.dm',
onboardingJsonPath: savedSpeakerEnrollmentJsonPath,
),
);
You can also start speech with a speaker-verification enrollment JSON path:
await speech.startWithSVOnboardingJson(
'en-US',
savedSpeakerEnrollmentJsonPath,
);
The companion wake-word package also accepts the same enrollment path when starting wake-word detection, so wake-word activation and STT can use the same speaker identity.
Wake-word package: https://github.com/frymanofer/Flutter_WakeWordDetection/
Speech Echo Pattern #
The standalone example app includes a useful test flow:
- Wake word fires.
- App pauses wake-word detection.
- App pauses STT.
- Selected Davoice speaker says an intro line.
- App resumes STT only after
await speak(...)returns. - User speaks.
- App waits for a silence timeout.
- App pauses STT.
- App repeats what the user said with TTS.
- App resumes STT.
Minimal implementation:
Timer? silenceTimer;
String transcript = '';
void onTranscript(String text) {
transcript = text.trim();
silenceTimer?.cancel();
silenceTimer = Timer(const Duration(seconds: 2), () async {
if (transcript.isEmpty) return;
final reply = transcript;
transcript = '';
await speech.pauseSpeechRecognition();
await speech.speak(reply, speakerId: 0, speed: 0.88);
await speech.unPauseSpeechRecognition(-1);
});
}
subscriptions.addAll([
speech.onSpeechPartialResults.listen((event) {
if (event.value.isNotEmpty) {
onTranscript(event.value.first);
}
}),
speech.onSpeechResults.listen((event) {
if (event.value.isNotEmpty) {
onTranscript(event.value.first);
}
}),
]);
Full AI Chat Pattern #
The same STT/TTS control pattern works for AI chat:
- Collect partial/final STT.
- Wait for a silence timeout.
- Pause STT.
- Send the transcript to your AI service.
- Speak the AI reply with Davoice TTS.
- Resume STT.
Timer? silenceTimer;
String pendingUserText = '';
bool aiTurnInFlight = false;
void onUserSpeech(String text) {
pendingUserText = text.trim();
silenceTimer?.cancel();
silenceTimer = Timer(const Duration(seconds: 2), () async {
final userText = pendingUserText;
if (userText.isEmpty || aiTurnInFlight) return;
pendingUserText = '';
aiTurnInFlight = true;
await speech.pauseSpeechRecognition();
try {
final aiReply = await sendToYourAIService(userText);
await speech.speak(aiReply, speakerId: 0, speed: 0.88);
} finally {
aiTurnInFlight = false;
await speech.unPauseSpeechRecognition(-1);
}
});
}
Audio Playback #
Play a WAV file or URL:
await speech.playWav('/path/to/file.wav');
If markAsLast is true, playWav completes after onFinishedSpeaking:
await speech.playWav('/path/to/file.wav', markAsLast: true);
Play PCM:
await speech.playPCM(
pcmBytes,
sampleRate: 16000,
channels: 1,
format: DavoicePcmFormat.i16,
);
Or pass an external PCM descriptor:
await speech.playBuffer(
DavoiceExternalPcm(
base64: base64Pcm,
sampleRate: 16000,
channels: 1,
format: DavoicePcmFormat.i16,
),
);
iOS Permission Helpers #
final hasMic = await speech.hasIOSMicPermissions();
final micGranted = hasMic || await speech.requestIOSMicPermissions(2500);
final hasSpeech = await speech.hasIOSSpeechRecognitionPermissions();
final speechGranted =
hasSpeech || await speech.requestIOSSpeechRecognitionPermissions(2500);
Microphone And Audio Controls #
await speech.pauseMicrophone();
await speech.unPauseMicrophone();
await speech.setAECEnabled(true);
Android Remote STT Helpers #
For Android remote-STT workflows:
await speech.initAllRemoteSTT(
model: 'assets/models/model_ex_ariana_fast.dm',
onboardingJsonPath: savedSpeakerEnrollmentJsonPath,
);
await speech.initAllRemoteSTTAndTTS(
model: 'assets/models/model_ex_ariana_fast.dm',
onboardingJsonPath: savedSpeakerEnrollmentJsonPath,
);
API Reference #
Initialization #
FlutterDavoice({ttsCompletionTimeout})initAll(DavoiceInitAllOptions options)destroyAll()initWithoutModel()destroyWithoutModel()destroyWihtouModel()initTTS(String model)initAllRemoteSTT({required String model, String? onboardingJsonPath})initAllRemoteSTTAndTTS({String? model, String? onboardingJsonPath})
Speech Recognition #
start(String locale, {DavoiceStartOptions? options})startWithSVOnboardingJson(String locale, String onboardingJsonPath)stop()cancel()isAvailable()isRecognizing()pauseSpeechRecognition()unPauseSpeechRecognition(int times, {int preFetchMs, int timeoutMs})pauseMicrophone()unPauseMicrophone()
Text To Speech And Playback #
speak(String text, {int speakerId, double speed})stopSpeaking()playWav(String pathOrUrl, {bool markAsLast})playPCM(Uint8List data, {required int sampleRate, int channels, bool interleaved, DavoicePcmFormat format, bool markAsLast})playBuffer(DavoiceExternalPcm pcm)
License And Audio #
setLicense(String licenseKey)isLicenseValid(String licenseKey)setAECEnabled(bool enabled)
Events #
eventsonSpeechStartonSpeechRecognizedonSpeechEndonSpeechErroronSpeechResultsonSpeechPartialResultsonSpeechVolumeChangedonNewSpeechWAVonFinishedSpeaking
iOS Permissions #
hasIOSMicPermissions()requestIOSMicPermissions(int waitTimeoutMs)hasIOSSpeechRecognitionPermissions()requestIOSSpeechRecognitionPermissions(int waitTimeoutMs)
Validation #
flutter analyze
flutter test
flutter pub publish --dry-run
The package repo intentionally excludes the runnable example app and large model assets. Use the standalone example repo for a complete app: https://github.com/frymanofer/Flutter_DaVoice