react-native-video-trim 4.1.0 → 5.0.1

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 (99) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +89 -76
  3. package/VideoTrim.podspec +3 -3
  4. package/android/build.gradle +6 -53
  5. package/android/gradle.properties +1 -1
  6. package/android/src/main/AndroidManifest.xml +1 -1
  7. package/android/src/main/java/com/{margelo/nitro/videotrim/VideoTrim.kt → videotrim/VideoTrimModule.kt} +246 -232
  8. package/android/src/main/java/com/videotrim/VideoTrimPackage.kt +33 -0
  9. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/enums/ErrorCode.java +1 -1
  10. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/interfaces/IVideoTrimmerView.java +1 -1
  11. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/interfaces/VideoTrimListener.java +5 -4
  12. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/MediaMetadataUtil.java +1 -1
  13. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/StorageUtil.java +1 -1
  14. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/utils/VideoTrimmerUtil.java +20 -18
  15. package/android/src/main/java/com/{margelo/nitro/videotrim → videotrim}/widgets/VideoTrimmerView.java +44 -45
  16. package/ios/AssetLoader.h +19 -0
  17. package/ios/AssetLoader.mm +87 -0
  18. package/ios/ErrorCode.h +9 -0
  19. package/ios/ProgressAlertController.h +15 -0
  20. package/ios/ProgressAlertController.mm +78 -0
  21. package/ios/VideoTrim.h +31 -0
  22. package/ios/VideoTrim.mm +663 -0
  23. package/ios/VideoTrimmer.h +67 -0
  24. package/ios/VideoTrimmer.mm +863 -0
  25. package/ios/VideoTrimmerThumb.h +23 -0
  26. package/ios/VideoTrimmerThumb.mm +175 -0
  27. package/ios/VideoTrimmerViewController.h +52 -0
  28. package/ios/VideoTrimmerViewController.mm +533 -0
  29. package/lib/module/NativeVideoTrim.js +5 -0
  30. package/lib/module/NativeVideoTrim.js.map +1 -0
  31. package/lib/module/index.js +24 -24
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/typescript/src/NativeVideoTrim.d.ts +107 -0
  34. package/lib/typescript/src/NativeVideoTrim.d.ts.map +1 -0
  35. package/lib/typescript/src/index.d.ts +16 -10
  36. package/lib/typescript/src/index.d.ts.map +1 -1
  37. package/package.json +15 -18
  38. package/src/NativeVideoTrim.ts +113 -0
  39. package/src/index.tsx +29 -31
  40. package/android/CMakeLists.txt +0 -24
  41. package/android/src/main/cpp/cpp-adapter.cpp +0 -6
  42. package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrimPackage.kt +0 -22
  43. package/ios/AssetLoader.swift +0 -99
  44. package/ios/ErrorCode.swift +0 -17
  45. package/ios/ProgressAlertController.swift +0 -100
  46. package/ios/VideoTrim.swift +0 -67
  47. package/ios/VideoTrimImpl.swift +0 -957
  48. package/ios/VideoTrimmer.swift +0 -872
  49. package/ios/VideoTrimmerThumb.swift +0 -175
  50. package/ios/VideoTrimmerViewController.swift +0 -557
  51. package/lib/module/VideoTrim.nitro.js +0 -4
  52. package/lib/module/VideoTrim.nitro.js.map +0 -1
  53. package/lib/typescript/src/VideoTrim.nitro.d.ts +0 -257
  54. package/lib/typescript/src/VideoTrim.nitro.d.ts.map +0 -1
  55. package/nitrogen/generated/android/c++/JEditorConfig.hpp +0 -237
  56. package/nitrogen/generated/android/c++/JFileValidationResult.hpp +0 -61
  57. package/nitrogen/generated/android/c++/JFunc_void.hpp +0 -74
  58. package/nitrogen/generated/android/c++/JFunc_void_std__string_std__unordered_map_std__string__std__string_.hpp +0 -89
  59. package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.cpp +0 -151
  60. package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +0 -68
  61. package/nitrogen/generated/android/c++/JTrimOptions.hpp +0 -109
  62. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +0 -72
  63. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/FileValidationResult.kt +0 -28
  64. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void.kt +0 -80
  65. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void_std__string_std__unordered_map_std__string__std__string_.kt +0 -80
  66. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/HybridVideoTrimSpec.kt +0 -86
  67. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/TrimOptions.kt +0 -40
  68. package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/videotrimOnLoad.kt +0 -35
  69. package/nitrogen/generated/android/videotrim+autolinking.cmake +0 -78
  70. package/nitrogen/generated/android/videotrim+autolinking.gradle +0 -27
  71. package/nitrogen/generated/android/videotrimOnLoad.cpp +0 -50
  72. package/nitrogen/generated/android/videotrimOnLoad.hpp +0 -25
  73. package/nitrogen/generated/ios/VideoTrim+autolinking.rb +0 -60
  74. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.cpp +0 -96
  75. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +0 -374
  76. package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +0 -56
  77. package/nitrogen/generated/ios/VideoTrimAutolinking.mm +0 -33
  78. package/nitrogen/generated/ios/VideoTrimAutolinking.swift +0 -25
  79. package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.cpp +0 -11
  80. package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.hpp +0 -127
  81. package/nitrogen/generated/ios/swift/EditorConfig.swift +0 -541
  82. package/nitrogen/generated/ios/swift/FileValidationResult.swift +0 -57
  83. package/nitrogen/generated/ios/swift/Func_void.swift +0 -46
  84. package/nitrogen/generated/ios/swift/Func_void_FileValidationResult.swift +0 -46
  85. package/nitrogen/generated/ios/swift/Func_void_bool.swift +0 -46
  86. package/nitrogen/generated/ios/swift/Func_void_double.swift +0 -46
  87. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +0 -46
  88. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +0 -46
  89. package/nitrogen/generated/ios/swift/Func_void_std__string_std__unordered_map_std__string__std__string_.swift +0 -54
  90. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +0 -46
  91. package/nitrogen/generated/ios/swift/HybridVideoTrimSpec.swift +0 -54
  92. package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +0 -241
  93. package/nitrogen/generated/ios/swift/TrimOptions.swift +0 -189
  94. package/nitrogen/generated/shared/c++/EditorConfig.hpp +0 -253
  95. package/nitrogen/generated/shared/c++/FileValidationResult.hpp +0 -77
  96. package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +0 -27
  97. package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +0 -80
  98. package/nitrogen/generated/shared/c++/TrimOptions.hpp +0 -125
  99. package/src/VideoTrim.nitro.ts +0 -263
