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
- onTrimmerContainerPanned(event);
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
@@ -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
- progressIndicator.alpha = (trimmingState == .none ? 1 : 0)
587
- progressIndicatorControl.isUserInteractionEnabled = (trimmingState == .none)
588
- if trimmingState == .none {
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.1.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@3.6.1",
94
+ "packageManager": "yarn@4.13.0",
95
95
  "jest": {
96
96
  "preset": "react-native",
97
97
  "modulePathIgnorePatterns": [