react-native-video-trim 1.0.10 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ //
2
+ // VideoTrimmerThumb.swift
3
+ // react-native-video-trim
4
+ //
5
+ // Created by Duc Trung Mai on 17/1/24.
6
+ //
7
+
8
+ import UIKit
9
+
10
+ @available(iOS 13.0, *)
11
+ class VideoTrimmerThumb: UIView {
12
+ var isActive = false
13
+
14
+ var leadingChevronImageView = UIImageView(image: UIImage(systemName: "chevron.compact.left"))
15
+ var trailingChevronView = UIImageView(image: UIImage(systemName: "chevron.compact.right"))
16
+
17
+ var wrapperView = UIView()
18
+ var leadingView = UIView()
19
+ var trailingView = UIView()
20
+ var topView = UIView()
21
+ var bottomView = UIView()
22
+
23
+ let leadingGrabber = UIControl()
24
+ let trailingGrabber = UIControl()
25
+
26
+ let chevronWidth = CGFloat(16)
27
+ let edgeHeight = CGFloat(4)
28
+
29
+ // MARK: - Input
30
+ @objc private func x(_ sender: Any) {
31
+
32
+ }
33
+
34
+ // MARK: - Private
35
+ private func updateColor() {
36
+ let color = UIColor.systemYellow
37
+ leadingView.backgroundColor = color
38
+ trailingView.backgroundColor = color
39
+ topView.backgroundColor = color
40
+ bottomView.backgroundColor = color
41
+ }
42
+
43
+ private func setup() {
44
+
45
+ leadingChevronImageView.contentMode = .scaleAspectFill
46
+ trailingChevronView.contentMode = .scaleAspectFill
47
+
48
+ leadingChevronImageView.tintColor = .white
49
+ trailingChevronView.tintColor = .white
50
+
51
+ leadingChevronImageView.tintAdjustmentMode = .normal
52
+ trailingChevronView.tintAdjustmentMode = .normal
53
+
54
+ leadingView.layer.cornerRadius = 6
55
+ leadingView.layer.cornerCurve = .continuous
56
+ leadingView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner]
57
+
58
+ trailingView.layer.cornerRadius = 6
59
+ trailingView.layer.cornerCurve = .continuous
60
+ trailingView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner]
61
+
62
+ leadingView.addSubview(leadingChevronImageView)
63
+ trailingView.addSubview(trailingChevronView)
64
+
65
+ // wrapperView.layer.shadowColor = UIColor.black.cgColor
66
+ // wrapperView.layer.shadowOffset = .zero
67
+ // wrapperView.layer.shadowRadius = 2
68
+ // wrapperView.layer.shadowOpacity = 0.25
69
+
70
+ wrapperView.addSubview(leadingView)
71
+ wrapperView.addSubview(trailingView)
72
+ wrapperView.addSubview(topView)
73
+ wrapperView.addSubview(bottomView)
74
+ addSubview(wrapperView)
75
+
76
+ wrapperView.addSubview(leadingGrabber)
77
+ wrapperView.addSubview(trailingGrabber)
78
+
79
+ updateColor()
80
+ }
81
+
82
+
83
+
84
+ // MARK: - UIView
85
+
86
+ override func layoutSubviews() {
87
+ super.layoutSubviews()
88
+
89
+ let size = bounds.size
90
+
91
+ wrapperView.frame = CGRect(origin: .zero, size: size)
92
+
93
+ leadingView.frame = CGRect(x: 0, y: 0, width: chevronWidth, height: bounds.height)
94
+ trailingView.frame = CGRect(x: bounds.width - chevronWidth, y: 0, width: chevronWidth, height: bounds.height)
95
+ topView.frame = CGRect(x: chevronWidth, y: 0, width: bounds.width - chevronWidth * 2, height: edgeHeight)
96
+ bottomView.frame = CGRect(x: chevronWidth, y: bounds.height - edgeHeight, width: bounds.width - chevronWidth * 2, height: edgeHeight)
97
+
98
+ let chevronHorizontalInset = CGFloat(2)
99
+ let chevronVerticalInset = CGFloat(8)
100
+ let chevronFrame = CGRect(x: chevronHorizontalInset, y: chevronVerticalInset, width: chevronWidth - chevronHorizontalInset * 2, height: size.height - chevronVerticalInset * 2)
101
+
102
+ leadingChevronImageView.frame = chevronFrame
103
+ trailingChevronView.frame = chevronFrame
104
+
105
+ leadingGrabber.frame = leadingView.frame
106
+ trailingGrabber.frame = trailingView.frame
107
+ }
108
+
109
+ override init(frame: CGRect) {
110
+ super.init(frame: frame)
111
+ setup()
112
+ }
113
+
114
+ required init?(coder: NSCoder) {
115
+ super.init(coder: coder)
116
+ setup()
117
+ }
118
+ }
119
+
@@ -0,0 +1,292 @@
1
+ //
2
+ // VideoTrimmerViewController.swift
3
+ // react-native-video-trim
4
+ //
5
+ // Created by Duc Trung Mai on 17/1/24.
6
+ //
7
+
8
+ import UIKit
9
+ import AVKit
10
+
11
+ extension CMTime {
12
+ var displayString: String {
13
+ let offset = TimeInterval(seconds)
14
+ let numberOfNanosecondsFloat = (offset - TimeInterval(Int(offset))) * 1000.0
15
+ let nanoseconds = Int(numberOfNanosecondsFloat)
16
+ let formatter = DateComponentsFormatter()
17
+ formatter.unitsStyle = .positional
18
+ formatter.zeroFormattingBehavior = .pad
19
+ formatter.allowedUnits = [.minute, .second]
20
+ return String(format: "%@.%03d", formatter.string(from: offset) ?? "00:00", nanoseconds)
21
+ }
22
+ }
23
+
24
+ extension AVAsset {
25
+ var fullRange: CMTimeRange {
26
+ return CMTimeRange(start: .zero, duration: duration)
27
+ }
28
+ func trimmedComposition(_ range: CMTimeRange) -> AVAsset {
29
+ guard CMTimeRangeEqual(fullRange, range) == false else {return self}
30
+
31
+ let composition = AVMutableComposition()
32
+ try? composition.insertTimeRange(range, of: self, at: .zero)
33
+
34
+ if let videoTrack = tracks(withMediaType: .video).first {
35
+ composition.tracks.forEach {$0.preferredTransform = videoTrack.preferredTransform}
36
+ }
37
+ return composition
38
+ }
39
+ }
40
+
41
+ @available(iOS 13.0, *)
42
+ class VideoTrimmerViewController: UIViewController {
43
+ var asset: AVAsset!
44
+ var maximumDuration: Int?
45
+ var cancelBtnText = "Cancel"
46
+ var saveButtonText = "Save"
47
+ var cancelBtnClicked: (() -> Void)?
48
+ var saveBtnClicked: ((CMTimeRange) -> Void)?
49
+
50
+ let playerController = AVPlayerViewController()
51
+ var trimmer: VideoTrimmer!
52
+ var timingStackView: UIStackView!
53
+ var leadingTrimLabel: UILabel!
54
+ var currentTimeLabel: UILabel!
55
+ var trailingTrimLabel: UILabel!
56
+
57
+ private var btnStackView: UIStackView!
58
+ private var cancelBtn: UIButton!
59
+ private var playBtn: UIButton!
60
+ private var saveBtn: UIButton!
61
+ private let playIcon = UIImage(systemName: "play.fill")
62
+ private let pauseIcon = UIImage(systemName: "pause.fill")
63
+
64
+ private var wasPlaying = false
65
+ private var player: AVPlayer! {playerController.player}
66
+ private var timeObserverToken: Any?
67
+
68
+
69
+ // MARK: - Input
70
+ @objc private func didBeginTrimming(_ sender: VideoTrimmer) {
71
+ updateLabels()
72
+
73
+ wasPlaying = (player.timeControlStatus != .paused)
74
+ player.pause()
75
+
76
+ updatePlayerAsset()
77
+ }
78
+
79
+ @objc private func didEndTrimming(_ sender: VideoTrimmer) {
80
+ updateLabels()
81
+
82
+ if wasPlaying == true {
83
+ player.play()
84
+ }
85
+
86
+ updatePlayerAsset()
87
+ }
88
+
89
+ @objc private func selectedRangeDidChanged(_ sender: VideoTrimmer) {
90
+ updateLabels()
91
+ }
92
+
93
+ @objc private func didBeginScrubbing(_ sender: VideoTrimmer) {
94
+ updateLabels()
95
+
96
+ wasPlaying = (player.timeControlStatus != .paused)
97
+ player.pause()
98
+ }
99
+
100
+ @objc private func didEndScrubbing(_ sender: VideoTrimmer) {
101
+ updateLabels()
102
+
103
+ if wasPlaying == true {
104
+ player.play()
105
+ }
106
+ }
107
+
108
+ @objc private func progressDidChanged(_ sender: VideoTrimmer) {
109
+ updateLabels()
110
+
111
+ let time = CMTimeSubtract(trimmer.progress, trimmer.selectedRange.start)
112
+ player.seek(to: time, toleranceBefore: .zero, toleranceAfter: .zero)
113
+ }
114
+
115
+ // MARK: - Private
116
+ private func updateLabels() {
117
+ leadingTrimLabel.text = trimmer.selectedRange.start.displayString
118
+ currentTimeLabel.text = trimmer.progress.displayString
119
+ trailingTrimLabel.text = trimmer.selectedRange.end.displayString
120
+ }
121
+
122
+ private func updatePlayerAsset() {
123
+ let outputRange = trimmer.trimmingState == .none ? trimmer.selectedRange : asset.fullRange
124
+ let trimmedAsset = asset.trimmedComposition(outputRange)
125
+ if trimmedAsset != player.currentItem?.asset {
126
+ player.replaceCurrentItem(with: AVPlayerItem(asset: trimmedAsset))
127
+ }
128
+ }
129
+
130
+ // MARK: - UIViewController
131
+ override func viewDidLoad() {
132
+ super.viewDidLoad()
133
+
134
+ view.backgroundColor = .black
135
+
136
+ // bottom action buttons
137
+ cancelBtn = UIButton(type: .system)
138
+ cancelBtn.setTitle(cancelBtnText, for: .normal)
139
+ cancelBtn.titleLabel?.font = .systemFont(ofSize: 18)
140
+ cancelBtn.setTitleColor(.white, for: .normal)
141
+ cancelBtn.addTarget(self, action: #selector(onCancelBtnClicked), for: .touchUpInside)
142
+
143
+
144
+ playBtn = UIButton(type: .system)
145
+ playBtn.setImage(playIcon, for: .normal)
146
+ playBtn.tintColor = .systemBlue
147
+ playBtn.addTarget(self, action: #selector(togglePlay(sender:)), for: .touchUpInside)
148
+
149
+ saveBtn = UIButton(type: .system)
150
+ saveBtn.setTitle(saveButtonText, for: .normal)
151
+ saveBtn.titleLabel?.font = .systemFont(ofSize: 18)
152
+ saveBtn.setTitleColor(.systemBlue, for: .normal)
153
+ saveBtn.addTarget(self, action: #selector(onSaveBtnClicked), for: .touchUpInside)
154
+
155
+ btnStackView = UIStackView(arrangedSubviews: [cancelBtn, playBtn, saveBtn])
156
+ btnStackView.axis = .horizontal
157
+ btnStackView.alignment = .fill
158
+ btnStackView.distribution = .fillEqually
159
+ btnStackView.spacing = UIStackView.spacingUseSystem
160
+ view.addSubview(btnStackView)
161
+ btnStackView.translatesAutoresizingMaskIntoConstraints = false
162
+ NSLayoutConstraint.activate([
163
+ btnStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
164
+ btnStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
165
+ btnStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16),
166
+ ])
167
+
168
+ // time labels
169
+ leadingTrimLabel = UILabel()
170
+ leadingTrimLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
171
+ leadingTrimLabel.textAlignment = .left
172
+ leadingTrimLabel.textColor = .white
173
+
174
+ currentTimeLabel = UILabel()
175
+ currentTimeLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
176
+ currentTimeLabel.textAlignment = .center
177
+ currentTimeLabel.textColor = .white
178
+
179
+ trailingTrimLabel = UILabel()
180
+ trailingTrimLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
181
+ trailingTrimLabel.textAlignment = .right
182
+ trailingTrimLabel.textColor = .white
183
+
184
+ timingStackView = UIStackView(arrangedSubviews: [leadingTrimLabel, currentTimeLabel, trailingTrimLabel])
185
+ timingStackView.axis = .horizontal
186
+ timingStackView.alignment = .fill
187
+ timingStackView.distribution = .fillEqually
188
+ timingStackView.spacing = UIStackView.spacingUseSystem
189
+ view.addSubview(timingStackView)
190
+ timingStackView.translatesAutoresizingMaskIntoConstraints = false
191
+ NSLayoutConstraint.activate([
192
+ timingStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
193
+ timingStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
194
+ timingStackView.bottomAnchor.constraint(equalTo: btnStackView.topAnchor, constant: -8),
195
+ ])
196
+
197
+ // THIS IS WHERE WE SETUP THE VIDEOTRIMMER:
198
+ trimmer = VideoTrimmer()
199
+ trimmer.asset = asset // this should happen before trimmer.selectedRange below otherwise its didSet will override
200
+ trimmer.minimumDuration = CMTime(seconds: 1, preferredTimescale: 600)
201
+
202
+ if maximumDuration != nil {
203
+ trimmer.maximumDuration = CMTime(seconds: max(1, Double(maximumDuration!)), preferredTimescale: 600) // minimum 1 second
204
+
205
+ // guard check to make sure max duration can only <= asset.duration
206
+ if CMTimeCompare(trimmer.maximumDuration, asset.duration) == 1 {
207
+ trimmer.maximumDuration = asset.duration
208
+ }
209
+
210
+ trimmer.selectedRange = CMTimeRange(start: .zero, end: trimmer.maximumDuration)
211
+ }
212
+
213
+ trimmer.addTarget(self, action: #selector(didBeginTrimming(_:)), for: VideoTrimmer.didBeginTrimming)
214
+ trimmer.addTarget(self, action: #selector(didEndTrimming(_:)), for: VideoTrimmer.didEndTrimming)
215
+ trimmer.addTarget(self, action: #selector(selectedRangeDidChanged(_:)), for: VideoTrimmer.selectedRangeChanged)
216
+ trimmer.addTarget(self, action: #selector(didBeginScrubbing(_:)), for: VideoTrimmer.didBeginScrubbing)
217
+ trimmer.addTarget(self, action: #selector(didEndScrubbing(_:)), for: VideoTrimmer.didEndScrubbing)
218
+ trimmer.addTarget(self, action: #selector(progressDidChanged(_:)), for: VideoTrimmer.progressChanged)
219
+ view.addSubview(trimmer)
220
+ trimmer.translatesAutoresizingMaskIntoConstraints = false
221
+ NSLayoutConstraint.activate([
222
+ trimmer.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
223
+ trimmer.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
224
+ trimmer.bottomAnchor.constraint(equalTo: timingStackView.topAnchor, constant: -16),
225
+ trimmer.heightAnchor.constraint(equalToConstant: 50),
226
+ ])
227
+
228
+ playerController.showsPlaybackControls = false // hide control buttons
229
+ if #available(iOS 16.0, *) {
230
+ playerController.allowsVideoFrameAnalysis = false // hide live text
231
+ }
232
+ playerController.player = AVPlayer()
233
+ try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: []) // this is to play audio even when device is in silent mode
234
+ addChild(playerController)
235
+ view.addSubview(playerController.view)
236
+ playerController.view.translatesAutoresizingMaskIntoConstraints = false
237
+ NSLayoutConstraint.activate([
238
+ playerController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
239
+ playerController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
240
+ playerController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
241
+ playerController.view.bottomAnchor.constraint(equalTo: trimmer.topAnchor, constant: -16)
242
+ ])
243
+
244
+ updatePlayerAsset()
245
+
246
+ timeObserverToken = player.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 30), queue: .main) { [weak self] time in
247
+ guard let self = self else {return}
248
+ // when we're not trimming, the players starting point is actual later than the trimmer,
249
+ // (because the vidoe has been trimmed), so we need to account for that.
250
+ // When we're trimming, we always show the full video
251
+ let finalTime = self.trimmer.trimmingState == .none ? CMTimeAdd(time, self.trimmer.selectedRange.start) : time
252
+ self.trimmer.progress = finalTime
253
+
254
+ if player.timeControlStatus == .playing {
255
+ playBtn.setImage(pauseIcon, for: .normal)
256
+ } else {
257
+ playBtn.setImage(playIcon, for: .normal)
258
+ }
259
+ }
260
+
261
+ updateLabels()
262
+ }
263
+
264
+ override func viewWillDisappear(_ animated: Bool) {
265
+ super.viewWillDisappear(animated)
266
+
267
+ player.pause()
268
+ if timeObserverToken != nil {
269
+ player.removeTimeObserver(timeObserverToken as Any)
270
+ timeObserverToken = nil
271
+ }
272
+ playerController.player = nil
273
+ playerController.dismiss(animated: false, completion: nil)
274
+ }
275
+
276
+ @objc private func togglePlay(sender: UIButton) {
277
+ if player.timeControlStatus == .playing {
278
+ player.pause()
279
+ } else {
280
+ player.play()
281
+ }
282
+
283
+ }
284
+
285
+ @objc func onSaveBtnClicked() {
286
+ saveBtnClicked?(trimmer.selectedRange)
287
+ }
288
+
289
+ @objc func onCancelBtnClicked() {
290
+ cancelBtnClicked?()
291
+ }
292
+ }
@@ -3,7 +3,10 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.cleanFiles = cleanFiles;
7
+ exports.deleteFile = deleteFile;
6
8
  exports.isValidVideo = isValidVideo;
9
+ exports.listFiles = listFiles;
7
10
  exports.showEditor = showEditor;
8
11
  var _reactNative = require("react-native");
9
12
  const LINKING_ERROR = `The package 'react-native-video-trim' doesn't seem to be linked. Make sure: \n\n` + _reactNative.Platform.select({
@@ -15,18 +18,31 @@ const VideoTrim = _reactNative.NativeModules.VideoTrim ? _reactNative.NativeModu
15
18
  throw new Error(LINKING_ERROR);
16
19
  }
17
20
  });
18
- async function showEditor(videoPath) {
21
+ /**
22
+ * Delete a file
23
+ *
24
+ * @param {string} videoPath: absolute non-empty file path to edit
25
+ * @param {EditorConfig} config: editor configuration
26
+ * @returns {void} A **Promise** which resolves `void`
27
+ */
28
+ async function showEditor(filePath) {
19
29
  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
+ }
20
33
  const {
21
34
  saveToPhoto = true
22
35
  } = config;
23
- const outputPath = await VideoTrim.showEditor(videoPath, config);
36
+ const outputPath = await VideoTrim.showEditor(filePath, config);
24
37
  if (_reactNative.Platform.OS === 'android') {
25
38
  if (saveToPhoto) {
26
39
  try {
27
40
  if (_reactNative.Platform.Version >= 33) {
28
41
  // since android 13 it's not needed to request permission for write storage: https://github.com/facebook/react-native/issues/36714#issuecomment-1491338276
29
42
  await VideoTrim.saveVideo(outputPath);
43
+ if (config.removeAfterSavedToPhoto) {
44
+ deleteFile(outputPath);
45
+ }
30
46
  } else {
31
47
  const granted = await _reactNative.PermissionsAndroid.request(_reactNative.PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
32
48
  title: 'Video Trimmer Photos Access Required',
@@ -37,6 +53,9 @@ async function showEditor(videoPath) {
37
53
  });
38
54
  if (granted === _reactNative.PermissionsAndroid.RESULTS.GRANTED) {
39
55
  await VideoTrim.saveVideo(outputPath);
56
+ if (config.removeAfterSavedToPhoto) {
57
+ deleteFile(outputPath);
58
+ }
40
59
  } else {
41
60
  throw new Error('Photos Library permission denied');
42
61
  }
@@ -51,7 +70,48 @@ async function showEditor(videoPath) {
51
70
  }
52
71
  }
53
72
  }
54
- function isValidVideo(videoPath) {
55
- return VideoTrim.isValidVideo(videoPath);
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);
85
+ }
86
+
87
+ /**
88
+ * Clean output files generated at all time
89
+ *
90
+ * @returns {Promise<string[]>} A **Promise** which resolves to array of files
91
+ */
92
+ function listFiles() {
93
+ return VideoTrim.listFiles();
94
+ }
95
+
96
+ /**
97
+ * Clean output files generated at all time
98
+ *
99
+ * @returns {Promise} A **Promise** which resolves to number of deleted files
100
+ */
101
+ function cleanFiles() {
102
+ return VideoTrim.cleanFiles();
103
+ }
104
+
105
+ /**
106
+ * Delete a file
107
+ *
108
+ * @param {string} filePath: absolute non-empty file path to delete
109
+ * @returns {Promise} A **Promise** which resolves `true` if successful
110
+ */
111
+ function deleteFile(filePath) {
112
+ if (!(filePath !== null && filePath !== void 0 && filePath.trim().length)) {
113
+ throw new Error('File path cannot be empty!');
114
+ }
115
+ return VideoTrim.deleteFile(filePath);
56
116
  }
57
117
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","VideoTrim","NativeModules","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","saveToPhoto","outputPath","OS","Version","saveVideo","granted","PermissionsAndroid","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","err","hideDialog","isValidVideo"],"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;AAUE,eAAeU,UAAUA,CAC9BC,SAAiB,EAEF;EAAA,IADfC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EAEzB,MAAM;IAAEG,WAAW,GAAG;EAAK,CAAC,GAAGJ,MAAM;EACrC,MAAMK,UAAU,GAAG,MAAMZ,SAAS,CAACK,UAAU,CAACC,SAAS,EAAEC,MAAM,CAAC;EAEhE,IAAIX,qBAAQ,CAACiB,EAAE,KAAK,SAAS,EAAE;IAC7B,IAAIF,WAAW,EAAE;MACf,IAAI;QACF,IAAIf,qBAAQ,CAACkB,OAAO,IAAI,EAAE,EAAE;UAC1B;UACA,MAAMd,SAAS,CAACe,SAAS,CAACH,UAAU,CAAC;QACvC,CAAC,MAAM;UACL,MAAMI,OAAO,GAAG,MAAMC,+BAAkB,CAACC,OAAO,CAC9CD,+BAAkB,CAACE,WAAW,CAACC,sBAAsB,EACrD;YACEC,KAAK,EAAE,sCAAsC;YAC7CC,OAAO,EAAE,mDAAmD;YAC5DC,aAAa,EAAE,cAAc;YAC7BC,cAAc,EAAE,QAAQ;YACxBC,cAAc,EAAE;UAClB,CACF,CAAC;UACD,IAAIT,OAAO,KAAKC,+BAAkB,CAACS,OAAO,CAACC,OAAO,EAAE;YAClD,MAAM3B,SAAS,CAACe,SAAS,CAACH,UAAU,CAAC;UACvC,CAAC,MAAM;YACL,MAAM,IAAIR,KAAK,CAAC,kCAAkC,CAAC;UACrD;QACF;MACF,CAAC,CAAC,OAAOwB,GAAG,EAAE;QACZ,MAAMA,GAAG;MACX,CAAC,SAAS;QACR5B,SAAS,CAAC6B,UAAU,CAAC,CAAC;MACxB;IACF,CAAC,MAAM;MACL7B,SAAS,CAAC6B,UAAU,CAAC,CAAC;IACxB;EACF;AACF;AAEO,SAASC,YAAYA,CAACxB,SAAiB,EAAoB;EAChE,OAAON,SAAS,CAAC8B,YAAY,CAACxB,SAAS,CAAC;AAC1C"}
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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"}
@@ -8,18 +8,31 @@ const VideoTrim = NativeModules.VideoTrim ? NativeModules.VideoTrim : new Proxy(
8
8
  throw new Error(LINKING_ERROR);
9
9
  }
10
10
  });
11
- export async function showEditor(videoPath) {
11
+ /**
12
+ * Delete a file
13
+ *
14
+ * @param {string} videoPath: absolute non-empty file path to edit
15
+ * @param {EditorConfig} config: editor configuration
16
+ * @returns {void} A **Promise** which resolves `void`
17
+ */
18
+ export async function showEditor(filePath) {
12
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
+ }
13
23
  const {
14
24
  saveToPhoto = true
15
25
  } = config;
16
- const outputPath = await VideoTrim.showEditor(videoPath, config);
26
+ const outputPath = await VideoTrim.showEditor(filePath, config);
17
27
  if (Platform.OS === 'android') {
18
28
  if (saveToPhoto) {
19
29
  try {
20
30
  if (Platform.Version >= 33) {
21
31
  // since android 13 it's not needed to request permission for write storage: https://github.com/facebook/react-native/issues/36714#issuecomment-1491338276
22
32
  await VideoTrim.saveVideo(outputPath);
33
+ if (config.removeAfterSavedToPhoto) {
34
+ deleteFile(outputPath);
35
+ }
23
36
  } else {
24
37
  const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, {
25
38
  title: 'Video Trimmer Photos Access Required',
@@ -30,6 +43,9 @@ export async function showEditor(videoPath) {
30
43
  });
31
44
  if (granted === PermissionsAndroid.RESULTS.GRANTED) {
32
45
  await VideoTrim.saveVideo(outputPath);
46
+ if (config.removeAfterSavedToPhoto) {
47
+ deleteFile(outputPath);
48
+ }
33
49
  } else {
34
50
  throw new Error('Photos Library permission denied');
35
51
  }
@@ -44,7 +60,48 @@ export async function showEditor(videoPath) {
44
60
  }
45
61
  }
46
62
  }
47
- export function isValidVideo(videoPath) {
48
- return VideoTrim.isValidVideo(videoPath);
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);
75
+ }
76
+
77
+ /**
78
+ * Clean output files generated at all time
79
+ *
80
+ * @returns {Promise<string[]>} A **Promise** which resolves to array of files
81
+ */
82
+ export function listFiles() {
83
+ return VideoTrim.listFiles();
84
+ }
85
+
86
+ /**
87
+ * Clean output files generated at all time
88
+ *
89
+ * @returns {Promise} A **Promise** which resolves to number of deleted files
90
+ */
91
+ export function cleanFiles() {
92
+ return VideoTrim.cleanFiles();
93
+ }
94
+
95
+ /**
96
+ * Delete a file
97
+ *
98
+ * @param {string} filePath: absolute non-empty file path to delete
99
+ * @returns {Promise} A **Promise** which resolves `true` if successful
100
+ */
101
+ export function deleteFile(filePath) {
102
+ if (!(filePath !== null && filePath !== void 0 && filePath.trim().length)) {
103
+ throw new Error('File path cannot be empty!');
104
+ }
105
+ return VideoTrim.deleteFile(filePath);
49
106
  }
50
107
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["NativeModules","PermissionsAndroid","Platform","LINKING_ERROR","select","ios","default","VideoTrim","Proxy","get","Error","showEditor","videoPath","config","arguments","length","undefined","saveToPhoto","outputPath","OS","Version","saveVideo","granted","request","PERMISSIONS","WRITE_EXTERNAL_STORAGE","title","message","buttonNeutral","buttonNegative","buttonPositive","RESULTS","GRANTED","err","hideDialog","isValidVideo"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"AAAA,SAASA,aAAa,EAAEC,kBAAkB,EAAEC,QAAQ,QAAQ,cAAc;AAE1E,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,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;AAUL,OAAO,eAAeQ,UAAUA,CAC9BC,SAAiB,EAEF;EAAA,IADfC,MAAoB,GAAAC,SAAA,CAAAC,MAAA,QAAAD,SAAA,QAAAE,SAAA,GAAAF,SAAA,MAAG,CAAC,CAAC;EAEzB,MAAM;IAAEG,WAAW,GAAG;EAAK,CAAC,GAAGJ,MAAM;EACrC,MAAMK,UAAU,GAAG,MAAMX,SAAS,CAACI,UAAU,CAACC,SAAS,EAAEC,MAAM,CAAC;EAEhE,IAAIX,QAAQ,CAACiB,EAAE,KAAK,SAAS,EAAE;IAC7B,IAAIF,WAAW,EAAE;MACf,IAAI;QACF,IAAIf,QAAQ,CAACkB,OAAO,IAAI,EAAE,EAAE;UAC1B;UACA,MAAMb,SAAS,CAACc,SAAS,CAACH,UAAU,CAAC;QACvC,CAAC,MAAM;UACL,MAAMI,OAAO,GAAG,MAAMrB,kBAAkB,CAACsB,OAAO,CAC9CtB,kBAAkB,CAACuB,WAAW,CAACC,sBAAsB,EACrD;YACEC,KAAK,EAAE,sCAAsC;YAC7CC,OAAO,EAAE,mDAAmD;YAC5DC,aAAa,EAAE,cAAc;YAC7BC,cAAc,EAAE,QAAQ;YACxBC,cAAc,EAAE;UAClB,CACF,CAAC;UACD,IAAIR,OAAO,KAAKrB,kBAAkB,CAAC8B,OAAO,CAACC,OAAO,EAAE;YAClD,MAAMzB,SAAS,CAACc,SAAS,CAACH,UAAU,CAAC;UACvC,CAAC,MAAM;YACL,MAAM,IAAIR,KAAK,CAAC,kCAAkC,CAAC;UACrD;QACF;MACF,CAAC,CAAC,OAAOuB,GAAG,EAAE;QACZ,MAAMA,GAAG;MACX,CAAC,SAAS;QACR1B,SAAS,CAAC2B,UAAU,CAAC,CAAC;MACxB;IACF,CAAC,MAAM;MACL3B,SAAS,CAAC2B,UAAU,CAAC,CAAC;IACxB;EACF;AACF;AAEA,OAAO,SAASC,YAAYA,CAACvB,SAAiB,EAAoB;EAChE,OAAOL,SAAS,CAAC4B,YAAY,CAACvB,SAAS,CAAC;AAC1C"}
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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"}