react-native-video-trim 6.0.13 → 6.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.
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +77 -3
- package/ios/VideoTrimmer.swift +102 -3
- package/ios/VideoTrimmerViewController.swift +20 -2
- package/lib/module/NativeVideoTrim.js +25 -0
- package/lib/module/NativeVideoTrim.js.map +1 -1
- package/lib/typescript/src/NativeVideoTrim.d.ts +103 -13
- package/lib/typescript/src/NativeVideoTrim.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/NativeVideoTrim.ts +103 -13
|
@@ -21,6 +21,7 @@ import android.util.AttributeSet;
|
|
|
21
21
|
import android.util.Log;
|
|
22
22
|
import android.util.TypedValue;
|
|
23
23
|
import android.view.LayoutInflater;
|
|
24
|
+
import android.view.GestureDetector;
|
|
24
25
|
import android.view.MotionEvent;
|
|
25
26
|
import android.view.View;
|
|
26
27
|
import android.widget.FrameLayout;
|
|
@@ -104,6 +105,13 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
104
105
|
private long zoomedInRangeDuration = 0;
|
|
105
106
|
private boolean isTrimmingLeading = false;
|
|
106
107
|
|
|
108
|
+
// range drag
|
|
109
|
+
private boolean isRangeDragging = false;
|
|
110
|
+
private float rangeDragInitialRawX = 0;
|
|
111
|
+
private long rangeDragInitialStartTime = 0;
|
|
112
|
+
private long rangeDragInitialEndTime = 0;
|
|
113
|
+
private GestureDetector rangeDragGestureDetector;
|
|
114
|
+
|
|
107
115
|
// thumbnail caching for zoom functionality
|
|
108
116
|
private final java.util.List<ImageView> cachedFullViewThumbnails = new java.util.ArrayList<>();
|
|
109
117
|
private volatile boolean isGeneratingThumbnails = false;
|
|
@@ -155,9 +163,24 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
155
163
|
initializeViews();
|
|
156
164
|
configure(config);
|
|
157
165
|
setUpListeners();
|
|
166
|
+
initRangeDragDetector();
|
|
158
167
|
setProgressIndicatorTouchListener();
|
|
159
168
|
}
|
|
160
169
|
|
|
170
|
+
private void initRangeDragDetector() {
|
|
171
|
+
rangeDragGestureDetector = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
|
|
172
|
+
@Override
|
|
173
|
+
public void onLongPress(MotionEvent e) {
|
|
174
|
+
isRangeDragging = true;
|
|
175
|
+
rangeDragInitialRawX = e.getRawX();
|
|
176
|
+
rangeDragInitialStartTime = startTime;
|
|
177
|
+
rangeDragInitialEndTime = endTime;
|
|
178
|
+
playHapticFeedback(true);
|
|
179
|
+
fadeOutProgressIndicator();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
161
184
|
private void initializeViews() {
|
|
162
185
|
mThumbnailContainer = findViewById(R.id.thumbnailContainer);
|
|
163
186
|
mVideoView = findViewById(R.id.video_loader);
|
|
@@ -486,11 +509,11 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
486
509
|
|
|
487
510
|
private void configure(ReadableMap config) {
|
|
488
511
|
if (config.hasKey("maxDuration") && config.getDouble("maxDuration") > 0) {
|
|
489
|
-
mMaxDuration = (long) Math.max(0, config.getDouble("maxDuration")
|
|
512
|
+
mMaxDuration = (long) Math.max(0, config.getDouble("maxDuration"));
|
|
490
513
|
}
|
|
491
514
|
|
|
492
515
|
if (config.hasKey("minDuration") && config.getDouble("minDuration") > 0) {
|
|
493
|
-
mMinDuration = (long) Math.max(1000L, config.getDouble("minDuration")
|
|
516
|
+
mMinDuration = (long) Math.max(1000L, config.getDouble("minDuration"));
|
|
494
517
|
}
|
|
495
518
|
|
|
496
519
|
cancelBtn.setText(config.getString("cancelButtonText"));
|
|
@@ -664,17 +687,29 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
664
687
|
|
|
665
688
|
private void setProgressIndicatorTouchListener() {
|
|
666
689
|
trimmerContainerBg.setOnTouchListener((view, event) -> {
|
|
690
|
+
rangeDragGestureDetector.onTouchEvent(event);
|
|
691
|
+
|
|
667
692
|
switch (event.getAction()) {
|
|
668
693
|
case MotionEvent.ACTION_DOWN:
|
|
694
|
+
isRangeDragging = false;
|
|
669
695
|
didClampWhilePanning = false;
|
|
670
696
|
onMediaPause();
|
|
671
697
|
onTrimmerContainerPanned(event);
|
|
672
698
|
playHapticFeedback(true);
|
|
673
699
|
break;
|
|
674
700
|
case MotionEvent.ACTION_MOVE:
|
|
675
|
-
|
|
701
|
+
if (isRangeDragging) {
|
|
702
|
+
onRangeDrag(event);
|
|
703
|
+
} else {
|
|
704
|
+
onTrimmerContainerPanned(event);
|
|
705
|
+
}
|
|
676
706
|
break;
|
|
677
707
|
case MotionEvent.ACTION_UP:
|
|
708
|
+
if (isRangeDragging) {
|
|
709
|
+
isRangeDragging = false;
|
|
710
|
+
fadeInProgressIndicator();
|
|
711
|
+
updateCurrentTime(true);
|
|
712
|
+
}
|
|
678
713
|
view.performClick();
|
|
679
714
|
break;
|
|
680
715
|
default:
|
|
@@ -730,6 +765,45 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
|
730
765
|
seekTo(newVideoPosition, false);
|
|
731
766
|
}
|
|
732
767
|
|
|
768
|
+
private void onRangeDrag(MotionEvent event) {
|
|
769
|
+
float deltaX = event.getRawX() - rangeDragInitialRawX;
|
|
770
|
+
float containerWidth = trimmerContainerBg.getWidth();
|
|
771
|
+
if (containerWidth <= 0) return;
|
|
772
|
+
|
|
773
|
+
long rangeDuration = rangeDragInitialEndTime - rangeDragInitialStartTime;
|
|
774
|
+
long deltaTime;
|
|
775
|
+
if (isZoomedIn) {
|
|
776
|
+
deltaTime = (long) (deltaX / containerWidth * zoomedInRangeDuration);
|
|
777
|
+
} else {
|
|
778
|
+
deltaTime = (long) (deltaX / containerWidth * mDuration);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
long newStart = rangeDragInitialStartTime + deltaTime;
|
|
782
|
+
long newEnd = newStart + rangeDuration;
|
|
783
|
+
|
|
784
|
+
boolean didClamp = false;
|
|
785
|
+
if (newStart < 0) {
|
|
786
|
+
newStart = 0;
|
|
787
|
+
newEnd = rangeDuration;
|
|
788
|
+
didClamp = true;
|
|
789
|
+
}
|
|
790
|
+
if (newEnd > mDuration) {
|
|
791
|
+
newEnd = mDuration;
|
|
792
|
+
newStart = newEnd - rangeDuration;
|
|
793
|
+
didClamp = true;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (didClamp && !didClampWhilePanning) {
|
|
797
|
+
playHapticFeedback(false);
|
|
798
|
+
}
|
|
799
|
+
didClampWhilePanning = didClamp;
|
|
800
|
+
|
|
801
|
+
startTime = newStart;
|
|
802
|
+
endTime = newEnd;
|
|
803
|
+
updateHandlePositions();
|
|
804
|
+
seekTo(startTime, false);
|
|
805
|
+
}
|
|
806
|
+
|
|
733
807
|
private void setHandleTouchListener(View handle, boolean isLeading) {
|
|
734
808
|
handle.setOnTouchListener((view, event) -> {
|
|
735
809
|
boolean draggingDisabled = mDuration < mMinDuration; // if the video is shorter than the minimum duration, disable dragging
|
package/ios/VideoTrimmer.swift
CHANGED
|
@@ -26,6 +26,11 @@ import AVFoundation
|
|
|
26
26
|
static let progressChanged = UIControl.Event(rawValue: 0b00010000 << 24)
|
|
27
27
|
static let didEndScrubbing = UIControl.Event(rawValue: 0b00100000 << 24)
|
|
28
28
|
|
|
29
|
+
// events for dragging the entire selected range
|
|
30
|
+
static let didBeginDraggingRange = UIControl.Event(rawValue: 0b01000000 << 24)
|
|
31
|
+
static let rangeDragChanged = UIControl.Event(rawValue: 0b10000000 << 24)
|
|
32
|
+
static let didEndDraggingRange = UIControl.Event(rawValue: 1 << 25)
|
|
33
|
+
|
|
29
34
|
private struct Thumbnail {
|
|
30
35
|
let uuid = UUID()
|
|
31
36
|
let imageView: UIImageView
|
|
@@ -249,6 +254,12 @@ import AVFoundation
|
|
|
249
254
|
private (set) var trailingGestureRecognizer: UILongPressGestureRecognizer!
|
|
250
255
|
private (set) var progressGestureRecognizer: UILongPressGestureRecognizer!
|
|
251
256
|
private (set) var thumbnailInteractionGestureRecognizer: UILongPressGestureRecognizer!
|
|
257
|
+
private (set) var rangeDragGestureRecognizer: UILongPressGestureRecognizer!
|
|
258
|
+
|
|
259
|
+
// range drag state
|
|
260
|
+
private(set) var isDraggingRange = false
|
|
261
|
+
private var rangeDragInitialRange: CMTimeRange = .zero
|
|
262
|
+
private var rangeDragInitialLocationX: CGFloat = 0
|
|
252
263
|
|
|
253
264
|
// private stuff
|
|
254
265
|
private var grabberOffset = CGFloat(0)
|
|
@@ -369,11 +380,18 @@ import AVFoundation
|
|
|
369
380
|
progressGestureRecognizer.require(toFail: trailingGestureRecognizer)
|
|
370
381
|
progressIndicatorControl.addGestureRecognizer(progressGestureRecognizer)
|
|
371
382
|
|
|
383
|
+
// Range drag: platform-default long press (0.5s hold, 10pt allowable movement)
|
|
384
|
+
rangeDragGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(rangeDragPanned(_:)))
|
|
385
|
+
rangeDragGestureRecognizer.require(toFail: leadingGestureRecognizer)
|
|
386
|
+
rangeDragGestureRecognizer.require(toFail: trailingGestureRecognizer)
|
|
387
|
+
thumbView.addGestureRecognizer(rangeDragGestureRecognizer)
|
|
388
|
+
|
|
372
389
|
thumbnailInteractionGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(thumbnailPanned(_:)))
|
|
373
390
|
thumbnailInteractionGestureRecognizer.allowableMovement = CGFloat.greatestFiniteMagnitude
|
|
374
391
|
thumbnailInteractionGestureRecognizer.minimumPressDuration = 0
|
|
375
392
|
thumbnailInteractionGestureRecognizer.require(toFail: leadingGestureRecognizer)
|
|
376
393
|
thumbnailInteractionGestureRecognizer.require(toFail: trailingGestureRecognizer)
|
|
394
|
+
thumbnailInteractionGestureRecognizer.require(toFail: rangeDragGestureRecognizer)
|
|
377
395
|
thumbView.addGestureRecognizer(thumbnailInteractionGestureRecognizer)
|
|
378
396
|
}
|
|
379
397
|
|
|
@@ -583,9 +601,10 @@ import AVFoundation
|
|
|
583
601
|
setNeedsLayout()
|
|
584
602
|
|
|
585
603
|
case .hiddenOnlyWhenTrimming:
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
604
|
+
let shouldShow = trimmingState == .none && !isDraggingRange
|
|
605
|
+
progressIndicator.alpha = (shouldShow ? 1 : 0)
|
|
606
|
+
progressIndicatorControl.isUserInteractionEnabled = shouldShow
|
|
607
|
+
if shouldShow {
|
|
589
608
|
setNeedsLayout()
|
|
590
609
|
if UIView.inheritedAnimationDuration > 0 {
|
|
591
610
|
UIView.performWithoutAnimation {
|
|
@@ -780,6 +799,86 @@ import AVFoundation
|
|
|
780
799
|
}
|
|
781
800
|
}
|
|
782
801
|
|
|
802
|
+
|
|
803
|
+
@objc private func rangeDragPanned(_ sender: UILongPressGestureRecognizer) {
|
|
804
|
+
switch sender.state {
|
|
805
|
+
case .began:
|
|
806
|
+
isDraggingRange = true
|
|
807
|
+
rangeDragInitialRange = selectedRange
|
|
808
|
+
rangeDragInitialLocationX = sender.location(in: self).x
|
|
809
|
+
didClampWhilePanning = false
|
|
810
|
+
|
|
811
|
+
if enableHapticFeedback {
|
|
812
|
+
UISelectionFeedbackGenerator().selectionChanged()
|
|
813
|
+
impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
|
814
|
+
impactFeedbackGenerator?.prepare()
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
|
|
818
|
+
self.updateProgressIndicator()
|
|
819
|
+
})
|
|
820
|
+
sendActions(for: Self.didBeginDraggingRange)
|
|
821
|
+
|
|
822
|
+
case .changed:
|
|
823
|
+
let currentX = sender.location(in: self).x
|
|
824
|
+
let deltaX = currentX - rangeDragInitialLocationX
|
|
825
|
+
let inset = thumbView.chevronWidth + horizontalInset
|
|
826
|
+
let availableWidth = bounds.width - inset * 2
|
|
827
|
+
let visibleDurationInSeconds = CGFloat(visibleRange.duration.seconds)
|
|
828
|
+
guard availableWidth > 0 && visibleDurationInSeconds > 0 else { return }
|
|
829
|
+
|
|
830
|
+
let deltaTime = CMTime(
|
|
831
|
+
seconds: Double(deltaX / availableWidth) * Double(visibleDurationInSeconds),
|
|
832
|
+
preferredTimescale: 600
|
|
833
|
+
)
|
|
834
|
+
let duration = rangeDragInitialRange.duration
|
|
835
|
+
var newStart = CMTimeAdd(rangeDragInitialRange.start, deltaTime)
|
|
836
|
+
var newEnd = CMTimeAdd(newStart, duration)
|
|
837
|
+
|
|
838
|
+
var didClamp = false
|
|
839
|
+
if CMTimeCompare(newStart, range.start) == -1 {
|
|
840
|
+
newStart = range.start
|
|
841
|
+
newEnd = CMTimeAdd(newStart, duration)
|
|
842
|
+
didClamp = true
|
|
843
|
+
}
|
|
844
|
+
if CMTimeCompare(newEnd, range.end) == 1 {
|
|
845
|
+
newEnd = range.end
|
|
846
|
+
newStart = CMTimeSubtract(newEnd, duration)
|
|
847
|
+
didClamp = true
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if didClamp && !didClampWhilePanning {
|
|
851
|
+
impactFeedbackGenerator?.impactOccurred()
|
|
852
|
+
}
|
|
853
|
+
didClampWhilePanning = didClamp
|
|
854
|
+
|
|
855
|
+
selectedRange = CMTimeRange(start: newStart, end: newEnd)
|
|
856
|
+
setNeedsLayout()
|
|
857
|
+
sendActions(for: Self.rangeDragChanged)
|
|
858
|
+
|
|
859
|
+
case .ended:
|
|
860
|
+
isDraggingRange = false
|
|
861
|
+
impactFeedbackGenerator = nil
|
|
862
|
+
UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
|
|
863
|
+
self.updateProgressIndicator()
|
|
864
|
+
})
|
|
865
|
+
sendActions(for: Self.didEndDraggingRange)
|
|
866
|
+
|
|
867
|
+
case .cancelled:
|
|
868
|
+
isDraggingRange = false
|
|
869
|
+
impactFeedbackGenerator = nil
|
|
870
|
+
UIView.animate(withDuration: 0.25, delay: 0, options: [.beginFromCurrentState, .allowUserInteraction], animations: {
|
|
871
|
+
self.updateProgressIndicator()
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
case .possible, .failed:
|
|
875
|
+
break
|
|
876
|
+
|
|
877
|
+
@unknown default:
|
|
878
|
+
break
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
783
882
|
// MARK: - UIView
|
|
784
883
|
|
|
785
884
|
override var intrinsicContentSize: CGSize {
|
|
@@ -131,6 +131,20 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
131
131
|
handleTrimmingEnd(false)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
@objc private func didBeginDraggingRange(_ sender: VideoTrimmer) {
|
|
135
|
+
handleBeforeProgressChange()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@objc private func rangeDragChanged(_ sender: VideoTrimmer) {
|
|
139
|
+
handleProgressChanged(time: trimmer.selectedRange.start)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@objc private func didEndDraggingRange(_ sender: VideoTrimmer) {
|
|
143
|
+
self.trimmer.progress = trimmer.selectedRange.start
|
|
144
|
+
updateLabels()
|
|
145
|
+
seek(to: trimmer.progress)
|
|
146
|
+
}
|
|
147
|
+
|
|
134
148
|
@objc private func didBeginScrubbing(_ sender: VideoTrimmer) {
|
|
135
149
|
handleBeforeProgressChange()
|
|
136
150
|
}
|
|
@@ -332,7 +346,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
332
346
|
trimmer.zoomOnWaitingDuration = zoomOnWaitingDuration
|
|
333
347
|
|
|
334
348
|
if let maxDuration = maximumDuration {
|
|
335
|
-
trimmer.maximumDuration = CMTime(seconds: max(1, Double(maxDuration)), preferredTimescale: 600)
|
|
349
|
+
trimmer.maximumDuration = CMTime(seconds: max(1, Double(maxDuration) / 1000.0), preferredTimescale: 600)
|
|
336
350
|
if trimmer.maximumDuration > asset!.duration {
|
|
337
351
|
trimmer.maximumDuration = asset!.duration
|
|
338
352
|
}
|
|
@@ -340,7 +354,7 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
340
354
|
}
|
|
341
355
|
|
|
342
356
|
if let minDuration = minimumDuration {
|
|
343
|
-
trimmer.minimumDuration = CMTime(seconds: max(1, Double(minDuration)), preferredTimescale: 600)
|
|
357
|
+
trimmer.minimumDuration = CMTime(seconds: max(1, Double(minDuration) / 1000.0), preferredTimescale: 600)
|
|
344
358
|
}
|
|
345
359
|
|
|
346
360
|
trimmer.addTarget(self, action: #selector(didBeginScrubbing(_:)), for: VideoTrimmer.didBeginScrubbing)
|
|
@@ -354,6 +368,10 @@ class VideoTrimmerViewController: UIViewController {
|
|
|
354
368
|
trimmer.addTarget(self, action: #selector(didBeginTrimmingFromEnd(_:)), for: VideoTrimmer.didBeginTrimmingFromEnd)
|
|
355
369
|
trimmer.addTarget(self, action: #selector(trailingGrabberChanged(_:)), for: VideoTrimmer.trailingGrabberChanged)
|
|
356
370
|
trimmer.addTarget(self, action: #selector(didEndTrimmingFromEnd(_:)), for: VideoTrimmer.didEndTrimmingFromEnd)
|
|
371
|
+
|
|
372
|
+
trimmer.addTarget(self, action: #selector(didBeginDraggingRange(_:)), for: VideoTrimmer.didBeginDraggingRange)
|
|
373
|
+
trimmer.addTarget(self, action: #selector(rangeDragChanged(_:)), for: VideoTrimmer.rangeDragChanged)
|
|
374
|
+
trimmer.addTarget(self, action: #selector(didEndDraggingRange(_:)), for: VideoTrimmer.didEndDraggingRange)
|
|
357
375
|
trimmer.alpha = 0
|
|
358
376
|
view.addSubview(trimmer)
|
|
359
377
|
trimmer.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { TurboModuleRegistry } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base options shared by both the editor and headless trim operations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Configuration for the video trimmer editor UI.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for headless (non-UI) trim operations.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Result returned by {@link Spec.isValidFile}.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Result returned by a trim operation (both editor and headless).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* TurboModule spec for the native VideoTrim module.
|
|
27
|
+
*/
|
|
28
|
+
|
|
4
29
|
export default TurboModuleRegistry.getEnforcing('VideoTrim');
|
|
5
30
|
//# sourceMappingURL=NativeVideoTrim.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeVideoTrim.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeVideoTrim.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;;AAGlD;AACA;AACA;;AAkBA;AACA;AACA;;AAgGA;AACA;AACA;;AAQA;AACA;AACA;;AAUA;AACA;AACA;;AAcA;AACA;AACA;;AAmEA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,WAAW,CAAC","ignoreList":[]}
|
|
@@ -1,115 +1,202 @@
|
|
|
1
1
|
import type { TurboModule } from 'react-native';
|
|
2
2
|
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';
|
|
3
|
+
/**
|
|
4
|
+
* Base options shared by both the editor and headless trim operations.
|
|
5
|
+
*/
|
|
3
6
|
export interface BaseOptions {
|
|
7
|
+
/** Whether to save the output file to the device's photo library. */
|
|
4
8
|
saveToPhoto: boolean;
|
|
9
|
+
/** Media type: `"video"` or `"audio"`. */
|
|
5
10
|
type: string;
|
|
11
|
+
/** Output file extension (e.g. `"mp4"`, `"wav"`). */
|
|
6
12
|
outputExt: string;
|
|
13
|
+
/** Whether to remove the output file after it has been saved to the photo library. */
|
|
7
14
|
removeAfterSavedToPhoto: boolean;
|
|
15
|
+
/** Whether to remove the output file if saving to the photo library fails. */
|
|
8
16
|
removeAfterFailedToSavePhoto: boolean;
|
|
17
|
+
/** Whether to enable video rotation during trimming. */
|
|
9
18
|
enableRotation: boolean;
|
|
19
|
+
/** Rotation angle in degrees (e.g. `90`, `180`, `270`). Only used when `enableRotation` is `true`. */
|
|
10
20
|
rotationAngle: number;
|
|
11
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for the video trimmer editor UI.
|
|
24
|
+
*/
|
|
12
25
|
export interface EditorConfig extends BaseOptions {
|
|
26
|
+
/** Whether to enable haptic feedback when interacting with the trimmer. */
|
|
13
27
|
enableHapticFeedback: boolean;
|
|
28
|
+
/** Maximum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|
|
14
29
|
maxDuration: number;
|
|
30
|
+
/** Minimum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|
|
15
31
|
minDuration: number;
|
|
32
|
+
/** Whether to open the system documents/files app after trimming finishes. */
|
|
16
33
|
openDocumentsOnFinish: boolean;
|
|
34
|
+
/** Whether to open the share sheet after trimming finishes. */
|
|
17
35
|
openShareSheetOnFinish: boolean;
|
|
36
|
+
/** Whether to remove the output file after it has been saved to documents. */
|
|
18
37
|
removeAfterSavedToDocuments: boolean;
|
|
38
|
+
/** Whether to remove the output file if saving to documents fails. */
|
|
19
39
|
removeAfterFailedToSaveDocuments: boolean;
|
|
40
|
+
/** Whether to remove the output file after it has been shared. */
|
|
20
41
|
removeAfterShared: boolean;
|
|
42
|
+
/** Whether to remove the output file if sharing fails. */
|
|
21
43
|
removeAfterFailedToShare: boolean;
|
|
44
|
+
/** Text for the cancel button. */
|
|
22
45
|
cancelButtonText: string;
|
|
46
|
+
/** Text for the save button. */
|
|
23
47
|
saveButtonText: string;
|
|
48
|
+
/** Whether to show a confirmation dialog when the cancel button is pressed. */
|
|
24
49
|
enableCancelDialog: boolean;
|
|
50
|
+
/** Title of the cancel confirmation dialog. */
|
|
25
51
|
cancelDialogTitle: string;
|
|
52
|
+
/** Message of the cancel confirmation dialog. */
|
|
26
53
|
cancelDialogMessage: string;
|
|
54
|
+
/** Text for the dismiss button in the cancel confirmation dialog. */
|
|
27
55
|
cancelDialogCancelText: string;
|
|
56
|
+
/** Text for the confirm button in the cancel confirmation dialog. */
|
|
28
57
|
cancelDialogConfirmText: string;
|
|
58
|
+
/** Whether to show a confirmation dialog when the save button is pressed. */
|
|
29
59
|
enableSaveDialog: boolean;
|
|
60
|
+
/** Title of the save confirmation dialog. */
|
|
30
61
|
saveDialogTitle: string;
|
|
62
|
+
/** Message of the save confirmation dialog. */
|
|
31
63
|
saveDialogMessage: string;
|
|
64
|
+
/** Text for the dismiss button in the save confirmation dialog. */
|
|
32
65
|
saveDialogCancelText: string;
|
|
66
|
+
/** Text for the confirm button in the save confirmation dialog. */
|
|
33
67
|
saveDialogConfirmText: string;
|
|
68
|
+
/** Text displayed while the video is being trimmed (e.g. `"Trimming video..."`). */
|
|
34
69
|
trimmingText: string;
|
|
70
|
+
/** iOS only. Whether to present the editor as a full-screen modal. */
|
|
35
71
|
fullScreenModalIOS: boolean;
|
|
72
|
+
/** Whether to auto-play the video when the editor opens. */
|
|
36
73
|
autoplay: boolean;
|
|
74
|
+
/** Position in milliseconds to seek to when the editor loads. Set to `-1` to disable. */
|
|
37
75
|
jumpToPositionOnLoad: number;
|
|
76
|
+
/** Whether to automatically close the editor when trimming finishes. */
|
|
38
77
|
closeWhenFinish: boolean;
|
|
78
|
+
/** Whether to allow the user to cancel an in-progress trim operation. */
|
|
39
79
|
enableCancelTrimming: boolean;
|
|
80
|
+
/** Text for the cancel-trimming button. */
|
|
40
81
|
cancelTrimmingButtonText: string;
|
|
82
|
+
/** Whether to show a confirmation dialog when cancelling an in-progress trim. */
|
|
41
83
|
enableCancelTrimmingDialog: boolean;
|
|
84
|
+
/** Title of the cancel-trimming confirmation dialog. */
|
|
42
85
|
cancelTrimmingDialogTitle: string;
|
|
86
|
+
/** Message of the cancel-trimming confirmation dialog. */
|
|
43
87
|
cancelTrimmingDialogMessage: string;
|
|
88
|
+
/** Text for the dismiss button in the cancel-trimming dialog. */
|
|
44
89
|
cancelTrimmingDialogCancelText: string;
|
|
90
|
+
/** Text for the confirm button in the cancel-trimming dialog. */
|
|
45
91
|
cancelTrimmingDialogConfirmText: string;
|
|
92
|
+
/** Custom header text displayed at the top of the editor. */
|
|
46
93
|
headerText: string;
|
|
94
|
+
/** Font size of the header text in sp/pt. */
|
|
47
95
|
headerTextSize: number;
|
|
96
|
+
/** Color of the header text as a `processColor` value. */
|
|
48
97
|
headerTextColor: number;
|
|
98
|
+
/** Whether to show an alert dialog when the media file fails to load. */
|
|
49
99
|
alertOnFailToLoad: boolean;
|
|
100
|
+
/** Title of the fail-to-load alert. */
|
|
50
101
|
alertOnFailTitle: string;
|
|
102
|
+
/** Message of the fail-to-load alert. */
|
|
51
103
|
alertOnFailMessage: string;
|
|
104
|
+
/** Text for the close button of the fail-to-load alert. */
|
|
52
105
|
alertOnFailCloseText: string;
|
|
53
|
-
/**
|
|
54
|
-
* Android only
|
|
55
|
-
* Update status bar to black background color when editor is opened
|
|
56
|
-
*/
|
|
106
|
+
/** Android only. Whether to update the status bar to a black background when the editor opens. */
|
|
57
107
|
changeStatusBarColorOnOpen?: boolean;
|
|
58
|
-
/**
|
|
59
|
-
* Color of the trimmer bar
|
|
60
|
-
*/
|
|
108
|
+
/** Color of the trimmer bar as a `processColor` value. */
|
|
61
109
|
trimmerColor?: number;
|
|
62
|
-
/**
|
|
63
|
-
* Color of the trimmer left/right handle icons
|
|
64
|
-
*/
|
|
110
|
+
/** Color of the trimmer left/right handle icons as a `processColor` value. */
|
|
65
111
|
handleIconColor?: number;
|
|
66
112
|
/**
|
|
67
|
-
* Duration for zoom-on-waiting feature in milliseconds (default: 5000)
|
|
68
|
-
* When user pauses while dragging trim handles, the view
|
|
69
|
-
* this duration around the current trim position for more precise editing
|
|
113
|
+
* Duration for the zoom-on-waiting feature in milliseconds (default: `5000`).
|
|
114
|
+
* When the user pauses while dragging trim handles, the view zooms to show
|
|
115
|
+
* this duration around the current trim position for more precise editing.
|
|
70
116
|
*/
|
|
71
117
|
zoomOnWaitingDuration?: number;
|
|
72
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Options for headless (non-UI) trim operations.
|
|
121
|
+
*/
|
|
73
122
|
export interface TrimOptions extends BaseOptions {
|
|
123
|
+
/** Start time of the trim range in milliseconds. */
|
|
74
124
|
startTime: number;
|
|
125
|
+
/** End time of the trim range in milliseconds. */
|
|
75
126
|
endTime: number;
|
|
76
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Result returned by {@link Spec.isValidFile}.
|
|
130
|
+
*/
|
|
77
131
|
export interface FileValidationResult {
|
|
132
|
+
/** Whether the file is a valid audio or video file. */
|
|
78
133
|
isValid: boolean;
|
|
134
|
+
/** Detected file type (e.g. `"video"`, `"audio"`, `"unknown"`). */
|
|
79
135
|
fileType: string;
|
|
136
|
+
/** Duration of the media file in milliseconds, or `-1` if invalid. */
|
|
80
137
|
duration: number;
|
|
81
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Result returned by a trim operation (both editor and headless).
|
|
141
|
+
*/
|
|
82
142
|
export interface TrimResult {
|
|
143
|
+
/** Start time of the trimmed range in milliseconds. */
|
|
83
144
|
startTime: number;
|
|
145
|
+
/** End time of the trimmed range in milliseconds. */
|
|
84
146
|
endTime: number;
|
|
147
|
+
/** Duration of the trimmed clip in milliseconds. */
|
|
85
148
|
duration: number;
|
|
149
|
+
/** Absolute path to the trimmed output file. */
|
|
86
150
|
outputPath: string;
|
|
151
|
+
/** Whether the trim operation completed successfully. */
|
|
87
152
|
success: boolean;
|
|
88
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* TurboModule spec for the native VideoTrim module.
|
|
156
|
+
*/
|
|
89
157
|
export interface Spec extends TurboModule {
|
|
158
|
+
/** Open the video trimmer editor for the given file. */
|
|
90
159
|
showEditor(filePath: string, config: EditorConfig): void;
|
|
160
|
+
/** List all output files generated by past trim operations. */
|
|
91
161
|
listFiles(): Promise<string[]>;
|
|
162
|
+
/** Delete all output files generated by past trim operations. Returns the number of files removed. */
|
|
92
163
|
cleanFiles(): Promise<number>;
|
|
164
|
+
/** Delete a single file at the given path. Resolves `true` on success. */
|
|
93
165
|
deleteFile(filePath: string): Promise<boolean>;
|
|
166
|
+
/** Programmatically close the editor if it is currently open. */
|
|
94
167
|
closeEditor(): void;
|
|
168
|
+
/** Check whether the given URL points to a valid audio or video file. */
|
|
95
169
|
isValidFile(url: string): Promise<FileValidationResult>;
|
|
170
|
+
/** Perform a headless trim (no UI) on the given URL with the specified options. */
|
|
96
171
|
trim(url: string, options: TrimOptions): Promise<TrimResult>;
|
|
172
|
+
/** Emitted when the trim operation starts. */
|
|
97
173
|
readonly onStartTrimming: EventEmitter<void>;
|
|
174
|
+
/** Emitted when the user cancels an in-progress trim operation. */
|
|
98
175
|
readonly onCancelTrimming: EventEmitter<void>;
|
|
176
|
+
/** Emitted when the user dismisses the editor without trimming. */
|
|
99
177
|
readonly onCancel: EventEmitter<void>;
|
|
178
|
+
/** Emitted when the editor is hidden. */
|
|
100
179
|
readonly onHide: EventEmitter<void>;
|
|
180
|
+
/** Emitted when the editor is shown. */
|
|
101
181
|
readonly onShow: EventEmitter<void>;
|
|
182
|
+
/** Emitted when trimming completes successfully. All time values are in milliseconds. */
|
|
102
183
|
readonly onFinishTrimming: EventEmitter<{
|
|
184
|
+
/** Absolute path to the trimmed output file. */
|
|
103
185
|
outputPath: string;
|
|
186
|
+
/** Start time of the trimmed range in milliseconds. */
|
|
104
187
|
startTime: number;
|
|
188
|
+
/** End time of the trimmed range in milliseconds. */
|
|
105
189
|
endTime: number;
|
|
190
|
+
/** Duration of the trimmed clip in milliseconds. */
|
|
106
191
|
duration: number;
|
|
107
192
|
}>;
|
|
193
|
+
/** Emitted with FFmpeg log output during trimming. */
|
|
108
194
|
readonly onLog: EventEmitter<{
|
|
109
195
|
level: string;
|
|
110
196
|
message: string;
|
|
111
197
|
sessionId: number;
|
|
112
198
|
}>;
|
|
199
|
+
/** Emitted with FFmpeg encoding statistics during trimming. */
|
|
113
200
|
readonly onStatistics: EventEmitter<{
|
|
114
201
|
sessionId: number;
|
|
115
202
|
videoFrameNumber: number;
|
|
@@ -120,11 +207,14 @@ export interface Spec extends TurboModule {
|
|
|
120
207
|
bitrate: number;
|
|
121
208
|
speed: number;
|
|
122
209
|
}>;
|
|
210
|
+
/** Emitted when an error occurs during trimming or file loading. */
|
|
123
211
|
readonly onError: EventEmitter<{
|
|
124
212
|
message: string;
|
|
125
213
|
errorCode: string;
|
|
126
214
|
}>;
|
|
215
|
+
/** Emitted when the media file has finished loading in the editor. */
|
|
127
216
|
readonly onLoad: EventEmitter<{
|
|
217
|
+
/** Duration of the loaded media in milliseconds. */
|
|
128
218
|
duration: number;
|
|
129
219
|
}>;
|
|
130
220
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeVideoTrim.d.ts","sourceRoot":"","sources":["../../../src/NativeVideoTrim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAE9E,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB,EAAE,OAAO,CAAC;IACjC,4BAA4B,EAAE,OAAO,CAAC;IACtC,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,oBAAoB,EAAE,OAAO,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,sBAAsB,EAAE,OAAO,CAAC;IAChC,2BAA2B,EAAE,OAAO,CAAC;IACrC,gCAAgC,EAAE,OAAO,CAAC;IAC1C,iBAAiB,EAAE,OAAO,CAAC;IAC3B,wBAAwB,EAAE,OAAO,CAAC;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,wBAAwB,EAAE,MAAM,CAAC;IACjC,0BAA0B,EAAE,OAAO,CAAC;IACpC,yBAAyB,EAAE,MAAM,CAAC;IAClC,2BAA2B,EAAE,MAAM,CAAC;IACpC,8BAA8B,EAAE,MAAM,CAAC;IACvC,+BAA+B,EAAE,MAAM,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAC7B
|
|
1
|
+
{"version":3,"file":"NativeVideoTrim.d.ts","sourceRoot":"","sources":["../../../src/NativeVideoTrim.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-video-trim",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "Video trimmer for your React Native app",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"workspaces": [
|
|
92
92
|
"example"
|
|
93
93
|
],
|
|
94
|
-
"packageManager": "yarn@
|
|
94
|
+
"packageManager": "yarn@4.13.0",
|
|
95
95
|
"jest": {
|
|
96
96
|
"preset": "react-native",
|
|
97
97
|
"modulePathIgnorePatterns": [
|
package/src/NativeVideoTrim.ts
CHANGED
|
@@ -2,122 +2,209 @@ import type { TurboModule } from 'react-native';
|
|
|
2
2
|
import { TurboModuleRegistry } from 'react-native';
|
|
3
3
|
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Base options shared by both the editor and headless trim operations.
|
|
7
|
+
*/
|
|
5
8
|
export interface BaseOptions {
|
|
9
|
+
/** Whether to save the output file to the device's photo library. */
|
|
6
10
|
saveToPhoto: boolean;
|
|
11
|
+
/** Media type: `"video"` or `"audio"`. */
|
|
7
12
|
type: string;
|
|
13
|
+
/** Output file extension (e.g. `"mp4"`, `"wav"`). */
|
|
8
14
|
outputExt: string;
|
|
15
|
+
/** Whether to remove the output file after it has been saved to the photo library. */
|
|
9
16
|
removeAfterSavedToPhoto: boolean;
|
|
17
|
+
/** Whether to remove the output file if saving to the photo library fails. */
|
|
10
18
|
removeAfterFailedToSavePhoto: boolean;
|
|
19
|
+
/** Whether to enable video rotation during trimming. */
|
|
11
20
|
enableRotation: boolean;
|
|
21
|
+
/** Rotation angle in degrees (e.g. `90`, `180`, `270`). Only used when `enableRotation` is `true`. */
|
|
12
22
|
rotationAngle: number;
|
|
13
23
|
}
|
|
14
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Configuration for the video trimmer editor UI.
|
|
27
|
+
*/
|
|
15
28
|
export interface EditorConfig extends BaseOptions {
|
|
29
|
+
/** Whether to enable haptic feedback when interacting with the trimmer. */
|
|
16
30
|
enableHapticFeedback: boolean;
|
|
31
|
+
/** Maximum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|
|
17
32
|
maxDuration: number;
|
|
33
|
+
/** Minimum allowed duration for the trimmed clip in milliseconds. Set to `-1` for no limit. */
|
|
18
34
|
minDuration: number;
|
|
35
|
+
/** Whether to open the system documents/files app after trimming finishes. */
|
|
19
36
|
openDocumentsOnFinish: boolean;
|
|
37
|
+
/** Whether to open the share sheet after trimming finishes. */
|
|
20
38
|
openShareSheetOnFinish: boolean;
|
|
39
|
+
/** Whether to remove the output file after it has been saved to documents. */
|
|
21
40
|
removeAfterSavedToDocuments: boolean;
|
|
41
|
+
/** Whether to remove the output file if saving to documents fails. */
|
|
22
42
|
removeAfterFailedToSaveDocuments: boolean;
|
|
43
|
+
/** Whether to remove the output file after it has been shared. */
|
|
23
44
|
removeAfterShared: boolean;
|
|
45
|
+
/** Whether to remove the output file if sharing fails. */
|
|
24
46
|
removeAfterFailedToShare: boolean;
|
|
47
|
+
/** Text for the cancel button. */
|
|
25
48
|
cancelButtonText: string;
|
|
49
|
+
/** Text for the save button. */
|
|
26
50
|
saveButtonText: string;
|
|
51
|
+
/** Whether to show a confirmation dialog when the cancel button is pressed. */
|
|
27
52
|
enableCancelDialog: boolean;
|
|
53
|
+
/** Title of the cancel confirmation dialog. */
|
|
28
54
|
cancelDialogTitle: string;
|
|
55
|
+
/** Message of the cancel confirmation dialog. */
|
|
29
56
|
cancelDialogMessage: string;
|
|
57
|
+
/** Text for the dismiss button in the cancel confirmation dialog. */
|
|
30
58
|
cancelDialogCancelText: string;
|
|
59
|
+
/** Text for the confirm button in the cancel confirmation dialog. */
|
|
31
60
|
cancelDialogConfirmText: string;
|
|
61
|
+
/** Whether to show a confirmation dialog when the save button is pressed. */
|
|
32
62
|
enableSaveDialog: boolean;
|
|
63
|
+
/** Title of the save confirmation dialog. */
|
|
33
64
|
saveDialogTitle: string;
|
|
65
|
+
/** Message of the save confirmation dialog. */
|
|
34
66
|
saveDialogMessage: string;
|
|
67
|
+
/** Text for the dismiss button in the save confirmation dialog. */
|
|
35
68
|
saveDialogCancelText: string;
|
|
69
|
+
/** Text for the confirm button in the save confirmation dialog. */
|
|
36
70
|
saveDialogConfirmText: string;
|
|
71
|
+
/** Text displayed while the video is being trimmed (e.g. `"Trimming video..."`). */
|
|
37
72
|
trimmingText: string;
|
|
73
|
+
/** iOS only. Whether to present the editor as a full-screen modal. */
|
|
38
74
|
fullScreenModalIOS: boolean;
|
|
75
|
+
/** Whether to auto-play the video when the editor opens. */
|
|
39
76
|
autoplay: boolean;
|
|
77
|
+
/** Position in milliseconds to seek to when the editor loads. Set to `-1` to disable. */
|
|
40
78
|
jumpToPositionOnLoad: number;
|
|
79
|
+
/** Whether to automatically close the editor when trimming finishes. */
|
|
41
80
|
closeWhenFinish: boolean;
|
|
81
|
+
/** Whether to allow the user to cancel an in-progress trim operation. */
|
|
42
82
|
enableCancelTrimming: boolean;
|
|
83
|
+
/** Text for the cancel-trimming button. */
|
|
43
84
|
cancelTrimmingButtonText: string;
|
|
85
|
+
/** Whether to show a confirmation dialog when cancelling an in-progress trim. */
|
|
44
86
|
enableCancelTrimmingDialog: boolean;
|
|
87
|
+
/** Title of the cancel-trimming confirmation dialog. */
|
|
45
88
|
cancelTrimmingDialogTitle: string;
|
|
89
|
+
/** Message of the cancel-trimming confirmation dialog. */
|
|
46
90
|
cancelTrimmingDialogMessage: string;
|
|
91
|
+
/** Text for the dismiss button in the cancel-trimming dialog. */
|
|
47
92
|
cancelTrimmingDialogCancelText: string;
|
|
93
|
+
/** Text for the confirm button in the cancel-trimming dialog. */
|
|
48
94
|
cancelTrimmingDialogConfirmText: string;
|
|
95
|
+
/** Custom header text displayed at the top of the editor. */
|
|
49
96
|
headerText: string;
|
|
97
|
+
/** Font size of the header text in sp/pt. */
|
|
50
98
|
headerTextSize: number;
|
|
99
|
+
/** Color of the header text as a `processColor` value. */
|
|
51
100
|
headerTextColor: number;
|
|
101
|
+
/** Whether to show an alert dialog when the media file fails to load. */
|
|
52
102
|
alertOnFailToLoad: boolean;
|
|
103
|
+
/** Title of the fail-to-load alert. */
|
|
53
104
|
alertOnFailTitle: string;
|
|
105
|
+
/** Message of the fail-to-load alert. */
|
|
54
106
|
alertOnFailMessage: string;
|
|
107
|
+
/** Text for the close button of the fail-to-load alert. */
|
|
55
108
|
alertOnFailCloseText: string;
|
|
56
|
-
/**
|
|
57
|
-
* Android only
|
|
58
|
-
* Update status bar to black background color when editor is opened
|
|
59
|
-
*/
|
|
109
|
+
/** Android only. Whether to update the status bar to a black background when the editor opens. */
|
|
60
110
|
changeStatusBarColorOnOpen?: boolean;
|
|
61
|
-
/**
|
|
62
|
-
* Color of the trimmer bar
|
|
63
|
-
*/
|
|
111
|
+
/** Color of the trimmer bar as a `processColor` value. */
|
|
64
112
|
trimmerColor?: number;
|
|
65
|
-
/**
|
|
66
|
-
* Color of the trimmer left/right handle icons
|
|
67
|
-
*/
|
|
113
|
+
/** Color of the trimmer left/right handle icons as a `processColor` value. */
|
|
68
114
|
handleIconColor?: number;
|
|
69
115
|
/**
|
|
70
|
-
* Duration for zoom-on-waiting feature in milliseconds (default: 5000)
|
|
71
|
-
* When user pauses while dragging trim handles, the view
|
|
72
|
-
* this duration around the current trim position for more precise editing
|
|
116
|
+
* Duration for the zoom-on-waiting feature in milliseconds (default: `5000`).
|
|
117
|
+
* When the user pauses while dragging trim handles, the view zooms to show
|
|
118
|
+
* this duration around the current trim position for more precise editing.
|
|
73
119
|
*/
|
|
74
120
|
zoomOnWaitingDuration?: number;
|
|
75
121
|
}
|
|
76
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Options for headless (non-UI) trim operations.
|
|
125
|
+
*/
|
|
77
126
|
export interface TrimOptions extends BaseOptions {
|
|
127
|
+
/** Start time of the trim range in milliseconds. */
|
|
78
128
|
startTime: number;
|
|
129
|
+
/** End time of the trim range in milliseconds. */
|
|
79
130
|
endTime: number;
|
|
80
131
|
}
|
|
81
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Result returned by {@link Spec.isValidFile}.
|
|
135
|
+
*/
|
|
82
136
|
export interface FileValidationResult {
|
|
137
|
+
/** Whether the file is a valid audio or video file. */
|
|
83
138
|
isValid: boolean;
|
|
139
|
+
/** Detected file type (e.g. `"video"`, `"audio"`, `"unknown"`). */
|
|
84
140
|
fileType: string;
|
|
141
|
+
/** Duration of the media file in milliseconds, or `-1` if invalid. */
|
|
85
142
|
duration: number;
|
|
86
143
|
}
|
|
87
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Result returned by a trim operation (both editor and headless).
|
|
147
|
+
*/
|
|
88
148
|
export interface TrimResult {
|
|
149
|
+
/** Start time of the trimmed range in milliseconds. */
|
|
89
150
|
startTime: number;
|
|
151
|
+
/** End time of the trimmed range in milliseconds. */
|
|
90
152
|
endTime: number;
|
|
153
|
+
/** Duration of the trimmed clip in milliseconds. */
|
|
91
154
|
duration: number;
|
|
155
|
+
/** Absolute path to the trimmed output file. */
|
|
92
156
|
outputPath: string;
|
|
157
|
+
/** Whether the trim operation completed successfully. */
|
|
93
158
|
success: boolean;
|
|
94
159
|
}
|
|
95
160
|
|
|
161
|
+
/**
|
|
162
|
+
* TurboModule spec for the native VideoTrim module.
|
|
163
|
+
*/
|
|
96
164
|
export interface Spec extends TurboModule {
|
|
165
|
+
/** Open the video trimmer editor for the given file. */
|
|
97
166
|
showEditor(filePath: string, config: EditorConfig): void;
|
|
167
|
+
/** List all output files generated by past trim operations. */
|
|
98
168
|
listFiles(): Promise<string[]>;
|
|
169
|
+
/** Delete all output files generated by past trim operations. Returns the number of files removed. */
|
|
99
170
|
cleanFiles(): Promise<number>;
|
|
171
|
+
/** Delete a single file at the given path. Resolves `true` on success. */
|
|
100
172
|
deleteFile(filePath: string): Promise<boolean>;
|
|
173
|
+
/** Programmatically close the editor if it is currently open. */
|
|
101
174
|
closeEditor(): void;
|
|
175
|
+
/** Check whether the given URL points to a valid audio or video file. */
|
|
102
176
|
isValidFile(url: string): Promise<FileValidationResult>;
|
|
177
|
+
/** Perform a headless trim (no UI) on the given URL with the specified options. */
|
|
103
178
|
trim(url: string, options: TrimOptions): Promise<TrimResult>;
|
|
104
179
|
|
|
180
|
+
/** Emitted when the trim operation starts. */
|
|
105
181
|
readonly onStartTrimming: EventEmitter<void>;
|
|
182
|
+
/** Emitted when the user cancels an in-progress trim operation. */
|
|
106
183
|
readonly onCancelTrimming: EventEmitter<void>;
|
|
184
|
+
/** Emitted when the user dismisses the editor without trimming. */
|
|
107
185
|
readonly onCancel: EventEmitter<void>;
|
|
186
|
+
/** Emitted when the editor is hidden. */
|
|
108
187
|
readonly onHide: EventEmitter<void>;
|
|
188
|
+
/** Emitted when the editor is shown. */
|
|
109
189
|
readonly onShow: EventEmitter<void>;
|
|
190
|
+
/** Emitted when trimming completes successfully. All time values are in milliseconds. */
|
|
110
191
|
readonly onFinishTrimming: EventEmitter<{
|
|
192
|
+
/** Absolute path to the trimmed output file. */
|
|
111
193
|
outputPath: string;
|
|
194
|
+
/** Start time of the trimmed range in milliseconds. */
|
|
112
195
|
startTime: number;
|
|
196
|
+
/** End time of the trimmed range in milliseconds. */
|
|
113
197
|
endTime: number;
|
|
198
|
+
/** Duration of the trimmed clip in milliseconds. */
|
|
114
199
|
duration: number;
|
|
115
200
|
}>;
|
|
201
|
+
/** Emitted with FFmpeg log output during trimming. */
|
|
116
202
|
readonly onLog: EventEmitter<{
|
|
117
203
|
level: string;
|
|
118
204
|
message: string;
|
|
119
205
|
sessionId: number;
|
|
120
206
|
}>;
|
|
207
|
+
/** Emitted with FFmpeg encoding statistics during trimming. */
|
|
121
208
|
readonly onStatistics: EventEmitter<{
|
|
122
209
|
sessionId: number;
|
|
123
210
|
videoFrameNumber: number;
|
|
@@ -128,11 +215,14 @@ export interface Spec extends TurboModule {
|
|
|
128
215
|
bitrate: number;
|
|
129
216
|
speed: number;
|
|
130
217
|
}>;
|
|
218
|
+
/** Emitted when an error occurs during trimming or file loading. */
|
|
131
219
|
readonly onError: EventEmitter<{
|
|
132
220
|
message: string;
|
|
133
221
|
errorCode: string;
|
|
134
222
|
}>;
|
|
223
|
+
/** Emitted when the media file has finished loading in the editor. */
|
|
135
224
|
readonly onLoad: EventEmitter<{
|
|
225
|
+
/** Duration of the loaded media in milliseconds. */
|
|
136
226
|
duration: number;
|
|
137
227
|
}>;
|
|
138
228
|
}
|