Skip to main content

flutter_video_editor_plugin 1.2.5 copy "flutter_video_editor_plugin: ^1.2.5" to clipboard
flutter_video_editor_plugin: ^1.2.5 copied to clipboard

Native timeline video editor for Flutter apps on iOS and Android. Presents the editor and returns the rendered MP4.

flutter_video_editor_plugin #

Timeline video editor for Flutter apps, backed by the native Infinive SDKs for iOS and Android. VideoEditor.present() opens the native editor and returns the rendered MP4.

A license key is required. Get a trial key for debug builds. Full documentation: infinive.dev/docs.

Timeline with clips, audio, text and filters Clip transitions Text overlay styles

Features #

Area Included
Sources Videos and photos from the gallery; photos become image clips
Timeline Pinch zoom, scrubbing, trim, split, delete, per-clip volume
Overlays Text (fonts, colors, styles); GIPHY stickers
Look Color filters, video effects, clip transitions
Audio Background music with waveform, section adjustment, volume; optional host audio picker
Editing Undo and redo up to 50 steps; drafts saved on exit
Export MP4 (H.264 or HEVC) in 9:16, 1:1, 4:5, 3:4 or 16:9; cover frame or photo
Analytics Usage stats in the result, for your own analytics
Localization English and Korean; strings overridable from native resources

Install #

dependencies:
  flutter_video_editor_plugin: ^1.2.5

The native SDKs are resolved by name (VideoEditorCore pod, show.infini:videoeditor Maven artifact). The plugin adds the Maven repository to the Android build.

Android #

  • minSdk 24.
  • Launch the editor from a FlutterActivity.
  • No manifest changes.

iOS #

  • platform :ios, '16.0' in the Podfile.
  • Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo access is needed to pick videos to edit.</string>

The editor asks for full photo-library access right before it opens the picker. If the user denies or limits access, it shows a Settings button; with no clips, present() returns null. initialSource doesn't need the permission.

Present the editor #

import 'package:flutter_video_editor_plugin/flutter_video_editor_plugin.dart';

final result = await VideoEditor.present(
  initialSource: pickedPath,   // optional; omit to open the gallery
  config: const VideoEditorConfig(licenseKey: 've_live_…', giphyApiKey: '…'),
  onFailure: (failure) => log('editor failed: $failure'),
);
if (result != null) {
  upload(result.videoPath);
}

null means the user closed the editor. The editor is a native screen over the current one, not a Flutter route.

initialSource is a local file path, or a content:// URI on Android. A photo becomes a one-second image clip; on Android, a photo path needs its file extension.

Result #

Field Value
videoPath Rendered MP4 in the app cache. Move, upload or delete it.
coverPath Cover JPEG in the app cache. Empty if no cover could be extracted (coverUnavailable arrives first).
durationSec Duration in seconds.
audioPaths Audio tracks used, in timeline order, as pickAudio or the system picker returned them.
draftId Draft holding this edit; null if drafts are off or nothing was saved. See "Resume drafts".
usage Usage stats. See "Read usage data".

VideoEditorScreen, the route wrapper, is deprecated and will be removed in the next major version.

Configure #

VideoEditor.present(
  config: VideoEditorConfig(
    licenseKey: 've_live_…',
    giphyApiKey: myGiphyKey,
    textFonts: const [TextFontSpec(displayName: 'Brand', asset: 'fonts/Brand-Bold.ttf')],
    minDurationSeconds: 3,
    maxDurationSeconds: 60,
    locale: Localizations.localeOf(context).toLanguageTag(),
    draftsEnabled: true,
    coverPickerEnabled: true,
  ),
  output: const RenderOutputConfig(aspectRatio: RenderAspectRatio.ratio1x1, bitrate: 5000000),
);
VideoEditorConfig
licenseKey Required.
giphyApiKey Sticker tool is disabled without it.
textFonts Replaces the bundled fonts. null keeps them. See "Add fonts".
minDurationSeconds, maxDurationSeconds Exportable length. Checked on "Next"; editing isn't restricted. null for no limit.
allowAddClip false hides the add button and keeps the last clip. Default true.
locale BCP 47 tag. Only needed if your app has its own language setting.
draftsEnabled See "Resume drafts". Default false.
coverPickerEnabled See "Let the user pick a cover". Default false.
detachedRenderEnabled See "Show render progress in your own UI". Default false.

Invalid values (empty licenseKey, minimum above maximum, unknown codec) are rejected with a bad_arguments PlatformException.

Output #

