react-native-video-trim 2.0.0 → 2.2.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.
Files changed (31) hide show
  1. package/README.md +168 -33
  2. package/android/src/main/AndroidManifest.xml +13 -0
  3. package/android/src/main/java/com/videotrim/VideoTrimModule.java +282 -75
  4. package/android/src/main/java/com/videotrim/enums/ErrorCode.java +10 -0
  5. package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +4 -1
  6. package/android/src/main/java/com/videotrim/utils/MediaMetadataUtil.java +75 -0
  7. package/android/src/main/java/com/videotrim/utils/StorageUtil.java +2 -2
  8. package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +26 -16
  9. package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +310 -81
  10. package/android/src/main/res/drawable/airpodsmax.xml +19 -0
  11. package/android/src/main/res/drawable/exclamationmark_triangle_fill.xml +15 -0
  12. package/android/src/main/res/drawable/thumb_container_bg.xml +8 -0
  13. package/android/src/main/res/layout/video_trimmer_view.xml +71 -4
  14. package/android/src/main/res/xml/file_paths.xml +5 -0
  15. package/ios/AssetLoader.swift +99 -0
  16. package/ios/ErrorCode.swift +16 -0
  17. package/ios/ProgressAlertController.swift +100 -0
  18. package/ios/VideoTrim.mm +4 -2
  19. package/ios/VideoTrim.swift +472 -177
  20. package/ios/VideoTrimmer.swift +16 -10
  21. package/ios/VideoTrimmerViewController.swift +191 -22
  22. package/lib/commonjs/index.js +25 -55
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/module/index.js +24 -55
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/typescript/index.d.ts +215 -9
  27. package/lib/typescript/index.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/index.tsx +229 -66
  30. package/android/src/main/java/iknow/android/utils/BuildConfig.java +0 -18
  31. package/android/src/main/java/iknow/android/utils/DateUtil.java +0 -64
@@ -36,7 +36,7 @@ import AVFoundation
36
36
  // TODO: migrate all to AutoLayout
37
37
  private let thumbView: VideoTrimmerThumb = {
38
38
  let view = VideoTrimmerThumb()
39
- view.accessibilityIdentifier = "wrapperView"
39
+ view.accessibilityIdentifier = "thumbView"
40
40
  return view
41
41
  }()
42
42
 
@@ -59,7 +59,7 @@ import AVFoundation
59
59
  let view = UIView()
60
60
  view.accessibilityIdentifier = "thumbnailClipView"
61
61
  view.translatesAutoresizingMaskIntoConstraints = false
62
-
62
+
63
63
  return view
64
64
  }()
65
65
 
@@ -152,6 +152,7 @@ import AVFoundation
152
152
  // a clip cannot be trimmed shorter than this duration
153
153
  var minimumDuration: CMTime = CMTime(seconds: 1, preferredTimescale: 600)
154
154
  var maximumDuration: CMTime = .positiveInfinity
155
+ var enableHapticFeedback = true
155
156
 
156
157
  // the available range of the asset.
157
158
  // Will be set to the full duration of the asset when assigning a new asset
@@ -517,7 +518,9 @@ import AVFoundation
517
518
  isZoomedIn = true
518
519
  animateChanges()
519
520
 
520
- UISelectionFeedbackGenerator().selectionChanged()
521
+ if enableHapticFeedback {
522
+ UISelectionFeedbackGenerator().selectionChanged()
523
+ }
521
524
  }
522
525
 
523
526
  private func animateChanges() {
@@ -530,12 +533,13 @@ import AVFoundation
530
533
  }
531
534
 
532
535
  private func startPanning() {
533
- UISelectionFeedbackGenerator().selectionChanged()
534
-
535
536
  didClampWhilePanning = false
536
537
 
537
- impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
538
- impactFeedbackGenerator?.prepare()
538
+ if enableHapticFeedback {
539
+ UISelectionFeedbackGenerator().selectionChanged()
540
+ impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
541
+ impactFeedbackGenerator?.prepare()
542
+ }
539
543
 
540
544
  UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
541
545
  self.updateProgressIndicator()
@@ -612,10 +616,12 @@ import AVFoundation
612
616
  }
