react-native-video-trim 4.0.0 → 4.1.0

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