react-native-video-trim 1.0.0 → 1.0.1

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.
@@ -91,9 +91,9 @@ 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
94
  implementation 'androidx.recyclerview:recyclerview:1.1.0'
96
95
  implementation 'com.guolindev.permissionx:permissionx:1.7.1'
96
+ implementation 'com.arthenica:ffmpeg-kit-full:5.1.LTS'
97
97
  }
98
98
 
99
99
  if (isNewArchitectureEnabled()) {
@@ -1,14 +1,25 @@
1
1
  package com.videotrim;
2
2
 
3
3
  import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
4
+
5
+ import android.Manifest;
4
6
  import android.app.Activity;
5
- import android.app.ProgressDialog;
7
+ import android.content.res.ColorStateList;
8
+ import android.graphics.Color;
6
9
  import android.media.MediaMetadataRetriever;
7
10
  import android.net.Uri;
11
+ import android.os.Build;
12
+ import android.view.Gravity;
13
+ import android.view.ViewGroup;
14
+ import android.widget.LinearLayout;
15
+ import android.widget.ProgressBar;
16
+ import android.widget.TextView;
8
17
 
9
18
  import androidx.annotation.NonNull;
10
19
  import androidx.annotation.Nullable;
11
20
  import androidx.appcompat.app.AlertDialog;
21
+ import androidx.fragment.app.FragmentActivity;
22
+
12
23
  import com.facebook.react.bridge.Arguments;
13
24
  import com.facebook.react.bridge.LifecycleEventListener;
14
25
  import com.facebook.react.bridge.Promise;
@@ -20,12 +31,14 @@ import com.facebook.react.bridge.ReadableMap;
20
31
  import com.facebook.react.bridge.WritableMap;
21
32
  import com.facebook.react.module.annotations.ReactModule;
22
33
  import com.facebook.react.modules.core.DeviceEventManagerModule;
34
+ import com.permissionx.guolindev.PermissionX;
23
35
  import com.videotrim.interfaces.VideoTrimListener;
24
36
  import com.videotrim.utils.StorageUtil;
25
37
  import com.videotrim.widgets.VideoTrimmerView;
38
+
39
+ import java.io.File;
26
40
  import java.io.IOException;
27
41
  import iknow.android.utils.BaseUtils;
28
- import nl.bravobit.ffmpeg.FFmpeg;
29
42
 
30
43
  @ReactModule(name = VideoTrimModule.NAME)
31
44
  public class VideoTrimModule extends ReactContextBaseJavaModule implements VideoTrimListener, LifecycleEventListener {
@@ -33,7 +46,8 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
33
46
  private static Boolean isInit = false;
34
47
  private VideoTrimmerView trimmerView;
35
48
  private AlertDialog alertDialog;
36
- private ProgressDialog mProgressDialog;
49
+ private AlertDialog mProgressDialog;
50
+ private ProgressBar mProgressBar;
37
51
  private Boolean mSaveToPhoto = true;
38
52
  private int mMaxDuration = 0;
39
53
  private int listenerCount = 0;
@@ -89,6 +103,17 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
89
103
  alertDialog.setView(trimmerView);
90
104
  alertDialog.show();
91
105
 
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
+
92
117
  // 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)
93
118
  alertDialog.setOnDismissListener(dialog -> {
94
119
  // This is called in same thread as the trimmer view -> UI thread
@@ -107,12 +132,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
107
132
  isInit = true;
108
133
  // we have to init this before create videoTrimmerView
109
134
  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
135
  }
117
136
 
118
137
  @Override
@@ -136,25 +155,58 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
136
155
  @Override public void onStartTrim() {
137
156
  sendEvent(getReactApplicationContext(), "onStartTrimming", null);
138
157
  runOnUiThread(() -> {
139
- buildDialog(getReactApplicationContext().getResources().getString(R.string.trimming)).show();
158
+ buildDialog();
140
159
  });
141
160
  }
142
161
 
162
+ @Override public void onTrimmingProgress(int percentage) {
163
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
164
+ mProgressBar.setProgress(percentage, true);
165
+ } else {
166
+ mProgressBar.setProgress(percentage);
167
+ }
168
+ }
169
+
170
+
143
171
  @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);
172
+ runOnUiThread(() -> {
173
+ 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
+ });
198
+ } else {
199
+ WritableMap map = Arguments.createMap();
200
+ map.putString("outputPath", in);
201
+ sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
156
202
  }
