Android
show.infini:videoeditor is the Android SDK, a single AAR built on Media3 and Compose.
Requirements
Section titled “Requirements”minSdk24,compileSdk36, AGP 8.x.- Kotlin 2.1+ (the SDK is compiled with Kotlin 2.2.20). Java-only hosts work too.
- Compose runtime and Media3 1.10 come in as transitive dependencies. The host doesn’t need to use Compose. If the host also uses Media3, Gradle picks the higher version.
Install
Section titled “Install”repositories { maven("https://dl.infinive.dev/maven")}dependencies { implementation("show.infini:videoeditor:1.2.4")}No manifest changes. The editor activity and the INTERNET permission (GIPHY) are merged from the SDK manifest; video selection uses the photo picker.
Launch the editor
Section titled “Launch the editor”import show.infini.videoeditor.VideoEditorActivityimport show.infini.videoeditor.editor.EditorConfig
val editor = registerForActivityResult(VideoEditorActivity.Contract) { result -> if (result != null) upload(result.videoPath) // null: cancelled}
editor.launch( VideoEditorActivity.intent( context = this, initialSource = pickedUri, // optional; omit to open the picker config = EditorConfig(licenseKey = "ve_live_…", giphyApiKey = "…"), onFailure = { failure -> Log.w(TAG, "editor failed: $failure") }, ),)The activity finishes itself. With startActivityForResult, unpack the result with VideoEditorActivity.result(resultCode, data).
initialSource is a content:// URI or a file path to a local video or photo. A photo becomes a one-second image clip; a photo path needs its file extension.
Result: EditorResult
Section titled “Result: EditorResult”| Field | Value |
|---|---|
videoPath |
Rendered MP4 in the app cache. Move, upload or delete it. |
coverPath |
Cover JPEG. Empty if no cover could be extracted (COVER_UNAVAILABLE arrives first). |
durationSec |
Duration in seconds. |
audioPaths |
Audio URIs or paths used, in timeline order, as audioPicker or the system picker returned them. |
draftId |
Draft holding this edit; null if drafts are off or nothing was saved. |
usage |
Usage stats. See “Read usage data”. |
Configure
Section titled “Configure”EditorConfig |
|
|---|---|
licenseKey |
Required. |
giphyApiKey |
Sticker tool is disabled without it. |
textFonts |
Replaces the bundled fonts. Omit to keep them. Empty list rejected. |
minDurationSeconds, maxDurationSeconds |
Exportable length. Checked on “Next”; editing isn’t restricted. |
allowAddClip |
false hides the add button and keeps the last clip. |
locale |
BCP 47 tag. null follows the app language, including Android 13 per-app language. |
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 throw IllegalArgumentException at construction: empty licenseKey, minimum above maximum, empty font list, unknown OutputSpec.codec, initialSource with draftId, draftId without draftsEnabled.
Output
Section titled “Output”Pass output = OutputSpec(…) to intent(). null uses OutputSpec.DEFAULT: 9:16, 7 Mbps cap, 30 fps cap, H.264. codec is CODEC_H264 or CODEC_HEVC.
OutputSpec.AspectRatio |
Size |
|---|---|
RATIO_9_16 (default) |
1080×1920 |
RATIO_1_1 |
1080×1080 |
RATIO_4_5 |
1080×1350 |
RATIO_3_4 |
1080×1440 |
RATIO_16_9 |
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
Section titled “Supply an audio picker”VideoEditorActivity.intent( context = this, config = …, audioPicker = Intent(this, MySoundLibraryActivity::class.java),)
// In MySoundLibraryActivitysetResult(RESULT_OK, Intent().setData(uri).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION))finish()The editor starts audioPicker for a result when the user adds or replaces audio. Return a content:// or file:// URI as data; RESULT_CANCELED or no data cancels. URIs from your own ContentProvider need FLAG_GRANT_READ_URI_PERMISSION. Without audioPicker, the system document picker opens.
Add fonts
Section titled “Add fonts”TextFontSpec(displayName = "Brand", asset = "fonts/Brand-Bold.ttf")assetis a path in your app’sassets/, as accepted byAssetManager.open.- Give the bold face. Overlay text uses one semi-bold weight; synthetic bold isn’t applied.
- The path identifies the font in drafts. Change it and existing text falls back to the first font.
Resume drafts
Section titled “Resume drafts”With draftsEnabled = true, the editor saves when the user leaves: close, back, or onStop. A render keeps the draft and returns its id in result.draftId.
import show.infini.videoeditor.editor.EditorDrafts
val drafts = EditorDrafts.list(context) // suspend; newest first
editor.launch( VideoEditorActivity.intent( context = this, draftId = drafts[0].id, config = EditorConfig(licenseKey = "ve_live_…", draftsEnabled = true), ),)
EditorDrafts.delete(context, drafts[0].id)EditorDrafts.deleteAll(context)- A save arrives as
RESULT_CANCELED. List again to see it. - Unchanged edits and empty timelines aren’t saved. Undo history isn’t saved.
EditorDraft:id,createdAt,updatedAt(epoch ms),durationSec,clipCount,coverPath(first-frame JPEG; empty until extracted).- Videos and audio are referenced, not copied. If a source is gone, opening fails with
SOURCE_UNREADABLEfollowed byRESULT_CANCELED, and the draft stays in the list. - Drafts are deleted only through
deleteanddeleteAll, which work withdraftsEnabledoff too. All draft calls are suspending and safe from any thread.
Let the user pick a cover
Section titled “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
Section titled “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 an EditorRenderJob that reports progress, the result and failures. Launch with RenderContract.
val editor = registerForActivityResult(VideoEditorActivity.RenderContract) { job -> if (job != null) uploadBanner.show(job.progress) // null: cancelled}editor.launch( VideoEditorActivity.intent( this, config = EditorConfig(licenseKey = "ve_live_…", draftsEnabled = true, detachedRenderEnabled = true), ),)appScope.launch { EditorRenderJob.current.filterNotNull().collect { job -> when (val end = job.state.first { it != EditorRenderState.Rendering }) { is EditorRenderState.Completed -> upload(end.result.videoPath, end.result.coverPath) is EditorRenderState.Failed -> showRetry(job.draftId) else -> Unit } }}Note: collect the outcome in an application-lifetime scope.
EditorRenderJob |
|
|---|---|
current |
The running job (StateFlow). null once it ends. |
draftId |
Draft holding the rendered state. null when draftsEnabled is off. |
progress |
StateFlow<Double>, 0 to 1. |
state |
Changes once from Rendering to Completed(result), Failed(failure) or Cancelled. An empty coverPath means no cover could be extracted. |
cancel() |
Stops the render. Call on the main thread. |
- The only failure is
RENDER_FAILED, delivered throughstate. - With
startActivityForResult, get the job withVideoEditorActivity.renderJob(resultCode, data). - 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
Section titled “Read usage data”result.usage (EditorUsage) 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 |
TextUsage: count, plus fonts, styles, colors (font displayName, style id, #AARRGGBB → count) |
filters, effects, transitions |
Preset id → count |
cover |
CoverUsage: source (FRAME or PHOTO), moved; 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
Section titled “Handle failures”intent(onFailure=) reports failures on the main thread; the editor shows its own message. Closing or cancelling arrives as RESULT_CANCELED.
reason |
When | Then |
|---|---|---|
SOURCE_UNREADABLE |
A source or draft couldn’t be opened | With no clips left, finishes with RESULT_CANCELED |
RENDER_FAILED |
Render failed; nativeCode is Media3 ExportException.errorCode |
Back to editing; “Next” can be tapped again |
COVER_UNAVAILABLE |
Render succeeded, cover extraction failed | Result follows with an empty coverPath |
LICENSE_INVALID |
No valid license | On open: finishes with RESULT_CANCELED. On render: stays on editing |
Localize
Section titled “Localize”Strings are SDK resources in English and Korean. Other languages fall back to English.
The editor follows the app language. If your app has its own language setting, pass it as EditorConfig.locale.
To change a string or add a language, define the same name in your strings.xml. Resource merging gives your value precedence.
<string name="videoeditor_next">계속</string>Names start with videoeditor_; the full list is res/values/public.xml in the AAR. The two strings with numbers are <plurals>.
SDK logs are on in debug builds of the host. To turn them on in a release build, run this before launching the app:
adb shell setprop log.tag.VideoEditor DEBUGFor a crash inside the SDK, send the stack trace with the SDK version.