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 CHANGED
@@ -1,4 +1,4 @@
1
- # react-native-video-trim
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
- cd ios & pod install
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';
@@ -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 'nl.bravobit:android-ffmpeg:1.1.7'
95
- implementation 'androidx.recyclerview:recyclerview:1.1.0'
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.app.ProgressDialog;
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 ProgressDialog mProgressDialog;
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(getReactApplicationContext().getResources().getString(R.string.trimming)).show();
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
- if (mProgressDialog.isShowing()) mProgressDialog.dismiss();
145
- WritableMap map = Arguments.createMap();
146
- map.putString("outputPath", in);
147
- sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
148
- if (mSaveToPhoto) {
149
- try {
150
- StorageUtil.saveVideoToGallery(getReactApplicationContext(), in);
151
- } catch (IOException e) {
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 ProgressDialog buildDialog(String msg) {
176
- if (mProgressDialog == null) {
177
- mProgressDialog = ProgressDialog.show(getReactApplicationContext().getCurrentActivity(), "", msg);
178
- }
179
- mProgressDialog.setMessage(msg);
180
- return mProgressDialog;
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
- notifyDataSetChanged();
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) {
@@ -2,6 +2,8 @@ package com.videotrim.interfaces;
2
2
 
3
3
  public interface VideoTrimListener {
4
4
  void onStartTrim();
5
+ void onTrimmingProgress(int percentage);
5
6
  void onFinishTrim(String url);
7
+ void onError();
6
8
  void onCancel();
7
9
  }
@@ -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
- PermissionX.init((FragmentActivity) context.getCurrentActivity())
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
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
212
- // For Android 10 and higher (API >= 29)
213
- saveVideoUsingMediaStore(context, videoFile);
214
- } else {
215
- // For Android 9 and below (API < 29)
216
- try {
217
- saveVideoUsingTraditionalStorage(context, videoFile);
218
- } catch (IOException e) {
219
- throw new RuntimeException(e);
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 final int MAX_COUNT_RANGE = 10; // how many images in the highlight range of seek bar
29
- public static final int SCREEN_WIDTH_FULL = DeviceUtil.getDeviceWidth();
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 final int VIDEO_FRAMES_WIDTH = SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
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
- try {
45
- final String tempOutFile = outputFile;
46
- FFmpeg.getInstance(context).execute(command, new ExecuteBinaryResponseHandler() {
51
+ final String tempOutFile = outputFile;
47
52
 
48
- @Override public void onSuccess(String s) {
49
- callback.onFinishTrim(tempOutFile);
50
- }
53
+ callback.onStartTrim();
54
+ FFmpegKit.executeAsync(cmd, session -> {
55
+ SessionState state = session.getState();
56
+ ReturnCode returnCode = session.getReturnCode();
51
57
 
52
- @Override public void onStart() {
53
- callback.onStartTrim();
54
- }
55
- });
56
- } catch (Exception e) {
57
- e.printStackTrace();
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 execut action when finish
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
  }
@@ -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,SAASU,UAAUA,CAACC,SAAiB,EAAmC;EAAA,IAAjCC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EACrE,MAAM;IAAEG,WAAW;IAAEC,WAAW,GAAG;EAAK,CAAC,GAAGL,MAAM;EAClDP,SAAS,CAACK,UAAU,CAACC,SAAS,EAAE;IAC9BM,WAAW;IACXD;EACF,CAAC,CAAC;AACJ;AAEO,SAASE,YAAYA,CAACP,SAAiB,EAAoB;EAChE,OAAON,SAAS,CAACa,YAAY,CAACP,SAAS,CAAC;AAC1C"}
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"}
@@ -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);
@@ -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;AAEtD,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,GAAGN,aAAa,CAACM,SAAS,GACrCN,aAAa,CAACM,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,SAASQ,UAAUA,CAACC,SAAiB,EAAmC;EAAA,IAAjCC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EACrE,MAAM;IAAEG,WAAW;IAAEC,WAAW,GAAG;EAAK,CAAC,GAAGL,MAAM;EAClDN,SAAS,CAACI,UAAU,CAACC,SAAS,EAAE;IAC9BM,WAAW;IACXD;EACF,CAAC,CAAC;AACJ;AAEA,OAAO,SAASE,YAAYA,CAACP,SAAiB,EAAoB;EAChE,OAAOL,SAAS,CAACY,YAAY,CAACP,SAAS,CAAC;AAC1C"}
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,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,YAAiB,GAAG,IAAI,CAM7E;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEhE"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-video-trim",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Video trimmer for your React Native app",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
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(videoPath: string, config: EditorConfig = {}): void {
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> {