react-native-video-trim 1.0.4 → 1.0.6
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 +3 -10
- package/android/build.gradle +0 -2
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +2 -10
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +0 -6
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +25 -7
- package/android/src/main/java/iknow/android/utils/BaseUtils.java +33 -0
- package/android/src/main/java/iknow/android/utils/BuildConfig.java +18 -0
- package/android/src/main/java/iknow/android/utils/DateUtil.java +64 -0
- package/android/src/main/java/iknow/android/utils/DeviceUtil.java +46 -0
- package/android/src/main/java/iknow/android/utils/UnitConverter.java +48 -0
- package/android/src/main/java/iknow/android/utils/callback/SingleCallback.java +11 -0
- package/android/src/main/java/iknow/android/utils/thread/BackgroundExecutor.java +246 -0
- package/android/src/main/java/iknow/android/utils/thread/UiThreadExecutor.java +120 -0
- package/ios/VideoTrim.swift +17 -0
- package/lib/commonjs/index.js +26 -20
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +26 -20
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +3 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +32 -22
package/README.md
CHANGED
|
@@ -15,16 +15,6 @@ npm install react-native-video-trim
|
|
|
15
15
|
|
|
16
16
|
yarn add react-native-video-trim
|
|
17
17
|
```
|
|
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
|
-
|
|
28
18
|
Next install CocoaPods deps:
|
|
29
19
|
|
|
30
20
|
```
|
|
@@ -157,6 +147,9 @@ Main method to show Video Editor UI.
|
|
|
157
147
|
|
|
158
148
|
- `saveToPhoto` (optional, `default = true`): whether to save video to photo/gallery after editing
|
|
159
149
|
- `maxDuration` (optional): maximum duration for the trimmed video
|
|
150
|
+
- `leftButtonText` (optional): text for left button in Editor dialog
|
|
151
|
+
- `rightButtonText` (optional): text for right button in Editor dialog
|
|
152
|
+
- `title` (optional, iOS only): title Editor dialog
|
|
160
153
|
|
|
161
154
|
If `saveToPhoto = true`, you must ensure that you have request permission to write to photo/gallery
|
|
162
155
|
- For Android: you need to have `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` in AndroidManifest.xml
|
package/android/build.gradle
CHANGED
|
@@ -81,7 +81,6 @@ android {
|
|
|
81
81
|
repositories {
|
|
82
82
|
mavenCentral()
|
|
83
83
|
google()
|
|
84
|
-
jcenter()
|
|
85
84
|
}
|
|
86
85
|
|
|
87
86
|
|
|
@@ -90,7 +89,6 @@ dependencies {
|
|
|
90
89
|
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
|
|
91
90
|
//noinspection GradleDynamicVersion
|
|
92
91
|
implementation "com.facebook.react:react-native:+"
|
|
93
|
-
implementation 'com.github.iknow4:android-utils-sdk:1.1.2'
|
|
94
92
|
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
|
95
93
|
implementation 'com.arthenica:ffmpeg-kit-full:5.1.LTS'
|
|
96
94
|
}
|
|
@@ -45,7 +45,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
45
45
|
private AlertDialog mProgressDialog;
|
|
46
46
|
private ProgressBar mProgressBar;
|
|
47
47
|
private Boolean mSaveToPhoto = true;
|
|
48
|
-
private int mMaxDuration = 0;
|
|
49
48
|
private int listenerCount = 0;
|
|
50
49
|
|
|
51
50
|
private Promise showEditorPromise;
|
|
@@ -71,9 +70,6 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
71
70
|
if (config.hasKey("saveToPhoto")) {
|
|
72
71
|
this.mSaveToPhoto = config.getBoolean("saveToPhoto");
|
|
73
72
|
}
|
|
74
|
-
if (config.hasKey("maxDuration")) {
|
|
75
|
-
this.mMaxDuration = config.getInt("maxDuration");
|
|
76
|
-
}
|
|
77
73
|
|
|
78
74
|
if (!_isValidVideo(videoPath)) {
|
|
79
75
|
WritableMap map = Arguments.createMap();
|
|
@@ -92,7 +88,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
92
88
|
// here is NOT main thread, we need to create VideoTrimmerView on UI thread, so that later we can update it using same thread
|
|
93
89
|
|
|
94
90
|
runOnUiThread(() -> {
|
|
95
|
-
trimmerView = new VideoTrimmerView(getReactApplicationContext(),
|
|
91
|
+
trimmerView = new VideoTrimmerView(getReactApplicationContext(), config, null);
|
|
96
92
|
trimmerView.setOnTrimVideoListener(this);
|
|
97
93
|
trimmerView.initVideoByURI(Uri.parse(videoPath));
|
|
98
94
|
|
|
@@ -161,11 +157,7 @@ public class VideoTrimModule extends ReactContextBaseJavaModule implements Video
|
|
|
161
157
|
WritableMap map = Arguments.createMap();
|
|
162
158
|
map.putString("outputPath", in);
|
|
163
159
|
sendEvent(getReactApplicationContext(), "onFinishTrimming", map);
|
|
164
|
-
|
|
165
|
-
showEditorPromise.resolve(in);
|
|
166
|
-
} else {
|
|
167
|
-
hideDialog();
|
|
168
|
-
}
|
|
160
|
+
showEditorPromise.resolve(in);
|
|
169
161
|
});
|
|
170
162
|
}
|
|
171
163
|
|
|
@@ -7,13 +7,8 @@ import android.net.Uri;
|
|
|
7
7
|
import android.util.Log;
|
|
8
8
|
|
|
9
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
10
|
import com.arthenica.ffmpegkit.ReturnCode;
|
|
14
11
|
import com.arthenica.ffmpegkit.SessionState;
|
|
15
|
-
import com.arthenica.ffmpegkit.Statistics;
|
|
16
|
-
import com.arthenica.ffmpegkit.StatisticsCallback;
|
|
17
12
|
import com.videotrim.interfaces.VideoTrimListener;
|
|
18
13
|
|
|
19
14
|
import java.text.SimpleDateFormat;
|
|
@@ -47,7 +42,6 @@ public class VideoTrimmerUtil {
|
|
|
47
42
|
outputFile = outputFile + "/" + outputName;
|
|
48
43
|
|
|
49
44
|
String cmd = "-i " + inputFile + " -ss " + startMs + "ms" + " -to " + endMs + "ms -c copy " + outputFile;
|
|
50
|
-
String[] command = cmd.split(" ");
|
|
51
45
|
final String tempOutFile = outputFile;
|
|
52
46
|
|
|
53
47
|
callback.onStartTrim();
|
|
@@ -22,6 +22,7 @@ import android.widget.FrameLayout;
|
|
|
22
22
|
import android.widget.ImageView;
|
|
23
23
|
import android.widget.LinearLayout;
|
|
24
24
|
import android.widget.RelativeLayout;
|
|
25
|
+
import android.widget.TextView;
|
|
25
26
|
import android.widget.Toast;
|
|
26
27
|
|
|
27
28
|
import androidx.appcompat.app.AlertDialog;
|
|
@@ -29,6 +30,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|
|
29
30
|
import androidx.recyclerview.widget.RecyclerView;
|
|
30
31
|
|
|
31
32
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
33
|
+
import com.facebook.react.bridge.ReadableMap;
|
|
32
34
|
import com.videotrim.R;
|
|
33
35
|
import com.videotrim.adapters.VideoTrimmerAdapter;
|
|
34
36
|
import com.videotrim.interfaces.IVideoTrimmerView;
|
|
@@ -82,19 +84,18 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
82
84
|
|
|
83
85
|
|
|
84
86
|
public VideoTrimmerView(ReactApplicationContext context, AttributeSet attrs) {
|
|
85
|
-
this(context, attrs, 0);
|
|
87
|
+
this(context, attrs, 0, null);
|
|
86
88
|
}
|
|
87
|
-
public VideoTrimmerView(ReactApplicationContext context,
|
|
88
|
-
this(context, attrs, 0);
|
|
89
|
-
this.mMaxDuration = maxDuration;
|
|
89
|
+
public VideoTrimmerView(ReactApplicationContext context, ReadableMap config, AttributeSet attrs) {
|
|
90
|
+
this(context, attrs, 0, config);
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
public VideoTrimmerView(ReactApplicationContext context, AttributeSet attrs, int defStyleAttr) {
|
|
93
|
+
public VideoTrimmerView(ReactApplicationContext context, AttributeSet attrs, int defStyleAttr, ReadableMap config) {
|
|
93
94
|
super(context, attrs, defStyleAttr);
|
|
94
|
-
init(context);
|
|
95
|
+
init(context, config);
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
private void init(ReactApplicationContext context) {
|
|
98
|
+
private void init(ReactApplicationContext context, ReadableMap config) {
|
|
98
99
|
this.mContext = context;
|
|
99
100
|
|
|
100
101
|
// listen to onConfigurationChanged doesn't work for this, it runs too soon
|
|
@@ -112,6 +113,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
112
113
|
mVideoThumbRecyclerView.setAdapter(mVideoThumbAdapter);
|
|
113
114
|
mVideoThumbRecyclerView.addOnScrollListener(mOnScrollListener);
|
|
114
115
|
|
|
116
|
+
configure(config);
|
|
115
117
|
setUpListeners();
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -485,4 +487,20 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
485
487
|
|
|
486
488
|
return screenWidth;
|
|
487
489
|
}
|
|
490
|
+
|
|
491
|
+
private void configure(ReadableMap config) {
|
|
492
|
+
if (config.hasKey("maxDuration")) {
|
|
493
|
+
this.mMaxDuration = config.getInt("maxDuration");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (config.hasKey("leftButtonText")) {
|
|
497
|
+
TextView tv = findViewById(R.id.cancelBtn);
|
|
498
|
+
tv.setText(config.getString("leftButtonText"));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (config.hasKey("rightButtonText")) {
|
|
502
|
+
TextView tv = findViewById(R.id.saveBtn);
|
|
503
|
+
tv.setText(config.getString("rightButtonText"));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
488
506
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
package iknow.android.utils;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
|
|
5
|
+
import java.lang.ref.WeakReference;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Author:J.Chou
|
|
9
|
+
* Date: 2016.07.21 11:45.
|
|
10
|
+
* Email: who_know_me@163.com
|
|
11
|
+
* Describe:
|
|
12
|
+
*/
|
|
13
|
+
public class BaseUtils {
|
|
14
|
+
|
|
15
|
+
private static final String ERROR_INIT = "Initialize BaseUtils with invoke init()";
|
|
16
|
+
|
|
17
|
+
private static WeakReference<Context> mWeakReferenceContext;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* init in Application
|
|
21
|
+
*/
|
|
22
|
+
public static void init(Context ctx){
|
|
23
|
+
mWeakReferenceContext = new WeakReference<>(ctx);
|
|
24
|
+
//something to do...
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public static Context getContext() {
|
|
28
|
+
if (mWeakReferenceContext == null) {
|
|
29
|
+
throw new IllegalArgumentException(ERROR_INIT);
|
|
30
|
+
}
|
|
31
|
+
return mWeakReferenceContext.get().getApplicationContext();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Source code recreated from a .class file by IntelliJ IDEA
|
|
3
|
+
// (powered by FernFlower decompiler)
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
package iknow.android.utils;
|
|
7
|
+
|
|
8
|
+
public final class BuildConfig {
|
|
9
|
+
public static final boolean DEBUG = false;
|
|
10
|
+
public static final String APPLICATION_ID = "iknow.android.utils";
|
|
11
|
+
public static final String BUILD_TYPE = "release";
|
|
12
|
+
public static final String FLAVOR = "";
|
|
13
|
+
public static final int VERSION_CODE = 1;
|
|
14
|
+
public static final String VERSION_NAME = "1.0";
|
|
15
|
+
|
|
16
|
+
public BuildConfig() {
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package iknow.android.utils;
|
|
2
|
+
|
|
3
|
+
import android.text.TextUtils;
|
|
4
|
+
|
|
5
|
+
import java.text.SimpleDateFormat;
|
|
6
|
+
import java.util.Date;
|
|
7
|
+
import java.util.Locale;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Author:J.Chou
|
|
11
|
+
* Date: 2016.07.21 11:43.
|
|
12
|
+
* Email: who_know_me@163.com
|
|
13
|
+
* Describe:
|
|
14
|
+
*/
|
|
15
|
+
public final class DateUtil {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* second to HH:MM:ss
|
|
19
|
+
* @param seconds
|
|
20
|
+
* @return
|
|
21
|
+
*/
|
|
22
|
+
public static String convertSecondsToTime(long seconds) {
|
|
23
|
+
String timeStr = null;
|
|
24
|
+
int hour = 0;
|
|
25
|
+
int minute = 0;
|
|
26
|
+
int second = 0;
|
|
27
|
+
if (seconds <= 0)
|
|
28
|
+
return "00:00";
|
|
29
|
+
else {
|
|
30
|
+
minute = (int)seconds / 60;
|
|
31
|
+
if (minute < 60) {
|
|
32
|
+
second = (int)seconds % 60;
|
|
33
|
+
timeStr = unitFormat(minute) + ":" + unitFormat(second);
|
|
34
|
+
} else {
|
|
35
|
+
hour = minute / 60;
|
|
36
|
+
if (hour > 99)
|
|
37
|
+
return "99:59:59";
|
|
38
|
+
minute = minute % 60;
|
|
39
|
+
second = (int)(seconds - hour * 3600 - minute * 60);
|
|
40
|
+
timeStr = unitFormat(hour) + ":" + unitFormat(minute) + ":" + unitFormat(second);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return timeStr;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static String convertSecondsToFormat(long seconds,String format){
|
|
47
|
+
|
|
48
|
+
if(TextUtils.isEmpty(format))
|
|
49
|
+
return "";
|
|
50
|
+
|
|
51
|
+
Date date = new Date(seconds);
|
|
52
|
+
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
|
|
53
|
+
return sdf.format(date);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private static String unitFormat(int i) {
|
|
57
|
+
String retStr = null;
|
|
58
|
+
if (i >= 0 && i < 10)
|
|
59
|
+
retStr = "0" + Integer.toString(i);
|
|
60
|
+
else
|
|
61
|
+
retStr = "" + i;
|
|
62
|
+
return retStr;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package iknow.android.utils;
|
|
2
|
+
|
|
3
|
+
import android.app.ActivityManager;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.pm.PackageManager;
|
|
6
|
+
|
|
7
|
+
import java.util.List;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Author:J.Chou
|
|
11
|
+
* Date: 2016.07.19 11:40.
|
|
12
|
+
* Email: who_know_me@163.com
|
|
13
|
+
* Describe:
|
|
14
|
+
*/
|
|
15
|
+
public final class DeviceUtil {
|
|
16
|
+
|
|
17
|
+
public static int getDeviceWidth() {
|
|
18
|
+
return BaseUtils.getContext().getResources().getDisplayMetrics().widthPixels;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static int getDeviceHeight(){
|
|
22
|
+
return BaseUtils.getContext().getResources().getDisplayMetrics().heightPixels;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static boolean hasAppInstalled(String pkgName) {
|
|
26
|
+
try {
|
|
27
|
+
BaseUtils.getContext().getPackageManager().getPackageInfo(pkgName, PackageManager.PERMISSION_GRANTED);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (PackageManager.NameNotFoundException e) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public static boolean isAppRunInBackground() {
|
|
35
|
+
ActivityManager activityManager = (ActivityManager) BaseUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE);
|
|
36
|
+
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
|
|
37
|
+
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
|
|
38
|
+
if (appProcess.processName.equals(BaseUtils.getContext().getPackageName())) {
|
|
39
|
+
// return true -> Run in background
|
|
40
|
+
// return false - > Run in foreground
|
|
41
|
+
return appProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package iknow.android.utils;
|
|
2
|
+
|
|
3
|
+
import android.util.DisplayMetrics;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Created by choujason on 7/31/16.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
public class UnitConverter {
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
public static DisplayMetrics getDisplayMetrics(){
|
|
13
|
+
|
|
14
|
+
return BaseUtils.getContext().getResources().getDisplayMetrics();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static float dpToPx(float dp) {
|
|
18
|
+
return dp * getDisplayMetrics().density;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public static int dpToPx(int dp) {
|
|
22
|
+
return (int) (dp * getDisplayMetrics().density + 0.5f);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public static float pxToDp(float px) {
|
|
26
|
+
return px / getDisplayMetrics().density;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public static int pxToDp(int px) {
|
|
30
|
+
return (int) (px / getDisplayMetrics().density + 0.5f);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public static float spToPx(float sp) {
|
|
34
|
+
return sp * getDisplayMetrics().scaledDensity;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static int spToPx(int sp) {
|
|
38
|
+
return (int) (sp * getDisplayMetrics().scaledDensity + 0.5f);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public static float pxToSp(float px) {
|
|
42
|
+
return px / getDisplayMetrics().scaledDensity;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public static int pxToSp(int px) {
|
|
46
|
+
return (int) (px / getDisplayMetrics().scaledDensity + 0.5f);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2010-2016 eBusiness Information, Excilys Group
|
|
3
|
+
* <p>
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
5
|
+
* use this file except in compliance with the License. You may obtain a copy of
|
|
6
|
+
* the License at
|
|
7
|
+
* <p>
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
* <p>
|
|
10
|
+
* Unless required by applicable law or agreed To in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
12
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
13
|
+
* License for the specific language governing permissions and limitations under
|
|
14
|
+
* the License.
|
|
15
|
+
*/
|
|
16
|
+
package iknow.android.utils.thread;
|
|
17
|
+
|
|
18
|
+
import android.util.Log;
|
|
19
|
+
|
|
20
|
+
import java.util.ArrayList;
|
|
21
|
+
import java.util.List;
|
|
22
|
+
import java.util.concurrent.Executor;
|
|
23
|
+
import java.util.concurrent.ExecutorService;
|
|
24
|
+
import java.util.concurrent.Executors;
|
|
25
|
+
import java.util.concurrent.Future;
|
|
26
|
+
import java.util.concurrent.ScheduledExecutorService;
|
|
27
|
+
import java.util.concurrent.TimeUnit;
|
|
28
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
29
|
+
|
|
30
|
+
public final class BackgroundExecutor {
|
|
31
|
+
|
|
32
|
+
private static final String TAG = "BackgroundExecutor";
|
|
33
|
+
|
|
34
|
+
public static final Executor DEFAULT_EXECUTOR = Executors.newScheduledThreadPool(2 * Runtime.getRuntime().availableProcessors());
|
|
35
|
+
private static Executor executor = DEFAULT_EXECUTOR;
|
|
36
|
+
private static final List<Task> TASKS = new ArrayList<>();
|
|
37
|
+
private static final ThreadLocal<String> CURRENT_SERIAL = new ThreadLocal<>();
|
|
38
|
+
|
|
39
|
+
private BackgroundExecutor() {
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Execute a runnable after the given delay.
|
|
44
|
+
*
|
|
45
|
+
* @param runnable the task to execute
|
|
46
|
+
* @param delay the time from now to delay execution, in milliseconds
|
|
47
|
+
* <p>
|
|
48
|
+
* if <code>delay</code> is strictly positive and the current
|
|
49
|
+
* executor does not support scheduling (if
|
|
50
|
+
* Executor has been called with such an
|
|
51
|
+
* executor)
|
|
52
|
+
* @return Future associated to the running task
|
|
53
|
+
* @throws IllegalArgumentException if the current executor set by Executor
|
|
54
|
+
* does not support scheduling
|
|
55
|
+
*/
|
|
56
|
+
private static Future<?> directExecute(Runnable runnable, long delay) {
|
|
57
|
+
Future<?> future = null;
|
|
58
|
+
if (delay > 0) {
|
|
59
|
+
/* no serial, but a delay: schedule the task */
|
|
60
|
+
if (!(executor instanceof ScheduledExecutorService)) {
|
|
61
|
+
throw new IllegalArgumentException("The executor set does not support scheduling");
|
|
62
|
+
}
|
|
63
|
+
ScheduledExecutorService scheduledExecutorService = (ScheduledExecutorService) executor;
|
|
64
|
+
future = scheduledExecutorService.schedule(runnable, delay, TimeUnit.MILLISECONDS);
|
|
65
|
+
} else {
|
|
66
|
+
if (executor instanceof ExecutorService) {
|
|
67
|
+
ExecutorService executorService = (ExecutorService) executor;
|
|
68
|
+
future = executorService.submit(runnable);
|
|
69
|
+
} else {
|
|
70
|
+
/* non-cancellable task */
|
|
71
|
+
executor.execute(runnable);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return future;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Execute a task after (at least) its delay <strong>and</strong> after all
|
|
79
|
+
* tasks added with the same non-null <code>serial</code> (if any) have
|
|
80
|
+
* completed execution.
|
|
81
|
+
*
|
|
82
|
+
* @param task the task to execute
|
|
83
|
+
* @throws IllegalArgumentException if <code>task.delay</code> is strictly positive and the
|
|
84
|
+
* current executor does not support scheduling (if
|
|
85
|
+
* Executor has been called with such an
|
|
86
|
+
* executor)
|
|
87
|
+
*/
|
|
88
|
+
public static synchronized void execute(Task task) {
|
|
89
|
+
Future<?> future = null;
|
|
90
|
+
if (task.serial == null || !hasSerialRunning(task.serial)) {
|
|
91
|
+
task.executionAsked = true;
|
|
92
|
+
future = directExecute(task, task.remainingDelay);
|
|
93
|
+
}
|
|
94
|
+
if ((task.id != null || task.serial != null) && !task.managed.get()) {
|
|
95
|
+
/* keep task */
|
|
96
|
+
task.future = future;
|
|
97
|
+
TASKS.add(task);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Indicates whether a task with the specified <code>serial</code> has been
|
|
103
|
+
* submitted to the executor.
|
|
104
|
+
*
|
|
105
|
+
* @param serial the serial queue
|
|
106
|
+
* @return <code>true</code> if such a task has been submitted,
|
|
107
|
+
* <code>false</code> otherwise
|
|
108
|
+
*/
|
|
109
|
+
private static boolean hasSerialRunning(String serial) {
|
|
110
|
+
for (Task task : TASKS) {
|
|
111
|
+
if (task.executionAsked && serial.equals(task.serial)) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Retrieve and remove the first task having the specified
|
|
120
|
+
* <code>serial</code> (if any).
|
|
121
|
+
*
|
|
122
|
+
* @param serial the serial queue
|
|
123
|
+
* @return task if found, <code>null</code> otherwise
|
|
124
|
+
*/
|
|
125
|
+
private static Task take(String serial) {
|
|
126
|
+
int len = TASKS.size();
|
|
127
|
+
for (int i = 0; i < len; i++) {
|
|
128
|
+
if (serial.equals(TASKS.get(i).serial)) {
|
|
129
|
+
return TASKS.remove(i);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cancel all tasks having the specified <code>id</code>.
|
|
137
|
+
*
|
|
138
|
+
* @param id the cancellation identifier
|
|
139
|
+
* @param mayInterruptIfRunning <code>true</code> if the thread executing this task should be
|
|
140
|
+
* interrupted; otherwise, in-progress tasks are allowed to
|
|
141
|
+
* complete
|
|
142
|
+
*/
|
|
143
|
+
public static synchronized void cancelAll(String id, boolean mayInterruptIfRunning) {
|
|
144
|
+
for (int i = TASKS.size() - 1; i >= 0; i--) {
|
|
145
|
+
Task task = TASKS.get(i);
|
|
146
|
+
if (id.equals(task.id)) {
|
|
147
|
+
if (task.future != null) {
|
|
148
|
+
task.future.cancel(mayInterruptIfRunning);
|
|
149
|
+
if (!task.managed.getAndSet(true)) {
|
|
150
|
+
/*
|
|
151
|
+
* the task has been submitted to the executor, but its
|
|
152
|
+
* execution has not started yet, so that its run()
|
|
153
|
+
* method will never call postExecute()
|
|
154
|
+
*/
|
|
155
|
+
task.postExecute();
|
|
156
|
+
}
|
|
157
|
+
} else if (task.executionAsked) {
|
|
158
|
+
Log.w(TAG, "A task with id " + task.id + " cannot be cancelled (the executor set does not support it)");
|
|
159
|
+
} else {
|
|
160
|
+
/* this task has not been submitted to the executor */
|
|
161
|
+
TASKS.remove(i);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public static abstract class Task implements Runnable {
|
|
168
|
+
|
|
169
|
+
private String id;
|
|
170
|
+
private long remainingDelay;
|
|
171
|
+
private long targetTimeMillis; /* since epoch */
|
|
172
|
+
private String serial;
|
|
173
|
+
private boolean executionAsked;
|
|
174
|
+
private Future<?> future;
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
* A task can be cancelled after it has been submitted to the executor
|
|
178
|
+
* but before its run() method is called. In that case, run() will never
|
|
179
|
+
* be called, hence neither will postExecute(): the tasks with the same
|
|
180
|
+
* serial identifier (if any) will never be submitted.
|
|
181
|
+
*
|
|
182
|
+
* Therefore, cancelAll() *must* call postExecute() if run() is not
|
|
183
|
+
* started.
|
|
184
|
+
*
|
|
185
|
+
* This flag guarantees that either cancelAll() or run() manages this
|
|
186
|
+
* task post execution, but not both.
|
|
187
|
+
*/
|
|
188
|
+
private AtomicBoolean managed = new AtomicBoolean();
|
|
189
|
+
|
|
190
|
+
public Task(String id, long delay, String serial) {
|
|
191
|
+
if (!"".equals(id)) {
|
|
192
|
+
this.id = id;
|
|
193
|
+
}
|
|
194
|
+
if (delay > 0) {
|
|
195
|
+
remainingDelay = delay;
|
|
196
|
+
targetTimeMillis = System.currentTimeMillis() + delay;
|
|
197
|
+
}
|
|
198
|
+
if (!"".equals(serial)) {
|
|
199
|
+
this.serial = serial;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@Override
|
|
204
|
+
public void run() {
|
|
205
|
+
if (managed.getAndSet(true)) {
|
|
206
|
+
/* cancelled and postExecute() already called */
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
CURRENT_SERIAL.set(serial);
|
|
212
|
+
execute();
|
|
213
|
+
} finally {
|
|
214
|
+
/* handle next tasks */
|
|
215
|
+
postExecute();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public abstract void execute();
|
|
220
|
+
|
|
221
|
+
private void postExecute() {
|
|
222
|
+
if (id == null && serial == null) {
|
|
223
|
+
/* nothing to do */
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
CURRENT_SERIAL.set(null);
|
|
227
|
+
synchronized (BackgroundExecutor.class) {
|
|
228
|
+
/* execution complete */
|
|
229
|
+
TASKS.remove(this);
|
|
230
|
+
|
|
231
|
+
if (serial != null) {
|
|
232
|
+
Task next = take(serial);
|
|
233
|
+
if (next != null) {
|
|
234
|
+
if (next.remainingDelay != 0) {
|
|
235
|
+
/* the delay may not have elapsed yet */
|
|
236
|
+
next.remainingDelay = Math.max(0L, targetTimeMillis - System.currentTimeMillis());
|
|
237
|
+
}
|
|
238
|
+
/* a task having the same serial was queued, execute it */
|
|
239
|
+
BackgroundExecutor.execute(next);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2010-2016 eBusiness Information, Excilys Group
|
|
3
|
+
* <p>
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
5
|
+
* use this file except in compliance with the License. You may obtain a copy of
|
|
6
|
+
* the License at
|
|
7
|
+
* <p>
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
* <p>
|
|
10
|
+
* Unless required by applicable law or agreed To in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
12
|
+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
13
|
+
* License for the specific language governing permissions and limitations under
|
|
14
|
+
* the License.
|
|
15
|
+
*/
|
|
16
|
+
package iknow.android.utils.thread;
|
|
17
|
+
|
|
18
|
+
import android.os.Handler;
|
|
19
|
+
import android.os.Looper;
|
|
20
|
+
import android.os.Message;
|
|
21
|
+
import android.os.SystemClock;
|
|
22
|
+
|
|
23
|
+
import java.util.HashMap;
|
|
24
|
+
import java.util.Map;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* This class provide operations for
|
|
28
|
+
* UiThread tasks.
|
|
29
|
+
*/
|
|
30
|
+
public final class UiThreadExecutor {
|
|
31
|
+
|
|
32
|
+
private static final Handler HANDLER = new Handler(Looper.getMainLooper()) {
|
|
33
|
+
@Override
|
|
34
|
+
public void handleMessage(Message msg) {
|
|
35
|
+
Runnable callback = msg.getCallback();
|
|
36
|
+
if (callback != null) {
|
|
37
|
+
callback.run();
|
|
38
|
+
decrementToken((Token) msg.obj);
|
|
39
|
+
} else {
|
|
40
|
+
super.handleMessage(msg);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
private static final Map<String, Token> TOKENS = new HashMap<>();
|
|
46
|
+
|
|
47
|
+
private UiThreadExecutor() {
|
|
48
|
+
// should not be instantiated
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Store a new task in the map for providing cancellation. This method is
|
|
53
|
+
* used by AndroidAnnotations and not intended to be called by clients.
|
|
54
|
+
*
|
|
55
|
+
* @param id the identifier of the task
|
|
56
|
+
* @param task the task itself
|
|
57
|
+
* @param delay the delay or zero to run immediately
|
|
58
|
+
*/
|
|
59
|
+
public static void runTask(String id, Runnable task, long delay) {
|
|
60
|
+
if ("".equals(id)) {
|
|
61
|
+
HANDLER.postDelayed(task, delay);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
long time = SystemClock.uptimeMillis() + delay;
|
|
65
|
+
HANDLER.postAtTime(task, nextToken(id), time);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private static Token nextToken(String id) {
|
|
69
|
+
synchronized (TOKENS) {
|
|
70
|
+
Token token = TOKENS.get(id);
|
|
71
|
+
if (token == null) {
|
|
72
|
+
token = new Token(id);
|
|
73
|
+
TOKENS.put(id, token);
|
|
74
|
+
}
|
|
75
|
+
token.runnablesCount++;
|
|
76
|
+
return token;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private static void decrementToken(Token token) {
|
|
81
|
+
synchronized (TOKENS) {
|
|
82
|
+
if (--token.runnablesCount == 0) {
|
|
83
|
+
String id = token.id;
|
|
84
|
+
Token old = TOKENS.remove(id);
|
|
85
|
+
if (old != token) {
|
|
86
|
+
// a runnable finished after cancelling, we just removed a
|
|
87
|
+
// wrong token, lets put it back
|
|
88
|
+
TOKENS.put(id, old);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Cancel all tasks having the specified <code>id</code>.
|
|
96
|
+
*
|
|
97
|
+
* @param id the cancellation identifier
|
|
98
|
+
*/
|
|
99
|
+
public static void cancelAll(String id) {
|
|
100
|
+
Token token;
|
|
101
|
+
synchronized (TOKENS) {
|
|
102
|
+
token = TOKENS.remove(id);
|
|
103
|
+
}
|
|
104
|
+
if (token == null) {
|
|
105
|
+
// nothing to cancel
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
HANDLER.removeCallbacksAndMessages(token);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private static final class Token {
|
|
112
|
+
int runnablesCount = 0;
|
|
113
|
+
final String id;
|
|
114
|
+
|
|
115
|
+
private Token(String id) {
|
|
116
|
+
this.id = id;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
}
|
package/ios/VideoTrim.swift
CHANGED
|
@@ -65,6 +65,23 @@ class VideoTrim: RCTEventEmitter, UIVideoEditorControllerDelegate, UINavigationC
|
|
|
65
65
|
root.present(editController, animated: true, completion: {
|
|
66
66
|
self.emitEventToJS("onShow", eventData: nil)
|
|
67
67
|
self.isShowing = true
|
|
68
|
+
|
|
69
|
+
if let topItem = editController.navigationBar.topItem{
|
|
70
|
+
if let title = config["title"] as? String, !title.isEmpty {
|
|
71
|
+
topItem.title = title
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// when it comes to bar button customization
|
|
75
|
+
// we can't customize text of original buttons here, we can only set attrs like enabled/hidden
|
|
76
|
+
// to customize text we need to create new button
|
|
77
|
+
if let leftBtnText = config["leftButtonText"] as? String, !leftBtnText.isEmpty {
|
|
78
|
+
topItem.leftBarButtonItem = UIBarButtonItem(title: leftBtnText, style: topItem.leftBarButtonItem?.style ?? .plain, target: topItem.leftBarButtonItem?.target, action: topItem.leftBarButtonItem?.action)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if let rightBtnText = config["rightButtonText"] as? String, !rightBtnText.isEmpty {
|
|
82
|
+
topItem.rightBarButtonItem = UIBarButtonItem(title: rightBtnText, style: topItem.rightBarButtonItem?.style ?? .plain, target: topItem.rightBarButtonItem?.target, action: topItem.rightBarButtonItem?.action)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
68
85
|
})
|
|
69
86
|
}
|
|
70
87
|
}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -18,30 +18,36 @@ const VideoTrim = _reactNative.NativeModules.VideoTrim ? _reactNative.NativeModu
|
|
|
18
18
|
async function showEditor(videoPath) {
|
|
19
19
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
20
20
|
const {
|
|
21
|
-
maxDuration,
|
|
22
21
|
saveToPhoto = true
|
|
23
22
|
} = config;
|
|
24
|
-
const outputPath = await VideoTrim.showEditor(videoPath,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
const outputPath = await VideoTrim.showEditor(videoPath, config);
|
|
24
|
+
if (_reactNative.Platform.OS === 'android') {
|
|
25
|
+
if (saveToPhoto) {
|
|
26
|
+
try {
|
|
27
|
+
if (_reactNative.Platform.Version >= 33) {
|
|
28
|
+
// since android 13 it's not needed to request permission for write storage: https://github.com/facebook/react-native/issues/36714#issuecomment-1491338276
|
|
29
|
+
await VideoTrim.saveVideo(outputPath);
|
|
30
|
+
} else {
|
|
31
|
+
const granted = await _reactNative.PermissionsAndroid.request(_reactNative.PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
|
32
|
+
title: 'Video Trimmer Photos Access Required',
|
|
33
|
+
message: 'Grant access to your Photos to write output Video',
|
|
34
|
+
buttonNeutral: 'Ask Me Later',
|
|
35
|
+
buttonNegative: 'Cancel',
|
|
36
|
+
buttonPositive: 'OK'
|
|
37
|
+
});
|
|
38
|
+
if (granted === _reactNative.PermissionsAndroid.RESULTS.GRANTED) {
|
|
39
|
+
await VideoTrim.saveVideo(outputPath);
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error('Photos Library permission denied');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
throw err;
|
|
46
|
+
} finally {
|
|
40
47
|
VideoTrim.hideDialog();
|
|
41
|
-
throw new Error('Camera permission denied');
|
|
42
48
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
49
|
+
} else {
|
|
50
|
+
VideoTrim.hideDialog();
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
}
|
|
@@ -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","
|
|
1
|
+
{"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","VideoTrim","NativeModules","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","saveToPhoto","outputPath","OS","Version","saveVideo","granted","PermissionsAndroid","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","err","hideDialog","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;AAUE,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,GAAG;EAAK,CAAC,GAAGJ,MAAM;EACrC,MAAMK,UAAU,GAAG,MAAMZ,SAAS,CAACK,UAAU,CAACC,SAAS,EAAEC,MAAM,CAAC;EAEhE,IAAIX,qBAAQ,CAACiB,EAAE,KAAK,SAAS,EAAE;IAC7B,IAAIF,WAAW,EAAE;MACf,IAAI;QACF,IAAIf,qBAAQ,CAACkB,OAAO,IAAI,EAAE,EAAE;UAC1B;UACA,MAAMd,SAAS,CAACe,SAAS,CAACH,UAAU,CAAC;QACvC,CAAC,MAAM;UACL,MAAMI,OAAO,GAAG,MAAMC,+BAAkB,CAACC,OAAO,CAC9CD,+BAAkB,CAACE,WAAW,CAACC,sBAAsB,EACrD;YACEC,KAAK,EAAE,sCAAsC;YAC7CC,OAAO,EAAE,mDAAmD;YAC5DC,aAAa,EAAE,cAAc;YAC7BC,cAAc,EAAE,QAAQ;YACxBC,cAAc,EAAE;UAClB,CACF,CAAC;UACD,IAAIT,OAAO,KAAKC,+BAAkB,CAACS,OAAO,CAACC,OAAO,EAAE;YAClD,MAAM3B,SAAS,CAACe,SAAS,CAACH,UAAU,CAAC;UACvC,CAAC,MAAM;YACL,MAAM,IAAIR,KAAK,CAAC,kCAAkC,CAAC;UACrD;QACF;MACF,CAAC,CAAC,OAAOwB,GAAG,EAAE;QACZ,MAAMA,GAAG;MACX,CAAC,SAAS;QACR5B,SAAS,CAAC6B,UAAU,CAAC,CAAC;MACxB;IACF,CAAC,MAAM;MACL7B,SAAS,CAAC6B,UAAU,CAAC,CAAC;IACxB;EACF;AACF;AAEO,SAASC,YAAYA,CAACxB,SAAiB,EAAoB;EAChE,OAAON,SAAS,CAAC8B,YAAY,CAACxB,SAAS,CAAC;AAC1C"}
|
package/lib/module/index.js
CHANGED
|
@@ -11,30 +11,36 @@ const VideoTrim = NativeModules.VideoTrim ? NativeModules.VideoTrim : new Proxy(
|
|
|
11
11
|
export async function showEditor(videoPath) {
|
|
12
12
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
13
13
|
const {
|
|
14
|
-
maxDuration,
|
|
15
14
|
saveToPhoto = true
|
|
16
15
|
} = config;
|
|
17
|
-
const outputPath = await VideoTrim.showEditor(videoPath,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
16
|
+
const outputPath = await VideoTrim.showEditor(videoPath, config);
|
|
17
|
+
if (Platform.OS === 'android') {
|
|
18
|
+
if (saveToPhoto) {
|
|
19
|
+
try {
|
|
20
|
+
if (Platform.Version >= 33) {
|
|
21
|
+
// since android 13 it's not needed to request permission for write storage: https://github.com/facebook/react-native/issues/36714#issuecomment-1491338276
|
|
22
|
+
await VideoTrim.saveVideo(outputPath);
|
|
23
|
+
} else {
|
|
24
|
+
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
|
|
25
|
+
title: 'Video Trimmer Photos Access Required',
|
|
26
|
+
message: 'Grant access to your Photos to write output Video',
|
|
27
|
+
buttonNeutral: 'Ask Me Later',
|
|
28
|
+
buttonNegative: 'Cancel',
|
|
29
|
+
buttonPositive: 'OK'
|
|
30
|
+
});
|
|
31
|
+
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
32
|
+
await VideoTrim.saveVideo(outputPath);
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error('Photos Library permission denied');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
throw err;
|
|
39
|
+
} finally {
|
|
33
40
|
VideoTrim.hideDialog();
|
|
34
|
-
throw new Error('Camera permission denied');
|
|
35
41
|
}
|
|
36
|
-
}
|
|
37
|
-
|
|
42
|
+
} else {
|
|
43
|
+
VideoTrim.hideDialog();
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
46
|
}
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","PermissionsAndroid","Platform","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","
|
|
1
|
+
{"version":3,"names":["NativeModules","PermissionsAndroid","Platform","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","saveToPhoto","outputPath","OS","Version","saveVideo","granted","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","err","hideDialog","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;AAUL,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,GAAG;EAAK,CAAC,GAAGJ,MAAM;EACrC,MAAMK,UAAU,GAAG,MAAMX,SAAS,CAACI,UAAU,CAACC,SAAS,EAAEC,MAAM,CAAC;EAEhE,IAAIX,QAAQ,CAACiB,EAAE,KAAK,SAAS,EAAE;IAC7B,IAAIF,WAAW,EAAE;MACf,IAAI;QACF,IAAIf,QAAQ,CAACkB,OAAO,IAAI,EAAE,EAAE;UAC1B;UACA,MAAMb,SAAS,CAACc,SAAS,CAACH,UAAU,CAAC;QACvC,CAAC,MAAM;UACL,MAAMI,OAAO,GAAG,MAAMrB,kBAAkB,CAACsB,OAAO,CAC9CtB,kBAAkB,CAACuB,WAAW,CAACC,sBAAsB,EACrD;YACEC,KAAK,EAAE,sCAAsC;YAC7CC,OAAO,EAAE,mDAAmD;YAC5DC,aAAa,EAAE,cAAc;YAC7BC,cAAc,EAAE,QAAQ;YACxBC,cAAc,EAAE;UAClB,CACF,CAAC;UACD,IAAIR,OAAO,KAAKrB,kBAAkB,CAAC8B,OAAO,CAACC,OAAO,EAAE;YAClD,MAAMzB,SAAS,CAACc,SAAS,CAACH,UAAU,CAAC;UACvC,CAAC,MAAM;YACL,MAAM,IAAIR,KAAK,CAAC,kCAAkC,CAAC;UACrD;QACF;MACF,CAAC,CAAC,OAAOuB,GAAG,EAAE;QACZ,MAAMA,GAAG;MACX,CAAC,SAAS;QACR1B,SAAS,CAAC2B,UAAU,CAAC,CAAC;MACxB;IACF,CAAC,MAAM;MACL3B,SAAS,CAAC2B,UAAU,CAAC,CAAC;IACxB;EACF;AACF;AAEA,OAAO,SAASC,YAAYA,CAACvB,SAAiB,EAAoB;EAChE,OAAOL,SAAS,CAAC4B,YAAY,CAACvB,SAAS,CAAC;AAC1C"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export interface EditorConfig {
|
|
2
2
|
saveToPhoto?: boolean;
|
|
3
3
|
maxDuration?: number;
|
|
4
|
+
title?: string;
|
|
5
|
+
leftButtonText?: string;
|
|
6
|
+
rightButtonText?: string;
|
|
4
7
|
}
|
|
5
8
|
export declare function showEditor(videoPath: string, config?: EditorConfig): Promise<void>;
|
|
6
9
|
export declare function isValidVideo(videoPath: string): Promise<boolean>;
|
|
@@ -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;
|
|
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;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,YAAiB,GACxB,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEhE"}
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -20,38 +20,48 @@ const VideoTrim = NativeModules.VideoTrim
|
|
|
20
20
|
export interface EditorConfig {
|
|
21
21
|
saveToPhoto?: boolean;
|
|
22
22
|
maxDuration?: number;
|
|
23
|
+
title?: string;
|
|
24
|
+
leftButtonText?: string;
|
|
25
|
+
rightButtonText?: string;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export async function showEditor(
|
|
26
29
|
videoPath: string,
|
|
27
30
|
config: EditorConfig = {}
|
|
28
31
|
): Promise<void> {
|
|
29
|
-
const {
|
|
30
|
-
const outputPath = await VideoTrim.showEditor(videoPath,
|
|
31
|
-
saveToPhoto,
|
|
32
|
-
maxDuration,
|
|
33
|
-
});
|
|
32
|
+
const { saveToPhoto = true } = config;
|
|
33
|
+
const outputPath = await VideoTrim.showEditor(videoPath, config);
|
|
34
34
|
|
|
35
|
-
if (Platform.OS === 'android'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
if (Platform.OS === 'android') {
|
|
36
|
+
if (saveToPhoto) {
|
|
37
|
+
try {
|
|
38
|
+
if (Platform.Version >= 33) {
|
|
39
|
+
// since android 13 it's not needed to request permission for write storage: https://github.com/facebook/react-native/issues/36714#issuecomment-1491338276
|
|
40
|
+
await VideoTrim.saveVideo(outputPath);
|
|
41
|
+
} else {
|
|
42
|
+
const granted = await PermissionsAndroid.request(
|
|
43
|
+
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE!,
|
|
44
|
+
{
|
|
45
|
+
title: 'Video Trimmer Photos Access Required',
|
|
46
|
+
message: 'Grant access to your Photos to write output Video',
|
|
47
|
+
buttonNeutral: 'Ask Me Later',
|
|
48
|
+
buttonNegative: 'Cancel',
|
|
49
|
+
buttonPositive: 'OK',
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
53
|
+
await VideoTrim.saveVideo(outputPath);
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error('Photos Library permission denied');
|
|
56
|
+
}
|
|
45
57
|
}
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
} else {
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw err;
|
|
60
|
+
} finally {
|
|
50
61
|
VideoTrim.hideDialog();
|
|
51
|
-
throw new Error('Camera permission denied');
|
|
52
62
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
63
|
+
} else {
|
|
64
|
+
VideoTrim.hideDialog();
|
|
55
65
|
}
|
|
56
66
|
}
|
|
57
67
|
}
|