Skip to main content

audio_waveform_kit 1.8.0 copy "audio_waveform_kit: ^1.8.0" to clipboard
audio_waveform_kit: ^1.8.0 copied to clipboard

Audio recording with waveform and spectrum visualization

audio_waveform_kit #

A Flutter package for audio recording with real-time waveform and spectrum visualization.

Features #

  • Record audio from the microphone with a single tap
  • Real-time waveform — scrolling bar history during recording
  • Real-time spectrum — FFT-based frequency display (pure Dart, no native libraries)
  • Oscilloscope view — raw PCM snapshot for signal inspection
  • Static visualizations — replay waveform, spectrum, and timeline after recording
  • Playback widget — progress bar + seek support out of the box
  • BLoC-based — predictable state machine, easy to integrate with existing BLoC apps

Getting started #

Add to pubspec.yaml:

dependencies:
  audio_waveform_kit: ^1.0.0

Permissions #

Android — AndroidManifest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

iOS — Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>Recording voice messages</string>

macOS — macos/Runner/DebugProfile.entitlements and Release.entitlements:

<key>com.apple.security.device.audio-input</key>
<true/>

Usage #

1. Wrap with AudioWaveformScope #

AudioWaveformScope(
  // optional — all params have sensible defaults
  spectrumConfig: SpectrumConfig(fftSize: 1024, frequencyBands: 64),
  maxWaveformSamples: 56,
  maxSnapshotSamples: 2048,
  child: MyScreen(),
)

AudioWaveformScope provides AudioRecordingService, SpectrumAnalyzer, and AudioRecordingBloc to the subtree.

To plug in your own capture implementation — streaming PCM to disk instead of buffering it in memory, recording from a foreground service, a custom file location — pass recordingService:

AudioWaveformScope(
  recordingService: MyDiskStreamingRecordingService(),
  child: MyScreen(),
)

The scope then uses that instance as-is and does not dispose it — its lifecycle is yours. Omit the parameter and the scope creates and owns an AudioRecordingServiceImpl as before.

2. Add a record button #

Use the built-in round button:

AudioRecordButton.defaultStyle(
  size: 72,
  recordingColor: Colors.red,   // optional
  idleColor: Colors.blue,       // optional
  onRecordingFinished: (RecordingResult result) {
    // available on all platforms:
    print(result.duration);           // Duration
    print(result.spectrumData);       // List<double> — FFT bins (dB)
    print(result.spectrumTimeline);   // List<List<double>> — frames × bands
    print(result.waveformSamples);    // List<double>
    print(result.rmsSamples);         // List<double>

    if (kIsWeb) {
      // result.wavBytes — Uint8List with full WAV file contents
      // result.filePath — generated key, not a real path on web
    } else {
      // result.filePath — absolute path to the saved .wav file
    }
  },
)

Or pass any widget via builder — receives isRecording and onTap:

AudioRecordButton(
  onRecordingFinished: (result) { … },
  builder: ({required context, required isRecording, required onTap}) {
    return GestureDetector(
      onTap: onTap,
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 200),
        width: 72,
        height: 72,
        color: isRecording ? Colors.red : Colors.blue,
        child: Icon(isRecording ? Icons.stop : Icons.mic),
      ),
    );
  },
)

3. Show live visualizations during recording #

// Scrolling amplitude bars
WaveformDisplay()

// Current level meter
RecordingLevelDisplay()

// FFT spectrum
LiveSpectrumDisplay()

// Oscilloscope (raw PCM)
StringSnapshotDisplay()

// Elapsed time
RecordingTimer()

4. Show static visualizations after recording #

These widgets read directly from AudioRecordingBloc — no props needed:

// Averaged spectrum
SpectrumDisplay()

// Spectrum over time (heatmap-style)
TimelineSpectrumDisplay()

// Messenger-style waveform (RMS per window)
StaticMessengerWaveformDisplay(samples: state.rmsSamples)

// Oscilloscope snapshot
StaticStringSnapshotDisplay()

Or access the data directly from the callback result:

onRecordingFinished: (result) {
  // result.spectrumData, result.spectrumTimeline, result.waveformSamples …
}

5. Play back the recorded file #

// from callback:
AudioWaveformPlayer(filePath: result.filePath)

