Projects and media
A project describes an edit: which media plays when, with what effects, text and audio. This page explains how it refers to media, so projects can live wherever your app keeps them and media can come from wherever you store it.
The editor and its project
An editor holds one project, plus its undo history and the current selection. The preview and the editor controls show whichever editor you give them, and follow every change, whether it comes from the user or from your code.
A project has a fixed size and frame rate, e.g. 1080×1920 at 30 fps for vertical video. Choose them when you create it; export can render smaller.
Media ids
A project never stores files, URLs or device paths. Each piece of media in it has a media id: your app's own id for that file, for example its key in your storage or its id in your database. The SDK treats ids as opaque text.
Two things follow:
- Projects travel. The project's JSON contains only ids, so you can store it on the device, sync it to your backend, or open it on another device.
- Your app decides where media lives. When the SDK needs a file it doesn't have, it asks your app to resolve the id.
Adding media
When the user adds a file, pass its id together with the file you already have. The SDK keeps a copy, so editing starts right away; it doesn't wait for your upload.
| Platform | Call |
|---|---|
| iOS | editor.addMedia(id:file:name:) |
| Android | editor.addMedia(file, id, name) |
| React Native | editor.addMedia(id, path, name) |
| Web | await editor.addMedia(id, file) |
The result is a source: the project now knows the file, its kind, duration and size. Place it on a track as a clip to show it in the timeline. One source can back many clips.
The SDK reads video, audio and images.
Resolving media ids
Set a resolver once. The SDK calls it with a media id whenever it needs that file and doesn't have it, typically after opening a project on a device that hasn't seen it before. Return where the file is:
- a URL: plain, signed (e.g. a pre-signed S3 URL from your backend), or on your CDN. The SDK downloads it into its cache and shows the progress on the clips;
- a local file your app already has.
- iOS
- Android
- React Native
- Web
NitrakitMedia.resolve = { id in
.remote(try await api.signedURL(for: id))
}
NitrakitMedia.resolve = { id -> MediaLocation.Remote(api.signedUrl(id)) }
setMediaResolver(async (id) => (await api.signedUrl(id)).url)
const editor = await createEditor({
resolveMedia: async (id) => (await api.signedUrl(id)).url,
})
On iOS, Android and React Native, files are kept in the SDK's cache, so an id is downloaded once per device. Without a resolver, media stays on the device it was added on, which suits apps that keep projects on the device only.
Opening a project
Opening a saved project shows it at once. Its media becomes available in the background: from the device when it's there, otherwise through your resolver.
- While a file downloads, its clips show the progress.
- If a file can't be resolved (deleted, no access, not uploaded yet), the project still opens
and its clips are marked missing. Retry later with
retryMedia. - Export waits for media that's still downloading, and fails if some is missing.
Uploads
Uploading is your app's job, so it fits your storage and your backend. While it runs, show its
progress on the clips with setMediaProgress(id, fraction), and clear it when done. The user
keeps editing meanwhile.
Tracks
| Kind | Holds |
|---|---|
| Visual | Video, images, stickers, text and colour solids. Higher tracks draw on top. |
| Audio | Music, voice-over, sound effects. |
| Effects | Effect clips that change everything below them while they play. |
A magnetic track keeps its clips back to back: removing or trimming a clip closes the gap. Use one magnetic visual track as the main story and free tracks for overlays, titles and music.
Time
All times are microseconds: one second is 1_000_000. Clip times are on the timeline;
keyframe times are relative to their clip.
Saving
A project saves as JSON. Store it however you like, and save as the user works: the SDK tells you after every change.
| iOS | Android | React Native | Web | |
|---|---|---|---|---|
| Save | editor.toJson() | editor.toJson() | editor.toJson() | editor.toJson() |
| Open | Editor(open: json), then loadMedia() | Editor(json), then loadMedia() | openEditor(json), then loadMedia() | editor.openProject(json) |
| Changes | setListener(_:) | setListener { … } | setListener(fn) | onChange option |