# How to Use Audio Editing Features in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 40-60 min
- Compatibility: FlutterFlow Pro+ (Custom Widgets and Custom Actions required)
- Last updated: March 2026

## TL;DR

FlutterFlow's built-in AudioPlayer widget plays audio but cannot edit it. For real audio editing, use audio_waveforms for waveform display and trim point selection, ffmpeg_kit_flutter for client-side trim, merge, and speed adjustments, or a Firebase Cloud Function running FFmpeg for server-side processing. Choose client-side for files under 50MB and server-side for larger files or complex operations.

## FlutterFlow Has No Built-In Audio Editor

This is one of the most common misunderstandings for FlutterFlow beginners: the AudioPlayer widget is a playback widget only. It has play, pause, seek, and volume controls — nothing for trimming, splitting, merging, or applying effects. Actual audio editing requires either running FFmpeg on-device via ffmpeg_kit_flutter, or uploading the file to a Cloud Function that runs server-side FFmpeg. This guide explains both approaches clearly and shows you when to use each.

## Before you start

- FlutterFlow Pro plan (Custom Actions required for FFmpeg integration)
- Firebase Storage configured for audio file upload and download
- Basic understanding of Firebase Cloud Functions for server-side processing
- An audio file picker already configured (file upload action)

## Step-by-step guide

### 1. Understand What FlutterFlow's AudioPlayer Can and Cannot Do

The AudioPlayer widget in FlutterFlow is built on the just_audio package. It supports playing audio from URL or local file, seeking to a specific position, looping, speed control (0.5x to 2x), and volume control. It does not support: trimming audio to a time range, splitting a file into segments, merging multiple files, changing pitch, removing silence, converting formats, or applying reverb or equalization effects. If your users need any of these features, you must use one of the two editing approaches in this guide — there is no setting or toggle in FlutterFlow that adds these capabilities to the built-in widget.

**Expected result:** You have a clear decision: playback only = use AudioPlayer widget. Editing = use ffmpeg_kit_flutter or Cloud Function.

### 2. Add the audio_waveforms Package for Visual Trim Point Selection

Users cannot trim audio without seeing the waveform. Add audio_waveforms to your Custom Code dependencies in FlutterFlow. Create a Custom Widget called WaveformTrimmer that renders the audio waveform using WaveformController and RecorderController from the audio_waveforms package. Overlay two draggable vertical handles on the waveform — one for the trim start position and one for the trim end position. Store the start and end positions as Page State variables (trimStart and trimEnd as Duration values). Add a Play Preview button that plays only the trimmed section using AudioPlayer.setClip(start, end) so users can hear the selection before confirming the trim.

```
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter/material.dart';

class WaveformTrimmerWidget extends StatefulWidget {
  final String audioPath;
  final Duration trimStart;
  final Duration trimEnd;
  final ValueChanged<Duration> onTrimStartChanged;
  final ValueChanged<Duration> onTrimEndChanged;

  const WaveformTrimmerWidget({
    super.key,
    required this.audioPath,
    required this.trimStart,
    required this.trimEnd,
    required this.onTrimStartChanged,
    required this.onTrimEndChanged,
  });

  @override
  State<WaveformTrimmerWidget> createState() => _WaveformTrimmerWidgetState();
}

class _WaveformTrimmerWidgetState extends State<WaveformTrimmerWidget> {
  late final PlayerController _controller;

  @override
  void initState() {
    super.initState();
    _controller = PlayerController();
    _controller.preparePlayer(path: widget.audioPath);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AudioFileWaveforms(
      playerController: _controller,
      size: Size(MediaQuery.of(context).size.width, 80),
      waveformType: WaveformType.fitWidth,
      playerWaveStyle: const PlayerWaveStyle(
        fixedWaveColor: Colors.grey,
        liveWaveColor: Colors.blue,
        spacing: 6,
      ),
    );
  }
}
```

