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