@@ -1,16 +1,16 @@
1
- package com.margelo.nitro.videotrim
1
+ package com.videotrim
2
2
 
3
3
  import android.R.attr.progressBarStyleHorizontal
4
4
  import android.R.attr.selectableItemBackground
5
5
  import android.R.color.holo_red_light
6
6
  import android.R.style.Theme_Black_NoTitleBar_Fullscreen
7
- import android.annotation.SuppressLint
8
7
  import android.app.Activity
9
8
  import android.content.Context
10
9
  import android.content.DialogInterface
11
10
  import android.content.Intent
12
11
  import android.content.pm.PackageManager
13
12
  import android.content.res.ColorStateList
13
+ import android.os.Build
14
14
  import android.util.Log
15
15
  import android.util.TypedValue
16
16
  import android.view.Gravity
@@ -25,19 +25,17 @@ import androidx.core.content.ContextCompat
25
25
  import androidx.core.content.FileProvider
26
26
  import androidx.core.graphics.toColorInt
27
27
  import androidx.core.net.toUri
28
+ import androidx.core.view.ViewCompat
29
+ import androidx.core.view.WindowInsetsCompat
28
30
  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
31
+ import com.arthenica.ffmpegkit.ReturnCode
32
+ import com.facebook.react.bridge.*
33
+ import com.facebook.react.module.annotations.ReactModule
34
+ import com.videotrim.enums.ErrorCode
35
+ import com.videotrim.interfaces.VideoTrimListener
36
+ import com.videotrim.utils.MediaMetadataUtil
37
+ import com.videotrim.utils.StorageUtil
38
+ import com.videotrim.widgets.VideoTrimmerView
41
39
  import iknow.android.utils.BaseUtils
42
40
  import java.io.File
43
41
  import java.io.FileInputStream
@@ -45,12 +43,11 @@ import java.io.IOException
45
43
  import java.text.SimpleDateFormat
46
44
  import java.util.Date
47
45
  import java.util.TimeZone
48
- import kotlin.coroutines.resume
49
- import kotlin.coroutines.resumeWithException
50
- import kotlin.coroutines.suspendCoroutine
51
46
 
