video_ultra_player
A Flutter plugin for previewing and exporting a native gapless media timeline made from local video and image files. Renders into a single Flutter Texture backed by AVFoundation (iOS) and AndroidX Media3 (Android).
Features
- Gapless native timeline preview in one Flutter
Texture.
- MP4 export with real-time progress stream.
- Video and image clips in the same timeline.
- Non-destructive clip editing: trim, split, insert, remove, move, replace.
- Per-clip speed (0.5× – 2×) and pan/crop with normalized alignment values.
- Overlay audio track with offset, volume, trim, fade-in, and fade-out.
- Undo/redo with
canUndo / canRedo flags in the state stream.
- Clip-to-clip crossfade transitions.
- Filmstrip thumbnail generation.
- Output aspect ratio presets and resolution control.
| Platform |
Preview |
Export |
| iOS |
✓ |
✓ |
| Android |
✓ |
✓ |
| Web / Desktop |
— |
— |
Getting started
flutter pub add video_ultra_player
import 'package:video_ultra_player/video_ultra_player.dart';
Basic usage
final player = NativeTimelinePlayer();
final textureId = await player.load([
const TimelineClip(path: '/path/intro.mp4', type: MediaType.video),
const TimelineClip(path: '/path/card.png', type: MediaType.image, duration: Duration(seconds: 3)),
const TimelineClip(path: '/path/outro.mp4', type: MediaType.video),
]);
// Render
Texture(textureId: textureId)
// Control
await player.play();
await player.pause();
await player.seekTo(const Duration(seconds: 2));
await player.seekToClip(1);
await player.setVolume(0.8);
// State
player.stateStream.listen((state) {
print('${state.globalPosition} — clip ${state.clipIndex} — undo: ${state.canUndo}');
});
// Dispose
await player.dispose();
Editing
All edits apply to the live composition and emit a new TimelinePlayerState.
await player.trimClip(0, trimStart: const Duration(seconds: 1), trimEnd: const Duration(seconds: 8));
await player.splitClip(1, const Duration(seconds: 3));
await player.insertClip(2, const TimelineClip(path: '/path/b-roll.mp4', type: MediaType.video));
await player.removeClip(2);
await player.moveClip(0, 2);
await player.replaceClip(1, const TimelineClip(path: '/path/new.mp4', type: MediaType.video));
await player.setClipSpeed(0, 0.5); // slow motion
await player.setClipAlignment(0, 0.4, -0.2);
await player.undo();
await player.redo();
Audio track
await player.setAudioTrack(const AudioTrack(
path: '/path/music.mp3',
offset: Duration(seconds: 2),
volume: 0.7,
fadeIn: Duration(milliseconds: 500),
fadeOut: Duration(seconds: 1),
));
await player.removeAudioTrack();
Export
// From clip list
final path = await player.exportTimeline(clips, outputPath: '/path/out.mp4');
// From current loaded timeline (matches the preview exactly)
final path = await player.exportCurrentTimeline();
// Track progress
player.exportProgress.listen((p) => print('${p.progress * 100}%'));
Clip thumbnails
final thumbs = await player.generateThumbnails(
'/path/clip.mp4',
[Duration.zero, const Duration(seconds: 1), const Duration(seconds: 2)],
width: 120,
); // List<String> of JPEG paths
Output config
await player.load(clips, config: const TimelineCompositionConfig(
aspectRatio: OutputAspectRatio.ratio9x16,
baseWidth: 1080,
));
API reference
NativeTimelinePlayer
| Member |
Description |
load(clips, {config}) |
Builds the native composition, returns textureId. |
exportTimeline(clips, {outputPath, config}) |
Exports a clip list to MP4. |
exportCurrentTimeline({outputPath}) |
Exports the loaded composition to MP4. |
play() / pause() |
Playback control. |
seekTo(Duration) / seekToClip(int) |
Seek by position or clip index. |
setVolume(double) |
Volume 0.0..1.0. |
trimClip / splitClip / insertClip / removeClip / moveClip / replaceClip |
Clip edits. |
setClipSpeed(int, double) |
Per-clip speed (0.5 – 2.0). |
setClipAlignment(int, double, double) |
Pan/crop alignment. |
setAudioTrack(AudioTrack) / removeAudioTrack() |
Overlay audio. |
undo() / redo() |
Edit history. |
generateThumbnails(path, timestamps, {width}) |
JPEG thumbnail list. |
stateStream |
Stream<TimelinePlayerState> |
exportProgress |
Stream<TimelineExportProgress> |
dispose() |
Releases native resources. |
TimelineClip
| Property |
Description |
path |
Absolute local file path. |
type |
MediaType.video or MediaType.image. |
duration |
Required for images (default: 2 s). |
speed |
Playback speed (0.5 – 2.0, default: 1.0). |
alignment / scale |
Pan/crop. |
trimStart / trimEnd |
Source in/out points. |
transitionToNext |
ClipTransition for a crossfade to the next clip. |
TimelinePlayerState
| Property |
Description |
globalPosition |
Position in the full timeline. |
clipIndex |
Active clip index. |
localPosition |
Position inside the active clip. |
isPlaying |
Playback state. |
totalDuration |
Total timeline duration. |
clipDurations |
Resolved duration per clip. |
canUndo / canRedo |
Edit history availability. |
Technical notes
- Empty clip lists are rejected by
load and exportTimeline.
- Image clips default to 2 s when
duration is omitted.
- iOS image clips are converted to temporary MP4 segments before composition.
- Exported files are returned as local paths — use
gal or similar to save to the gallery.
exportTimeline is independent of the preview player and can be called without load.