**Expected result:** The audio waveform renders as a scrollable visualization. Two colored handles mark the trim start and end points that the user can drag.

### 3. Execute Client-Side Audio Trim with ffmpeg_kit_flutter

After the user sets trim points, execute the actual trim operation on-device using ffmpeg_kit_flutter. Add the package to your Custom Code dependencies. Create a Custom Action called trimAudio that takes the source file path, trimStart seconds, and trimEnd seconds, builds the FFmpeg command string, executes it, and returns the output file path. The on-device approach works well for files under 50MB and produces results in 2-10 seconds depending on the clip length and device speed. For longer recordings, show a progress indicator during processing.

```
import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

Future<String?> trimAudio(
  String inputPath,
  double startSeconds,
  double endSeconds,
) async {
  final duration = endSeconds - startSeconds;
  if (duration <= 0) return null;

  final directory = await getTemporaryDirectory();
  final timestamp = DateTime.now().millisecondsSinceEpoch;
  final outputPath = '${directory.path}/trimmed_$timestamp.m4a';

  // FFmpeg trim command
  // -ss: start time, -t: duration, -c copy: no re-encode (fast)
  final command = '-ss $startSeconds -t $duration -i "$inputPath" -c copy "$outputPath"';

  final session = await FFmpegKit.execute(command);
  final returnCode = await session.getReturnCode();

  if (ReturnCode.isSuccess(returnCode)) {
    return outputPath;
  } else {
    final logs = await session.getAllLogsAsString();
    print('FFmpeg error: $logs');
    return null;
  }
}
```

**Expected result:** After tapping Trim, the Custom Action runs in 1-5 seconds and returns the path to the trimmed audio file, which you can then play in AudioPlayer to confirm.

### 4. Merge Multiple Audio Files with FFmpeg

To combine multiple clips into one recording, use FFmpeg's concat filter. Create a Custom Action called mergeAudioFiles that takes a List of file paths, writes a temporary concat list file, and runs the FFmpeg concat command. This is useful for podcast editing, audio messaging with multiple takes, or any scenario where users record in segments. The concat demuxer (-f concat) is faster than the filter_complex approach for simple sequential merging without transitions.

```
import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

Future<String?> mergeAudioFiles(List<String> inputPaths) async {
  if (inputPaths.length < 2) return inputPaths.isEmpty ? null : inputPaths[0];

  final dir = await getTemporaryDirectory();
  final timestamp = DateTime.now().millisecondsSinceEpoch;

  // Write concat list file
  final listFile = File('${dir.path}/concat_$timestamp.txt');
  final lines = inputPaths.map((p) => "file '$p'").join('\n');
  await listFile.writeAsString(lines);

  final outputPath = '${dir.path}/merged_$timestamp.m4a';
  final command =
      '-f concat -safe 0 -i "${listFile.path}" -c copy "$outputPath"';

  final session = await FFmpegKit.execute(command);
  final returnCode = await session.getReturnCode();

  await listFile.delete();

  return ReturnCode.isSuccess(returnCode) ? outputPath : null;
}
```

**Expected result:** Three recorded clips merge into a single continuous audio file that plays back seamlessly in AudioPlayer.

### 5. Upload Processed Audio to Firebase Storage

After trimming or merging, upload the resulting file to Firebase Storage and save the URL to Firestore. Create a Custom Action called uploadProcessedAudio that reads the local file as bytes, uploads to a user-specific Storage path, and returns the download URL. Update the relevant Firestore document with the new audio_url and duration_seconds fields. Delete the temporary local file after successful upload to free device storage.

