react-native-video-trim 2.0.0 → 2.1.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.
- package/README.md +33 -9
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +185 -64
- package/android/src/main/java/com/videotrim/enums/ErrorCode.java +11 -0
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +2 -1
- package/android/src/main/java/com/videotrim/utils/MediaMetadataUtil.java +75 -0
- package/android/src/main/java/com/videotrim/utils/StorageUtil.java +2 -2
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +15 -8
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +239 -70
- package/android/src/main/res/drawable/airpodsmax.xml +19 -0
- package/android/src/main/res/drawable/exclamationmark_triangle_fill.xml +15 -0
- package/android/src/main/res/drawable/thumb_container_bg.xml +8 -0
- package/android/src/main/res/layout/video_trimmer_view.xml +51 -4
- package/android/src/main/res/xml/file_paths.xml +5 -0
- package/ios/AssetLoader.swift +99 -0
- package/ios/ErrorCode.swift +16 -0
- package/ios/VideoTrim.mm +4 -2
- package/ios/VideoTrim.swift +380 -167
- package/ios/VideoTrimmer.swift +16 -10
- package/ios/VideoTrimmerViewController.swift +78 -12
- package/lib/commonjs/index.js +20 -57
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +19 -57
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +47 -9
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +56 -66
- package/android/src/main/java/iknow/android/utils/BuildConfig.java +0 -18
- package/android/src/main/java/iknow/android/utils/DateUtil.java +0 -64
package/ios/VideoTrimmer.swift
CHANGED
|
@@ -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 = "
|
|
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
|
-
|
|
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
|
-
|
|
538
|
-
|
|
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,36 @@ extension CMTime {
|
|
|
22
22
|
|
|
23
23
|
@available(iOS 13.0, *)
|
|
24
24
|
class VideoTrimmerViewController: UIViewController {
|
|
25
|
-
var asset: AVAsset
|
|
25
|
+
var asset: AVAsset? {
|
|
26
|
+
didSet {
|
|
27
|
+
if let _ = asset {
|
|
28
|
+
setupVideoTrimmer()
|
|
29
|
+
setupPlayerController()
|
|
30
|
+
setupTimeObserver()
|
|
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
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
26
47
|
var maximumDuration: Int?
|
|
27
48
|
var minimumDuration: Int?
|
|
28
49
|
var cancelBtnText = "Cancel"
|
|
29
50
|
var saveButtonText = "Save"
|
|
30
51
|
var cancelBtnClicked: (() -> Void)?
|
|
31
52
|
var saveBtnClicked: ((CMTimeRange) -> Void)?
|
|
53
|
+
var isVideoType = true
|
|
54
|
+
var enableHapticFeedback = true
|
|
32
55
|
|
|
33
56
|
private let playerController = AVPlayerViewController()
|
|
34
57
|
private var trimmer: VideoTrimmer!
|
|
@@ -39,9 +62,11 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
39
62
|
private var btnStackView: UIStackView!
|
|
40
63
|
private var cancelBtn: UIButton!
|
|
41
64
|
private var playBtn: UIButton!
|
|
65
|
+
private var loadingIndicator = UIActivityIndicatorView()
|
|
42
66
|
private var saveBtn: UIButton!
|
|
43
67
|
private let playIcon = UIImage(systemName: "play.fill")
|
|
44
68
|
private let pauseIcon = UIImage(systemName: "pause.fill")
|
|
69
|
+
private let audioBannerView = UIImage(systemName: "airpodsmax")
|
|
45
70
|
private var player: AVPlayer! { playerController.player }
|
|
46
71
|
private var timeObserverToken: Any?
|
|
47
72
|
|
|
@@ -49,6 +74,32 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
49
74
|
private var chaseTime = CMTime.zero
|
|
50
75
|
private var preferredFrameRate: Float = 23.98
|
|
51
76
|
|
|
77
|
+
public func onAssetFailToLoad() {
|
|
78
|
+
loadingIndicator.stopAnimating()
|
|
79
|
+
btnStackView.removeArrangedSubview(loadingIndicator)
|
|
80
|
+
loadingIndicator.removeFromSuperview()
|
|
81
|
+
|
|
82
|
+
let imageViewContainer = UIView()
|
|
83
|
+
let imageView = UIImageView(image: UIImage(systemName: "exclamationmark.triangle.fill"))
|
|
84
|
+
imageView.tintColor = .systemYellow
|
|
85
|
+
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
86
|
+
|
|
87
|
+
imageViewContainer.addSubview(imageView)
|
|
88
|
+
NSLayoutConstraint.activate([
|
|
89
|
+
imageView.widthAnchor.constraint(equalToConstant: 36),
|
|
90
|
+
imageView.heightAnchor.constraint(equalToConstant: 36),
|
|
91
|
+
imageView.centerXAnchor.constraint(equalTo: imageViewContainer.centerXAnchor),
|
|
92
|
+
imageView.centerYAnchor.constraint(equalTo: imageViewContainer.centerYAnchor)
|
|
93
|
+
])
|
|
94
|
+
imageViewContainer.alpha = 0
|
|
95
|
+
|
|
96
|
+
btnStackView.insertArrangedSubview(imageViewContainer, at: 1)
|
|
97
|
+
|
|
98
|
+
UIView.animate(withDuration: 0.25, animations: {
|
|
99
|
+
imageViewContainer.alpha = 1
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
52
103
|
// MARK: - Input
|
|
53
104
|
@objc private func didBeginTrimmingFromStart(_ sender: VideoTrimmer) {
|
|
54
105
|
handleBeforeProgressChange()
|
|
@@ -117,16 +168,13 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
117
168
|
setupView()
|
|
118
169
|
setupButtons()
|
|
119
170
|
setupTimeLabels()
|
|
120
|
-
setupVideoTrimmer()
|
|
121
|
-
setupPlayerController()
|
|
122
|
-
setupTimeObserver()
|
|
123
|
-
|
|
124
|
-
updateLabels()
|
|
125
171
|
}
|
|
126
172
|
|
|
127
173
|
override func viewWillDisappear(_ animated: Bool) {
|
|
128
174
|
super.viewWillDisappear(animated)
|
|
129
175
|
|
|
176
|
+
// if asset has been initialized
|
|
177
|
+
guard let _ = asset else { return }
|
|
130
178
|
player.pause()
|
|
131
179
|
if let token = timeObserverToken {
|
|
132
180
|
player.removeTimeObserver(token)
|
|
@@ -170,26 +218,38 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
170
218
|
private func setupButtons() {
|
|
171
219
|
cancelBtn = UIButton.createButton(title: cancelBtnText, font: .systemFont(ofSize: 18), titleColor: .white, target: self, action: #selector(onCancelBtnClicked))
|
|
172
220
|
playBtn = UIButton.createButton(image: playIcon, tintColor: .white, target: self, action: #selector(togglePlay(sender:)))
|
|
221
|
+
playBtn.alpha = 0
|
|
222
|
+
playBtn.isEnabled = false
|
|
223
|
+
|
|
173
224
|
saveBtn = UIButton.createButton(title: saveButtonText, font: .systemFont(ofSize: 18), titleColor: .systemBlue, target: self, action: #selector(onSaveBtnClicked))
|
|
225
|
+
saveBtn.alpha = 0
|
|
226
|
+
saveBtn.isEnabled = false
|
|
174
227
|
|
|
175
|
-
btnStackView = UIStackView(arrangedSubviews: [cancelBtn,
|
|
228
|
+
btnStackView = UIStackView(arrangedSubviews: [cancelBtn, loadingIndicator, saveBtn])
|
|
176
229
|
btnStackView.axis = .horizontal
|
|
177
|
-
btnStackView.alignment = .
|
|
230
|
+
btnStackView.alignment = .center
|
|
178
231
|
btnStackView.distribution = .fillEqually
|
|
179
232
|
btnStackView.spacing = UIStackView.spacingUseSystem
|
|
180
|
-
view.addSubview(btnStackView)
|
|
181
233
|
btnStackView.translatesAutoresizingMaskIntoConstraints = false
|
|
234
|
+
|
|
235
|
+
view.addSubview(btnStackView)
|
|
236
|
+
|
|
182
237
|
NSLayoutConstraint.activate([
|
|
183
238
|
btnStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
|
|
184
239
|
btnStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
|
|
185
240
|
btnStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16)
|
|
186
241
|
])
|
|
242
|
+
|
|
243
|
+
loadingIndicator.startAnimating()
|
|
187
244
|
}
|
|
188
245
|
|
|
189
246
|
private func setupTimeLabels() {
|
|
190
247
|
leadingTrimLabel = UILabel.createLabel(textAlignment: .left, textColor: .white)
|
|
248
|
+
leadingTrimLabel.text = "00:00.000"
|
|
191
249
|
currentTimeLabel = UILabel.createLabel(textAlignment: .center, textColor: .white)
|
|
250
|
+
currentTimeLabel.text = "00:00.000"
|
|
192
251
|
trailingTrimLabel = UILabel.createLabel(textAlignment: .right, textColor: .white)
|
|
252
|
+
trailingTrimLabel.text = "00:00.000"
|
|
193
253
|
|
|
194
254
|
timingStackView = UIStackView(arrangedSubviews: [leadingTrimLabel, currentTimeLabel, trailingTrimLabel])
|
|
195
255
|
timingStackView.axis = .horizontal
|
|
@@ -209,11 +269,12 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
209
269
|
trimmer = VideoTrimmer()
|
|
210
270
|
trimmer.asset = asset
|
|
211
271
|
trimmer.minimumDuration = CMTime(seconds: 1, preferredTimescale: 600)
|
|
272
|
+
trimmer.enableHapticFeedback = enableHapticFeedback
|
|
212
273
|
|
|
213
274
|
if let maxDuration = maximumDuration {
|
|
214
275
|
trimmer.maximumDuration = CMTime(seconds: max(1, Double(maxDuration)), preferredTimescale: 600)
|
|
215
|
-
if trimmer.maximumDuration > asset
|
|
216
|
-
trimmer.maximumDuration = asset
|
|
276
|
+
if trimmer.maximumDuration > asset!.duration {
|
|
277
|
+
trimmer.maximumDuration = asset!.duration
|
|
217
278
|
}
|
|
218
279
|
trimmer.selectedRange = CMTimeRange(start: .zero, end: trimmer.maximumDuration)
|
|
219
280
|
}
|
|
@@ -233,6 +294,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
233
294
|
trimmer.addTarget(self, action: #selector(didBeginTrimmingFromEnd(_:)), for: VideoTrimmer.didBeginTrimmingFromEnd)
|
|
234
295
|
trimmer.addTarget(self, action: #selector(trailingGrabberChanged(_:)), for: VideoTrimmer.trailingGrabberChanged)
|
|
235
296
|
trimmer.addTarget(self, action: #selector(didEndTrimmingFromEnd(_:)), for: VideoTrimmer.didEndTrimmingFromEnd)
|
|
297
|
+
trimmer.alpha = 0
|
|
236
298
|
view.addSubview(trimmer)
|
|
237
299
|
trimmer.translatesAutoresizingMaskIntoConstraints = false
|
|
238
300
|
NSLayoutConstraint.activate([
|
|
@@ -241,6 +303,10 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
241
303
|
trimmer.bottomAnchor.constraint(equalTo: timingStackView.topAnchor, constant: -16),
|
|
242
304
|
trimmer.heightAnchor.constraint(equalToConstant: 50)
|
|
243
305
|
])
|
|
306
|
+
|
|
307
|
+
UIView.animate(withDuration: 0.25, animations: {
|
|
308
|
+
self.trimmer.alpha = 1
|
|
309
|
+
})
|
|
244
310
|
}
|
|
245
311
|
|
|
246
312
|
private func setupPlayerController() {
|
|
@@ -249,7 +315,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
249
315
|
playerController.allowsVideoFrameAnalysis = false
|
|
250
316
|
}
|
|
251
317
|
playerController.player = AVPlayer()
|
|
252
|
-
player.replaceCurrentItem(with: AVPlayerItem(asset: asset))
|
|
318
|
+
player.replaceCurrentItem(with: AVPlayerItem(asset: asset!))
|
|
253
319
|
|
|
254
320
|
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
|
|
255
321
|
addChild(playerController)
|
package/lib/commonjs/index.js
CHANGED
|
@@ -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.
|
|
9
|
+
exports.isValidFile = isValidFile;
|
|
9
10
|
exports.listFiles = listFiles;
|
|
10
11
|
exports.showEditor = showEditor;
|
|
11
12
|
var _reactNative = require("react-native");
|
|
@@ -25,63 +26,9 @@ 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
|
-
|
|
29
|
+
function showEditor(filePath) {
|
|
29
30
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
30
|
-
|
|
31
|
-
throw new Error('File path cannot be empty!');
|
|
32
|
-
}
|
|
33
|
-
const {
|
|
34
|
-
saveToPhoto = true
|
|
35
|
-
} = 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);
|
|
31
|
+
VideoTrim.showEditor(filePath, config);
|
|
85
32
|
}
|
|
86
33
|
|
|
87
34
|
/**
|
|
@@ -114,4 +61,20 @@ function deleteFile(filePath) {
|
|
|
114
61
|
}
|
|
115
62
|
return VideoTrim.deleteFile(filePath);
|
|
116
63
|
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Close editor
|
|
67
|
+
*/
|
|
68
|
+
function closeEditor() {
|
|
69
|
+
return VideoTrim.closeEditor();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if a file is valid audio or video file
|
|
74
|
+
*
|
|
75
|
+
* @returns {Promise} A **Promise** which resolves file info if successful
|
|
76
|
+
*/
|
|
77
|
+
function isValidFile(url) {
|
|
78
|
+
return VideoTrim.isValidFile(url);
|
|
79
|
+
}
|
|
117
80
|
//# 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","
|
|
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"}
|
package/lib/module/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NativeModules,
|
|
1
|
+
import { NativeModules, Platform } 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,9 @@ 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
|
|
18
|
+
export function showEditor(filePath) {
|
|
19
19
|
let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
20
|
-
|
|
21
|
-
throw new Error('File path cannot be empty!');
|
|
22
|
-
}
|
|
23
|
-
const {
|
|
24
|
-
saveToPhoto = true
|
|
25
|
-
} = 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);
|
|
20
|
+
VideoTrim.showEditor(filePath, config);
|
|
75
21
|
}
|
|
76
22
|
|
|
77
23
|
/**
|
|
@@ -104,4 +50,20 @@ export function deleteFile(filePath) {
|
|
|
104
50
|
}
|
|
105
51
|
return VideoTrim.deleteFile(filePath);
|
|
106
52
|
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Close editor
|
|
56
|
+
*/
|
|
57
|
+
export function closeEditor() {
|
|
58
|
+
return VideoTrim.closeEditor();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if a file is valid audio or video file
|
|
63
|
+
*
|
|
64
|
+
* @returns {Promise} A **Promise** which resolves file info if successful
|
|
65
|
+
*/
|
|
66
|
+
export function isValidFile(url) {
|
|
67
|
+
return VideoTrim.isValidFile(url);
|
|
68
|
+
}
|
|
107
69
|
//# sourceMappingURL=index.js.map
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeModules","
|
|
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,6 +1,14 @@
|
|
|
1
1
|
export interface EditorConfig {
|
|
2
|
+
/**
|
|
3
|
+
* Enable haptic feedback
|
|
4
|
+
* @default true
|
|
5
|
+
*/
|
|
6
|
+
enableHapticFeedback?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Save the output file to Photos Library. Only video is supported. Note that you have to make sure you have permission to save to Photos Library.
|
|
9
|
+
* @default false
|
|
10
|
+
*/
|
|
2
11
|
saveToPhoto?: boolean;
|
|
3
|
-
removeAfterSavedToPhoto?: boolean;
|
|
4
12
|
maxDuration?: number;
|
|
5
13
|
minDuration?: number;
|
|
6
14
|
cancelButtonText?: string;
|
|
@@ -17,6 +25,33 @@ export interface EditorConfig {
|
|
|
17
25
|
saveDialogConfirmText?: string;
|
|
18
26
|
trimmingText?: string;
|
|
19
27
|
fullScreenModalIOS?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Type of the file to edit. If video file, recommend to use `video`. If audio file, recommend to use `audio`.
|
|
30
|
+
* @default "video"
|
|
31
|
+
*/
|
|
32
|
+
type?: 'video' | 'audio';
|
|
33
|
+
/**
|
|
34
|
+
* Output file extension. If video file, recommend to use `mp4` or `mov`. If audio file, recommend to use `wav` or `m4a`.
|
|
35
|
+
* @default "mp4"
|
|
36
|
+
* @example "mp4", "mov", "wav", "m4a", "3gp", "avi", "mkv", "flv", "wmv", "webm"
|
|
37
|
+
*/
|
|
38
|
+
outputExt?: string;
|
|
39
|
+
openDocumentsOnFinish?: boolean;
|
|
40
|
+
openShareSheetOnFinish?: boolean;
|
|
41
|
+
removeAfterSavedToPhoto?: boolean;
|
|
42
|
+
removeAfterFailedToSavePhoto?: boolean;
|
|
43
|
+
removeAfterSavedToDocuments?: boolean;
|
|
44
|
+
removeAfterFailedToSaveDocuments?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Remove the file after shared to other apps. Currently only support iOS, on Android there's no way to detect if the file is shared or not.
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
removeAfterShared?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Remove the file after failed to share to other apps. Currently only support iOS, on Android there's no way to detect if the file is shared or not.
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
removeAfterFailedToShare?: boolean;
|
|
20
55
|
}
|
|
21
56
|
/**
|
|
22
57
|
* Delete a file
|
|
@@ -25,14 +60,7 @@ export interface EditorConfig {
|
|
|
25
60
|
* @param {EditorConfig} config: editor configuration
|
|
26
61
|
* @returns {void} A **Promise** which resolves `void`
|
|
27
62
|
*/
|
|
28
|
-
export declare function showEditor(filePath: string, config?: EditorConfig):
|
|
29
|
-
/**
|
|
30
|
-
* Delete a file
|
|
31
|
-
*
|
|
32
|
-
* @param {string} filePath: absolute non-empty file path to check if editable
|
|
33
|
-
* @returns {Promise} A **Promise** which resolves `true` if editable
|
|
34
|
-
*/
|
|
35
|
-
export declare function isValidVideo(filePath: string): Promise<boolean>;
|
|
63
|
+
export declare function showEditor(filePath: string, config?: EditorConfig): void;
|
|
36
64
|
/**
|
|
37
65
|
* Clean output files generated at all time
|
|
38
66
|
*
|
|
@@ -52,4 +80,14 @@ export declare function cleanFiles(): Promise<number>;
|
|
|
52
80
|
* @returns {Promise} A **Promise** which resolves `true` if successful
|
|
53
81
|
*/
|
|
54
82
|
export declare function deleteFile(filePath: string): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Close editor
|
|
85
|
+
*/
|
|
86
|
+
export declare function closeEditor(): void;
|
|
87
|
+
/**
|
|
88
|
+
* Check if a file is valid audio or video file
|
|
89
|
+
*
|
|
90
|
+
* @returns {Promise} A **Promise** which resolves file info if successful
|
|
91
|
+
*/
|
|
92
|
+
export declare function isValidFile(url: string): Promise<boolean>;
|
|
55
93
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,gCAAgC,CAAC,EAAE,OAAO,CAAC;IAC3C;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,YAAiB,GAAG,IAAI,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAE7C;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAE5C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAK7D;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEzD"}
|