Feature spec
AdvancedCategory
media-content
Build with AI
3–6 days with FlutterFlow + custom Dart wiring
Custom build
4–8 weeks custom dev
Running cost
$0/mo (on-device processing); $0–$25/mo if cloud storage is added
Works on
Everything it takes to ship a Video Clipping and Editing Tool — parts, prompts, and real costs.
Video clipping on mobile needs two things: a timeline UI with draggable in/out handles and an on-device FFmpeg engine (ffmpeg_kit_flutter) to perform the trim. All processing runs on the user's device — $0 compute cost. With FlutterFlow you can wire the timeline UI and video_player in 3–6 days, but the FFmpeg command string requires a Custom Action with hand-written Dart. Export and gallery save work on both iOS and Android with the right permission setup.
What a Video Clipping and Editing Tool Actually Is
A video editing feature lets users trim raw recordings down to the moment that matters, add text captions, and export a polished clip to their gallery or cloud — all without leaving the app. The underlying engine is FFmpeg, the open-source media processing library, running on-device via the ffmpeg_kit_flutter package. Because processing is local, there are no per-minute cloud costs and no video ever leaves the device unless the user explicitly saves to cloud. The product decisions that determine build complexity are: whether you need frame-accurate trimming vs. stream-copy (much faster but limited to keyframe boundaries), whether text overlays are static or animated, and whether the output goes to the device gallery, your cloud storage, or a sharing API.
What users consider table stakes in 2026
- Scrub-bar timeline with draggable left/right handles for setting clip in and out points
- Thumbnail strip showing video frames at regular intervals so users can see what they're trimming
- Playback that loops within the selected range so the user previews the exact clip
- Text overlay support with font picker, size slider, and position drag
- Export progress indicator with a cancel button for long exports
- Save to device gallery on both iOS and Android with a success confirmation
Anatomy of the Feature
Six components. FlutterFlow handles the video player and scrubber UI well visually; the FFmpeg command string and export pipeline must be written in a Custom Action. That Custom Action is where every broken first build fails.
Media picker
UIimage_picker (^1.1.2) for selecting video from the device gallery. permission_handler package manages the Photos/MediaLibrary permission request on both iOS and Android. file_picker provides a document picker fallback on iOS when image_picker cannot access certain library locations.
Note: On iOS, request permission_handler Permission.photos before calling image_picker — the picker silently returns null if permission is denied without showing a system dialog after the first refusal.
Video player and scrubber
UIvideo_player Flutter package (^2.9.0) for playback. A custom Flutter widget wraps a RangeSlider mapped to start_time and end_time in seconds. The player's seekTo() is called on range change to keep playback in sync with the selected range. Loop playback is achieved by listening to the position stream and seekTo(startTime) when position exceeds endTime.
Note: Always call controller.initialize() in initState() and await it before calling play(). Without initialization, the first play call shows a black frame with an audio pop. Show a shimmer loading placeholder until initialized.
Timeline thumbnail strip
UIVideoThumbnail package extracts JPEG frames at configurable intervals (e.g. one frame every 500 ms of video). Thumbnails are displayed in a horizontal ListView with fixed-width cells aligned to the RangeSlider below. Each thumbnail gives the user a visual reference for what content is in each section of the video.
Note: Thumbnail extraction is synchronous and can take 2–5 seconds for a 60-second video — run it in a compute isolate or show a shimmer strip until complete.
Text overlay layer
UIFlutter Stack widget with Positioned draggable Text widgets. An 'Add Text' button opens a bottom sheet with a TextField, font size slider (from the google_fonts package), and color picker (flutter_colorpicker). Each text overlay stores its text, position (dx, dy as fractions of video dimensions), font size, and color — passed to FFmpeg as drawtext filter parameters during export.
Note: Store text overlay positions as percentages of video width/height, not absolute pixels — the export canvas may differ in resolution from the preview size.
Frame extraction engine
Backendffmpeg_kit_flutter (^6.0.3) runs FFmpeg commands within the Flutter app sandbox. For stream-copy trim (fastest, no re-encode): FFmpegKit.executeAsync('-ss {start} -to {end} -i {input} -c copy {output}'). For text overlays or re-encoding to constrain file size: add -vf drawtext=... and -crf 28 -vf scale=1280:-2 flags.
Note: ffmpeg_kit_flutter requires a Custom Action block in FlutterFlow — it cannot be wired visually. The absolute path to the input and output files must be constructed dynamically using getTemporaryDirectory() from path_provider.
Export pipeline
BackendAfter FFmpeg completes, image_gallery_saver copies the output file from the app's temporary directory to the device's photo library. The export result callback from FFmpegKit provides the return code (RC); RC 0 means success. A SnackBar confirms success or failure. Optionally, the exported file is uploaded to Supabase Storage for cloud backup.
Note: On iOS, the MediaLibrary permission (NSPhotoLibraryAddUsageDescription in Info.plist) is required for image_gallery_saver to write to the gallery. If missing, the export finishes with RC 0 but no file appears — the most common silent failure in this feature.
The data model
One table stores video project metadata so users can resume unfinished edits. Run this in the Supabase SQL editor:
1create table public.video_projects (2 id uuid primary key default gen_random_uuid(),3 user_id uuid references auth.users(id) on delete cascade not null,4 original_asset_url text,5 output_asset_url text,6 clips jsonb not null default '[]',7 text_overlays jsonb not null default '[]',8 status text not null default 'draft'9 check (status in ('draft', 'exporting', 'exported', 'error')),10 created_at timestamptz not null default now()11);1213alter table public.video_projects enable row level security;1415create policy "Users can manage own video projects"16 on public.video_projects for all17 using (auth.uid() = user_id)18 with check (auth.uid() = user_id);1920create index video_projects_user_recent_idx21 on public.video_projects (user_id, created_at desc);Heads up: The clips jsonb column stores [{start_time, end_time}] for multi-clip projects. text_overlays stores [{text, dx, dy, font_size, color}]. The status enum enables a resume-later flow where users return to find their draft edits intact.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
Full native Flutter with ffmpeg_kit_flutter gives frame-accurate edits, hardware-accelerated export on supported devices, full codec control, and background export via WorkManager on Android. Choose this path when video quality or export speed is a product differentiator.
Step by step
- 1Native Flutter project with ffmpeg_kit_flutter, custom timeline widget using RenderObject for pixel-accurate scrubbing, and thumbnail extraction in compute isolates
- 2Hardware-accelerated encode using h264_videotoolbox on iOS and h264_mediacodec on Android for 3–5x faster export
- 3Background export with WorkManager (Android) and BGTaskScheduler (iOS) so users can close the app during long exports
- 4Multi-clip merge using FFmpeg concat filter with cross-dissolve transitions
- 5Integration with proprietary video CDN (Mux, Cloudflare Stream) for streaming the exported clip
Where this path bites
- 4–8 week timeline requires engineers with FFmpeg command expertise and native iOS/Android deployment experience
- Hardware codec acceleration varies by device — test on both flagship and mid-range Android devices
Third-party services you'll need
Video processing happens entirely on-device — the only optional cloud cost is backup storage:
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| ffmpeg_kit_flutter | On-device video trimming, compositing, and encoding engine | Open-source, free, runs on-device (no API cost) | Free |
| Firebase Storage | Optional cloud backup of raw and edited video files | 5 GB free | Blaze pay-as-you-go approx $0.026/GB (approx) |
| image_gallery_saver | Saves exported video to device gallery on iOS and Android | Open-source, free | Free |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
All processing runs on-device via ffmpeg_kit_flutter. No cloud required for basic clip/trim. Supabase or Firebase are optional and within free tiers at this scale.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
Export silently succeeds but no file appears in iOS gallery
Symptom: On iOS, image_gallery_saver requires the NSPhotoLibraryAddUsageDescription permission key in Info.plist. Without it, the export callback from ffmpeg_kit_flutter returns RC 0 (success) — FFmpeg completed without errors — but image_gallery_saver cannot write to the gallery and silently discards the file. The user sees a 'Saved!' SnackBar but finds nothing in Photos.
Fix: Add NSPhotoLibraryAddUsageDescription to your app's Info.plist with a meaningful description ('Used to save your edited videos to Photos'). In FlutterFlow, add this under Project Settings → iOS Info.plist. Also call Permission.photos.request() via permission_handler before invoking image_gallery_saver and check the result — show an error if permission is denied.
Video player shows black frame on first load
Symptom: Initializing video_player without awaiting controller.initialize() causes the first call to play() to fail silently: the video element renders black for 1–3 seconds, and if the user taps play again quickly, a duplicate controller is created. On slower Android devices this compounds into an audio-video sync issue that persists for the session.
Fix: Call controller = VideoPlayerController.file(file) in initState(), immediately follow with await controller.initialize(), and only then call setState() to render the video widget and call controller.play(). Display a shimmer loading placeholder bound to a _isInitialized boolean while the await resolves.
RangeSlider seek causes stuttering during drag
Symptom: Calling videoPlayerController.seekTo(Duration(seconds: value)) inside onChanged fires on every pointer event during drag — potentially hundreds of calls per second. The VideoPlayerController cannot handle rapid seek requests and stalls, causing visible stuttering and making the scrub experience feel broken.
Fix: Debounce seek calls with a 100 ms Timer: cancel any pending timer on each onChanged event, start a new 100 ms timer that performs the seek. Only call seekTo on onChangeEnd for the actual playback position update. This keeps the RangeSlider position responsive while limiting seek calls to one per drag gesture.
FlutterFlow Custom Action uses hardcoded file paths
Symptom: FlutterFlow's code generation creates Custom Actions with hardcoded path strings like '/var/mobile/Containers/...' that were valid on the development device but break on any other device or OS version update. The FFmpeg command fails with 'No such file or directory' because the input file path no longer exists.
Fix: Inside the Custom Action, always call getTemporaryDirectory() from path_provider to get the platform-correct temporary directory path, then construct the absolute file path dynamically: final dir = await getTemporaryDirectory(); final outputPath = '${dir.path}/clip_${DateTime.now().millisecondsSinceEpoch}.mp4';. Never hardcode any path segment.
Export produces multi-GB files from 4K source videos
Symptom: ffmpeg_kit_flutter's stream-copy trim (-c copy flag) copies the original codec without re-encoding — perfect for speed, but it preserves the original bitrate. A 30-second trim from a 4K iPhone video can produce a 400–800 MB output file, making gallery save very slow and cloud upload impractical.
Fix: Add re-encoding flags to the FFmpeg command: -vf scale=1280:-2 -c:v libx264 -crf 28 -c:a aac. The scale filter resizes to 1280px wide while preserving aspect ratio; -crf 28 produces a visually acceptable quality at ~5–8 Mbps for 1080p, reducing file sizes by 5–10x. Warn users that re-encoding takes longer than stream copy.
Best practices
Always construct FFmpeg output paths using getTemporaryDirectory() from path_provider — hardcoded paths break across devices and OS updates
Debounce videoPlayerController.seekTo() calls during RangeSlider drag to 100 ms — raw onChanged calls cause seek-queue overflow and stuttering
Show a CircularProgressIndicator with a cancel button during export — FFmpeg on mid-range Android can take 30+ seconds for a 60-second clip with re-encoding
Add NSPhotoLibraryAddUsageDescription to Info.plist from day one — missing it is the single most common silent failure in this feature on iOS
Extract thumbnails in a compute isolate or show a shimmer strip immediately — synchronous thumbnail extraction blocks the UI thread for 2–5 seconds on long videos
Constrain export size with -vf scale=1280:-2 -crf 28 by default — users don't need 4K exports from a phone editor and large files slow everything downstream
Clean up temporary files in the app's temp directory after export or upload — ffmpeg output files accumulate quickly and consume device storage
When You Need Custom Development
FlutterFlow's Custom Action covers basic trim-and-export reliably. Certain editing requirements outgrow that pattern:
- Frame-by-frame editing or a multi-track timeline (NLE-style) — these require a custom RenderObject timeline widget and frame-accurate seek that goes beyond what FlutterFlow's UI builder can configure visually
- Real-time filters or color grading LUTs applied during playback — this requires Metal shaders (iOS) or Vulkan/OpenGL ES (Android) that cannot be implemented in a FlutterFlow Custom Action
- AI-powered auto-highlight clipping that analyzes long recordings for the best moments — requires on-device ML Kit or a cloud vision API, plus background processing
- Integration with a proprietary video CDN (Mux, Cloudflare Stream) for streaming the exported clip to other users — needs server-side upload orchestration and signed URL management
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
Can I trim a video without re-encoding it on mobile?
Yes — stream-copy trim with the -c copy FFmpeg flag performs a near-instant cut along keyframe boundaries without re-encoding. The trade-off: the cut is not truly frame-accurate because it snaps to the nearest keyframe, which may be up to a second from where you set the handle. For social clips where a 1-second rounding is acceptable, stream-copy is significantly faster. For precise cuts, add re-encoding flags (-c:v libx264 -crf 28) which process every frame.
What's the maximum video size ffmpeg_kit_flutter can handle?
There is no hard limit imposed by the library — the constraint is available device RAM and storage. ffmpeg_kit_flutter streams frames through memory rather than loading the entire file at once, so even multi-GB 4K videos process successfully. However, re-encoding a 60-minute 4K video can take 20+ minutes on a mid-range phone. Practically, test with the upper bound of what your users would plausibly record — usually 5–15 minutes of 1080p.
How do I add captions or subtitles to a clipped video in Flutter?
Use the FFmpeg drawtext filter: add a -vf "drawtext=text='Caption text':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=h-100" flag to the FFmpeg command string. For multiple captions at different timestamps, stack multiple drawtext filters with enable='between(t,start,end)' timing expressions. This burns the text into the video. For overlay-only captions (not burned in), keep the text as a separate Flutter widget layer and only burn during final export.
Can FlutterFlow handle video editing or do I need custom code?
FlutterFlow handles the UI layer — video player, scrubber, thumbnail strip, text overlay positioning — visually. The FFmpeg command execution requires a Custom Action with hand-written Dart. It's roughly a 50/50 split: half is visual configuration, half is a single Custom Action that contains the FFmpeg logic. If you're comfortable writing 30–40 lines of Dart in a Custom Action block, FlutterFlow is a solid foundation for this feature.
How do I save an edited video to the device gallery on iOS and Android?
Use the image_gallery_saver package after FFmpeg completes. On iOS, add NSPhotoLibraryAddUsageDescription to Info.plist and request Permission.photos via permission_handler before saving. On Android, no additional permissions are needed for API level 29 and above (scoped storage). Call GallerySaver.saveVideo(outputPath) and check the result boolean — show a SnackBar on success and an error dialog on failure.
What permissions does video editing require on iOS vs Android?
On iOS: NSPhotoLibraryUsageDescription (to read videos from Photos for import) and NSPhotoLibraryAddUsageDescription (to save exports back to Photos). Both are required in Info.plist or the App Store will reject the build. On Android 13+: READ_MEDIA_VIDEO is required to read from the gallery; WRITE_EXTERNAL_STORAGE is not needed on API 29+ due to scoped storage. Add both iOS descriptions in FlutterFlow under Project Settings → iOS Info.plist Entries.
How long does it take to export a 1-minute trimmed clip?
Stream-copy trim (-c copy): nearly instant, 1–3 seconds regardless of clip length. Re-encoding to 1080p with -crf 28: 30–90 seconds on a mid-range Android (Snapdragon 680-class) and 15–30 seconds on an iPhone 12 or newer. Enable hardware acceleration if available — h264_videotoolbox on iOS and h264_mediacodec on Android reduce re-encode times by 3–5x. Always show a progress indicator with estimated time remaining during re-encode exports.
Can I merge two video clips together in a Flutter app?
Yes, using ffmpeg_kit_flutter's concat filter. Create a text file listing the input paths (the FFmpeg concat demuxer format), then run: FFmpegKit.executeAsync('-f concat -safe 0 -i {list_file} -c copy {output}'). For transitions between clips, add -filter_complex with xfade expressions, which requires re-encoding. Write the list file to getTemporaryDirectory() using Dart's File API, then reference that path in the FFmpeg command.
Need this feature production-ready?
RapidDev builds a video clipping and editing tool into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.