react-native-video-trim 3.0.10 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +80 -63
- package/VideoTrim.podspec +24 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +82 -49
- package/android/gradle.properties +7 -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 +646 -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 +51 -41
- package/android/src/main/java/com/{videotrim → margelo/nitro/videotrim}/widgets/VideoTrimmerView.java +45 -69
- package/ios/AssetLoader.swift +2 -2
- package/ios/ErrorCode.swift +2 -2
- package/ios/ProgressAlertController.swift +2 -2
- package/ios/VideoTrim.swift +52 -835
- package/ios/VideoTrimImpl.swift +957 -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 +98 -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} +125 -134
- package/lib/typescript/src/VideoTrim.nitro.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +49 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/nitrogen/generated/android/c++/JEditorConfig.hpp +237 -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 +151 -0
- package/nitrogen/generated/android/c++/JHybridVideoTrimSpec.hpp +68 -0
- package/nitrogen/generated/android/c++/JTrimOptions.hpp +109 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/EditorConfig.kt +72 -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 +86 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/videotrim/TrimOptions.kt +40 -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 +96 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Bridge.hpp +374 -0
- package/nitrogen/generated/ios/VideoTrim-Swift-Cxx-Umbrella.hpp +56 -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 +127 -0
- package/nitrogen/generated/ios/swift/EditorConfig.swift +541 -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.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 +54 -0
- package/nitrogen/generated/ios/swift/HybridVideoTrimSpec_cxx.swift +241 -0
- package/nitrogen/generated/ios/swift/TrimOptions.swift +189 -0
- package/nitrogen/generated/shared/c++/EditorConfig.hpp +253 -0
- package/nitrogen/generated/shared/c++/FileValidationResult.hpp +77 -0
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.cpp +27 -0
- package/nitrogen/generated/shared/c++/HybridVideoTrimSpec.hpp +80 -0
- package/nitrogen/generated/shared/c++/TrimOptions.hpp +125 -0
- package/package.json +75 -71
- package/src/VideoTrim.nitro.ts +263 -0
- package/src/index.tsx +120 -257
- 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
package/ios/VideoTrim.swift
CHANGED
|
@@ -1,850 +1,67 @@
|
|
|
1
|
-
import
|
|
2
|
-
import React
|
|
1
|
+
import NitroModules
|
|
3
2
|
import ffmpegkit
|
|
3
|
+
import Photos
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
private var enableCancelDialog = true
|
|
23
|
-
private var cancelDialogTitle = "Warning!"
|
|
24
|
-
private var cancelDialogMessage = "Are you sure want to cancel?"
|
|
25
|
-
private var cancelDialogCancelText = "Close"
|
|
26
|
-
private var cancelDialogConfirmText = "Proceed"
|
|
27
|
-
private var enableSaveDialog = true
|
|
28
|
-
private var saveDialogTitle = "Confirmation!"
|
|
29
|
-
private var saveDialogMessage = "Are you sure want to save?"
|
|
30
|
-
private var saveDialogCancelText = "Close"
|
|
31
|
-
private var saveDialogConfirmText = "Proceed"
|
|
32
|
-
private var fullScreenModalIOS = false
|
|
33
|
-
private var cancelButtonText = "Cancel"
|
|
34
|
-
private var saveButtonText = "Save"
|
|
35
|
-
private var vc: VideoTrimmerViewController?
|
|
36
|
-
private var isVideoType = true
|
|
37
|
-
private var outputExt = "mp4"
|
|
38
|
-
private var openDocumentsOnFinish = false
|
|
39
|
-
private var openShareSheetOnFinish = false
|
|
40
|
-
private var outputFile: URL?
|
|
41
|
-
private var closeWhenFinish = true
|
|
42
|
-
private var enableCancelTrimming = true
|
|
43
|
-
private var cancelTrimmingButtonText = "Cancel"
|
|
44
|
-
private var enableCancelTrimmingDialog = true
|
|
45
|
-
private var cancelTrimmingDialogTitle = "Warning!"
|
|
46
|
-
private var cancelTrimmingDialogMessage = "Are you sure want to trimming?"
|
|
47
|
-
private var cancelTrimmingDialogCancelText = "Close"
|
|
48
|
-
private var cancelTrimmingDialogConfirmText = "Proceed"
|
|
49
|
-
private var alertOnFailToLoad = true
|
|
50
|
-
private var alertOnFailTitle = "Error"
|
|
51
|
-
private var alertOnFailMessage =
|
|
52
|
-
"Fail to load media. Possibly invalid file or no network connection"
|
|
53
|
-
private var alertOnFailCloseText = "Close"
|
|
54
|
-
|
|
55
|
-
@objc
|
|
56
|
-
static override func requiresMainQueueSetup() -> Bool {
|
|
57
|
-
return true
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
override func supportedEvents() -> [String]! {
|
|
61
|
-
return ["VideoTrim"]
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
override func startObserving() {
|
|
65
|
-
hasListeners = true
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
override func stopObserving() {
|
|
69
|
-
hasListeners = false
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@objc(showEditor:withConfig:)
|
|
73
|
-
func showEditor(uri: String, config: NSDictionary) {
|
|
74
|
-
if isShowing {
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
saveToPhoto = config["saveToPhoto"] as? Bool ?? false
|
|
78
|
-
|
|
79
|
-
removeAfterSavedToPhoto =
|
|
80
|
-
config["removeAfterSavedToPhoto"] as? Bool ?? false
|
|
81
|
-
removeAfterFailedToSavePhoto =
|
|
82
|
-
config["removeAfterFailedToSavePhoto"] as? Bool ?? false
|
|
83
|
-
removeAfterSavedToDocuments =
|
|
84
|
-
config["removeAfterSavedToDocuments"] as? Bool ?? false
|
|
85
|
-
removeAfterFailedToSaveDocuments =
|
|
86
|
-
config["removeAfterFailedToSaveDocuments"] as? Bool ?? false
|
|
87
|
-
removeAfterShared = config["removeAfterShared"] as? Bool ?? false
|
|
88
|
-
removeAfterFailedToShare =
|
|
89
|
-
config["removeAfterFailedToShare"] as? Bool ?? false
|
|
90
|
-
|
|
91
|
-
enableCancelDialog = config["enableCancelDialog"] as? Bool ?? true
|
|
92
|
-
cancelDialogTitle = config["cancelDialogTitle"] as? String ?? "Warning!"
|
|
93
|
-
cancelDialogMessage =
|
|
94
|
-
config["cancelDialogMessage"] as? String ?? "Are you sure want to cancel?"
|
|
95
|
-
cancelDialogCancelText =
|
|
96
|
-
config["cancelDialogCancelText"] as? String ?? "Close"
|
|
97
|
-
cancelDialogConfirmText =
|
|
98
|
-
config["cancelDialogConfirmText"] as? String ?? "Proceed"
|
|
99
|
-
|
|
100
|
-
enableSaveDialog = config["enableSaveDialog"] as? Bool ?? true
|
|
101
|
-
saveDialogTitle = config["saveDialogTitle"] as? String ?? "Confirmation!"
|
|
102
|
-
saveDialogMessage =
|
|
103
|
-
config["saveDialogMessage"] as? String ?? "Are you sure want to save?"
|
|
104
|
-
saveDialogCancelText = config["saveDialogCancelText"] as? String ?? "Close"
|
|
105
|
-
saveDialogConfirmText =
|
|
106
|
-
config["saveDialogConfirmText"] as? String ?? "Proceed"
|
|
107
|
-
trimmingText = config["trimmingText"] as? String ?? "Trimming video..."
|
|
108
|
-
fullScreenModalIOS = config["fullScreenModalIOS"] as? Bool ?? false
|
|
109
|
-
isVideoType = (config["type"] as? String ?? "video") == "video"
|
|
110
|
-
outputExt = config["outputExt"] as? String ?? "mp4"
|
|
111
|
-
openDocumentsOnFinish = config["openDocumentsOnFinish"] as? Bool ?? false
|
|
112
|
-
openShareSheetOnFinish = config["openShareSheetOnFinish"] as? Bool ?? false
|
|
113
|
-
|
|
114
|
-
closeWhenFinish = config["closeWhenFinish"] as? Bool ?? true
|
|
115
|
-
enableCancelTrimming = config["enableCancelTrimming"] as? Bool ?? true
|
|
116
|
-
cancelTrimmingButtonText =
|
|
117
|
-
config["cancelTrimmingButtonText"] as? String ?? "Cancel"
|
|
118
|
-
enableCancelTrimmingDialog =
|
|
119
|
-
config["enableCancelTrimmingDialog"] as? Bool ?? true
|
|
120
|
-
cancelTrimmingDialogTitle =
|
|
121
|
-
config["cancelTrimmingDialogTitle"] as? String ?? "Warning!"
|
|
122
|
-
cancelTrimmingDialogMessage =
|
|
123
|
-
config["cancelTrimmingDialogMessage"] as? String
|
|
124
|
-
?? "Are you sure want to cancel trimming?"
|
|
125
|
-
cancelTrimmingDialogCancelText =
|
|
126
|
-
config["cancelTrimmingDialogCancelText"] as? String ?? "Close"
|
|
127
|
-
cancelTrimmingDialogConfirmText =
|
|
128
|
-
config["cancelTrimmingDialogConfirmText"] as? String ?? "Proceed"
|
|
129
|
-
alertOnFailToLoad = config["alertOnFailToLoad"] as? Bool ?? true
|
|
130
|
-
alertOnFailTitle = config["alertOnFailTitle"] as? String ?? "Error"
|
|
131
|
-
alertOnFailMessage =
|
|
132
|
-
config["alertOnFailMessage"] as? String
|
|
133
|
-
?? "Fail to load media. Possibly invalid file or no network connection"
|
|
134
|
-
alertOnFailCloseText = config["alertOnFailCloseText"] as? String ?? "Close"
|
|
135
|
-
|
|
136
|
-
if let cancelBtnText = config["cancelButtonText"] as? String,
|
|
137
|
-
!cancelBtnText.isEmpty
|
|
138
|
-
{
|
|
139
|
-
self.cancelButtonText = cancelBtnText
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if let saveButtonText = config["saveButtonText"] as? String,
|
|
143
|
-
!saveButtonText.isEmpty
|
|
144
|
-
{
|
|
145
|
-
self.saveButtonText = saveButtonText
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let destPath = URL(string: uri)
|
|
149
|
-
let newPath = renameFile(at: destPath!, newName: "beforeTrim")
|
|
150
|
-
|
|
151
|
-
guard let destPath = newPath else { return }
|
|
152
|
-
|
|
153
|
-
DispatchQueue.main.async {
|
|
154
|
-
self.vc = VideoTrimmerViewController()
|
|
155
|
-
|
|
156
|
-
guard let vc = self.vc else { return }
|
|
157
|
-
|
|
158
|
-
vc.configure(config: config)
|
|
159
|
-
|
|
160
|
-
vc.cancelBtnClicked = {
|
|
161
|
-
if !self.enableCancelDialog {
|
|
162
|
-
self.emitEventToJS("onCancel", eventData: nil)
|
|
163
|
-
|
|
164
|
-
vc.dismiss(
|
|
165
|
-
animated: true,
|
|
166
|
-
completion: {
|
|
167
|
-
self.emitEventToJS("onHide", eventData: nil)
|
|
168
|
-
self.isShowing = false
|
|
169
|
-
})
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Create Alert
|
|
174
|
-
let dialogMessage = UIAlertController(
|
|
175
|
-
title: self.cancelDialogTitle, message: self.cancelDialogMessage,
|
|
176
|
-
preferredStyle: .alert)
|
|
177
|
-
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
178
|
-
|
|
179
|
-
// Create OK button with action handler
|
|
180
|
-
let ok = UIAlertAction(
|
|
181
|
-
title: self.cancelDialogConfirmText, style: .destructive,
|
|
182
|
-
handler: { (action) -> Void in
|
|
183
|
-
self.emitEventToJS("onCancel", eventData: nil)
|
|
184
|
-
|
|
185
|
-
vc.dismiss(
|
|
186
|
-
animated: true,
|
|
187
|
-
completion: {
|
|
188
|
-
self.emitEventToJS("onHide", eventData: nil)
|
|
189
|
-
self.isShowing = false
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
// Create Cancel button with action handlder
|
|
194
|
-
let cancel = UIAlertAction(
|
|
195
|
-
title: self.cancelDialogCancelText, style: .cancel)
|
|
196
|
-
|
|
197
|
-
//Add OK and Cancel button to an Alert object
|
|
198
|
-
dialogMessage.addAction(ok)
|
|
199
|
-
dialogMessage.addAction(cancel)
|
|
200
|
-
|
|
201
|
-
// Present alert message to user
|
|
202
|
-
if let root = RCTPresentedViewController() {
|
|
203
|
-
root.present(dialogMessage, animated: true, completion: nil)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
vc.saveBtnClicked = { (selectedRange: CMTimeRange) in
|
|
208
|
-
if !self.enableSaveDialog {
|
|
209
|
-
self.trim(
|
|
210
|
-
viewController: vc, inputFile: destPath,
|
|
211
|
-
videoDuration: self.vc!.asset!.duration.seconds,
|
|
212
|
-
startTime: selectedRange.start.seconds,
|
|
213
|
-
endTime: selectedRange.end.seconds)
|
|
214
|
-
return
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Create Alert
|
|
218
|
-
let dialogMessage = UIAlertController(
|
|
219
|
-
title: self.saveDialogTitle, message: self.saveDialogMessage,
|
|
220
|
-
preferredStyle: .alert)
|
|
221
|
-
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
222
|
-
|
|
223
|
-
// Create OK button with action handler
|
|
224
|
-
let ok = UIAlertAction(
|
|
225
|
-
title: self.saveDialogConfirmText, style: .default,
|
|
226
|
-
handler: { (action) -> Void in
|
|
227
|
-
self.trim(
|
|
228
|
-
viewController: vc, inputFile: destPath,
|
|
229
|
-
videoDuration: vc.asset!.duration.seconds,
|
|
230
|
-
startTime: selectedRange.start.seconds,
|
|
231
|
-
endTime: selectedRange.end.seconds)
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
// Create Cancel button with action handlder
|
|
235
|
-
let cancel = UIAlertAction(
|
|
236
|
-
title: self.saveDialogCancelText, style: .cancel)
|
|
237
|
-
|
|
238
|
-
//Add OK and Cancel button to an Alert object
|
|
239
|
-
dialogMessage.addAction(ok)
|
|
240
|
-
dialogMessage.addAction(cancel)
|
|
241
|
-
|
|
242
|
-
// Present alert message to user
|
|
243
|
-
if let root = RCTPresentedViewController() {
|
|
244
|
-
root.present(dialogMessage, animated: true, completion: nil)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
vc.isModalInPresentation = true // prevent modal closed by swipe down
|
|
249
|
-
|
|
250
|
-
if self.fullScreenModalIOS {
|
|
251
|
-
vc.modalPresentationStyle = .fullScreen
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if let root = RCTPresentedViewController() {
|
|
255
|
-
root.present(
|
|
256
|
-
vc, animated: true,
|
|
257
|
-
completion: {
|
|
258
|
-
self.emitEventToJS("onShow", eventData: nil)
|
|
259
|
-
self.isShowing = true
|
|
260
|
-
|
|
261
|
-
// start loading asset after view is finished presenting
|
|
262
|
-
// otherwise it may run too fast for local file and autoplay looks weird
|
|
263
|
-
let assetLoader = AssetLoader()
|
|
264
|
-
assetLoader.delegate = self
|
|
265
|
-
assetLoader.loadAsset(url: destPath, isVideoType: self.isVideoType)
|
|
266
|
-
})
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private func copyFileToDocumentDir(uri: String) -> URL? {
|
|
273
|
-
if let videoURL = URL(string: uri) {
|
|
274
|
-
// Save the video to the document directory
|
|
275
|
-
let documentsDirectory = FileManager.default.urls(
|
|
276
|
-
for: .documentDirectory, in: .userDomainMask
|
|
277
|
-
).first!
|
|
278
|
-
// Extract the file extension from the videoURL
|
|
279
|
-
let fileExtension = videoURL.pathExtension
|
|
280
|
-
|
|
281
|
-
// Define the filename with the correct file extension
|
|
282
|
-
let timestamp = Int(Date().timeIntervalSince1970)
|
|
283
|
-
let destinationURL = documentsDirectory.appendingPathComponent(
|
|
284
|
-
"\(FILE_PREFIX)_original_\(timestamp).\(fileExtension)")
|
|
285
|
-
|
|
286
|
-
do {
|
|
287
|
-
try FileManager.default.copyItem(at: videoURL, to: destinationURL)
|
|
288
|
-
} catch {
|
|
289
|
-
print("Error while copying file to document directory \(error)")
|
|
290
|
-
return nil
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return destinationURL
|
|
294
|
-
} else {
|
|
295
|
-
return nil
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
private func emitEventToJS(_ eventName: String, eventData: [String: Any]?) {
|
|
300
|
-
if hasListeners {
|
|
301
|
-
var modifiedEventData = eventData ?? [:] // If eventData is nil, create an empty dictionary
|
|
302
|
-
modifiedEventData["name"] = eventName
|
|
303
|
-
sendEvent(withName: "VideoTrim", body: modifiedEventData)
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
@objc(listFiles:withRejecter:)
|
|
308
|
-
func listFiles(
|
|
309
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
310
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
311
|
-
) {
|
|
312
|
-
let files = listFiles()
|
|
313
|
-
resolve(files.map { $0.absoluteString })
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
@objc(cleanFiles:withRejecter:)
|
|
317
|
-
func cleanFiles(
|
|
318
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
319
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
320
|
-
) {
|
|
321
|
-
let files = listFiles()
|
|
322
|
-
var successCount = 0
|
|
323
|
-
for file in files {
|
|
324
|
-
let state = deleteFile(url: file)
|
|
325
|
-
|
|
326
|
-
if state == 0 {
|
|
327
|
-
successCount += 1
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
resolve(successCount)
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
@objc(deleteFile:withResolver:withRejecter:)
|
|
335
|
-
func deleteFile(
|
|
336
|
-
uri: String, resolve: @escaping RCTPromiseResolveBlock,
|
|
337
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
338
|
-
) {
|
|
339
|
-
let state = deleteFile(url: URL(string: uri)!)
|
|
340
|
-
resolve(state == 0)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
private func listFiles() -> [URL] {
|
|
344
|
-
var files: [URL] = []
|
|
345
|
-
|
|
346
|
-
let documentsDirectory = FileManager.default.urls(
|
|
347
|
-
for: .documentDirectory, in: .userDomainMask
|
|
348
|
-
).first!
|
|
349
|
-
|
|
350
|
-
do {
|
|
351
|
-
let directoryContents = try FileManager.default.contentsOfDirectory(
|
|
352
|
-
at: documentsDirectory, includingPropertiesForKeys: nil)
|
|
353
|
-
|
|
354
|
-
for fileURL in directoryContents {
|
|
355
|
-
if fileURL.lastPathComponent.starts(with: FILE_PREFIX) {
|
|
356
|
-
files.append(fileURL)
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
} catch {
|
|
360
|
-
print("[listFiles] Error when retrieving files: \(error)")
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
return files
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
private func deleteFile(url: URL) -> Int {
|
|
367
|
-
do {
|
|
368
|
-
if FileManager.default.fileExists(atPath: url.path) {
|
|
369
|
-
try FileManager.default.removeItem(at: url)
|
|
370
|
-
|
|
371
|
-
return 0
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return 1
|
|
375
|
-
} catch {
|
|
376
|
-
print("[deleteFile] Error deleting files: \(error)")
|
|
377
|
-
|
|
378
|
-
return 2
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
private func trim(
|
|
383
|
-
viewController: VideoTrimmerViewController, inputFile: URL,
|
|
384
|
-
videoDuration: Double, startTime: Double, endTime: Double
|
|
385
|
-
) {
|
|
386
|
-
vc?.pausePlayer()
|
|
387
|
-
|
|
388
|
-
let timestamp = Int(Date().timeIntervalSince1970)
|
|
389
|
-
let outputName = "\(FILE_PREFIX)_\(timestamp).\(outputExt)"
|
|
390
|
-
let documentsDirectory = FileManager.default.urls(
|
|
391
|
-
for: .documentDirectory, in: .userDomainMask
|
|
392
|
-
).first!
|
|
393
|
-
outputFile = documentsDirectory.appendingPathComponent(outputName)
|
|
394
|
-
|
|
395
|
-
let formatter = DateFormatter()
|
|
396
|
-
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ"
|
|
397
|
-
formatter.timeZone = TimeZone(identifier: "UTC")
|
|
398
|
-
let dateTime = formatter.string(from: Date())
|
|
399
|
-
|
|
400
|
-
emitEventToJS("onStartTrimming", eventData: nil)
|
|
401
|
-
|
|
402
|
-
var ffmpegSession: FFmpegSession?
|
|
403
|
-
let progressAlert = ProgressAlertController()
|
|
404
|
-
progressAlert.modalPresentationStyle = .overFullScreen
|
|
405
|
-
progressAlert.modalTransitionStyle = .crossDissolve
|
|
406
|
-
progressAlert.setTitle(trimmingText)
|
|
407
|
-
|
|
408
|
-
if enableCancelTrimming {
|
|
409
|
-
progressAlert.setCancelTitle(cancelTrimmingButtonText)
|
|
410
|
-
progressAlert.showCancelBtn()
|
|
411
|
-
progressAlert.onDismiss = {
|
|
412
|
-
if self.enableCancelTrimmingDialog {
|
|
413
|
-
let dialogMessage = UIAlertController(
|
|
414
|
-
title: self.cancelTrimmingDialogTitle,
|
|
415
|
-
message: self.cancelTrimmingDialogMessage, preferredStyle: .alert)
|
|
416
|
-
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
417
|
-
|
|
418
|
-
// Create OK button with action handler
|
|
419
|
-
let ok = UIAlertAction(
|
|
420
|
-
title: self.cancelDialogConfirmText, style: .destructive,
|
|
421
|
-
handler: { (action) -> Void in
|
|
422
|
-
|
|
423
|
-
if let ffmpegSession = ffmpegSession {
|
|
424
|
-
ffmpegSession.cancel()
|
|
425
|
-
} else {
|
|
426
|
-
self.emitEventToJS("onCancelTrimming", eventData: nil)
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
progressAlert.dismiss(animated: true)
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
// Create Cancel button with action handlder
|
|
433
|
-
let cancel = UIAlertAction(
|
|
434
|
-
title: self.cancelDialogCancelText, style: .cancel)
|
|
435
|
-
|
|
436
|
-
//Add OK and Cancel button to an Alert object
|
|
437
|
-
dialogMessage.addAction(ok)
|
|
438
|
-
dialogMessage.addAction(cancel)
|
|
439
|
-
|
|
440
|
-
// Present alert message to user
|
|
441
|
-
if let root = RCTPresentedViewController() {
|
|
442
|
-
root.present(dialogMessage, animated: true, completion: nil)
|
|
443
|
-
}
|
|
444
|
-
} else {
|
|
445
|
-
if let ffmpegSession = ffmpegSession {
|
|
446
|
-
ffmpegSession.cancel()
|
|
447
|
-
} else {
|
|
448
|
-
self.emitEventToJS("onCancelTrimming", eventData: nil)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
progressAlert.dismiss(animated: true)
|
|
5
|
+
class VideoTrim: HybridVideoTrimSpec {
|
|
6
|
+
|
|
7
|
+
private let impl = VideoTrimImpl()
|
|
8
|
+
|
|
9
|
+
public func showEditor(
|
|
10
|
+
filePath: String,
|
|
11
|
+
config: EditorConfig,
|
|
12
|
+
onEvent: @escaping (_ eventName: String, _ payload: Dictionary<String, String>) -> Void
|
|
13
|
+
) throws {
|
|
14
|
+
impl.showEditor(uri: filePath, editorConfig: config, onEvent: onEvent)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
func listFiles() throws -> Promise<[String]> {
|
|
18
|
+
return Promise.async {
|
|
19
|
+
// This runs on a separate Thread, and can use `await` syntax!
|
|
20
|
+
let files = self.impl.listFiles().map { $0.absoluteString }
|
|
21
|
+
return files
|
|
452
22
|
}
|
|
453
|
-
|
|
454
|
-
}
|
|
455
23
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
"\(inputFile)",
|
|
468
|
-
"-c",
|
|
469
|
-
"copy",
|
|
470
|
-
"-metadata",
|
|
471
|
-
"creation_time=\(dateTime)",
|
|
472
|
-
outputFile!.absoluteString,
|
|
473
|
-
]
|
|
474
|
-
|
|
475
|
-
print("Command: ", cmds.joined(separator: " "))
|
|
476
|
-
|
|
477
|
-
let eventPayload: [String: Any] = [
|
|
478
|
-
"command": cmds.joined(separator: " ")
|
|
479
|
-
]
|
|
480
|
-
self.emitEventToJS("onLog", eventData: eventPayload)
|
|
481
|
-
|
|
482
|
-
ffmpegSession = FFmpegKit.execute(
|
|
483
|
-
withArgumentsAsync: cmds,
|
|
484
|
-
withCompleteCallback: { session in
|
|
485
|
-
|
|
486
|
-
// always hide progressAlert
|
|
487
|
-
DispatchQueue.main.async {
|
|
488
|
-
progressAlert.dismiss(animated: true)
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
let state = session?.getState()
|
|
492
|
-
let returnCode = session?.getReturnCode()
|
|
493
|
-
|
|
494
|
-
if ReturnCode.isSuccess(returnCode) {
|
|
495
|
-
let eventPayload: [String: Any] = [
|
|
496
|
-
"outputPath": self.outputFile!.absoluteString,
|
|
497
|
-
"startTime": (startTime * 1000).rounded(),
|
|
498
|
-
"endTime": (endTime * 1000).rounded(),
|
|
499
|
-
"duration": (videoDuration * 1000).rounded(),
|
|
500
|
-
]
|
|
501
|
-
self.emitEventToJS("onFinishTrimming", eventData: eventPayload)
|
|
502
|
-
|
|
503
|
-
if self.saveToPhoto && self.isVideoType {
|
|
504
|
-
PHPhotoLibrary.requestAuthorization { status in
|
|
505
|
-
guard status == .authorized else {
|
|
506
|
-
self.onError(
|
|
507
|
-
message: "Permission to access Photo Library is not granted",
|
|
508
|
-
code: .noPhotoPermission)
|
|
509
|
-
return
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
PHPhotoLibrary.shared().performChanges({
|
|
513
|
-
let request =
|
|
514
|
-
PHAssetChangeRequest.creationRequestForAssetFromVideo(
|
|
515
|
-
atFileURL: self.outputFile!)
|
|
516
|
-
request?.creationDate = Date()
|
|
517
|
-
}) { success, error in
|
|
518
|
-
if success {
|
|
519
|
-
print("Edited video saved to Photo Library successfully.")
|
|
520
|
-
|
|
521
|
-
if self.removeAfterSavedToPhoto {
|
|
522
|
-
let _ = self.deleteFile(url: self.outputFile!)
|
|
523
|
-
}
|
|
524
|
-
} else {
|
|
525
|
-
self.onError(
|
|
526
|
-
message:
|
|
527
|
-
"Failed to save edited video to Photo Library: \(error?.localizedDescription ?? "Unknown error")",
|
|
528
|
-
code: .failToSaveToPhoto)
|
|
529
|
-
if self.removeAfterFailedToSavePhoto {
|
|
530
|
-
let _ = self.deleteFile(url: self.outputFile!)
|
|
531
|
-
}
|
|
24
|
+
|
|
25
|
+
public func cleanFiles() throws -> Promise<Double> {
|
|
26
|
+
return Promise.async {
|
|
27
|
+
// This runs on a separate Thread, and can use `await` syntax!
|
|
28
|
+
let files = self.impl.listFiles()
|
|
29
|
+
var successCount = 0
|
|
30
|
+
for file in files {
|
|
31
|
+
let state = self.impl.deleteFile(url: file)
|
|
32
|
+
|
|
33
|
+
if state == 0 {
|
|
34
|
+
successCount += 1
|
|
532
35
|
}
|
|
533
|
-
}
|
|
534
36
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
// must return otherwise editor will close
|
|
539
|
-
return
|
|
540
|
-
} else if self.openShareSheetOnFinish {
|
|
541
|
-
self.shareFile(fileURL: self.outputFile!)
|
|
542
|
-
|
|
543
|
-
// must return otherwise editor will close
|
|
544
|
-
return
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if self.closeWhenFinish {
|
|
548
|
-
self.closeEditor()
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
} else if ReturnCode.isCancel(returnCode) {
|
|
552
|
-
// CANCEL
|
|
553
|
-
self.emitEventToJS("onCancelTrimming", eventData: nil)
|
|
554
|
-
} else {
|
|
555
|
-
// FAILURE
|
|
556
|
-
self.onError(
|
|
557
|
-
message:
|
|
558
|
-
"Command failed with state \(String(describing: FFmpegKitConfig.sessionState(toString: state ?? .failed))) and rc \(String(describing: returnCode)).\(String(describing: session?.getFailStackTrace()))",
|
|
559
|
-
code: .trimmingFailed)
|
|
560
|
-
if self.closeWhenFinish {
|
|
561
|
-
self.closeEditor()
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
},
|
|
566
|
-
withLogCallback: { log in
|
|
567
|
-
guard let log = log else { return }
|
|
568
|
-
|
|
569
|
-
print("FFmpeg process started with log " + (log.getMessage()))
|
|
570
|
-
|
|
571
|
-
let eventPayload: [String: Any] = [
|
|
572
|
-
"level": log.getLevel(),
|
|
573
|
-
"message": log.getMessage() ?? "",
|
|
574
|
-
"sessionId": log.getSessionId(),
|
|
575
|
-
]
|
|
576
|
-
self.emitEventToJS("onLog", eventData: eventPayload)
|
|
577
|
-
|
|
578
|
-
},
|
|
579
|
-
withStatisticsCallback: { statistics in
|
|
580
|
-
guard let statistics = statistics else { return }
|
|
581
|
-
|
|
582
|
-
let timeInMilliseconds = statistics.getTime()
|
|
583
|
-
if timeInMilliseconds > 0 {
|
|
584
|
-
let completePercentage = timeInMilliseconds / (videoDuration * 1000) // from 0 -> 1
|
|
585
|
-
DispatchQueue.main.async {
|
|
586
|
-
progressAlert.setProgress(Float(completePercentage))
|
|
587
|
-
}
|
|
37
|
+
|
|
38
|
+
return Double(successCount)
|
|
588
39
|
}
|
|
589
|
-
|
|
590
|
-
let eventPayload: [String: Any] = [
|
|
591
|
-
"sessionId": statistics.getSessionId(),
|
|
592
|
-
"videoFrameNumber": statistics.getVideoFrameNumber(),
|
|
593
|
-
"videoFps": statistics.getVideoFps(),
|
|
594
|
-
"videoQuality": statistics.getVideoQuality(),
|
|
595
|
-
"size": statistics.getSize(),
|
|
596
|
-
"time": statistics.getTime(),
|
|
597
|
-
"bitrate": statistics.getBitrate(),
|
|
598
|
-
"speed": statistics.getSpeed(),
|
|
599
|
-
]
|
|
600
|
-
self.emitEventToJS("onStatistics", eventData: eventPayload)
|
|
601
|
-
})
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
func assetLoader(
|
|
605
|
-
_ loader: AssetLoader, didFailWithError error: any Error, forKey key: String
|
|
606
|
-
) {
|
|
607
|
-
let message = "Failed to load \(key): \(error.localizedDescription)"
|
|
608
|
-
print("Failed to load \(key)", message)
|
|
609
|
-
|
|
610
|
-
self.onError(message: message, code: .failToLoadMedia)
|
|
611
|
-
vc?.onAssetFailToLoad()
|
|
612
|
-
|
|
613
|
-
if alertOnFailToLoad {
|
|
614
|
-
let dialogMessage = UIAlertController(
|
|
615
|
-
title: alertOnFailTitle, message: alertOnFailMessage,
|
|
616
|
-
preferredStyle: .alert)
|
|
617
|
-
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
618
|
-
|
|
619
|
-
// Create Cancel button with action handlder
|
|
620
|
-
let ok = UIAlertAction(title: alertOnFailCloseText, style: .default)
|
|
621
|
-
|
|
622
|
-
//Add OK and Cancel button to an Alert object
|
|
623
|
-
dialogMessage.addAction(ok)
|
|
624
|
-
|
|
625
|
-
// Present alert message to user
|
|
626
|
-
if let root = RCTPresentedViewController() {
|
|
627
|
-
root.present(dialogMessage, animated: true, completion: nil)
|
|
628
|
-
}
|
|
629
40
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
let eventPayload: [String: Any] = [
|
|
638
|
-
"duration": loader.asset!.duration.seconds * 1000
|
|
639
|
-
]
|
|
640
|
-
self.emitEventToJS("onLoad", eventData: eventPayload)
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
private func saveFileToFilesApp(fileURL: URL) {
|
|
644
|
-
DispatchQueue.main.async {
|
|
645
|
-
let documentPicker = UIDocumentPickerViewController(
|
|
646
|
-
url: fileURL, in: .exportToService)
|
|
647
|
-
documentPicker.delegate = self
|
|
648
|
-
documentPicker.modalPresentationStyle = .formSheet
|
|
649
|
-
if let root = RCTPresentedViewController() {
|
|
650
|
-
root.present(documentPicker, animated: true, completion: nil)
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
private func shareFile(fileURL: URL) {
|
|
656
|
-
DispatchQueue.main.async {
|
|
657
|
-
// Create an instance of UIActivityViewController
|
|
658
|
-
let activityViewController = UIActivityViewController(
|
|
659
|
-
activityItems: [fileURL], applicationActivities: nil)
|
|
660
|
-
|
|
661
|
-
activityViewController.completionWithItemsHandler = {
|
|
662
|
-
activityType, completed, returnedItems, error in
|
|
663
|
-
|
|
664
|
-
if let error = error {
|
|
665
|
-
let message = "Sharing error: \(error.localizedDescription)"
|
|
666
|
-
print(message)
|
|
667
|
-
self.onError(message: message, code: .failToShare)
|
|
668
|
-
|
|
669
|
-
if self.removeAfterFailedToShare {
|
|
670
|
-
let _ = self.deleteFile(url: self.outputFile!)
|
|
671
|
-
}
|
|
672
|
-
return
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
if completed {
|
|
676
|
-
print("User completed the sharing activity")
|
|
677
|
-
if self.removeAfterShared {
|
|
678
|
-
let _ = self.deleteFile(url: self.outputFile!)
|
|
679
|
-
}
|
|
680
|
-
} else {
|
|
681
|
-
print("User cancelled or failed to complete the sharing activity")
|
|
682
|
-
if self.removeAfterFailedToShare {
|
|
683
|
-
let _ = self.deleteFile(url: self.outputFile!)
|
|
684
|
-
}
|
|
41
|
+
|
|
42
|
+
public func deleteFile(filePath: String) throws -> Promise<Bool> {
|
|
43
|
+
return Promise.async {
|
|
44
|
+
// This runs on a separate Thread, and can use `await` syntax!
|
|
45
|
+
let state = self.impl.deleteFile(url: URL(string: filePath)!)
|
|
46
|
+
return state == 0
|
|
685
47
|
}
|
|
686
|
-
|
|
687
|
-
self.closeEditor()
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Present the share sheet
|
|
692
|
-
if let root = RCTPresentedViewController() {
|
|
693
|
-
root.present(activityViewController, animated: true, completion: nil)
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
func documentPicker(
|
|
700
|
-
_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]
|
|
701
|
-
) {
|
|
702
|
-
if removeAfterSavedToDocuments {
|
|
703
|
-
let _ = deleteFile(url: outputFile!)
|
|
704
|
-
}
|
|
705
|
-
closeEditor()
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
|
|
709
|
-
{
|
|
710
|
-
if removeAfterFailedToSaveDocuments {
|
|
711
|
-
let _ = deleteFile(url: outputFile!)
|
|
712
48
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
@objc(closeEditor:withRejecter:)
|
|
717
|
-
func closeEditor(
|
|
718
|
-
resolve: @escaping RCTPromiseResolveBlock,
|
|
719
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
720
|
-
) {
|
|
721
|
-
closeEditor()
|
|
722
|
-
resolve(true)
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
private func closeEditor() {
|
|
726
|
-
guard let vc = vc else { return }
|
|
727
|
-
// some how in case we trim a very short video the view controller is still visible after first .dismiss call
|
|
728
|
-
// even the file is successfully saved
|
|
729
|
-
// that's why we need a small delay here to ensure vc will be dismissed
|
|
730
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
731
|
-
vc.dismiss(
|
|
732
|
-
animated: true,
|
|
733
|
-
completion: {
|
|
734
|
-
self.emitEventToJS("onHide", eventData: nil)
|
|
735
|
-
self.isShowing = false
|
|
736
|
-
})
|
|
49
|
+
|
|
50
|
+
public func closeEditor(onComplete: @escaping () -> Void) throws {
|
|
51
|
+
impl.closeEditor(onComplete)
|
|
737
52
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
reject: @escaping RCTPromiseRejectBlock
|
|
744
|
-
) {
|
|
745
|
-
let fileURL = URL(string: uri)!
|
|
746
|
-
checkFileValidity(url: fileURL) { isValid, fileType, duration in
|
|
747
|
-
if isValid {
|
|
748
|
-
print("Valid \(fileType) file with duration: \(duration) milliseconds")
|
|
749
|
-
} else {
|
|
750
|
-
print("Invalid file")
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
let payload: [String: Any] = [
|
|
754
|
-
"isValid": isValid,
|
|
755
|
-
"fileType": fileType,
|
|
756
|
-
"duration": duration,
|
|
757
|
-
]
|
|
758
|
-
resolve(payload)
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
private func onError(message: String, code: ErrorCode) {
|
|
764
|
-
let eventPayload: [String: String] = [
|
|
765
|
-
"message": message,
|
|
766
|
-
"errorCode": code.rawValue,
|
|
767
|
-
]
|
|
768
|
-
self.emitEventToJS("onError", eventData: eventPayload)
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
private func checkFileValidity(
|
|
772
|
-
url: URL, completion: @escaping (Bool, String, Double) -> Void
|
|
773
|
-
) {
|
|
774
|
-
let asset = AVAsset(url: url)
|
|
775
|
-
|
|
776
|
-
// Load the duration and tracks asynchronously
|
|
777
|
-
asset.loadValuesAsynchronously(forKeys: ["duration", "tracks"]) {
|
|
778
|
-
var error: NSError? = nil
|
|
779
|
-
|
|
780
|
-
// Check if the duration and tracks are loaded
|
|
781
|
-
let durationStatus = asset.statusOfValue(
|
|
782
|
-
forKey: "duration", error: &error)
|
|
783
|
-
let tracksStatus = asset.statusOfValue(forKey: "tracks", error: &error)
|
|
784
|
-
|
|
785
|
-
// Ensure both properties are loaded successfully
|
|
786
|
-
guard durationStatus == .loaded, tracksStatus == .loaded, error == nil
|
|
787
|
-
else {
|
|
788
|
-
DispatchQueue.main.async {
|
|
789
|
-
completion(false, "unknown", -1)
|
|
53
|
+
|
|
54
|
+
public func isValidFile(url: String) throws -> Promise<FileValidationResult> {
|
|
55
|
+
return Promise.async {
|
|
56
|
+
// This runs on a separate Thread, and can use```` `await` syntax!
|
|
57
|
+
return await self.impl.isValidFile(uri: url)
|
|
790
58
|
}
|
|
791
|
-
return
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// Check if the asset contains any video or audio tracks
|
|
795
|
-
let videoTracks = asset.tracks(withMediaType: .video)
|
|
796
|
-
let audioTracks = asset.tracks(withMediaType: .audio)
|
|
797
|
-
|
|
798
|
-
let isValid = !videoTracks.isEmpty || !audioTracks.isEmpty
|
|
799
|
-
let fileType: String
|
|
800
|
-
if !videoTracks.isEmpty {
|
|
801
|
-
fileType = "video"
|
|
802
|
-
} else if !audioTracks.isEmpty {
|
|
803
|
-
fileType = "audio"
|
|
804
|
-
} else {
|
|
805
|
-
fileType = "unknown"
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
let duration = CMTimeGetSeconds(asset.duration) * 1000
|
|
809
|
-
|
|
810
|
-
DispatchQueue.main.async {
|
|
811
|
-
completion(isValid, fileType, isValid ? duration.rounded() : -1)
|
|
812
|
-
}
|
|
813
59
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
let directory = url.deletingLastPathComponent()
|
|
821
|
-
|
|
822
|
-
// Get the file extension
|
|
823
|
-
let fileExtension = url.pathExtension
|
|
824
|
-
|
|
825
|
-
// Create the new file URL with the new name and the same extension
|
|
826
|
-
let newFileURL = directory.appendingPathComponent(newName)
|
|
827
|
-
.appendingPathExtension(fileExtension)
|
|
828
|
-
|
|
829
|
-
// Check if a file with the new name already exists
|
|
830
|
-
if fileManager.fileExists(atPath: newFileURL.path) {
|
|
831
|
-
do {
|
|
832
|
-
// If the file exists, remove it first to avoid conflicts
|
|
833
|
-
try fileManager.removeItem(at: newFileURL)
|
|
834
|
-
} catch {
|
|
835
|
-
print("Error removing existing file: \(error)")
|
|
836
|
-
return nil
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
do {
|
|
841
|
-
// Rename (move) the file
|
|
842
|
-
try fileManager.moveItem(at: url, to: newFileURL)
|
|
843
|
-
print("File renamed successfully to \(newFileURL.absoluteString)")
|
|
844
|
-
return newFileURL
|
|
845
|
-
} catch {
|
|
846
|
-
print("Error renaming file: \(error)")
|
|
847
|
-
return nil
|
|
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
|
+
}
|
|
848
66
|
}
|
|
849
|
-
}
|
|
850
67
|
}
|