A high-performance native video processing and editing library for React Native, built with Nitro Modules.
The goal is to provide a native, high-performance video editing foundation for React Native, while keeping the JavaScript API simple and moving performance-sensitive media operations to native Android and iOS implementations.
Current version: 0.1.0
This is the first public development release.
The following capabilities are currently available on Android and iOS:
- Video session
- Metadata extraction
- Thumbnail extraction
- Timeline thumbnails
- Video playback
- Video preview
- Timeline scrubbing
- Timeline trim selection
- Native video trimming
- Native video export
- Export progress
- Export cancellation
Simulator.Screen.Recording.-.iPhone.15.Pro.-.2026-08-26.at.11.52.20.1.mp4
This library requires modern React Native features for JSI bindings:
- React Native:
0.83.0or higher.- Note: The
<VideoPreview>component uses Nitro's JSI bindings to pass callbacks/references directly to Fabric. This requiresRawValueto JSI conversion support which is only available in React Native 0.83+. Running on older versions (e.g. 0.81) will trigger a native C++RawValuecast assertion crash.
- Note: The
- New Architecture: Enabled (Fabric + TurboModules).
yarn add @mindinventory/react-native-nitro-video
# or
npm install @mindinventory/react-native-nitro-videoThis library depends on the following libraries which must be installed in your project:
yarn add react-native-nitro-modules react-native-worklets react-native-gesture-handler react-native-reanimated
# or
npm install react-native-nitro-modules react-native-worklets react-native-gesture-handler react-native-reanimatedEnsure you follow the installation and setup guides for each of these peer dependencies (e.g. configuring babel.config.js for Reanimated, etc.).
For iOS, go to your project's ios folder and install pods:
cd ios && pod install- β‘ High performance β expensive media operations run natively.
- π§© Nitro-based architecture β use HybridObjects and HybridViews for low-overhead JS β native communication.
- π± Android + iOS β keep the public JavaScript API platform-independent.
- π¬ Video editing foundation β build toward trimming, composition, export, and advanced editing.
- π§ Simple JavaScript API β React Native developers should not need to understand native media internals.
- π Explicit lifecycle management β native sessions and players have clear ownership and release paths.
- ποΈ Incremental architecture β introduce abstractions when a real feature requires them.
The library uses Nitro HybridObjects and HybridViews as the primary native communication layer.
React Native / JavaScript
β
βΌ
Video API
β
βββββββ΄ββββββ
βΌ βΌ
VideoSession VideoPlayer
β β
β βΌ
β VideoPreview
β β
βββββ΄ββββ βββββ΄ββββ
βΌ βΌ βΌ βΌ
Android iOS Android iOS
β β β β
βΌ βΌ βΌ βΌ
Native AVFoundation ExoPlayer AVPlayer
Media APIs
The editor layer combines the media session, metadata, player, and timeline:
VideoSession
β
βββ metadata
βββ thumbnails
β
βΌ
ThumbnailManager
β
βΌ
TimelineDataSource
β
βΌ
VideoTimeline
VideoPlayer
β
βΌ
VideoEditorController
β
βΌ
useVideoEditor()
const session = await Video.load(videoUri);const metadata = await session.getMetadata();export interface VideoMetadata {
duration: number;
width: number;
height: number;
rotation: number;
frameRate: number;
bitrate: number;
codec: string;
fileSize: number;
}const thumbnail = await session.getThumbnail(5000);Resize natively:
const thumbnail = await session.getThumbnail(5000, {
width: 320,
height: 180,
});The output is a JPEG-encoded Nitro ArrayBuffer.
const thumbnails = await session.getThumbnails({
startTimeMs: 0,
endTimeMs: 10000,
intervalMs: 1000,
width: 160,
height: 90,
});export interface VideoThumbnail {
timeMs: number;
data: ArrayBuffer;
}const manager = new ThumbnailManager(session, {
intervalMs: 1000,
width: 160,
height: 90,
cacheWindowMs: 30000,
maxCacheItems: 300,
});await manager.getRange(0, 10000);
await manager.getVisibleRange({
startTimeMs: 0,
endTimeMs: 10000,
prefetchMs: 5000,
});const player = await Video.createPlayer(videoUri);
await player.play();
await player.pause();
await player.seek(5000);
const currentTime = await player.getCurrentTime();
const isPlaying = await player.isPlaying();
await player.release();The player exposes native position updates:
player.addPositionListener((timeMs) => {
console.log('Position:', timeMs);
});
player.removePositionListener();VideoPreview is a Nitro HybridView backed by PlayerView on Android and AVPlayerLayer on iOS.
\<VideoPreview
hybridRef={callback((ref) => {
previewRef.current = ref;
})}
style={{
width: '100%',
height: 240,
}}
/>The player is attached using the HybridView methods:
previewRef.current?.attachPlayer(player);
previewRef.current?.detachPlayer();VideoTimeline is the reusable timeline component for playback scrubbing and trim-range selection.
type VideoTimelineMode = 'scrub' | 'trim';The mode can be switched at runtime without recreating the timeline.
interface VideoTimelineProps {
session: VideoSession;
durationMs: number;
width: number;
currentTimeMs?: number;
mode?: VideoTimelineMode;
disabled?: boolean;
trimRange?: VideoTimeRange;
minTrimDurationMs?: number;
thumbnailWidth?: number;
thumbnailHeight?: number;
intervalMs?: number;
prefetchMs?: number;
onTrimRangeChange?: (range: VideoTimeRange) => void;
onTimeChange?: (timeMs: number) => void;
onScrub?: (timeMs: number) => void;
onScrubStart?: () => void;
onScrubEnd?: () => void;
onTrimInteractionStart?: () => void;
onTrimInteractionEnd?: () => void;
}<VideoTimeline
session={session}
durationMs={metadata.duration}
currentTimeMs={state.currentTimeMs}
mode="scrub"
onScrub={seek}
/>const [trimRange, setTrimRange] = useState({
startTimeMs: 0,
endTimeMs: metadata.duration,
});
<VideoTimeline
session={session}
durationMs={metadata.duration}
currentTimeMs={state.currentTimeMs}
mode="trim"
trimRange={trimRange}
minTrimDurationMs={500}
onTrimRangeChange={setTrimRange}
onScrub={seek}
/>;Trim mode provides draggable start/end handles, selected-range feedback, minimum-range enforcement, and edge auto-scroll.
The trim range is controlled by the consumer:
interface VideoTimeRange {
startTimeMs: number;
endTimeMs: number;
}disabled prevents timeline scrubbing and trim interaction while keeping the timeline visible.
The editor combines session, metadata, player state, playback errors, and timeline behavior:
const { state, seek, play, pause, togglePlayback, retry } = useVideoEditor(
session,
metadata,
player
);Current state:
export interface VideoEditorState {
durationMs: number;
currentTimeMs: number;
isPlaying: boolean;
error: VideoPlaybackError | null;
}The editor handles playback, pause, seeking, current-position synchronization, timeline scrubbing, playback-driven timeline movement, playback errors, and manual playback retry.
The actual trim operation remains on the native session:
const result = await session.trim({
startTimeMs: 5000,
endTimeMs: 12000,
});To export a trimmed video segment, use session.exportVideo(options). This performs a native background export task:
const task = await session.exportVideo({
startTimeMs: 1000, // optional
endTimeMs: 5000, // optional
});
// Track export progress
task.addProgressListener((event) => {
console.log(
`Export progress: ${event.progress * 100}%, status: ${event.status}`
);
});
// Wait for the result or catch any failures / cancellations
try {
const result = await task.getResult();
console.log('Export finished. Saved to:', result.outputUri);
} catch (error) {
if (error.code === 'CANCELLED') {
console.log('Export was cancelled.');
} else {
console.error('Export failed:', error.message);
}
}
// To cancel the export task programmatically:
// await task.cancel();export type VideoExportStatus =
| 'preparing'
| 'exporting'
| 'completed'
| 'cancelled'
| 'failed';
export interface VideoExportProgress {
progress: number;
status: VideoExportStatus;
}
export interface VideoExportResult {
outputUri: string;
}
export type VideoExportErrorCode =
| 'INVALID_TIME_RANGE'
| 'SOURCE_NOT_FOUND'
| 'EXPORT_FAILED'
| 'CANCELLED'
| 'OUTPUT_FAILED';
---
# π Nitro Specifications
## VideoModule
```ts
export interface VideoModule extends HybridObject<{
ios: 'swift';
android: 'kotlin';
}> {
load(source: string): Promise<VideoSession>;
createPlayer(source: string): Promise<VideoPlayer>;
}export interface VideoSession extends HybridObject<{
ios: 'swift';
android: 'kotlin';
}> {
getMetadata(): Promise<VideoMetadata>;
getThumbnail(
timeMs: number,
options?: ThumbnailOptions
): Promise<ArrayBuffer>;
getThumbnails(request: ThumbnailRequest): Promise<VideoThumbnail[]>;
trim(options: VideoTrimOptions): Promise<VideoTrimResult>;
exportVideo(options?: VideoExportOptions): Promise<VideoExportTask>;
release(): Promise<void>;
}export interface VideoPlayer extends HybridObject<{
ios: 'swift';
android: 'kotlin';
}> {
play(): Promise<void>;
pause(): Promise<void>;
seek(timeMs: number): Promise<void>;
getCurrentTime(): Promise<number>;
isPlaying(): Promise<boolean>;
addPositionListener(listener: (timeMs: number) => void): void;
removePositionListener(): void;
addErrorListener(listener: (error: VideoPlaybackError) => void): void;
removeErrorListener(): void;
retry(): Promise<void>;
release(): Promise<void>;
}export interface VideoExportTask extends HybridObject<{
ios: 'swift';
android: 'kotlin';
}> {
getResult(): Promise<VideoExportResult>;
cancel(): Promise<void>;
addProgressListener(listener: (progress: VideoExportProgress) => void): void;
removeProgressListener(): void;
}export interface VideoPreviewProps extends HybridViewProps {}
export interface VideoPreviewMethods extends HybridViewMethods {
attachPlayer(player: VideoPlayer): void;
detachPlayer(): void;
}
export type VideoPreview = HybridView<
VideoPreviewProps,
VideoPreviewMethods,
{
ios: 'swift';
android: 'kotlin';
}
>;The view is implemented in Kotlin on Android and Swift on iOS.
The library delegates performance-sensitive media operations to native platform APIs via Nitro:
- Android: Uses Jetpack Media3 (
ExoPlayer,PlayerView) for video playback/preview, Jetpack Media3Transformerfor trimming and export tasks, andMediaMetadataRetrieverfor metadata and frame (thumbnail) extraction. - iOS: Uses AVFoundation (
AVPlayer,AVPlayerLayer,AVAssetImageGenerator,AVAssetExportSession) for playback, metadata, frame extraction, trimming, and export tasks.
src/
βββ video.ts
βββ index.tsx
βββ VideoPreview.tsx
βββ specs/
β βββ VideoExportTask.nitro.ts
β βββ VideoModule.nitro.ts
β βββ VideoPlayer.nitro.ts
β βββ VideoPreview.nitro.ts
β βββ VideoSession.nitro.ts
βββ types/
β βββ error.ts
β βββ export.ts
β βββ metadata.ts
β βββ playback.ts
β βββ thumb.ts
β βββ timelines.ts
β βββ trim.ts
βββ timeline/
β βββ ThumbnailManager.ts
β βββ TimelineDataSource.ts
β βββ VideoTimeline.tsx
β βββ thumbnailToUri.ts
βββ editor/
βββ VideoEditorController.ts
βββ useVideoEditor.ts
βββ validateTrim.ts
android/
βββ src/main/java/com/margelo/nitro/nitrovideo/
βββ NitroVideo.kt
βββ NitroVideoPackage.kt
βββ HybridVideoSession.kt
βββ HybridVideoPlayer.kt
βββ HybridVideoPreview.kt
βββ codec/
β βββ VideoDecoderResolver.kt
βββ export/
β βββ HybridVideoExportTask.kt
β βββ VideoExportException.kt
βββ metadata/
β βββ MetadataExtractor.kt
βββ thumbnail/
β βββ ThumbnailExtractor.kt
βββ trim/
βββ TrimExtractor.kt
ios/
βββ HybridVideoExportTask.swift
βββ HybridVideoPlayer.swift
βββ HybridVideoPreview.swift
βββ HybridVideoSession.swift
βββ NitroVideo.swift
βββ Metadata/
β βββ MetadataExtractor.swift
β βββ VideoMetadataError.swift
βββ thumbnail/
β βββ ThumbnailExtractor.swift
βββ trim/
βββ VideoTrimmer.swift
nitrogen/
βββ generated/
βββ android/
βββ ios/
Generated files under nitrogen/generated/ must not be edited manually.
The TypeScript Nitro specifications are the source of truth.
The development progress and feature goals of the library:
- Core Video Session: Loading video sources, lifecycle management (
Video.load(),VideoSession.release()). - Metadata Extraction: Access to video dimensions, rotation, codec, bitrate, frame rate, and file size.
- Thumbnail System: Frame-accurate single and batch thumbnail extraction at custom intervals, cached locally using an LRU cache.
- Video Playback & Preview: Native
VideoPlayerand<VideoPreview>UI components with playback positioning listeners. - Video Trimming: Native frame-accurate trimming of videos generating a new output file.
- Video Export: Native export task with progress monitoring and cancellation support.
- Multi-Clip Editing & Composition: Support for merging, transitions, and native rendering of multiple clips.
- Soundtrack / Audio System: Native background audio tracks addition, volume control, and mixing.
- Advanced Editing Features: Overlays (text, images), filters, and speed adjustments.
JavaScript
β
β commands / configuration
βΌ
Nitro
β
βΌ
Native media engine
JavaScript should not repeatedly process decoded video frames.
Large media data should not unnecessarily travel through:
Native β C++ β JS β Native
Only data required by the JavaScript API should cross the boundary.
Native frame
β
Native resize
β
JPEG encoding
β
Nitro ArrayBuffer
β
JavaScript
Operations involving disk I/O, decoding, encoding, frame extraction, and export should not block the React Native JavaScript thread.
const player = await Video.createPlayer(uri);
try {
await player.play();
} finally {
await player.release();
}A minimal editor flow:
const session = await Video.load(uri);
const metadata = await session.getMetadata();
const player = await Video.createPlayer(uri);
const editor = useVideoEditor(session, metadata, player);In React, the resulting state can drive the preview, timeline, and controls:
\<VideoPreview
hybridRef={callback((ref) => {
previewRef.current = ref;
})}
style={{
width: '100%',
height: 240,
}}
/>
\<VideoTimeline
session={session}
durationMs={metadata.duration}
currentTimeMs={state.currentTimeMs}
onScrub={seek}
/>The example application is used to validate the native implementation.
TypeScript API
β
Nitrogen generation
β
Android / iOS native implementation
β
Example application
β
Device / simulator testing
When changing:
src/specs/*.nitro.ts
run:
yarn nitrogen
before rebuilding the example application.
Generated files should never be edited manually.
All public video timeline values use milliseconds.
5000; // 5 secondsBytes.
Bits per second.
Pixels.
Frames per second.
Thumbnail/frame extraction currently returns JPEG encoded as a Nitro ArrayBuffer.
The project is currently in an early public release stage.
Currently:
-
Thumbnail batch cancellation and progress callbacks are not yet exposed.
-
Dedicated native playback-state events (
playing,paused,buffering,ended) are not yet exposed. -
Background playback/interruption handling is not yet a dedicated abstraction.
-
Remote video source support is not yet defined as a single cross-platform contract.
-
Multi-clip composition is not implemented yet.
-
Advanced editing operations are not implemented yet.
-
APIs may change before the first stable release.
React Native Nitro Video
β
βΌ
βββββββββββββββββββ
β Video Session β
β β
β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Metadata β
β β
β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Thumbnails β
β β
β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Timeline / β
β Batch Thumbnailsβ
β β
β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Preview / β
β Playback β
β β
β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Trim β
β β
β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Export β
β β
β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Multi-Clip β
β Editing β
β π β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Advanced Editor β
β Features β
β π β
βββββββββββββββββββ
This project prioritizes measured architecture over premature abstraction.
New abstractions should be introduced when a real feature requires them.
The architecture intentionally avoids introducing large abstractions such as:
VideoSource
SessionManager
EditingGraph
TimelineEngine
ExportManager
until their responsibilities become necessary.
The objective is to keep the core implementation understandable while allowing the architecture to evolve toward a complete native video editing engine.
This project is licensed under the MIT License - see the LICENSE file for details.