waveform_recorder 2.0.0
waveform_recorder: ^2.0.0 copied to clipboard
A Flutter package for recording audio with real-time waveform visualization, ideal for voice messaging and audio recording features in chat applications.
The waveform_recorder package provides a widget to show a waveform as an audio stream is being recorded in the style of recent AI chat packages.

Setup #
For this package to work, you'll need to set up the underlying record
package according to the setup and permission
instructions.
Usage #
The main entry point for this package is the WaveformRecorder widget, which
requires an instance of the WaveformRecorderController to start/stop
recording. Here's an example of using the recorder to record audio and then
allowing the user to play it back:
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:waveform_recorder/waveform_recorder.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _textController = TextEditingController();
final _waveController = WaveformRecorderController();
@override
void dispose() {
_textController.dispose();
_waveController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('WaveForm Example')),
body: ListenableBuilder(
listenable: _waveController,
builder: (context, _) => Column(
children: [
Expanded(
child: Center(
child: OutlinedButton(
onPressed: !_waveController.isRecording &&
_waveController.file != null
? _playRecording
: null,
child: const Text('Play'),
),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: _waveController.isRecording
? WaveformRecorder(
height: 48,
controller: _waveController,
onRecordingStopped: _onRecordingStopped,
)
: TextField(
controller: _textController,
maxLines: 1,
decoration: const InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 8,
),
),
),
),
),
const Gap(8),
IconButton(
icon: Icon(
_waveController.isRecording ? Icons.stop : Icons.mic,
),
// starting can block on an OS permission prompt
onPressed: _waveController.isStarting
? null
: _toggleRecording,
),
],
),
),
],
),
),
),
);
Future<void> _toggleRecording() => switch (_waveController.isRecording) {
true => _waveController.stopRecording(),
false => _waveController.startRecording(),
};
Future<void> _onRecordingStopped() async {
final file = _waveController.file;
if (file == null) return;
_textController.text = ''
'${_waveController.fileName}: '
'${_waveController.length.inMilliseconds / 1000} seconds';
debugPrint('Recording properties:');
debugPrint(' uri: ${file.uri}');
debugPrint(' fileName: ${_waveController.fileName}');
debugPrint(' mimeType: ${_waveController.mimeType}');
}
Future<void> _playRecording() async {
final file = _waveController.file;
if (file == null) return;
final source = file is FileSystemXFile
? DeviceFileSource(file.path)
: UrlSource(file.uri);
await AudioPlayer().play(source);
}
}
Usage Considerations #
WaveformRecorderController.file is an XFile from the cross_file
package; waveform_recorder re-exports
XFile, FileSystemXFile and ScopedStorageXFile so you don't need to depend on
cross_file yourself.
On every platform except the web, the recording is a temporary file on disk and
file is a FileSystemXFile; it's your app's responsibility to remove this
temp file when it's done with it. On the web, the recording is a blob URL and
file is a ScopedStorageXFile whose uri is that URL. Use the controller's
fileName and mimeType for a display/download name and content type, e.g.
Future<void> _deleteRecording() async {
final file = _waveController.file;
if (file is FileSystemXFile) await File(file.path).delete();
}
Upgrading to 2.0 #
Version 2.0 moves to cross_file 0.4, whose XFile no longer has path,
mimeType or a synchronous name:
file.path→(file as FileSystemXFile).pathon native platforms, orfile.uri(the blob URL) on the webfile.name→controller.fileNamefile.mimeType→controller.mimeType
It also requires Dart 3.11 / Flutter 3.41 or later.