RenderOutputConfig overrides only the fields you set. Defaults: 9:16, 7 Mbps cap, 30 fps cap, H.264.

RenderAspectRatio Size
ratio9x16 (default) 1080×1920
ratio1x1 1080×1080
ratio4x5 1080×1350
ratio3x4 1080×1440
ratio16x9 1920×1080

Sources are fitted with black bars. Sources below the frame-rate cap keep their rate. Audio: AAC stereo, 48 kHz, 256 kbps.

Supply an audio picker #

await VideoEditor.present(
  config: …,
  pickAudio: () async => await mySoundLibrary.pick(),
);

Return a local file path (or a content:// URI on Android), or null to cancel. Without pickAudio, the system file picker opens.

Note: the callback runs while the native editor is on screen, so it can't show Flutter widgets. Pick from files already on the device or download the track on demand.

Add fonts #

flutter:
  fonts:
    - family: Brand
      fonts:
        - asset: fonts/Brand-Bold.ttf
TextFontSpec(displayName: 'Brand', asset: 'fonts/Brand-Bold.ttf')
  • Give the bold face. Overlay text uses one semi-bold weight.
  • The asset path identifies the font in drafts. Rename it and existing text falls back to the first font.
  • Files that are missing or aren't fonts are dropped from the list.
  • No UIAppFonts entry or native asset copy needed.

Resume drafts #

With draftsEnabled: true, the editor saves when the user leaves: close, back, or the app going to the background. A render keeps the draft and returns its id in result.draftId.

final drafts = await VideoEditor.drafts();   // newest first

final result = await VideoEditor.present(
  draftId: drafts.first.id,
  config: const VideoEditorConfig(licenseKey: 've_live_…', draftsEnabled: true),
  onFailure: (failure) => log('$failure'),
);

await VideoEditor.deleteDraft(drafts.first.id);
await VideoEditor.deleteAllDrafts();
  • A save returns null from present(), like a cancel. Call drafts() again to see it.
  • Unchanged edits and empty timelines aren't saved. Undo history isn't saved.
  • draftId requires draftsEnabled and can't be combined with initialSource.
  • VideoEditorDraft: id, createdAt, updatedAt, durationSec, clipCount, coverPath (first-frame JPEG; empty until extracted).
  • Videos are referenced, not copied. If a source is gone, or iOS photo access is revoked, opening fails with sourceUnreadable and the draft stays in the list.
  • Drafts are deleted only through deleteDraft and deleteAllDrafts, which work with draftsEnabled off too.

Let the user pick a cover #

With coverPickerEnabled: true, "Next" opens a cover screen: the paused preview on top, a thumbnail strip of the whole video below. The user slides the frame to a time or picks a gallery photo.

  • A time gives that frame of the rendered video. A photo is center-cropped to the output aspect ratio. Either way: JPEG, 480 px on the short side.
  • With no selection, the cover is the first frame.
  • Back returns to editing. A cancelled or failed render returns to the cover screen.
  • Duration limits are checked before the cover screen. The selection isn't saved in drafts.
  • Strings: videoeditor_cover_title, videoeditor_cover_choose_from_gallery.

Show render progress in your own UI #

Set detachedRenderEnabled: true to draw render progress in your own UI. The editor closes when the render starts, and the render continues as a VideoEditorRenderJob that reports progress, the result and failures.

await VideoEditor.present(
  config: const VideoEditorConfig(
    licenseKey: 've_live_…',
    draftsEnabled: true,
    detachedRenderEnabled: true,
  ),
  onRenderStarted: (job) {
    uploads.track(job);
    Navigator.of(context).pushReplacementNamed('/feed');
  },
);
void track(VideoEditorRenderJob job) {
  job.progress.listen((value) => banner.value = value);
  job.outcome.then((outcome) {
    switch (outcome) {
      case RenderCompleted(:final result):
        upload(result.videoPath, result.coverPath);
      case RenderFailed():
        showRetry(job.draftId);
      case RenderCancelled():
        break;
    }
  });
}
VideoEditorRenderJob
draftId Draft holding the rendered state. null when draftsEnabled is off.
progress, latestProgress 0 to 1.
outcome Completes once with RenderCompleted(result), RenderFailed(failure) or RenderCancelled(). An empty coverPath means no cover could be extracted.
cancel() Stops the render.
  • onRenderStarted is called before present() completes; present() then returns null.
  • VideoEditor.currentRender is the running job, null once it ends.
  • The only failure is renderFailed, delivered through outcome.
  • One job runs at a time. Tapping "Next" in any editor meanwhile keeps the user on the editing screen and shows videoeditor_render_busy.
  • To render again, open the editor with draftId.

Read usage data #

result.usage counts what's in the rendered video. Items removed during editing aren't counted.

Field Value
videoClipCount, imageClipCount Clips on the timeline; each piece of a split counts
distinctSourceCount Distinct source paths
sourceDurationSec Total length of distinct video sources; images excluded
adjustedVolumeClipCount, mutedClipCount Video clips with changed volume (muted included); muted video clips
audioCount, adjustedAudioCount Audio tracks; tracks whose section within the song was moved
stickerCount Stickers
texts count, plus fonts, styles, colors maps (font displayName, style id, #AARRGGBB → count)
filters, effects, transitions Preset id → count
cover CoverUsage: source (CoverSource.frame or .photo), moved (a frame other than the first); null when the cover picker is off
editingSec Time on the editing screen; excludes license check, cover screen, render, background
renderSec Duration of the render that produced this result
renderAttemptCount Renders started in this session, this one included; failed and cancelled count
fullscreenOpenCount Fullscreen preview opens
undoCount, redoCount Undo and redo actions that changed something

Handle failures #

present() throws PlatformException only when the editor can't open:

Code Cause
editor_already_open Only one editor at a time
no_activity, no_view_controller No screen to attach to
bad_arguments Invalid configuration or output
launch_failed Android: activity couldn't start

Once open, failures go to onFailure and the editor shows its own message:

reason When Then
sourceUnreadable A source or draft couldn't be opened With no clips left, the editor closes and present() returns null
renderFailed Render failed Back to editing; "Next" can be tapped again
coverUnavailable Render succeeded, cover extraction failed Result follows with an empty coverPath
licenseInvalid No valid license On open: closes, returns null. On render: stays on editing

nativeCode is platform-specific (iOS NSError.code, Android Media3 ExportException.errorCode). Store the platform with it.

Localize #

Strings are native SDK resources in English and Korean. Other languages fall back to English.

Language #

The editor follows the app language. If your app has its own language setting (MaterialApp(locale:)), pass it as VideoEditorConfig.locale. On iOS, declare CFBundleLocalizations in Info.plist so the device language is picked up. A change applies the next time the editor opens.

Override strings #

Define the same key in your native resources. Your value wins.

<!-- android/app/src/main/res/values/strings.xml (values-ko/, values-ja/, …) -->
<string name="videoeditor_next">Continue</string>
/* ios/Runner/en.lproj/Localizable.strings (ko.lproj, ja.lproj, …) */
"videoeditor_next" = "Continue";
  • Keys start with videoeditor_ and match across platforms. Full lists: res/values/public.xml (Android SDK), Resources/en.lproj/Localizable.strings (iOS SDK).
  • videoeditor_duration_too_short and videoeditor_duration_too_long are Android <plurals> and iOS .stringsdict entries.
  • Transition, filter and bundled font names use videoeditor_transition_*, videoeditor_filter_*, videoeditor_font_*. Host font names are shown as given.

API #

Name
VideoEditor.present() Opens the editor; returns VideoEditorResult?
VideoEditor.drafts(), deleteDraft(), deleteAllDrafts() Draft list and deletion
VideoEditor.currentRender The running render job
VideoEditorRenderJob, VideoEditorRenderOutcome Render progress and outcome for your own UI
VideoEditorResult videoPath, coverPath, durationSec, audioPaths, usage, draftId
VideoEditorUsage, TextUsage, CoverUsage, CoverSource Usage stats
VideoEditorDraft Draft list entry
VideoEditorFailure, VideoEditorFailureReason reason, nativeCode, detail
VideoEditorConfig Editor configuration
RenderOutputConfig, RenderAspectRatio, RenderVideoCodec Output encoding
TextFontSpec Display name and pubspec asset path
VideoEditorScreen Deprecated route wrapper

Dependencies #

flutter only.

License #

Commercial; requires a license key from Infinishow. See LICENSE and infinive.dev.

8
likes
140
points
1.66k
downloads
screenshot

Documentation

Documentation
API reference

Publisher

verified publisherinfinive.dev

Weekly Downloads

Native timeline video editor for Flutter apps on iOS and Android. Presents the editor and returns the rendered MP4.

Homepage
View/report issues

Topics

#video #video-editor #video-editing #video-processing #video-overlay

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flutter_video_editor_plugin

Packages that implement flutter_video_editor_plugin