52
- @DoNotStrip
53
- class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListener {
47
+ @ReactModule(name = VideoTrimModule.NAME)
48
+ class VideoTrimModule(reactContext: ReactApplicationContext) :
49
+ NativeVideoTrimSpec(reactContext), VideoTrimListener, LifecycleEventListener {
50
+
54
51
  private var isInit: Boolean = false
55
52
  private var trimmerView: VideoTrimmerView? = null
56
53
  private var alertDialog: AlertDialog? = null
@@ -59,10 +56,8 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
59
56
  private var mProgressBar: ProgressBar? = null
60
57
  private var outputFile: String? = null
61
58
  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
59
+ private var editorConfig: ReadableMap? = null
60
+ private var trimOptions: ReadableMap? = null
66
61
 
67
62
  init {
68
63
  val mActivityEventListener = object : BaseActivityEventListener() {
@@ -76,7 +71,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
76
71
 
77
72
  val uri = intent?.data ?: return
78
73
  try {
79
- NitroModules.applicationContext?.contentResolver?.openOutputStream(uri)
74
+ reactApplicationContext.contentResolver?.openOutputStream(uri)
80
75
  ?.use { outputStream ->
81
76
  FileInputStream(outputFile).use { fileInputStream ->
82
77
  val buffer = ByteArray(1024)
@@ -88,9 +83,14 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
88
83
  } ?: return
89
84
  // File saved successfully
90
85
  Log.d(TAG, "File saved successfully to $uri")
91
- if (editorConfig?.removeAfterSavedToDocuments == true || trimOptions?.removeAfterFailedToSaveDocuments == true) {
86
+
87
+ if (
88
+ editorConfig?.getBoolean("removeAfterSavedToDocuments") == true ||
89
+ trimOptions?.getBoolean("removeAfterFailedToSaveDocuments") == true
90
+ ) {
92
91
  StorageUtil.deleteFile(outputFile)
93
92
  }
93
+
94
94
  } catch (e: Exception) {
95
95
  e.printStackTrace()
96
96
  // Handle the error
@@ -98,7 +98,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
98
98
  "Failed to save edited video to Documents: ${e.localizedMessage}",
99
99
  ErrorCode.FAIL_TO_SAVE_TO_DOCUMENTS
100
100
  )
101
- if (editorConfig?.removeAfterFailedToSaveDocuments == true || trimOptions?.removeAfterFailedToSaveDocuments == true) {
101
+ if (editorConfig?.getBoolean("removeAfterFailedToSaveDocuments") == true || trimOptions?.getBoolean("removeAfterFailedToSaveDocuments") == true) {
102
102
  StorageUtil.deleteFile(outputFile)
103
103
  }
104
104
  } finally {
@@ -107,23 +107,22 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
107
107
  }
108
108
  }
109
109
  }
110
- NitroModules.applicationContext?.addActivityEventListener(mActivityEventListener)
110
+ reactApplicationContext.addActivityEventListener(mActivityEventListener)
111
111
  }
112
112
 
113
113
  override fun showEditor(
114
114
  filePath: String,
115
- config: EditorConfig,
116
- onEvent: (eventName: String, payload: Map<String, String>) -> Unit
115
+ config: ReadableMap,
117
116
  ) {
118
117
  if (trimmerView != null || alertDialog != null) {
119
118
  return
120
119
  }
121
120
 
122
121
  this.editorConfig = config
123
- this.onEvent = onEvent
124
- this.isVideoType = config.type == "video"
125
122
 
126
- val activity = NitroModules.applicationContext?.currentActivity
123
+ this.isVideoType = config.hasKey("type") && config.getString("type") == "video"
124
+
125
+ val activity = reactApplicationContext.currentActivity
127
126
  if (!isInit) {
128
127
  init()
129
128
  isInit = true
@@ -131,7 +130,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
131
130
 
132
131
  // here is NOT main thread, we need to create VideoTrimmerView on UI thread, so that later we can update it using same thread
133
132
  UiThreadUtil.runOnUiThread {
134
- trimmerView = VideoTrimmerView(NitroModules.applicationContext, editorConfig, null)
133
+ trimmerView = VideoTrimmerView(reactApplicationContext, editorConfig, null)
135
134
  trimmerView?.setOnTrimVideoListener(this)
136
135
  trimmerView?.initByURI(filePath.toUri())
137
136
 
@@ -141,7 +140,13 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
141
140
  builder.setCancelable(false)
142
141
  alertDialog = builder.create()
143
142
  alertDialog?.setView(trimmerView)
144
- alertDialog?.show()
143
+
144
+ // Apply safe area handling after the dialog is shown
145
+ alertDialog?.setOnShowListener {
146
+ applySafeAreaToDialog(alertDialog!!, trimmerView!!)
147
+
148
+ emitOnShow()
149
+ }
145
150
 
146
151
  // 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
152
  alertDialog!!.setOnDismissListener {
@@ -151,16 +156,44 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
151
156
  trimmerView = null
152
157
  }
153
158
  hideDialog(true)
154
- sendEvent("onHide", mapOf());
159
+ emitOnHide()
155
160
  }
156
- sendEvent("onShow", mapOf());
161
+
162
+ alertDialog?.show()
163
+ }
164
+ }
165
+
166
+ // because trimmerView is rendered within the dialog, we need to apply safe area insets to the dialog
167
+ // else setOnApplyWindowInsetsListener will not fire
168
+ private fun applySafeAreaToDialog(dialog: AlertDialog, trimmerView: VideoTrimmerView) {
169
+ val window = dialog.window
170
+ if (window != null) {
171
+ // Enable edge-to-edge for the dialog window
172
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
173
+ window.setDecorFitsSystemWindows(false)
174
+ }
175
+
176
+ // Get the dialog's decorView and apply insets listener
177
+ val decorView = window.decorView
178
+ ViewCompat.setOnApplyWindowInsetsListener(decorView) { _, windowInsets ->
179
+ val insets = windowInsets.getInsets(
180
+ WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
181
+ )
182
+ Log.d(TAG, "Dialog insets: top=${insets.top}, left=${insets.left}, bottom=${insets.bottom}, right=${insets.right}")
183
+
184
+ // Apply padding to the trimmer view
185
+ trimmerView.setPadding(insets.left, insets.top, insets.right, insets.bottom)
186
+
187
+ WindowInsetsCompat.CONSUMED
188
+ }
189
+ ViewCompat.requestApplyInsets(decorView)
157
190
  }
158
191
  }
159
192
 
160
193
  private fun init() {
161
194
  isInit = true
162
195
  // we have to init this before create videoTrimmerView
163
- BaseUtils.init(NitroModules.applicationContext)
196
+ BaseUtils.init(reactApplicationContext)
164
197
  }
165
198
 
166
199
  override fun onHostResume() {
@@ -178,8 +211,15 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
178
211
  hideDialog(true)
179
212
  }
180
213
 
214
+ override fun invalidate() {
215
+ super.invalidate()
216
+ hideDialog(true)
217
+ }
218
+
181
219
  override fun onLoad(duration: Int) {
182
- sendEvent("onLoad", mapOf("duration" to duration.toString()))
220
+ val map = Arguments.createMap()
221
+ map.putInt("duration", duration)
222
+ emitOnLoad(map)
183
223
  }
184
224
 
185
225
  override fun onTrimmingProgress(percentage: Int) {
@@ -196,20 +236,18 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
196
236
  // save output file to use in other places
197
237
  outputFile = out
198
238
 
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
- )
239
+ val map = Arguments.createMap()
240
+ map.putString("outputPath", outputFile)
241
+ map.putInt("duration", duration)
242
+ map.putDouble("startTime", startTime.toDouble())
243
+ map.putDouble("endTime", endTime.toDouble())
244
+ emitOnFinishTrimming(map)
207
245
 
208
- if (editorConfig!!.saveToPhoto && isVideoType) {
246
+ if (editorConfig?.getBoolean("saveToPhoto") == true && isVideoType) {
209
247
  try {
210
- StorageUtil.saveVideoToGallery(NitroModules.applicationContext, outputFile)
248
+ StorageUtil.saveVideoToGallery(reactApplicationContext, outputFile)
211
249
  Log.d(TAG, "Edited video saved to Photo Library successfully.")
212
- if (editorConfig!!.removeAfterSavedToPhoto) {
250
+ if (editorConfig?.getBoolean("removeAfterSavedToPhoto") == true) {
213
251
  StorageUtil.deleteFile(outputFile)
214
252
  }
215
253
  } catch (e: IOException) {
@@ -218,56 +256,52 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
218
256
  "Failed to save edited video to Photo Library: " + e.localizedMessage,
219
257
  ErrorCode.FAIL_TO_SAVE_TO_PHOTO
220
258
  )
221
- if (editorConfig!!.removeAfterFailedToSavePhoto) {
259
+ if (editorConfig?.getBoolean("removeAfterFailedToSavePhoto") == true) {
222
260
  StorageUtil.deleteFile(outputFile)
223
261
  }
224
262
  } finally {
225
- hideDialog(editorConfig!!.closeWhenFinish)
263
+ hideDialog(editorConfig?.getBoolean("closeWhenFinish") ?: true)
226
264
  }
227
- } else if (editorConfig!!.openDocumentsOnFinish) {
265
+ } else if (editorConfig?.getBoolean("openDocumentsOnFinish") == true) {
228
266
  saveFileToExternalStorage(File(outputFile!!))
229
- } else if (editorConfig!!.openShareSheetOnFinish) {
230
- hideDialog(editorConfig!!.closeWhenFinish)
231
- shareFile(NitroModules.applicationContext!!, File(outputFile!!))
267
+ } else if (editorConfig?.getBoolean("openShareSheetOnFinish") == true) {
268
+ hideDialog(editorConfig?.getBoolean("closeWhenFinish") ?: true)
269
+ shareFile(reactApplicationContext, File(outputFile!!))
232
270
  } else {
233
- hideDialog(editorConfig!!.closeWhenFinish)
271
+ hideDialog(editorConfig?.getBoolean("closeWhenFinish") ?: true)
234
272
  }
235
273
  }
236
274
 
237
275
  override fun onCancelTrim() {
238
- sendEvent("onCancelTrimming", mapOf())
276
+ emitOnCancelTrimming()
239
277
  }
240
278
 
241
279
  override fun onError(errorMessage: String?, errorCode: ErrorCode) {
242
- sendEvent(
243
- "onError", mapOf(
244
- "message" to errorMessage.toString(),
245
- "errorCode" to errorCode.name
246
- )
247
- )
280
+ val map = Arguments.createMap()
281
+ map.putString("message", errorMessage)
282
+ map.putString("errorCode", errorCode.name)
283
+ emitOnError(map)
248
284
  }
249
285
 
250
286
  override fun onCancel() {
251
- if (!editorConfig!!.enableCancelDialog) {
252
- sendEvent("onCancel", mapOf())
287
+ if (!editorConfig?.getBoolean("enableCancelDialog")!!) {
288
+ emitOnCancel()
253
289
  hideDialog(true)
254
290
  return
255
291
  }
256
292
 
257
- val builder = AlertDialog.Builder(
258
- NitroModules.applicationContext?.currentActivity!!
259
- )
260
- builder.setMessage(editorConfig!!.cancelDialogMessage)
261
- builder.setTitle(editorConfig!!.cancelDialogTitle)
293
+ val builder = AlertDialog.Builder(reactApplicationContext.currentActivity!!)
294
+ builder.setMessage(editorConfig?.getString("cancelDialogMessage"))
295
+ builder.setTitle(editorConfig?.getString("cancelDialogTitle"))
262
296
  builder.setCancelable(false)
263
- builder.setPositiveButton(editorConfig!!.cancelDialogConfirmText) { dialog: DialogInterface, which: Int ->
297
+ builder.setPositiveButton(editorConfig?.getString("cancelDialogConfirmText")) { dialog: DialogInterface, _: Int ->
264
298
  dialog.cancel()
265
- sendEvent("onCancel", mapOf())
299
+ emitOnCancel()
266
300
  hideDialog(true)
267
301
  }
268
302
  builder.setNegativeButton(
269
- editorConfig!!.cancelDialogCancelText
270
- ) { dialog: DialogInterface, which: Int ->
303
+ editorConfig?.getString("cancelDialogCancelText") ?: "Cancel"
304
+ ) { dialog: DialogInterface, _: Int ->
271
305
  dialog.cancel()
272
306
  }
273
307
  val alertDialog = builder.create()
@@ -275,23 +309,21 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
275
309
  }
276
310
 
277
311
  override fun onSave() {
278
- if (!editorConfig!!.enableSaveDialog) {
312
+ if (!editorConfig?.getBoolean("enableSaveDialog")!!) {
279
313
  startTrim()
280
314
  return
281
315
  }
282
316
 
283
- val builder = AlertDialog.Builder(
284
- NitroModules.applicationContext?.currentActivity!!
285
- )
286
- builder.setMessage(editorConfig!!.saveDialogMessage)
287
- builder.setTitle(editorConfig!!.saveDialogTitle)
317
+ val builder = AlertDialog.Builder(reactApplicationContext.currentActivity!!)
318
+ builder.setMessage(editorConfig?.getString("saveDialogMessage"))
319
+ builder.setTitle(editorConfig?.getString("saveDialogTitle"))
288
320
  builder.setCancelable(false)
289
- builder.setPositiveButton(editorConfig!!.saveDialogConfirmText) { dialog: DialogInterface, which: Int ->
321
+ builder.setPositiveButton(editorConfig?.getString("saveDialogConfirmText")) { dialog: DialogInterface, _: Int ->
290
322
  dialog.cancel()
291
323
  startTrim()
292
324
  }
293
325
  builder.setNegativeButton(
294
- editorConfig!!.saveDialogCancelText
326
+ editorConfig?.getString("saveDialogCancelText") ?: "Cancel"
295
327
  ) { dialog: DialogInterface, _: Int ->
296
328
  dialog.cancel()
297
329
  }
@@ -299,16 +331,16 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
299
331
  alertDialog.show()
300
332
  }
301
333
 
302
- override fun onLog(log: Map<String, String>) {
303
- sendEvent("onLog", log)
334
+ override fun onLog(log: ReadableMap) {
335
+ emitOnLog( log)
304
336
  }
305
337
 
306
- override fun onStatistics(statistics: Map<String, String>) {
307
- sendEvent("onStatistics", statistics)
338
+ override fun onStatistics(statistics: ReadableMap) {
339
+ emitOnStatistics(statistics)
308
340
  }
309
341
 
310
342
  private fun startTrim() {
311
- val activity = NitroModules.applicationContext?.currentActivity
343
+ val activity = reactApplicationContext.currentActivity
312
344
  // Create the parent layout for the dialog
313
345
  val layout = LinearLayout(activity)
314
346
  layout.layoutParams = ViewGroup.LayoutParams(
@@ -325,7 +357,8 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
325
357
  ViewGroup.LayoutParams.WRAP_CONTENT,
326
358
  ViewGroup.LayoutParams.WRAP_CONTENT
327
359
  )
328
- textView.text = editorConfig!!.trimmingText
360
+ textView.text = editorConfig?.getString("trimmingText")
361
+ ?: "Trimming in progress..."
329
362
  textView.gravity = Gravity.CENTER
330
363
  textView.textSize = 18f
331
364
  layout.addView(textView)
@@ -340,14 +373,15 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
340
373
  layout.addView(mProgressBar)
341
374
 
342
375
  // Create button
343
- if (editorConfig!!.enableCancelTrimming) {
376
+ if (editorConfig?.getBoolean("enableCancelTrimming") == true) {
344
377
  val button = Button(activity)
345
378
  button.layoutParams = ViewGroup.LayoutParams(
346
379
  ViewGroup.LayoutParams.WRAP_CONTENT,
347
380
  ViewGroup.LayoutParams.WRAP_CONTENT
348
381
  )
349
382
  // Set the text and style it like a text button
350
- button.text = editorConfig!!.cancelTrimmingButtonText
383
+ button.text = editorConfig?.getString("cancelTrimmingText")
384
+ ?: "Cancel Trimming"
351
385
  button.setTextColor(
352
386
  ContextCompat.getColor(
353
387
  activity!!,
@@ -359,15 +393,15 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
359
393
  val outValue = TypedValue()
360
394
  activity.theme.resolveAttribute(selectableItemBackground, outValue, true)
361
395
  button.setBackgroundResource(outValue.resourceId)
362
- button.setOnClickListener { v: View? ->
363
- if (editorConfig!!.enableCancelTrimmingDialog) {
396
+ button.setOnClickListener { _: View? ->
397
+ if (editorConfig?.getBoolean("enableCancelTrimmingDialog") == true) {
364
398
  val builder = AlertDialog.Builder(
365
399
  activity
366
400
  )
367
- builder.setMessage(editorConfig!!.cancelTrimmingDialogMessage)
368
- builder.setTitle(editorConfig!!.cancelTrimmingDialogTitle)
401
+ builder.setMessage(editorConfig?.getString("cancelTrimmingDialogMessage"))
402
+ builder.setTitle(editorConfig?.getString("cancelTrimmingDialogTitle"))
369
403
  builder.setCancelable(false)
370
- builder.setPositiveButton(editorConfig!!.cancelTrimmingDialogConfirmText) { dialog: DialogInterface?, which: Int ->
404
+ builder.setPositiveButton(editorConfig?.getString("cancelTrimmingDialogConfirmText")) { _: DialogInterface?, _: Int ->
371
405
  if (trimmerView != null) {
372
406
  trimmerView!!.onCancelTrimClicked()
373
407
  }
@@ -376,8 +410,8 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
376
410
  }
377
411
  }
378
412
  builder.setNegativeButton(
379
- editorConfig!!.cancelTrimmingDialogCancelText
380
- ) { dialog: DialogInterface, which: Int ->
413
+ editorConfig?.getString("cancelTrimmingDialogCancelText") ?: "Close"
414
+ ) { dialog: DialogInterface, _: Int ->
381
415
  dialog.cancel()
382
416
  }
383
417
  cancelTrimmingConfirmDialog = builder.create()
@@ -406,7 +440,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
406
440
  mProgressDialog = builder.create()
407
441
 
408
442
  mProgressDialog!!.setOnShowListener {
409
- sendEvent("onStartTrimming", mapOf())
443
+ emitOnStartTrimming()
410
444
  if (trimmerView != null) {
411
445
  trimmerView!!.onSaveClicked()
412
446
  }
@@ -440,27 +474,24 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
440
474
  }
441
475
  }
442
476
 
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
- }
477
+ // private fun sendEvent(
478
+ // eventName: String,
479
+ // params: Map<String, String>
480
+ // ) {
481
+ // onEvent?.let { it(eventName, params) }
482
+ //
483
+ // if (eventName == "onHide" && onComplete != null) {
484
+ // onComplete?.let { it() }
485
+ // onComplete = null // Clear the callback after invoking it
486
+ // }
487
+ // }
488
+
489
+ override fun listFiles(promise: Promise) {
490
+ promise.resolve(StorageUtil.listFiles(reactApplicationContext))
459
491
  }
460
492
 
461
- override fun cleanFiles(): Promise<Double> {
462
- return Promise.async {
463
- val files = StorageUtil.listFiles(NitroModules.applicationContext)
493
+ override fun cleanFiles(promise: Promise) {
494
+ val files = StorageUtil.listFiles(reactApplicationContext)
464
495
  var successCount = 0
465
496
  for (file in files) {
466
497
  val state = StorageUtil.deleteFile(file)
@@ -469,143 +500,127 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
469
500
  }
470
501
  }
471
502
 
472
- successCount.toDouble()
473
- }
503
+ promise.resolve(successCount.toDouble())
474
504
  }
475
505
 
476
- override fun deleteFile(filePath: String): Promise<Boolean> {
477
- return Promise.async {
478
- StorageUtil.deleteFile(filePath)
479
- }
506
+ override fun deleteFile(filePath: String?, promise: Promise) {
507
+ promise.resolve(StorageUtil.deleteFile(filePath))
480
508
  }
481
509
 
482
- override fun closeEditor(onComplete: () -> Unit) {
483
- this.onComplete = onComplete
510
+ override fun closeEditor() {
484
511
  hideDialog(true)
512
+ emitOnHide()
485
513
  }
486
514
 
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
- }
515
+ override fun isValidFile(url: String, promise: Promise) {
516
+ MediaMetadataUtil.checkFileValidity(url) { isValid: Boolean, fileType: String, duration: Long ->
517
+ if (isValid) {
518
+ Log.d(TAG, "Valid $fileType file with duration: $duration milliseconds")
519
+ } else {
520
+ Log.d(TAG, "Invalid file")
505
521
  }
506
- // Resolve the promise with the FileValidationResult
507
- getValidationResult()
508
- }
509
- }
522
+ // Create a FileValidationResult object
510
523
 
511
- override fun trim(url: String, options: TrimOptions): Promise<String> {
512
- return Promise.async {
513
- trimOptions = options
524
+ val result = Arguments.createMap()
525
+ result.putBoolean("isValid", isValid)
526
+ result.putString("fileType", fileType)
527
+ result.putDouble("duration", duration.toDouble())
514
528
 
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")
529
+ promise.resolve(result)
530
+ }
531
+ }
519
532
 
520
- dateFormat.timeZone = TimeZone.getTimeZone("UTC")
521
- val formattedDateTime = dateFormat.format(currentDate)
533
+ override fun trim(url: String, options: ReadableMap?, promise: Promise) {
534
+ trimOptions = options
522
535
 
523
- var cmds = arrayOf(
524
- "-ss",
525
- "${options.startTime}ms",
526
- "-to",
527
- "${options.endTime}ms",
528
- )
536
+ val currentDate = Date()
537
+ val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
529
538
 
530
- if (options.enableRotation) {
531
- cmds += arrayOf("-display_rotation", "${options.rotationAngle}")
532
- }
539
+ dateFormat.timeZone = TimeZone.getTimeZone("UTC")
540
+ val formattedDateTime = dateFormat.format(currentDate)
533
541
 
534
- outputFile = StorageUtil.getOutputPath(NitroModules.applicationContext, options.outputExt)
542
+ var cmds = arrayOf(
543
+ "-ss",
544
+ "${options?.getDouble("startTime") ?: 0 }ms",
545
+ "-to",
546
+ "${options?.getDouble("endTime") ?: 1000}ms",
547
+ )
535
548
 
536
- cmds += arrayOf(
537
- "-i",
538
- url,
539
- "-c",
540
- "copy",
541
- "-metadata",
542
- "creation_time=$formattedDateTime",
543
- outputFile!!
544
- )
549
+ if (options?.getBoolean("enableRotation") == true) {
550
+ cmds += arrayOf("-display_rotation", "${options.getDouble("rotationAngle")}")
551
+ }
545
552
 
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
- }
553
+ outputFile = StorageUtil.getOutputPath(reactApplicationContext, options?.getString("outputExt") ?: "mp4")
561
554
 
562
- continuation.resume(outputFile!!)
563
- } catch (e: IOException) {
564
- e.printStackTrace()
555
+ cmds += arrayOf(
556
+ "-i",
557
+ url,
558
+ "-c",
559
+ "copy",
560
+ "-metadata",
561
+ "creation_time=$formattedDateTime",
562
+ outputFile!!
563
+ )
565
564
 
566
- if (options.removeAfterFailedToSavePhoto) {
567
- StorageUtil.deleteFile(outputFile)
568
- }
565
+ Log.d(TAG, "Command: ${cmds.joinToString(",")}")
566
+
567
+ FFmpegKit.executeWithArgumentsAsync(cmds, { session ->
568
+ val state = session.state
569
+ val returnCode = session.returnCode
570
+ when {
571
+ ReturnCode.isSuccess(returnCode) -> {
572
+ // SUCCESS
573
+ if (options?.getBoolean("saveToPhoto") == true && options.getString("type") == "video") {
574
+ try {
575
+ StorageUtil.saveVideoToGallery(reactApplicationContext, outputFile)
576
+ Log.d(TAG, "Edited video saved to Photo Library successfully.")
577
+ if (options.getBoolean("removeAfterSavedToPhoto")) {
578
+ StorageUtil.deleteFile(outputFile)
579
+ }
569
580
 
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
- }
581
+ promise.resolve(outputFile)
582
+ } catch (e: IOException) {
583
+ e.printStackTrace()
580
584
 
581
- continuation.resume(outputFile!!)
582
- }
585
+ if (options.getBoolean("removeAfterFailedToSavePhoto")) {
586
+ StorageUtil.deleteFile(outputFile)
583
587
  }
584
- ReturnCode.isCancel(returnCode) -> {
585
- // CANCEL
586
- println("FFmpeg command was cancelled")
587
- continuation.resumeWithException(
588
- Exception("FFmpeg command was cancelled")
588
+
589
+ promise.reject(
590
+ Exception("Failed to save edited video to Photo Library: " + e.localizedMessage)
589
591
  )
590
592
  }
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
- )
593
+ } else {
594
+ if (options?.getBoolean("openDocumentsOnFinish") == true) {
595
+ saveFileToExternalStorage(File(outputFile!!))
596
+ } else if (options?.getBoolean("openShareSheetOnFinish") == true) {
597
+ shareFile(reactApplicationContext, File(outputFile!!))
598
598
  }
599
+
600
+ promise.resolve(outputFile)
599
601
  }
600
- }, { log ->
601
- Log.d(TAG, "FFmpeg process started with log ${log.message}")
602
- }, { statistics ->
603
- // Handle statistics if needed
604
- })
602
+ }
603
+ ReturnCode.isCancel(returnCode) -> {
604
+ // CANCEL
605
+ println("FFmpeg command was cancelled")
606
+ promise.reject(
607
+ Exception("FFmpeg command was cancelled")
608
+ )
609
+ }
610
+ else -> {
611
+ // FAILURE
612
+ val errorMessage = String.format("Command failed with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace());
613
+ println(errorMessage)
614
+ promise.reject(
615
+ Exception(errorMessage)
616
+ )
617
+ }
605
618
  }
606
-
607
- getTrimresult()
608
- }
619
+ }, { log ->
620
+ Log.d(TAG, "FFmpeg process started with log ${log.message}")
621
+ }, { statistics ->
622
+ // Handle statistics if needed
623
+ })
609
624
  }
610
625
 
611
626
  private fun saveFileToExternalStorage(file: File) {
@@ -613,8 +628,7 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
613
628
  intent.addCategory(Intent.CATEGORY_OPENABLE)
614
629
  intent.setType("*/*") // Change MIME type as needed
615
630
  intent.putExtra(Intent.EXTRA_TITLE, file.name)
616
- NitroModules.applicationContext?.currentActivity!!
617
- .startActivityForResult(intent, REQUEST_CODE_SAVE_FILE)
631
+ reactApplicationContext.currentActivity?.startActivityForResult(intent, REQUEST_CODE_SAVE_FILE)
618
632
  }
619
633
 
620
634
  private fun shareFile(context: Context, file: File) {
@@ -635,11 +649,11 @@ class VideoTrim : HybridVideoTrimSpec(), VideoTrimListener, LifecycleEventListen
635
649
  }
636
650
 
637
651
  // directly use context.startActivity(shareIntent) will cause crash
638
- NitroModules.applicationContext?.currentActivity!!
639
- .startActivity(Intent.createChooser(shareIntent, "Share file"))
652
+ reactApplicationContext.currentActivity?.startActivity(Intent.createChooser(shareIntent, "Share file"))
640
653
  }
641
654
 
642
655
  companion object {
656
+ const val NAME = "VideoTrim"
643
657
  const val TAG = "VideoTrimModule"
644
658
  const val REQUEST_CODE_SAVE_FILE = 1
645
659
  }