```
import 'package:firebase_storage/firebase_storage.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:io';

Future<String?> uploadProcessedAudio(
  String localPath,
  String userId,
  String recordingId,
) async {
  final file = File(localPath);
  if (!file.existsSync()) return null;

  final filename = 'recording_${DateTime.now().millisecondsSinceEpoch}.m4a';
  final ref = FirebaseStorage.instance
      .ref('users/$userId/recordings/$filename');

  await ref.putFile(file, SettableMetadata(contentType: 'audio/m4a'));
  final url = await ref.getDownloadURL();

  await FirebaseFirestore.instance
      .collection('recordings')
      .doc(recordingId)
      .update({
        'audio_url': url,
        'processed_at': FieldValue.serverTimestamp(),
        'filename': filename,
      });

  // Clean up temp file
  await file.delete();
  return url;
}
```

**Expected result:** The processed audio file is uploaded to Firebase Storage and the Firestore recording document is updated with the new URL. The AudioPlayer widget can now load the trimmed version.

## Complete code example

File: `audio_editor_actions.dart`

```dart
// Complete audio editing Custom Actions for FlutterFlow
// Requires: ffmpeg_kit_flutter, path_provider in pubspec dependencies

import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:path_provider/path_provider.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:io';

// ─── Trim Audio ──────────────────────────────────────────────────────────────

Future<String?> trimAudio(
  String inputPath,
  double startSeconds,
  double endSeconds,
) async {
  final duration = endSeconds - startSeconds;
  if (duration <= 0.5) return null; // Minimum 0.5 second clip

  final dir = await getTemporaryDirectory();
  final ts = DateTime.now().millisecondsSinceEpoch;
  final outputPath = '${dir.path}/trim_$ts.m4a';

  final cmd = '-ss $startSeconds -t $duration -i "$inputPath" -c copy "$outputPath"';
  final session = await FFmpegKit.execute(cmd);
  final rc = await session.getReturnCode();
  return ReturnCode.isSuccess(rc) ? outputPath : null;
}

// ─── Merge Audio ─────────────────────────────────────────────────────────────

Future<String?> mergeAudioFiles(List<String> paths) async {
  if (paths.isEmpty) return null;
  if (paths.length == 1) return paths[0];

  final dir = await getTemporaryDirectory();
  final ts = DateTime.now().millisecondsSinceEpoch;

  final listFile = File('${dir.path}/list_$ts.txt');
  await listFile.writeAsString(paths.map((p) => "file '$p'").join('\n'));

  final outputPath = '${dir.path}/merged_$ts.m4a';
  final cmd = '-f concat -safe 0 -i "${listFile.path}" -c copy "$outputPath"';
  final session = await FFmpegKit.execute(cmd);
  final rc = await session.getReturnCode();

  await listFile.delete();
  return ReturnCode.isSuccess(rc) ? outputPath : null;
}

// ─── Change Speed ─────────────────────────────────────────────────────────────

Future<String?> changeAudioSpeed(
  String inputPath,
  double speed, // 0.5 = half speed, 2.0 = double speed
) async {
  if (speed < 0.5 || speed > 2.0) return null;

  final dir = await getTemporaryDirectory();
  final ts = DateTime.now().millisecondsSinceEpoch;
  final outputPath = '${dir.path}/speed_$ts.m4a';

  // atempo must be between 0.5 and 2.0 — chain for values outside range
  final cmd = '-i "$inputPath" -filter:a "atempo=$speed" "$outputPath"';
  final session = await FFmpegKit.execute(cmd);
  final rc = await session.getReturnCode();
  return ReturnCode.isSuccess(rc) ? outputPath : null;
}

// ─── Upload to Firebase Storage ───────────────────────────────────────────────

Future<String?> uploadAudio(String localPath, String userId) async {
  final file = File(localPath);
  if (!file.existsSync()) return null;

  final name = 'audio_${DateTime.now().millisecondsSinceEpoch}.m4a';
  final ref = FirebaseStorage.instance.ref('users/$userId/audio/$name');
  await ref.putFile(file, SettableMetadata(contentType: 'audio/m4a'));
  final url = await ref.getDownloadURL();
  await file.delete();
  return url;
}
```

## Common mistakes

