Skip to content

thangdevalone/react-native-media-toolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

react-native-media-toolkit

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.

npm license

     


Compatibility

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.
Use expo run:ios or expo run:android instead.


Features

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.


Installation

npm install react-native-media-toolkit react-native-nitro-modules
# or
yarn add react-native-media-toolkit react-native-nitro-modules

iOS:

cd ios && pod install

Android: No extra steps. Gradle resolves Media3 automatically.


Usage

import { MediaToolkit } from 'react-native-media-toolkit';

Crop image

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);

Compress image

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'
});

Trim video

const result = await MediaToolkit.trimVideo(videoUri, {
  startTime: 2000,  // start in milliseconds
  endTime: 7000,    // end in milliseconds
});

Crop video

const result = await MediaToolkit.cropVideo(videoUri, {
  x: 0.1,
  y: 0.1,
  width: 0.8,
  height: 0.8,
});

Trim + Crop in one pass

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.

Compress video

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, and bitrate are all optional — but the library needs at least one signal to determine bitrate. If you pass nothing, it defaults to quality: 'medium' (~4 Mbps). targetSizeInMB takes highest priority; bitrate overrides quality.

Extract thumbnail

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 ms

API Reference

Convention: Required = must be provided. Optional = has a sensible default or can be omitted entirely.

cropImage(uri, options): Promise<MediaResult>

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.

compressImage(uri, options): Promise<MediaResult>

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

trimVideo(uri, options): Promise<MediaResult>

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.

cropVideo(uri, options): Promise<MediaResult>

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.

trimAndCropVideo(uri, options): Promise<MediaResult>

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.

compressVideo(uri, options): Promise<MediaResult>

All options are optional. The bitrate strategy follows this priority:

  1. targetSizeInMB → smart-compress: calculates optimal bitrate and resolution from duration + target size (highest priority)
  2. bitrate → explicit bitrate override (takes priority over quality)
  3. 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.

getThumbnail(uri, options?): Promise<ThumbnailResult>

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

Return types

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, and duration in ThumbnailResult refer to the source video metadata — not the thumbnail image. This makes getThumbnail a lightweight way to probe video metadata without processing the file.


Custom UI

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 strip
  • CropOverlay — a draggable crop box with corner resize handles

You can copy these components directly into your project and adapt them as needed.


Performance

This library is designed around two principles: avoid unnecessary work and stay on the native thread.

No bridge overhead

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.

Trim without re-encoding

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.

Single-pass trim + crop

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.

Memory-Efficient Image Processing (OOM-Free)

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.inSampleSize to subsample the image during the hardware decoding phase, bypassing full-resolution memory allocation entirely.
  • iOS: Uses CGImageSourceCreateThumbnailAtIndex to instruct ImageIO to decode and downscale directly from the file descriptor buffer.

Smart Video Compression

The compressVideo API provides a dynamically balanced encoding strategy via the targetSizeInMB flag. When provided, the library will:

  • Calculate a bounded target bitrate mapped by the duration of the media track.
  • Adjust the output resolution dynamically, floor-bounded by minResolution to maintain pixel clarity at constrained bitrates.
  • Optionally strip the audio track (muteAudio) to allocate the entire output bandwidth to the visual presentation.

Comparison with common alternatives

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.


Architecture

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)

Contributing

See CONTRIBUTING.md

License

MIT — see LICENSE

Author

thangdevalonequangthangvtlg@gmail.com
GitHub: https://github.com/thangdevalone

About

A lightweight and high-performance media processing library for React Native, built on top of Nitro Modules (JSI).

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors