react-native-video-trim 4.0.0 → 4.1.0
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 +36 -8
- package/VideoTrim.podspec +1 -1
- package/android/build.gradle +9 -1
- package/android/gradle.properties +2 -0
- package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrim.kt +138 -121
- package/android/src/main/java/com/margelo/nitro/videotrim/utils/VideoTrimmerUtil.java +29 -21
- package/android/src/main/java/com/margelo/nitro/videotrim/widgets/VideoTrimmerView.java +10 -6
- package/ios/VideoTrim.swift +7 -0
- package/ios/VideoTrimImpl.swift +221 -124
- package/lib/module/index.js +38 -11
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/VideoTrim.nitro.d.ts +74 -57
- package/lib/typescript/src/VideoTrim.nitro.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +9 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JEditorConfig.hpp +54 -46
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.cpp +20 -0
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JTrimOptions.hpp +109 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +14 -12
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/HybridVideoTrimSpec.kt +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/TrimOptions.kt +40 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +43 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +3 -0
- package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.hpp +11 -0
- package/nitrogen/generated/ios/swift/EditorConfig.swift +116 -94
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +19 -0
- package/nitrogen/generated/ios/swift/TrimOptions.swift +189 -0
- package/nitrogen/generated/shared/c++/EditorConfig.hpp +54 -46
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/TrimOptions.hpp +125 -0
- package/package.json +1 -1
- package/src/VideoTrim.nitro.ts +76 -57
- package/src/index.tsx +45 -11
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# Table of contents
|
|
1
2
|
- [Installation](#installation)
|
|
2
3
|
* [For iOS (React Native CLI project)](#for-ios-react-native-cli-project)
|
|
3
4
|
* [For Expo project](#for-expo-project)
|
|
@@ -19,8 +20,6 @@
|
|
|
19
20
|
- [Android: update SDK version](#android-update-sdk-version)
|
|
20
21
|
- [Thanks](#thanks)
|
|
21
22
|
|
|
22
|
-
<!-- TOC end -->
|
|
23
|
-
|
|
24
23
|
# React Native Video Trim
|
|
25
24
|
<div align="center">
|
|
26
25
|
<h2>Video trimmer for your React Native app</h2>
|
|
@@ -31,7 +30,7 @@
|
|
|
31
30
|
|
|
32
31
|
## Features
|
|
33
32
|
- ✅ Support video and audio
|
|
34
|
-
- ✅ Support local files
|
|
33
|
+
- ✅ Support local files and remote files (remote files need `https` version, see below)
|
|
35
34
|
- ✅ Save to Photos, Documents and Share to other apps
|
|
36
35
|
- ✅ Check if file is valid video/audio
|
|
37
36
|
- ✅ File operations: list, clean up, delete specific file
|
|
@@ -71,9 +70,10 @@ npx pod-install ios
|
|
|
71
70
|
You need to run `prebuild` in order for native code takes effect:
|
|
72
71
|
```
|
|
73
72
|
npx expo prebuild
|
|
74
|
-
|
|
75
|
-
npx pod-install ios
|
|
76
73
|
```
|
|
74
|
+
Then you need to restart to make the changes take effect
|
|
75
|
+
|
|
76
|
+
> Note that on iOS you'll need to run on real device, Expo Go may not work because of library linking
|
|
77
77
|
|
|
78
78
|
## Usage
|
|
79
79
|
|
|
@@ -211,6 +211,8 @@ Main method to show Video Editor UI.
|
|
|
211
211
|
- `alertOnFailTitle` (`default = "Error"`)
|
|
212
212
|
- `alertOnFailMessage` (`default = "Fail to load media. Possibly invalid file or no network connection"`)
|
|
213
213
|
- `alertOnFailCloseText` (`default = "Close"`)
|
|
214
|
+
- `enableRotation` (`default = false`)
|
|
215
|
+
- `rotationAngle` (`default = 0`)
|
|
214
216
|
|
|
215
217
|
If `saveToPhoto = true`, you must ensure that you have request permission to write to photo/gallery
|
|
216
218
|
- For Android: you need to have `<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />` in AndroidManifest.xml
|
|
@@ -241,6 +243,10 @@ If you face issue when building Android app related to `file_paths`, then you ma
|
|
|
241
243
|
</paths>
|
|
242
244
|
```
|
|
243
245
|
|
|
246
|
+
# trim(url: string, options: TrimOptions): Promise<string>
|
|
247
|
+
|
|
248
|
+
Directly trim a file without showing editor
|
|
249
|
+
|
|
244
250
|
## isValidFile(videoPath: string)
|
|
245
251
|
|
|
246
252
|
This method is to check if a path is a valid video/audio
|
|
@@ -363,14 +369,36 @@ If there's error while loading media, there'll be a prompt
|
|
|
363
369
|
|
|
364
370
|
Related props: `alertOnFailToLoad, alertOnFailTitle, alertOnFailMessage, alertOnFailCloseText`
|
|
365
371
|
|
|
372
|
+
# Use FFMPEG HTTPS version
|
|
373
|
+
|
|
374
|
+
If you want to trim a remote file, you need to use `https` version (default is `min` which does not support remote file).
|
|
375
|
+
|
|
376
|
+
Do the following:
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
// android/build.gradle
|
|
380
|
+
buildscript {
|
|
381
|
+
ext {
|
|
382
|
+
VideoTrim_ffmpeg_package=https
|
|
383
|
+
|
|
384
|
+
// optional: VideoTrim_ffmpeg_version=6.0.1
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ios
|
|
389
|
+
FFMPEGKIT_PACKAGE=https FFMPEG_KIT_PACKAGE_VERSION=6.0 pod install
|
|
390
|
+
```
|
|
391
|
+
|
|
366
392
|
# Android: update SDK version
|
|
367
393
|
You can override sdk version to use any version in your `android/build.gradle` > `buildscript` > `ext`
|
|
368
394
|
```gradle
|
|
369
395
|
buildscript {
|
|
370
396
|
ext {
|
|
371
|
-
|
|
372
|
-
VideoTrim_minSdkVersion
|
|
373
|
-
VideoTrim_targetSdkVersion
|
|
397
|
+
VideoTrim_kotlinVersion=2.0.21
|
|
398
|
+
VideoTrim_minSdkVersion=24
|
|
399
|
+
VideoTrim_targetSdkVersion=34
|
|
400
|
+
VideoTrim_compileSdkVersion=35
|
|
401
|
+
VideoTrim_ndkVersion=27.1.12297006
|
|
374
402
|
}
|
|
375
403
|
}
|
|
376
404
|
```
|
package/VideoTrim.podspec
CHANGED
|
@@ -15,7 +15,7 @@ Pod::Spec.new do |s|
|
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
17
17
|
|
|
18
|
-
s.dependency
|
|
18
|
+
s.dependency "ffmpeg-mobile-#{ENV['FFMPEGKIT_PACKAGE'] || 'min'}", ENV['FFMPEGKIT_PACKAGE_VERSION'] || '~> 6.0'
|
|
19
19
|
|
|
20
20
|
load 'nitrogen/generated/ios/VideoTrim+autolinking.rb'
|
|
21
21
|
add_nitrogen_files(s)
|
package/android/build.gradle
CHANGED
|
@@ -30,6 +30,14 @@ def getExtOrIntegerDefault(name) {
|
|
|
30
30
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["VideoTrim_" + name]).toInteger()
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
def getPackageNameOrDefault() {
|
|
34
|
+
return rootProject.ext.has("VideoTrim_ffmpeg_package") ? rootProject.ext.get("VideoTrim_ffmpeg_package") : project.properties["VideoTrim_ffmpeg_package"]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def getPackageVersionOrDefault() {
|
|
38
|
+
return rootProject.ext.has("VideoTrim_ffmpeg_version") ? rootProject.ext.get("VideoTrim_ffmpeg_version") : project.properties["VideoTrim_ffmpeg_version"]
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
android {
|
|
34
42
|
namespace "com.margelo.nitro.videotrim"
|
|
35
43
|
|
|
@@ -126,6 +134,6 @@ dependencies {
|
|
|
126
134
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
127
135
|
implementation project(":react-native-nitro-modules")
|
|
128
136
|
|
|
129
|
-
implementation 'io.github.maitrungduc1410:ffmpeg-kit-
|
|
137
|
+
implementation 'io.github.maitrungduc1410:ffmpeg-kit-' + getPackageNameOrDefault() +':' + getPackageVersionOrDefault()
|
|
130
138
|
}
|
|
131
139
|
|
|
@@ -4,6 +4,7 @@ import android.R.attr.progressBarStyleHorizontal
|
|
|
4
4
|
import android.R.attr.selectableItemBackground
|
|
5
5
|
import android.R.color.holo_red_light
|
|
6
6
|
import android.R.style.Theme_Black_NoTitleBar_Fullscreen
|
|
7
|
+
import android.annotation.SuppressLint
|
|
7
8
|
import android.app.Activity
|
|
8
9
|
import android.content.Context
|
|
9
10
|
import android.content.DialogInterface
|
|
@@ -24,6 +25,8 @@ import androidx.core.content.ContextCompat
|
|
|
24
25
|
import androidx.core.content.FileProvider
|
|
25
26
|
import androidx.core.graphics.toColorInt
|
|
26
27
|
import androidx.core.net.toUri
|
|
28
|
+
import com.arthenica.ffmpegkit.FFmpegKit
|
|
29
|
+
import com.arthenica.ffmpegkit.ReturnCode;
|
|
27
30
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
28
31
|
import com.facebook.react.bridge.BaseActivityEventListener
|
|
29
32
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
@@ -39,7 +42,11 @@ import iknow.android.utils.BaseUtils
|
|
|
39
42
|
import java.io.File
|
|
40
43
|
import java.io.FileInputStream
|
|
41
44
|
import java.io.IOException
|
|
45
|
+
import java.text.SimpleDateFormat
|
|
46
|
+
import java.util.Date
|
|
47
|
+
import java.util.TimeZone
|
|
42
48
|
import kotlin.coroutines.resume
|
|
49
|
+
import kotlin.coroutines.resumeWithException
|
|
43
50
|
import kotlin.coroutines.suspendCoroutine
|
|
44
51
|
|
|
45
52
|
@DoNotStrip
|
|
@@ -50,41 +57,10 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
50
57
|
private var mProgressDialog: AlertDialog? = null
|
|
51
58
|
private var cancelTrimmingConfirmDialog: AlertDialog? = null
|
|
52
59
|
private var mProgressBar: ProgressBar? = null
|
|
53
|
-
|
|
54
|
-
// private var enableCancelTrimming = true
|
|
55
|
-
//
|
|
56
|
-
// private var cancelTrimmingButtonText: String? = "Cancel"
|
|
57
|
-
// private var enableCancelTrimmingDialog = true
|
|
58
|
-
// private var cancelTrimmingDialogTitle: String? = "Warning!"
|
|
59
|
-
// private var cancelTrimmingDialogMessage: String? = "Are you sure want to cancel trimming?"
|
|
60
|
-
// private var cancelTrimmingDialogCancelText: String? = "Close"
|
|
61
|
-
// private var cancelTrimmingDialogConfirmText: String? = "Proceed"
|
|
62
|
-
// private var enableCancelDialog = true
|
|
63
|
-
// private var cancelDialogTitle: String? = "Warning!"
|
|
64
|
-
// private var cancelDialogMessage: String? = "Are you sure want to cancel?"
|
|
65
|
-
// private var cancelDialogCancelText: String? = "Close"
|
|
66
|
-
// private var cancelDialogConfirmText: String? = "Proceed"
|
|
67
|
-
// private var enableSaveDialog = true
|
|
68
|
-
// private var saveDialogTitle: String? = "Confirmation!"
|
|
69
|
-
// private var saveDialogMessage: String? = "Are you sure want to save?"
|
|
70
|
-
// private var saveDialogCancelText: String? = "Close"
|
|
71
|
-
// private var saveDialogConfirmText: String? = "Proceed"
|
|
72
|
-
// private var trimmingText: String? = "Trimming video..."
|
|
73
60
|
private var outputFile: String? = null
|
|
74
|
-
// private var saveToPhoto = false
|
|
75
|
-
// private var removeAfterSavedToPhoto = false
|
|
76
|
-
// private var removeAfterFailedToSavePhoto = false
|
|
77
|
-
// private var removeAfterSavedToDocuments = false
|
|
78
|
-
// private var removeAfterFailedToSaveDocuments = false
|
|
79
|
-
|
|
80
|
-
// private boolean removeAfterShared = false; // TODO: on Android there's no way to know if user shared the file or share sheet closed
|
|
81
|
-
// private boolean removeAfterFailedToShare = false; // TODO: implement this
|
|
82
|
-
// private var openDocumentsOnFinish = false
|
|
83
|
-
// private var openShareSheetOnFinish = false
|
|
84
61
|
private var isVideoType = true
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
private lateinit var editorConfig: EditorConfig
|
|
62
|
+
private var editorConfig: EditorConfig? = null
|
|
63
|
+
private var trimOptions: TrimOptions? = null
|
|
88
64
|
private var onEvent: ((eventName: String, payload: Map<String, String>) -> Unit)? = null
|
|
89
65
|
private var onComplete: (() -> Unit)? = null
|
|
90
66
|
|
|
@@ -112,7 +88,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
112
88
|
} ?: return
|
|
113
89
|
// File saved successfully
|
|
114
90
|
Log.d(TAG, "File saved successfully to $uri")
|
|
115
|
-
if (editorConfig
|
|
91
|
+
if (editorConfig?.removeAfterSavedToDocuments == true || trimOptions?.removeAfterFailedToSaveDocuments == true) {
|
|
116
92
|
StorageUtil.deleteFile(outputFile)
|
|
117
93
|
}
|
|
118
94
|
} catch (e: Exception) {
|
|
@@ -122,7 +98,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
122
98
|
"Failed to save edited video to Documents: ${e.localizedMessage}",
|
|
123
99
|
ErrorCode.FAIL_TO_SAVE_TO_DOCUMENTS
|
|
124
100
|
)
|
|
125
|
-
if (editorConfig
|
|
101
|
+
if (editorConfig?.removeAfterFailedToSaveDocuments == true || trimOptions?.removeAfterFailedToSaveDocuments == true) {
|
|
126
102
|
StorageUtil.deleteFile(outputFile)
|
|
127
103
|
}
|
|
128
104
|
} finally {
|
|
@@ -145,68 +121,9 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
145
121
|
|
|
146
122
|
this.editorConfig = config
|
|
147
123
|
this.onEvent = onEvent
|
|
148
|
-
|
|
149
|
-
// !config.hasKey("enableCancelTrimming") || config.getBoolean("enableCancelTrimming")
|
|
150
|
-
//
|
|
151
|
-
// cancelTrimmingButtonText =
|
|
152
|
-
// if (config.hasKey("cancelTrimmingButtonText")) config.getString("cancelTrimmingButtonText") else "Cancel"
|
|
153
|
-
// enableCancelTrimmingDialog =
|
|
154
|
-
// !config.hasKey("enableCancelTrimmingDialog") || config.getBoolean("enableCancelTrimmingDialog")
|
|
155
|
-
// cancelTrimmingDialogTitle =
|
|
156
|
-
// if (config.hasKey("cancelTrimmingDialogTitle")) config.getString("cancelTrimmingDialogTitle") else "Warning!"
|
|
157
|
-
// cancelTrimmingDialogMessage =
|
|
158
|
-
// if (config.hasKey("cancelTrimmingDialogMessage")) config.getString("cancelTrimmingDialogMessage") else "Are you sure want to cancel trimming?"
|
|
159
|
-
// cancelTrimmingDialogCancelText =
|
|
160
|
-
// if (config.hasKey("cancelTrimmingDialogCancelText")) config.getString("cancelTrimmingDialogCancelText") else "Close"
|
|
161
|
-
// cancelTrimmingDialogConfirmText =
|
|
162
|
-
// if (config.hasKey("cancelTrimmingDialogConfirmText")) config.getString("cancelTrimmingDialogConfirmText") else "Proceed"
|
|
163
|
-
//
|
|
164
|
-
// enableCancelDialog =
|
|
165
|
-
// !config.hasKey("enableCancelDialog") || config.getBoolean("enableCancelDialog")
|
|
166
|
-
// cancelDialogTitle =
|
|
167
|
-
// if (config.hasKey("cancelDialogTitle")) config.getString("cancelDialogTitle") else "Warning!"
|
|
168
|
-
// cancelDialogMessage =
|
|
169
|
-
// if (config.hasKey("cancelDialogMessage")) config.getString("cancelDialogMessage") else "Are you sure want to cancel?"
|
|
170
|
-
// cancelDialogCancelText =
|
|
171
|
-
// if (config.hasKey("cancelDialogCancelText")) config.getString("cancelDialogCancelText") else "Close"
|
|
172
|
-
// cancelDialogConfirmText =
|
|
173
|
-
// if (config.hasKey("cancelDialogConfirmText")) config.getString("cancelDialogConfirmText") else "Proceed"
|
|
174
|
-
//
|
|
175
|
-
// enableSaveDialog = !config.hasKey("enableSaveDialog") || config.getBoolean("enableSaveDialog")
|
|
176
|
-
// saveDialogTitle =
|
|
177
|
-
// if (config.hasKey("saveDialogTitle")) config.getString("saveDialogTitle") else "Confirmation!"
|
|
178
|
-
// saveDialogMessage =
|
|
179
|
-
// if (config.hasKey("saveDialogMessage")) config.getString("saveDialogMessage") else "Are you sure want to save?"
|
|
180
|
-
// saveDialogCancelText =
|
|
181
|
-
// if (config.hasKey("saveDialogCancelText")) config.getString("saveDialogCancelText") else "Close"
|
|
182
|
-
// saveDialogConfirmText =
|
|
183
|
-
// if (config.hasKey("saveDialogConfirmText")) config.getString("saveDialogConfirmText") else "Proceed"
|
|
184
|
-
// trimmingText =
|
|
185
|
-
// if (config.hasKey("trimmingText")) config.getString("trimmingText") else "Trimming video..."
|
|
186
|
-
//
|
|
187
|
-
// saveToPhoto = config.hasKey("saveToPhoto") && config.getBoolean("saveToPhoto")
|
|
188
|
-
// removeAfterSavedToPhoto =
|
|
189
|
-
// config.hasKey("removeAfterSavedToPhoto") && config.getBoolean("removeAfterSavedToPhoto")
|
|
190
|
-
// removeAfterFailedToSavePhoto =
|
|
191
|
-
// config.hasKey("removeAfterFailedToSavePhoto") && config.getBoolean("removeAfterFailedToSavePhoto")
|
|
192
|
-
// removeAfterSavedToDocuments =
|
|
193
|
-
// config.hasKey("removeAfterSavedToDocuments") && config.getBoolean("removeAfterSavedToDocuments")
|
|
194
|
-
// removeAfterFailedToSaveDocuments =
|
|
195
|
-
// config.hasKey("removeAfterFailedToSaveDocuments") && config.getBoolean("removeAfterFailedToSaveDocuments")
|
|
196
|
-
// // removeAfterShared = config.hasKey("removeAfterShared") && config.getBoolean("removeAfterShared");
|
|
197
|
-
//// removeAfterFailedToShare = config.hasKey("removeAfterFailedToShare") && config.getBoolean("removeAfterFailedToShare");
|
|
198
|
-
// openDocumentsOnFinish =
|
|
199
|
-
// config.hasKey("openDocumentsOnFinish") && config.getBoolean("openDocumentsOnFinish")
|
|
200
|
-
//
|
|
201
|
-
// openShareSheetOnFinish =
|
|
202
|
-
// config.hasKey("openShareSheetOnFinish") && config.getBoolean("openShareSheetOnFinish")
|
|
203
|
-
|
|
204
|
-
isVideoType = config.type == "video"
|
|
205
|
-
|
|
206
|
-
// closeWhenFinish = !config.hasKey("closeWhenFinish") || config.getBoolean("closeWhenFinish")
|
|
124
|
+
this.isVideoType = config.type == "video"
|
|
207
125
|
|
|
208
126
|
val activity = NitroModules.applicationContext?.currentActivity
|
|
209
|
-
|
|
210
127
|
if (!isInit) {
|
|
211
128
|
init()
|
|
212
129
|
isInit = true
|
|
@@ -288,11 +205,11 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
288
205
|
)
|
|
289
206
|
)
|
|
290
207
|
|
|
291
|
-
if (editorConfig
|
|
208
|
+
if (editorConfig!!.saveToPhoto && isVideoType) {
|
|
292
209
|
try {
|
|
293
210
|
StorageUtil.saveVideoToGallery(NitroModules.applicationContext, outputFile)
|
|
294
211
|
Log.d(TAG, "Edited video saved to Photo Library successfully.")
|
|
295
|
-
if (editorConfig
|
|
212
|
+
if (editorConfig!!.removeAfterSavedToPhoto) {
|
|
296
213
|
StorageUtil.deleteFile(outputFile)
|
|
297
214
|
}
|
|
298
215
|
} catch (e: IOException) {
|
|
@@ -301,19 +218,19 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
301
218
|
"Failed to save edited video to Photo Library: " + e.localizedMessage,
|
|
302
219
|
ErrorCode.FAIL_TO_SAVE_TO_PHOTO
|
|
303
220
|
)
|
|
304
|
-
if (editorConfig
|
|
221
|
+
if (editorConfig!!.removeAfterFailedToSavePhoto) {
|
|
305
222
|
StorageUtil.deleteFile(outputFile)
|
|
306
223
|
}
|
|
307
224
|
} finally {
|
|
308
|
-
hideDialog(editorConfig
|
|
225
|
+
hideDialog(editorConfig!!.closeWhenFinish)
|
|
309
226
|
}
|
|
310
|
-
} else if (editorConfig
|
|
227
|
+
} else if (editorConfig!!.openDocumentsOnFinish) {
|
|
311
228
|
saveFileToExternalStorage(File(outputFile!!))
|
|
312
|
-
} else if (editorConfig
|
|
313
|
-
hideDialog(editorConfig
|
|
229
|
+
} else if (editorConfig!!.openShareSheetOnFinish) {
|
|
230
|
+
hideDialog(editorConfig!!.closeWhenFinish)
|
|
314
231
|
shareFile(NitroModules.applicationContext!!, File(outputFile!!))
|
|
315
232
|
} else {
|
|
316
|
-
hideDialog(editorConfig
|
|
233
|
+
hideDialog(editorConfig!!.closeWhenFinish)
|
|
317
234
|
}
|
|
318
235
|
}
|
|
319
236
|
|
|
@@ -331,7 +248,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
331
248
|
}
|
|
332
249
|
|
|
333
250
|
override fun onCancel() {
|
|
334
|
-
if (!editorConfig
|
|
251
|
+
if (!editorConfig!!.enableCancelDialog) {
|
|
335
252
|
sendEvent("onCancel", mapOf())
|
|
336
253
|
hideDialog(true)
|
|
337
254
|
return
|
|
@@ -340,16 +257,16 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
340
257
|
val builder = AlertDialog.Builder(
|
|
341
258
|
NitroModules.applicationContext?.currentActivity!!
|
|
342
259
|
)
|
|
343
|
-
builder.setMessage(editorConfig
|
|
344
|
-
builder.setTitle(editorConfig
|
|
260
|
+
builder.setMessage(editorConfig!!.cancelDialogMessage)
|
|
261
|
+
builder.setTitle(editorConfig!!.cancelDialogTitle)
|
|
345
262
|
builder.setCancelable(false)
|
|
346
|
-
builder.setPositiveButton(editorConfig
|
|
263
|
+
builder.setPositiveButton(editorConfig!!.cancelDialogConfirmText) { dialog: DialogInterface, which: Int ->
|
|
347
264
|
dialog.cancel()
|
|
348
265
|
sendEvent("onCancel", mapOf())
|
|
349
266
|
hideDialog(true)
|
|
350
267
|
}
|
|
351
268
|
builder.setNegativeButton(
|
|
352
|
-
editorConfig
|
|
269
|
+
editorConfig!!.cancelDialogCancelText
|
|
353
270
|
) { dialog: DialogInterface, which: Int ->
|
|
354
271
|
dialog.cancel()
|
|
355
272
|
}
|
|
@@ -358,7 +275,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
358
275
|
}
|
|
359
276
|
|
|
360
277
|
override fun onSave() {
|
|
361
|
-
if (!editorConfig
|
|
278
|
+
if (!editorConfig!!.enableSaveDialog) {
|
|
362
279
|
startTrim()
|
|
363
280
|
return
|
|
364
281
|
}
|
|
@@ -366,15 +283,15 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
366
283
|
val builder = AlertDialog.Builder(
|
|
367
284
|
NitroModules.applicationContext?.currentActivity!!
|
|
368
285
|
)
|
|
369
|
-
builder.setMessage(editorConfig
|
|
370
|
-
builder.setTitle(editorConfig
|
|
286
|
+
builder.setMessage(editorConfig!!.saveDialogMessage)
|
|
287
|
+
builder.setTitle(editorConfig!!.saveDialogTitle)
|
|
371
288
|
builder.setCancelable(false)
|
|
372
|
-
builder.setPositiveButton(editorConfig
|
|
289
|
+
builder.setPositiveButton(editorConfig!!.saveDialogConfirmText) { dialog: DialogInterface, which: Int ->
|
|
373
290
|
dialog.cancel()
|
|
374
291
|
startTrim()
|
|
375
292
|
}
|
|
376
293
|
builder.setNegativeButton(
|
|
377
|
-
editorConfig
|
|
294
|
+
editorConfig!!.saveDialogCancelText
|
|
378
295
|
) { dialog: DialogInterface, _: Int ->
|
|
379
296
|
dialog.cancel()
|
|
380
297
|
}
|
|
@@ -408,7 +325,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
408
325
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
409
326
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
410
327
|
)
|
|
411
|
-
textView.text = editorConfig
|
|
328
|
+
textView.text = editorConfig!!.trimmingText
|
|
412
329
|
textView.gravity = Gravity.CENTER
|
|
413
330
|
textView.textSize = 18f
|
|
414
331
|
layout.addView(textView)
|
|
@@ -423,14 +340,14 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
423
340
|
layout.addView(mProgressBar)
|
|
424
341
|
|
|
425
342
|
// Create button
|
|
426
|
-
if (editorConfig
|
|
343
|
+
if (editorConfig!!.enableCancelTrimming) {
|
|
427
344
|
val button = Button(activity)
|
|
428
345
|
button.layoutParams = ViewGroup.LayoutParams(
|
|
429
346
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
430
347
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
431
348
|
)
|
|
432
349
|
// Set the text and style it like a text button
|
|
433
|
-
button.text = editorConfig
|
|
350
|
+
button.text = editorConfig!!.cancelTrimmingButtonText
|
|
434
351
|
button.setTextColor(
|
|
435
352
|
ContextCompat.getColor(
|
|
436
353
|
activity!!,
|
|
@@ -443,14 +360,14 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
443
360
|
activity.theme.resolveAttribute(selectableItemBackground, outValue, true)
|
|
444
361
|
button.setBackgroundResource(outValue.resourceId)
|
|
445
362
|
button.setOnClickListener { v: View? ->
|
|
446
|
-
if (editorConfig
|
|
363
|
+
if (editorConfig!!.enableCancelTrimmingDialog) {
|
|
447
364
|
val builder = AlertDialog.Builder(
|
|
448
365
|
activity
|
|
449
366
|
)
|
|
450
|
-
builder.setMessage(editorConfig
|
|
451
|
-
builder.setTitle(editorConfig
|
|
367
|
+
builder.setMessage(editorConfig!!.cancelTrimmingDialogMessage)
|
|
368
|
+
builder.setTitle(editorConfig!!.cancelTrimmingDialogTitle)
|
|
452
369
|
builder.setCancelable(false)
|
|
453
|
-
builder.setPositiveButton(editorConfig
|
|
370
|
+
builder.setPositiveButton(editorConfig!!.cancelTrimmingDialogConfirmText) { dialog: DialogInterface?, which: Int ->
|
|
454
371
|
if (trimmerView != null) {
|
|
455
372
|
trimmerView!!.onCancelTrimClicked()
|
|
456
373
|
}
|
|
@@ -459,7 +376,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
459
376
|
}
|
|
460
377
|
}
|
|
461
378
|
builder.setNegativeButton(
|
|
462
|
-
editorConfig
|
|
379
|
+
editorConfig!!.cancelTrimmingDialogCancelText
|
|
463
380
|
) { dialog: DialogInterface, which: Int ->
|
|
464
381
|
dialog.cancel()
|
|
465
382
|
}
|
|
@@ -591,6 +508,106 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
|
|
|
591
508
|
}
|
|
592
509
|
}
|
|
593
510
|
|
|
511
|
+
override fun trim(url: String, options: TrimOptions): Promise<String> {
|
|
512
|
+
return Promise.async {
|
|
513
|
+
trimOptions = options
|
|
514
|
+
|
|
515
|
+
@SuppressLint("SimpleDateFormat")
|
|
516
|
+
suspend fun getTrimresult(): String = suspendCoroutine { continuation ->
|
|
517
|
+
val currentDate = Date()
|
|
518
|
+
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
|
|
519
|
+
|
|
520
|
+
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
|
521
|
+
val formattedDateTime = dateFormat.format(currentDate)
|
|
522
|
+
|
|
523
|
+
var cmds = arrayOf(
|
|
524
|
+
"-ss",
|
|
525
|
+
"${options.startTime}ms",
|
|
526
|
+
"-to",
|
|
527
|
+
"${options.endTime}ms",
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
if (options.enableRotation) {
|
|
531
|
+
cmds += arrayOf("-display_rotation", "${options.rotationAngle}")
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
outputFile = StorageUtil.getOutputPath(NitroModules.applicationContext, options.outputExt)
|
|
535
|
+
|
|
536
|
+
cmds += arrayOf(
|
|
537
|
+
"-i",
|
|
538
|
+
url,
|
|
539
|
+
"-c",
|
|
540
|
+
"copy",
|
|
541
|
+
"-metadata",
|
|
542
|
+
"creation_time=$formattedDateTime",
|
|
543
|
+
outputFile!!
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
Log.d(TAG, "Command: ${cmds.joinToString(",")}")
|
|
547
|
+
|
|
548
|
+
FFmpegKit.executeWithArgumentsAsync(cmds, { session ->
|
|
549
|
+
val state = session.state
|
|
550
|
+
val returnCode = session.returnCode
|
|
551
|
+
when {
|
|
552
|
+
ReturnCode.isSuccess(returnCode) -> {
|
|
553
|
+
// SUCCESS
|
|
554
|
+
if (options.saveToPhoto && options.type == "video") {
|
|
555
|
+
try {
|
|
556
|
+
StorageUtil.saveVideoToGallery(NitroModules.applicationContext, outputFile)
|
|
557
|
+
Log.d(TAG, "Edited video saved to Photo Library successfully.")
|
|
558
|
+
if (options.removeAfterSavedToPhoto) {
|
|
559
|
+
StorageUtil.deleteFile(outputFile)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
continuation.resume(outputFile!!)
|
|
563
|
+
} catch (e: IOException) {
|
|
564
|
+
e.printStackTrace()
|
|
565
|
+
|
|
566
|
+
if (options.removeAfterFailedToSavePhoto) {
|
|
567
|
+
StorageUtil.deleteFile(outputFile)
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
continuation.resumeWithException(
|
|
571
|
+
Exception("Failed to save edited video to Photo Library: " + e.localizedMessage)
|
|
572
|
+
)
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
if (options.openDocumentsOnFinish) {
|
|
576
|
+
saveFileToExternalStorage(File(outputFile!!))
|
|
577
|
+
} else if (options.openShareSheetOnFinish) {
|
|
578
|
+
shareFile(NitroModules.applicationContext!!, File(outputFile!!))
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
continuation.resume(outputFile!!)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
ReturnCode.isCancel(returnCode) -> {
|
|
585
|
+
// CANCEL
|
|
586
|
+
println("FFmpeg command was cancelled")
|
|
587
|
+
continuation.resumeWithException(
|
|
588
|
+
Exception("FFmpeg command was cancelled")
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
else -> {
|
|
592
|
+
// FAILURE
|
|
593
|
+
val errorMessage = String.format("Command failed with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace());
|
|
594
|
+
println(errorMessage)
|
|
595
|
+
continuation.resumeWithException(
|
|
596
|
+
Exception(errorMessage)
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}, { log ->
|
|
601
|
+
Log.d(TAG, "FFmpeg process started with log ${log.message}")
|
|
602
|
+
}, { statistics ->
|
|
603
|
+
// Handle statistics if needed
|
|
604
|
+
})
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
getTrimresult()
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
594
611
|
private fun saveFileToExternalStorage(file: File) {
|
|
595
612
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
596
613
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
@@ -13,8 +13,10 @@ import com.margelo.nitro.videotrim.enums.ErrorCode;
|
|
|
13
13
|
import com.margelo.nitro.videotrim.interfaces.VideoTrimListener;
|
|
14
14
|
|
|
15
15
|
import java.text.SimpleDateFormat;
|
|
16
|
+
import java.util.ArrayList;
|
|
16
17
|
import java.util.Date;
|
|
17
18
|
import java.util.HashMap;
|
|
19
|
+
import java.util.List;
|
|
18
20
|
import java.util.Map;
|
|
19
21
|
import java.util.TimeZone;
|
|
20
22
|
|
|
@@ -43,7 +45,7 @@ public class VideoTrimmerUtil {
|
|
|
43
45
|
public static final int THUMB_WIDTH = UnitConverter.dpToPx(25); // x2 for better resolution
|
|
44
46
|
private static final int THUMB_RESOLUTION_RES = 2; // double thumb resolution for better quality
|
|
45
47
|
|
|
46
|
-
public static FFmpegSession trim(String inputFile, String outputFile, int videoDuration, long startMs, long endMs, final VideoTrimListener callback) {
|
|
48
|
+
public static FFmpegSession trim(String inputFile, String outputFile, int videoDuration, long startMs, long endMs, boolean enableRotation, double rotationAngle, final VideoTrimListener callback) {
|
|
47
49
|
// Get the current date and time
|
|
48
50
|
Date currentDate = new Date();
|
|
49
51
|
|
|
@@ -55,26 +57,32 @@ public class VideoTrimmerUtil {
|
|
|
55
57
|
// Format the current date and time
|
|
56
58
|
String formattedDateTime = dateFormat.format(currentDate);
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
60
|
+
// create list to store commands
|
|
61
|
+
List<String> cmds = new ArrayList<>();
|
|
62
|
+
cmds.add("-ss");
|
|
63
|
+
cmds.add(startMs + "ms");
|
|
64
|
+
cmds.add("-to");
|
|
65
|
+
cmds.add(endMs + "ms");
|
|
66
|
+
|
|
67
|
+
if (enableRotation) {
|
|
68
|
+
// add "-display_rotation" and rotation angle to the command, contact, not creating new
|
|
69
|
+
cmds.add("-display_rotation");
|
|
70
|
+
cmds.add(String.valueOf(rotationAngle));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
cmds.add("-i");
|
|
74
|
+
cmds.add(inputFile);
|
|
75
|
+
cmds.add("-c");
|
|
76
|
+
cmds.add("copy");
|
|
77
|
+
cmds.add("-metadata");
|
|
78
|
+
cmds.add("creation_time=" + formattedDateTime);
|
|
79
|
+
cmds.add(outputFile);
|
|
80
|
+
|
|
81
|
+
String[] command = cmds.toArray(new String[0]);
|
|
82
|
+
|
|
83
|
+
Log.d(TAG,"Command: " + String.join(",", command));
|
|
84
|
+
|
|
85
|
+
return FFmpegKit.executeWithArgumentsAsync(command, session -> {
|
|
78
86
|
SessionState state = session.getState();
|
|
79
87
|
ReturnCode returnCode = session.getReturnCode();
|
|
80
88
|
if (ReturnCode.isSuccess(session.getReturnCode())) {
|
|
@@ -87,6 +87,9 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
87
87
|
private RelativeLayout trimmerContainerWrapper;
|
|
88
88
|
|
|
89
89
|
private long startTime = 0, endTime = 0;
|
|
90
|
+
private boolean enableRotation = false;
|
|
91
|
+
private double rotationAngle = 0.0;
|
|
92
|
+
|
|
90
93
|
private Vibrator vibrator;
|
|
91
94
|
private boolean didClampWhilePanning = false;
|
|
92
95
|
|
|
@@ -374,6 +377,8 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
374
377
|
mDuration,
|
|
375
378
|
startTime,
|
|
376
379
|
endTime,
|
|
380
|
+
enableRotation,
|
|
381
|
+
rotationAngle,
|
|
377
382
|
mOnTrimVideoListener
|
|
378
383
|
);
|
|
379
384
|
}
|
|
@@ -484,12 +489,11 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
484
489
|
headerView.setVisibility(View.VISIBLE);
|
|
485
490
|
|
|
486
491
|
alertOnFailToLoad = config.getAlertOnFailToLoad();
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
alertOnFailCloseText = config.getAlertOnFailCloseText();
|
|
492
|
+
alertOnFailTitle = config.getAlertOnFailTitle();
|
|
493
|
+
alertOnFailMessage = config.getAlertOnFailMessage();
|
|
494
|
+
alertOnFailCloseText = config.getAlertOnFailCloseText();
|
|
495
|
+
enableRotation = config.getEnableRotation();
|
|
496
|
+
rotationAngle = config.getRotationAngle();
|
|
493
497
|
}
|
|
494
498
|
|
|
495
499
|
private void startTimingRunnable() {
|
package/ios/VideoTrim.swift
CHANGED
|
@@ -57,4 +57,11 @@ class VideoTrim: HybridVideoTrimSpec {
|
|
|
57
57
|
return await self.impl.isValidFile(uri: url)
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
func trim(url: String, options: TrimOptions) throws -> NitroModules.Promise<String> {
|
|
62
|
+
return Promise.async {
|
|
63
|
+
// This runs on a separate Thread, and can use```` `await` syntax!
|
|
64
|
+
return try await self.impl.trim(url: url, options: options)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
60
67
|
}
|