Android
By the end of this page your app shows the editor on a video, lets users add their own media through your picker, and exports the result.
Requirements
- Android 8.0 or later (
minSdk26). - Kotlin. Native libraries are included for
arm64-v8a,armeabi-v7a,x86andx86_64.
Install
Everything is in the dev.nitrakit package; the views are in dev.nitrakit.ui.
The SDK asks for no permissions: media reaches it through your picker.
1. Install your license key Stable
The key unlocks full-length, watermark-free exports for your app. Install it once at start-up,
for example in Application.onCreate. Without a key everything still works, so you can skip
this while trying the SDK.
class App : Application() {
override fun onCreate() {
super.onCreate()
val status = NitrakitLicense.set(this, "nk1.…")
if (status.problems.isNotEmpty()) Log.w("App", "Nitrakit: ${status.problems}")
}
}
NitrakitLicense.set takes a Context because the SDK checks your package name and signing
certificate against the key. See License key.
2. Create a project Stable
An Editor holds one project and its undo history. Create it with your output size and frame
rate, then add media: adding a file makes it a source; placing the source on a track makes
it a clip the user sees in the timeline.
val editor = Editor(1080u, 1920u, 30u, 1u)
val media = editor.addMedia(file, id = assetId)
val track = editor.apply(Edit.AddTrack(TrackKind.VISUAL, true))
editor.apply(Edit.InsertMedia(track, media.source, 0, 0, media.durationUs))
assetId is your app's id for the file, for example its key in your storage: the project
refers to media only by these ids. The SDK keeps its own copy of the file, so editing starts
right away. Times are microseconds.
3. Tell the SDK where media lives Stable
When a project is opened on a device that doesn't have its media yet, the SDK asks your app where each media id lives. Return a URL (it can be signed) and the SDK downloads it, showing the progress on the clips:
NitrakitMedia.resolve = { id -> MediaLocation.Remote(api.signedUrl(id)) } // or Local(file)
Set it once, at start-up; it's a suspend function. Skip it if your projects never leave the
device. See Projects and media.
4. Show the editor Stable
Two views make up the editor: NitrakitPreviewView plays the project and lets the user move
and resize layers; NitrakitTimelineView has the timeline, tools and panels. Give both the
same editor; they stay in sync with each other and with your code.
val preview = NitrakitPreviewView(this).apply { this.editor = editor }
val dock = NitrakitTimelineView(this).apply { this.editor = editor }
setContentView(LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(preview, LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f))
addView(dock, LinearLayout.LayoutParams(MATCH_PARENT, dp(400)))
})
Create the views in code; they don't inflate from XML. When the screen goes away, release them and close the editor:
override fun onDestroy() {
dock.editor = null
preview.editor = null
editor.close()
super.onDestroy()
}
The views survive backgrounding on their own: the preview keeps its position and the editor keeps its state.
5. Connect your pickers Stable
The editor never opens the gallery or a file browser itself. When the user taps
Add media, picks a font or a colour, it asks your app through onRequest, so these
screens look like the rest of your app and media can come from wherever you keep it.
onRequest is a suspend function: show your picker, wait for the result and return it.
Return RequestResponse.Cancel if the user backed out, or null to let the SDK show its
built-in picker:
dock.onRequest = { request ->
when (request.kind) {
RequestType.ADD_MEDIA, RequestType.ADD_OVERLAY,
RequestType.ADD_AUDIO, RequestType.ADD_STICKER -> {
val file = pickMedia() // your picker, copied to a File
if (file == null) RequestResponse.Cancel
else RequestResponse.Source(editor.addMedia(file, id = startUpload(file)).source)
}
else -> null // built-in picker
}
}
It starts on the main thread. The built-in pickers cover fonts, colours, text, text styles and filters; adding media always needs yours.
See Your pickers for every request and its answer.
6. Save the project Stable
Projects save as JSON, with media referred to by your ids. Store it on the device or sync it to your backend, and reopen it anywhere:
val json = editor.toJson()
val reopened = Editor(json)
reopened.loadMedia() // from the device, else through NitrakitMedia.resolve
To save as the user works, set a listener; it's called after every change. While your app
uploads a file, show the progress on its clips with editor.setMediaProgress(id, fraction).
7. Export Stable
Export renders the project to an MP4 in the background while the user keeps editing. Progress and the result arrive on the export thread:
val job = editor.export(outputPath, editor.exportSettingsFor(1080u), object : ExportListener {
override fun progress(fraction: Double, frames: ULong, total: ULong) {
runOnUiThread { /* update your progress bar */ }
}
override fun finished(result: ExportResult, message: String) {
// DONE: the file is at your path. CANCELLED or FAILED: no file (message says why).
}
})
// job.cancel()
Close the ExportJob once it has finished. See Export.
Playback
Control playback from your own buttons through the preview's player:
preview.player?.play()
preview.player?.pause()
preview.player?.seek(2_000_000)
preview.onPosition = { us -> /* main thread */ }
The player exists while the preview is on screen; onPlayer tells you when a new one is
ready, for example after the app returns from the background.
Good to know
-
Errors: calls that can fail throw
EditorError, e.g.Collision(a clip would overlap another) orMedia(a file can't be read). -
Release builds: if you minify with R8, keep the SDK's classes; its native code calls back into them by name:
-keep class dev.nitrakit.** { *; }