react-native-video-trim 2.1.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -36
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +109 -21
- package/android/src/main/java/com/videotrim/enums/ErrorCode.java +1 -2
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +2 -0
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +12 -9
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +118 -58
- package/android/src/main/res/layout/video_trimmer_view.xml +20 -0
- package/ios/AssetLoader.swift +1 -1
- package/ios/ErrorCode.swift +1 -1
- package/ios/ProgressAlertController.swift +100 -0
- package/ios/VideoTrim.swift +130 -48
- package/ios/VideoTrimmerViewController.swift +130 -27
- package/lib/commonjs/index.js +8 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +9 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +168 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/index.tsx +176 -3
- package/ios/VideoTrim.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -4
package/ios/VideoTrim.swift
CHANGED
|
@@ -29,8 +29,6 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
29
29
|
private var saveDialogCancelText = "Close"
|
|
30
30
|
private var saveDialogConfirmText = "Proceed"
|
|
31
31
|
private var fullScreenModalIOS = false
|
|
32
|
-
private var maxDuration: Int?
|
|
33
|
-
private var minDuration: Int?
|
|
34
32
|
private var cancelButtonText = "Cancel"
|
|
35
33
|
private var saveButtonText = "Save"
|
|
36
34
|
private var vc: VideoTrimmerViewController?
|
|
@@ -39,8 +37,18 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
39
37
|
private var openDocumentsOnFinish = false
|
|
40
38
|
private var openShareSheetOnFinish = false
|
|
41
39
|
private var outputFile: URL?
|
|
42
|
-
private var
|
|
43
|
-
|
|
40
|
+
private var closeWhenFinish = true
|
|
41
|
+
private var enableCancelTrimming = true;
|
|
42
|
+
private var cancelTrimmingButtonText = "Cancel";
|
|
43
|
+
private var enableCancelTrimmingDialog = true;
|
|
44
|
+
private var cancelTrimmingDialogTitle = "Warning!";
|
|
45
|
+
private var cancelTrimmingDialogMessage = "Are you sure want to trimming?";
|
|
46
|
+
private var cancelTrimmingDialogCancelText = "Close";
|
|
47
|
+
private var cancelTrimmingDialogConfirmText = "Proceed";
|
|
48
|
+
private var alertOnFailToLoad = true;
|
|
49
|
+
private var alertOnFailTitle = "Error";
|
|
50
|
+
private var alertOnFailMessage = "Fail to load media. Possibly invalid file or no network connection";
|
|
51
|
+
private var alertOnFailCloseText = "Close";
|
|
44
52
|
|
|
45
53
|
@objc
|
|
46
54
|
static override func requiresMainQueueSetup() -> Bool {
|
|
@@ -90,15 +98,19 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
90
98
|
outputExt = config["outputExt"] as? String ?? "mp4"
|
|
91
99
|
openDocumentsOnFinish = config["openDocumentsOnFinish"] as? Bool ?? false
|
|
92
100
|
openShareSheetOnFinish = config["openShareSheetOnFinish"] as? Bool ?? false
|
|
93
|
-
enableHapticFeedback = config["enableHapticFeedback"] as? Bool ?? true
|
|
94
|
-
|
|
95
|
-
if let maxDuration = config["maxDuration"] as? Int {
|
|
96
|
-
self.maxDuration = maxDuration
|
|
97
|
-
}
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
closeWhenFinish = config["closeWhenFinish"] as? Bool ?? true
|
|
103
|
+
enableCancelTrimming = config["enableCancelTrimming"] as? Bool ?? true
|
|
104
|
+
cancelTrimmingButtonText = config["cancelTrimmingButtonText"] as? String ?? "Cancel"
|
|
105
|
+
enableCancelTrimmingDialog = config["enableCancelTrimmingDialog"] as? Bool ?? true
|
|
106
|
+
cancelTrimmingDialogTitle = config["cancelTrimmingDialogTitle"] as? String ?? "Warning!"
|
|
107
|
+
cancelTrimmingDialogMessage = config["cancelTrimmingDialogMessage"] as? String ?? "Are you sure want to cancel trimming?"
|
|
108
|
+
cancelTrimmingDialogCancelText = config["cancelTrimmingDialogCancelText"] as? String ?? "Close"
|
|
109
|
+
cancelTrimmingDialogConfirmText = config["cancelTrimmingDialogConfirmText"] as? String ?? "Proceed"
|
|
110
|
+
alertOnFailToLoad = config["alertOnFailToLoad"] as? Bool ?? true
|
|
111
|
+
alertOnFailTitle = config["alertOnFailTitle"] as? String ?? "Error"
|
|
112
|
+
alertOnFailMessage = config["alertOnFailMessage"] as? String ?? "Fail to load media. Possibly invalid file or no network connection"
|
|
113
|
+
alertOnFailCloseText = config["alertOnFailCloseText"] as? String ?? "Close"
|
|
102
114
|
|
|
103
115
|
if let cancelBtnText = config["cancelButtonText"] as? String, !cancelBtnText.isEmpty {
|
|
104
116
|
self.cancelButtonText = cancelBtnText
|
|
@@ -110,26 +122,17 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
110
122
|
|
|
111
123
|
let destPath = URL(string: uri)
|
|
112
124
|
guard let destPath = destPath else { return }
|
|
113
|
-
let assetLoader = AssetLoader()
|
|
114
|
-
assetLoader.delegate = self
|
|
115
|
-
assetLoader.loadAsset(url: destPath, isVideoType: isVideoType)
|
|
116
125
|
|
|
117
126
|
DispatchQueue.main.async {
|
|
118
127
|
self.vc = VideoTrimmerViewController()
|
|
119
128
|
|
|
120
129
|
guard let vc = self.vc else { return }
|
|
121
130
|
|
|
122
|
-
vc.
|
|
123
|
-
vc.minimumDuration = self.minDuration
|
|
124
|
-
vc.cancelBtnText = self.cancelButtonText
|
|
125
|
-
vc.saveButtonText = self.saveButtonText
|
|
126
|
-
vc.isVideoType = self.isVideoType
|
|
127
|
-
vc.enableHapticFeedback = self.enableHapticFeedback
|
|
131
|
+
vc.configure(config: config)
|
|
128
132
|
|
|
129
|
-
|
|
130
133
|
vc.cancelBtnClicked = {
|
|
131
134
|
if !self.enableCancelDialog {
|
|
132
|
-
self.emitEventToJS("
|
|
135
|
+
self.emitEventToJS("onCancel", eventData: nil)
|
|
133
136
|
|
|
134
137
|
vc.dismiss(animated: true, completion: {
|
|
135
138
|
self.emitEventToJS("onHide", eventData: nil)
|
|
@@ -140,10 +143,11 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
140
143
|
|
|
141
144
|
// Create Alert
|
|
142
145
|
let dialogMessage = UIAlertController(title: self.cancelDialogTitle, message: self.cancelDialogMessage, preferredStyle: .alert)
|
|
146
|
+
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
143
147
|
|
|
144
148
|
// Create OK button with action handler
|
|
145
149
|
let ok = UIAlertAction(title: self.cancelDialogConfirmText, style: .destructive, handler: { (action) -> Void in
|
|
146
|
-
self.emitEventToJS("
|
|
150
|
+
self.emitEventToJS("onCancel", eventData: nil)
|
|
147
151
|
|
|
148
152
|
vc.dismiss(animated: true, completion: {
|
|
149
153
|
self.emitEventToJS("onHide", eventData: nil)
|
|
@@ -172,7 +176,8 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
172
176
|
|
|
173
177
|
// Create Alert
|
|
174
178
|
let dialogMessage = UIAlertController(title: self.saveDialogTitle, message: self.saveDialogMessage, preferredStyle: .alert)
|
|
175
|
-
|
|
179
|
+
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
180
|
+
|
|
176
181
|
// Create OK button with action handler
|
|
177
182
|
let ok = UIAlertAction(title: self.saveDialogConfirmText, style: .default, handler: { (action) -> Void in
|
|
178
183
|
self.trim(viewController: vc,inputFile: destPath, videoDuration: vc.asset!.duration.seconds, startTime: selectedRange.start.seconds, endTime: selectedRange.end.seconds)
|
|
@@ -201,6 +206,12 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
201
206
|
root.present(vc, animated: true, completion: {
|
|
202
207
|
self.emitEventToJS("onShow", eventData: nil)
|
|
203
208
|
self.isShowing = true
|
|
209
|
+
|
|
210
|
+
// start loading asset after view is finished presenting
|
|
211
|
+
// otherwise it may run too fast for local file and autoplay looks weird
|
|
212
|
+
let assetLoader = AssetLoader()
|
|
213
|
+
assetLoader.delegate = self
|
|
214
|
+
assetLoader.loadAsset(url: destPath, isVideoType: self.isVideoType)
|
|
204
215
|
})
|
|
205
216
|
}
|
|
206
217
|
}
|
|
@@ -303,6 +314,8 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
303
314
|
}
|
|
304
315
|
|
|
305
316
|
private func trim(viewController: VideoTrimmerViewController, inputFile: URL, videoDuration: Double, startTime: Double, endTime: Double) {
|
|
317
|
+
vc?.pausePlayer()
|
|
318
|
+
|
|
306
319
|
let timestamp = Int(Date().timeIntervalSince1970)
|
|
307
320
|
let outputName = "\(FILE_PREFIX)_\(timestamp).\(outputExt)"
|
|
308
321
|
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
@@ -313,25 +326,60 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
313
326
|
formatter.timeZone = TimeZone(identifier: "UTC")
|
|
314
327
|
let dateTime = formatter.string(from: Date())
|
|
315
328
|
|
|
316
|
-
|
|
329
|
+
emitEventToJS("onStartTrimming", eventData: nil)
|
|
317
330
|
|
|
318
|
-
|
|
319
|
-
let
|
|
331
|
+
var ffmpegSession: FFmpegSession?
|
|
332
|
+
let progressAlert = ProgressAlertController()
|
|
333
|
+
progressAlert.modalPresentationStyle = .overFullScreen
|
|
334
|
+
progressAlert.modalTransitionStyle = .crossDissolve
|
|
335
|
+
progressAlert.setTitle(trimmingText)
|
|
320
336
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
337
|
+
if enableCancelTrimming {
|
|
338
|
+
progressAlert.setCancelTitle(cancelTrimmingButtonText)
|
|
339
|
+
progressAlert.showCancelBtn()
|
|
340
|
+
progressAlert.onDismiss = {
|
|
341
|
+
if self.enableCancelTrimmingDialog {
|
|
342
|
+
let dialogMessage = UIAlertController(title: self.cancelTrimmingDialogTitle, message: self.cancelTrimmingDialogMessage, preferredStyle: .alert)
|
|
343
|
+
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
344
|
+
|
|
345
|
+
// Create OK button with action handler
|
|
346
|
+
let ok = UIAlertAction(title: self.cancelDialogConfirmText, style: .destructive, handler: { (action) -> Void in
|
|
347
|
+
|
|
348
|
+
if let ffmpegSession = ffmpegSession {
|
|
349
|
+
ffmpegSession.cancel()
|
|
350
|
+
} else {
|
|
351
|
+
self.emitEventToJS("onCancelTrimming", eventData: nil)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
progressAlert.dismiss(animated: true)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
// Create Cancel button with action handlder
|
|
358
|
+
let cancel = UIAlertAction(title: self.cancelDialogCancelText, style: .cancel)
|
|
359
|
+
|
|
360
|
+
//Add OK and Cancel button to an Alert object
|
|
361
|
+
dialogMessage.addAction(ok)
|
|
362
|
+
dialogMessage.addAction(cancel)
|
|
363
|
+
|
|
364
|
+
// Present alert message to user
|
|
365
|
+
if let root = RCTPresentedViewController() {
|
|
366
|
+
root.present(dialogMessage, animated: true, completion: nil)
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
if let ffmpegSession = ffmpegSession {
|
|
370
|
+
ffmpegSession.cancel()
|
|
371
|
+
} else {
|
|
372
|
+
self.emitEventToJS("onCancelTrimming", eventData: nil)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
progressAlert.dismiss(animated: true)
|
|
376
|
+
}
|
|
327
377
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
])
|
|
334
|
-
})
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if let root = RCTPresentedViewController() {
|
|
382
|
+
root.present(progressAlert, animated: true, completion: nil)
|
|
335
383
|
}
|
|
336
384
|
|
|
337
385
|
let cmds = [
|
|
@@ -355,14 +403,16 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
355
403
|
]
|
|
356
404
|
self.emitEventToJS("onLog", eventData: eventPayload)
|
|
357
405
|
|
|
358
|
-
FFmpegKit.execute(withArgumentsAsync: cmds, withCompleteCallback: { session in
|
|
406
|
+
ffmpegSession = FFmpegKit.execute(withArgumentsAsync: cmds, withCompleteCallback: { session in
|
|
407
|
+
|
|
408
|
+
// always hide progressAlert
|
|
359
409
|
DispatchQueue.main.async {
|
|
360
|
-
|
|
410
|
+
progressAlert.dismiss(animated: true)
|
|
361
411
|
}
|
|
362
412
|
|
|
363
413
|
let state = session?.getState()
|
|
364
414
|
let returnCode = session?.getReturnCode()
|
|
365
|
-
|
|
415
|
+
|
|
366
416
|
if ReturnCode.isSuccess(returnCode) {
|
|
367
417
|
let eventPayload: [String: Any] = ["outputPath": self.outputFile!.absoluteString, "startTime": (startTime * 1000).rounded(), "endTime": (endTime * 1000).rounded(), "duration": (videoDuration * 1000).rounded()]
|
|
368
418
|
self.emitEventToJS("onFinishTrimming", eventData: eventPayload)
|
|
@@ -403,12 +453,23 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
403
453
|
// must return otherwise editor will close
|
|
404
454
|
return
|
|
405
455
|
}
|
|
456
|
+
|
|
457
|
+
if self.closeWhenFinish {
|
|
458
|
+
self.closeEditor()
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
} else if ReturnCode.isCancel(returnCode) {
|
|
462
|
+
// CANCEL
|
|
463
|
+
self.emitEventToJS("onCancelTrimming", eventData: nil)
|
|
406
464
|
} else {
|
|
407
|
-
//
|
|
465
|
+
// FAILURE
|
|
408
466
|
self.onError(message: "Command failed with state \(String(describing: FFmpegKitConfig.sessionState(toString: state ?? .failed))) and rc \(String(describing: returnCode)).\(String(describing: session?.getFailStackTrace()))", code: .trimmingFailed)
|
|
467
|
+
if self.closeWhenFinish {
|
|
468
|
+
self.closeEditor()
|
|
469
|
+
}
|
|
409
470
|
}
|
|
410
471
|
|
|
411
|
-
|
|
472
|
+
|
|
412
473
|
}, withLogCallback: { log in
|
|
413
474
|
guard let log = log else { return }
|
|
414
475
|
|
|
@@ -428,7 +489,7 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
428
489
|
if timeInMilliseconds > 0 {
|
|
429
490
|
let completePercentage = timeInMilliseconds / (videoDuration * 1000); // from 0 -> 1
|
|
430
491
|
DispatchQueue.main.async {
|
|
431
|
-
|
|
492
|
+
progressAlert.setProgress(Float(completePercentage))
|
|
432
493
|
}
|
|
433
494
|
}
|
|
434
495
|
|
|
@@ -450,14 +511,35 @@ class VideoTrim: RCTEventEmitter, AssetLoaderDelegate, UIDocumentPickerDelegate
|
|
|
450
511
|
let message = "Failed to load \(key): \(error.localizedDescription)"
|
|
451
512
|
print(message)
|
|
452
513
|
|
|
453
|
-
self.onError(message: message, code: .
|
|
514
|
+
self.onError(message: message, code: .failToLoadMedia)
|
|
454
515
|
vc?.onAssetFailToLoad()
|
|
516
|
+
|
|
517
|
+
if alertOnFailToLoad {
|
|
518
|
+
let dialogMessage = UIAlertController(title: alertOnFailTitle, message: alertOnFailMessage, preferredStyle: .alert)
|
|
519
|
+
dialogMessage.overrideUserInterfaceStyle = .dark
|
|
520
|
+
|
|
521
|
+
// Create Cancel button with action handlder
|
|
522
|
+
let ok = UIAlertAction(title: alertOnFailCloseText, style: .default)
|
|
523
|
+
|
|
524
|
+
//Add OK and Cancel button to an Alert object
|
|
525
|
+
dialogMessage.addAction(ok)
|
|
526
|
+
|
|
527
|
+
// Present alert message to user
|
|
528
|
+
if let root = RCTPresentedViewController() {
|
|
529
|
+
root.present(dialogMessage, animated: true, completion: nil)
|
|
530
|
+
}
|
|
531
|
+
}
|
|
455
532
|
}
|
|
456
533
|
|
|
457
534
|
func assetLoaderDidSucceed(_ loader: AssetLoader) {
|
|
458
535
|
print("Asset loaded successfully")
|
|
459
536
|
|
|
460
537
|
vc?.asset = loader.asset
|
|
538
|
+
|
|
539
|
+
let eventPayload: [String: Any] = [
|
|
540
|
+
"duration": loader.asset!.duration.seconds * 1000,
|
|
541
|
+
]
|
|
542
|
+
self.emitEventToJS("onLoad", eventData: eventPayload)
|
|
461
543
|
}
|
|
462
544
|
|
|
463
545
|
|
|
@@ -29,29 +29,16 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
29
29
|
setupPlayerController()
|
|
30
30
|
setupTimeObserver()
|
|
31
31
|
updateLabels()
|
|
32
|
-
|
|
33
|
-
loadingIndicator.stopAnimating()
|
|
34
|
-
btnStackView.removeArrangedSubview(loadingIndicator)
|
|
35
|
-
loadingIndicator.removeFromSuperview()
|
|
36
|
-
btnStackView.insertArrangedSubview(playBtn, at: 1)
|
|
37
|
-
|
|
38
|
-
UIView.animate(withDuration: 0.25, animations: {
|
|
39
|
-
self.playBtn.alpha = 1
|
|
40
|
-
self.playBtn.isEnabled = true
|
|
41
|
-
self.saveBtn.alpha = 1
|
|
42
|
-
self.saveBtn.isEnabled = true
|
|
43
|
-
})
|
|
44
32
|
}
|
|
45
33
|
}
|
|
46
34
|
}
|
|
47
|
-
var maximumDuration: Int?
|
|
48
|
-
var minimumDuration: Int?
|
|
49
|
-
var
|
|
50
|
-
var saveButtonText = "Save"
|
|
35
|
+
private var maximumDuration: Int?
|
|
36
|
+
private var minimumDuration: Int?
|
|
37
|
+
private var cancelButtonText = "Cancel"
|
|
38
|
+
private var saveButtonText = "Save"
|
|
51
39
|
var cancelBtnClicked: (() -> Void)?
|
|
52
40
|
var saveBtnClicked: ((CMTimeRange) -> Void)?
|
|
53
|
-
var
|
|
54
|
-
var enableHapticFeedback = true
|
|
41
|
+
private var enableHapticFeedback = true
|
|
55
42
|
|
|
56
43
|
private let playerController = AVPlayerViewController()
|
|
57
44
|
private var trimmer: VideoTrimmer!
|
|
@@ -62,13 +49,19 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
62
49
|
private var btnStackView: UIStackView!
|
|
63
50
|
private var cancelBtn: UIButton!
|
|
64
51
|
private var playBtn: UIButton!
|
|
65
|
-
private
|
|
52
|
+
private let loadingIndicator = UIActivityIndicatorView()
|
|
66
53
|
private var saveBtn: UIButton!
|
|
67
54
|
private let playIcon = UIImage(systemName: "play.fill")
|
|
68
55
|
private let pauseIcon = UIImage(systemName: "pause.fill")
|
|
69
56
|
private let audioBannerView = UIImage(systemName: "airpodsmax")
|
|
70
57
|
private var player: AVPlayer! { playerController.player }
|
|
71
58
|
private var timeObserverToken: Any?
|
|
59
|
+
private var autoplay = false
|
|
60
|
+
private var jumpToPositionOnLoad: Double = 0;
|
|
61
|
+
private var headerText: String?
|
|
62
|
+
private var headerTextSize = 16
|
|
63
|
+
private var headerTextColor: NSNumber?
|
|
64
|
+
private var headerView: UIView?
|
|
72
65
|
|
|
73
66
|
var isSeekInProgress: Bool = false // Marker
|
|
74
67
|
private var chaseTime = CMTime.zero
|
|
@@ -83,7 +76,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
83
76
|
let imageView = UIImageView(image: UIImage(systemName: "exclamationmark.triangle.fill"))
|
|
84
77
|
imageView.tintColor = .systemYellow
|
|
85
78
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
86
|
-
|
|
79
|
+
|
|
87
80
|
imageViewContainer.addSubview(imageView)
|
|
88
81
|
NSLayoutConstraint.activate([
|
|
89
82
|
imageView.widthAnchor.constraint(equalToConstant: 36),
|
|
@@ -94,7 +87,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
94
87
|
imageViewContainer.alpha = 0
|
|
95
88
|
|
|
96
89
|
btnStackView.insertArrangedSubview(imageViewContainer, at: 1)
|
|
97
|
-
|
|
90
|
+
|
|
98
91
|
UIView.animate(withDuration: 0.25, animations: {
|
|
99
92
|
imageViewContainer.alpha = 1
|
|
100
93
|
})
|
|
@@ -158,7 +151,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
158
151
|
private func handleTrimmingEnd(_ start: Bool) {
|
|
159
152
|
self.trimmer.progress = start ? trimmer.selectedRange.start : trimmer.selectedRange.end
|
|
160
153
|
updateLabels()
|
|
161
|
-
|
|
154
|
+
seek(to: trimmer.progress)
|
|
162
155
|
}
|
|
163
156
|
|
|
164
157
|
// MARK: - UIViewController
|
|
@@ -176,6 +169,10 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
176
169
|
// if asset has been initialized
|
|
177
170
|
guard let _ = asset else { return }
|
|
178
171
|
player.pause()
|
|
172
|
+
|
|
173
|
+
// Clean up the observer
|
|
174
|
+
player.removeObserver(self, forKeyPath: "status")
|
|
175
|
+
|
|
179
176
|
if let token = timeObserverToken {
|
|
180
177
|
player.removeTimeObserver(token)
|
|
181
178
|
timeObserverToken = nil
|
|
@@ -187,13 +184,18 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
187
184
|
playerController.dismiss(animated: false, completion: nil)
|
|
188
185
|
}
|
|
189
186
|
|
|
187
|
+
public func pausePlayer() {
|
|
188
|
+
player.pause()
|
|
189
|
+
setPlayBtnIcon()
|
|
190
|
+
}
|
|
191
|
+
|
|
190
192
|
@objc private func togglePlay(sender: UIButton) {
|
|
191
193
|
if player.timeControlStatus == .playing {
|
|
192
194
|
player.pause()
|
|
193
195
|
} else {
|
|
194
196
|
if CMTimeCompare(trimmer.progress, trimmer.selectedRange.end) != -1 {
|
|
195
197
|
trimmer.progress = trimmer.selectedRange.start
|
|
196
|
-
|
|
198
|
+
self.seek(to: trimmer.progress)
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
player.play()
|
|
@@ -212,11 +214,41 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
212
214
|
|
|
213
215
|
// MARK: - Setup Methods
|
|
214
216
|
private func setupView() {
|
|
215
|
-
|
|
217
|
+
self.overrideUserInterfaceStyle = .dark
|
|
218
|
+
view.backgroundColor = .black // need to have this otherwise during animation the background of this VC is still white in white theme
|
|
219
|
+
|
|
220
|
+
if let headerText = headerText {
|
|
221
|
+
headerView = UIView()
|
|
222
|
+
headerView!.translatesAutoresizingMaskIntoConstraints = false
|
|
223
|
+
view.addSubview(headerView!)
|
|
224
|
+
let headerTextView = UITextView()
|
|
225
|
+
headerTextView.text = headerText
|
|
226
|
+
headerTextView.textAlignment = .center
|
|
227
|
+
headerTextView.textColor = headerTextColor != nil ? RCTConvert.uiColor(headerTextColor) : .white
|
|
228
|
+
headerTextView.font = UIFont.systemFont(ofSize: CGFloat(headerTextSize)) // Set font size here
|
|
229
|
+
headerTextView.translatesAutoresizingMaskIntoConstraints = false
|
|
230
|
+
headerView!.addSubview(headerTextView)
|
|
231
|
+
|
|
232
|
+
NSLayoutConstraint.activate([
|
|
233
|
+
// HeaderView constraints
|
|
234
|
+
headerView!.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
235
|
+
headerView!.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
236
|
+
headerView!.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
237
|
+
headerView!.heightAnchor.constraint(greaterThanOrEqualToConstant: 50),
|
|
238
|
+
|
|
239
|
+
// HeaderText constraints
|
|
240
|
+
headerTextView.topAnchor.constraint(equalTo: headerView!.topAnchor),
|
|
241
|
+
headerTextView.bottomAnchor.constraint(equalTo: headerView!.bottomAnchor),
|
|
242
|
+
headerTextView.leadingAnchor.constraint(equalTo: headerView!.leadingAnchor),
|
|
243
|
+
headerTextView.trailingAnchor.constraint(equalTo: headerView!.trailingAnchor),
|
|
244
|
+
])
|
|
245
|
+
|
|
246
|
+
view.layoutIfNeeded() // layout after activate constraints, otherwise headerView height = screen height, which leads to playerViewController is missing at runtime
|
|
247
|
+
}
|
|
216
248
|
}
|
|
217
249
|
|
|
218
250
|
private func setupButtons() {
|
|
219
|
-
cancelBtn = UIButton.createButton(title:
|
|
251
|
+
cancelBtn = UIButton.createButton(title: cancelButtonText, font: .systemFont(ofSize: 18), titleColor: .white, target: self, action: #selector(onCancelBtnClicked))
|
|
220
252
|
playBtn = UIButton.createButton(image: playIcon, tintColor: .white, target: self, action: #selector(togglePlay(sender:)))
|
|
221
253
|
playBtn.alpha = 0
|
|
222
254
|
playBtn.isEnabled = false
|
|
@@ -317,6 +349,9 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
317
349
|
playerController.player = AVPlayer()
|
|
318
350
|
player.replaceCurrentItem(with: AVPlayerItem(asset: asset!))
|
|
319
351
|
|
|
352
|
+
// Add observer for player status
|
|
353
|
+
player.addObserver(self, forKeyPath: "status", options: [.new, .initial], context: nil)
|
|
354
|
+
|
|
320
355
|
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
|
|
321
356
|
addChild(playerController)
|
|
322
357
|
view.addSubview(playerController.view)
|
|
@@ -324,7 +359,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
324
359
|
NSLayoutConstraint.activate([
|
|
325
360
|
playerController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
326
361
|
playerController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
327
|
-
playerController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
362
|
+
playerController.view.topAnchor.constraint(equalTo: headerView != nil ? headerView!.bottomAnchor : view.safeAreaLayoutGuide.topAnchor),
|
|
328
363
|
playerController.view.bottomAnchor.constraint(equalTo: trimmer.topAnchor, constant: -16)
|
|
329
364
|
])
|
|
330
365
|
|
|
@@ -352,7 +387,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
352
387
|
if CMTimeCompare(self.trimmer.progress, trimmer.selectedRange.end) == 1 {
|
|
353
388
|
player.pause()
|
|
354
389
|
self.trimmer.progress = trimmer.selectedRange.end
|
|
355
|
-
|
|
390
|
+
self.seek(to: trimmer.selectedRange.end)
|
|
356
391
|
}
|
|
357
392
|
|
|
358
393
|
currentTimeLabel.text = trimmer.progress.displayString
|
|
@@ -399,6 +434,74 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
399
434
|
}
|
|
400
435
|
}
|
|
401
436
|
}
|
|
437
|
+
|
|
438
|
+
public func configure(config: NSDictionary) {
|
|
439
|
+
if let maxDuration = config["maxDuration"] as? Int {
|
|
440
|
+
maximumDuration = maxDuration
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if let minDuration = config["minDuration"] as? Int {
|
|
444
|
+
minimumDuration = minDuration
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if let cancelButtonText = config["cancelButtonText"] as? String, !cancelButtonText.isEmpty {
|
|
448
|
+
self.cancelButtonText = cancelButtonText
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if let saveButtonText = config["saveButtonText"] as? String, !saveButtonText.isEmpty {
|
|
452
|
+
self.saveButtonText = saveButtonText
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if let jumpToPositionOnLoad = config["jumpToPositionOnLoad"] as? Int {
|
|
456
|
+
self.jumpToPositionOnLoad = Double(jumpToPositionOnLoad)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
enableHapticFeedback = config["enableHapticFeedback"] as? Bool ?? true
|
|
460
|
+
autoplay = config["autoplay"] as? Bool ?? false
|
|
461
|
+
|
|
462
|
+
if let jumpToPositionOnLoad = config["jumpToPositionOnLoad"] as? Int {
|
|
463
|
+
self.jumpToPositionOnLoad = Double(jumpToPositionOnLoad)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if let headerText = config["headerText"] as? String, !headerText.isEmpty {
|
|
467
|
+
self.headerText = headerText
|
|
468
|
+
|
|
469
|
+
headerTextSize = config["headerTextSize"] as? Int ?? 16
|
|
470
|
+
headerTextColor = config["headerTextColor"] as? NSNumber
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
|
475
|
+
if keyPath == "status" {
|
|
476
|
+
if player.status == .readyToPlay {
|
|
477
|
+
loadingIndicator.stopAnimating()
|
|
478
|
+
btnStackView.removeArrangedSubview(loadingIndicator)
|
|
479
|
+
loadingIndicator.removeFromSuperview()
|
|
480
|
+
btnStackView.insertArrangedSubview(playBtn, at: 1)
|
|
481
|
+
|
|
482
|
+
UIView.animate(withDuration: 0.25, animations: {
|
|
483
|
+
self.playBtn.alpha = 1
|
|
484
|
+
self.playBtn.isEnabled = true
|
|
485
|
+
self.saveBtn.alpha = 1
|
|
486
|
+
self.saveBtn.isEnabled = true
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
if jumpToPositionOnLoad > 0 {
|
|
490
|
+
let duration = (asset?.duration.seconds ?? 0) * 1000
|
|
491
|
+
let time = jumpToPositionOnLoad > duration ? duration : jumpToPositionOnLoad
|
|
492
|
+
let cmtime = CMTime(value: CMTimeValue(time), timescale: 1000)
|
|
493
|
+
|
|
494
|
+
self.seek(to: cmtime)
|
|
495
|
+
self.trimmer.progress = cmtime
|
|
496
|
+
self.currentTimeLabel.text = self.trimmer.progress.displayString
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if autoplay {
|
|
500
|
+
togglePlay(sender: playBtn)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
402
505
|
}
|
|
403
506
|
|
|
404
507
|
private extension UIButton {
|
package/lib/commonjs/index.js
CHANGED
|
@@ -28,7 +28,14 @@ const VideoTrim = _reactNative.NativeModules.VideoTrim ? _reactNative.NativeModu
|
|
|
28
28
|
*/
|
|
29
29
|
function showEditor(filePath) {
|
|
30
30
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
31
|
-
|
|
31
|
+
const {
|
|
32
|
+
headerTextColor
|
|
33
|
+
} = config;
|
|
34
|
+
const color = (0, _reactNative.processColor)(headerTextColor);
|
|
35
|
+
VideoTrim.showEditor(filePath, {
|
|
36
|
+
...config,
|
|
37
|
+
headerTextColor: color
|
|
38
|
+
});
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","VideoTrim","NativeModules","Proxy","get","Error","showEditor","filePath","config","arguments","length","undefined","listFiles","cleanFiles","deleteFile","trim","closeEditor","isValidFile","url"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;
|
|
1
|
+
{"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","VideoTrim","NativeModules","Proxy","get","Error","showEditor","filePath","config","arguments","length","undefined","headerTextColor","color","processColor","listFiles","cleanFiles","deleteFile","trim","closeEditor","isValidFile","url"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAOA,MAAMC,aAAa,GAChB,kFAAiF,GAClFC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,SAAS,GAAGC,0BAAa,CAACD,SAAS,GACrCC,0BAAa,CAACD,SAAS,GACvB,IAAIE,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAiOL;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAASU,UAAUA,CAACC,QAAgB,EAAmC;EAAA,IAAjCC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EACpE,MAAM;IAAEG;EAAgB,CAAC,GAAGJ,MAAM;EAClC,MAAMK,KAAK,GAAG,IAAAC,yBAAY,EAACF,eAAe,CAAC;EAC3CX,SAAS,CAACK,UAAU,CAACC,QAAQ,EAAE;IAAE,GAAGC,MAAM;IAAEI,eAAe,EAAEC;EAAM,CAAC,CAAC;AACvE;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASE,SAASA,CAAA,EAAsB;EAC7C,OAAOd,SAAS,CAACc,SAAS,CAAC,CAAC;AAC9B;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,UAAUA,CAAA,EAAoB;EAC5C,OAAOf,SAAS,CAACe,UAAU,CAAC,CAAC;AAC/B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,UAAUA,CAACV,QAAgB,EAAoB;EAC7D,IAAI,EAACA,QAAQ,aAARA,QAAQ,eAARA,QAAQ,CAAEW,IAAI,CAAC,CAAC,CAACR,MAAM,GAAE;IAC5B,MAAM,IAAIL,KAAK,CAAC,4BAA4B,CAAC;EAC/C;EACA,OAAOJ,SAAS,CAACgB,UAAU,CAACV,QAAQ,CAAC;AACvC;;AAEA;AACA;AACA;AACO,SAASY,WAAWA,CAAA,EAAS;EAClC,OAAOlB,SAAS,CAACkB,WAAW,CAAC,CAAC;AAChC;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,WAAWA,CAACC,GAAW,EAAoB;EACzD,OAAOpB,SAAS,CAACmB,WAAW,CAACC,GAAG,CAAC;AACnC"}
|
package/lib/module/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NativeModules, Platform } from 'react-native';
|
|
1
|
+
import { NativeModules, Platform, processColor } from 'react-native';
|
|
2
2
|
const LINKING_ERROR = `The package 'react-native-video-trim' doesn't seem to be linked. Make sure: \n\n` + Platform.select({
|
|
3
3
|
ios: "- You have run 'pod install'\n",
|
|
4
4
|
default: ''
|
|
@@ -17,7 +17,14 @@ const VideoTrim = NativeModules.VideoTrim ? NativeModules.VideoTrim : new Proxy(
|
|
|
17
17
|
*/
|
|
18
18
|
export function showEditor(filePath) {
|
|
19
19
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
20
|
-
|
|
20
|
+
const {
|
|
21
|
+
headerTextColor
|
|
22
|
+
} = config;
|
|
23
|
+
const color = processColor(headerTextColor);
|
|
24
|
+
VideoTrim.showEditor(filePath, {
|
|
25
|
+
...config,
|
|
26
|
+
headerTextColor: color
|
|
27
|
+
});
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
/**
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","Platform","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","filePath","config","arguments","length","undefined","listFiles","cleanFiles","deleteFile","trim","closeEditor","isValidFile","url"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"names":["NativeModules","Platform","processColor","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","filePath","config","arguments","length","undefined","headerTextColor","color","listFiles","cleanFiles","deleteFile","trim","closeEditor","isValidFile","url"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,SACEA,aAAa,EACbC,QAAQ,EACRC,YAAY,QAEP,cAAc;AAErB,MAAMC,aAAa,GAChB,kFAAiF,GAClFF,QAAQ,CAACG,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,SAAS,GAAGP,aAAa,CAACO,SAAS,GACrCP,aAAa,CAACO,SAAS,GACvB,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAiOL;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASQ,UAAUA,CAACC,QAAgB,EAAmC;EAAA,IAAjCC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EACpE,MAAM;IAAEG;EAAgB,CAAC,GAAGJ,MAAM;EAClC,MAAMK,KAAK,GAAGhB,YAAY,CAACe,eAAe,CAAC;EAC3CV,SAAS,CAACI,UAAU,CAACC,QAAQ,EAAE;IAAE,GAAGC,MAAM;IAAEI,eAAe,EAAEC;EAAM,CAAC,CAAC;AACvE;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,SAASA,CAAA,EAAsB;EAC7C,OAAOZ,SAAS,CAACY,SAAS,CAAC,CAAC;AAC9B;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAoB;EAC5C,OAAOb,SAAS,CAACa,UAAU,CAAC,CAAC;AAC/B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACT,QAAgB,EAAoB;EAC7D,IAAI,EAACA,QAAQ,aAARA,QAAQ,eAARA,QAAQ,CAAEU,IAAI,CAAC,CAAC,CAACP,MAAM,GAAE;IAC5B,MAAM,IAAIL,KAAK,CAAC,4BAA4B,CAAC;EAC/C;EACA,OAAOH,SAAS,CAACc,UAAU,CAACT,QAAQ,CAAC;AACvC;;AAEA;AACA;AACA;AACA,OAAO,SAASW,WAAWA,CAAA,EAAS;EAClC,OAAOhB,SAAS,CAACgB,WAAW,CAAC,CAAC;AAChC;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,WAAWA,CAACC,GAAW,EAAoB;EACzD,OAAOlB,SAAS,CAACiB,WAAW,CAACC,GAAG,CAAC;AACnC"}
|