Read this in: Tiếng Việt
Native image and video processing for React Native — crop, trim, compress, and thumbnail extraction.
Built on Nitro Modules (JSI), using AVFoundation on iOS and Jetpack Media3 Transformer on Android. No FFmpeg dependency.
| Environment | Support |
|---|---|
| React Native CLI (New Architecture) | Supported |
| Expo with Dev Client / Custom Build | Supported |
| Expo Go | Not supported (requires native build) |
| React Native | 0.75+ (New Architecture required) |
| iOS | 15.1+ |
| Android | API 24+ (Android 7.0) |
Expo note: This library requires a native build. It cannot run in Expo Go.
Useexpo run:iosorexpo run:androidinstead.
| Feature | iOS | Android |
|---|---|---|
| Crop image | AVFoundation / CGImage | Bitmap |
| Compress image | CGImageSource (OOM-free) | BitmapFactory / inSampleSize |
| Trim video (start/end in ms) | AVAssetExportSession | Media3 Transformer |
| Crop video (relative region) | AVMutableVideoComposition | Media3 Presentation |
| Trim + Crop in single pass | AVMutableVideoComposition | Media3 Transformer |
| Compress video | AVAssetExportSession presets | Media3 Transformer |
| Extract thumbnail from video | AVAssetImageGenerator | MediaMetadataRetriever |
All crop coordinates use a relative (0.0–1.0) system — independent of screen resolution.
npm install react-native-media-toolkit react-native-nitro-modules
# or
yarn add react-native-media-toolkit react-native-nitro-modulesiOS:
cd ios && pod installAndroid: No extra steps. Gradle resolves Media3 automatically.
import { MediaToolkit } from 'react-native-media-toolkit';const result = await MediaToolkit.cropImage(imageUri, {
x: 0.25, // required — left offset relative to image width (0.0–1.0)
y: 0.25, // required — top offset relative to image height (0.0–1.0)
width: 0.5, // required — crop width relative to image width (0.0–1.0)
height: 0.5, // required — crop height relative to image height (0.0–1.0)
outputPath: '/custom/path/out.jpg', // optional
});
console.log(result.uri, result.width, result.height);const result = await MediaToolkit.compressImage(imageUri, {
quality: 70, // optional — 0–100, default 80
maxWidth: 1080, // optional — max output width, aspect ratio preserved
maxHeight: 1920, // optional — max output height, aspect ratio preserved
format: 'jpeg', // optional — 'jpeg' | 'png' | 'webp', default 'jpeg'
});const result = await MediaToolkit.trimVideo(videoUri, {
startTime: 2000, // start in milliseconds
endTime: 7000, // end in milliseconds
});const result = await MediaToolkit.cropVideo(videoUri, {
x: 0.1,
y: 0.1,
width: 0.8,
height: 0.8,
});const result = await MediaToolkit.trimAndCropVideo(videoUri, {
startTime: 1000,
endTime: 8000,
x: 0.0,
y: 0.1,
width: 1.0,
height: 0.8,
});Faster than running trim and crop separately — only one encode pass.
The compressor supports two modes. Use one of them:
Mode 1 — Smart compress to a target file size (recommended):
const result = await MediaToolkit.compressVideo(videoUri, {
targetSizeInMB: 8, // required for this mode — target output size in MB
minResolution: 480, // optional — minimum short-edge resolution (default 720)
muteAudio: false, // optional — strip audio track (default false)
width: 1280, // optional — max output width, aspect ratio preserved
});Mode 2 — Quality preset or explicit bitrate:
const result = await MediaToolkit.compressVideo(videoUri, {
quality: 'medium', // optional — 'low' | 'medium' | 'high' (default 'medium')
bitrate: 2_000_000, // optional — explicit bitrate in bps (overrides quality)
muteAudio: false, // optional — strip audio track (default false)
width: 1280, // optional — max output width, aspect ratio preserved
});Note:
targetSizeInMB,quality, andbitrateare all optional — but the library needs at least one signal to determine bitrate. If you pass nothing, it defaults toquality: 'medium'(~4 Mbps).targetSizeInMBtakes highest priority;bitrateoverridesquality.
const thumb = await MediaToolkit.getThumbnail(videoUri, {
timeMs: 3000, // frame time in milliseconds, default 0
quality: 85, // 0–100, default 80
maxWidth: 720, // max thumbnail width (does not affect returned metadata)
});
// thumb.uri → thumbnail JPEG file
// thumb.width → source video width (rotation-corrected)
// thumb.height → source video height
// thumb.size → source video file size in bytes
// thumb.duration → source video duration in msConvention:
Required= must be provided.Optional= has a sensible default or can be omitted entirely.
| Option | Type | Required | Description |
|---|---|---|---|
x |
number |
Required | Left offset relative to image width (0.0–1.0) |
y |
number |
Required | Top offset relative to image height (0.0–1.0) |
width |
number |
Required | Crop width relative to image width (0.0–1.0) |
height |
number |
Required | Crop height relative to image height (0.0–1.0) |
outputPath |
string |
Optional | Absolute path for the output file. Defaults to a temp file. |
All options are optional. Pass an empty object {} to use all defaults.
| Option | Type | Default | Description |
|---|---|---|---|
quality |
number |
80 |
JPEG/WebP encode quality (0–100) |
maxWidth |
number |
original | Max output width in px (aspect ratio preserved) |
maxHeight |
number |
original | Max output height in px (aspect ratio preserved) |
format |
string |
'jpeg' |
Output format: 'jpeg' | 'png' | 'webp' |
outputPath |
string |
temp file | Absolute path for the output file |
| Option | Type | Required | Description |
|---|---|---|---|
startTime |
number |
Required | Trim start position in milliseconds |
endTime |
number |
Required | Trim end position in milliseconds |
outputPath |
string |
Optional | Absolute path for the output file. Defaults to a temp file. |
Same relative coordinate system as cropImage — all values in the range (0.0–1.0).
| Option | Type | Required | Description |
|---|---|---|---|
x |
number |
Required | Left offset relative to frame width (0.0–1.0) |
y |
number |
Required | Top offset relative to frame height (0.0–1.0) |
width |
number |
Required | Crop width relative to frame width (0.0–1.0) |
height |
number |
Required | Crop height relative to frame height (0.0–1.0) |
outputPath |
string |
Optional | Absolute path for the output file. Defaults to a temp file. |
Combines trim and crop into a single encode pass — faster and avoids double-encode quality loss.
| Option | Type | Required | Description |
|---|---|---|---|
startTime |
number |
Required | Trim start position in milliseconds |
endTime |
number |
Required | Trim end position in milliseconds |
x |
number |
Required | Crop left offset relative to frame width (0.0–1.0) |
y |
number |
Required | Crop top offset relative to frame height (0.0–1.0) |
width |
number |
Required | Crop width relative to frame width (0.0–1.0) |
height |
number |
Required | Crop height relative to frame height (0.0–1.0) |
outputPath |
string |
Optional | Absolute path for the output file. Defaults to a temp file. |
All options are optional. The bitrate strategy follows this priority:
targetSizeInMB→ smart-compress: calculates optimal bitrate and resolution from duration + target size (highest priority)bitrate→ explicit bitrate override (takes priority overquality)quality→ preset mapping:low≈ 1 Mbps ·medium≈ 4 Mbps ·high≈ 8 Mbps (default)
If none of the three are passed, the library falls back to quality: 'medium'.
| Option | Type | Default | Description |
|---|---|---|---|
targetSizeInMB |
number |
— | Optional. Target output file size in MB. When set, overrides quality and bitrate. |
minResolution |
number |
720 |
Optional. Minimum short-edge resolution (px) when using targetSizeInMB. Prevents over-downscaling. |
quality |
string |
'medium' |
Optional. Preset: 'low' | 'medium' | 'high'. Ignored if targetSizeInMB or bitrate is set. |
bitrate |
number |
— | Optional. Explicit target bitrate in bps. Overrides quality; ignored if targetSizeInMB is set. |
width |
number |
original | Optional. Max output width in px (aspect ratio preserved). |
muteAudio |
boolean |
false |
Optional. Strip audio track from the output. |
outputPath |
string |
temp file | Optional. Absolute path for the output file. |
options itself is optional — pass nothing to extract a full-res JPEG at time 0.
| Option | Type | Default | Description |
|---|---|---|---|
timeMs |
number |
0 |
Frame time in milliseconds |
quality |
number |
80 |
JPEG output quality (0–100) |
maxWidth |
number |
original | Max thumbnail width in px (aspect ratio preserved) |
outputPath |
string |
temp file | Absolute path for the output JPEG |
interface MediaResult {
uri: string; // file:// URI of the output file
size: number; // file size in bytes
width: number; // output width in pixels
height: number; // output height in pixels
duration: number; // duration in ms (0 for images)
mime: string; // MIME type, e.g. 'video/mp4'
}
interface ThumbnailResult {
uri: string; // file:// URI of the output JPEG thumbnail
size: number; // source video file size in bytes
width: number; // source video width in pixels (rotation-corrected)
height: number; // source video height in pixels (rotation-corrected)
duration: number; // source video duration in milliseconds
}Note:
width,height,size, anddurationinThumbnailResultrefer to the source video metadata — not the thumbnail image. This makesgetThumbnaila lightweight way to probe video metadata without processing the file.
This library is headless — it provides native processing logic only, without any built-in UI.
You are free to build any trim timeline, crop overlay, or progress indicator that fits your app design.
The example app (example/src/App.tsx) includes reference implementations of:
VideoTrimBar— a timeline scrubber with dual handles and thumbnail stripCropOverlay— a draggable crop box with corner resize handles
You can copy these components directly into your project and adapt them as needed.
This library is designed around two principles: avoid unnecessary work and stay on the native thread.
All API calls go through JSI (JavaScript Interface) via Nitro Modules. There is no JSON serialization between JS and native — the call is a direct C++ function call. This eliminates the main bottleneck of the old Bridge architecture.
trimVideo uses AVAssetExportPresetPassthrough on iOS and a keyframe-aligned cut on Android. The compressed bitstream is copied as-is — no decode, no re-encode. A 30-second video trims in under 1 second regardless of resolution.
Running trimVideo then cropVideo sequentially means two full encode passes: decode → encode → decode → encode. trimAndCropVideo does both in one session: decode → encode once. This halves the processing time and avoids quality loss from double encoding.
Standard image processing operations can cause Out-Of-Memory (OOM) exceptions when decoding high-resolution images (e.g., 40MP+). To prevent this, the library handles decoding via Load-Time Downsampling:
- Android: Utilizes
BitmapFactory.Options.inSampleSizeto subsample the image during the hardware decoding phase, bypassing full-resolution memory allocation entirely. - iOS: Uses
CGImageSourceCreateThumbnailAtIndexto instructImageIOto decode and downscale directly from the file descriptor buffer.
The compressVideo API provides a dynamically balanced encoding strategy via the targetSizeInMB flag. When provided, the library will:
- Calculate a bounded target
bitratemapped by thedurationof the media track. - Adjust the output resolution dynamically, floor-bounded by
minResolutionto maintain pixel clarity at constrained bitrates. - Optionally strip the audio track (
muteAudio) to allocate the entire output bandwidth to the visual presentation.
| Library | Native engine | JS bridge | Trim (no re-encode) | Single-pass trim+crop |
|---|---|---|---|---|
| react-native-media-toolkit | AVFoundation / Media3 | JSI (no overhead) | Yes | Yes |
| react-native-video-trim | AVFoundation / FFmpegKit | Bridge | iOS only | No |
| react-native-compressor | AVFoundation / MediaCodec | Bridge | No | No |
| ffmpeg-kit-react-native | FFmpeg (software) | Bridge | No | No |
Note: FFmpegKit is the most flexible option for complex pipelines. This library is optimized for the common cases: trim, crop, compress, and thumbnail extraction.
react-native-media-toolkit/
├── src/
│ ├── MediaToolkit.nitro.ts Nitro HybridObject spec + TypeScript types
│ └── index.ts Public JS/TS API
├── ios/
│ ├── HybridMediaToolkit.swift Nitro entry point (Swift)
│ ├── ImageProcessor.swift CGImage crop and compress
│ ├── VideoProcessor.swift AVFoundation trim, crop, compress, thumbnail
│ └── MediaToolkitErrors.swift Error definitions
├── android/
│ └── src/main/java/com/mediatoolkit/
│ ├── HybridMediaToolkit.kt Nitro entry point (Kotlin)
│ ├── ImageProcessor.kt Bitmap crop and compress
│ ├── VideoProcessor.kt Media3 trim, crop, compress, thumbnail
│ └── MediaToolkitException.kt Error definitions
├── nitrogen/ Generated C++ and Swift/Kotlin bridge files
└── example/ Demo app (Expo Dev Client)
See CONTRIBUTING.md
MIT — see LICENSE
thangdevalone — quangthangvtlg@gmail.com
GitHub: https://github.com/thangdevalone


