react-native-video-trim 1.0.0 → 1.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 +12 -2
- package/android/build.gradle +2 -3
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +104 -30
- package/android/src/main/java/com/videotrim/adapters/VideoTrimmerAdapter.java +4 -10
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +2 -0
- package/android/src/main/java/com/videotrim/utils/StorageUtil.java +12 -26
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +35 -23
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +51 -6
- package/lib/commonjs/index.js +21 -2
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +22 -3
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +29 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# React Native Video Trim
|
|
2
2
|
<div align="center">
|
|
3
3
|
<h2>Video trimmer for your React Native app</h2>
|
|
4
4
|
|
|
@@ -16,13 +16,23 @@ npm install react-native-video-trim
|
|
|
16
16
|
yarn add react-native-video-trim
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Next for Android, under `android/app/build.gradle`, add `jcenter` to `repositories` this:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
repositories {
|
|
23
|
+
... other repos if any
|
|
24
|
+
jcenter()
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
19
28
|
Next install CocoaPods deps:
|
|
20
29
|
|
|
21
30
|
```
|
|
22
|
-
|
|
31
|
+
npx pod-install ios
|
|
23
32
|
```
|
|
24
33
|
|
|
25
34
|
## Usage
|
|
35
|
+
> Note that for iOS you have to try on real device
|
|
26
36
|
|
|
27
37
|
```js
|
|
28
38
|
import { showEditor } from 'react-native-video-trim';
|
package/android/build.gradle
CHANGED
|
@@ -91,9 +91,8 @@ dependencies {
|
|
|
91
91
|
//noinspection GradleDynamicVersion
|
|
92
92
|
implementation "com.facebook.react:react-native:+"
|
|
93
93
|
implementation 'com.github.iknow4:android-utils-sdk:1.1.2'
|
|
94
|
-
implementation '
|
|
95
|
-
implementation '
|
|
96
|
-
implementation 'com.guolindev.permissionx:permissionx:1.7.1'
|
|
94
|
+
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
|
95
|
+
implementation 'com.arthenica:ffmpeg-kit-full:5.1.LTS'
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
if (isNewArchitectureEnabled()) {
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
package com.videotrim;
|
|
2
2
|
|
|
3
3
|
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
|
|
4
|
+
|
|
4
5
|
import android.app.Activity;
|
|
5
|
-
import android.
|
|
6
|
+
import android.content.res.ColorStateList;
|
|
7
|
+
import android.graphics.Color;
|
|
6
8
|
import android.media.MediaMetadataRetriever;
|
|
7
9
|
import android.net.Uri;
|
|
10
|
+
import android.os.Build;
|
|
11
|
+
import android.view.Gravity;
|
|
12
|
+
import android.view.ViewGroup;
|
|
13
|
+
import android.widget.LinearLayout;
|
|
14
|
+
import android.widget.ProgressBar;
|
|
15
|
+
import android.widget.TextView;
|
|
8
16
|
|
|
9
17
|
import androidx.annotation.NonNull;
|
|
10
18
|
import androidx.annotation.Nullable;
|
|
11
19
|
import androidx.appcompat.app.AlertDialog;
|
|
20
|
+
|
|
12
21
|
import com.facebook.react.bridge.Arguments;
|
|
13
22
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
14
23
|
import com.facebook.react.bridge.Promise;
|
|
@@ -23,9 +32,9 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
|
23
32
|
import com.videotrim.interfaces.VideoTrimListener;
|
|
24
33
|
import com.videotrim.utils.StorageUtil;
|
|
25
34
|
import com.videotrim.widgets.VideoTrimmerView;
|
|
35
|
+
|
|
26
36
|
import java.io.IOException;
|
|
27
37
|
import iknow.android.utils.BaseUtils;
|
|
28
|
-
import nl.bravobit.ffmpeg.FFmpeg;
|
|
29
38
|
|
|
30
39
|
@ReactModule(name = VideoTrimModule.NAME)
|
|
31
40
|
public class VideoTrimModule extends ReactContextBaseJavaModule implements VideoTrimListener, LifecycleEventListener {
|
|
@@ -33,11 +42,14 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
33
42
|
private static Boolean isInit = false;
|
|
34
43
|
private VideoTrimmerView trimmerView;
|
|
35
44
|
private AlertDialog alertDialog;
|
|
36
|
-
private
|
|
45
|
+
private AlertDialog mProgressDialog;
|
|
46
|
+
private ProgressBar mProgressBar;
|
|
37
47
|
private Boolean mSaveToPhoto = true;
|
|
38
48
|
private int mMaxDuration = 0;
|
|
39
49
|
private int listenerCount = 0;
|
|
40
50
|
|
|
51
|
+
private Promise showEditorPromise;
|
|
52
|
+
|
|
41
53
|
public VideoTrimModule(ReactApplicationContext reactContext) {
|
|
42
54
|
super(reactContext);
|
|
43
55
|
}
|
|
@@ -50,7 +62,8 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
50
62
|
|
|
51
63
|
|
|
52
64
|
@ReactMethod
|
|
53
|
-
public void showEditor(String videoPath, ReadableMap config) {
|
|
65
|
+
public void showEditor(String videoPath, ReadableMap config, Promise promise) {
|
|
66
|
+
showEditorPromise = promise;
|
|
54
67
|
if (trimmerView != null || alertDialog != null) {
|
|
55
68
|
return;
|
|
56
69
|
}
|
|
@@ -107,12 +120,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
107
120
|
isInit = true;
|
|
108
121
|
// we have to init this before create videoTrimmerView
|
|
109
122
|
BaseUtils.init(getReactApplicationContext());
|
|
110
|
-
if (!FFmpeg.getInstance(getReactApplicationContext()).isSupported()) {
|
|
111
|
-
// we have to call this for FFMPEG to initialize, otherwise it'll throw can't open ffmpeg (no such file or dir)
|
|
112
|
-
WritableMap mapE = Arguments.createMap();
|
|
113
|
-
mapE.putString("message", "Android CPU arch not supported");
|
|
114
|
-
sendEvent(getReactApplicationContext(), "onError", mapE);
|
|
115
|
-
}
|
|
116
123
|
}
|
|
117
124
|
|
|
118
125
|
@Override
|
|
@@ -136,25 +143,36 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
136
143
|
@Override public void onStartTrim() {
|
|
137
144
|
sendEvent(getReactApplicationContext(), "onStartTrimming", null);
|
|
138
145
|
runOnUiThread(() -> {
|
|
139
|
-
buildDialog(
|
|
146
|
+
buildDialog();
|
|
140
147
|
});
|
|
141
148
|
}
|
|
142
149
|
|
|
150
|
+
@Override public void onTrimmingProgress(int percentage) {
|
|
151
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
152
|
+
mProgressBar.setProgress(percentage, true);
|
|
153
|
+
} else {
|
|
154
|
+
mProgressBar.setProgress(percentage);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
143
159
|
@Override public void onFinishTrim(String in) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
e.printStackTrace();
|
|
153
|
-
WritableMap mapE = Arguments.createMap();
|
|
154
|
-
mapE.putString("message", "Fail to save to Gallery. Please check if you have correct permission");
|
|
155
|
-
sendEvent(getReactApplicationContext(), "onError", mapE);
|
|
160
|
+
runOnUiThread(() -> {
|
|
161
|
+
WritableMap map = Arguments.createMap();
|
|
162
|
+
map.putString("outputPath", in);
|
|
163
|
+
sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
|
|
164
|
+
if (mSaveToPhoto) {
|
|
165
|
+
showEditorPromise.resolve(in);
|
|
166
|
+
} else {
|
|
167
|
+
hideDialog();
|
|
156
168
|
}
|
|
157
|
-
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@Override public void onError() {
|
|
173
|
+
WritableMap map = Arguments.createMap();
|
|
174
|
+
map.putString("message", "Error when trimming, please try again");
|
|
175
|
+
sendEvent(getReactApplicationContext(), "onError", map);
|
|
158
176
|
this.hideDialog();
|
|
159
177
|
}
|
|
160
178
|
|
|
@@ -163,7 +181,14 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
163
181
|
this.hideDialog();
|
|
164
182
|
}
|
|
165
183
|
|
|
184
|
+
@ReactMethod
|
|
166
185
|
private void hideDialog() {
|
|
186
|
+
if (mProgressDialog != null) {
|
|
187
|
+
if (mProgressDialog.isShowing()) mProgressDialog.dismiss();
|
|
188
|
+
mProgressBar = null;
|
|
189
|
+
mProgressDialog = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
167
192
|
if (alertDialog != null) {
|
|
168
193
|
if(alertDialog.isShowing()) {
|
|
169
194
|
alertDialog.dismiss();
|
|
@@ -172,12 +197,45 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
172
197
|
}
|
|
173
198
|
}
|
|
174
199
|
|
|
175
|
-
private
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
private void buildDialog() {
|
|
201
|
+
Activity activity = getReactApplicationContext().getCurrentActivity();
|
|
202
|
+
// Create the parent layout for the dialog
|
|
203
|
+
LinearLayout layout = new LinearLayout(activity);
|
|
204
|
+
layout.setLayoutParams(new ViewGroup.LayoutParams(
|
|
205
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
206
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
207
|
+
));
|
|
208
|
+
layout.setOrientation(LinearLayout.VERTICAL);
|
|
209
|
+
layout.setGravity(Gravity.CENTER_HORIZONTAL);
|
|
210
|
+
layout.setPadding(16, 32, 16, 32);
|
|
211
|
+
|
|
212
|
+
// Create and add the TextView
|
|
213
|
+
TextView textView = new TextView(activity);
|
|
214
|
+
textView.setLayoutParams(new ViewGroup.LayoutParams(
|
|
215
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
216
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
217
|
+
));
|
|
218
|
+
textView.setText(getReactApplicationContext().getResources().getString(R.string.trimming));
|
|
219
|
+
textView.setTextSize(18);
|
|
220
|
+
layout.addView(textView);
|
|
221
|
+
|
|
222
|
+
// Create and add the ProgressBar
|
|
223
|
+
mProgressBar = new ProgressBar(activity, null, android.R.attr.progressBarStyleHorizontal);
|
|
224
|
+
mProgressBar.setLayoutParams(new ViewGroup.LayoutParams(
|
|
225
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
226
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
227
|
+
));
|
|
228
|
+
mProgressBar.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#2196F3")));
|
|
229
|
+
layout.addView(mProgressBar);
|
|
230
|
+
|
|
231
|
+
// Create the AlertDialog
|
|
232
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
|
233
|
+
builder.setCancelable(false);
|
|
234
|
+
builder.setView(layout);
|
|
235
|
+
|
|
236
|
+
// Show the dialog
|
|
237
|
+
mProgressDialog = builder.create();
|
|
238
|
+
mProgressDialog.show();
|
|
181
239
|
}
|
|
182
240
|
|
|
183
241
|
@ReactMethod
|
|
@@ -202,6 +260,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
202
260
|
}
|
|
203
261
|
}
|
|
204
262
|
|
|
263
|
+
|
|
205
264
|
public boolean _isValidVideo(String filePath) {
|
|
206
265
|
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
|
207
266
|
|
|
@@ -220,4 +279,19 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
220
279
|
private void isValidVideo(String filePath, Promise promise) {
|
|
221
280
|
promise.resolve(_isValidVideo(filePath));
|
|
222
281
|
}
|
|
282
|
+
|
|
283
|
+
@ReactMethod
|
|
284
|
+
private void saveVideo(String filePath, Promise promise) {
|
|
285
|
+
try {
|
|
286
|
+
StorageUtil.saveVideoToGallery(getReactApplicationContext(), filePath);
|
|
287
|
+
} catch (IOException e) {
|
|
288
|
+
e.printStackTrace();
|
|
289
|
+
WritableMap mapE = Arguments.createMap();
|
|
290
|
+
mapE.putString("message", "Fail while copying file to Gallery");
|
|
291
|
+
sendEvent(getReactApplicationContext(), "onError", mapE);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.hideDialog();
|
|
295
|
+
promise.resolve(null);
|
|
296
|
+
}
|
|
223
297
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.videotrim.adapters;
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
import android.content.Context;
|
|
5
4
|
import android.graphics.Bitmap;
|
|
6
5
|
import android.view.LayoutInflater;
|
|
@@ -8,23 +7,18 @@ import android.view.View;
|
|
|
8
7
|
import android.view.ViewGroup;
|
|
9
8
|
import android.widget.ImageView;
|
|
10
9
|
import android.widget.LinearLayout;
|
|
11
|
-
|
|
12
10
|
import androidx.annotation.NonNull;
|
|
13
11
|
import androidx.recyclerview.widget.RecyclerView;
|
|
14
|
-
|
|
15
12
|
import com.videotrim.R;
|
|
16
13
|
import com.videotrim.utils.VideoTrimmerUtil;
|
|
17
|
-
|
|
18
14
|
import java.util.ArrayList;
|
|
19
15
|
import java.util.List;
|
|
20
16
|
|
|
21
17
|
public class VideoTrimmerAdapter extends RecyclerView.Adapter {
|
|
22
|
-
private List<Bitmap> mBitmaps = new ArrayList<>();
|
|
23
|
-
private LayoutInflater mInflater;
|
|
24
|
-
private Context context;
|
|
18
|
+
private final List<Bitmap> mBitmaps = new ArrayList<>();
|
|
19
|
+
private final LayoutInflater mInflater;
|
|
25
20
|
|
|
26
21
|
public VideoTrimmerAdapter(Context context) {
|
|
27
|
-
this.context = context;
|
|
28
22
|
this.mInflater = LayoutInflater.from(context);
|
|
29
23
|
}
|
|
30
24
|
|
|
@@ -42,10 +36,10 @@ public class VideoTrimmerAdapter extends RecyclerView.Adapter {
|
|
|
42
36
|
|
|
43
37
|
public void addBitmaps(Bitmap bitmap) {
|
|
44
38
|
mBitmaps.add(bitmap);
|
|
45
|
-
|
|
39
|
+
notifyItemInserted(mBitmaps.size() - 1);
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
private final class TrimmerViewHolder extends RecyclerView.ViewHolder {
|
|
42
|
+
private static final class TrimmerViewHolder extends RecyclerView.ViewHolder {
|
|
49
43
|
ImageView thumbImageView;
|
|
50
44
|
|
|
51
45
|
TrimmerViewHolder(View itemView) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.videotrim.utils;
|
|
2
2
|
|
|
3
|
-
import android.Manifest;
|
|
4
3
|
import android.annotation.SuppressLint;
|
|
5
4
|
import android.content.ContentValues;
|
|
6
5
|
import android.content.Context;
|
|
@@ -11,12 +10,7 @@ import android.os.Environment;
|
|
|
11
10
|
import android.provider.MediaStore;
|
|
12
11
|
import android.text.TextUtils;
|
|
13
12
|
import android.util.Log;
|
|
14
|
-
|
|
15
|
-
import androidx.fragment.app.FragmentActivity;
|
|
16
|
-
|
|
17
13
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
18
|
-
import com.permissionx.guolindev.PermissionX;
|
|
19
|
-
|
|
20
14
|
import java.io.File;
|
|
21
15
|
import java.io.FileInputStream;
|
|
22
16
|
import java.io.FileOutputStream;
|
|
@@ -202,27 +196,19 @@ public class StorageUtil {
|
|
|
202
196
|
}
|
|
203
197
|
|
|
204
198
|
public static void saveVideoToGallery(ReactApplicationContext context, String videoFilePath) throws IOException {
|
|
205
|
-
|
|
206
|
-
.permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
207
|
-
.request((allGranted, grantedList, deniedList) -> {
|
|
208
|
-
if (allGranted) {
|
|
209
|
-
File videoFile = new File(videoFilePath);
|
|
199
|
+
File videoFile = new File(videoFilePath);
|
|
210
200
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
} else {
|
|
223
|
-
throw new RuntimeException();
|
|
224
|
-
}
|
|
225
|
-
});
|
|
201
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
202
|
+
// For Android 10 and higher (API >= 29)
|
|
203
|
+
saveVideoUsingMediaStore(context, videoFile);
|
|
204
|
+
} else {
|
|
205
|
+
// For Android 9 and below (API < 29)
|
|
206
|
+
try {
|
|
207
|
+
saveVideoUsingTraditionalStorage(context, videoFile);
|
|
208
|
+
} catch (IOException e) {
|
|
209
|
+
throw new RuntimeException(e);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
226
212
|
}
|
|
227
213
|
|
|
228
214
|
// Save video using MediaStore for API level >= 29
|
|
@@ -4,7 +4,16 @@ import android.content.Context;
|
|
|
4
4
|
import android.graphics.Bitmap;
|
|
5
5
|
import android.media.MediaMetadataRetriever;
|
|
6
6
|
import android.net.Uri;
|
|
7
|
+
import android.util.Log;
|
|
7
8
|
|
|
9
|
+
import com.arthenica.ffmpegkit.FFmpegKit;
|
|
10
|
+
import com.arthenica.ffmpegkit.FFmpegSession;
|
|
11
|
+
import com.arthenica.ffmpegkit.FFmpegSessionCompleteCallback;
|
|
12
|
+
import com.arthenica.ffmpegkit.LogCallback;
|
|
13
|
+
import com.arthenica.ffmpegkit.ReturnCode;
|
|
14
|
+
import com.arthenica.ffmpegkit.SessionState;
|
|
15
|
+
import com.arthenica.ffmpegkit.Statistics;
|
|
16
|
+
import com.arthenica.ffmpegkit.StatisticsCallback;
|
|
8
17
|
import com.videotrim.interfaces.VideoTrimListener;
|
|
9
18
|
|
|
10
19
|
import java.text.SimpleDateFormat;
|
|
@@ -15,8 +24,6 @@ import iknow.android.utils.DeviceUtil;
|
|
|
15
24
|
import iknow.android.utils.UnitConverter;
|
|
16
25
|
import iknow.android.utils.callback.SingleCallback;
|
|
17
26
|
import iknow.android.utils.thread.BackgroundExecutor;
|
|
18
|
-
import nl.bravobit.ffmpeg.ExecuteBinaryResponseHandler;
|
|
19
|
-
import nl.bravobit.ffmpeg.FFmpeg;
|
|
20
27
|
|
|
21
28
|
public class VideoTrimmerUtil {
|
|
22
29
|
|
|
@@ -25,37 +32,46 @@ public class VideoTrimmerUtil {
|
|
|
25
32
|
// public static final int VIDEO_MAX_TIME = 10;// max 10 seconds for trimming
|
|
26
33
|
// public static final long MAX_SHOOT_DURATION = VIDEO_MAX_TIME * 1000L;
|
|
27
34
|
public static long maxShootDuration = 10 * 1000L;
|
|
28
|
-
public static
|
|
29
|
-
public static
|
|
35
|
+
public static int MAX_COUNT_RANGE = 10; // how many images in the highlight range of seek bar
|
|
36
|
+
public static int SCREEN_WIDTH_FULL = DeviceUtil.getDeviceWidth();
|
|
30
37
|
public static final int RECYCLER_VIEW_PADDING = UnitConverter.dpToPx(35);
|
|
31
|
-
public static
|
|
38
|
+
public static int VIDEO_FRAMES_WIDTH = SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
|
|
32
39
|
// public static final int THUMB_WIDTH = (SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2) / VIDEO_MAX_TIME;
|
|
33
40
|
public static int mThumbWidth = 0; // make it automatic
|
|
34
41
|
public static final int THUMB_HEIGHT = UnitConverter.dpToPx(50); // x2 for better resolution
|
|
35
42
|
private static final int THUMB_RESOLUTION_RES = 2; // double thumb resolution for better quality
|
|
36
43
|
|
|
37
|
-
public static void trim(Context context, String inputFile, String outputFile, long startMs, long endMs, final VideoTrimListener callback) {
|
|
44
|
+
public static void trim(Context context, String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback) {
|
|
38
45
|
final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
|
|
39
46
|
final String outputName = "trimmedVideo_" + timeStamp + ".mp4";
|
|
40
47
|
outputFile = outputFile + "/" + outputName;
|
|
41
48
|
|
|
42
49
|
String cmd = "-i " + inputFile + " -ss " + startMs + "ms" + " -to " + endMs + "ms -c copy " + outputFile;
|
|
43
50
|
String[] command = cmd.split(" ");
|
|
44
|
-
|
|
45
|
-
final String tempOutFile = outputFile;
|
|
46
|
-
FFmpeg.getInstance(context).execute(command, new ExecuteBinaryResponseHandler() {
|
|
51
|
+
final String tempOutFile = outputFile;
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
callback.onStartTrim();
|
|
54
|
+
FFmpegKit.executeAsync(cmd, session -> {
|
|
55
|
+
SessionState state = session.getState();
|
|
56
|
+
ReturnCode returnCode = session.getReturnCode();
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
Log.d(TAG, String.format("FFmpeg process exited with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace()));
|
|
59
|
+
|
|
60
|
+
if (state.equals(SessionState.COMPLETED)) {
|
|
61
|
+
callback.onFinishTrim(tempOutFile);
|
|
62
|
+
} else {
|
|
63
|
+
callback.onError();
|
|
64
|
+
}
|
|
65
|
+
}, log -> {
|
|
66
|
+
|
|
67
|
+
}, statistics -> {
|
|
68
|
+
int timeInMilliseconds = statistics.getTime();
|
|
69
|
+
if (timeInMilliseconds > 0) {
|
|
70
|
+
int completePercentage =
|
|
71
|
+
(timeInMilliseconds * 100) / videoDuration;
|
|
72
|
+
callback.onTrimmingProgress(Math.min(Math.max(completePercentage, 0), 100));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
59
75
|
}
|
|
60
76
|
|
|
61
77
|
public static void shootVideoThumbInBackground(final Context context, final Uri videoUri, final int totalThumbsCount, final long startPosition,
|
|
@@ -72,10 +88,6 @@ public class VideoTrimmerUtil {
|
|
|
72
88
|
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(frameTime * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
|
|
73
89
|
if(bitmap == null) continue;
|
|
74
90
|
try {
|
|
75
|
-
if (mThumbWidth <= 0) { // only take first item
|
|
76
|
-
int width = THUMB_HEIGHT * bitmap.getWidth() / bitmap.getHeight();
|
|
77
|
-
mThumbWidth = width;
|
|
78
|
-
}
|
|
79
91
|
bitmap = Bitmap.createScaledBitmap(bitmap, mThumbWidth * THUMB_RESOLUTION_RES, THUMB_HEIGHT * THUMB_RESOLUTION_RES, false);
|
|
80
92
|
} catch (final Throwable t) {
|
|
81
93
|
t.printStackTrace();
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
package com.videotrim.widgets;
|
|
2
2
|
|
|
3
|
-
import static com.videotrim.utils.VideoTrimmerUtil.MAX_COUNT_RANGE;
|
|
4
3
|
import static com.videotrim.utils.VideoTrimmerUtil.RECYCLER_VIEW_PADDING;
|
|
5
4
|
import static com.videotrim.utils.VideoTrimmerUtil.VIDEO_FRAMES_WIDTH;
|
|
6
5
|
|
|
7
6
|
import android.animation.ValueAnimator;
|
|
8
7
|
import android.content.Context;
|
|
8
|
+
import android.content.pm.ActivityInfo;
|
|
9
|
+
import android.content.res.Configuration;
|
|
10
|
+
import android.graphics.Bitmap;
|
|
11
|
+
import android.media.MediaMetadataRetriever;
|
|
9
12
|
import android.media.MediaPlayer;
|
|
10
13
|
import android.net.Uri;
|
|
11
14
|
import android.os.Handler;
|
|
@@ -33,6 +36,7 @@ import com.videotrim.interfaces.VideoTrimListener;
|
|
|
33
36
|
import com.videotrim.utils.StorageUtil;
|
|
34
37
|
import com.videotrim.utils.VideoTrimmerUtil;
|
|
35
38
|
|
|
39
|
+
import iknow.android.utils.DeviceUtil;
|
|
36
40
|
import iknow.android.utils.thread.BackgroundExecutor;
|
|
37
41
|
import iknow.android.utils.thread.UiThreadExecutor;
|
|
38
42
|
|
|
@@ -76,6 +80,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
76
80
|
private Boolean mIsPrepared = false;
|
|
77
81
|
private int mMaxDuration = 0;
|
|
78
82
|
|
|
83
|
+
|
|
79
84
|
public VideoTrimmerView(ReactApplicationContext context, AttributeSet attrs) {
|
|
80
85
|
this(context, attrs, 0);
|
|
81
86
|
}
|
|
@@ -91,6 +96,9 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
91
96
|
|
|
92
97
|
private void init(ReactApplicationContext context) {
|
|
93
98
|
this.mContext = context;
|
|
99
|
+
|
|
100
|
+
// listen to onConfigurationChanged doesn't work for this, it runs too soon
|
|
101
|
+
context.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
94
102
|
LayoutInflater.from(context).inflate(R.layout.video_trimmer_view, this, true);
|
|
95
103
|
|
|
96
104
|
mLinearVideo = findViewById(R.id.layout_surface_view);
|
|
@@ -110,13 +118,19 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
110
118
|
private void initRangeSeekBarView() {
|
|
111
119
|
if(mRangeSeekBarView != null) return;
|
|
112
120
|
mLeftProgressPos = 0;
|
|
121
|
+
|
|
122
|
+
VideoTrimmerUtil.SCREEN_WIDTH_FULL = this.getScreenWidthInPortraitMode();
|
|
123
|
+
VideoTrimmerUtil.VIDEO_FRAMES_WIDTH = VideoTrimmerUtil.SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
|
|
124
|
+
VideoTrimmerUtil.MAX_COUNT_RANGE = Math.max(((int) VIDEO_FRAMES_WIDTH / VideoTrimmerUtil.mThumbWidth), VideoTrimmerUtil.MAX_COUNT_RANGE);
|
|
125
|
+
|
|
113
126
|
if (mDuration <= VideoTrimmerUtil.maxShootDuration) {
|
|
114
|
-
mThumbsTotalCount = MAX_COUNT_RANGE;
|
|
127
|
+
mThumbsTotalCount = VideoTrimmerUtil.MAX_COUNT_RANGE;
|
|
115
128
|
mRightProgressPos = mDuration;
|
|
116
129
|
} else {
|
|
117
|
-
mThumbsTotalCount = (int) (mDuration * 1.0f / (VideoTrimmerUtil.maxShootDuration * 1.0f) * MAX_COUNT_RANGE);
|
|
130
|
+
mThumbsTotalCount = (int) (mDuration * 1.0f / (VideoTrimmerUtil.maxShootDuration * 1.0f) * VideoTrimmerUtil.MAX_COUNT_RANGE);
|
|
118
131
|
mRightProgressPos = VideoTrimmerUtil.maxShootDuration;
|
|
119
132
|
}
|
|
133
|
+
|
|
120
134
|
mVideoThumbRecyclerView.addItemDecoration(new SpacesItemDecoration2(RECYCLER_VIEW_PADDING, mThumbsTotalCount));
|
|
121
135
|
mRangeSeekBarView = new RangeSeekBarView(mContext, mLeftProgressPos, mRightProgressPos);
|
|
122
136
|
mRangeSeekBarView.setSelectedMinValue(mLeftProgressPos);
|
|
@@ -126,8 +140,8 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
126
140
|
mRangeSeekBarView.setNotifyWhileDragging(true);
|
|
127
141
|
mRangeSeekBarView.setOnRangeSeekBarChangeListener(mOnRangeSeekBarChangeListener);
|
|
128
142
|
mSeekBarLayout.addView(mRangeSeekBarView);
|
|
129
|
-
if(mThumbsTotalCount - MAX_COUNT_RANGE > 0) {
|
|
130
|
-
mAverageMsPx = (mDuration - VideoTrimmerUtil.maxShootDuration) / (float) (mThumbsTotalCount - MAX_COUNT_RANGE);
|
|
143
|
+
if(mThumbsTotalCount - VideoTrimmerUtil.MAX_COUNT_RANGE > 0) {
|
|
144
|
+
mAverageMsPx = (mDuration - VideoTrimmerUtil.maxShootDuration) / (float) (mThumbsTotalCount - VideoTrimmerUtil.MAX_COUNT_RANGE);
|
|
131
145
|
} else {
|
|
132
146
|
mAverageMsPx = 0f;
|
|
133
147
|
}
|
|
@@ -174,6 +188,14 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
174
188
|
mDuration = mVideoView.getDuration();
|
|
175
189
|
|
|
176
190
|
VideoTrimmerUtil.maxShootDuration = mMaxDuration > 0 ? Math.min(mMaxDuration * 1000L, mDuration) : mDuration;
|
|
191
|
+
|
|
192
|
+
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
|
|
193
|
+
mediaMetadataRetriever.setDataSource(mContext, mSourceUri);
|
|
194
|
+
// take first frame
|
|
195
|
+
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
|
|
196
|
+
int width = VideoTrimmerUtil.THUMB_HEIGHT * bitmap.getWidth() / bitmap.getHeight();
|
|
197
|
+
VideoTrimmerUtil.mThumbWidth = width;
|
|
198
|
+
|
|
177
199
|
if (!getRestoreState()) {
|
|
178
200
|
seekTo((int) mRedProgressBarPos);
|
|
179
201
|
} else {
|
|
@@ -277,6 +299,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
277
299
|
VideoTrimmerUtil.trim(mContext,
|
|
278
300
|
mSourceUri.getPath(),
|
|
279
301
|
StorageUtil.getCacheDir(),
|
|
302
|
+
mDuration,
|
|
280
303
|
mLeftProgressPos,
|
|
281
304
|
mRightProgressPos,
|
|
282
305
|
mOnTrimVideoListener);
|
|
@@ -433,11 +456,33 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
433
456
|
}
|
|
434
457
|
}
|
|
435
458
|
|
|
459
|
+
@Override
|
|
460
|
+
protected void onDetachedFromWindow() {
|
|
461
|
+
super.onDetachedFromWindow();
|
|
462
|
+
mContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
463
|
+
}
|
|
464
|
+
|
|
436
465
|
/**
|
|
437
|
-
* Cancel trim thread
|
|
466
|
+
* Cancel trim thread execute action when finish
|
|
438
467
|
*/
|
|
439
468
|
@Override public void onDestroy() {
|
|
440
469
|
BackgroundExecutor.cancelAll("", true);
|
|
441
470
|
UiThreadExecutor.cancelAll("");
|
|
471
|
+
mContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
private int getScreenWidthInPortraitMode() {
|
|
475
|
+
int screenWidth = DeviceUtil.getDeviceWidth();
|
|
476
|
+
int screenHeight = DeviceUtil.getDeviceHeight();
|
|
477
|
+
|
|
478
|
+
// Check the current orientation
|
|
479
|
+
int currentOrientation = getResources().getConfiguration().orientation;
|
|
480
|
+
|
|
481
|
+
// Swap width and height if the current orientation is landscape
|
|
482
|
+
if (currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
483
|
+
return screenHeight;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return screenWidth;
|
|
442
487
|
}
|
|
443
488
|
}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -15,16 +15,35 @@ const VideoTrim = _reactNative.NativeModules.VideoTrim ? _reactNative.NativeModu
|
|
|
15
15
|
throw new Error(LINKING_ERROR);
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
|
-
function showEditor(videoPath) {
|
|
18
|
+
async function showEditor(videoPath) {
|
|
19
19
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
20
20
|
const {
|
|
21
21
|
maxDuration,
|
|
22
22
|
saveToPhoto = true
|
|
23
23
|
} = config;
|
|
24
|
-
VideoTrim.showEditor(videoPath, {
|
|
24
|
+
const outputPath = await VideoTrim.showEditor(videoPath, {
|
|
25
25
|
saveToPhoto,
|
|
26
26
|
maxDuration
|
|
27
27
|
});
|
|
28
|
+
if (_reactNative.Platform.OS === 'android' && saveToPhoto) {
|
|
29
|
+
try {
|
|
30
|
+
const granted = await _reactNative.PermissionsAndroid.request(_reactNative.PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
|
31
|
+
title: 'Video Trimmer Camera Access Required',
|
|
32
|
+
message: 'Grant access to your Camera to write output Video',
|
|
33
|
+
buttonNeutral: 'Ask Me Later',
|
|
34
|
+
buttonNegative: 'Cancel',
|
|
35
|
+
buttonPositive: 'OK'
|
|
36
|
+
});
|
|
37
|
+
if (granted === _reactNative.PermissionsAndroid.RESULTS.GRANTED) {
|
|
38
|
+
await VideoTrim.saveVideo(outputPath);
|
|
39
|
+
} else {
|
|
40
|
+
VideoTrim.hideDialog();
|
|
41
|
+
throw new Error('Camera permission denied');
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
28
47
|
}
|
|
29
48
|
function isValidVideo(videoPath) {
|
|
30
49
|
return VideoTrim.isValidVideo(videoPath);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","VideoTrim","NativeModules","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","maxDuration","saveToPhoto","isValidVideo"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAMC,aAAa,GAChB,kFAAiF,GAClFC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,SAAS,GAAGC,0BAAa,CAACD,SAAS,GACrCC,0BAAa,CAACD,SAAS,GACvB,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAOE,
|
|
1
|
+
{"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","VideoTrim","NativeModules","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","maxDuration","saveToPhoto","outputPath","OS","granted","PermissionsAndroid","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","saveVideo","hideDialog","err","isValidVideo"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAMC,aAAa,GAChB,kFAAiF,GAClFC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,SAAS,GAAGC,0BAAa,CAACD,SAAS,GACrCC,0BAAa,CAACD,SAAS,GACvB,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAOE,eAAeU,UAAUA,CAC9BC,SAAiB,EAEF;EAAA,IADfC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EAEzB,MAAM;IAAEG,WAAW;IAAEC,WAAW,GAAG;EAAK,CAAC,GAAGL,MAAM;EAClD,MAAMM,UAAU,GAAG,MAAMb,SAAS,CAACK,UAAU,CAACC,SAAS,EAAE;IACvDM,WAAW;IACXD;EACF,CAAC,CAAC;EAEF,IAAIf,qBAAQ,CAACkB,EAAE,KAAK,SAAS,IAAIF,WAAW,EAAE;IAC5C,IAAI;MACF,MAAMG,OAAO,GAAG,MAAMC,+BAAkB,CAACC,OAAO,CAC9CD,+BAAkB,CAACE,WAAW,CAACC,sBAAsB,EACrD;QACEC,KAAK,EAAE,sCAAsC;QAC7CC,OAAO,EAAE,mDAAmD;QAC5DC,aAAa,EAAE,cAAc;QAC7BC,cAAc,EAAE,QAAQ;QACxBC,cAAc,EAAE;MAClB,CACF,CAAC;MACD,IAAIT,OAAO,KAAKC,+BAAkB,CAACS,OAAO,CAACC,OAAO,EAAE;QAClD,MAAM1B,SAAS,CAAC2B,SAAS,CAACd,UAAU,CAAC;MACvC,CAAC,MAAM;QACLb,SAAS,CAAC4B,UAAU,CAAC,CAAC;QACtB,MAAM,IAAIxB,KAAK,CAAC,0BAA0B,CAAC;MAC7C;IACF,CAAC,CAAC,OAAOyB,GAAG,EAAE;MACZ,MAAMA,GAAG;IACX;EACF;AACF;AAEO,SAASC,YAAYA,CAACxB,SAAiB,EAAoB;EAChE,OAAON,SAAS,CAAC8B,YAAY,CAACxB,SAAS,CAAC;AAC1C"}
|
package/lib/module/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NativeModules, Platform } from 'react-native';
|
|
1
|
+
import { NativeModules, PermissionsAndroid, Platform } from 'react-native';
|
|
2
2
|
const LINKING_ERROR = `The package 'react-native-video-trim' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
|
|
3
3
|
ios: "- You have run 'pod install'\n",
|
|
4
4
|
default: ''
|
|
@@ -8,16 +8,35 @@ const VideoTrim = NativeModules.VideoTrim ? NativeModules.VideoTrim : new Proxy(
|
|
|
8
8
|
throw new Error(LINKING_ERROR);
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
|
-
export function showEditor(videoPath) {
|
|
11
|
+
export async function showEditor(videoPath) {
|
|
12
12
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
13
13
|
const {
|
|
14
14
|
maxDuration,
|
|
15
15
|
saveToPhoto = true
|
|
16
16
|
} = config;
|
|
17
|
-
VideoTrim.showEditor(videoPath, {
|
|
17
|
+
const outputPath = await VideoTrim.showEditor(videoPath, {
|
|
18
18
|
saveToPhoto,
|
|
19
19
|
maxDuration
|
|
20
20
|
});
|
|
21
|
+
if (Platform.OS === 'android' && saveToPhoto) {
|
|
22
|
+
try {
|
|
23
|
+
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
|
24
|
+
title: 'Video Trimmer Camera Access Required',
|
|
25
|
+
message: 'Grant access to your Camera to write output Video',
|
|
26
|
+
buttonNeutral: 'Ask Me Later',
|
|
27
|
+
buttonNegative: 'Cancel',
|
|
28
|
+
buttonPositive: 'OK'
|
|
29
|
+
});
|
|
30
|
+
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
31
|
+
await VideoTrim.saveVideo(outputPath);
|
|
32
|
+
} else {
|
|
33
|
+
VideoTrim.hideDialog();
|
|
34
|
+
throw new Error('Camera permission denied');
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
21
40
|
}
|
|
22
41
|
export function isValidVideo(videoPath) {
|
|
23
42
|
return VideoTrim.isValidVideo(videoPath);
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","Platform","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","maxDuration","saveToPhoto","isValidVideo"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,SAASA,aAAa,EAAEC,QAAQ,QAAQ,cAAc;
|
|
1
|
+
{"version":3,"names":["NativeModules","PermissionsAndroid","Platform","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","maxDuration","saveToPhoto","outputPath","OS","granted","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","saveVideo","hideDialog","err","isValidVideo"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,SAASA,aAAa,EAAEC,kBAAkB,EAAEC,QAAQ,QAAQ,cAAc;AAE1E,MAAMC,aAAa,GAChB,kFAAiF,GAClFD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,SAAS,GAAGP,aAAa,CAACO,SAAS,GACrCP,aAAa,CAACO,SAAS,GACvB,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAOL,OAAO,eAAeQ,UAAUA,CAC9BC,SAAiB,EAEF;EAAA,IADfC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EAEzB,MAAM;IAAEG,WAAW;IAAEC,WAAW,GAAG;EAAK,CAAC,GAAGL,MAAM;EAClD,MAAMM,UAAU,GAAG,MAAMZ,SAAS,CAACI,UAAU,CAACC,SAAS,EAAE;IACvDM,WAAW;IACXD;EACF,CAAC,CAAC;EAEF,IAAIf,QAAQ,CAACkB,EAAE,KAAK,SAAS,IAAIF,WAAW,EAAE;IAC5C,IAAI;MACF,MAAMG,OAAO,GAAG,MAAMpB,kBAAkB,CAACqB,OAAO,CAC9CrB,kBAAkB,CAACsB,WAAW,CAACC,sBAAsB,EACrD;QACEC,KAAK,EAAE,sCAAsC;QAC7CC,OAAO,EAAE,mDAAmD;QAC5DC,aAAa,EAAE,cAAc;QAC7BC,cAAc,EAAE,QAAQ;QACxBC,cAAc,EAAE;MAClB,CACF,CAAC;MACD,IAAIR,OAAO,KAAKpB,kBAAkB,CAAC6B,OAAO,CAACC,OAAO,EAAE;QAClD,MAAMxB,SAAS,CAACyB,SAAS,CAACb,UAAU,CAAC;MACvC,CAAC,MAAM;QACLZ,SAAS,CAAC0B,UAAU,CAAC,CAAC;QACtB,MAAM,IAAIvB,KAAK,CAAC,0BAA0B,CAAC;MAC7C;IACF,CAAC,CAAC,OAAOwB,GAAG,EAAE;MACZ,MAAMA,GAAG;IACX;EACF;AACF;AAEA,OAAO,SAASC,YAAYA,CAACvB,SAAiB,EAAoB;EAChE,OAAOL,SAAS,CAAC4B,YAAY,CAACvB,SAAS,CAAC;AAC1C"}
|
|
@@ -2,6 +2,6 @@ export interface EditorConfig {
|
|
|
2
2
|
saveToPhoto?: boolean;
|
|
3
3
|
maxDuration?: number;
|
|
4
4
|
}
|
|
5
|
-
export declare function showEditor(videoPath: string, config?: EditorConfig): void
|
|
5
|
+
export declare function showEditor(videoPath: string, config?: EditorConfig): Promise<void>;
|
|
6
6
|
export declare function isValidVideo(videoPath: string): Promise<boolean>;
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,YAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEhE"}
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NativeModules, Platform } from 'react-native';
|
|
1
|
+
import { NativeModules, PermissionsAndroid, Platform } from 'react-native';
|
|
2
2
|
|
|
3
3
|
const LINKING_ERROR =
|
|
4
4
|
`The package 'react-native-video-trim' doesn't seem to be linked. Make sure: \n\n` +
|
|
@@ -22,12 +22,38 @@ export interface EditorConfig {
|
|
|
22
22
|
maxDuration?: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export function showEditor(
|
|
25
|
+
export async function showEditor(
|
|
26
|
+
videoPath: string,
|
|
27
|
+
config: EditorConfig = {}
|
|
28
|
+
): Promise<void> {
|
|
26
29
|
const { maxDuration, saveToPhoto = true } = config;
|
|
27
|
-
VideoTrim.showEditor(videoPath, {
|
|
30
|
+
const outputPath = await VideoTrim.showEditor(videoPath, {
|
|
28
31
|
saveToPhoto,
|
|
29
32
|
maxDuration,
|
|
30
33
|
});
|
|
34
|
+
|
|
35
|
+
if (Platform.OS === 'android' && saveToPhoto) {
|
|
36
|
+
try {
|
|
37
|
+
const granted = await PermissionsAndroid.request(
|
|
38
|
+
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE!,
|
|
39
|
+
{
|
|
40
|
+
title: 'Video Trimmer Camera Access Required',
|
|
41
|
+
message: 'Grant access to your Camera to write output Video',
|
|
42
|
+
buttonNeutral: 'Ask Me Later',
|
|
43
|
+
buttonNegative: 'Cancel',
|
|
44
|
+
buttonPositive: 'OK',
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
48
|
+
await VideoTrim.saveVideo(outputPath);
|
|
49
|
+
} else {
|
|
50
|
+
VideoTrim.hideDialog();
|
|
51
|
+
throw new Error('Camera permission denied');
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
31
57
|
}
|
|
32
58
|
|
|
33
59
|
export function isValidVideo(videoPath: string): Promise<boolean> {
|