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 +485 -444
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +69 -1
- package/android/src/main/res/layout/video_trimmer_view.xml +7 -7
- package/android/src/main/res/values/strings.xml +1 -0
- package/ios/VideoTrim.mm +11 -2
- package/ios/VideoTrimmer.swift +2 -2
- package/ios/VideoTrimmerThumb.swift +14 -1
- package/ios/VideoTrimmerViewController.swift +37 -63
- package/lib/module/NativeVideoTrim.js.map +1 -1
- package/lib/module/index.js +11 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeVideoTrim.d.ts +8 -0
- package/lib/typescript/src/NativeVideoTrim.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeVideoTrim.ts +8 -0
- package/src/index.tsx +14 -4
package/README.md
CHANGED
|
@@ -1,534 +1,575 @@
|
|
|
1
|
-
# Table of
|
|
1
|
+
# Table of Contents
|
|
2
|
+
- [Overview](#overview)
|
|
2
3
|
- [Installation](#installation)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
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
|
-
<
|
|
28
|
-
<
|
|
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
|
-
##
|
|
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
|
-
|
|
38
|
+
A powerful, easy-to-use video and audio trimming library for React Native applications.
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
### ✨ Key Features
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
136
|
+
## Quick Start
|
|
78
137
|
|
|
79
|
-
|
|
138
|
+
Get up and running in 3 simple steps:
|
|
80
139
|
|
|
81
|
-
```
|
|
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
|
-
//
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
205
|
+
### trim()
|
|
233
206
|
|
|
234
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
300
|
-
subscription.remove();
|
|
301
|
-
};
|
|
302
|
-
}, []);
|
|
213
|
+
**Returns:** Promise resolving to the output file path
|
|
303
214
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
+
## Platform Setup
|
|
354
323
|
|
|
355
|
-
|
|
324
|
+
### Android SDK Customization
|
|
356
325
|
|
|
357
|
-
|
|
358
|
-
Main method to show Video Editor UI.
|
|
326
|
+
You can override SDK versions in `android/build.gradle`:
|
|
359
327
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
342
|
+
### Audio Trimming
|
|
443
343
|
|
|
444
|
-
|
|
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
|
-
|
|
349
|
+
For audio-only trimming, specify the media type and output format:
|
|
447
350
|
|
|
448
|
-
|
|
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
|
-
|
|
359
|
+
### Remote Files (HTTPS)
|
|
451
360
|
|
|
452
|
-
|
|
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
|
-
|
|
455
|
-
|
|
374
|
+
**iOS:**
|
|
375
|
+
```bash
|
|
376
|
+
FFMPEGKIT_PACKAGE=https FFMPEG_KIT_PACKAGE_VERSION=6.0 pod install
|
|
377
|
+
```
|
|
456
378
|
|
|
457
|
-
|
|
458
|
-
|
|
379
|
+
**Usage:**
|
|
380
|
+
```javascript
|
|
381
|
+
showEditor('https://example.com/video.mp4', {
|
|
382
|
+
maxDuration: 60000,
|
|
383
|
+
});
|
|
384
|
+
```
|
|
459
385
|
|
|
460
|
-
|
|
461
|
-
Delete a file in app storage. Return `true` if success
|
|
386
|
+
### Video Rotation
|
|
462
387
|
|
|
463
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
})
|
|
390
|
+
```javascript
|
|
391
|
+
showEditor(videoUrl, {
|
|
392
|
+
enableRotation: true,
|
|
393
|
+
rotationAngle: 90, // 90, 180, 270 degrees
|
|
394
|
+
});
|
|
475
395
|
```
|
|
476
396
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
420
|
+
Handle loading errors gracefully:
|
|
491
421
|
|
|
492
|
-
|
|
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
|
-
|
|
431
|
+
## Examples
|
|
495
432
|
|
|
496
|
-
|
|
433
|
+
### Complete Implementation (New Architecture)
|
|
497
434
|
|
|
498
|
-
|
|
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
|
-
|
|
441
|
+
export default function VideoTrimmer() {
|
|
442
|
+
const listeners = useRef({});
|
|
501
443
|
|
|
502
|
-
|
|
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
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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
|
-
|
|
515
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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)
|