- **Expecting FlutterFlow's AudioPlayer widget to have trim, split, or effect controls** — The AudioPlayer widget is a playback-only widget. It wraps the just_audio package which plays files — it has no audio manipulation capabilities whatsoever. There is no hidden setting that enables editing. Fix: Accept that editing requires Custom Actions with ffmpeg_kit_flutter for on-device processing, or a Cloud Function for server-side operations. The AudioPlayer widget can be used for preview playback after editing.
- **Processing large audio files on-device with FFmpeg and blocking the UI thread** — FFmpeg operations on files over 50MB can take 30+ seconds on mid-range devices. If this runs on the main thread, the entire app freezes. Fix: Run FFmpeg in a compute isolate using Flutter's compute() function, or move large file processing to a Cloud Function. Show a progress indicator and keep the UI responsive during processing.
- **Not deleting temporary files after upload** — Each trim or merge operation creates a new file in the device's temporary directory. Processing 10 audio clips leaves 10 files consuming 100MB+ of device storage that is never reclaimed. Fix: Always call file.delete() after successfully uploading the processed audio to Firebase Storage. The Storage URL is your permanent reference — the local temp file is disposable.

## Best practices

- Always validate that the trim end position is greater than the trim start — a zero-length trim will produce an empty file that causes confusing errors downstream.
- Show a waveform visualization before asking users to set trim points — users cannot accurately trim audio they cannot see.
- Use -c copy in FFmpeg trim and concat operations to avoid re-encoding when the input format matches the output format — this is 10-50x faster.
- For files over 50MB or operations involving multiple filters, process server-side via Cloud Function to avoid on-device memory and time constraints.
- Cache the audio duration in Firestore alongside the URL so you do not need to load the full audio file just to display its length.
- Test audio editing on a physical device — iOS simulators have audio hardware limitations and Android emulators may produce incorrect FFmpeg results.
- Provide a Cancel operation that deletes any partially processed temporary files to avoid storage leaks on interrupted edits.

## Frequently asked questions

### Does FlutterFlow's AudioPlayer widget support trim or cut operations?

No. The AudioPlayer widget supports playback controls only: play, pause, seek, loop, speed (0.5x-2x), and volume. It cannot modify audio files. For editing, you need Custom Actions using ffmpeg_kit_flutter or a server-side Cloud Function with FFmpeg.

### What audio formats does ffmpeg_kit_flutter support?

ffmpeg_kit_flutter supports all formats that FFmpeg supports: MP3, M4A, AAC, WAV, OGG, FLAC, OPUS, and many more. For mobile apps, M4A/AAC is recommended as the output format — it has the best compatibility across iOS and Android with good compression.

### How do I show a progress percentage during FFmpeg processing?

Use FFmpegKitConfig.enableStatisticsCallback() to receive progress updates. The Statistics object has a time property (milliseconds processed) which you can divide by the total duration to calculate percentage. Update a Page State variable from this callback to drive a progress bar widget.

### Can I do real-time audio effects (reverb, EQ) in FlutterFlow?

Real-time audio effects require a native audio engine. You would need to build a Custom Widget using platform channels to access AVAudioEngine on iOS or AudioTrack with OpenSL ES on Android. This is significantly more complex than file-based FFmpeg editing and typically beyond what FlutterFlow's Custom Code system is designed for.

### Is ffmpeg_kit_flutter available for Flutter Web?

No. ffmpeg_kit_flutter is a mobile-only package. For Flutter Web audio processing, you would need to use JavaScript FFmpeg via dart:js interop or send files to a Cloud Function. If your app needs to run on Web, the Cloud Function approach is the most portable.

### How do I add a recording feature so users can record and then edit?

Use the record package (pub.dev/packages/record) in a Custom Action to capture microphone audio to a local .m4a file. After the user stops recording, pass the file path to your trimAudio or mergeAudioFiles Custom Actions. The AudioPlayer widget can then play back the recording for review before the user confirms their edit.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-use-flutterflow-s-built-in-audio-editing-features
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-use-flutterflow-s-built-in-audio-editing-features