// or from bloc state:
final state = context.read<AudioRecordingBloc>().state as AudioRecordingState$Finished;
AudioWaveformPlayer(filePath: state.filePath)

AudioWaveformPlayer is self-contained — it manages its own AudioPlayerBloc and does not require AudioWaveformScope.

If you need the waveform itself to double as the seek bar (e.g. a segment list next to it must drive the same player), use MessengerWaveformPlayer instead — unlike AudioWaveformPlayer, it does not create its own bloc:

BlocProvider(
  create: (_) => AudioPlayerBloc(filePath: result.filePath),
  child: MessengerWaveformPlayer(
    samples: result.rmsSamples,
    duration: result.duration,
  ),
)

Position is shown as a darker shade over the already-played bars, not a cursor line; tap/drag seeks. Seek never starts playback by itself — even before the first Play, dragging only moves the position.

Recording state machine #

Idle → [Start] → Recording → [Stop] → Finished
                            → [error] → Error
     ← [Reset] ←─────────────────────────────
State Notable fields
AudioRecordingState$Idle —
AudioRecordingState$Recording duration, waveformSamples, rmsSamples, snapshotSamples, liveSpectrumData
AudioRecordingState$Finished filePath, wavBytes (web only), duration, waveformSamples, rmsSamples, snapshotSamples, spectrumData, spectrumTimeline
AudioRecordingState$Error message

Level scale #

StringSnapshotDisplay, WaveformDisplay (envelope style), RecordingLevelDisplay and StaticLevelDisplay all size themselves the same way: RMS level of the signal mapped logarithmically onto floorDb…ceilingDb. A linear gain cannot show both room noise and speech — they sit 30–40 dB apart, so one multiplier either buries the noise at zero or clips speech at the edge of the box.

Parameter Default Description
floorDb -55.0 RMS level at which the display is empty/flat — anything quieter is silence
ceilingDb -15.0 RMS level at which the display reaches full size

MessengerWaveformDisplay (live) uses the same idea via logarithmic: true and minDbThreshold (equivalent to floorDb, ceilingDb fixed at 0).

Oscilloscope tuning #

StringSnapshotDisplay and StaticStringSnapshotDisplay draw raw PCM, so the defaults matter. The swing of the string comes from the signal level on a dB scale, the wave shape is normalized separately — a linear gain cannot show both room noise and speech, they are 30–40 dB apart.

Parameter Default Description
floorDb -55.0 RMS level at which the string lies flat — anything quieter is silence
ceilingDb -15.0 RMS level at which the string swings across the whole box
pointSpacing 3.0 Logical pixels per drawn point; samples in between are averaged (low-pass). 0 draws every sample
alignToZeroCrossing true Starts the curve at a rising zero crossing so the wave does not slide horizontally
minAmplitudeFraction 0.02 Peak below this draws a flat resting string, before the level is even measured
autoGain false Legacy per-frame peak normalization — quiet noise fills the whole box and the string jumps between frames
maxGain 12.0 Upper bound for autoGain amplification

Too still while talking — raise floorDb (-45) or lower ceilingDb (-20). Too lively while silent — lower floorDb (-65). Pre-1.3 look: autoGain: true, pointSpacing: 0, alignToZeroCrossing: false.

SpectrumConfig #

Parameter Default Description
fftSize 1024 FFT window size (power of 2)
frequencyBands 64 Number of output frequency bins
frequencyMin 20 Lower frequency limit (Hz)
frequencyMax 20000 Upper frequency limit (Hz)
sampleRate 44100 Input sample rate (Hz)
dynamicRangeDb 60.0 dB range shown; lower = fewer but louder bars
displayType linear linear or logarithmic frequency scale

Running the example #

cd example && flutter run -d linux
# or
cd example && flutter run -d chrome

Dependencies #

Package Purpose
record Microphone capture (PCM stream)
audioplayers Playback
flutter_bloc + bloc_concurrency State management
equatable Value equality
path_provider Temporary file storage
1
likes
135
points
322
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Audio recording with waveform and spectrum visualization

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

audioplayers, bloc_concurrency, collection, equatable, flutter, flutter_bloc, path_provider, record

More

Packages that depend on audio_waveform_kit