react-native-video-trim 6.1.0 → 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.
|
@@ -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);
|
|
@@ -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
|
}
|
|
@@ -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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-video-trim",
|
|
3
|
-
"version": "6.
|
|
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": [
|