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.
@@ -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 enableHapticFeedback = true
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
- if let minDuration = config["minDuration"] as? Int {
100
- self.minDuration = minDuration
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.maximumDuration = self.maxDuration
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("onCancelTrimming", eventData: nil)
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("onCancelTrimming", eventData: nil)
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
- self.emitEventToJS("onStartTrimming", eventData: nil)
329
+ emitEventToJS("onStartTrimming", eventData: nil)
317
330
 
318
- // Create Alert
319
- let progressDialog = UIAlertController(title: trimmingText, message: nil, preferredStyle: .alert)
331
+ var ffmpegSession: FFmpegSession?
332
+ let progressAlert = ProgressAlertController()
333
+ progressAlert.modalPresentationStyle = .overFullScreen
334
+ progressAlert.modalTransitionStyle = .crossDissolve
335
+ progressAlert.setTitle(trimmingText)
320
336
 
321
- // Present alert message to user
322
- let progressView = UIProgressView(frame: .zero)
323
- progressView.tintColor = .systemBlue
324
- if let root = RCTPresentedViewController() {
325
- root.present(progressDialog, animated: true, completion: {
326
- progressDialog.view.addSubview(progressView)
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
- progressView.translatesAutoresizingMaskIntoConstraints = false
329
- NSLayoutConstraint.activate([
330
- progressView.leadingAnchor.constraint(equalTo: progressDialog.view.leadingAnchor, constant: 8),
331
- progressView.trailingAnchor.constraint(equalTo: progressDialog.view.trailingAnchor, constant: -8),
332
- progressView.bottomAnchor.constraint(equalTo: progressDialog.view.bottomAnchor, constant: -8)
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
- progressDialog.dismiss(animated: true)
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
- // CANCEL + FAILURE
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
- self.closeEditor()
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
- progressView.setProgress(Float(completePercentage), animated: true)
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: .failToLoadVideo)
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 cancelBtnText = "Cancel"
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 isVideoType = true
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 var loadingIndicator = UIActivityIndicatorView()
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
- player.seek(to: trimmer.progress, toleranceBefore: .zero, toleranceAfter: .zero)
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
- player.seek(to: trimmer.progress, toleranceBefore: .zero, toleranceAfter: .zero)
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
- view.backgroundColor = .black
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: cancelBtnText, font: .systemFont(ofSize: 18), titleColor: .white, target: self, action: #selector(onCancelBtnClicked))
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
- player.seek(to: trimmer.selectedRange.end, toleranceBefore: .zero, toleranceAfter: .zero)
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 {
@@ -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
- VideoTrim.showEditor(filePath, config);
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;AAEA,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;AA2DL;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;EACpER,SAAS,CAACK,UAAU,CAACC,QAAQ,EAAEC,MAAM,CAAC;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASI,SAASA,CAAA,EAAsB;EAC7C,OAAOX,SAAS,CAACW,SAAS,CAAC,CAAC;AAC9B;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,UAAUA,CAAA,EAAoB;EAC5C,OAAOZ,SAAS,CAACY,UAAU,CAAC,CAAC;AAC/B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAASC,UAAUA,CAACP,QAAgB,EAAoB;EAC7D,IAAI,EAACA,QAAQ,aAARA,QAAQ,eAARA,QAAQ,CAAEQ,IAAI,CAAC,CAAC,CAACL,MAAM,GAAE;IAC5B,MAAM,IAAIL,KAAK,CAAC,4BAA4B,CAAC;EAC/C;EACA,OAAOJ,SAAS,CAACa,UAAU,CAACP,QAAQ,CAAC;AACvC;;AAEA;AACA;AACA;AACO,SAASS,WAAWA,CAAA,EAAS;EAClC,OAAOf,SAAS,CAACe,WAAW,CAAC,CAAC;AAChC;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASC,WAAWA,CAACC,GAAW,EAAoB;EACzD,OAAOjB,SAAS,CAACgB,WAAW,CAACC,GAAG,CAAC;AACnC"}
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"}
@@ -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
- VideoTrim.showEditor(filePath, config);
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
  /**
@@ -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,SAASA,aAAa,EAAEC,QAAQ,QAAQ,cAAc;AAEtD,MAAMC,aAAa,GAChB,kFAAiF,GAClFD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD,GACtD,+BAA+B;AAEjC,MAAMC,SAAS,GAAGN,aAAa,CAACM,SAAS,GACrCN,aAAa,CAACM,SAAS,GACvB,IAAIC,KAAK,CACP,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AA2DL;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;EACpEP,SAAS,CAACI,UAAU,CAACC,QAAQ,EAAEC,MAAM,CAAC;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASI,SAASA,CAAA,EAAsB;EAC7C,OAAOV,SAAS,CAACU,SAAS,CAAC,CAAC;AAC9B;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAAA,EAAoB;EAC5C,OAAOX,SAAS,CAACW,UAAU,CAAC,CAAC;AAC/B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACP,QAAgB,EAAoB;EAC7D,IAAI,EAACA,QAAQ,aAARA,QAAQ,eAARA,QAAQ,CAAEQ,IAAI,CAAC,CAAC,CAACL,MAAM,GAAE;IAC5B,MAAM,IAAIL,KAAK,CAAC,4BAA4B,CAAC;EAC/C;EACA,OAAOH,SAAS,CAACY,UAAU,CAACP,QAAQ,CAAC;AACvC;;AAEA;AACA;AACA;AACA,OAAO,SAASS,WAAWA,CAAA,EAAS;EAClC,OAAOd,SAAS,CAACc,WAAW,CAAC,CAAC;AAChC;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,WAAWA,CAACC,GAAW,EAAoB;EACzD,OAAOhB,SAAS,CAACe,WAAW,CAACC,GAAG,CAAC;AACnC"}
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"}