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.