shakebug-react-native 2.4.9 → 2.4.11
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/README.md +0 -12
- package/VideoPlayer/PlayVideo.tsx +4 -46
- package/VideoPlayer/index.js +6 -43
- package/android/src/main/java/com/shakebug/BaseUtils.java +7 -19
- package/android/src/main/java/com/shakebug/DeviceUtil.java +34 -21
- package/android/src/main/java/com/shakebug/ShakebugPackage.java +3 -3
- package/android/src/main/java/com/shakebug/UnitConverter.java +8 -8
- package/android/src/main/java/com/shakebug/VideoPlayerModule.java +81 -0
- package/android/src/main/java/com/shakebug/VideoTrimModule.java +387 -0
- package/android/src/main/java/com/shakebug/VideoTrimUtil.java +133 -0
- package/android/src/main/java/com/shakebug/adapters/VideoTrimmerAdapter.java +53 -0
- package/android/src/main/java/com/shakebug/callback/SingleCallback.java +0 -6
- package/android/src/main/java/com/shakebug/thread/BackgroundExecutor.java +1 -93
- package/android/src/main/java/com/shakebug/thread/UiThreadExecutor.java +0 -37
- package/android/src/main/java/com/shakebug/utils/StorageUtil.java +135 -0
- package/android/src/main/java/com/shakebug/utils/VideoTrimmerUtil.java +70 -0
- package/android/src/main/java/com/shakebug/widgets/RangeSeekBarView.java +534 -0
- package/android/src/main/java/com/shakebug/widgets/SpacesItemDecoration2.java +1 -2
- package/android/src/main/java/com/shakebug/widgets/VideoPlayerView.java +317 -0
- package/android/src/main/java/com/shakebug/widgets/VideoTrimmerView.java +520 -0
- package/android/src/main/res/drawable/ic_volume_off.xml +10 -0
- package/android/src/main/res/drawable/ic_volume_on.xml +10 -0
- package/android/src/main/res/layout/{video_player_view.xml → shakebug_video_player_view.xml} +29 -9
- package/ios/TrimVideo/ShakebugSDKVideoTrim.swift +389 -0
- package/ios/TrimVideo/VideoTrim-Bridging-Header.h +10 -0
- package/ios/TrimVideo/VideoTrim.m +23 -0
- package/ios/TrimVideo/VideoTrimmer.swift +810 -0
- package/ios/TrimVideo/VideoTrimmerThumb.swift +119 -0
- package/ios/TrimVideo/VideoTrimmerViewController.swift +322 -0
- package/ios/VideoPlayer/VideoPlayerModule.m +23 -7
- package/package.json +1 -1
- package/src/APIs/AddIssue.js +7 -5
- package/src/APIs/AddReview.js +5 -2
- package/src/APIs/CustomUserId.js +5 -2
- package/src/APIs/EndSession.js +3 -2
- package/src/APIs/Event.js +5 -2
- package/src/APIs/FetchRemoteCodeAPI.js +5 -2
- package/src/APIs/MatchKey.js +3 -2
- package/src/APIs/StartSession.js +44 -23
- package/src/Componanats/LoadingModal.js +59 -0
- package/src/Componanats/ShakeFunction.js +32 -14
- package/src/Screens/AddDetailsScreen.js +28 -31
- package/src/Screens/FirstScreen.js +15 -18
- package/src/Screens/Shakebug.js +11 -6
- package/src/Screens/TrimVideoScreen.js +37 -22
- package/src/index.tsx +7 -6
- package/trimVideo/src/index.tsx +12 -48
- package/Volumes:.fileloc +0 -8
- package/shakebug-react-native-2.4.8.tgz +0 -0
- /package/android/src/main/res/layout/{video_thumb_item_layout.xml → shakebug_video_thumb_item_layout.xml} +0 -0
- /package/android/src/main/res/layout/{video_trimmer_view.xml → shakebug_video_trimmer_view.xml} +0 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
//
|
|
2
|
+
// VideoTrim.swift
|
|
3
|
+
// Shakebug
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import React
|
|
7
|
+
import Photos
|
|
8
|
+
import AVFoundation
|
|
9
|
+
|
|
10
|
+
@objc(ShakebugSDKVideoTrim)
|
|
11
|
+
class ShakebugSDKVideoTrim: RCTEventEmitter {
|
|
12
|
+
private let FILE_PREFIX = "trimmedVideo"
|
|
13
|
+
private var hasListeners = false
|
|
14
|
+
private var isShowing = false
|
|
15
|
+
|
|
16
|
+
private var saveToPhoto = true
|
|
17
|
+
private var removeAfterSavedToPhoto = false
|
|
18
|
+
private var trimmingText = "Trimming video..."
|
|
19
|
+
|
|
20
|
+
@objc
|
|
21
|
+
static override func requiresMainQueueSetup() -> Bool {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
override func supportedEvents() -> [String]! {
|
|
26
|
+
return ["ShakebugSDKVideoTrim", "VideoTrim"]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override func startObserving() {
|
|
30
|
+
hasListeners = true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override func stopObserving() {
|
|
34
|
+
hasListeners = false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@objc(isValidVideo:withResolver:withRejecter:)
|
|
38
|
+
func isValidVideo(uri: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
39
|
+
if let destPath = copyFileToDocumentDir(uri: uri) {
|
|
40
|
+
resolve(UIVideoEditorController.canEditVideo(atPath: destPath.path))
|
|
41
|
+
let _ = deleteFile(url: destPath)
|
|
42
|
+
} else {
|
|
43
|
+
resolve(false)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@objc(showEditor:withConfig:)
|
|
48
|
+
func showEditor(uri: String, config: NSDictionary){
|
|
49
|
+
if isShowing {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
saveToPhoto = config["saveToPhoto"] as? Bool ?? true
|
|
54
|
+
removeAfterSavedToPhoto = config["removeAfterSavedToPhoto"] as? Bool ?? false
|
|
55
|
+
|
|
56
|
+
let enableCancelDialog = config["enableCancelDialog"] as? Bool ?? true
|
|
57
|
+
let cancelDialogTitle = config["cancelDialogTitle"] as? String ?? "Warning!"
|
|
58
|
+
let cancelDialogMessage = config["cancelDialogMessage"] as? String ?? "Are you sure want to cancel?"
|
|
59
|
+
let cancelDialogCancelText = config["cancelDialogCancelText"] as? String ?? "Close"
|
|
60
|
+
let cancelDialogConfirmText = config["cancelDialogConfirmText"] as? String ?? "Proceed"
|
|
61
|
+
|
|
62
|
+
let enableSaveDialog = config["enableSaveDialog"] as? Bool ?? true
|
|
63
|
+
let saveDialogTitle = config["saveDialogTitle"] as? String ?? "Confirmation!"
|
|
64
|
+
let saveDialogMessage = config["saveDialogMessage"] as? String ?? "Are you sure want to save?"
|
|
65
|
+
let saveDialogCancelText = config["saveDialogCancelText"] as? String ?? "Close"
|
|
66
|
+
let saveDialogConfirmText = config["saveDialogConfirmText"] as? String ?? "Proceed"
|
|
67
|
+
trimmingText = config["trimmingText"] as? String ?? "Trimming video..."
|
|
68
|
+
let fullScreenModalIOS = config["fullScreenModalIOS"] as? Bool ?? false
|
|
69
|
+
|
|
70
|
+
DispatchQueue.main.async {
|
|
71
|
+
let presentTrimmer = { (destPath: URL) in
|
|
72
|
+
if UIVideoEditorController.canEditVideo(atPath: destPath.path) {
|
|
73
|
+
if #available(iOS 13.0, *) {
|
|
74
|
+
let vc = VideoTrimmerViewController()
|
|
75
|
+
vc.asset = AVURLAsset(url: destPath, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
|
|
76
|
+
|
|
77
|
+
if let maxDuration = config["maxDuration"] as? Int {
|
|
78
|
+
vc.maximumDuration = maxDuration
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if let minDuration = config["minDuration"] as? Int {
|
|
82
|
+
vc.minimumDuration = minDuration
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if let cancelBtnText = config["cancelButtonText"] as? String, !cancelBtnText.isEmpty {
|
|
86
|
+
vc.cancelBtnText = cancelBtnText
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if let saveButtonText = config["saveButtonText"] as? String, !saveButtonText.isEmpty {
|
|
90
|
+
vc.saveButtonText = saveButtonText
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
vc.cancelBtnClicked = {
|
|
94
|
+
if !enableCancelDialog {
|
|
95
|
+
let _ = self.deleteFile(url: destPath)
|
|
96
|
+
self.emitEventToJS("onCancel", eventData: nil)
|
|
97
|
+
vc.dismiss(animated: true, completion: {
|
|
98
|
+
self.emitEventToJS("onHide", eventData: nil)
|
|
99
|
+
self.isShowing = false
|
|
100
|
+
})
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let cancelDialog = UIAlertController(title: cancelDialogTitle, message: cancelDialogMessage, preferredStyle: .alert)
|
|
105
|
+
|
|
106
|
+
let ok = UIAlertAction(title: cancelDialogConfirmText, style: .default, handler: { (action) -> Void in
|
|
107
|
+
let _ = self.deleteFile(url: destPath)
|
|
108
|
+
self.emitEventToJS("onCancel", eventData: nil)
|
|
109
|
+
vc.dismiss(animated: true, completion: {
|
|
110
|
+
self.emitEventToJS("onHide", eventData: nil)
|
|
111
|
+
self.isShowing = false
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
let cancel = UIAlertAction(title: cancelDialogCancelText, style: .cancel)
|
|
116
|
+
|
|
117
|
+
cancelDialog.addAction(ok)
|
|
118
|
+
cancelDialog.addAction(cancel)
|
|
119
|
+
|
|
120
|
+
vc.present(cancelDialog, animated: true, completion: nil)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
vc.saveBtnClicked = { (selectedRange) in
|
|
124
|
+
if !enableSaveDialog {
|
|
125
|
+
self.trim(viewController: vc, inputFile: destPath, videoDuration: vc.asset.duration.seconds, startTime: selectedRange.start.seconds, endTime: selectedRange.end.seconds)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let saveDialog = UIAlertController(title: saveDialogTitle, message: saveDialogMessage, preferredStyle: .alert)
|
|
130
|
+
|
|
131
|
+
let ok = UIAlertAction(title: saveDialogConfirmText, style: .default, handler: { (action) -> Void in
|
|
132
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
|
133
|
+
self.trim(viewController: vc, inputFile: destPath, videoDuration: vc.asset.duration.seconds, startTime: selectedRange.start.seconds, endTime: selectedRange.end.seconds)
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
let cancel = UIAlertAction(title: saveDialogCancelText, style: .cancel)
|
|
138
|
+
|
|
139
|
+
saveDialog.addAction(ok)
|
|
140
|
+
saveDialog.addAction(cancel)
|
|
141
|
+
|
|
142
|
+
vc.present(saveDialog, animated: true, completion: nil)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
vc.isModalInPresentation = true
|
|
146
|
+
vc.modalPresentationStyle = fullScreenModalIOS ? .fullScreen : .popover
|
|
147
|
+
if let root = RCTPresentedViewController() {
|
|
148
|
+
root.present(vc, animated: true, completion: {
|
|
149
|
+
self.isShowing = true
|
|
150
|
+
self.emitEventToJS("onShow", eventData: nil)
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
let eventPayload: [String: Any] = ["message": "File is not a valid video"]
|
|
156
|
+
self.emitEventToJS("onError", eventData: eventPayload)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let dialogMessage = UIAlertController(title: "Loading video...", message: nil, preferredStyle: .alert)
|
|
161
|
+
let customVC = UIViewController()
|
|
162
|
+
customVC.view.backgroundColor = .clear
|
|
163
|
+
customVC.preferredContentSize = CGSize(width: 250, height: 50)
|
|
164
|
+
let indicator = UIActivityIndicatorView(style: .medium)
|
|
165
|
+
indicator.startAnimating()
|
|
166
|
+
indicator.translatesAutoresizingMaskIntoConstraints = false
|
|
167
|
+
customVC.view.addSubview(indicator)
|
|
168
|
+
NSLayoutConstraint.activate([
|
|
169
|
+
indicator.centerXAnchor.constraint(equalTo: customVC.view.centerXAnchor),
|
|
170
|
+
indicator.centerYAnchor.constraint(equalTo: customVC.view.centerYAnchor)
|
|
171
|
+
])
|
|
172
|
+
dialogMessage.setValue(customVC, forKey: "contentViewController")
|
|
173
|
+
if let root = RCTPresentedViewController() {
|
|
174
|
+
root.present(dialogMessage, animated: true) {
|
|
175
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
176
|
+
if let destPath = self.copyFileToDocumentDir(uri: uri) {
|
|
177
|
+
DispatchQueue.main.async {
|
|
178
|
+
dialogMessage.dismiss(animated: true) {
|
|
179
|
+
presentTrimmer(destPath)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
DispatchQueue.main.async {
|
|
184
|
+
dialogMessage.dismiss(animated: true) {
|
|
185
|
+
let eventPayload: [String: Any] = ["message": "File is invalid"]
|
|
186
|
+
self.emitEventToJS("onError", eventData: eventPayload)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
DispatchQueue.global(qos: .userInitiated).async {
|
|
194
|
+
if let destPath = self.copyFileToDocumentDir(uri: uri) {
|
|
195
|
+
DispatchQueue.main.async {
|
|
196
|
+
presentTrimmer(destPath)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private func copyFileToDocumentDir(uri: String) -> URL? {
|
|
205
|
+
var cleanUri = uri.replacingOccurrences(of: "file://", with: "")
|
|
206
|
+
if let decoded = cleanUri.removingPercentEncoding {
|
|
207
|
+
cleanUri = decoded
|
|
208
|
+
}
|
|
209
|
+
let fileURL = cleanUri.hasPrefix("/") ? URL(fileURLWithPath: cleanUri) : URL(string: uri)
|
|
210
|
+
guard let validURL = fileURL else { return nil }
|
|
211
|
+
|
|
212
|
+
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
213
|
+
let fileExtension = validURL.pathExtension.isEmpty ? "mp4" : validURL.pathExtension
|
|
214
|
+
let timestamp = Int(Date().timeIntervalSince1970)
|
|
215
|
+
let destinationURL = documentsDirectory.appendingPathComponent("\(FILE_PREFIX)_original_\(timestamp).\(fileExtension)")
|
|
216
|
+
|
|
217
|
+
do {
|
|
218
|
+
if FileManager.default.fileExists(atPath: destinationURL.path) {
|
|
219
|
+
try FileManager.default.removeItem(at: destinationURL)
|
|
220
|
+
}
|
|
221
|
+
try FileManager.default.copyItem(at: validURL, to: destinationURL)
|
|
222
|
+
return destinationURL
|
|
223
|
+
} catch {
|
|
224
|
+
print("Error while copying file to document directory \(error)")
|
|
225
|
+
return validURL
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private func emitEventToJS(_ eventName: String, eventData: [String: Any]?) {
|
|
230
|
+
if hasListeners {
|
|
231
|
+
var modifiedEventData = eventData ?? [:]
|
|
232
|
+
modifiedEventData["name"] = eventName
|
|
233
|
+
sendEvent(withName: "VideoTrim", body: modifiedEventData)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@objc(listFiles:withRejecter:)
|
|
238
|
+
func listFiles(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
239
|
+
let files = listFiles()
|
|
240
|
+
resolve(files.map{ $0.absoluteString })
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@objc(cleanFiles:withRejecter:)
|
|
244
|
+
func cleanFiles(resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
245
|
+
let files = listFiles()
|
|
246
|
+
var successCount = 0
|
|
247
|
+
for file in files {
|
|
248
|
+
let state = deleteFile(url: file)
|
|
249
|
+
if state == 0 {
|
|
250
|
+
successCount += 1
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
resolve(successCount)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@objc(deleteFile:withResolver:withRejecter:)
|
|
257
|
+
func deleteFile(uri: String, resolve: @escaping RCTPromiseResolveBlock,reject: @escaping RCTPromiseRejectBlock) -> Void {
|
|
258
|
+
var cleanUri = uri.replacingOccurrences(of: "file://", with: "")
|
|
259
|
+
if let decoded = cleanUri.removingPercentEncoding {
|
|
260
|
+
cleanUri = decoded
|
|
261
|
+
}
|
|
262
|
+
let fileURL = cleanUri.hasPrefix("/") ? URL(fileURLWithPath: cleanUri) : URL(string: uri)
|
|
263
|
+
if let url = fileURL {
|
|
264
|
+
let state = deleteFile(url: url)
|
|
265
|
+
resolve(state == 0)
|
|
266
|
+
} else {
|
|
267
|
+
resolve(false)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private func listFiles() -> [URL] {
|
|
272
|
+
var files: [URL] = []
|
|
273
|
+
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
274
|
+
do {
|
|
275
|
+
let directoryContents = try FileManager.default.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil)
|
|
276
|
+
for fileURL in directoryContents {
|
|
277
|
+
if fileURL.lastPathComponent.starts(with: FILE_PREFIX) {
|
|
278
|
+
files.append(fileURL)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} catch {
|
|
282
|
+
print("[listFiles] Error when retrieving files: \(error)")
|
|
283
|
+
}
|
|
284
|
+
return files
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private func deleteFile(url: URL) -> Int {
|
|
288
|
+
do {
|
|
289
|
+
if FileManager.default.fileExists(atPath: url.path) {
|
|
290
|
+
try FileManager.default.removeItem(at: url)
|
|
291
|
+
return 0
|
|
292
|
+
}
|
|
293
|
+
return 1
|
|
294
|
+
} catch {
|
|
295
|
+
print("[deleteFile] Error deleting files: \(error)")
|
|
296
|
+
return 2
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
@available(iOS 13.0, *)
|
|
301
|
+
private func trim(viewController: VideoTrimmerViewController, inputFile: URL, videoDuration: Double, startTime: Double, endTime: Double) {
|
|
302
|
+
let timestamp = Int(Date().timeIntervalSince1970)
|
|
303
|
+
let outputName = "\(FILE_PREFIX)_\(timestamp).mp4"
|
|
304
|
+
let outputURL = inputFile.deletingLastPathComponent().appendingPathComponent(outputName)
|
|
305
|
+
|
|
306
|
+
self.emitEventToJS("onStartTrimming", eventData: nil)
|
|
307
|
+
|
|
308
|
+
let dialogMessage = UIAlertController(title: trimmingText, message: nil, preferredStyle: .alert)
|
|
309
|
+
|
|
310
|
+
let customVC = UIViewController()
|
|
311
|
+
customVC.view.backgroundColor = .clear
|
|
312
|
+
customVC.preferredContentSize = CGSize(width: 250, height: 50)
|
|
313
|
+
|
|
314
|
+
let progressView = UIProgressView(frame: .zero)
|
|
315
|
+
progressView.tintColor = .systemBlue
|
|
316
|
+
customVC.view.addSubview(progressView)
|
|
317
|
+
progressView.translatesAutoresizingMaskIntoConstraints = false
|
|
318
|
+
NSLayoutConstraint.activate([
|
|
319
|
+
progressView.leadingAnchor.constraint(equalTo: customVC.view.leadingAnchor, constant: 16),
|
|
320
|
+
progressView.trailingAnchor.constraint(equalTo: customVC.view.trailingAnchor, constant: -16),
|
|
321
|
+
progressView.centerYAnchor.constraint(equalTo: customVC.view.centerYAnchor)
|
|
322
|
+
])
|
|
323
|
+
|
|
324
|
+
dialogMessage.setValue(customVC, forKey: "contentViewController")
|
|
325
|
+
|
|
326
|
+
viewController.present(dialogMessage, animated: true, completion: nil)
|
|
327
|
+
|
|
328
|
+
let asset = AVAsset(url: inputFile)
|
|
329
|
+
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
|
|
330
|
+
dialogMessage.dismiss(animated: true)
|
|
331
|
+
let eventPayload: [String: Any] = ["message": "Failed to create export session"]
|
|
332
|
+
self.emitEventToJS("onError", eventData: eventPayload)
|
|
333
|
+
return
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if FileManager.default.fileExists(atPath: outputURL.path) {
|
|
337
|
+
try? FileManager.default.removeItem(at: outputURL)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
exportSession.outputURL = outputURL
|
|
341
|
+
exportSession.outputFileType = .mp4
|
|
342
|
+
let startCMTime = CMTime(seconds: startTime, preferredTimescale: 600)
|
|
343
|
+
let durationCMTime = CMTime(seconds: max(0.1, (endTime - startTime)), preferredTimescale: 600)
|
|
344
|
+
exportSession.timeRange = CMTimeRange(start: startCMTime, duration: durationCMTime)
|
|
345
|
+
|
|
346
|
+
var timer: Timer?
|
|
347
|
+
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
|
|
348
|
+
DispatchQueue.main.async {
|
|
349
|
+
progressView.setProgress(exportSession.progress, animated: true)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
exportSession.exportAsynchronously {
|
|
354
|
+
DispatchQueue.main.async {
|
|
355
|
+
timer?.invalidate()
|
|
356
|
+
timer = nil
|
|
357
|
+
let _ = self.deleteFile(url: inputFile)
|
|
358
|
+
|
|
359
|
+
if exportSession.status == .completed {
|
|
360
|
+
let outputPath = outputURL.path
|
|
361
|
+
let eventPayload: [String: Any] = ["outputPath": outputPath]
|
|
362
|
+
self.emitEventToJS("onFinishTrimming", eventData: eventPayload)
|
|
363
|
+
|
|
364
|
+
if self.saveToPhoto {
|
|
365
|
+
PHPhotoLibrary.requestAuthorization { status in
|
|
366
|
+
guard status == .authorized else { return }
|
|
367
|
+
PHPhotoLibrary.shared().performChanges({
|
|
368
|
+
let request = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
|
|
369
|
+
request?.creationDate = Date()
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
let err = exportSession.error?.localizedDescription ?? "Export failed"
|
|
375
|
+
let eventPayload: [String: Any] = ["message": err]
|
|
376
|
+
self.emitEventToJS("onError", eventData: eventPayload)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
380
|
+
dialogMessage.dismiss(animated: false)
|
|
381
|
+
viewController.dismiss(animated: true, completion: {
|
|
382
|
+
self.emitEventToJS("onHide", eventData: nil)
|
|
383
|
+
self.isShowing = false
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// VideoTrim.m
|
|
3
|
+
// Shakebug
|
|
4
|
+
//
|
|
5
|
+
// Created by ideveloper2 on 26/04/24.
|
|
6
|
+
// Copyright © 2024 Facebook. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#import <React/RCTBridgeModule.h>
|
|
10
|
+
#import <React/RCTEventEmitter.h>
|
|
11
|
+
|
|
12
|
+
@interface RCT_EXTERN_MODULE(ShakebugSDKVideoTrim, RCTEventEmitter)
|
|
13
|
+
|
|
14
|
+
RCT_EXTERN_METHOD(showEditor:(NSString*)uri withConfig:(NSDictionary *)config)
|
|
15
|
+
RCT_EXTERN_METHOD(isValidVideo:(NSString*)uri withResolver:(RCTPromiseResolveBlock)resolve
|
|
16
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
17
|
+
RCT_EXTERN_METHOD(listFiles:(RCTPromiseResolveBlock)resolve
|
|
18
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
19
|
+
RCT_EXTERN_METHOD(cleanFiles:(RCTPromiseResolveBlock)resolve
|
|
20
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
21
|
+
RCT_EXTERN_METHOD(deleteFile:(NSString*)uri withResolver:(RCTPromiseResolveBlock)resolve
|
|
22
|
+
withRejecter:(RCTPromiseRejectBlock)reject)
|
|
23
|
+
@end
|