react-native-video-trim 1.0.1 → 1.0.3

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,8 +91,7 @@ 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 'androidx.recyclerview:recyclerview:1.1.0'
95
- implementation 'com.guolindev.permissionx:permissionx:1.7.1'
94
+ implementation 'androidx.recyclerview:recyclerview:1.3.1'
96
95
  implementation 'com.arthenica:ffmpeg-kit-full:5.1.LTS'
97
96
  }
98
97
 
@@ -1,2 +1,2 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.videotrim">
2
2
  </manifest>
@@ -2,7 +2,6 @@ package com.videotrim;
2
2
 
3
3
  import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
4
4
 
5
- import android.Manifest;
6
5
  import android.app.Activity;
7
6
  import android.content.res.ColorStateList;
8
7
  import android.graphics.Color;
@@ -18,7 +17,6 @@ import android.widget.TextView;
18
17
  import androidx.annotation.NonNull;
19
18
  import androidx.annotation.Nullable;
20
19
  import androidx.appcompat.app.AlertDialog;
21
- import androidx.fragment.app.FragmentActivity;
22
20
 
23
21
  import com.facebook.react.bridge.Arguments;
24
22
  import com.facebook.react.bridge.LifecycleEventListener;
@@ -31,12 +29,10 @@ import com.facebook.react.bridge.ReadableMap;
31
29
  import com.facebook.react.bridge.WritableMap;
32
30
  import com.facebook.react.module.annotations.ReactModule;
33
31
  import com.facebook.react.modules.core.DeviceEventManagerModule;
34
- import com.permissionx.guolindev.PermissionX;
35
32
  import com.videotrim.interfaces.VideoTrimListener;
36
33
  import com.videotrim.utils.StorageUtil;
37
34
  import com.videotrim.widgets.VideoTrimmerView;
38
35
 
39
- import java.io.File;
40
36
  import java.io.IOException;
41
37
  import iknow.android.utils.BaseUtils;
42
38
 
@@ -52,6 +48,8 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
52
48
  private int mMaxDuration = 0;
53
49
  private int listenerCount = 0;
54
50
 
51
+ private Promise showEditorPromise;
52
+
55
53
  public VideoTrimModule(ReactApplicationContext reactContext) {
56
54
  super(reactContext);
57
55
  }
@@ -64,7 +62,8 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
64
62
 
65
63
 
66
64
  @ReactMethod