613
617
  switch sender.state {
614
618
  case .began:
619
+ if enableHapticFeedback {
620
+ UISelectionFeedbackGenerator().selectionChanged()
621
+ impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
622
+ impactFeedbackGenerator?.prepare()
623
+ }
615
624
 
616
- UISelectionFeedbackGenerator().selectionChanged()
617
- impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
618
- impactFeedbackGenerator?.prepare()
619
625
  didClampWhilePanning = false
620
626
 
621
627
  isScrubbing = true
@@ -22,13 +22,23 @@ extension CMTime {
22
22
 
23
23
  @available(iOS 13.0, *)
24
24
  class VideoTrimmerViewController: UIViewController {
25
- var asset: AVAsset!
26
- var maximumDuration: Int?
27
- var minimumDuration: Int?
28
- var cancelBtnText = "Cancel"
29
- var saveButtonText = "Save"
25
+ var asset: AVAsset? {
26
+ didSet {
27
+ if let _ = asset {
28
+ setupVideoTrimmer()
29
+ setupPlayerController()
30
+ setupTimeObserver()
31
+ updateLabels()
32
+ }
33
+ }
34
+ }
35
+ private var maximumDuration: Int?
36
+ private var minimumDuration: Int?
37
+ private var cancelButtonText = "Cancel"
38
+ private var saveButtonText = "Save"
30
39
  var cancelBtnClicked: (() -> Void)?
31
40
  var saveBtnClicked: ((CMTimeRange) -> Void)?
41
+ private var enableHapticFeedback = true
32
42
 
33
43
  private let playerController = AVPlayerViewController()
34
44
  private var trimmer: VideoTrimmer!
@@ -39,16 +49,50 @@ class VideoTrimmerViewController: UIViewController {
39
49
  private var btnStackView: UIStackView!
40
50
  private var cancelBtn: UIButton!
41
51
  private var playBtn: UIButton!
52
+ private let loadingIndicator = UIActivityIndicatorView()
42
53
  private var saveBtn: UIButton!
43
54
  private let playIcon = UIImage(systemName: "play.fill")
44
55
  private let pauseIcon = UIImage(systemName: "pause.fill")
56
+ private let audioBannerView = UIImage(systemName: "airpodsmax")
45
57
  private var player: AVPlayer! { playerController.player }
46
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?
47
65
 
48
66
  var isSeekInProgress: Bool = false // Marker
49
67
  private var chaseTime = CMTime.zero
50
68
  private var preferredFrameRate: Float = 23.98
51
69
 
70
+ public func onAssetFailToLoad() {
71
+ loadingIndicator.stopAnimating()
72
+ btnStackView.removeArrangedSubview(loadingIndicator)
73
+ loadingIndicator.removeFromSuperview()
74
+
75
+ let imageViewContainer = UIView()
76
+ let imageView = UIImageView(image: UIImage(systemName: "exclamationmark.triangle.fill"))
77
+ imageView.tintColor = .systemYellow
78
+ imageView.translatesAutoresizingMaskIntoConstraints = false
79
+
80
+ imageViewContainer.addSubview(imageView)
81
+ NSLayoutConstraint.activate([
82
+ imageView.widthAnchor.constraint(equalToConstant: 36),
83
+ imageView.heightAnchor.constraint(equalToConstant: 36),
84
+ imageView.centerXAnchor.constraint(equalTo: imageViewContainer.centerXAnchor),
85
+ imageView.centerYAnchor.constraint(equalTo: imageViewContainer.centerYAnchor)
86
+ ])
87
+ imageViewContainer.alpha = 0
88
+
89
+ btnStackView.insertArrangedSubview(imageViewContainer, at: 1)
90
+
91
+ UIView.animate(withDuration: 0.25, animations: {
92
+ imageViewContainer.alpha = 1
93
+ })
94
+ }
95
+
52
96
  // MARK: - Input
53
97
  @objc private func didBeginTrimmingFromStart(_ sender: VideoTrimmer) {
54
98
  handleBeforeProgressChange()
@@ -107,7 +151,7 @@ class VideoTrimmerViewController: UIViewController {
107
151
  private func handleTrimmingEnd(_ start: Bool) {
108
152
  self.trimmer.progress = start ? trimmer.selectedRange.start : trimmer.selectedRange.end
109
153
  updateLabels()
110
- player.seek(to: trimmer.progress, toleranceBefore: .zero, toleranceAfter: .zero)
154
+ seek(to: trimmer.progress)
111
155
  }
112
156
 
113
157
  // MARK: - UIViewController
@@ -117,17 +161,18 @@ class VideoTrimmerViewController: UIViewController {
117
161
  setupView()
118
162
  setupButtons()
119
163
  setupTimeLabels()
120
- setupVideoTrimmer()
121
- setupPlayerController()
122
- setupTimeObserver()
123
-
124
- updateLabels()
125
164
  }
126
165
 
127
166
  override func viewWillDisappear(_ animated: Bool) {
128
167
  super.viewWillDisappear(animated)
129
168
 
169
+ // if asset has been initialized
170
+ guard let _ = asset else { return }
130
171
  player.pause()
172
+
173
+ // Clean up the observer
174
+ player.removeObserver(self, forKeyPath: "status")
175
+
131
176
  if let token = timeObserverToken {
132
177
  player.removeTimeObserver(token)
133
178
  timeObserverToken = nil
@@ -139,13 +184,18 @@ class VideoTrimmerViewController: UIViewController {
139
184
  playerController.dismiss(animated: false, completion: nil)
140
185
  }
141
186
 
187
+ public func pausePlayer() {
188
+ player.pause()
189
+ setPlayBtnIcon()
190
+ }
191
+
142
192
  @objc private func togglePlay(sender: UIButton) {
143
193
  if player.timeControlStatus == .playing {
144
194
  player.pause()
145
195
  } else {
146
196
  if CMTimeCompare(trimmer.progress, trimmer.selectedRange.end) != -1 {
147
197
  trimmer.progress = trimmer.selectedRange.start
148
- player.seek(to: trimmer.progress, toleranceBefore: .zero, toleranceAfter: .zero)
198
+ self.seek(to: trimmer.progress)
149
199
  }
150
200
 
151
201
  player.play()
@@ -164,32 +214,74 @@ class VideoTrimmerViewController: UIViewController {
164
214
 
165
215
  // MARK: - Setup Methods
166
216
  private func setupView() {
167
- 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
+ }
168
248
  }
169
249
 
170
250
  private func setupButtons() {
171
- 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))
172
252
  playBtn = UIButton.createButton(image: playIcon, tintColor: .white, target: self, action: #selector(togglePlay(sender:)))
253
+ playBtn.alpha = 0
254
+ playBtn.isEnabled = false
255
+
173
256
  saveBtn = UIButton.createButton(title: saveButtonText, font: .systemFont(ofSize: 18), titleColor: .systemBlue, target: self, action: #selector(onSaveBtnClicked))
257
+ saveBtn.alpha = 0
258
+ saveBtn.isEnabled = false
174
259
 
175
- btnStackView = UIStackView(arrangedSubviews: [cancelBtn, playBtn, saveBtn])
260
+ btnStackView = UIStackView(arrangedSubviews: [cancelBtn, loadingIndicator, saveBtn])
176
261
  btnStackView.axis = .horizontal
177
- btnStackView.alignment = .fill
262
+ btnStackView.alignment = .center
178
263
  btnStackView.distribution = .fillEqually
179
264
  btnStackView.spacing = UIStackView.spacingUseSystem
180
- view.addSubview(btnStackView)
181
265
  btnStackView.translatesAutoresizingMaskIntoConstraints = false
266
+
267
+ view.addSubview(btnStackView)
268
+
182
269
  NSLayoutConstraint.activate([
183
270
  btnStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
184
271
  btnStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
185
272
  btnStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16)
186
273
  ])
274
+
275
+ loadingIndicator.startAnimating()
187
276
  }
188
277
 
189
278
  private func setupTimeLabels() {
190
279
  leadingTrimLabel = UILabel.createLabel(textAlignment: .left, textColor: .white)
280
+ leadingTrimLabel.text = "00:00.000"
191
281
  currentTimeLabel = UILabel.createLabel(textAlignment: .center, textColor: .white)
282
+ currentTimeLabel.text = "00:00.000"
192
283
  trailingTrimLabel = UILabel.createLabel(textAlignment: .right, textColor: .white)
284
+ trailingTrimLabel.text = "00:00.000"
193
285
 
194
286
  timingStackView = UIStackView(arrangedSubviews: [leadingTrimLabel, currentTimeLabel, trailingTrimLabel])
195
287
  timingStackView.axis = .horizontal
@@ -209,11 +301,12 @@ class VideoTrimmerViewController: UIViewController {
209
301
  trimmer = VideoTrimmer()
210
302
  trimmer.asset = asset
211
303
  trimmer.minimumDuration = CMTime(seconds: 1, preferredTimescale: 600)
304
+ trimmer.enableHapticFeedback = enableHapticFeedback
212
305
 
213
306
  if let maxDuration = maximumDuration {
214
307
  trimmer.maximumDuration = CMTime(seconds: max(1, Double(maxDuration)), preferredTimescale: 600)
215
- if trimmer.maximumDuration > asset.duration {
216
- trimmer.maximumDuration = asset.duration
308
+ if trimmer.maximumDuration > asset!.duration {
309
+ trimmer.maximumDuration = asset!.duration
217
310
  }
218
311
  trimmer.selectedRange = CMTimeRange(start: .zero, end: trimmer.maximumDuration)
219
312
  }
@@ -233,6 +326,7 @@ class VideoTrimmerViewController: UIViewController {
233
326
  trimmer.addTarget(self, action: #selector(didBeginTrimmingFromEnd(_:)), for: VideoTrimmer.didBeginTrimmingFromEnd)
234
327
  trimmer.addTarget(self, action: #selector(trailingGrabberChanged(_:)), for: VideoTrimmer.trailingGrabberChanged)
235
328
  trimmer.addTarget(self, action: #selector(didEndTrimmingFromEnd(_:)), for: VideoTrimmer.didEndTrimmingFromEnd)
329
+ trimmer.alpha = 0
236
330
  view.addSubview(trimmer)
237
331
  trimmer.translatesAutoresizingMaskIntoConstraints = false
238
332
  NSLayoutConstraint.activate([
@@ -241,6 +335,10 @@ class VideoTrimmerViewController: UIViewController {
241
335
  trimmer.bottomAnchor.constraint(equalTo: timingStackView.topAnchor, constant: -16),
242
336
  trimmer.heightAnchor.constraint(equalToConstant: 50)
243
337
  ])
338
+
339
+ UIView.animate(withDuration: 0.25, animations: {
340
+ self.trimmer.alpha = 1
341
+ })
244
342
  }
245
343
 
246
344
  private func setupPlayerController() {
@@ -249,7 +347,10 @@ class VideoTrimmerViewController: UIViewController {
249
347
  playerController.allowsVideoFrameAnalysis = false
250
348
  }
251
349
  playerController.player = AVPlayer()
252
- player.replaceCurrentItem(with: AVPlayerItem(asset: asset))
350
+ player.replaceCurrentItem(with: AVPlayerItem(asset: asset!))
351
+
352
+ // Add observer for player status
353
+ player.addObserver(self, forKeyPath: "status", options: [.new, .initial], context: nil)
253
354
 
254
355
  try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
255
356
  addChild(playerController)
@@ -258,7 +359,7 @@ class VideoTrimmerViewController: UIViewController {
258
359
  NSLayoutConstraint.activate([
259
360
  playerController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
260
361
  playerController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
261
- playerController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
362
+ playerController.view.topAnchor.constraint(equalTo: headerView != nil ? headerView!.bottomAnchor : view.safeAreaLayoutGuide.topAnchor),
262
363
  playerController.view.bottomAnchor.constraint(equalTo: trimmer.topAnchor, constant: -16)
263
364
  ])
264
365
 
@@ -286,7 +387,7 @@ class VideoTrimmerViewController: UIViewController {
286
387
  if CMTimeCompare(self.trimmer.progress, trimmer.selectedRange.end) == 1 {
287
388
  player.pause()
288
389
  self.trimmer.progress = trimmer.selectedRange.end
289
- player.seek(to: trimmer.selectedRange.end, toleranceBefore: .zero, toleranceAfter: .zero)
390
+ self.seek(to: trimmer.selectedRange.end)
290
391
  }
291
392
 
292
393
  currentTimeLabel.text = trimmer.progress.displayString
@@ -333,6 +434,74 @@ class VideoTrimmerViewController: UIViewController {
333
434
  }
334
435
  }
335
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
+ }
336
505
  }
337
506
 
338
507
  private extension UIButton {
@@ -4,8 +4,9 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.cleanFiles = cleanFiles;
7
+ exports.closeEditor = closeEditor;
7
8
  exports.deleteFile = deleteFile;
8
- exports.isValidVideo = isValidVideo;
9
+ exports.isValidFile = isValidFile;
9
10
  exports.listFiles = listFiles;
10
11
  exports.showEditor = showEditor;
11
12
  var _reactNative = require("react-native");
@@ -25,63 +26,16 @@ const VideoTrim = _reactNative.NativeModules.VideoTrim ? _reactNative.NativeModu
25
26
  * @param {EditorConfig} config: editor configuration
26
27
  * @returns {void} A **Promise** which resolves `void`
27
28
  */
28
- async function showEditor(filePath) {
29
+ function showEditor(filePath) {
29
30
  let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
30
- if (!(filePath !== null && filePath !== void 0 && filePath.trim().length)) {
31
- throw new Error('File path cannot be empty!');
32
- }
33
31
  const {
34
- saveToPhoto = true
32
+ headerTextColor
35
33
  } = config;
36
- const outputPath = await VideoTrim.showEditor(filePath, config);
37
- if (_reactNative.Platform.OS === 'android') {
38
- if (saveToPhoto) {
39
- try {
40
- if (_reactNative.Platform.Version >= 33) {
41
- // since android 13 it's not needed to request permission for write storage: https://github.com/facebook/react-native/issues/36714#issuecomment-1491338276
42
- await VideoTrim.saveVideo(outputPath);
43
- if (config.removeAfterSavedToPhoto) {
44
- deleteFile(outputPath);
45
- }
46
- } else {
47
- const granted = await _reactNative.PermissionsAndroid.request(_reactNative.PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
48
- title: 'Video Trimmer Photos Access Required',
49
- message: 'Grant access to your Photos to write output Video',
50
- buttonNeutral: 'Ask Me Later',
51
- buttonNegative: 'Cancel',
52
- buttonPositive: 'OK'
53
- });
54
- if (granted === _reactNative.PermissionsAndroid.RESULTS.GRANTED) {
55
- await VideoTrim.saveVideo(outputPath);
56
- if (config.removeAfterSavedToPhoto) {
57
- deleteFile(outputPath);
58
- }
59
- } else {
60
- throw new Error('Photos Library permission denied');
61
- }
62
- }
63
- } catch (err) {
64
- throw err;
65
- } finally {
66
- VideoTrim.hideDialog();
67
- }
68
- } else {
69
- VideoTrim.hideDialog();
70
- }
71
- }
72
- }
73
-
74
- /**
75
- * Delete a file
76
- *
77
- * @param {string} filePath: absolute non-empty file path to check if editable
78
- * @returns {Promise} A **Promise** which resolves `true` if editable
79
- */
80
- function isValidVideo(filePath) {
81
- if (!(filePath !== null && filePath !== void 0 && filePath.trim().length)) {
82
- throw new Error('File path cannot be empty!');
83
- }
84
- return VideoTrim.isValidVideo(filePath);
34
+ const color = (0, _reactNative.processColor)(headerTextColor);
35
+ VideoTrim.showEditor(filePath, {
36
+ ...config,
37
+ headerTextColor: color
38
+ });
85
39
  }
86
40
 
87
41
  /**
@@ -114,4 +68,20 @@ function deleteFile(filePath) {
114
68
  }
115
69
  return VideoTrim.deleteFile(filePath);
116
70
  }
71
+
72
+ /**
73
+ * Close editor
74
+ */
75
+ function closeEditor() {
76
+ return VideoTrim.closeEditor();
77
+ }
78
+
79
+ /**
80
+ * Check if a file is valid audio or video file
81
+ *
82
+ * @returns {Promise} A **Promise** which resolves file info if successful
83
+ */
84
+ function isValidFile(url) {
85
+ return VideoTrim.isValidFile(url);
86
+ }
117
87
  //# sourceMappingURL=index.js.map
@@ -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","trim","saveToPhoto","outputPath","OS","Version","saveVideo","removeAfterSavedToPhoto","deleteFile","granted","PermissionsAndroid","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","err","hideDialog","isValidVideo","listFiles","cleanFiles"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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"}
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, PermissionsAndroid, 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: ''
@@ -15,63 +15,16 @@ const VideoTrim = NativeModules.VideoTrim ? NativeModules.VideoTrim : new Proxy(
15
15
  * @param {EditorConfig} config: editor configuration
16
16
  * @returns {void} A **Promise** which resolves `void`
17
17
  */
18
- export async function showEditor(filePath) {
18
+ export function showEditor(filePath) {
19
19
  let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
20
- if (!(filePath !== null && filePath !== void 0 && filePath.trim().length)) {
21
- throw new Error('File path cannot be empty!');
22
- }
23
20
  const {
24
- saveToPhoto = true
21
+ headerTextColor
25
22
  } = config;
26
- const outputPath = await VideoTrim.showEditor(filePath, config);
27
- if (Platform.OS === 'android') {
28
- if (saveToPhoto) {
29
- try {
30
- if (Platform.Version >= 33) {
31
- // since android 13 it's not needed to request permission for write storage: https://github.com/facebook/react-native/issues/36714#issuecomment-1491338276
32
- await VideoTrim.saveVideo(outputPath);
33
- if (config.removeAfterSavedToPhoto) {
34
- deleteFile(outputPath);
35
- }
36
- } else {
37
- const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
38
- title: 'Video Trimmer Photos Access Required',
39
- message: 'Grant access to your Photos to write output Video',
40
- buttonNeutral: 'Ask Me Later',
41
- buttonNegative: 'Cancel',
42
- buttonPositive: 'OK'
43
- });
44
- if (granted === PermissionsAndroid.RESULTS.GRANTED) {
45
- await VideoTrim.saveVideo(outputPath);
46
- if (config.removeAfterSavedToPhoto) {
47
- deleteFile(outputPath);
48
- }
49
- } else {
50
- throw new Error('Photos Library permission denied');
51
- }
52
- }
53
- } catch (err) {
54
- throw err;
55
- } finally {
56
- VideoTrim.hideDialog();
57
- }
58
- } else {
59
- VideoTrim.hideDialog();
60
- }
61
- }
62
- }
63
-
64
- /**
65
- * Delete a file
66
- *
67
- * @param {string} filePath: absolute non-empty file path to check if editable
68
- * @returns {Promise} A **Promise** which resolves `true` if editable
69
- */
70
- export function isValidVideo(filePath) {
71
- if (!(filePath !== null && filePath !== void 0 && filePath.trim().length)) {
72
- throw new Error('File path cannot be empty!');
73
- }
74
- return VideoTrim.isValidVideo(filePath);
23
+ const color = processColor(headerTextColor);
24
+ VideoTrim.showEditor(filePath, {
25
+ ...config,
26
+ headerTextColor: color
27
+ });
75
28
  }
76
29
 
77
30
  /**
@@ -104,4 +57,20 @@ export function deleteFile(filePath) {
104
57
  }
105
58
  return VideoTrim.deleteFile(filePath);
106
59
  }
60
+
61
+ /**
62
+ * Close editor
63
+ */
64
+ export function closeEditor() {
65
+ return VideoTrim.closeEditor();
66
+ }
67
+
68
+ /**
69
+ * Check if a file is valid audio or video file
70
+ *
71
+ * @returns {Promise} A **Promise** which resolves file info if successful
72
+ */
73
+ export function isValidFile(url) {
74
+ return VideoTrim.isValidFile(url);
75
+ }
107
76
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["NativeModules","PermissionsAndroid","Platform","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","filePath","config","arguments","length","undefined","trim","saveToPhoto","outputPath","OS","Version","saveVideo","removeAfterSavedToPhoto","deleteFile","granted","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","err","hideDialog","isValidVideo","listFiles","cleanFiles"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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"}
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"}