react-native-video-trim 6.0.0 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,534 +1,575 @@
1
- # Table of contents
1
+ # Table of Contents
2
+ - [Overview](#overview)
2
3
  - [Installation](#installation)
3
- * [For iOS (React Native CLI project)](#for-ios-react-native-cli-project)
4
- * [For Android New Arch (React Native CLI project)](#for-android-new-arch-react-native-cli-project)
5
- * [For Expo project](#for-expo-project)
6
- * [Usage](#usage)
7
- - [Methods](#methods)
8
- * [showEditor(videoPath: string, config?: EditorConfig) => void)](#showeditorvideopath-string-config-editorconfig--void)
9
- * [trim(url: string, options: TrimOptions): Promise<string>](#trimurl-string-options-trimoptions-promise)
10
- * [isValidFile(videoPath: string)](#isvalidfilevideopath-string)
11
- * [closeEditor()](#closeeditor)
12
- * [listFiles()](#listfiles)
13
- * [cleanFiles()](#cleanfiles)
14
- * [deleteFile()](#deletefile)
15
- - [Audio only support](#audio-only-support)
16
- - [Cancel trimming](#cancel-trimming)
17
- - [Fail to load media](#fail-to-load-media)
18
- - [Rotation](#rotation)
19
- - [Use FFMPEG HTTPS version](#use-ffmpeg-https-version)
20
- - [Android: update SDK version](#android-update-sdk-version)
21
- - [Thanks](#thanks)
4
+ - [Quick Start](#quick-start)
5
+ - [API Reference](#api-reference)
6
+ * [showEditor()](#showeditor)
7
+ * [trim()](#trim)
8
+ * [File Management](#file-management)
9
+ - [Configuration Options](#configuration-options)
10
+ * [Basic Options](#basic-options)
11
+ * [UI Customization](#ui-customization)
12
+ * [Behavior Options](#behavior-options)
13
+ - [Platform Setup](#platform-setup)
14
+ - [Advanced Features](#advanced-features)
15
+ * [Audio Trimming](#audio-trimming)
16
+ * [Remote Files (HTTPS)](#remote-files-https)
17
+ * [Video Rotation](#video-rotation)
18
+ - [Examples](#examples)
19
+ - [Troubleshooting](#troubleshooting)
22
20
 
23
21
  # React Native Video Trim
24
- <div align="center">
25
- <h2>Video trimmer for your React Native app</h2>
26
22
 
27
- <img src="images/android.gif" width="300" />
28
- <img src="images/ios.gif" width="300" />
23
+ <div align="center">
24
+ <h2>📱 Professional video trimmer for React Native apps</h2>
25
+
26
+ <img src="images/android.gif" width="300" />
27
+ <img src="images/ios.gif" width="300" />
28
+
29
+ <p>
30
+ <strong>✅ iOS & Android</strong> •
31
+ <strong>✅ New & Old Architecture</strong> •
32
+ <strong>✅ Expo Compatible</strong>
33
+ </p>
29
34
  </div>
30
35
 
31
- ## Features
32
- - ✅ Support video and audio
33
- - ✅ Support local files and remote files (remote files need `https` version, see below)
34
- - ✅ Save to Photos, Documents and Share to other apps
35
- - ✅ Check if file is valid video/audio
36
- - ✅ File operations: list, clean up, delete specific file
37
- - ✅ Support React Native New + Old Arch
38
-
39
- <div align="left">
40
- <img src="images/document_picker.png" width="300" />
41
- <img src="images/share_sheet.png" width="300" />
42
- </div>
36
+ ## Overview
43
37
 
44
- # Installation
38
+ A powerful, easy-to-use video and audio trimming library for React Native applications.
45
39
 
46
- Both new + old arch are supported in a single distribution
40
+ ### Key Features
47
41
 
48
- ```sh
49
- npm install react-native-video-trim
42
+ - **📹 Video & Audio Support** - Trim both video and audio files
43
+ - **🌐 Local & Remote Files** - Support for local storage and HTTPS URLs
44
+ - **💾 Multiple Save Options** - Photos, Documents, or Share to other apps
45
+ - **✅ File Validation** - Built-in validation for media files
46
+ - **🗂️ File Management** - List, clean up, and delete specific files
47
+ - **🔄 Universal Architecture** - Works with both New and Old React Native architectures
50
48
 
51
- # or with yarn
52
- yarn add react-native-video-trim
49
+ ### 🎛️ Core Capabilities
53
50
 
54
- ```
51
+ | Feature | Description |
52
+ |---------|-------------|
53
+ | **Trimming** | Precise video/audio trimming with visual controls |
54
+ | **Validation** | Check if files are valid video/audio before processing |
55
+ | **Save Options** | Photos, Documents, Share sheet integration |
56
+ | **File Management** | Complete file lifecycle management |
57
+ | **Customization** | Extensive UI and behavior customization |
55
58
 
56
- ## iOS (RN CLI project)
57
- Run the following command to setup for iOS:
59
+ <div align="center">
60
+ <img src="images/document_picker.png" width="250" />
61
+ <img src="images/share_sheet.png" width="250" />
62
+ </div>
63
+
64
+ ## Installation
65
+
66
+ ```bash
67
+ npm install react-native-video-trim
68
+ # or
69
+ yarn add react-native-video-trim
58
70
  ```
71
+
72
+ ### Platform Setup
73
+
74
+ <details>
75
+ <summary><strong>📱 iOS Setup (React Native CLI)</strong></summary>
76
+
77
+ ```bash
59
78
  npx pod-install ios
60
79
  ```
61
- ## Android New Arch (RN CLI project)
62
- If you are using New Arch, in `android` folder run:
80
+
81
+ **Permissions Required:**
82
+ - For saving to Photos: Add `NSPhotoLibraryUsageDescription` to `Info.plist`
83
+ </details>
84
+
85
+ <details>
86
+ <summary><strong>🤖 Android Setup (React Native CLI)</strong></summary>
87
+
88
+ **For New Architecture:**
89
+ ```bash
90
+ cd android && ./gradlew generateCodegenArtifactsFromSchema
91
+ ```
92
+
93
+ **Permissions Required:**
94
+ - For saving to Photos: Add to `AndroidManifest.xml`:
95
+ ```xml
96
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
63
97
  ```
64
- ./gradlew generateCodegenArtifactsFromSchema
98
+
99
+ **For Share Sheet functionality**, add to `AndroidManifest.xml`:
100
+ ```xml
101
+ <application>
102
+ <!-- your other configs -->
103
+
104
+ <provider
105
+ android:name="androidx.core.content.FileProvider"
106
+ android:authorities="${applicationId}.provider"
107
+ android:exported="false"
108
+ android:grantUriPermissions="true">
109
+ <meta-data
110
+ android:name="android.support.FILE_PROVIDER_PATHS"
111
+ android:resource="@xml/file_paths" />
112
+ </provider>
113
+ </application>
65
114
  ```
66
- ## For Expo project
67
- You need to run `prebuild` in order for native code takes effect:
115
+
116
+ Create `android/app/src/main/res/xml/file_paths.xml`:
117
+ ```xml
118
+ <?xml version="1.0" encoding="utf-8"?>
119
+ <paths xmlns:android="http://schemas.android.com/apk/res/android">
120
+ <files-path name="internal_files" path="." />
121
+ <external-path name="external_files" path="." />
122
+ </paths>
68
123
  ```
124
+ </details>
125
+
126
+ <details>
127
+ <summary><strong>🔧 Expo Setup</strong></summary>
128
+
129
+ ```bash
69
130
  npx expo prebuild
70
131
  ```
71
- Then you should to restart to make the changes take effect
72
-
73
- Note that on iOS, Expo Go may not work because of library linking, you may see this error:
74
132
 
75
- <img src="images/expo_error.PNG" width="150" />
133
+ Then rebuild your app. **Note:** Expo Go may not work due to native dependencies - use development builds or `expo run:ios`/`expo run:android`.
134
+ </details>
76
135
 
77
- To avoid the error, you should open `ios/yourproject.xcworkspace` then manually build and run your app
136
+ ## Quick Start
78
137
 
79
- ## Usage
138
+ Get up and running in 3 simple steps:
80
139
 
81
- ```js
140
+ ```javascript
82
141
  import { showEditor } from 'react-native-video-trim';
83
142
 
84
- // ...
85
-
143
+ // 1. Basic usage - open video editor
86
144
  showEditor(videoUrl);
87
145
 
88
- // or with output length limit
89
-
146
+ // 2. With duration limit
90
147
  showEditor(videoUrl, {
91
148
  maxDuration: 20,
92
149
  });
150
+
151
+ // 3. With save options
152
+ showEditor(videoUrl, {
153
+ maxDuration: 30,
154
+ saveToPhoto: true,
155
+ openShareSheetOnFinish: true,
156
+ });
93
157
  ```
94
- Usually this library will be used along with other library to select video file, Eg. [react-native-image-picker](https://github.com/react-native-image-picker/react-native-image-picker). Below are real world examples:
95
158
 
96
- <details>
97
- <summary>New Arch usage</summary>
98
-
99
- ```jsx
100
- import * as React from 'react';
101
-
102
- import {
103
- StyleSheet,
104
- View,
105
- Text,
106
- TouchableOpacity,
107
- type EventSubscription,
108
- } from 'react-native';
109
- import { isValidFile, showEditor, type Spec } from 'react-native-video-trim';
159
+ ### Complete Example with File Picker
160
+
161
+ ```javascript
162
+ import { showEditor } from 'react-native-video-trim';
110
163
  import { launchImageLibrary } from 'react-native-image-picker';
111
164
 
112
- export default function App() {
113
- const listenerSubscription = useRef<Record<string, EventSubscription>>({});
165
+ const trimVideo = () => {
166
+ // Pick a video
167
+ launchImageLibrary({ mediaType: 'video' }, (response) => {
168
+ if (response.assets && response.assets[0]) {
169
+ const videoUri = response.assets[0].uri;
170
+
171
+ // Open editor
172
+ showEditor(videoUri, {
173
+ maxDuration: 60, // 60 seconds max
174
+ saveToPhoto: true,
175
+ });
176
+ }
177
+ });
178
+ };
179
+ ```
114
180
 
115
- useEffect(() => {
116
- listenerSubscription.current.onLoad = (NativeVideoTrim as Spec).onLoad(
117
- ({ duration }) => console.log('onLoad', duration)
118
- );
119
-
120
- listenerSubscription.current.onStartTrimming =
121
- (NativeVideoTrim as Spec).onStartTrimming(() => console.log('onStartTrimming'));
122
-
123
- listenerSubscription.current.onCancelTrimming =
124
- (NativeVideoTrim as Spec).onCancelTrimming(() => console.log('onCancelTrimming'));
125
- listenerSubscription.current.onCancel = (NativeVideoTrim as Spec).onCancel(() =>
126
- console.log('onCancel')
127
- );
128
- listenerSubscription.current.onHide = (NativeVideoTrim as Spec).onHide(() =>
129
- console.log('onHide')
130
- );
131
- listenerSubscription.current.onShow = (NativeVideoTrim as Spec).onShow(() =>
132
- console.log('onShow')
133
- );
134
- listenerSubscription.current.onFinishTrimming =
135
- (NativeVideoTrim as Spec).onFinishTrimming(
136
- ({ outputPath, startTime, endTime, duration }) =>
137
- console.log(
138
- 'onFinishTrimming',
139
- `outputPath: ${outputPath}, startTime: ${startTime}, endTime: ${endTime}, duration: ${duration}`
140
- )
141
- );
142
- listenerSubscription.current.onLog = (NativeVideoTrim as Spec).onLog(
143
- ({ level, message, sessionId }) =>
144
- console.log(
145
- 'onLog',
146
- `level: ${level}, message: ${message}, sessionId: ${sessionId}`
147
- )
148
- );
149
- listenerSubscription.current.onStatistics = (NativeVideoTrim as Spec).onStatistics(
150
- ({
151
- sessionId,
152
- videoFrameNumber,
153
- videoFps,
154
- videoQuality,
155
- size,
156
- time,
157
- bitrate,
158
- speed,
159
- }) =>
160
- console.log(
161
- 'onStatistics',
162
- `sessionId: ${sessionId}, videoFrameNumber: ${videoFrameNumber}, videoFps: ${videoFps}, videoQuality: ${videoQuality}, size: ${size}, time: ${time}, bitrate: ${bitrate}, speed: ${speed}`
163
- )
164
- );
165
- listenerSubscription.current.onError = (NativeVideoTrim as Spec).onError(
166
- ({ message, errorCode }) =>
167
- console.log('onError', `message: ${message}, errorCode: ${errorCode}`)
168
- );
181
+ > 💡 **More Examples:** Check the [example folder](./example/src/) for complete implementation details with event listeners for both New and Old architectures.
169
182
 
170
- return () => {
171
- listenerSubscription.current.onLoad?.remove();
172
- listenerSubscription.current.onStartTrimming?.remove();
173
- listenerSubscription.current.onCancelTrimming?.remove();
174
- listenerSubscription.current.onCancel?.remove();
175
- listenerSubscription.current.onHide?.remove();
176
- listenerSubscription.current.onShow?.remove();
177
- listenerSubscription.current.onFinishTrimming?.remove();
178
- listenerSubscription.current.onLog?.remove();
179
- listenerSubscription.current.onStatistics?.remove();
180
- listenerSubscription.current.onError?.remove();
181
- listenerSubscription.current = {};
182
- };
183
- });
183
+ ## API Reference
184
184
 
185
- return (
186
- <View style={styles.container}>
187
- <TouchableOpacity
188
- onPress={async () => {
189
- const result = await launchImageLibrary({
190
- mediaType: 'video',
191
- assetRepresentationMode: 'current',
192
- });
193
-
194
- isValidFile(result.assets![0]?.uri || '').then((res) =>
195
- console.log(res)
196
- );
197
-
198
- showEditor(result.assets![0]?.uri || '', {
199
- maxDuration: 20,
200
- });
201
- }}
202
- style={{ padding: 10, backgroundColor: 'red' }}
203
- >
204
- <Text>Launch Library</Text>
205
- </TouchableOpacity>
206
- <TouchableOpacity
207
- onPress={() => {
208
- isValidFile('invalid file path').then((res) => console.log(res));
209
- }}
210
- style={{
211
- padding: 10,
212
- backgroundColor: 'blue',
213
- marginTop: 20,
214
- }}
215
- >
216
- <Text>Check Video Valid</Text>
217
- </TouchableOpacity>
218
- </View>
219
- );
220
- }
185
+ ### showEditor()
186
+
187
+ Opens the video trimmer interface.
221
188
 
222
- const styles = StyleSheet.create({
223
- container: {
224
- flex: 1,
225
- alignItems: 'center',
226
- justifyContent: 'center',
227
- },
189
+ ```typescript
190
+ showEditor(videoPath: string, config?: EditorConfig): void
191
+ ```
192
+
193
+ **Parameters:**
194
+ - `videoPath` (string): Path to video file (local or remote HTTPS URL)
195
+ - `config` (EditorConfig, optional): Configuration options (see [Configuration Options](#configuration-options))
196
+
197
+ **Example:**
198
+ ```javascript
199
+ showEditor('/path/to/video.mp4', {
200
+ maxDuration: 30,
201
+ saveToPhoto: true,
228
202
  });
229
203
  ```
230
- </details>
231
204
 
232
- <br />
205
+ ### trim()
233
206
 
234
- <details>
235
- <summary>Old Arch usage</summary>
236
-
237
- ```jsx
238
- import * as React from 'react';
239
-
240
- import {
241
- StyleSheet,
242
- View,
243
- Text,
244
- TouchableOpacity,
245
- NativeEventEmitter,
246
- NativeModules,
247
- } from 'react-native';
248
- import { isValidFile, showEditor } from 'react-native-video-trim';
249
- import { launchImageLibrary } from 'react-native-image-picker';
207
+ Programmatically trim a video without showing the UI.
250
208
 
251
- export default function App() {
252
- useEffect(() => {
253
- const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
254
- const subscription = eventEmitter.addListener('VideoTrim', (event) => {
255
- switch (event.name) {
256
- case 'onLoad': {
257
- console.log('onLoadListener', event);
258
- break;
259
- }
260
- case 'onShow': {
261
- console.log('onShowListener', event);
262
- break;
263
- }
264
- case 'onHide': {
265
- console.log('onHide', event);
266
- break;
267
- }
268
- case 'onStartTrimming': {
269
- console.log('onStartTrimming', event);
270
- break;
271
- }
272
- case 'onFinishTrimming': {
273
- console.log('onFinishTrimming', event);
274
- break;
275
- }
276
- case 'onCancelTrimming': {
277
- console.log('onCancelTrimming', event);
278
- break;
279
- }
280
- case 'onCancel': {
281
- console.log('onCancel', event);
282
- break;
283
- }
284
- case 'onError': {
285
- console.log('onError', event);
286
- break;
287
- }
288
- case 'onLog': {
289
- console.log('onLog', event);
290
- break;
291
- }
292
- case 'onStatistics': {
293
- console.log('onStatistics', event);
294
- break;
295
- }
296
- }
297
- });
209
+ ```typescript
210
+ trim(url: string, options: TrimOptions): Promise<string>
211
+ ```
298
212
 
299
- return () => {
300
- subscription.remove();
301
- };
302
- }, []);
213
+ **Returns:** Promise resolving to the output file path
303
214
 
304
- return (
305
- <View style={styles.container}>
306
- <TouchableOpacity
307
- onPress={async () => {
308
- const result = await launchImageLibrary({
309
- mediaType: 'video',
310
- assetRepresentationMode: 'current',
311
- });
312
-
313
- isValidFile(result.assets![0]?.uri || '').then((res) =>
314
- console.log(res)
315
- );
316
-
317
- showEditor(result.assets![0]?.uri || '', {
318
- maxDuration: 20,
319
- });
320
- }}
321
- style={{ padding: 10, backgroundColor: 'red' }}
322
- >
323
- <Text>Launch Library</Text>
324
- </TouchableOpacity>
325
- <TouchableOpacity
326
- onPress={() => {
327
- isValidFile('invalid file path').then((res) => console.log(res));
328
- }}
329
- style={{
330
- padding: 10,
331
- backgroundColor: 'blue',
332
- marginTop: 20,
333
- }}
334
- >
335
- <Text>Check Video Valid</Text>
336
- </TouchableOpacity>
337
- </View>
338
- );
215
+ **Example:**
216
+ ```javascript
217
+ const outputPath = await trim('/path/to/video.mp4', {
218
+ startTime: 5000, // 5 seconds
219
+ endTime: 25000, // 25 seconds
220
+ });
221
+ ```
222
+
223
+ ### File Management
224
+
225
+ | Method | Description | Returns |
226
+ |--------|-------------|---------|
227
+ | `isValidFile(videoPath)` | Check if file is valid video/audio | `Promise<boolean>` |
228
+ | `listFiles()` | List all generated output files | `Promise<string[]>` |
229
+ | `cleanFiles()` | Delete all generated files | `Promise<number>` |
230
+ | `deleteFile(filePath)` | Delete specific file | `Promise<boolean>` |
231
+ | `closeEditor()` | Close the editor interface | `void` |
232
+
233
+ **Examples:**
234
+ ```javascript
235
+ // Validate file before processing
236
+ const isValid = await isValidFile('/path/to/video.mp4');
237
+ if (!isValid) {
238
+ console.log('Invalid video file');
239
+ return;
339
240
  }
340
241
 
341
- const styles = StyleSheet.create({
342
- container: {
343
- flex: 1,
344
- alignItems: 'center',
345
- justifyContent: 'center',
346
- },
347
- });
242
+ // Clean up generated files
243
+ const deletedCount = await cleanFiles();
244
+ console.log(`Deleted ${deletedCount} files`);
348
245
  ```
349
- </details>
350
246
 
351
- <br />
247
+ ## Configuration Options
248
+
249
+ All configuration options are optional. Here are the most commonly used ones:
250
+
251
+ ### Basic Options
252
+
253
+ | Option | Type | Default | Description |
254
+ |--------|------|---------|-------------|
255
+ | `type` | `'video' \| 'audio'` | `'video'` | Media type to trim |
256
+ | `outputExt` | `string` | `'mp4'` | Output file extension |
257
+ | `maxDuration` | `number` | - | Maximum duration in milliseconds |
258
+ | `minDuration` | `number` | `1000` | Minimum duration in milliseconds |
259
+ | `autoplay` | `boolean` | `false` | Auto-play media on load |
260
+ | `jumpToPositionOnLoad` | `number` | - | Initial position in milliseconds |
261
+
262
+ ### Save & Share Options
263
+
264
+ | Option | Type | Default | Description |
265
+ |--------|------|---------|-------------|
266
+ | `saveToPhoto` | `boolean` | `false` | Save to photo gallery (requires permissions) |
267
+ | `openDocumentsOnFinish` | `boolean` | `false` | Open document picker when done |
268
+ | `openShareSheetOnFinish` | `boolean` | `false` | Open share sheet when done |
269
+ | `removeAfterSavedToPhoto` | `boolean` | `false` | Delete file after saving to photos |
270
+ | `removeAfterShared` | `boolean` | `false` | Delete file after sharing (iOS only) |
271
+
272
+ ### UI Customization
273
+
274
+ | Option | Type | Default | Description |
275
+ |--------|------|---------|-------------|
276
+ | `cancelButtonText` | `string` | `"Cancel"` | Cancel button text |
277
+ | `saveButtonText` | `string` | `"Save"` | Save button text |
278
+ | `trimmingText` | `string` | `"Trimming video..."` | Progress dialog text |
279
+ | `headerText` | `string` | - | Header text |
280
+ | `headerTextSize` | `number` | `16` | Header text size |
281
+ | `headerTextColor` | `string` | `"white"` | Header text color |
282
+ | `trimmerColor` | `string` | - | Trimmer bar color |
283
+ | `handleIconColor` | `string` | - | Trimmer handle color |
284
+ | `fullScreenModalIOS` | `boolean` | `false` | Use fullscreen modal on iOS |
285
+
286
+ ### Advanced Options
287
+
288
+ | Option | Type | Default | Description |
289
+ |--------|------|---------|-------------|
290
+ | `enableHapticFeedback` | `boolean` | `true` | Enable haptic feedback |
291
+ | `closeWhenFinish` | `boolean` | `true` | Close editor when done |
292
+ | `enableRotation` | `boolean` | `false` | Enable video rotation |
293
+ | `rotationAngle` | `number` | `0` | Rotation angle in degrees |
294
+ | `changeStatusBarColorOnOpen` | `boolean` | `false` | Change status bar color (Android) |
295
+ | `alertOnFailToLoad` | `boolean` | `true` | Show alert on load failure |
296
+
297
+ ### Example Configuration
298
+
299
+ ```javascript
300
+ showEditor(videoPath, {
301
+ // Basic settings
302
+ maxDuration: 60000,
303
+ minDuration: 3000,
304
+
305
+ // Save options
306
+ saveToPhoto: true,
307
+ openShareSheetOnFinish: true,
308
+ removeAfterSavedToPhoto: true,
309
+
310
+ // UI customization
311
+ headerText: "Trim Your Video",
312
+ cancelButtonText: "Back",
313
+ saveButtonText: "Done",
314
+ trimmerColor: "#007AFF",
315
+
316
+ // Behavior
317
+ autoplay: true,
318
+ enableCancelTrimming: true,
319
+ });
320
+ ```
352
321
 
353
- Checkout [Example folder](./example/src/) for more details
322
+ ## Platform Setup
354
323
 
355
- # Methods
324
+ ### Android SDK Customization
356
325
 
357
- ## showEditor(videoPath: string, config?: EditorConfig) => void)
358
- Main method to show Video Editor UI.
326
+ You can override SDK versions in `android/build.gradle`:
359
327
 
360
- *Params*:
361
- - `videoPath`: Path to video file, if this is an invalid path, `onError` event will be fired
362
- - `config` (optional, every sub props of `config` is optional):
363
-
364
- - `type` (`default = video`): which player to use, `video` or `audio`
365
- - `outputExt` (`default = mp4`): output file extension
366
- - `enableHapticFeedback` (`default = true`): whether to enable haptic feedback
367
- - `saveToPhoto` (Video-only, `default = false`): whether to save video to photo/gallery after editing
368
- - `openDocumentsOnFinish` (`default = false`): open Document Picker on done trimming
369
- - `openShareSheetOnFinish` (`default = false`): open Share Sheet on done trimming
370
- - `removeAfterSavedToPhoto` (`default = false`): whether to remove output file from storage after saved to Photo successfully
371
- - `removeAfterFailedToSavePhoto` (`default = false`): whether to remove output file if fail to save to Photo
372
- - `removeAfterSavedToDocuments` (`default = false`): whether to remove output file from storage after saved Documents successfully
373
- - `removeAfterFailedToSaveDocuments` (`default = false`): whether to remove output file from storage after fail to save to Documents
374
- - `removeAfterShared` (`default = false`): whether to remove output file from storage after saved Share successfully. iOS only, on Android you'll have to manually remove the file (this is because on Android there's no way to detect when sharing is successful)
375
- - `removeAfterFailedToShare` (`default = false`): whether to remove output file from storage after fail to Share. iOS only, on Android you'll have to manually remove the file
376
- - `maxDuration` (optional): maximum duration for the trimmed video
377
- - `minDuration` (`default = 1000`): minimum duration for the trimmed video
378
- - `cancelButtonText` (`default= "Cancel"`): text of left button in Editor dialog
379
- - `saveButtonText` (`default= "Save"`): text of right button in Editor dialog
380
- - `enableCancelDialog` (`default = true`): whether to show alert dialog on press Cancel
381
- - `cancelDialogTitle` (`default = "Warning!"`)
382
- - `cancelDialogMessage` (`default = "Are you sure want to cancel?"`)
383
- - `cancelDialogCancelText` (`default = "Close"`)
384
- - `cancelDialogConfirmText` (`default = "Proceed"`)
385
- - `enableSaveDialog` (`default = true`): whether to show alert dialog on press Save
386
- - `saveDialogTitle` (`default = "Confirmation!"`)
387
- - `saveDialogMessage` (`default = "Are you sure want to save?"`)
388
- - `saveDialogCancelText` (`default = "Close"`)
389
- - `saveDialogConfirmText` (`default = "Proceed"`)
390
- - `fullScreenModalIOS` (`default = false`): whether to open editor in fullscreen modal
391
- - `trimmingText` (`default = "Trimming video..."`): trimming text on the progress dialog
392
- - `autoplay` (`default = false`): whether to autoplay media on load
393
- - `jumpToPositionOnLoad` (optional): which time position should jump on media loaded (millisecond)
394
- - `closeWhenFinish` (`default = true`): should editor close on finish trimming
395
- - `enableCancelTrimming` (`default = true`): enable cancel trimming
396
- - `cancelTrimmingButtonText` (`default = "Cancel"`)
397
- - `enableCancelTrimmingDialog` (`default = true`)
398
- - `cancelTrimmingDialogTitle` (`default = "Warning!"`)
399
- - `cancelTrimmingDialogMessage` (`default = "Are you sure want to cancel trimming?"`)
400
- - `cancelTrimmingDialogCancelText` (`default = "Close"`)
401
- - `cancelTrimmingDialogConfirmText` (`default = "Proceed"`)
402
- - `headerText` (optional)
403
- - `headerTextSize` (`default = 16`)
404
- - `headerTextColor` (`default = white`)
405
- - `alertOnFailToLoad` (`default = true`)
406
- - `alertOnFailTitle` (`default = "Error"`)
407
- - `alertOnFailMessage` (`default = "Fail to load media. Possibly invalid file or no network connection"`)
408
- - `alertOnFailCloseText` (`default = "Close"`)
409
- - `enableRotation` (`default = false`)
410
- - `rotationAngle` (`default = 0`)
411
- - `changeStatusBarColorOnOpen` (`default = false`): (Android only) Update status bar color to black background color when editor is opened (useful in somecases where your theme has titlebar in different color than black)
412
-
413
- If `saveToPhoto = true`, you must ensure that you have request permission to write to photo/gallery
414
- - For Android: you need to have `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` in AndroidManifest.xml
415
- - For iOS: you need `NSPhotoLibraryUsageDescription` in Info.plist
416
-
417
- If `openShareSheetOnFinish=true`, on Android you'll need to update `AndroidManifest.xml` like below:
418
- ```xml
419
- </application>
420
- ...
421
- <provider
422
- android:name="androidx.core.content.FileProvider"
423
- android:authorities="${applicationId}.provider"
424
- android:exported="false"
425
- android:grantUriPermissions="true">
426
- <meta-data
427
- android:name="android.support.FILE_PROVIDER_PATHS"
428
- android:resource="@xml/file_paths" />
429
- </provider>
430
- </application>
328
+ ```gradle
329
+ buildscript {
330
+ ext {
331
+ VideoTrim_kotlinVersion = '2.0.21'
332
+ VideoTrim_minSdkVersion = 24
333
+ VideoTrim_targetSdkVersion = 34
334
+ VideoTrim_compileSdkVersion = 35
335
+ VideoTrim_ndkVersion = '27.1.12297006'
336
+ }
337
+ }
431
338
  ```
432
339
 
433
- If you face issue when building Android app related to `file_paths`, then you may need to create `res/xml/file_paths.xml`: with the following content:
434
- ```xml
435
- <?xml version="1.0" encoding="utf-8"?>
436
- <paths xmlns:android="http://schemas.android.com/apk/res/android">
437
- <files-path name="internal_files" path="." />
438
- <external-path name="external_files" path="." />
439
- </paths>
440
- ```
340
+ ## Advanced Features
441
341
 
442
- ## trim(url: string, options: TrimOptions): Promise<string>
342
+ ### Audio Trimming
443
343
 
444
- Directly trim a file without showing editor
344
+ <div align="center">
345
+ <img src="images/audio_android.jpg" width="200" />
346
+ <img src="images/audio_ios.jpg" width="200" />
347
+ </div>
445
348
 
446
- ## isValidFile(videoPath: string)
349
+ For audio-only trimming, specify the media type and output format:
447
350
 
448
- This method is to check if a path is a valid video/audio
351
+ ```javascript
352
+ showEditor(audioUrl, {
353
+ type: 'audio', // Enable audio mode
354
+ outputExt: 'wav', // Output format (mp3, wav, m4a, etc.)
355
+ maxDuration: 30000, // 30 seconds max
356
+ });
357
+ ```
449
358
 
450
- ## closeEditor()
359
+ ### Remote Files (HTTPS)
451
360
 
452
- Close Editor
361
+ To trim remote files, you need the HTTPS-enabled version of FFmpeg:
362
+
363
+ **Android:**
364
+ ```gradle
365
+ // android/build.gradle
366
+ buildscript {
367
+ ext {
368
+ VideoTrim_ffmpeg_package = 'https'
369
+ // Optional: VideoTrim_ffmpeg_version = '6.0.1'
370
+ }
371
+ }
372
+ ```
453
373
 
454
- ## listFiles()
455
- Return array of generated output files in app storage. (`Promise<string[]>`)
374
+ **iOS:**
375
+ ```bash
376
+ FFMPEGKIT_PACKAGE=https FFMPEG_KIT_PACKAGE_VERSION=6.0 pod install
377
+ ```
456
378
 
457
- ## cleanFiles()
458
- Clean all generated output files in app storage. Return number of successfully deleted files (`Promise<number>`)
379
+ **Usage:**
380
+ ```javascript
381
+ showEditor('https://example.com/video.mp4', {
382
+ maxDuration: 60000,
383
+ });
384
+ ```
459
385
 
460
- ## deleteFile()
461
- Delete a file in app storage. Return `true` if success
386
+ ### Video Rotation
462
387
 
463
- # Audio only support
464
- <div align="left">
465
- <img src="images/audio_android.jpg" width="200" />
466
- <img src="images/audio_ios.jpg" width="200" />
467
- </div>
388
+ Rotate videos during trimming using metadata (doesn't re-encode):
468
389
 
469
- For audio only you have to pass `type=audio` and `outputExt`:
470
- ```ts
471
- showEditor(url, {
472
- type: 'audio', // important
473
- outputExt: 'wav', // important: any audio type for output file extension
474
- })
390
+ ```javascript
391
+ showEditor(videoUrl, {
392
+ enableRotation: true,
393
+ rotationAngle: 90, // 90, 180, 270 degrees
394
+ });
475
395
  ```
476
396
 
477
- # Cancel trimming
478
- <div align="left">
479
- <img src="images/progress.jpg" width="200" />
480
- <img src="images/cancel_confirm.jpg" width="200" />
397
+ **Note:** Uses `display_rotation` metadata - playback may vary by platform/player.
398
+
399
+ ### Trimming Progress & Cancellation
400
+
401
+ <div align="center">
402
+ <img src="images/progress.jpg" width="200" />
403
+ <img src="images/cancel_confirm.jpg" width="200" />
481
404
  </div>
482
405
 
483
- While trimming, you can press Cancel to terminate the process.
406
+ Users can cancel trimming while in progress:
407
+
408
+ ```javascript
409
+ showEditor(videoUrl, {
410
+ enableCancelTrimming: true,
411
+ cancelTrimmingButtonText: "Stop",
412
+ trimmingText: "Processing video...",
413
+ });
414
+ ```
484
415
 
485
- Related props: `enableCancelTrimming, cancelTrimmingButtonText, enableCancelTrimmingDialog, cancelTrimmingDialogTitle, cancelTrimmingDialogMessage, cancelTrimmingDialogCancelText, cancelTrimmingDialogConfirmText`
416
+ ### Error Handling
486
417
 
487
- # Fail to load media
488
418
  <img src="images/fail_to_load_media.jpg" width="200" />
489
419
 
490
- If there's error while loading media, there'll be a prompt
420
+ Handle loading errors gracefully:
491
421
 
492
- Related props: `alertOnFailToLoad, alertOnFailTitle, alertOnFailMessage, alertOnFailCloseText`
422
+ ```javascript
423
+ showEditor(videoUrl, {
424
+ alertOnFailToLoad: true,
425
+ alertOnFailTitle: "Oops!",
426
+ alertOnFailMessage: "Cannot load this video file",
427
+ alertOnFailCloseText: "OK",
428
+ });
429
+ ```
493
430
 
494
- # Rotation
431
+ ## Examples
495
432
 
496
- To trim & rotate video you can pass `enableRotation` and `rotationAngle` to `showEditor`/`trim`. But note that it doesn't re-encode the video, instead the lib uses `display_rotation` metadata from ffmpeg, and some players/platforms may show differently.
433
+ ### Complete Implementation (New Architecture)
497
434
 
498
- # Use FFMPEG HTTPS version
435
+ ```javascript
436
+ import React, { useEffect, useRef } from 'react';
437
+ import { TouchableOpacity, Text, View } from 'react-native';
438
+ import { showEditor, isValidFile, type Spec } from 'react-native-video-trim';
439
+ import { launchImageLibrary } from 'react-native-image-picker';
499
440
 
500
- If you want to trim a remote file, you need to use `https` version (default is `min` which does not support remote file).
441
+ export default function VideoTrimmer() {
442
+ const listeners = useRef({});
501
443
 
502
- Do the following:
444
+ useEffect(() => {
445
+ // Set up event listeners
446
+ listeners.current.onFinishTrimming = (NativeVideoTrim as Spec)
447
+ .onFinishTrimming(({ outputPath, startTime, endTime, duration }) => {
448
+ console.log('Trimming completed:', {
449
+ outputPath,
450
+ startTime,
451
+ endTime,
452
+ duration
453
+ });
454
+ });
455
+
456
+ listeners.current.onError = (NativeVideoTrim as Spec)
457
+ .onError(({ message, errorCode }) => {
458
+ console.error('Trimming error:', message, errorCode);
459
+ });
503
460
 
504
- ```
505
- // android/build.gradle
506
- buildscript {
507
- ext {
508
- VideoTrim_ffmpeg_package=https
461
+ return () => {
462
+ // Cleanup listeners
463
+ Object.values(listeners.current).forEach(listener =>
464
+ listener?.remove()
465
+ );
466
+ };
467
+ }, []);
468
+
469
+ const selectAndTrimVideo = async () => {
470
+ const result = await launchImageLibrary({
471
+ mediaType: 'video',
472
+ quality: 1,
473
+ });
474
+
475
+ if (result.assets?.[0]?.uri) {
476
+ const videoUri = result.assets[0].uri;
477
+
478
+ // Validate file first
479
+ const isValid = await isValidFile(videoUri);
480
+ if (!isValid) {
481
+ console.log('Invalid video file');
482
+ return;
483
+ }
509
484
 
510
- // optional: VideoTrim_ffmpeg_version=6.0.1
485
+ // Open editor
486
+ showEditor(videoUri, {
487
+ maxDuration: 60000, // 1 minute max
488
+ saveToPhoto: true, // Save to gallery
489
+ openShareSheetOnFinish: true,
490
+ headerText: "Trim Video",
491
+ trimmerColor: "#007AFF",
492
+ });
511
493
  }
512
- }
494
+ };
513
495
 
514
- // ios
515
- FFMPEGKIT_PACKAGE=https FFMPEG_KIT_PACKAGE_VERSION=6.0 pod install
496
+ return (
497
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
498
+ <TouchableOpacity
499
+ onPress={selectAndTrimVideo}
500
+ style={{
501
+ backgroundColor: '#007AFF',
502
+ padding: 15,
503
+ borderRadius: 8
504
+ }}
505
+ >
506
+ <Text style={{ color: 'white', fontSize: 16 }}>
507
+ Select & Trim Video
508
+ </Text>
509
+ </TouchableOpacity>
510
+ </View>
511
+ );
512
+ }
516
513
  ```
517
514
 
518
- # Android: update SDK version
519
- You can override sdk version to use any version in your `android/build.gradle` > `buildscript` > `ext`
520
- ```gradle
521
- buildscript {
522
- ext {
523
- VideoTrim_kotlinVersion=2.0.21
524
- VideoTrim_minSdkVersion=24
525
- VideoTrim_targetSdkVersion=34
526
- VideoTrim_compileSdkVersion=35
527
- VideoTrim_ndkVersion=27.1.12297006
528
- }
515
+ ### Old Architecture Implementation
516
+
517
+ ```javascript
518
+ import React, { useEffect } from 'react';
519
+ import { NativeEventEmitter, NativeModules } from 'react-native';
520
+ import { showEditor } from 'react-native-video-trim';
521
+
522
+ export default function VideoTrimmer() {
523
+ useEffect(() => {
524
+ const eventEmitter = new NativeEventEmitter(NativeModules.VideoTrim);
525
+
526
+ const subscription = eventEmitter.addListener('VideoTrim', (event) => {
527
+ switch (event.name) {
528
+ case 'onFinishTrimming':
529
+ console.log('Video trimmed:', event.outputPath);
530
+ break;
531
+ case 'onError':
532
+ console.error('Trimming failed:', event.message);
533
+ break;
534
+ // Handle other events...
535
+ }
536
+ });
537
+
538
+ return () => subscription.remove();
539
+ }, []);
540
+
541
+ // Rest of implementation...
529
542
  }
530
543
  ```
531
544
 
532
- # Thanks
533
- - Android part is created by modified + fix bugs from: https://github.com/iknow4/Android-Video-Trimmer
534
- - iOS UI is created from: https://github.com/AndreasVerhoeven/VideoTrimmerControl
545
+ ## Troubleshooting
546
+
547
+ ### Common Issues
548
+
549
+ **Android Build Errors:**
550
+ - Ensure `file_paths.xml` exists for share functionality
551
+ - Check SDK versions match your project requirements
552
+ - Verify permissions in `AndroidManifest.xml`
553
+
554
+ **iOS Build Errors:**
555
+ - Run `pod install` after installation
556
+ - Check Info.plist permissions for photo access
557
+ - Use development builds with Expo (not Expo Go)
558
+
559
+ **Runtime Issues:**
560
+ - Validate files with `isValidFile()` before processing
561
+ - Use HTTPS version for remote files
562
+ - Check network connectivity for remote files
563
+ - Ensure proper permissions for save operations
564
+
565
+ ### Performance Tips
566
+
567
+ - Use `trim()` for batch processing without UI
568
+ - Clean up generated files regularly with `cleanFiles()`
569
+ - Consider file compression for large videos
570
+ - Use appropriate `maxDuration` limits
571
+
572
+ ## Credits
573
+
574
+ - **Android:** Based on [Android-Video-Trimmer](https://github.com/iknow4/Android-Video-Trimmer)
575
+ - **iOS:** UI from [VideoTrimmerControl](https://github.com/AndreasVerhoeven/VideoTrimmerControl)