flutter_video_editor_plugin 1.1.3
flutter_video_editor_plugin: ^1.1.3 copied to clipboard
Native video editor for Flutter apps on iOS and Android: multi-clip timeline, trim, split, text, stickers, filters, effects, transitions and music.
flutter_video_editor_plugin #
Flutter binding for the Infinive video editor SDK: a timeline-based, multi-clip video editor with trim, split, stickers, text, color filters, video effects, transitions and background audio, exporting MP4.
The editing screen is native: VideoEditorCore on iOS and show.infini:videoeditor on Android. This package presents it and returns the result.
No third-party media SDK is involved. Decoding, preview and rendering use Media3 on Android and AVFoundation with Core Image on iOS. The preview and the export are rendered from the same composition.
A license key is required to open the editor. Full documentation: infinive.dev/docs.
Features #
| Area | Included |
|---|---|
| Sources | Multiple videos and photos from the gallery; photos become image clips |
| Timeline | Pinch zoom, scrubbing, trim, split, delete and per-clip volume |
| Overlays | Text with fonts, colors and styles; GIPHY stickers with transparent backgrounds |
| Look | Color filters, video effects and clip transitions |
| Audio | Background music with waveform, section adjustment and volume; optional host audio picker |
| Editing | Undo and redo up to 50 steps; drafts saved on exit and resumed by the host |
| Export | MP4 (H.264 or HEVC) in 9:16, 1:1, 4:5, 3:4 or 16:9, with a cover frame or photo |
| Analytics | Usage data in the result: what went into the video and how the edit went, for the host's own analytics |
| Localization | English and Korean bundled; strings overridable from native resources |
Installation #
dependencies:
flutter_video_editor_plugin: ^1.1.3
The plugin depends on the native SDKs by name: the VideoEditorCore pod on iOS and the show.infini:videoeditor Maven coordinate on Android. The published artifacts are fetched automatically; the plugin adds the Maven repository to the Android build.
Android #
minSdk24 or later.- The editor must be launched from a
FlutterActivity. - No additional permissions. Video selection uses the photo picker, and the
INTERNETpermission used for GIPHY is declared in the SDK manifest.
iOS #
- Podfile:
platform :ios, '16.0'. Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo access is needed to pick videos to edit.</string>
The iOS editor requests photo-library read access immediately before opening the picker. Only full access is accepted. If access is denied or limited to selected photos, the editor shows an explanation with a button that opens Settings, and returns null if no clips are present. Passing a file through initialSource does not involve this permission. On Android the photo picker provides URIs without a permission, so no request is made.
No camera or microphone keys are required.
Usage #
import 'package:flutter_video_editor_plugin/flutter_video_editor_plugin.dart';
final result = await VideoEditor.present(
// When omitted, the editor opens the gallery (videos and photos; a photo becomes a one-second image clip).
// When provided, the editor starts with that local video or photo. A file path, or a content:// URI on Android; network URLs are not accepted.
// On Android, a photo given as a file path must have its extension.
initialSource: pickedPath,
// licenseKey is required.
config: const VideoEditorConfig(licenseKey: 've_live_…', giphyApiKey: '…'),
// Failures after the editor opens. See “Failures”.
onFailure: (failure) => log('editor failed: $failure'),
);
if (result != null) {
print(result.videoPath); // the rendered MP4
print(result.coverPath); // the cover JPEG; an empty string means no cover is available
print(result.durationSec);
print(result.audioPaths); // audio tracks used, in timeline order, as your pickAudio callback supplied them
print(result.draftId); // the draft that holds this edit, or null; see “Drafts”
print(result.usage); // what went into the video and how the edit went; see “Usage data”
}
The native screen is presented over the current screen. present() completes with the result, or null if the user closed the editor.
Both files are temporary files in the application cache; the host moves, uploads or deletes them.
The VideoEditorScreen widget is deprecated. It remains as a wrapper so that hosts that pushed it as a route continue to work, and it will be removed in the next major version.
Configuration #
VideoEditor.present(
config: VideoEditorConfig(
// Required.
licenseKey: 've_live_…',
// Without it, the sticker button is shown disabled.
giphyApiKey: myGiphyKey,
// Text font presets. null selects the bundled fonts.
// Each entry is a font file declared in the pubspec. Give the bold face. See “Fonts”.
textFonts: const [
TextFontSpec(displayName: 'Brand', asset: 'fonts/Brand-Bold.ttf'),
],
// Exportable length in seconds. null means no limit.
// Editing is not restricted. When “Next” is tapped outside the range, the editor informs the user and does not render.
minDurationSeconds: 3,
maxDurationSeconds: 60,
// false hides the add button on the timeline and prevents deleting the last clip.
allowAddClip: true,
// Language of the editor’s strings. Set this only if the application has its own language setting. See “Localization”.
locale: Localizations.localeOf(context).toLanguageTag(),
// Save the editing state on the device without rendering. See “Drafts”.
draftsEnabled: true,
// Show a cover-selection screen between “Next” and the render. See “Cover picker”.
coverPickerEnabled: true,
),
// Output encoding. Only the fields that are set are overridden; the remaining defaults are
// 9:16 (1080×1920), a 7 Mbps cap, a 30 fps cap and H.264.
// The aspect ratio is one of 9:16, 1:1, 4:5, 3:4 and 16:9.
output: const RenderOutputConfig(aspectRatio: RenderAspectRatio.ratio1x1, bitrate: 5000000),
);
RenderAspectRatio |
Output size |
|---|---|
ratio9x16 (default) |
1080×1920 |
ratio1x1 |
1080×1080 |
ratio4x5 |
1080×1350 |
ratio3x4 |
1080×1440 |
ratio16x9 |
1920×1080 |
Sources are scaled to fit without cropping, with black bars. A source below the frame-rate cap keeps its frame rate. Audio is AAC stereo, 48 kHz, 256 kbps.
Audio picker #
The editor’s audio picker is the system file picker. When pickAudio is provided, it is called instead whenever the user adds or replaces audio, and the returned local file path (or content:// URI on Android) is imported. Returning null cancels. The system picker is used only when pickAudio is not provided.
await VideoEditor.present(
config: …,
pickAudio: () async => await mySoundLibrary.pick(), // a local file path, or null
);
The callback runs while the native editor is in front, so it cannot show Flutter widgets. It must produce a path without a Flutter screen, for example from files already on the device or a track downloaded on demand.
Fonts #
Declare the font file in the pubspec as usual and pass its asset path:
flutter:
fonts:
- family: Brand
fonts:
- asset: fonts/Brand-Bold.ttf
TextFontSpec(displayName: 'Brand', asset: 'fonts/Brand-Bold.ttf')
No UIAppFonts entry and no copy into the native assets/ directory is needed.
Give the bold face. Overlay text is rendered in a single semi-bold weight, and synthetic bold is not applied. A file that is missing or is not a font is omitted from the list; no system font is substituted. The asset path is also the font’s identity in saved drafts: if it changes later, text that used it is rendered in the first font of the list.
Drafts #
With draftsEnabled: true, leaving the editing screen saves the editing state: closing the editor, navigating back or moving the application to the background saves without prompting. A render does not delete the draft: the rendered state is saved and its id is returned as VideoEditorResult.draftId. The host deletes drafts. Nothing is written when nothing has changed or when there are no clips; opening a video and leaving without editing does not create a draft. The undo history is not saved.
// List, most recently saved first. The list screen is the host’s.
final drafts = await VideoEditor.drafts();
for (final d in drafts) {
print('${d.id} ${d.clipCount} clips ${d.durationSec}s ${d.updatedAt} ${d.coverPath}');
}
// Resume. draftsEnabled must be true, and draftId cannot be combined with initialSource.
final result = await VideoEditor.present(
draftId: drafts.first.id,
config: const VideoEditorConfig(licenseKey: 've_live_…', draftsEnabled: true),
onFailure: (failure) => log('$failure'), // if the draft cannot be opened: sourceUnreadable, then null
);
// Delete. This is the only way to discard a draft, including one that was rendered (result.draftId).
await VideoEditor.deleteDraft(drafts.first.id);
await VideoEditor.deleteAllDrafts();
- A save is returned from
present()asnull, the same as a cancellation. The host learns of the draft by callingdrafts()again. coverPathis a first-frame JPEG and is empty when no cover exists (immediately after a save it may not have been extracted yet).- If a draft cannot be opened (a source is missing or a file is corrupt),
onFailurereceivessourceUnreadableand the editor closes. The draft remains in the list. - Videos are referenced, not copied: files passed through
initialSource, Android gallery URIs and iOS photo-library items. If the original is deleted, or the permission is revoked on iOS, the draft cannot be opened. - Listing and deleting work regardless of
draftsEnabled.
Cover picker #
With coverPickerEnabled: true, a cover screen follows “Next”. The upper part is the editing screen’s preview (the same composition, with filters, text, stickers and transitions) paused at the chosen time; below it is a strip of thumbnails from the start to the end of the video. Slide the white frame to choose a time, or tap “Choose from gallery” to pick a photo. The duration limit is checked at the first “Next”; when the length is out of range, the cover screen is not shown.
- A chosen time yields that frame of the rendered video; a photo is center-cropped to the output aspect ratio. The cover is a JPEG, 480 px on the short side. With no selection, the cover is the first frame.
- Back returns to the editing screen. Cancelling or failing a render returns to the cover screen with the selection kept.
- The selection is not saved in drafts.
- Strings:
videoeditor_cover_titleandvideoeditor_cover_choose_from_gallery.
Usage data #
VideoEditorResult.usage is a VideoEditorUsage. Counts describe what is in the rendered video; items added and removed during editing are not counted. The values are for the host's own analytics, and the SDK does not send them anywhere.
| Field | Meaning |
|---|---|
videoClipCount, imageClipCount |
Video and image clips on the timeline. Each piece of a split clip counts. |
distinctSourceCount |
Sources the clips came from. Clips with the same path count once. |
sourceDurationSec |
Total length of the distinct video sources, in seconds. Images are excluded. |
adjustedVolumeClipCount, mutedClipCount |
Video clips whose volume was changed (muted ones included), and muted video clips. |
audioCount, adjustedAudioCount |
Audio tracks, and audio tracks whose used range within the song was moved. |
stickerCount |
Stickers. |
texts |
count, plus fonts, styles and colors mapping a font displayName, a style id and a #AARRGGBB color to a count. Bundled fonts have the same name on both platforms. |
filters, effects, transitions |
Filter preset id, effect id (flip_h, split_3, quad, vhs, glitch) and transition preset id to a count. |
cover |
CoverUsage with source (CoverSource.frame or CoverSource.photo) and moved (a frame other than the first). null when coverPickerEnabled is off. |
editingSec |
Seconds on the editing screen, excluding the license check, the cover screen, rendering and time in the background. |
renderSec |
Seconds taken by the render that produced this result. |
renderAttemptCount |
Renders started in this session, this one included. Failed and cancelled renders count. |
fullscreenOpenCount |
Times the fullscreen preview was opened. |
undoCount, redoCount |
Undo and redo actions. Taps with nothing to undo or redo are not counted. |
Failures #
Only failures to present the screen are thrown as a PlatformException.
| Code | Meaning |
|---|---|
editor_already_open |
The editor is already open. Only one instance is allowed. |
no_activity, no_view_controller |
There is no screen to attach to. |
bad_arguments |
The configuration or output did not pass the SDK’s validation (an empty licenseKey, a minimum greater than the maximum, or an unknown codec). |
launch_failed |
Android: the activity could not be started. |
Failures after the editor is open are delivered through onFailure. The editor informs the user. Cancellations are not reported here.
reason |
When | Afterwards |
|---|---|---|
sourceUnreadable |
A picked source or a draft could not be opened | If no clips remain, the editor closes and the future completes with null |
renderFailed |
The render failed (nativeCode is the platform’s original code) |
The editing screen returns and “Next” can be tapped again |
coverUnavailable |
The render succeeded but no cover could be extracted | The result follows with an empty coverPath |
licenseInvalid |
No valid license | On open: the editor closes and the future completes with null. At render start: the editing screen remains |
The meaning of nativeCode differs per platform (iOS NSError.code; Android Media3 ExportException.errorCode). Record the platform with it when aggregating.
Localization #
The editor’s strings are resources of the native SDKs. Korean and English are bundled, and English is used when the application language is neither.
Language
The default is the application language as seen by the operating system. Applications with an in-app language setting (MaterialApp(locale:)) pass it through VideoEditorConfig.locale, typically as Localizations.localeOf(context).toLanguageTag(). For iOS to follow the device language, the application’s Info.plist must declare CFBundleLocalizations, as described in the iOS section of Flutter’s internationalization guide.
Changing strings
Define the same key in the application’s native resources, in the same place permission strings are localized. The application’s value takes precedence.
<!-- 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 are identical on both platforms and begin with
videoeditor_. The lists are in the SDKs’res/values/public.xmlandResources/en.lproj/Localizable.strings. - The two strings that include numbers (
videoeditor_duration_too_shortandvideoeditor_duration_too_long) are Android<plurals>and iOS.stringsdictentries. - The names of transitions, filters and bundled fonts are in the same table (
videoeditor_transition_*,videoeditor_filter_*,videoeditor_font_*). ThedisplayNameof a host-supplied font is shown as given. - A change of
localetakes effect the next time the editor is opened.
Public API #
| Name | Description |
|---|---|
VideoEditor.present() |
Presents the editor and returns the result. Failures are delivered through onFailure; draftId resumes a draft; pickAudio supplies a host audio picker. |
VideoEditor.drafts(), VideoEditor.deleteDraft(), VideoEditor.deleteAllDrafts() |
List and delete drafts. |
VideoEditorResult |
The result: videoPath, coverPath, durationSec, audioPaths, usage, draftId. |
VideoEditorUsage |
Usage data: counts of what went into the video (TextUsage, CoverUsage) and editing and render times. |
VideoEditorDraft |
A draft entry: id, createdAt, updatedAt, durationSec, clipCount, coverPath. |
VideoEditorFailure, VideoEditorFailureReason |
Why the editor produced no result: reason, nativeCode, detail. |
VideoEditorConfig |
Configuration: license key (required), GIPHY key, text fonts, duration limits, add-clip control, locale, drafts and the cover picker. |
RenderOutputConfig, RenderAspectRatio, RenderVideoCodec |
Output aspect ratio, bitrate, frame-rate cap and codec. |
TextFontSpec |
A text font preset: display name and pubspec asset path. |
VideoEditorScreen |
Deprecated widget wrapper around present(). |
Dependencies #
flutter only. Everything else is contained in the native SDKs.
License #
Commercial. Use requires a license key issued by Infinishow. See the LICENSE file and infinive.dev.