fft_recorder_ui 1.0.4 copy "fft_recorder_ui: ^1.0.4" to clipboard
fft_recorder_ui: ^1.0.4 copied to clipboard

audio recorder + FFT bar visualizer Flutter package. Built on top of flutter_recorder.

example/lib/main.dart

// The following Flutter application demonstrates a complete FFT recorder example.
// This example encompasses audio recording, FFT data visualization, and simple playback of the latest recorded file.
// Each critical part of the code is explained in detail below:

import 'dart:async'; // Provides support for asynchronous programming, required for streams and async/await.

import 'package:audioplayers/audioplayers.dart'; // Used for audio playback functionality.
import 'package:flutter/material.dart'; // Flutter Material UI components.
import 'package:fft_recorder_ui/fft_recorder_ui.dart'; // Provides FFT recording and visualization tools.
import 'package:path_provider/path_provider.dart'; // To locate paths in the mobile filesystem.

/// Entry point of the Flutter application.
/// Runs the [RecorderExampleApp] root widget.
void main() {
  runApp(const RecorderExampleApp());
}

/// The root widget of the application which sets up the main theme and home page.
///
/// [RecorderExampleApp] is a stateless widget for simple configuration of MaterialApp.
/// It defines the application's name, disables the debug banner, sets a theme, and assigns the main page.
class RecorderExampleApp extends StatelessWidget {
  const RecorderExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FFT Recorder UI Example',
      debugShowCheckedModeBanner: false, // Hides the debug banner in non-debug builds.
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.indigo,
        ), // Sets color palette from an indigo seed color.
        useMaterial3: true, // Enables Material 3 UI elements.
      ),
      home: const RecorderExamplePage(), // Specifies [RecorderExamplePage] as the landing page.
    );
  }
}

/// The main interactive page showing FFT recording controls and a simple visualizer.
///
/// [RecorderExamplePage] is a stateful widget in order to:
/// 1. Track UI state (such as the last recorded file, current FFT data, button enable/disable states).
/// 2. Manage long-lived resources such as the FFT controller and AudioPlayer.
class RecorderExamplePage extends StatefulWidget {
  const RecorderExamplePage({super.key});

  @override
  State<RecorderExamplePage> createState() => _RecorderExamplePageState();
}

class _RecorderExamplePageState extends State<RecorderExamplePage> {
  // The controller encapsulating recording, playback, and FFT data streaming logic.
  late final FftRecorderController _controller;

  // Subscription to FFT data updates, used to display real-time FFT bar visualizations.
  StreamSubscription<List<double>>? _fftSubscription;

  // Holds the latest FFT data sample for visual display.
  List<double> _fftData = [];

  // Used for audio file playback of recorded WAV files.
  late final AudioPlayer _player;

  // Tracks the most recently saved recording file path.
  String? _lastSavedPath;

  /// Initializes the audio recorder controller, requests microphone permissions, and
  /// sets up FFT data stream listening and audio playback.
  @override
  void initState() {
    super.initState();
    // Initialize the recorder controller.
    _controller = FftRecorderController();

    // Request microphone permission from the user.
    _controller.requestMicPermission();

    // Initialize the audio player for playback.
    _player = AudioPlayer();

    // Listen to the FFT data stream from the controller; update [_fftData] on new data.
    _fftSubscription = _controller.fftStream.listen((data) {
      if (!mounted) return; // Prevent updates after widget disposal.
      setState(() {
        _fftData = data;
      });
    });
  }

  /// Disposes of resources when the widget is removed from the widget tree.
  /// Cancels subscriptions and disposes controllers and players to avoid memory leaks.
  @override
  void dispose() {
    _fftSubscription?.cancel(); // Cancel FFT data stream.
    _player.dispose(); // Release audio player resources.
    _controller.dispose(); // Release FFT recorder resources.
    super.dispose();
  }

  /// Starts a new audio recording session, saves the output file to the app's documents directory,
  /// and updates UI state to remember the location of the recorded file.
  Future<void> _startRecording() async {
    // Get the directory for application documents.
    final dir = await getApplicationDocumentsDirectory();

    // Build a new unique file name based on timestamp to prevent overwrites.
    final filePath = '${dir.path}/recording_${DateTime.now().millisecondsSinceEpoch}.wav';
    // Start the recording, saving WAV audio to [filePath].
    await _controller.startRecording(filePath: filePath);

    // Update the state so that the last saved path is now [filePath].
    setState(() => _lastSavedPath = filePath);
  }