157
- }
203
+ });
204
+ }
205
+
206
+ @Override public void onError() {
207
+ WritableMap map = Arguments.createMap();
208
+ map.putString("message", "Error when trimming, please try again");
209
+ sendEvent(getReactApplicationContext(), "onError", map);
158
210
  this.hideDialog();
159
211
  }
160
212
 
@@ -164,6 +216,12 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
164
216
  }
165
217
 
166
218
  private void hideDialog() {
219
+ if (mProgressDialog != null) {
220
+ if (mProgressDialog.isShowing()) mProgressDialog.dismiss();
221
+ mProgressBar = null;
222
+ mProgressDialog = null;
223
+ }
224
+
167
225
  if (alertDialog != null) {
168
226
  if(alertDialog.isShowing()) {
169
227
  alertDialog.dismiss();
@@ -172,12 +230,45 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
172
230
  }
173
231
  }
174
232
 
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;
233
+ private void buildDialog() {
234
+ Activity activity = getReactApplicationContext().getCurrentActivity();
235
+ // Create the parent layout for the dialog
236
+ LinearLayout layout = new LinearLayout(activity);
237
+ layout.setLayoutParams(new ViewGroup.LayoutParams(
238
+ ViewGroup.LayoutParams.WRAP_CONTENT,
239
+ ViewGroup.LayoutParams.WRAP_CONTENT
240
+ ));
241
+ layout.setOrientation(LinearLayout.VERTICAL);
242
+ layout.setGravity(Gravity.CENTER_HORIZONTAL);
243
+ layout.setPadding(16, 32, 16, 32);
244
+
245
+ // Create and add the TextView
246
+ TextView textView = new TextView(activity);
247
+ textView.setLayoutParams(new ViewGroup.LayoutParams(
248
+ ViewGroup.LayoutParams.WRAP_CONTENT,
249
+ ViewGroup.LayoutParams.WRAP_CONTENT
250
+ ));
251
+ textView.setText(getReactApplicationContext().getResources().getString(R.string.trimming));
252
+ textView.setTextSize(18);
253
+ layout.addView(textView);
254
+
255
+ // Create and add the ProgressBar
256
+ mProgressBar = new ProgressBar(activity, null, android.R.attr.progressBarStyleHorizontal);
257
+ mProgressBar.setLayoutParams(new ViewGroup.LayoutParams(
258
+ ViewGroup.LayoutParams.MATCH_PARENT,
259
+ ViewGroup.LayoutParams.WRAP_CONTENT
260
+ ));
261
+ mProgressBar.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#2196F3")));
262
+ layout.addView(mProgressBar);
263
+
264
+ // Create the AlertDialog
265
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
266
+ builder.setCancelable(false);
267
+ builder.setView(layout);
268
+
269
+ // Show the dialog
270
+ mProgressDialog = builder.create();
271
+ mProgressDialog.show();
181
272
  }
182
273
 
183
274
  @ReactMethod
@@ -202,6 +293,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
202
293
  }
203
294
  }
204
295
 
296
+
205
297
  public boolean _isValidVideo(String filePath) {
206
298
  MediaMetadataRetriever retriever = new MediaMetadataRetriever();
207
299
 
@@ -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
  }
@@ -202,27 +202,19 @@ public class StorageUtil {
202
202
  }
203
203
 
204
204
  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);
210
-
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
- });
205
+ File videoFile = new File(videoFilePath);
206
+
207
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
208
+ // For Android 10 and higher (API >= 29)
209
+ saveVideoUsingMediaStore(context, videoFile);
210
+ } else {
211
+ // For Android 9 and below (API < 29)
212
+ try {
213
+ saveVideoUsingTraditionalStorage(context, videoFile);
214
+ } catch (IOException e) {
215
+ throw new RuntimeException(e);
216
+ }
217
+ }
226
218
  }
227
219
 
228
220
  // 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
 
@@ -34,28 +41,37 @@ public class VideoTrimmerUtil {
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,
@@ -277,6 +277,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
277
277
  VideoTrimmerUtil.trim(mContext,
278
278
  mSourceUri.getPath(),
279
279
  StorageUtil.getCacheDir(),
280
+ mDuration,
280
281
  mLeftProgressPos,
281
282
  mRightProgressPos,
282
283
  mOnTrimVideoListener);
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.1",
4
4
  "description": "Video trimmer for your React Native app",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",