react-native-video-trim 3.0.10 → 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/LICENSE +1 -1
- package/README.md +80 -63
- package/VideoTrim.podspec +24 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +82 -49
- package/android/gradle.properties +7 -5
- package/android/src/main/AndroidManifest.xml +4 -2
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrim.kt +646 -0
- package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrimPackage.kt +22 -0
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/enums/ErrorCode.java +1 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/interfaces/IVideoTrimmerView.java +1 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/interfaces/VideoTrimListener.java +6 -5
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/MediaMetadataUtil.java +1 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/StorageUtil.java +3 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/VideoTrimmerUtil.java +51 -41
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/widgets/VideoTrimmerView.java +45 -69
- package/ios/AssetLoader.swift +2 -2
- package/ios/ErrorCode.swift +2 -2
- package/ios/ProgressAlertController.swift +2 -2
- package/ios/VideoTrim.swift +52 -835
- package/ios/VideoTrimImpl.swift +957 -0
- package/ios/VideoTrimmer.swift +2 -3
- package/ios/VideoTrimmerThumb.swift +33 -26
- package/ios/VideoTrimmerViewController.swift +47 -28
- package/lib/module/VideoTrim.nitro.js +4 -0
- package/lib/module/VideoTrim.nitro.js.map +1 -0
- package/lib/module/index.js +98 -22
- package/lib/module/index.js.map +1 -1
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/{index.d.ts → src/VideoTrim.nitro.d.ts} +125 -134
- package/lib/typescript/src/VideoTrim.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +49 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/nitrogen/generated/android/c++/JEditorConfig.hpp +237 -0
- package/nitrogen/generated/android/c++/JFileValidationResult.hpp +61 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +74 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string_std__unordered_map_std__string__std__string_.hpp +89 -0
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.cpp +151 -0
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +68 -0
- package/nitrogen/generated/android/c++/JTrimOptions.hpp +109 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +72 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/FileValidationResult.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void_std__string_std__unordered_map_std__string__std__string_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/HybridVideoTrimSpec.kt +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/TrimOptions.kt +40 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/videotrimOnLoad.kt +35 -0
- package/nitrogen/generated/android/videotrim+autolinking.cmake +78 -0
- package/nitrogen/generated/android/videotrim+autolinking.gradle +27 -0
- package/nitrogen/generated/android/videotrimOnLoad.cpp +50 -0
- package/nitrogen/generated/android/videotrimOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/VideoTrim+autolinking.rb +60 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.cpp +96 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +374 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +56 -0
- package/nitrogen/generated/ios/VideoTrimAutolinking.mm +33 -0
- package/nitrogen/generated/ios/VideoTrimAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.hpp +127 -0
- package/nitrogen/generated/ios/swift/EditorConfig.swift +541 -0
- package/nitrogen/generated/ios/swift/FileValidationResult.swift +57 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_FileValidationResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string_std__unordered_map_std__string__std__string_.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +241 -0
- package/nitrogen/generated/ios/swift/TrimOptions.swift +189 -0
- package/nitrogen/generated/shared/c++/EditorConfig.hpp +253 -0
- package/nitrogen/generated/shared/c++/FileValidationResult.hpp +77 -0
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +27 -0
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +80 -0
- package/nitrogen/generated/shared/c++/TrimOptions.hpp +125 -0
- package/package.json +75 -71
- package/src/VideoTrim.nitro.ts +263 -0
- package/src/index.tsx +120 -257
- package/android/src/main/AndroidManifestDeprecated.xml +0 -3
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +0 -603
- package/android/src/main/java/com/videotrim/VideoTrimPackage.java +0 -28
- package/ios/VideoTrim-Bridging-Header.h +0 -2
- package/ios/VideoTrim.mm +0 -17
- package/ios/VideoTrim.xcodeproj/project.pbxproj +0 -283
- package/lib/commonjs/index.js +0 -87
- package/lib/commonjs/index.js.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/react-native-video-trim.podspec +0 -43
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
package com.margelo.nitro.videotrim
|
|
2
|
+
|
|
3
|
+
import android.R.attr.progressBarStyleHorizontal
|
|
4
|
+
import android.R.attr.selectableItemBackground
|
|
5
|
+
import android.R.color.holo_red_light
|
|
6
|
+
import android.R.style.Theme_Black_NoTitleBar_Fullscreen
|
|
7
|
+
import android.annotation.SuppressLint
|
|
8
|
+
import android.app.Activity
|
|
9
|
+
import android.content.Context
|
|
10
|
+
import android.content.DialogInterface
|
|
11
|
+
import android.content.Intent
|
|
12
|
+
import android.content.pm.PackageManager
|
|
13
|
+
import android.content.res.ColorStateList
|
|
14
|
+
import android.util.Log
|
|
15
|
+
import android.util.TypedValue
|
|
16
|
+
import android.view.Gravity
|
|
17
|
+
import android.view.View
|
|
18
|
+
import android.view.ViewGroup
|
|
19
|
+
import android.widget.Button
|
|
20
|
+
import android.widget.LinearLayout
|
|
21
|
+
import android.widget.ProgressBar
|
|
22
|
+
import android.widget.TextView
|
|
23
|
+
import androidx.appcompat.app.AlertDialog
|
|
24
|
+
import androidx.core.content.ContextCompat
|
|
25
|
+
import androidx.core.content.FileProvider
|
|
26
|
+
import androidx.core.graphics.toColorInt
|
|
27
|
+
import androidx.core.net.toUri
|
|
28
|
+
import com.arthenica.ffmpegkit.FFmpegKit
|
|
29
|
+
import com.arthenica.ffmpegkit.ReturnCode;
|
|
30
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
31
|
+
import com.facebook.react.bridge.BaseActivityEventListener
|
|
32
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
33
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
34
|
+
import com.margelo.nitro.NitroModules
|
|
35
|
+
import com.margelo.nitro.core.Promise
|
|
36
|
+
import com.margelo.nitro.videotrim.enums.ErrorCode
|
|
37
|
+
import com.margelo.nitro.videotrim.interfaces.VideoTrimListener
|
|
38
|
+
import com.margelo.nitro.videotrim.utils.MediaMetadataUtil
|
|
39
|
+
import com.margelo.nitro.videotrim.utils.StorageUtil
|
|
40
|
+
import com.margelo.nitro.videotrim.widgets.VideoTrimmerView
|
|
41
|
+
import iknow.android.utils.BaseUtils
|
|
42
|
+
import java.io.File
|
|
43
|
+
import java.io.FileInputStream
|
|
44
|
+
import java.io.IOException
|
|
45
|
+
import java.text.SimpleDateFormat
|
|
46
|
+
import java.util.Date
|
|
47
|
+
import java.util.TimeZone
|
|
48
|
+
import kotlin.coroutines.resume
|
|
49
|
+
import kotlin.coroutines.resumeWithException
|
|
50
|
+
import kotlin.coroutines.suspendCoroutine
|
|
51
|
+
|
|
52
|
+
@DoNotStrip
|
|
53
|
+
class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListener {
|
|
54
|
+
private var isInit: Boolean = false
|
|
55
|
+
private var trimmerView: VideoTrimmerView? = null
|
|
56
|
+
private var alertDialog: AlertDialog? = null
|
|
57
|
+
private var mProgressDialog: AlertDialog? = null
|
|
58
|
+
private var cancelTrimmingConfirmDialog: AlertDialog? = null
|
|
59
|
+
private var mProgressBar: ProgressBar? = null
|
|
60
|
+
private var outputFile: String? = null
|
|
61
|
+
private var isVideoType = true
|
|
62
|
+
private var editorConfig: EditorConfig? = null
|
|
63
|
+
private var trimOptions: TrimOptions? = null
|
|
64
|
+
private var onEvent: ((eventName: String, payload: Map<String, String>) -> Unit)? = null
|
|
65
|
+
private var onComplete: (() -> Unit)? = null
|
|
66
|
+
|
|
67
|
+
init {
|
|
68
|
+
val mActivityEventListener = object : BaseActivityEventListener() {
|
|
69
|
+
override fun onActivityResult(
|
|
70
|
+
activity: Activity,
|
|
71
|
+
requestCode: Int,
|
|
72
|
+
resultCode: Int,
|
|
73
|
+
intent: Intent?
|
|
74
|
+
) {
|
|
75
|
+
if (requestCode == REQUEST_CODE_SAVE_FILE && resultCode == Activity.RESULT_OK) {
|
|
76
|
+
|
|
77
|
+
val uri = intent?.data ?: return
|
|
78
|
+
try {
|
|
79
|
+
NitroModules.applicationContext?.contentResolver?.openOutputStream(uri)
|
|
80
|
+
?.use { outputStream ->
|
|
81
|
+
FileInputStream(outputFile).use { fileInputStream ->
|
|
82
|
+
val buffer = ByteArray(1024)
|
|
83
|
+
var length: Int
|
|
84
|
+
while (fileInputStream.read(buffer).also { length = it } > 0) {
|
|
85
|
+
outputStream.write(buffer, 0, length)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} ?: return
|
|
89
|
+
// File saved successfully
|
|
90
|
+
Log.d(TAG, "File saved successfully to $uri")
|
|
91
|
+
if (editorConfig?.removeAfterSavedToDocuments == true || trimOptions?.removeAfterFailedToSaveDocuments == true) {
|
|
92
|
+
StorageUtil.deleteFile(outputFile)
|
|
93
|
+
}
|
|
94
|
+
} catch (e: Exception) {
|
|
95
|
+
e.printStackTrace()
|
|
96
|
+
// Handle the error
|
|
97
|
+
onError(
|
|
98
|
+
"Failed to save edited video to Documents: ${e.localizedMessage}",
|
|
99
|
+
ErrorCode.FAIL_TO_SAVE_TO_DOCUMENTS
|
|
100
|
+
)
|
|
101
|
+
if (editorConfig?.removeAfterFailedToSaveDocuments == true || trimOptions?.removeAfterFailedToSaveDocuments == true) {
|
|
102
|
+
StorageUtil.deleteFile(outputFile)
|
|
103
|
+
}
|
|
104
|
+
} finally {
|
|
105
|
+
hideDialog(true)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
NitroModules.applicationContext?.addActivityEventListener(mActivityEventListener)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
override fun showEditor(
|
|
114
|
+
filePath: String,
|
|
115
|
+
config: EditorConfig,
|
|
116
|
+
onEvent: (eventName: String, payload: Map<String, String>) -> Unit
|
|
117
|
+
) {
|
|
118
|
+
if (trimmerView != null || alertDialog != null) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.editorConfig = config
|
|
123
|
+
this.onEvent = onEvent
|
|
124
|
+
this.isVideoType = config.type == "video"
|
|
125
|
+
|
|
126
|
+
val activity = NitroModules.applicationContext?.currentActivity
|
|
127
|
+
if (!isInit) {
|
|
128
|
+
init()
|
|
129
|
+
isInit = true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// here is NOT main thread, we need to create VideoTrimmerView on UI thread, so that later we can update it using same thread
|
|
133
|
+
UiThreadUtil.runOnUiThread {
|
|
134
|
+
trimmerView = VideoTrimmerView(NitroModules.applicationContext, editorConfig, null)
|
|
135
|
+
trimmerView?.setOnTrimVideoListener(this)
|
|
136
|
+
trimmerView?.initByURI(filePath.toUri())
|
|
137
|
+
|
|
138
|
+
val builder = AlertDialog.Builder(
|
|
139
|
+
activity!!, Theme_Black_NoTitleBar_Fullscreen
|
|
140
|
+
)
|
|
141
|
+
builder.setCancelable(false)
|
|
142
|
+
alertDialog = builder.create()
|
|
143
|
+
alertDialog?.setView(trimmerView)
|
|
144
|
+
alertDialog?.show()
|
|
145
|
+
|
|
146
|
+
// 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)
|
|
147
|
+
alertDialog!!.setOnDismissListener {
|
|
148
|
+
// This is called in same thread as the trimmer view -> UI thread
|
|
149
|
+
if (trimmerView != null) {
|
|
150
|
+
trimmerView!!.onDestroy()
|
|
151
|
+
trimmerView = null
|
|
152
|
+
}
|
|
153
|
+
hideDialog(true)
|
|
154
|
+
sendEvent("onHide", mapOf());
|
|
155
|
+
}
|
|
156
|
+
sendEvent("onShow", mapOf());
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun init() {
|
|
161
|
+
isInit = true
|
|
162
|
+
// we have to init this before create videoTrimmerView
|
|
163
|
+
BaseUtils.init(NitroModules.applicationContext)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
override fun onHostResume() {
|
|
167
|
+
Log.d(TAG, "onHostResume: ")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
override fun onHostPause() {
|
|
171
|
+
Log.d(TAG, "onHostPause: ")
|
|
172
|
+
if (trimmerView != null) {
|
|
173
|
+
trimmerView!!.onMediaPause()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
override fun onHostDestroy() {
|
|
178
|
+
hideDialog(true)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
override fun onLoad(duration: Int) {
|
|
182
|
+
sendEvent("onLoad", mapOf("duration" to duration.toString()))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
override fun onTrimmingProgress(percentage: Int) {
|
|
186
|
+
// prevent onTrimmingProgress is called after onFinishTrim (some rare cases)
|
|
187
|
+
if (mProgressBar == null) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
mProgressBar!!.setProgress(percentage, true)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
override fun onFinishTrim(out: String, startTime: Long, endTime: Long, duration: Int) {
|
|
196
|
+
// save output file to use in other places
|
|
197
|
+
outputFile = out
|
|
198
|
+
|
|
199
|
+
sendEvent(
|
|
200
|
+
"onFinishTrimming", mapOf(
|
|
201
|
+
"outputPath" to (outputFile ?: ""),
|
|
202
|
+
"duration" to duration.toString(),
|
|
203
|
+
"startTime" to startTime.toString(),
|
|
204
|
+
"endTime" to endTime.toString()
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if (editorConfig!!.saveToPhoto && isVideoType) {
|
|
209
|
+
try {
|
|
210
|
+
StorageUtil.saveVideoToGallery(NitroModules.applicationContext, outputFile)
|
|
211
|
+
Log.d(TAG, "Edited video saved to Photo Library successfully.")
|
|
212
|
+
if (editorConfig!!.removeAfterSavedToPhoto) {
|
|
213
|
+
StorageUtil.deleteFile(outputFile)
|
|
214
|
+
}
|
|
215
|
+
} catch (e: IOException) {
|
|
216
|
+
e.printStackTrace()
|
|
217
|
+
onError(
|
|
218
|
+
"Failed to save edited video to Photo Library: " + e.localizedMessage,
|
|
219
|
+
ErrorCode.FAIL_TO_SAVE_TO_PHOTO
|
|
220
|
+
)
|
|
221
|
+
if (editorConfig!!.removeAfterFailedToSavePhoto) {
|
|
222
|
+
StorageUtil.deleteFile(outputFile)
|
|
223
|
+
}
|
|
224
|
+
} finally {
|
|
225
|
+
hideDialog(editorConfig!!.closeWhenFinish)
|
|
226
|
+
}
|
|
227
|
+
} else if (editorConfig!!.openDocumentsOnFinish) {
|
|
228
|
+
saveFileToExternalStorage(File(outputFile!!))
|
|
229
|
+
} else if (editorConfig!!.openShareSheetOnFinish) {
|
|
230
|
+
hideDialog(editorConfig!!.closeWhenFinish)
|
|
231
|
+
shareFile(NitroModules.applicationContext!!, File(outputFile!!))
|
|
232
|
+
} else {
|
|
233
|
+
hideDialog(editorConfig!!.closeWhenFinish)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
override fun onCancelTrim() {
|
|
238
|
+
sendEvent("onCancelTrimming", mapOf())
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
override fun onError(errorMessage: String?, errorCode: ErrorCode) {
|
|
242
|
+
sendEvent(
|
|
243
|
+
"onError", mapOf(
|
|
244
|
+
"message" to errorMessage.toString(),
|
|
245
|
+
"errorCode" to errorCode.name
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
override fun onCancel() {
|
|
251
|
+
if (!editorConfig!!.enableCancelDialog) {
|
|
252
|
+
sendEvent("onCancel", mapOf())
|
|
253
|
+
hideDialog(true)
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
val builder = AlertDialog.Builder(
|
|
258
|
+
NitroModules.applicationContext?.currentActivity!!
|
|
259
|
+
)
|
|
260
|
+
builder.setMessage(editorConfig!!.cancelDialogMessage)
|
|
261
|
+
builder.setTitle(editorConfig!!.cancelDialogTitle)
|
|
262
|
+
builder.setCancelable(false)
|
|
263
|
+
builder.setPositiveButton(editorConfig!!.cancelDialogConfirmText) { dialog: DialogInterface, which: Int ->
|
|
264
|
+
dialog.cancel()
|
|
265
|
+
sendEvent("onCancel", mapOf())
|
|
266
|
+
hideDialog(true)
|
|
267
|
+
}
|
|
268
|
+
builder.setNegativeButton(
|
|
269
|
+
editorConfig!!.cancelDialogCancelText
|
|
270
|
+
) { dialog: DialogInterface, which: Int ->
|
|
271
|
+
dialog.cancel()
|
|
272
|
+
}
|
|
273
|
+
val alertDialog = builder.create()
|
|
274
|
+
alertDialog.show()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
override fun onSave() {
|
|
278
|
+
if (!editorConfig!!.enableSaveDialog) {
|
|
279
|
+
startTrim()
|
|
280
|
+
return
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
val builder = AlertDialog.Builder(
|
|
284
|
+
NitroModules.applicationContext?.currentActivity!!
|
|
285
|
+
)
|
|
286
|
+
builder.setMessage(editorConfig!!.saveDialogMessage)
|
|
287
|
+
builder.setTitle(editorConfig!!.saveDialogTitle)
|
|
288
|
+
builder.setCancelable(false)
|
|
289
|
+
builder.setPositiveButton(editorConfig!!.saveDialogConfirmText) { dialog: DialogInterface, which: Int ->
|
|
290
|
+
dialog.cancel()
|
|
291
|
+
startTrim()
|
|
292
|
+
}
|
|
293
|
+
builder.setNegativeButton(
|
|
294
|
+
editorConfig!!.saveDialogCancelText
|
|
295
|
+
) { dialog: DialogInterface, _: Int ->
|
|
296
|
+
dialog.cancel()
|
|
297
|
+
}
|
|
298
|
+
val alertDialog = builder.create()
|
|
299
|
+
alertDialog.show()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
override fun onLog(log: Map<String, String>) {
|
|
303
|
+
sendEvent("onLog", log)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
override fun onStatistics(statistics: Map<String, String>) {
|
|
307
|
+
sendEvent("onStatistics", statistics)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private fun startTrim() {
|
|
311
|
+
val activity = NitroModules.applicationContext?.currentActivity
|
|
312
|
+
// Create the parent layout for the dialog
|
|
313
|
+
val layout = LinearLayout(activity)
|
|
314
|
+
layout.layoutParams = ViewGroup.LayoutParams(
|
|
315
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
316
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
317
|
+
)
|
|
318
|
+
layout.orientation = LinearLayout.VERTICAL
|
|
319
|
+
layout.gravity = Gravity.CENTER_HORIZONTAL
|
|
320
|
+
layout.setPadding(16, 32, 16, 32)
|
|
321
|
+
|
|
322
|
+
// Create and add the TextView
|
|
323
|
+
val textView = TextView(activity)
|
|
324
|
+
textView.layoutParams = ViewGroup.LayoutParams(
|
|
325
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
326
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
327
|
+
)
|
|
328
|
+
textView.text = editorConfig!!.trimmingText
|
|
329
|
+
textView.gravity = Gravity.CENTER
|
|
330
|
+
textView.textSize = 18f
|
|
331
|
+
layout.addView(textView)
|
|
332
|
+
|
|
333
|
+
// Create and add the ProgressBar
|
|
334
|
+
mProgressBar = ProgressBar(activity, null, progressBarStyleHorizontal)
|
|
335
|
+
mProgressBar!!.layoutParams = ViewGroup.LayoutParams(
|
|
336
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
337
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
338
|
+
)
|
|
339
|
+
mProgressBar!!.progressTintList = ColorStateList.valueOf("#2196F3".toColorInt())
|
|
340
|
+
layout.addView(mProgressBar)
|
|
341
|
+
|
|
342
|
+
// Create button
|
|
343
|
+
if (editorConfig!!.enableCancelTrimming) {
|
|
344
|
+
val button = Button(activity)
|
|
345
|
+
button.layoutParams = ViewGroup.LayoutParams(
|
|
346
|
+
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
347
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
348
|
+
)
|
|
349
|
+
// Set the text and style it like a text button
|
|
350
|
+
button.text = editorConfig!!.cancelTrimmingButtonText
|
|
351
|
+
button.setTextColor(
|
|
352
|
+
ContextCompat.getColor(
|
|
353
|
+
activity!!,
|
|
354
|
+
holo_red_light
|
|
355
|
+
)
|
|
356
|
+
) // or use your custom color
|
|
357
|
+
|
|
358
|
+
// Apply ripple effect while keeping the button background transparent
|
|
359
|
+
val outValue = TypedValue()
|
|
360
|
+
activity.theme.resolveAttribute(selectableItemBackground, outValue, true)
|
|
361
|
+
button.setBackgroundResource(outValue.resourceId)
|
|
362
|
+
button.setOnClickListener { v: View? ->
|
|
363
|
+
if (editorConfig!!.enableCancelTrimmingDialog) {
|
|
364
|
+
val builder = AlertDialog.Builder(
|
|
365
|
+
activity
|
|
366
|
+
)
|
|
367
|
+
builder.setMessage(editorConfig!!.cancelTrimmingDialogMessage)
|
|
368
|
+
builder.setTitle(editorConfig!!.cancelTrimmingDialogTitle)
|
|
369
|
+
builder.setCancelable(false)
|
|
370
|
+
builder.setPositiveButton(editorConfig!!.cancelTrimmingDialogConfirmText) { dialog: DialogInterface?, which: Int ->
|
|
371
|
+
if (trimmerView != null) {
|
|
372
|
+
trimmerView!!.onCancelTrimClicked()
|
|
373
|
+
}
|
|
374
|
+
if (mProgressDialog != null && mProgressDialog!!.isShowing) {
|
|
375
|
+
mProgressDialog!!.dismiss()
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
builder.setNegativeButton(
|
|
379
|
+
editorConfig!!.cancelTrimmingDialogCancelText
|
|
380
|
+
) { dialog: DialogInterface, which: Int ->
|
|
381
|
+
dialog.cancel()
|
|
382
|
+
}
|
|
383
|
+
cancelTrimmingConfirmDialog = builder.create()
|
|
384
|
+
cancelTrimmingConfirmDialog!!.show()
|
|
385
|
+
} else {
|
|
386
|
+
if (trimmerView != null) {
|
|
387
|
+
trimmerView!!.onCancelTrimClicked()
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (mProgressDialog != null && mProgressDialog!!.isShowing) {
|
|
391
|
+
mProgressDialog!!.dismiss()
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
layout.addView(button)
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Create the AlertDialog
|
|
399
|
+
val builder = AlertDialog.Builder(
|
|
400
|
+
activity!!
|
|
401
|
+
)
|
|
402
|
+
builder.setCancelable(false)
|
|
403
|
+
builder.setView(layout)
|
|
404
|
+
|
|
405
|
+
// Show the dialog
|
|
406
|
+
mProgressDialog = builder.create()
|
|
407
|
+
|
|
408
|
+
mProgressDialog!!.setOnShowListener {
|
|
409
|
+
sendEvent("onStartTrimming", mapOf())
|
|
410
|
+
if (trimmerView != null) {
|
|
411
|
+
trimmerView!!.onSaveClicked()
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
mProgressDialog!!.show()
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private fun hideDialog(shouldCloseEditor: Boolean) {
|
|
419
|
+
// handle the case when the cancel dialog is still showing but the trimming is finished
|
|
420
|
+
if (cancelTrimmingConfirmDialog != null) {
|
|
421
|
+
if (cancelTrimmingConfirmDialog!!.isShowing) {
|
|
422
|
+
cancelTrimmingConfirmDialog!!.dismiss()
|
|
423
|
+
}
|
|
424
|
+
cancelTrimmingConfirmDialog = null
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (mProgressDialog != null) {
|
|
428
|
+
if (mProgressDialog!!.isShowing) mProgressDialog!!.dismiss()
|
|
429
|
+
mProgressBar = null
|
|
430
|
+
mProgressDialog = null
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (shouldCloseEditor) {
|
|
434
|
+
if (alertDialog != null) {
|
|
435
|
+
if (alertDialog!!.isShowing) {
|
|
436
|
+
alertDialog!!.dismiss()
|
|
437
|
+
}
|
|
438
|
+
alertDialog = null
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private fun sendEvent(
|
|
444
|
+
eventName: String,
|
|
445
|
+
params: Map<String, String>
|
|
446
|
+
) {
|
|
447
|
+
onEvent?.let { it(eventName, params) }
|
|
448
|
+
|
|
449
|
+
if (eventName == "onHide" && onComplete != null) {
|
|
450
|
+
onComplete?.let { it() }
|
|
451
|
+
onComplete = null // Clear the callback after invoking it
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
override fun listFiles(): Promise<Array<String>> {
|
|
456
|
+
return Promise.async {
|
|
457
|
+
StorageUtil.listFiles(NitroModules.applicationContext)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
override fun cleanFiles(): Promise<Double> {
|
|
462
|
+
return Promise.async {
|
|
463
|
+
val files = StorageUtil.listFiles(NitroModules.applicationContext)
|
|
464
|
+
var successCount = 0
|
|
465
|
+
for (file in files) {
|
|
466
|
+
val state = StorageUtil.deleteFile(file)
|
|
467
|
+
if (state) {
|
|
468
|
+
successCount++
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
successCount.toDouble()
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
override fun deleteFile(filePath: String): Promise<Boolean> {
|
|
477
|
+
return Promise.async {
|
|
478
|
+
StorageUtil.deleteFile(filePath)
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
override fun closeEditor(onComplete: () -> Unit) {
|
|
483
|
+
this.onComplete = onComplete
|
|
484
|
+
hideDialog(true)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
override fun isValidFile(url: String): Promise<FileValidationResult> {
|
|
488
|
+
return Promise.async {
|
|
489
|
+
// Use a suspending function to handle the callback
|
|
490
|
+
suspend fun getValidationResult(): FileValidationResult = suspendCoroutine { continuation ->
|
|
491
|
+
MediaMetadataUtil.checkFileValidity(url) { isValid: Boolean, fileType: String, duration: Long ->
|
|
492
|
+
if (isValid) {
|
|
493
|
+
Log.d(TAG, "Valid $fileType file with duration: $duration milliseconds")
|
|
494
|
+
} else {
|
|
495
|
+
Log.d(TAG, "Invalid file")
|
|
496
|
+
}
|
|
497
|
+
// Create a FileValidationResult object
|
|
498
|
+
val result = FileValidationResult(
|
|
499
|
+
isValid = isValid,
|
|
500
|
+
fileType = fileType,
|
|
501
|
+
duration = duration.toDouble() // Convert Long to Double
|
|
502
|
+
)
|
|
503
|
+
continuation.resume(result)
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Resolve the promise with the FileValidationResult
|
|
507
|
+
getValidationResult()
|
|
508
|
+
}
|
|
509
|
+
}
|
|
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
|
+
|
|
611
|
+
private fun saveFileToExternalStorage(file: File) {
|
|
612
|
+
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
613
|
+
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
|
614
|
+
intent.setType("*/*") // Change MIME type as needed
|
|
615
|
+
intent.putExtra(Intent.EXTRA_TITLE, file.name)
|
|
616
|
+
NitroModules.applicationContext?.currentActivity!!
|
|
617
|
+
.startActivityForResult(intent, REQUEST_CODE_SAVE_FILE)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
private fun shareFile(context: Context, file: File) {
|
|
621
|
+
val fileUri = FileProvider.getUriForFile(context, context.packageName + ".provider", file)
|
|
622
|
+
|
|
623
|
+
val shareIntent = Intent(Intent.ACTION_SEND)
|
|
624
|
+
shareIntent.setType("*/*")
|
|
625
|
+
shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri)
|
|
626
|
+
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
627
|
+
|
|
628
|
+
// Grant permissions to all applications that can handle the intent
|
|
629
|
+
for (resolveInfo in context.packageManager.queryIntentActivities(
|
|
630
|
+
shareIntent,
|
|
631
|
+
PackageManager.MATCH_DEFAULT_ONLY
|
|
632
|
+
)) {
|
|
633
|
+
val packageName = resolveInfo.activityInfo.packageName
|
|
634
|
+
context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// directly use context.startActivity(shareIntent) will cause crash
|
|
638
|
+
NitroModules.applicationContext?.currentActivity!!
|
|
639
|
+
.startActivity(Intent.createChooser(shareIntent, "Share file"))
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
companion object {
|
|
643
|
+
const val TAG = "VideoTrimModule"
|
|
644
|
+
const val REQUEST_CODE_SAVE_FILE = 1
|
|
645
|
+
}
|
|
646
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.margelo.nitro.videotrim
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
|
|
8
|
+
class VideoTrimPackage : TurboReactPackage() {
|
|
9
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
14
|
+
return ReactModuleInfoProvider { HashMap() }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
init {
|
|
19
|
+
System.loadLibrary("videotrim")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
package com.videotrim.interfaces;
|
|
1
|
+
package com.margelo.nitro.videotrim.interfaces;
|
|
2
2
|
|
|
3
|
-
import com.
|
|
4
|
-
|
|
3
|
+
import com.margelo.nitro.videotrim.enums.ErrorCode;
|
|
4
|
+
|
|
5
|
+
import java.util.Map;
|
|
5
6
|
|
|
6
7
|
public interface VideoTrimListener {
|
|
7
8
|
void onLoad(int duration);
|
|
@@ -11,6 +12,6 @@ public interface VideoTrimListener {
|
|
|
11
12
|
void onError(String errorMessage, ErrorCode errorCode);
|
|
12
13
|
void onCancel();
|
|
13
14
|
void onSave();
|
|
14
|
-
void onLog(
|
|
15
|
-
void onStatistics(
|
|
15
|
+
void onLog(Map<String, String> log);
|
|
16
|
+
void onStatistics(Map<String, String> statistics);
|
|
16
17
|
}
|