flutter_voice_recorder_ui 0.2.2
flutter_voice_recorder_ui: ^0.2.2 copied to clipboard
A WhatsApp-style hold-to-record voice button with live waveform animation. Outputs raw PCM bytes for streaming to voice-AI APIs (OpenAI, Gemini, Whisper).
flutter_voice_recorder_ui #
A WhatsApp-style hold-to-record voice button with a live waveform animation, designed for streaming raw PCM bytes to voice-AI APIs (OpenAI Realtime, Gemini Live, Whisper, Deepgram).
Born out of a real production voice-AI assistant where I needed a recorder that streams chunks while the user is still talking โ not after they release the button.
โจ Features #
- ๐๏ธ Long-press to record โ quick taps are ignored so you don't trigger the mic permission prompt by accident
- ใฐ๏ธ Live amplitude-driven waveform (~10 fps from the real mic level)
- โฌ ๏ธ Slide-to-cancel โ the mic visually slides over the pill, occluding "Slide to cancel" as you drag further. Rubber-band resistance past the threshold so it never feels stuck.
- ๐ Slide-up-to-lock โ the lock rail grows like an elevator shaft as your finger rises; the mic follows your finger in both axes.
- ๐คฒ Hands-free locked bar with pause / delete / send
- ๐ก Streaming PCM bytes โ pipe to voice-AI APIs while the user is still talking
- ๐๏ธ Defaults tuned for LLM voice endpoints (16 kHz / 16-bit / mono)
- ๐ก๏ธ Built-in mic permission handling
- ๐งฉ Theme-adaptive defaults โ picks up your app's
colorScheme.primaryautomatically. All colors / sizes / thresholds remain overridable.
๐ธ Demo #

The mic button picks up Theme.colorScheme.primary automatically โ here
it's Material 3's default indigo. Pass micColor for any other color.
๐ Getting Started #
1. Install #
dependencies:
flutter_voice_recorder_ui: ^0.1.0
2. Platform setup #
iOS โ add to ios/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>We need the microphone to record your voice messages.</string>
Android โ add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
macOS โ add to macos/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>We need the microphone to record your voice messages.</string>
Add microphone access to both macos/Runner/DebugProfile.entitlements and
Release.entitlements (required for the sandbox):
<key>com.apple.security.device.audio-input</key>
<true/>
If permission was denied earlier, enable the mic for your app in System Settings โ Privacy & Security โ Microphone, then restart the app.
3. Use the widget #
import 'package:flutter_voice_recorder_ui/flutter_voice_recorder_ui.dart';
VoiceRecorderButton(
onRecordingComplete: (result) {
print('Recorded ${result.durationMs}ms');
result.bytesStream.listen((chunk) {
// chunk is Uint8List of raw 16-bit PCM bytes
});
},
onRecordingCancelled: () => print('Cancelled'),
onPermissionDenied: () => print('Mic denied'),
)
That's it. Hold to record, release to finish, slide left to cancel, slide up to lock.
๐ Gestures #
| You do | What happens |
|---|---|
| Tap & release (under ~120 ms) | Nothing. Quick taps are filtered so the mic permission prompt isn't triggered accidentally. |
| Long-press (hold beyond ~120 ms) | Recording starts. The pill expands left of the mic; the lock rail appears above it. |
| Release after a real long-press | Recording stops and onRecordingComplete fires with the duration + the still-open byte stream. |
| Slide โฌ left while holding | The mic slides over the pill. Past cancelSlideThreshold the mic goes red and the pill dims. Release here โ onRecordingCancelled. |
| Slide โฌ up while holding | The mic rises with your finger; the lock rail stretches like an elevator shaft. Past lockSlideThreshold the recording locks hands-free. |
| In locked mode | Pause/resume, delete, or send via the on-screen controls. |
All thresholds are configurable. Haptics fire on press, on threshold crossings, and on release.
๐ค Streaming to voice-AI APIs #
The byte stream emits chunks while the user is still recording, so you can send them to a streaming API in real time.
OpenAI Realtime #
VoiceRecorderButton(
onRecordingComplete: (result) {
result.bytesStream.listen((chunk) {
openAiSocket.add(jsonEncode({
'type': 'input_audio_buffer.append',
'audio': base64Encode(chunk),
}));
});
},
)
Google Gemini Live #
result.bytesStream.listen((chunk) {
geminiLiveSession.sendRealtimeInput(
media: Blob(mimeType: 'audio/pcm', data: chunk),
);
});
Whisper (batch โ collect first, then send) #
final buffer = <int>[];
result.bytesStream.listen(buffer.addAll, onDone: () async {
final bytes = Uint8List.fromList(buffer);
await whisperClient.transcribe(bytes);
});
โ๏ธ Configuration #
Defaults are theme-adaptive โ out of the box the mic button uses
Theme.colorScheme.primary, so the button matches your app. Override
anything you want:
VoiceRecorderButton(
onRecordingComplete: (r) { /* ... */ },
config: const VoiceRecorderConfig(
sampleRate: 24000, // Match your API's expected rate
numChannels: 1,
encoder: AudioEncoder.pcm16bits,
),
size: 56,
micColor: Color(0xFF25D366), // WhatsApp green (or use your brand)
pillColor: Colors.white,
waveformColor: Color(0xFF25D366),
timerColor: Color(0xFF111B21),
recordingIndicatorColor: Color(0xFFEF4444), // pulsing red mic in pill
pillMicGap: 12,
cancelSlideThreshold: 96,
lockSlideThreshold: 48,
enableLock: true,
hapticFeedback: true,
)
The locked-recording bar (the hands-free UI that appears after slide-up) is also themable:
LockedRecordingBar(
// ...required fields...
waveformColor: Color(0xFF8696A0),
sendButtonColor: Color(0xFF25D366),
)
๐งช Using the controller directly #
If you don't want the widget โ say, for a custom UI โ drive the controller yourself:
final controller = VoiceRecorderController();
await controller.start();
controller.bytesStream.listen(sendToApi);
controller.amplitudeStream.listen((amp) => drawBar(amp));
await controller.stop();
await controller.dispose();
๐ Roadmap #
- โ Optional file output (m4a / wav) for non-streaming use cases
- โ Waveform "history" view of completed recordings
- โ Web platform support
- โ Built-in OpenAI Realtime / Gemini Live adapters
PRs welcome.
๐ License #
MIT โ see LICENSE.
๐ About #
Built by Amir Hameed, Senior Flutter Developer. Extracted from production voice-AI work on the Saleforge assistant.