  /// Plays back the audio file using the [AudioPlayer].
  /// Stops any current playback before starting the new one.
  ///
  /// [path]: The file system path to the WAV file to play.
  Future<void> _playRecording(String path) async {
    await _player.stop(); // Stop any ongoing playback.
    await _player.play(DeviceFileSource(path)); // Play the new file.
  }

  /// Builds the main user interface:
  /// - Provides buttons to control recording (start, stop, pause, resume) and audio playback.
  /// - Displays the most recent saved file path.
  /// - Contains an FFT bar visualizer showing real-time frequency data.
  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      appBar: AppBar(title: const Text('Recorder + Bar Visualizer')),

      // Padding ensures content isn't flush with the screen edges.
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // The Wrap widget arranges action buttons gracefully, allowing them to wrap to new lines if space is insufficient.
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                // Play button: enabled only if a recording exists.
                ElevatedButton(
                  onPressed: _lastSavedPath == null
                      ? null
                      : () {
                          _playRecording(_lastSavedPath!);
                        },
                  child: const Text('재생'), // "Play" in Korean
                ),
                // Start Recording button: enabled only if not currently recording.
                ElevatedButton(
                  onPressed: _controller.recordingStatus.value == RecordingStatus.recording
                      ? null
                      : _startRecording,
                  child: const Text('녹음 시작'), // "Start Recording" in Korean
                ),
                // Pause button: enabled only if currently recording.
                ElevatedButton(
                  onPressed: _controller.recordingStatus.value == RecordingStatus.recording
                      ? _controller.pauseRecording
                      : null,
                  child: const Text('일시정지'), // "Pause" in Korean
                ),
                // Resume button: enabled only if currently paused.
                ElevatedButton(
                  onPressed: _controller.recordingStatus.value == RecordingStatus.paused
                      ? _controller.resumeRecording
                      : null,
                  child: const Text('재개'), // "Resume" in Korean
                ),
                // Stop button: enabled if recording is not idle.
                ElevatedButton(
                  onPressed: _controller.recordingStatus.value == RecordingStatus.idle
                      ? null
                      : () {
                          // Stop the recording and update the last saved path if a new recording was created.
                          final path = _controller.stopRecording();
                          setState(() {
                            _lastSavedPath = path ?? _lastSavedPath;
                          });
                          // After stopping, if a path exists, play the recorded audio right away.
                          final playPath = path ?? _lastSavedPath;
                          if (playPath != null) {
                            _playRecording(playPath);
                          }
                        },
                  child: const Text('정지'), // "Stop" in Korean
                ),
              ],
            ),
            const SizedBox(height: 12),

            // Displays the path of the last saved recording.
            if (_lastSavedPath != null)
              Align(
                alignment: Alignment.centerLeft,
                child: Text('Saved: $_lastSavedPath', style: theme.textTheme.bodySmall),
              ),

            const SizedBox(height: 16),

            // A container for the FFT data bar visualizer.
            // Its appearance is styled to stand out, with rounded corners and a dark background.
            Container(
              width: 170,
              height: 48,
              alignment: Alignment.center,
              decoration: BoxDecoration(
                color: Colors.grey.shade900,
                borderRadius: BorderRadius.circular(12),
              ),
              padding: const EdgeInsets.all(12),

              // The [BarVisualizer] displays the most recent FFT data as a sequence of vertical bars.
              // Customizable parameters control the number, width, spacing, and color of the bars.
              // If no data exists, a placeholder message is shown.
              child: BarVisualizer(
                data: _fftData, // The amplitude values to visualize.
                barColor: Colors.white,
                barCount: 9,
                barWidth: 5.33,
                maxHeight: 48,
                spacing: 8,
                emptyText: 'FFT 데이터 대기 중', // "Waiting for FFT data" in Korean
              ),
            ),
            const SizedBox(height: 16),
          ],
        ),
      ),
    );
  }
}
9
likes
140
points
357
downloads

Publisher

verified publishernaeileun.dev

Weekly Downloads

audio recorder + FFT bar visualizer Flutter package. Built on top of flutter_recorder.

Homepage

Topics

#audio #recorder #fft #visualization

Documentation

API reference

License

MIT (license)

Dependencies

flutter, flutter_recorder, permission_handler

More

Packages that depend on fft_recorder_ui