react-native-video-trim 1.0.9 → 1.0.11
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 +38 -5
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +96 -12
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +1 -0
- package/android/src/main/java/com/videotrim/utils/StorageUtil.java +14 -143
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +2 -10
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +5 -32
- package/android/src/main/res/values/strings.xml +1 -1
- package/ios/VideoTrim.mm +6 -1
- package/ios/VideoTrim.swift +279 -115
- package/ios/VideoTrimmer.swift +808 -0
- package/ios/VideoTrimmerThumb.swift +119 -0
- package/ios/VideoTrimmerViewController.swift +292 -0
- package/lib/commonjs/index.js +64 -4
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +61 -4
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +46 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/react-native-video-trim.podspec +1 -0
- package/src/index.tsx +75 -5
package/README.md
CHANGED
|
@@ -146,18 +146,38 @@ Main method to show Video Editor UI.
|
|
|
146
146
|
- `config` (optional):
|
|
147
147
|
|
|
148
148
|
- `saveToPhoto` (optional, `default = true`): whether to save video to photo/gallery after editing
|
|
149
|
+
- `removeAfterSavedToPhoto` (optional, `default = false`): whether to remove output file from storage after saved to Photo
|
|
149
150
|
- `maxDuration` (optional): maximum duration for the trimmed video
|
|
150
151
|
- `cancelButtonText` (optional): text of left button in Editor dialog
|
|
151
152
|
- `saveButtonText` (optional): text of right button in Editor dialog
|
|
152
|
-
-
|
|
153
|
+
- `enableCancelDialog` (optional, `default = true`): whether to show alert dialog on press Cancel
|
|
154
|
+
- `cancelDialogTitle` (optional, `default = "Warning!"`)
|
|
155
|
+
- `cancelDialogMessage` (optional, `default = "Are you sure want to cancel?"`)
|
|
156
|
+
- `cancelDialogCancelText` (optional, `default = "Close"`)
|
|
157
|
+
- `cancelDialogConfirmText` (optional, `default = "Proceed"`)
|
|
158
|
+
- `enableSaveDialog` (optional, `default = true`): whether to show alert dialog on press Save
|
|
159
|
+
- `saveDialogTitle` (optional, `default = "Confirmation!"`)
|
|
160
|
+
- `saveDialogMessage` (optional, `default = "Are you sure want to save?"`)
|
|
161
|
+
- `saveDialogCancelText` (optional, `default = "Close"`)
|
|
162
|
+
- `saveDialogConfirmText` (optional, `default = "Proceed"`)
|
|
163
|
+
- `trimmingText` (optional, `default = "Trimming video..."`): trimming text on the progress dialog
|
|
153
164
|
|
|
154
165
|
If `saveToPhoto = true`, you must ensure that you have request permission to write to photo/gallery
|
|
155
166
|
- For Android: you need to have `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` in AndroidManifest.xml
|
|
156
167
|
- For iOS: you need `NSPhotoLibraryUsageDescription` in Info.plist
|
|
157
168
|
|
|
158
|
-
## isValidVideo
|
|
169
|
+
## isValidVideo(videoPath: string)
|
|
159
170
|
|
|
160
|
-
This method is to check if a path is a actual video. It returns `Promise<boolean>`
|
|
171
|
+
This method is to check if a path is a actual video and editable. It returns `Promise<boolean>`
|
|
172
|
+
|
|
173
|
+
## listFiles()
|
|
174
|
+
Return array of generated output files in app storage. (`Promise<string[]>`)
|
|
175
|
+
|
|
176
|
+
## cleanFiles()
|
|
177
|
+
Clean all generated output files in app storage. Return number of successfully deleted files (`Promise<number>`)
|
|
178
|
+
|
|
179
|
+
## deleteFile()
|
|
180
|
+
Delete a file in app storage. Return `true` if success
|
|
161
181
|
|
|
162
182
|
# Events
|
|
163
183
|
To listen for events you interest, do the following:
|
|
@@ -177,7 +197,7 @@ useEffect(() => {
|
|
|
177
197
|
break;
|
|
178
198
|
}
|
|
179
199
|
case 'onStartTrimming': {
|
|
180
|
-
//
|
|
200
|
+
// on start trimming
|
|
181
201
|
console.log('onStartTrimming', event);
|
|
182
202
|
break;
|
|
183
203
|
}
|
|
@@ -204,6 +224,19 @@ useEffect(() => {
|
|
|
204
224
|
};
|
|
205
225
|
}, []);
|
|
206
226
|
```
|
|
227
|
+
# FFMPEG Version
|
|
228
|
+
This library uses FFMPEG-Kit Android under the hood, by default FFMPEG-min is used, which gives smallest bundle size: https://github.com/arthenica/ffmpeg-kit#9-packages
|
|
229
|
+
|
|
230
|
+
If you ever need to use other version of FFMPEG-Kit for Android, you can do the following, in your `android/build.gradle` > `buildscript` > `ext`:
|
|
207
231
|
|
|
232
|
+
```gradle
|
|
233
|
+
buildscript {
|
|
234
|
+
ext {
|
|
235
|
+
ffmpegKitPackage = "full" // default "min", if followed by lts then LTS version is use. Eg "full-lts"
|
|
236
|
+
|
|
237
|
+
ffmpegKitPackageVersion = "5.1.LTS" // use exact version, highest precedence, default 6.0-2 if ignored
|
|
238
|
+
}
|
|
239
|
+
```
|
|
208
240
|
# Thanks
|
|
209
|
-
Android part is created by modified + fix bugs from
|
|
241
|
+
- Android part is created by modified + fix bugs from: https://github.com/iknow4/Android-Video-Trimmer
|
|
242
|
+
- iOS UI is created from: https://github.com/AndreasVerhoeven/VideoTrimmerControl
|
|
@@ -44,11 +44,22 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
44
44
|
private AlertDialog alertDialog;
|
|
45
45
|
private AlertDialog mProgressDialog;
|
|
46
46
|
private ProgressBar mProgressBar;
|
|
47
|
-
private Boolean mSaveToPhoto = true;
|
|
48
47
|
private int listenerCount = 0;
|
|
49
48
|
|
|
50
49
|
private Promise showEditorPromise;
|
|
51
50
|
|
|
51
|
+
private boolean enableCancelDialog = true;
|
|
52
|
+
private String cancelDialogTitle = "Warning!";
|
|
53
|
+
private String cancelDialogMessage = "Are you sure want to cancel?";
|
|
54
|
+
private String cancelDialogCancelText = "Close";
|
|
55
|
+
private String cancelDialogConfirmText = "Proceed";
|
|
56
|
+
private boolean enableSaveDialog = true;
|
|
57
|
+
private String saveDialogTitle = "Confirmation!";
|
|
58
|
+
private String saveDialogMessage = "Are you sure want to save?";
|
|
59
|
+
private String saveDialogCancelText = "Close";
|
|
60
|
+
private String saveDialogConfirmText = "Proceed";
|
|
61
|
+
private String trimmingText = "Trimming video...";
|
|
62
|
+
|
|
52
63
|
public VideoTrimModule(ReactApplicationContext reactContext) {
|
|
53
64
|
super(reactContext);
|
|
54
65
|
}
|
|
@@ -67,17 +78,26 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
67
78
|
return;
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
if (
|
|
71
|
-
this.mSaveToPhoto = config.getBoolean("saveToPhoto");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!_isValidVideo(videoPath)) {
|
|
81
|
+
if (!isValidVideo(videoPath)) {
|
|
75
82
|
WritableMap map = Arguments.createMap();
|
|
76
83
|
map.putString("message", "File is not a valid video");
|
|
77
84
|
sendEvent(getReactApplicationContext(), "onError", map);
|
|
78
85
|
return;
|
|
79
86
|
}
|
|
80
87
|
|
|
88
|
+
enableCancelDialog = config.hasKey("enableCancelDialog") ? config.getBoolean("enableCancelDialog") : true;
|
|
89
|
+
cancelDialogTitle = config.hasKey("cancelDialogTitle") ? config.getString("cancelDialogTitle") : "Warning!";
|
|
90
|
+
cancelDialogMessage = config.hasKey("cancelDialogMessage") ? config.getString("cancelDialogMessage") : "Are you sure want to cancel?";
|
|
91
|
+
cancelDialogCancelText = config.hasKey("cancelDialogCancelText") ? config.getString("cancelDialogCancelText") : "Close";
|
|
92
|
+
cancelDialogConfirmText = config.hasKey("cancelDialogConfirmText") ? config.getString("cancelDialogConfirmText") : "Proceed";
|
|
93
|
+
|
|
94
|
+
enableSaveDialog = config.hasKey("enableSaveDialog") ? config.getBoolean("enableSaveDialog") : true;
|
|
95
|
+
saveDialogTitle = config.hasKey("saveDialogTitle") ? config.getString("saveDialogTitle") : "Confirmation!";
|
|
96
|
+
saveDialogMessage = config.hasKey("saveDialogMessage") ? config.getString("saveDialogMessage") : "Are you sure want to save?";
|
|
97
|
+
saveDialogCancelText = config.hasKey("saveDialogCancelText") ? config.getString("saveDialogCancelText") : "Close";
|
|
98
|
+
saveDialogConfirmText = config.hasKey("saveDialogConfirmText") ? config.getString("saveDialogConfirmText") : "Proceed";
|
|
99
|
+
trimmingText = config.hasKey("trimmingText") ? config.getString("trimmingText") : "Trimming video...";
|
|
100
|
+
|
|
81
101
|
Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
82
102
|
|
|
83
103
|
if (!isInit) {
|
|
@@ -169,8 +189,47 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
169
189
|
}
|
|
170
190
|
|
|
171
191
|
@Override public void onCancel() {
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
if (!enableCancelDialog) {
|
|
193
|
+
sendEvent(getReactApplicationContext(), "onCancelTrimming", null);
|
|
194
|
+
hideDialog();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(getReactApplicationContext().getCurrentActivity());
|
|
199
|
+
builder.setMessage(cancelDialogMessage);
|
|
200
|
+
builder.setTitle(cancelDialogTitle);
|
|
201
|
+
builder.setCancelable(false);
|
|
202
|
+
builder.setPositiveButton(cancelDialogConfirmText, (dialog, which) -> {
|
|
203
|
+
dialog.cancel();
|
|
204
|
+
sendEvent(getReactApplicationContext(), "onCancelTrimming", null);
|
|
205
|
+
hideDialog();
|
|
206
|
+
});
|
|
207
|
+
builder.setNegativeButton(cancelDialogCancelText, (dialog, which) -> {
|
|
208
|
+
dialog.cancel();
|
|
209
|
+
});
|
|
210
|
+
AlertDialog alertDialog = builder.create();
|
|
211
|
+
alertDialog.show();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@Override public void onSave() {
|
|
215
|
+
if (!enableSaveDialog) {
|
|
216
|
+
trimmerView.onSaveClicked();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(getReactApplicationContext().getCurrentActivity());
|
|
221
|
+
builder.setMessage(saveDialogMessage);
|
|
222
|
+
builder.setTitle(saveDialogTitle);
|
|
223
|
+
builder.setCancelable(false);
|
|
224
|
+
builder.setPositiveButton(saveDialogConfirmText, (dialog, which) -> {
|
|
225
|
+
dialog.cancel();
|
|
226
|
+
trimmerView.onSaveClicked();
|
|
227
|
+
});
|
|
228
|
+
builder.setNegativeButton(saveDialogCancelText, (dialog, which) -> {
|
|
229
|
+
dialog.cancel();
|
|
230
|
+
});
|
|
231
|
+
AlertDialog alertDialog = builder.create();
|
|
232
|
+
alertDialog.show();
|
|
174
233
|
}
|
|
175
234
|
|
|
176
235
|
@ReactMethod
|
|
@@ -190,7 +249,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
190
249
|
}
|
|
191
250
|
|
|
192
251
|
private void buildDialog() {
|
|
193
|
-
Activity activity =
|
|
252
|
+
Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
194
253
|
// Create the parent layout for the dialog
|
|
195
254
|
LinearLayout layout = new LinearLayout(activity);
|
|
196
255
|
layout.setLayoutParams(new ViewGroup.LayoutParams(
|
|
@@ -207,7 +266,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
207
266
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
208
267
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
209
268
|
));
|
|
210
|
-
textView.setText(
|
|
269
|
+
textView.setText(trimmingText);
|
|
211
270
|
textView.setTextSize(18);
|
|
212
271
|
layout.addView(textView);
|
|
213
272
|
|
|
@@ -253,7 +312,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
253
312
|
}
|
|
254
313
|
|
|
255
314
|
|
|
256
|
-
public boolean
|
|
315
|
+
public boolean isValidVideo(String filePath) {
|
|
257
316
|
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
|
258
317
|
|
|
259
318
|
try {
|
|
@@ -269,7 +328,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
269
328
|
|
|
270
329
|
@ReactMethod
|
|
271
330
|
private void isValidVideo(String filePath, Promise promise) {
|
|
272
|
-
promise.resolve(
|
|
331
|
+
promise.resolve(isValidVideo(filePath));
|
|
273
332
|
}
|
|
274
333
|
|
|
275
334
|
@ReactMethod
|
|
@@ -286,4 +345,29 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
286
345
|
this.hideDialog();
|
|
287
346
|
promise.resolve(null);
|
|
288
347
|
}
|
|
348
|
+
|
|
349
|
+
@ReactMethod
|
|
350
|
+
private void listFiles(Promise promise) {
|
|
351
|
+
String[] files = StorageUtil.listFiles(getReactApplicationContext());
|
|
352
|
+
promise.resolve(Arguments.fromArray(files));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@ReactMethod
|
|
356
|
+
private void cleanFiles(Promise promise) {
|
|
357
|
+
String[] files = StorageUtil.listFiles(getReactApplicationContext());
|
|
358
|
+
int successCount = 0;
|
|
359
|
+
for (String file : files) {
|
|
360
|
+
boolean state = StorageUtil.deleteFile(file);
|
|
361
|
+
if (state) {
|
|
362
|
+
successCount++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
promise.resolve(successCount);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@ReactMethod
|
|
369
|
+
private void deleteFile(String filePath, Promise promise) {
|
|
370
|
+
boolean state = StorageUtil.deleteFile(filePath);
|
|
371
|
+
promise.resolve(state);
|
|
372
|
+
}
|
|
289
373
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.videotrim.utils;
|
|
2
2
|
|
|
3
|
-
import android.annotation.SuppressLint;
|
|
4
3
|
import android.content.ContentValues;
|
|
5
4
|
import android.content.Context;
|
|
6
5
|
import android.media.MediaScannerConnection;
|
|
@@ -9,7 +8,6 @@ import android.os.Build;
|
|
|
9
8
|
import android.os.Environment;
|
|
10
9
|
import android.provider.MediaStore;
|
|
11
10
|
import android.text.TextUtils;
|
|
12
|
-
import android.util.Log;
|
|
13
11
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
14
12
|
import java.io.File;
|
|
15
13
|
import java.io.FileInputStream;
|
|
@@ -17,163 +15,36 @@ import java.io.FileOutputStream;
|
|
|
17
15
|
import java.io.IOException;
|
|
18
16
|
import java.io.InputStream;
|
|
19
17
|
import java.io.OutputStream;
|
|
20
|
-
import java.util.
|
|
21
|
-
|
|
22
|
-
import iknow.android.utils.BaseUtils;
|
|
23
|
-
import iknow.android.utils.BuildConfig;
|
|
24
|
-
|
|
18
|
+
import java.util.ArrayList;
|
|
19
|
+
import java.util.List;
|
|
25
20
|
|
|
26
21
|
@SuppressWarnings({ "ResultOfMethodCallIgnored", "FieldCanBeLocal" })
|
|
27
22
|
public class StorageUtil {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
private static String sDataDir;
|
|
33
|
-
private static String sCacheDir;
|
|
34
|
-
|
|
35
|
-
public static String getAppDataDir() {
|
|
36
|
-
if (TextUtils.isEmpty(sDataDir)) {
|
|
37
|
-
try {
|
|
38
|
-
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
39
|
-
sDataDir = Environment.getExternalStorageDirectory().getPath() + APP_DATA_PATH;
|
|
40
|
-
if (TextUtils.isEmpty(sDataDir)) {
|
|
41
|
-
sDataDir = BaseUtils.getContext().getFilesDir().getAbsolutePath();
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
sDataDir = BaseUtils.getContext().getFilesDir().getAbsolutePath();
|
|
45
|
-
}
|
|
46
|
-
} catch (Throwable e) {
|
|
47
|
-
e.printStackTrace();
|
|
48
|
-
sDataDir = BaseUtils.getContext().getFilesDir().getAbsolutePath();
|
|
49
|
-
}
|
|
50
|
-
File file = new File(sDataDir);
|
|
51
|
-
if (!file.exists()) {//判断文件目录是否存在
|
|
52
|
-
file.mkdirs();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return sDataDir;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public static String getCacheDir() {
|
|
59
|
-
if (TextUtils.isEmpty(sCacheDir)) {
|
|
60
|
-
File file = null;
|
|
61
|
-
Context context = BaseUtils.getContext();
|
|
62
|
-
try {
|
|
63
|
-
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
|
64
|
-
file = context.getExternalCacheDir();
|
|
65
|
-
if (file == null || !file.exists()) {
|
|
66
|
-
file = getExternalCacheDirManual(context);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (file == null) {
|
|
70
|
-
file = context.getCacheDir();
|
|
71
|
-
if (file == null || !file.exists()) {
|
|
72
|
-
file = getCacheDirManual(context);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
Log.w(TAG, "cache dir = " + file.getAbsolutePath());
|
|
76
|
-
sCacheDir = file.getAbsolutePath();
|
|
77
|
-
} catch (Throwable ignored) {
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return sCacheDir;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private static File getExternalCacheDirManual(Context context) {
|
|
84
|
-
File dataDir = new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data");
|
|
85
|
-
File appCacheDir = new File(new File(dataDir, context.getPackageName()), "cache");
|
|
86
|
-
if (!appCacheDir.exists()) {
|
|
87
|
-
if (!appCacheDir.mkdirs()) {//
|
|
88
|
-
Log.w(TAG, "Unable to create external cache directory");
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
new File(appCacheDir, ".nomedia").createNewFile();
|
|
93
|
-
} catch (IOException e) {
|
|
94
|
-
Log.i(TAG, "Can't create \".nomedia\" file in application external cache directory");
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return appCacheDir;
|
|
23
|
+
public static String getOutputPath(Context context) { // use same extension as inputFile
|
|
24
|
+
long timestamp = System.currentTimeMillis() / 1000;
|
|
25
|
+
File file = new File(context.getFilesDir(), VideoTrimmerUtil.FILE_PREFIX + "_" + timestamp + ".mp4"); // always use mp4 to prevent any issue with ffmpeg
|
|
26
|
+
return file.getAbsolutePath();
|
|
98
27
|
}
|
|
99
28
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return new File(cacheDirPath);
|
|
104
|
-
}
|
|
29
|
+
public static String[] listFiles(Context context) {
|
|
30
|
+
File filesDir = context.getFilesDir();
|
|
31
|
+
File[] files = filesDir.listFiles((dir, name) -> name.startsWith(VideoTrimmerUtil.FILE_PREFIX));
|
|
105
32
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
File[] files = cacheFile.listFiles();
|
|
112
|
-
for (int i = 0; i < files.length; i++) {
|
|
113
|
-
// 是文件则直接删除
|
|
114
|
-
if (files[i].exists() && files[i].isFile()) {
|
|
115
|
-
files[i].delete();
|
|
116
|
-
} else if (files[i].exists() && files[i].isDirectory()) {
|
|
117
|
-
// 递归删除文件
|
|
118
|
-
delFiles(files[i].getAbsolutePath());
|
|
119
|
-
// 删除完目录下面的所有文件后再删除该文件夹
|
|
120
|
-
files[i].delete();
|
|
33
|
+
List<String> fileUrls = new ArrayList<>();
|
|
34
|
+
if (files != null) {
|
|
35
|
+
for (File file : files) {
|
|
36
|
+
fileUrls.add(file.getAbsolutePath());
|
|
121
37
|
}
|
|
122
38
|
}
|
|
123
39
|
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
public static long sizeOfDirectory(File dir) {
|
|
128
|
-
if (dir.exists()) {
|
|
129
|
-
long result = 0;
|
|
130
|
-
File[] fileList = dir.listFiles();
|
|
131
|
-
for (int i = 0; i < fileList.length; i++) {
|
|
132
|
-
// Recursive call if it's a directory
|
|
133
|
-
if (fileList[i].isDirectory()) {
|
|
134
|
-
result += sizeOfDirectory(fileList[i]);
|
|
135
|
-
} else {
|
|
136
|
-
// Sum the file size in bytes
|
|
137
|
-
result += fileList[i].length();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return result; // return the file size
|
|
141
|
-
}
|
|
142
|
-
return 0;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* @param length 长度 byte为单位
|
|
147
|
-
* 将文件大小转换为KB,MB格式
|
|
148
|
-
*/
|
|
149
|
-
public static String getFileSize(long length) {
|
|
150
|
-
int MB = 1024 * 1024;
|
|
151
|
-
if (length < MB) {
|
|
152
|
-
double resultKB = length * 1.0 / 1024;
|
|
153
|
-
return String.format(Locale.getDefault(), "%.1f", resultKB) + "Kb";
|
|
154
|
-
}
|
|
155
|
-
double resultMB = length * 1.0 / MB;
|
|
156
|
-
return String.format(Locale.getDefault(), "%.1f", resultMB) + "Mb";
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
public static boolean isFileExist(String path) {
|
|
160
|
-
if (TextUtils.isEmpty(path)) return false;
|
|
161
|
-
File file = new File(path);
|
|
162
|
-
return file.exists();
|
|
40
|
+
return fileUrls.toArray(new String[0]);
|
|
163
41
|
}
|
|
164
42
|
|
|
165
|
-
/**
|
|
166
|
-
* @param path 路径
|
|
167
|
-
* @return 是否删除成功
|
|
168
|
-
*/
|
|
169
43
|
public static boolean deleteFile(String path) {
|
|
170
44
|
if (TextUtils.isEmpty(path)) return true;
|
|
171
45
|
return deleteFile(new File(path));
|
|
172
46
|
}
|
|
173
47
|
|
|
174
|
-
/**
|
|
175
|
-
* @return 是否删除成功
|
|
176
|
-
*/
|
|
177
48
|
public static boolean deleteFile(File file) {
|
|
178
49
|
if (file == null || !file.exists()) return true;
|
|
179
50
|
|
|
@@ -23,6 +23,7 @@ import iknow.android.utils.thread.BackgroundExecutor;
|
|
|
23
23
|
public class VideoTrimmerUtil {
|
|
24
24
|
|
|
25
25
|
private static final String TAG = VideoTrimmerUtil.class.getSimpleName();
|
|
26
|
+
public static final String FILE_PREFIX = "trimmedVideo";
|
|
26
27
|
public static final long MIN_SHOOT_DURATION = 3000L;// min 3 seconds for trimming
|
|
27
28
|
// public static final int VIDEO_MAX_TIME = 10;// max 10 seconds for trimming
|
|
28
29
|
// public static final long MAX_SHOOT_DURATION = VIDEO_MAX_TIME * 1000L;
|
|
@@ -37,22 +38,13 @@ public class VideoTrimmerUtil {
|
|
|
37
38
|
private static final int THUMB_RESOLUTION_RES = 2; // double thumb resolution for better quality
|
|
38
39
|
|
|
39
40
|
public static void trim(Context context, String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback) {
|
|
40
|
-
final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
|
41
|
-
final String outputName = "trimmedVideo_" + timeStamp + ".mp4";
|
|
42
|
-
outputFile = outputFile + "/" + outputName;
|
|
43
|
-
|
|
44
41
|
String cmd = "-i " + inputFile + " -ss " + startMs + "ms" + " -to " + endMs + "ms -c copy " + outputFile;
|
|
45
|
-
final String tempOutFile = outputFile;
|
|
46
|
-
|
|
47
42
|
callback.onStartTrim();
|
|
48
43
|
FFmpegKit.executeAsync(cmd, session -> {
|
|
49
44
|
SessionState state = session.getState();
|
|
50
|
-
ReturnCode returnCode = session.getReturnCode();
|
|
51
|
-
|
|
52
|
-
Log.d(TAG, String.format("FFmpeg process exited with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace()));
|
|
53
45
|
|
|
54
46
|
if (state.equals(SessionState.COMPLETED)) {
|
|
55
|
-
callback.onFinishTrim(
|
|
47
|
+
callback.onFinishTrim(outputFile);
|
|
56
48
|
} else {
|
|
57
49
|
callback.onError();
|
|
58
50
|
}
|
|
@@ -165,10 +165,6 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
private void onCancelClicked() {
|
|
169
|
-
mOnTrimVideoListener.onCancel();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
168
|
private void videoPrepared(MediaPlayer mp) {
|
|
173
169
|
ViewGroup.LayoutParams lp = mVideoView.getLayoutParams();
|
|
174
170
|
int videoWidth = mp.getVideoWidth();
|
|
@@ -245,36 +241,13 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
245
241
|
|
|
246
242
|
private void setUpListeners() {
|
|
247
243
|
findViewById(R.id.cancelBtn).setOnClickListener(view -> {
|
|
248
|
-
|
|
249
|
-
builder.setMessage("Are you sure want to cancel?");
|
|
250
|
-
builder.setTitle("Warning!");
|
|
251
|
-
builder.setCancelable(false);
|
|
252
|
-
builder.setPositiveButton("Proceed", (dialog, which) -> {
|
|
253
|
-
dialog.cancel();
|
|
254
|
-
onCancelClicked();
|
|
255
|
-
});
|
|
256
|
-
builder.setNegativeButton("Close", (dialog, which) -> {
|
|
257
|
-
dialog.cancel();
|
|
258
|
-
});
|
|
259
|
-
AlertDialog alertDialog = builder.create();
|
|
260
|
-
alertDialog.show();
|
|
244
|
+
mOnTrimVideoListener.onCancel();
|
|
261
245
|
});
|
|
262
246
|
|
|
263
247
|
findViewById(R.id.saveBtn).setOnClickListener(view -> {
|
|
264
|
-
|
|
265
|
-
builder.setMessage("Are you sure want to save?");
|
|
266
|
-
builder.setTitle("Confirmation!");
|
|
267
|
-
builder.setCancelable(false);
|
|
268
|
-
builder.setPositiveButton("Proceed", (dialog, which) -> {
|
|
269
|
-
dialog.cancel();
|
|
270
|
-
onSaveClicked();
|
|
271
|
-
});
|
|
272
|
-
builder.setNegativeButton("Close", (dialog, which) -> {
|
|
273
|
-
dialog.cancel();
|
|
274
|
-
});
|
|
275
|
-
AlertDialog alertDialog = builder.create();
|
|
276
|
-
alertDialog.show();
|
|
248
|
+
mOnTrimVideoListener.onSave();
|
|
277
249
|
});
|
|
250
|
+
|
|
278
251
|
mVideoView.setOnPreparedListener(mp -> {
|
|
279
252
|
// this is called everytime activity goes active, and can fire multiple times
|
|
280
253
|
// so that we create a flag to not run below code more than once
|
|
@@ -293,14 +266,14 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
293
266
|
});
|
|
294
267
|
}
|
|
295
268
|
|
|
296
|
-
|
|
269
|
+
public void onSaveClicked() {
|
|
297
270
|
if (mRightProgressPos - mLeftProgressPos < VideoTrimmerUtil.MIN_SHOOT_DURATION) {
|
|
298
271
|
Toast.makeText(mContext, "Video shorter than 3s, can't proceed", Toast.LENGTH_SHORT).show();
|
|
299
272
|
} else {
|
|
300
273
|
mVideoView.pause();
|
|
301
274
|
VideoTrimmerUtil.trim(mContext,
|
|
302
275
|
mSourceUri.getPath(),
|
|
303
|
-
StorageUtil.
|
|
276
|
+
StorageUtil.getOutputPath(mContext),
|
|
304
277
|
mDuration,
|
|
305
278
|
mLeftProgressPos,
|
|
306
279
|
mRightProgressPos,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
tools:ignore="MissingTranslation">
|
|
4
4
|
<string name="app_name">Video Trim</string>
|
|
5
5
|
<string name="trimmed_done">video trim completed!</string>
|
|
6
|
-
<string name="trimming">Trimming video...</string
|
|
6
|
+
<!-- <string name="trimming">Trimming video...</string>-->
|
|
7
7
|
<string name="compressing">Compressing video...</string>
|
|
8
8
|
<string name="compressed_done">video compression completed!</string>
|
|
9
9
|
<string name="open_permission">Open permission</string>
|
package/ios/VideoTrim.mm
CHANGED
|
@@ -6,5 +6,10 @@
|
|
|
6
6
|
RCT_EXTERN_METHOD(showEditor:(NSString*)uri withConfig:(NSDictionary *)config)
|
|
7
7
|
RCT_EXTERN_METHOD(isValidVideo:(NSString*)uri withResolver:(RCTPromiseResolveBlock)resolve
|
|
8
8
|
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
9
|
-
|
|
9
|
+
RCT_EXTERN_METHOD(listFiles:(RCTPromiseResolveBlock)resolve
|
|
10
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
11
|
+
RCT_EXTERN_METHOD(cleanFiles:(RCTPromiseResolveBlock)resolve
|
|
12
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
13
|
+
RCT_EXTERN_METHOD(deleteFile:(NSString*)uri withResolver:(RCTPromiseResolveBlock)resolve
|
|
14
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
10
15
|
@end
|