react-native-clip-trim
v6.1.0-beta.1
Published
Video trimmer for your React Native app
Readme
Table of Contents
- Overview
- Installation
- Quick Start
- API Reference
- Configuration Options
- Platform Setup
- Advanced Features
- Examples
- Troubleshooting
Segmented Video Trim for React Native
Overview
A powerful, easy-to-use segmented video and audio trimming library for React Native applications. Create multiple video segments from different sources, manage them together with duration constraints, and build professional video editing workflows.
✨ Key Features
- 🎬 Segmented Video Editing - Create and manage multiple video segments from different sources
- ⏱️ Duration Management - Set maximum duration limits and track segment usage
- 📹 Video & Audio Support - Trim both video and audio files
- 🌐 Local & Remote Files - Support for local storage and HTTPS URLs
- 💾 Multiple Save Options - Photos, Documents, or Share to other apps
- ✅ File Validation - Built-in validation for media files
- 🗂️ File Management - List, clean up, and delete specific files
- 🔄 Universal Architecture - Works with both New and Old React Native architectures
🎛️ Core Capabilities
| Feature | Description | |---------|-------------| | Segmented Trimming | Create multiple video segments and manage them together | | Duration Tracking | Monitor total duration, available time, and segment count | | Precise Trimming | Visual controls for exact start/end time selection | | 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 |
Installation
npm install segmented-video-trim
# or
yarn add segmented-video-trimPlatform Setup
npx pod-install iosPermissions Required:
- For saving to Photos: Add
NSPhotoLibraryUsageDescriptiontoInfo.plist
For New Architecture:
cd android && ./gradlew generateCodegenArtifactsFromSchemaPermissions Required:
- For saving to Photos: Add to
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 prebuildThen rebuild your app. Note: Expo Go may not work due to native dependencies - use development builds or expo run:ios/expo run:android.
Quick Start
Get up and running in 3 simple steps:
import { showEditor } from 'segmented-video-trim';
// 1. Basic usage - open video editor
showEditor(videoUrl);
// 2. With duration limit (for segmented editing)
showEditor(videoUrl, {
maxDuration: 20, // Maximum duration in seconds
});
// 3. With save options
showEditor(videoUrl, {
maxDuration: 30,
saveToPhoto: true,
openShareSheetOnFinish: true,
});Segmented Video Editing
Create multiple video segments and manage them together:
import { showEditor } from 'segmented-video-trim';
import { SegmentManager } from './utils/SegmentManager';
// Initialize segment manager with max duration
const segmentManager = new SegmentManager(60); // 60 seconds max
// Add segments from different videos
const pickAndTrimVideo = async () => {
const videoUri = await pickVideoFromGallery();
// Get available time for this segment
const availableTime = segmentManager.getAvailableTime();
// Show editor with dynamic max duration
showEditor(videoUri, {
maxDuration: availableTime, // Constrain to available time
saveToPhoto: false,
});
};
// Handle trimmed segment
const handleSegmentComplete = ({ outputPath, startTime, endTime }) => {
const duration = (endTime - startTime) / 1000; // Convert to seconds
const segment = {
duration,
video: { uri: outputPath },
};
if (segmentManager.addSegment(segment)) {
console.log('Segment added:', {
totalDuration: segmentManager.getTotalDuration(),
availableTime: segmentManager.getAvailableTime(),
});
}
};Complete Example with File Picker
import { showEditor } from 'segmented-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.
API Reference
showEditor()
Opens the video trimmer interface.
showEditor(videoPath: string, config?: EditorConfig): voidParameters:
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,
});trim()
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
});File Management
| 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`);Configuration Options
All configuration options are optional. Here are the most commonly used ones:
Basic Options
| 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 |
Save & Share Options
| 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) |
UI Customization
| 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 |
Dialog Options
| 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 |
Advanced Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enableHapticFeedback | boolean | true | Enable haptic feedback |
| closeWhenFinish | boolean | true | Close editor when done |
| enableRotation | boolean | false | Enable video rotation |
| rotationAngle | number | 0 | Rotation angle in degrees |
| changeStatusBarColorOnOpen | boolean | false | Change status bar color (Android only) |
| zoomOnWaitingDuration | number | 5000 | Duration for zoom-on-waiting feature in milliseconds (default: 5000) |
Example Configuration
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,
});Platform Setup
Android SDK Customization
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'
}
}Advanced Features
Audio Trimming
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
});Remote Files (HTTPS)
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 installUsage:
showEditor('https://example.com/video.mp4', {
maxDuration: 60000,
});Video Rotation
Rotate videos during trimming using metadata (doesn't re-encode):
showEditor(videoUrl, {
enableRotation: true,
rotationAngle: 90, // 90, 180, 270 degrees
});Note: Uses display_rotation metadata - playback may vary by platform/player.
Trimming Progress & Cancellation
Users can cancel trimming while in progress:
showEditor(videoUrl, {
enableCancelTrimming: true,
cancelTrimmingButtonText: "Stop",
trimmingText: "Processing video...",
});Error Handling
Handle loading errors gracefully:
showEditor(videoUrl, {
alertOnFailToLoad: true,
alertOnFailTitle: "Oops!",
alertOnFailMessage: "Cannot load this video file",
alertOnFailCloseText: "OK",
});Examples
Segmented Video Editing (New Architecture)
import React, { useEffect, useRef, useState } from 'react';
import { TouchableOpacity, Text, View } from 'react-native';
import { showEditor, isValidFile, type Spec } from 'segmented-video-trim';
import { launchImageLibrary } from 'react-native-image-picker';
import { SegmentManager, type Segment } from './utils/SegmentManager';
import NativeVideoTrim from 'segmented-video-trim';
export default function SegmentedVideoTrimmer() {
const [segments, setSegments] = useState<Segment[]>([]);
const [maxDuration] = useState(60); // 60 seconds max
const segmentManagerRef = useRef(new SegmentManager(maxDuration));
const listeners = useRef({});
useEffect(() => {
// Set up event listeners
listeners.current.onFinishTrimming = (NativeVideoTrim as Spec)
.onFinishTrimming(({ outputPath, startTime, endTime }) => {
const duration = (endTime - startTime) / 1000; // Convert to seconds
const segment = {
duration,
video: { uri: outputPath },
sourceType: 'gallery' as const,
};
if (segmentManagerRef.current.addSegment(segment)) {
setSegments(segmentManagerRef.current.getSegments());
console.log('Segment added:', {
totalDuration: segmentManagerRef.current.getTotalDuration(),
availableTime: segmentManagerRef.current.getAvailableTime(),
});
}
});
listeners.current.onError = (NativeVideoTrim as Spec)
.onError(({ message, errorCode }) => {
console.error('Trimming error:', message, errorCode);
});
return () => {
Object.values(listeners.current).forEach(listener =>
listener?.remove()
);
};
}, []);
const addVideoSegment = 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;
}
// Get available time for this segment
const availableTime = segmentManagerRef.current.getAvailableTime();
if (availableTime <= 0) {
console.log('No time remaining');
return;
}
// Open editor with dynamic max duration
showEditor(videoUri, {
maxDuration: availableTime, // Constrain to available time
saveToPhoto: false,
headerText: "Trim Video Segment",
trimmerColor: "#007AFF",
});
}
};
const totalDuration = segmentManagerRef.current.getTotalDuration();
const availableTime = segmentManagerRef.current.getAvailableTime();
return (
<View style={{ flex: 1, padding: 20 }}>
<Text>Max Duration: {maxDuration}s</Text>
<Text>Used: {totalDuration.toFixed(1)}s</Text>
<Text>Available: {availableTime.toFixed(1)}s</Text>
<Text>Segments: {segments.length}</Text>
<TouchableOpacity
onPress={addVideoSegment}
disabled={availableTime <= 0}
style={{
backgroundColor: availableTime > 0 ? '#007AFF' : '#ccc',
padding: 15,
borderRadius: 8,
marginTop: 20,
}}
>
<Text style={{ color: 'white', fontSize: 16 }}>
Add Video Segment
</Text>
</TouchableOpacity>
</View>
);
}Old Architecture Implementation
import React, { useEffect } from 'react';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { showEditor } from 'segmented-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...
}Troubleshooting
Common Issues
Android Build Errors:
- Ensure
file_paths.xmlexists for share functionality - Check SDK versions match your project requirements
- Verify permissions in
AndroidManifest.xml
iOS Build Errors:
- Run
pod installafter installation - Check Info.plist permissions for photo access
- Use development builds with Expo (not Expo Go)
Runtime Issues:
- Validate files with
isValidFile()before processing - Use HTTPS version for remote files
- Check network connectivity for remote files
- Ensure proper permissions for save operations
Performance Tips
- Use
trim()for batch processing without UI - Clean up generated files regularly with
cleanFiles() - Consider file compression for large videos
Credits
- Android: Based on Android-Video-Trimmer
- iOS: UI from VideoTrimmerControl
