react-native-video-trim 3.0.10 → 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.
- package/LICENSE +1 -1
- package/README.md +46 -57
- package/VideoTrim.podspec +24 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +76 -51
- package/android/gradle.properties +5 -5
- package/android/src/main/AndroidManifest.xml +4 -2
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrim.kt +629 -0
- package/android/src/main/java/com/margelo/nitro/videotrim/VideoTrimPackage.kt +22 -0
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/enums/ErrorCode.java +1 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/interfaces/IVideoTrimmerView.java +1 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/interfaces/VideoTrimListener.java +6 -5
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/MediaMetadataUtil.java +1 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/StorageUtil.java +3 -1
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/utils/VideoTrimmerUtil.java +22 -20
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/widgets/VideoTrimmerView.java +38 -66
- package/ios/AssetLoader.swift +2 -2
- package/ios/ErrorCode.swift +2 -2
- package/ios/ProgressAlertController.swift +2 -2
- package/ios/VideoTrim.swift +48 -838
- package/ios/VideoTrimImpl.swift +860 -0
- package/ios/VideoTrimmer.swift +2 -3
- package/ios/VideoTrimmerThumb.swift +33 -26
- package/ios/VideoTrimmerViewController.swift +47 -28
- package/lib/module/VideoTrim.nitro.js +4 -0
- package/lib/module/VideoTrim.nitro.js.map +1 -0
- package/lib/module/index.js +71 -22
- package/lib/module/index.js.map +1 -1
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/{index.d.ts → src/VideoTrim.nitro.d.ts} +63 -89
- package/lib/typescript/src/VideoTrim.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +41 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/nitrogen/generated/android/c++/JEditorConfig.hpp +229 -0
- package/nitrogen/generated/android/c++/JFileValidationResult.hpp +61 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +74 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string_std__unordered_map_std__string__std__string_.hpp +89 -0
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.cpp +131 -0
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +67 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +70 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/FileValidationResult.kt +28 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/Func_void_std__string_std__unordered_map_std__string__std__string_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/HybridVideoTrimSpec.kt +82 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/videotrimOnLoad.kt +35 -0
- package/nitrogen/generated/android/videotrim+autolinking.cmake +78 -0
- package/nitrogen/generated/android/videotrim+autolinking.gradle +27 -0
- package/nitrogen/generated/android/videotrimOnLoad.cpp +50 -0
- package/nitrogen/generated/android/videotrimOnLoad.hpp +25 -0
- package/nitrogen/generated/ios/VideoTrim+autolinking.rb +60 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.cpp +88 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +331 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +53 -0
- package/nitrogen/generated/ios/VideoTrimAutolinking.mm +33 -0
- package/nitrogen/generated/ios/VideoTrimAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridVideoTrimSpecSwift.hpp +116 -0
- package/nitrogen/generated/ios/swift/EditorConfig.swift +519 -0
- package/nitrogen/generated/ios/swift/FileValidationResult.swift +57 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_FileValidationResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string_std__unordered_map_std__string__std__string_.swift +54 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_std__string_.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec.swift +53 -0
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +222 -0
- package/nitrogen/generated/shared/c++/EditorConfig.hpp +245 -0
- package/nitrogen/generated/shared/c++/FileValidationResult.hpp +77 -0
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +76 -0
- package/package.json +75 -71
- package/src/VideoTrim.nitro.ts +244 -0
- package/src/index.tsx +87 -258
- package/android/src/main/AndroidManifestDeprecated.xml +0 -3
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +0 -603
- package/android/src/main/java/com/videotrim/VideoTrimPackage.java +0 -28
- package/ios/VideoTrim-Bridging-Header.h +0 -2
- package/ios/VideoTrim.mm +0 -17
- package/ios/VideoTrim.xcodeproj/project.pbxproj +0 -283
- package/lib/commonjs/index.js +0 -87
- package/lib/commonjs/index.js.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/react-native-video-trim.podspec +0 -43
|
@@ -0,0 +1,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,7 +1,8 @@
|
|
|
1
|
-
package com.videotrim.interfaces;
|
|
1
|
+
package com.margelo.nitro.videotrim.interfaces;
|
|
2
2
|
|
|
3
|
-
import com.
|
|
4
|
-
|
|
3
|
+
import com.margelo.nitro.videotrim.enums.ErrorCode;
|
|
4
|
+
|
|
5
|
+
import java.util.Map;
|
|
5
6
|
|
|
6
7
|
public interface VideoTrimListener {
|
|
7
8
|
void onLoad(int duration);
|
|
@@ -11,6 +12,6 @@ public interface VideoTrimListener {
|
|
|
11
12
|
void onError(String errorMessage, ErrorCode errorCode);
|
|
12
13
|
void onCancel();
|
|
13
14
|
void onSave();
|
|
14
|
-
void onLog(
|
|
15
|
-
void onStatistics(
|
|
15
|
+
void onLog(Map<String, String> log);
|
|
16
|
+
void onStatistics(Map<String, String> statistics);
|
|
16
17
|
}
|