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.
Files changed (92) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +80 -63
  3. package/VideoTrim.podspec +24 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +82 -49
  6. package/android/gradle.properties +7 -5
  7. package/android/src/main/AndroidManifest.xml +4 -2
  8. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  9. package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrim.kt +646 -0
  10. package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrimPackage.kt +22 -0
  11. package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/enums/ErrorCode.java +1 -1
  12. package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/interfaces/IVideoTrimmerView.java +1 -1
  13. package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/interfaces/VideoTrimListener.java +6 -5
  14. package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/MediaMetadataUtil.java +1 -1
  15. package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/StorageUtil.java +3 -1
  16. package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/VideoTrimmerUtil.java +51 -41
  17. package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/widgets/VideoTrimmerView.java +45 -69
  18. package/ios/AssetLoader.swift +2 -2
  19. package/ios/ErrorCode.swift +2 -2
  20. package/ios/ProgressAlertController.swift +2 -2
  21. package/ios/VideoTrim.swift +52 -835
  22. package/ios/VideoTrimImpl.swift +957 -0
  23. package/ios/VideoTrimmer.swift +2 -3
  24. package/ios/VideoTrimmerThumb.swift +33 -26
  25. package/ios/VideoTrimmerViewController.swift +47 -28
  26. package/lib/module/VideoTrim.nitro.js +4 -0
  27. package/lib/module/VideoTrim.nitro.js.map +1 -0
  28. package/lib/module/index.js +98 -22
  29. package/lib/module/index.js.map +1 -1
  30. package/lib/module/package.json +1 -0
  31. package/lib/typescript/package.json +1 -0
  32. package/lib/typescript/{index.d.ts → src/VideoTrim.nitro.d.ts} +125 -134
  33. package/lib/typescript/src/VideoTrim.nitro.d.ts.map +1 -0
  34. package/lib/typescript/src/index.d.ts +49 -0
  35. package/lib/typescript/src/index.d.ts.map +1 -0
  36. package/nitrogen/generated/android/c++/JEditorConfig.hpp +237 -0
  37. package/nitrogen/generated/android/c++/JFileValidationResult.hpp +61 -0
  38. package/nitrogen/generated/android/c++/JFunc_void.hpp +74 -0
  39. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__unordered_map_std__string__std__string_.hpp +89 -0
  40. package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.cpp +151 -0
  41. package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +68 -0
  42. package/nitrogen/generated/android/c++/JTrimOptions.hpp +109 -0
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +72 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/FileValidationResult.kt +28 -0
  45. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void.kt +80 -0
  46. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void_std__string_std__unordered_map_std__string__std__string_.kt +80 -0
  47. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/HybridVideoTrimSpec.kt +86 -0
  48. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/TrimOptions.kt +40 -0
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/videotrimOnLoad.kt +35 -0
  50. package/nitrogen/generated/android/videotrim+autolinking.cmake +78 -0
  51. package/nitrogen/generated/android/videotrim+autolinking.gradle +27 -0
  52. package/nitrogen/generated/android/videotrimOnLoad.cpp +50 -0
  53. package/nitrogen/generated/android/videotrimOnLoad.hpp +25 -0
  54. package/nitrogen/generated/ios/VideoTrim+autolinking.rb +60 -0
  55. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.cpp +96 -0
  56. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +374 -0
  57. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +56 -0
  58. package/nitrogen/generated/ios/VideoTrimAutolinking.mm +33 -0
  59. package/nitrogen/generated/ios/VideoTrimAutolinking.swift +25 -0
  60. package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.cpp +11 -0
  61. package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.hpp +127 -0
  62. package/nitrogen/generated/ios/swift/EditorConfig.swift +541 -0
  63. package/nitrogen/generated/ios/swift/FileValidationResult.swift +57 -0
  64. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  65. package/nitrogen/generated/ios/swift/Func_void_FileValidationResult.swift +46 -0
  66. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  67. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  68. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  69. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  70. package/nitrogen/generated/ios/swift/Func_void_std__string_std__unordered_map_std__string__std__string_.swift +54 -0
  71. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +46 -0
  72. package/nitrogen/generated/ios/swift/HybridVideoTrimSpec.swift +54 -0
  73. package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +241 -0
  74. package/nitrogen/generated/ios/swift/TrimOptions.swift +189 -0
  75. package/nitrogen/generated/shared/c++/EditorConfig.hpp +253 -0
  76. package/nitrogen/generated/shared/c++/FileValidationResult.hpp +77 -0
  77. package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +27 -0
  78. package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +80 -0
  79. package/nitrogen/generated/shared/c++/TrimOptions.hpp +125 -0
  80. package/package.json +75 -71
  81. package/src/VideoTrim.nitro.ts +263 -0
  82. package/src/index.tsx +120 -257
  83. package/android/src/main/AndroidManifestDeprecated.xml +0 -3
  84. package/android/src/main/java/com/videotrim/VideoTrimModule.java +0 -603
  85. package/android/src/main/java/com/videotrim/VideoTrimPackage.java +0 -28
  86. package/ios/VideoTrim-Bridging-Header.h +0 -2
  87. package/ios/VideoTrim.mm +0 -17
  88. package/ios/VideoTrim.xcodeproj/project.pbxproj +0 -283
  89. package/lib/commonjs/index.js +0 -87
  90. package/lib/commonjs/index.js.map +0 -1
  91. package/lib/typescript/index.d.ts.map +0 -1
  92. 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,4 +1,4 @@
1
- package com.videotrim.enums;
1
+ package com.margelo.nitro.videotrim.enums;
2
2
 
3
3
  public enum ErrorCode {
4
4
  TRIMMING_FAILED,
@@ -1,4 +1,4 @@
1
- package com.videotrim.interfaces;
1
+ package com.margelo.nitro.videotrim.interfaces;
2
2
 
3
3
  public interface IVideoTrimmerView {
4
4
  void onDestroy();
@@ -1,7 +1,8 @@
1
- package com.videotrim.interfaces;
1
+ package com.margelo.nitro.videotrim.interfaces;
2
2
 
3
- import com.facebook.react.bridge.WritableMap;
4
- import com.videotrim.enums.ErrorCode;
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(WritableMap log);
15
- void onStatistics(WritableMap statistics);
15
+ void onLog(Map<String, String> log);
16
+ void onStatistics(Map<String, String> statistics);
16
17
  }
@@ -1,4 +1,4 @@
1
- package com.videotrim.utils;
1
+ package com.margelo.nitro.videotrim.utils;
2
2
 
3
3
  import android.media.MediaMetadataRetriever;
4
4
  import android.util.Log;