
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
react-native-video-trim
Advanced tools
✅ iOS & Android • ✅ New & Old Architecture • ✅ Expo Compatible
A powerful, easy-to-use video and audio trimming library for React Native applications.
| Feature | Description |
|---|---|
| Trimming | Video/audio trimming with visual timeline controls |
| Transforms | Horizontal flip, 90° rotation, and freeform crop with undo/redo |
| Precise Trimming | Frame-accurate cuts using hardware re-encoding (opt-in) |
| Validation | Check if files are valid video/audio before processing |
| Save Options | Photos, Documents, Share sheet integration |
| File Management | Complete file lifecycle management |
| Customization | Extensive UI and behavior customization |
npm install react-native-video-trim
# or
yarn add react-native-video-trim
npx pod-install ios
Permissions Required:
NSPhotoLibraryUsageDescription to Info.plistFor New Architecture:
cd android && ./gradlew generateCodegenArtifactsFromSchema
Permissions Required:
AndroidManifest.xml:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
For Share Sheet functionality, add to AndroidManifest.xml:
<application>
<!-- your other configs -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
Create android/app/src/main/res/xml/file_paths.xml:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="internal_files" path="." />
<external-path name="external_files" path="." />
</paths>
npx expo prebuild
Then rebuild your app. Note: Expo Go may not work due to native dependencies - use development builds or expo run:ios/expo run:android.
Get up and running in 3 simple steps:
import { showEditor } from 'react-native-video-trim';
// 1. Basic usage - open video editor
showEditor(videoUrl);
// 2. With duration limit
showEditor(videoUrl, {
maxDuration: 20,
});
// 3. With save options
showEditor(videoUrl, {
maxDuration: 30,
saveToPhoto: true,
openShareSheetOnFinish: true,
});
import { showEditor } from 'react-native-video-trim';
import { launchImageLibrary } from 'react-native-image-picker';
const trimVideo = () => {
// Pick a video
launchImageLibrary({ mediaType: 'video' }, (response) => {
if (response.assets && response.assets[0]) {
const videoUri = response.assets[0].uri;
// Open editor
showEditor(videoUri, {
maxDuration: 60, // 60 seconds max
saveToPhoto: true,
});
}
});
};
💡 More Examples: Check the example folder for complete implementation details with event listeners for both New and Old architectures.
Opens the video trimmer interface.
showEditor(videoPath: string, config?: EditorConfig): void
Parameters:
videoPath (string): Path to video file (local or remote HTTPS URL)config (EditorConfig, optional): Configuration options (see Configuration Options)Example:
showEditor('/path/to/video.mp4', {
maxDuration: 30,
saveToPhoto: true,
});
Programmatically trim a video without showing the UI.
trim(url: string, options: TrimOptions): Promise<TrimResult>
Returns: Promise resolving to the TrimResult interface
Example:
const outputPath = await trim('/path/to/video.mp4', {
startTime: 5000, // 5 seconds
endTime: 25000, // 25 seconds
});
| Method | Description | Returns |
|---|---|---|
isValidFile(videoPath) | Check if file is valid video/audio | Promise<boolean> |
listFiles() | List all generated output files | Promise<string[]> |
cleanFiles() | Delete all generated files | Promise<number> |
deleteFile(filePath) | Delete specific file | Promise<boolean> |
closeEditor() | Close the editor interface | void |
Examples:
// Validate file before processing
const isValid = await isValidFile('/path/to/video.mp4');
if (!isValid) {
console.log('Invalid video file');
return;
}
// Clean up generated files
const deletedCount = await cleanFiles();
console.log(`Deleted ${deletedCount} files`);
All configuration options are optional. Here are the most commonly used ones:
| Option | Type | Default | Description |
|---|---|---|---|
type | 'video' | 'audio' | 'video' | Media type to trim |
outputExt | string | 'mp4' | Output file extension |
maxDuration | number | video duration | Maximum duration in milliseconds |
minDuration | number | 1000 | Minimum duration in milliseconds |
autoplay | boolean | false | Auto-play media on load |
jumpToPositionOnLoad | number | - | Initial position in milliseconds |
| Option | Type | Default | Description |
|---|---|---|---|
saveToPhoto | boolean | false | Save to photo gallery (requires permissions) |
openDocumentsOnFinish | boolean | false | Open document picker when done |
openShareSheetOnFinish | boolean | false | Open share sheet when done |
removeAfterSavedToPhoto | boolean | false | Delete file after saving to photos |
removeAfterFailedToSavePhoto | boolean | false | Delete file if saving to photos fails |
removeAfterSavedToDocuments | boolean | false | Delete file after saving to documents |
removeAfterFailedToSaveDocuments | boolean | false | Delete file if saving to documents fails |
removeAfterShared | boolean | false | Delete file after sharing (iOS only) |
removeAfterFailedToShare | boolean | false | Delete file if sharing fails (iOS only) |
| Option | Type | Default | Description |
|---|---|---|---|
cancelButtonText | string | "Cancel" | Cancel button text |
saveButtonText | string | "Save" | Save button text |
trimmingText | string | "Trimming video..." | Progress dialog text |
headerText | string | - | Header text |
headerTextSize | number | 16 | Header text size |
headerTextColor | string | - | Header text color |
trimmerColor | string | - | Trimmer bar color |
handleIconColor | string | - | Trimmer left/right handles color |
fullScreenModalIOS | boolean | false | Use fullscreen modal on iOS |
| Option | Type | Default | Description |
|---|---|---|---|
enableCancelDialog | boolean | true | Show confirmation dialog on cancel |
cancelDialogTitle | string | "Warning!" | Cancel dialog title |
cancelDialogMessage | string | "Are you sure want to cancel?" | Cancel dialog message |
cancelDialogCancelText | string | "Close" | Cancel dialog cancel button text |
cancelDialogConfirmText | string | "Proceed" | Cancel dialog confirm button text |
| Option | Type | Default | Description |
|---|---|---|---|
enableSaveDialog | boolean | true | Show confirmation dialog on save |
saveDialogTitle | string | "Confirmation!" | Save dialog title |
saveDialogMessage | string | "Are you sure want to save?" | Save dialog message |
saveDialogCancelText | string | "Close" | Save dialog cancel button text |
saveDialogConfirmText | string | "Proceed" | Save dialog confirm button text |
| Option | Type | Default | Description |
|---|---|---|---|
enableCancelTrimming | boolean | true | Enable cancel during trimming |
cancelTrimmingButtonText | string | "Cancel" | Cancel trimming button text |
enableCancelTrimmingDialog | boolean | true | Show cancel trimming confirmation |
cancelTrimmingDialogTitle | string | "Warning!" | Cancel trimming dialog title |
cancelTrimmingDialogMessage | string | "Are you sure want to cancel trimming?" | Cancel trimming dialog message |
cancelTrimmingDialogCancelText | string | "Close" | Cancel trimming dialog cancel button |
cancelTrimmingDialogConfirmText | string | "Proceed" | Cancel trimming dialog confirm button |
| Option | Type | Default | Description |
|---|---|---|---|
alertOnFailToLoad | boolean | true | Show alert dialog on load failure |
alertOnFailTitle | string | "Error" | Error dialog title |
alertOnFailMessage | string | "Fail to load media..." | Error dialog message |
alertOnFailCloseText | string | "Close" | Error dialog close button text |
| Option | Type | Default | Description |
|---|---|---|---|
enableHapticFeedback | boolean | true | Enable haptic feedback |
closeWhenFinish | boolean | true | Close editor when done |
enablePreciseTrimming | boolean | false | Re-encode for frame-accurate cuts (slower, see Precise Frame Trimming) |
changeStatusBarColorOnOpen | boolean | false | Change status bar color (Android only) |
zoomOnWaitingDuration | number | 5000 | Duration for zoom-on-waiting feature in milliseconds (default: 5000) |
showEditor(videoPath, {
// Basic settings
maxDuration: 60000,
minDuration: 3000,
// Save options
saveToPhoto: true,
openShareSheetOnFinish: true,
removeAfterSavedToPhoto: true,
// UI customization
headerText: "Trim Your Video",
cancelButtonText: "Back",
saveButtonText: "Done",
trimmerColor: "#007AFF",
// Behavior
autoplay: true,
enableCancelTrimming: true,
});
You can override SDK versions in android/build.gradle:
buildscript {
ext {
VideoTrim_kotlinVersion = '2.0.21'
VideoTrim_minSdkVersion = 24
VideoTrim_targetSdkVersion = 34
VideoTrim_compileSdkVersion = 35
VideoTrim_ndkVersion = '27.1.12297006'
}
}
For audio-only trimming, specify the media type and output format:
showEditor(audioUrl, {
type: 'audio', // Enable audio mode
outputExt: 'wav', // Output format (mp3, wav, m4a, etc.)
maxDuration: 30000, // 30 seconds max
});
To trim remote files, you need the HTTPS-enabled version of FFmpeg:
Android:
// android/build.gradle
buildscript {
ext {
VideoTrim_ffmpeg_package = 'https'
// Optional: VideoTrim_ffmpeg_version = '6.0.1'
}
}
iOS:
FFMPEGKIT_PACKAGE=https FFMPEG_KIT_PACKAGE_VERSION=6.0 pod install
Usage:
showEditor('https://example.com/video.mp4', {
maxDuration: 60000,
});
The editor includes built-in transform controls — horizontal flip, 90° left rotation, and freeform crop — with full undo/redo support. These appear as toolbar buttons in the editor UI on both iOS and Android.
When any transform is applied, FFmpeg automatically re-encodes the video using the platform's hardware encoder (h264_videotoolbox on iOS, h264_mediacodec on Android) at the source bitrate to preserve quality. No additional configuration is needed.
By default, trimming uses FFmpeg's stream copy (-c copy), which is very fast but can only cut at keyframes. The actual start/end points may drift by several seconds from what the user selected.
Enable enablePreciseTrimming for frame-accurate cuts:
// Editor mode
showEditor(videoUrl, {
enablePreciseTrimming: true,
});
// Headless mode
const result = await trim(videoUrl, {
startTime: 5000,
endTime: 15000,
enablePreciseTrimming: true,
});
enablePreciseTrimming: false (default) | enablePreciseTrimming: true | |
|---|---|---|
| Speed | Very fast (stream copy) | Slower (hardware re-encode) |
| Accuracy | Keyframe-aligned (may drift 1-5s) | Frame-accurate |
| Quality | Lossless (original bitstream) | Near-lossless (matched bitrate) |
Note: When transforms (flip/rotate/crop) are applied, re-encoding already happens regardless of this flag, so precise trimming comes for free in that case.
Users can cancel trimming while in progress:
showEditor(videoUrl, {
enableCancelTrimming: true,
cancelTrimmingButtonText: "Stop",
trimmingText: "Processing video...",
});
Handle loading errors gracefully:
showEditor(videoUrl, {
alertOnFailToLoad: true,
alertOnFailTitle: "Oops!",
alertOnFailMessage: "Cannot load this video file",
alertOnFailCloseText: "OK",
});
import React, { useEffect, useRef } from 'react';
import { TouchableOpacity, Text, View } from 'react-native';
import { showEditor, isValidFile, type Spec } from 'react-native-video-trim';
import { launchImageLibrary } from 'react-native-image-picker';
export default function VideoTrimmer() {
const listeners = useRef({});
useEffect(() => {
// Set up event listeners
listeners.current.onFinishTrimming = (NativeVideoTrim as Spec)
.onFinishTrimming(({ outputPath, startTime, endTime, duration }) => {
console.log('Trimming completed:', {
outputPath,
startTime,
endTime,
duration
});
});
listeners.current.onError = (NativeVideoTrim as Spec)
.onError(({ message, errorCode }) => {
console.error('Trimming error:', message, errorCode);
});
return () => {
// Cleanup listeners
Object.values(listeners.current).forEach(listener =>
listener?.remove()
);
};
}, []);
const selectAndTrimVideo = async () => {
const result = await launchImageLibrary({
mediaType: 'video',
quality: 1,
});
if (result.assets?.[0]?.uri) {
const videoUri = result.assets[0].uri;
// Validate file first
const isValid = await isValidFile(videoUri);
if (!isValid) {
console.log('Invalid video file');
return;
}
// Open editor
showEditor(videoUri, {
maxDuration: 60000, // 1 minute max
saveToPhoto: true, // Save to gallery
openShareSheetOnFinish: true,
headerText: "Trim Video",
trimmerColor: "#007AFF",
});
}
};
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<TouchableOpacity
onPress={selectAndTrimVideo}
style={{
backgroundColor: '#007AFF',
padding: 15,
borderRadius: 8
}}
>
<Text style={{ color: 'white', fontSize: 16 }}>
Select & Trim Video
</Text>
</TouchableOpacity>
</View>
);
}
import React, { useEffect } from 'react';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { showEditor } from 'react-native-video-trim';
export default function VideoTrimmer() {
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
const subscription = eventEmitter.addListener('VideoTrim', (event) => {
switch (event.name) {
case 'onFinishTrimming':
console.log('Video trimmed:', event.outputPath);
break;
case 'onError':
console.error('Trimming failed:', event.message);
break;
// Handle other events...
}
});
return () => subscription.remove();
}, []);
// Rest of implementation...
}
Android Build Errors:
file_paths.xml exists for share functionalityAndroidManifest.xmliOS Build Errors:
pod install after installationRuntime Issues:
isValidFile() before processingtrim() for batch processing without UIcleanFiles()FAQs
Video trimmer for your React Native app
We found that react-native-video-trim demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.