67
- public void showEditor(String videoPath, ReadableMap config) {
65
+ public void showEditor(String videoPath, ReadableMap config, Promise promise) {
66
+ showEditorPromise = promise;
68
67
  if (trimmerView != null || alertDialog != null) {
69
68
  return;
70
69
  }
@@ -103,17 +102,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
103
102
  alertDialog.setView(trimmerView);
104
103
  alertDialog.show();
105
104
 
106
-
107
- if (this.mSaveToPhoto) {
108
- // some how this is not fired when user first install app and tap Allow
109
- // so that we just request permission first, and later it'll be able to save to Gallery immediately
110
- PermissionX.init((FragmentActivity) getReactApplicationContext().getCurrentActivity())
111
- .permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
112
- .request((allGranted, grantedList, deniedList) -> {
113
-
114
- });
115
- }
116
-
117
105
  // this is to ensure to release resource if dialog is dismissed in unexpected way (Eg. open control/notification center by dragging from top of screen)
118
106
  alertDialog.setOnDismissListener(dialog -> {
119
107
  // This is called in same thread as the trimmer view -> UI thread
@@ -170,35 +158,13 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
170
158
 
171
159
  @Override public void onFinishTrim(String in) {
172
160
  runOnUiThread(() -> {
161
+ WritableMap map = Arguments.createMap();
162
+ map.putString("outputPath", in);
163
+ sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
173
164
  if (mSaveToPhoto) {
174
- PermissionX.init((FragmentActivity) getReactApplicationContext().getCurrentActivity())
175
- .permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
176
- .request((allGranted, grantedList, deniedList) -> {
177
- // some how this is not fired when user first tap Allow
178
- if (allGranted) {
179
- try {
180
- StorageUtil.saveVideoToGallery(getReactApplicationContext(), in);
181
- WritableMap map = Arguments.createMap();
182
- map.putString("outputPath", in);
183
- sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
184
- } catch (IOException e) {
185
- e.printStackTrace();
186
- WritableMap mapE = Arguments.createMap();
187
- mapE.putString("message", "Fail while copying file to Gallery");
188
- sendEvent(getReactApplicationContext(), "onError", mapE);
189
- }
190
-
191
- this.hideDialog();
192
- } else {
193
- WritableMap mapE = Arguments.createMap();
194
- mapE.putString("message", "Fail to save to Gallery. Please check if you have correct permission");
195
- sendEvent(getReactApplicationContext(), "onError", mapE);
196
- }
197
- });
165
+ showEditorPromise.resolve(in);
198
166
  } else {
199
- WritableMap map = Arguments.createMap();
200
- map.putString("outputPath", in);
201
- sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
167
+ hideDialog();
202
168
  }
203
169
  });
204
170
  }
@@ -215,6 +181,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
215
181
  this.hideDialog();
216
182
  }
217
183
 
184
+ @ReactMethod
218
185
  private void hideDialog() {
219
186
  if (mProgressDialog != null) {
220
187
  if (mProgressDialog.isShowing()) mProgressDialog.dismiss();
@@ -312,4 +279,19 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
312
279
  private void isValidVideo(String filePath, Promise promise) {
313
280
  promise.resolve(_isValidVideo(filePath));
314
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
+ }
315
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) {
@@ -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;
@@ -32,10 +32,10 @@ public class VideoTrimmerUtil {
32
32
  // public static final int VIDEO_MAX_TIME = 10;// max 10 seconds for trimming
33
33
  // public static final long MAX_SHOOT_DURATION = VIDEO_MAX_TIME * 1000L;
34
34
  public static long maxShootDuration = 10 * 1000L;
35
- public static final int MAX_COUNT_RANGE = 10; // how many images in the highlight range of seek bar
36
- 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();
37
37
  public static final int RECYCLER_VIEW_PADDING = UnitConverter.dpToPx(35);
38
- 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;
39
39
  // public static final int THUMB_WIDTH = (SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2) / VIDEO_MAX_TIME;
40
40
  public static int mThumbWidth = 0; // make it automatic
41
41
  public static final int THUMB_HEIGHT = UnitConverter.dpToPx(50); // x2 for better resolution
@@ -88,10 +88,6 @@ public class VideoTrimmerUtil {
88
88
  Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(frameTime * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
89
89
  if(bitmap == null) continue;
90
90
  try {
91
- if (mThumbWidth <= 0) { // only take first item
92
- int width = THUMB_HEIGHT * bitmap.getWidth() / bitmap.getHeight();
93
- mThumbWidth = width;
94
- }
95
91
  bitmap = Bitmap.createScaledBitmap(bitmap, mThumbWidth * THUMB_RESOLUTION_RES, THUMB_HEIGHT * THUMB_RESOLUTION_RES, false);
96
92
  } catch (final Throwable t) {
97
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 {
@@ -434,11 +456,33 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
434
456
  }
435
457
  }
436
458
 
459
+ @Override
460
+ protected void onDetachedFromWindow() {
461
+ super.onDetachedFromWindow();
462
+ mContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
463
+ }
464
+
437
465
  /**
438
- * Cancel trim thread execut action when finish
466
+ * Cancel trim thread execute action when finish
439
467
  */
440
468
  @Override public void onDestroy() {
441
469
  BackgroundExecutor.cancelAll("", true);
442
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;
443
487
  }
444
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.1",
3
+ "version": "1.0.3",
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> {