react-native-video-trim 2.0.0 → 2.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.
Files changed (31) hide show
  1. package/README.md +168 -33
  2. package/android/src/main/AndroidManifest.xml +13 -0
  3. package/android/src/main/java/com/videotrim/VideoTrimModule.java +282 -75
  4. package/android/src/main/java/com/videotrim/enums/ErrorCode.java +10 -0
  5. package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +4 -1
  6. package/android/src/main/java/com/videotrim/utils/MediaMetadataUtil.java +75 -0
  7. package/android/src/main/java/com/videotrim/utils/StorageUtil.java +2 -2
  8. package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +26 -16
  9. package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +310 -81
  10. package/android/src/main/res/drawable/airpodsmax.xml +19 -0
  11. package/android/src/main/res/drawable/exclamationmark_triangle_fill.xml +15 -0
  12. package/android/src/main/res/drawable/thumb_container_bg.xml +8 -0
  13. package/android/src/main/res/layout/video_trimmer_view.xml +71 -4
  14. package/android/src/main/res/xml/file_paths.xml +5 -0
  15. package/ios/AssetLoader.swift +99 -0
  16. package/ios/ErrorCode.swift +16 -0
  17. package/ios/ProgressAlertController.swift +100 -0
  18. package/ios/VideoTrim.mm +4 -2
  19. package/ios/VideoTrim.swift +472 -177
  20. package/ios/VideoTrimmer.swift +16 -10
  21. package/ios/VideoTrimmerViewController.swift +191 -22
  22. package/lib/commonjs/index.js +25 -55
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/module/index.js +24 -55
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/typescript/index.d.ts +215 -9
  27. package/lib/typescript/index.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/index.tsx +229 -66
  30. package/android/src/main/java/iknow/android/utils/BuildConfig.java +0 -18
  31. package/android/src/main/java/iknow/android/utils/DateUtil.java +0 -64
@@ -8,6 +8,7 @@ import android.content.Context;
8
8
  import android.content.pm.ActivityInfo;
9
9
  import android.content.res.Configuration;
10
10
  import android.graphics.Bitmap;
11
+ import android.graphics.drawable.GradientDrawable;
11
12
  import android.media.MediaMetadataRetriever;
12
13
  import android.media.MediaPlayer;
13
14
  import android.net.Uri;
@@ -17,24 +18,32 @@ import android.os.VibrationEffect;
17
18
  import android.os.Vibrator;
18
19
  import android.util.AttributeSet;
19
20
  import android.util.Log;
21
+ import android.util.TypedValue;
20
22
  import android.view.LayoutInflater;
21
23
  import android.view.MotionEvent;
22
24
  import android.view.View;
23
25
  import android.widget.FrameLayout;
24
26
  import android.widget.ImageView;
25
27
  import android.widget.LinearLayout;
28
+ import android.widget.ProgressBar;
26
29
  import android.widget.RelativeLayout;
27
30
  import android.widget.TextView;
28
31
  import android.widget.VideoView;
29
32
 
33
+ import androidx.appcompat.app.AlertDialog;
34
+
35
+ import com.arthenica.ffmpegkit.FFmpegSession;
30
36
  import com.facebook.react.bridge.ReactApplicationContext;
31
37
  import com.facebook.react.bridge.ReadableMap;
32
38
  import com.videotrim.R;
39
+ import com.videotrim.enums.ErrorCode;
33
40
  import com.videotrim.interfaces.IVideoTrimmerView;
34
41
  import com.videotrim.interfaces.VideoTrimListener;
42
+ import com.videotrim.utils.MediaMetadataUtil;
35
43
  import com.videotrim.utils.StorageUtil;
36
44
  import com.videotrim.utils.VideoTrimmerUtil;
37
45
 
46
+ import java.io.IOException;
38
47
  import java.util.Locale;
39
48
 
40
49
  import iknow.android.utils.DeviceUtil;
@@ -52,7 +61,6 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
52
61
  private Uri mSourceUri;
53
62
  private VideoTrimListener mOnTrimVideoListener;
54
63
  private int mDuration = 0;
55
- private Boolean mIsPrepared = false;
56
64
  private long mMaxDuration = (long) Double.POSITIVE_INFINITY;
57
65
  private long mMinDuration = VideoTrimmerUtil.MIN_SHOOT_DURATION;
58
66
 
@@ -82,6 +90,27 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
82
90
  private final Handler zoomWaitTimer = new Handler();
83
91
  private Runnable zoomRunnable;
84
92
 
93
+ private MediaMetadataRetriever mediaMetadataRetriever;
94
+ private ProgressBar loadingIndicator;
95
+ private TextView saveBtn;
96
+ private TextView cancelBtn;
97
+ private FrameLayout audioBannerView;
98
+ private boolean isVideoType = true;
99
+ private MediaPlayer audioPlayer;
100
+ private ImageView failToLoadBtn;
101
+
102
+ private String mOutputExt = "mp4";
103
+ private boolean enableHapticFeedback = true;
104
+ private boolean autoplay = false;
105
+ private long jumpToPositionOnLoad = 0;
106
+ private FrameLayout headerView;
107
+ private TextView headerText;
108
+ private FFmpegSession ffmpegSession;
109
+ private boolean alertOnFailToLoad = true;
110
+ private String alertOnFailTitle = "Error";
111
+ private String alertOnFailMessage = "Fail to load media. Possibly invalid file or no network connection";
112
+ private String alertOnFailCloseText = "Close";
113
+
85
114
  public VideoTrimmerView(ReactApplicationContext context, ReadableMap config, AttributeSet attrs) {
86
115
  this(context, attrs, 0, config);
87
116
  }
@@ -118,20 +147,82 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
118
147
  trailingHandle = findViewById(R.id.trailingHandle);
119
148
  leadingOverlay = findViewById(R.id.leadingOverlay);
120
149
  trailingOverlay = findViewById(R.id.trailingOverlay);
150
+
121
151
  trimmerContainerWrapper = findViewById(R.id.trimmerContainerWrapper);
122
152
  trimmerContainerWrapper.setVisibility(View.INVISIBLE);
123
153
  trimmerContainerWrapper.setAlpha(0f);
154
+
155
+ loadingIndicator = findViewById(R.id.loadingIndicator);
156
+ saveBtn = findViewById(R.id.saveBtn);
157
+ cancelBtn = findViewById(R.id.cancelBtn);
158
+ audioBannerView = findViewById(R.id.audioBannerView);
159
+ failToLoadBtn = findViewById(R.id.failToLoadBtn);
160
+
161
+ headerView = findViewById(R.id.headerView);
162
+ headerText = findViewById(R.id.headerText);
124
163
  }
125
164
 
126
- public void initVideoByURI(final Uri videoURI) {
165
+ public void initByURI(final Uri videoURI) {
127
166
  mSourceUri = videoURI;
128
- mVideoView.setVideoURI(videoURI);
129
- mVideoView.requestFocus();
167
+
168
+ if (isVideoType) {
169
+ mVideoView.setVideoURI(videoURI);
170
+ mVideoView.requestFocus();
171
+
172
+ mVideoView.setOnPreparedListener(mp -> {
173
+ mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
174
+ mediaPrepared();
175
+ });
176
+
177
+ mVideoView.setOnErrorListener(this::onFailToLoadMedia);
178
+
179
+ mVideoView.setOnCompletionListener(mp -> mediaCompleted());
180
+ } else {
181
+ mVideoView.setVisibility(View.GONE);
182
+ audioBannerView.setAlpha(0f);
183
+ audioBannerView.setVisibility(View.VISIBLE);
184
+ audioBannerView.animate().alpha(1f).setDuration(500).start();
185
+
186
+ audioPlayer = new MediaPlayer();
187
+ try {
188
+ audioPlayer.setDataSource(videoURI.toString());
189
+ audioPlayer.setOnPreparedListener(mp -> {
190
+ mediaPrepared();
191
+ });
192
+ audioPlayer.setOnCompletionListener(mp -> mediaCompleted());
193
+ audioPlayer.setOnErrorListener(this::onFailToLoadMedia);
194
+
195
+ audioPlayer.prepareAsync(); // use prepareAsync to avoid blocking the main thread
196
+ } catch (IOException e) {
197
+ e.printStackTrace();
198
+ mediaFailed();
199
+ mOnTrimVideoListener.onError("Error initializing audio player. Please try again.", ErrorCode.FAIL_TO_INITIALIZE_AUDIO_PLAYER);
200
+ }
201
+ }
202
+ }
203
+
204
+ private boolean onFailToLoadMedia(MediaPlayer mp, int what, int extra) {
205
+ mediaFailed();
206
+ mOnTrimVideoListener.onError("Error loading media file. Please try again.", ErrorCode.FAIL_TO_LOAD_MEDIA);
207
+ if (alertOnFailToLoad) {
208
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext.getCurrentActivity());
209
+ builder.setMessage(alertOnFailMessage);
210
+ builder.setTitle(alertOnFailTitle);
211
+ builder.setCancelable(false);
212
+ builder.setPositiveButton(alertOnFailCloseText, (dialog, which) -> {
213
+ dialog.cancel();
214
+ });
215
+
216
+ AlertDialog alertDialog = builder.create();
217
+ alertDialog.show();
218
+ }
219
+
220
+ return true;
130
221
  }
131
222
 
132
- private void startShootVideoThumbs(final Context context, final Uri videoUri, int totalThumbsCount, long startPosition, long endPosition) {
223
+ private void startShootVideoThumbs(final Context context, int totalThumbsCount, long startPosition, long endPosition) {
133
224
  mThumbnailContainer.removeAllViews();
134
- VideoTrimmerUtil.shootVideoThumbInBackground(context, videoUri, totalThumbsCount, startPosition, endPosition,
225
+ VideoTrimmerUtil.shootVideoThumbInBackground(mediaMetadataRetriever, totalThumbsCount, startPosition, endPosition,
135
226
  (bitmap, interval) -> {
136
227
  if (bitmap != null) {
137
228
  runOnUiThread(() -> {
@@ -147,27 +238,32 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
147
238
  });
148
239
  }
149
240
 
150
- private void videoPrepared(MediaPlayer mp) {
151
- mDuration = mVideoView.getDuration();
152
-
241
+ private void mediaPrepared() {
242
+ mDuration = isVideoType ? mVideoView.getDuration() : audioPlayer.getDuration();
153
243
  mMaxDuration = Math.min(mMaxDuration, mDuration);
154
244
 
155
- MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
156
- mediaMetadataRetriever.setDataSource(mContext, mSourceUri);
157
- // take first frame
158
- Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
245
+ if (isVideoType) {
246
+ mediaMetadataRetriever = MediaMetadataUtil.getMediaMetadataRetriever(mSourceUri.toString());
247
+ if (mediaMetadataRetriever == null) {
248
+ mOnTrimVideoListener.onError("Error when retrieving video info. Please try again.", ErrorCode.FAIL_TO_GET_VIDEO_INFO);
249
+ return;
250
+ }
159
251
 
160
- if (bitmap != null) {
161
- VideoTrimmerUtil.mThumbWidth = VideoTrimmerUtil.THUMB_HEIGHT * bitmap.getWidth() / bitmap.getHeight();
162
- }
252
+ // take first frame
253
+ Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
163
254
 
164
- VideoTrimmerUtil.SCREEN_WIDTH_FULL = this.getScreenWidthInPortraitMode();
165
- VideoTrimmerUtil.VIDEO_FRAMES_WIDTH = VideoTrimmerUtil.SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
166
- VideoTrimmerUtil.MAX_COUNT_RANGE = Math.max((VIDEO_FRAMES_WIDTH / VideoTrimmerUtil.mThumbWidth), VideoTrimmerUtil.MAX_COUNT_RANGE);
255
+ if (bitmap != null) {
256
+ VideoTrimmerUtil.mThumbWidth = VideoTrimmerUtil.THUMB_HEIGHT * bitmap.getWidth() / bitmap.getHeight();
257
+ }
167
258
 
168
- int mThumbsTotalCount;
169
- mThumbsTotalCount = (int) (mDuration * 1.0f / (mMaxDuration * 1.0f) * VideoTrimmerUtil.MAX_COUNT_RANGE);
170
- startShootVideoThumbs(mContext, mSourceUri, mThumbsTotalCount, 0, mDuration);
259
+ VideoTrimmerUtil.SCREEN_WIDTH_FULL = this.getScreenWidthInPortraitMode();
260
+ VideoTrimmerUtil.VIDEO_FRAMES_WIDTH = VideoTrimmerUtil.SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
261
+ VideoTrimmerUtil.MAX_COUNT_RANGE = Math.max((VIDEO_FRAMES_WIDTH / VideoTrimmerUtil.mThumbWidth), VideoTrimmerUtil.MAX_COUNT_RANGE);
262
+
263
+ startShootVideoThumbs(mContext, VideoTrimmerUtil.MAX_COUNT_RANGE, 0, mDuration);
264
+ } else {
265
+
266
+ }
171
267
 
172
268
  // Set initial handle positions if mMaxDuration < video duration
173
269
  if (mMaxDuration < mDuration) {
@@ -176,11 +272,40 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
176
272
  endTime = mDuration;
177
273
  }
178
274
  updateHandlePositions();
275
+
276
+ loadingIndicator.setVisibility(View.GONE);
277
+ mPlayView.setVisibility(View.VISIBLE);
278
+ saveBtn.setVisibility(View.VISIBLE);
279
+
280
+ if (jumpToPositionOnLoad > 0) {
281
+ seekTo(jumpToPositionOnLoad > mDuration ? mDuration : jumpToPositionOnLoad, true);
282
+ }
283
+
284
+ if (autoplay) {
285
+ playOrPause();
286
+ }
287
+
288
+ mOnTrimVideoListener.onLoad(mDuration);
289
+ }
290
+
291
+ private void updateGradientColors(int startColor, int endColor) {
292
+ GradientDrawable gradientDrawable = new GradientDrawable();
293
+ gradientDrawable.setShape(GradientDrawable.RECTANGLE);
294
+ gradientDrawable.setCornerRadius(6f); // Adjust corner radius as needed
295
+ gradientDrawable.setColors(new int[]{startColor, endColor});
296
+ gradientDrawable.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
297
+
298
+ mThumbnailContainer.setBackground(gradientDrawable);
299
+ }
300
+
301
+ private void mediaFailed() {
302
+ loadingIndicator.setVisibility(View.GONE);
303
+ failToLoadBtn.setVisibility(View.VISIBLE);
179
304
  }
180
305
 
181
306
  private void updateHandlePositions() {
182
- float startPercent = (float) startTime / mVideoView.getDuration();
183
- float endPercent = (float) endTime / mVideoView.getDuration();
307
+ float startPercent = (float) startTime / mDuration;
308
+ float endPercent = (float) endTime / mDuration;
184
309
 
185
310
  float containerWidth = trimmerContainerBg.getWidth();
186
311
  float leadingHandleX = startPercent * containerWidth;
@@ -194,36 +319,54 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
194
319
 
195
320
  trimmerContainerWrapper.setVisibility(View.VISIBLE);
196
321
  trimmerContainerWrapper.animate().alpha(1f).setDuration(250).start();
197
-
198
- // because on load video will not start and just display black screen
199
- // here we'll seek to first frame to make it more friendly
200
- mVideoView.seekTo(1);
201
322
  }
202
323
 
203
- private void videoCompleted() {
324
+ private void mediaCompleted() {
204
325
  setPlayPauseViewIcon(false);
205
326
  mTimingHandler.removeCallbacks(mTimingRunnable);
206
327
  }
207
328
 
208
329
  private void playOrPause() {
209
- if (mVideoView.isPlaying()) {
210
- onVideoPause();
330
+ if (isVideoType) {
331
+ if (mVideoView.isPlaying()) {
332
+ onMediaPause();
333
+ } else {
334
+ // if current video time >= end time, seek to start time
335
+ if (mVideoView.getCurrentPosition() >= endTime) {
336
+ seekTo(startTime, true);
337
+ }
338
+ mVideoView.start();
339
+ startTimingRunnable();
340
+ }
341
+ setPlayPauseViewIcon(mVideoView.isPlaying());
342
+
211
343
  } else {
212
- // if current video time >= end time, seek to start time
213
- if (mVideoView.getCurrentPosition() >= endTime) {
214
- seekTo(startTime, true);
344
+ if (audioPlayer.isPlaying()) {
345
+ onMediaPause();
346
+ } else {
347
+ if (audioPlayer.getCurrentPosition() >= endTime) {
348
+ seekTo(startTime, true);
349
+ }
350
+ audioPlayer.start();
351
+ startTimingRunnable();
215
352
  }
216
- mVideoView.start();
217
- startTimingRunnable();
353
+ setPlayPauseViewIcon(audioPlayer.isPlaying());
218
354
  }
219
- setPlayPauseViewIcon(mVideoView.isPlaying());
220
355
  }
221
356
 
222
- public void onVideoPause() {
223
- if (mVideoView.isPlaying()) {
224
- mTimingHandler.removeCallbacks(mTimingRunnable);
225
- mVideoView.pause();
226
- setPlayPauseViewIcon(false);
357
+ public void onMediaPause() {
358
+ if (isVideoType) {
359
+ if (mVideoView.isPlaying()) {
360
+ mTimingHandler.removeCallbacks(mTimingRunnable);
361
+ mVideoView.pause();
362
+ setPlayPauseViewIcon(false);
363
+ }
364
+ } else {
365
+ if (audioPlayer.isPlaying()) {
366
+ mTimingHandler.removeCallbacks(mTimingRunnable);
367
+ audioPlayer.pause();
368
+ setPlayPauseViewIcon(false);
369
+ }
227
370
  }
228
371
  }
229
372
 
@@ -232,36 +375,39 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
232
375
  }
233
376
 
234
377
  private void setUpListeners() {
235
- findViewById(R.id.cancelBtn).setOnClickListener(view -> mOnTrimVideoListener.onCancel());
236
- findViewById(R.id.saveBtn).setOnClickListener(view -> mOnTrimVideoListener.onSave());
237
-
238
- mVideoView.setOnPreparedListener(mp -> {
239
- if (!mIsPrepared) {
240
- mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
241
- videoPrepared(mp);
242
- mIsPrepared = true;
243
- }
244
- });
245
-
246
- mVideoView.setOnCompletionListener(mp -> videoCompleted());
378
+ cancelBtn.setOnClickListener(view -> mOnTrimVideoListener.onCancel());
379
+ saveBtn.setOnClickListener(view -> mOnTrimVideoListener.onSave());
247
380
  mPlayView.setOnClickListener(view -> playOrPause());
248
381
  setHandleTouchListener(leadingHandle, true);
249
382
  setHandleTouchListener(trailingHandle, false);
250
383
  }
251
384
 
252
385
  public void onSaveClicked() {
253
- onVideoPause();
254
- VideoTrimmerUtil.trim(
255
- mSourceUri.getPath(),
256
- StorageUtil.getOutputPath(mContext),
386
+ onMediaPause();
387
+ ffmpegSession = VideoTrimmerUtil.trim(
388
+ mSourceUri.toString(),
389
+ StorageUtil.getOutputPath(mContext, mOutputExt),
257
390
  mDuration,
258
391
  startTime,
259
392
  endTime,
260
393
  mOnTrimVideoListener);
261
394
  }
262
395
 
396
+ public void onCancelTrimClicked() {
397
+ if (ffmpegSession != null) {
398
+ ffmpegSession.cancel();
399
+ } else {
400
+ mOnTrimVideoListener.onCancelTrim();
401
+ }
402
+ }
403
+
263
404
  private void seekTo(long msec, boolean needUpdateProgress) {
264
- mVideoView.seekTo((int) msec);
405
+ if (isVideoType) {
406
+ mVideoView.seekTo((int) msec);
407
+ } else {
408
+ audioPlayer.seekTo((int) msec);
409
+ }
410
+
265
411
  updateCurrentTime(needUpdateProgress);
266
412
  }
267
413
 
@@ -283,6 +429,19 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
283
429
  mContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
284
430
  mTimingHandler.removeCallbacks(mTimingRunnable);
285
431
  zoomWaitTimer.removeCallbacks(zoomRunnable);
432
+
433
+ try {
434
+ if (mediaMetadataRetriever != null) {
435
+ mediaMetadataRetriever.release();
436
+ }
437
+ } catch (Exception e) {
438
+ e.printStackTrace();
439
+ }
440
+
441
+ if (audioPlayer != null) {
442
+ audioPlayer.stop();
443
+ audioPlayer.release();
444
+ }
286
445
  }
287
446
 
288
447
  private int getScreenWidthInPortraitMode() {
@@ -303,12 +462,70 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
303
462
  mMinDuration = Math.max(1000L, config.getInt("minDuration") * 1000L);
304
463
  }
305
464
  if (config.hasKey("cancelButtonText")) {
306
- TextView tv = findViewById(R.id.cancelBtn);
307
- tv.setText(config.getString("cancelButtonText"));
465
+ cancelBtn.setText(config.getString("cancelButtonText"));
308
466
  }
309
467
  if (config.hasKey("saveButtonText")) {
310
- TextView tv = findViewById(R.id.saveBtn);
311
- tv.setText(config.getString("saveButtonText"));
468
+ saveBtn.setText(config.getString("saveButtonText"));
469
+ }
470
+
471
+ if (config.hasKey("type")) {
472
+ isVideoType = !config.getString("type").equals("audio");
473
+
474
+ // if (!isVideoType) {
475
+ // mThumbnailContainer.setAlpha(0f);
476
+ // mThumbnailContainer.setBackground(ContextCompat.getDrawable(mContext, R.drawable.thumb_container_bg));
477
+ // }
478
+ }
479
+
480
+ if (config.hasKey("outputExt")) {
481
+ mOutputExt = config.getString("outputExt");
482
+ } else if (!isVideoType) {
483
+ mOutputExt = "wav";
484
+ }
485
+
486
+ if (config.hasKey("enableHapticFeedback")) {
487
+ enableHapticFeedback = config.getBoolean("enableHapticFeedback");
488
+ }
489
+
490
+ if (config.hasKey("autoplay")) {
491
+ autoplay = config.getBoolean("autoplay");
492
+ }
493
+
494
+ if (config.hasKey("jumpToPositionOnLoad")) {
495
+ jumpToPositionOnLoad = config.getInt("jumpToPositionOnLoad");
496
+ }
497
+ // check if config.getString("headerText") is not empty
498
+
499
+ if (config.hasKey("headerText") && !config.getString("headerText").isEmpty()){
500
+ headerText.setText(config.getString("headerText"));
501
+
502
+ if (config.hasKey("headerTextSize")) {
503
+ int textSize = config.getInt("headerTextSize");
504
+ if (textSize < 0) {
505
+ textSize = 16;
506
+ }
507
+ headerText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize);
508
+ }
509
+
510
+ if (config.hasKey("headerTextColor")) {
511
+ headerText.setTextColor(config.getInt("headerTextColor"));
512
+ }
513
+
514
+ headerView.setVisibility(View.VISIBLE);
515
+ }
516
+
517
+ alertOnFailToLoad = !config.hasKey("alertOnFailToLoad") || config.getBoolean("alertOnFailToLoad");
518
+
519
+ if (config.hasKey("alertOnFailTitle")) {
520
+ alertOnFailTitle = config.getString("alertOnFailTitle");
521
+ }
522
+
523
+ if (config.hasKey("alertOnFailMessage")) {
524
+ alertOnFailMessage = config.getString("alertOnFailMessage");
525
+ }
526
+
527
+ if (config.hasKey("alertOnFailCloseText")) {
528
+ alertOnFailCloseText = config.getString("alertOnFailCloseText");
312
529
  }
313
530
  }
314
531
 
@@ -316,10 +533,15 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
316
533
  mTimingRunnable = new Runnable() {
317
534
  @Override
318
535
  public void run() {
319
- int currentPosition = mVideoView.getCurrentPosition();
536
+ int currentPosition;
537
+ if (isVideoType) {
538
+ currentPosition = mVideoView.getCurrentPosition();
539
+ } else {
540
+ currentPosition = audioPlayer.getCurrentPosition();
541
+ }
320
542
 
321
543
  if (currentPosition >= endTime) {
322
- onVideoPause();
544
+ onMediaPause();
323
545
  seekTo(endTime, true); // Ensure exact end time display
324
546
  } else {
325
547
  updateCurrentTime(true);
@@ -332,8 +554,15 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
332
554
 
333
555
  private void updateCurrentTime(boolean needUpdateProgress) {
334
556
  // TODO: check the case after drag the progress indicator and hit play, it'll play a little bit earlier than the progress indicator
335
- int currentPosition = mVideoView.getCurrentPosition();
336
- int duration = mVideoView.getDuration();
557
+
558
+ int currentPosition;
559
+ if (isVideoType) {
560
+ currentPosition = mVideoView.getCurrentPosition();
561
+ } else {
562
+ currentPosition = audioPlayer.getCurrentPosition();
563
+ }
564
+
565
+ int duration = mDuration;
337
566
 
338
567
  if (currentPosition >= duration - 100) {
339
568
  currentPosition = duration;
@@ -373,7 +602,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
373
602
  switch (event.getAction()) {
374
603
  case MotionEvent.ACTION_DOWN:
375
604
  didClampWhilePanning = false;
376
- onVideoPause();
605
+ onMediaPause();
377
606
  onTrimmerContainerPanned(event);
378
607
  playHapticFeedback(true);
379
608
  break;
@@ -402,8 +631,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
402
631
  // check play haptic feedback
403
632
  if (newX <= leftBoundary) {
404
633
  didClamp = true;
405
- }
406
- else if (newX >= rightBoundary) {
634
+ } else if (newX >= rightBoundary) {
407
635
  didClamp = true;
408
636
  }
409
637
 
@@ -418,16 +646,17 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
418
646
 
419
647
  // TODO: check this
420
648
  float indicatorPositionPercent = indicatorPosition / (trimmerContainerBg.getWidth() - progressIndicator.getWidth());
421
- long newVideoPosition = (long) (indicatorPositionPercent * mVideoView.getDuration());
649
+ long newVideoPosition = (long) (indicatorPositionPercent * mDuration);
422
650
 
423
651
  seekTo(newVideoPosition, false);
424
652
  }
653
+
425
654
  private void setHandleTouchListener(View handle, boolean isLeading) {
426
655
  handle.setOnTouchListener((view, event) -> {
427
656
  switch (event.getAction()) {
428
657
  case MotionEvent.ACTION_DOWN:
429
658
  didClampWhilePanning = false;
430
- onVideoPause();
659
+ onMediaPause();
431
660
  fadeOutProgressIndicator();
432
661
  seekTo(isLeading ? startTime : endTime, true);
433
662
  playHapticFeedback(true);
@@ -446,7 +675,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
446
675
  // Calculate new startTime or endTime
447
676
  if (isLeading) {
448
677
  // Calculate the new startTime based on the handle's new position
449
- long newStartTime = (long) ((newX / trimmerContainerBg.getWidth()) * mVideoView.getDuration());
678
+ long newStartTime = (long) ((newX / trimmerContainerBg.getWidth()) * mDuration);
450
679
  // Calculate the duration between the new startTime and the current endTime
451
680
  long duration = endTime - newStartTime;
452
681
  if (duration >= mMinDuration && duration <= mMaxDuration) {
@@ -458,19 +687,19 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
458
687
  // If the duration is less than the minimum, set startTime to the maximum possible to maintain the minimum duration
459
688
  startTime = endTime - mMinDuration;
460
689
  // Adjust the handle position accordingly
461
- view.setX((float) startTime / mVideoView.getDuration() * trimmerContainerBg.getWidth());
690
+ view.setX((float) startTime / mDuration * trimmerContainerBg.getWidth());
462
691
  progressIndicator.setX(view.getX() + view.getWidth());
463
692
  } else {
464
693
  didClamp = true;
465
694
  // If the duration is greater than the maximum, set startTime to the minimum possible to maintain the maximum duration
466
695
  startTime = endTime - mMaxDuration;
467
696
  // Adjust the handle position accordingly
468
- view.setX((float) startTime / mVideoView.getDuration() * trimmerContainerBg.getWidth());
697
+ view.setX((float) startTime / mDuration * trimmerContainerBg.getWidth());
469
698
  progressIndicator.setX(view.getX() + view.getWidth());
470
699
  }
471
700
  } else {
472
701
  // Calculate the new endTime based on the handle's new position
473
- long newEndTime = (long) (((newX - view.getWidth()) / trimmerContainerBg.getWidth()) * mVideoView.getDuration());
702
+ long newEndTime = (long) (((newX - view.getWidth()) / trimmerContainerBg.getWidth()) * mDuration);
474
703
  // Calculate the duration between the new endTime and the current startTime
475
704
  long duration = newEndTime - startTime;
476
705
  if (duration >= mMinDuration && duration <= mMaxDuration) {
@@ -482,14 +711,14 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
482
711
  // If the duration is less than the minimum, set endTime to the minimum possible to maintain the minimum duration
483
712
  endTime = startTime + mMinDuration;
484
713
  // Adjust the handle position accordingly
485
- view.setX((float) endTime / mVideoView.getDuration() * trimmerContainerBg.getWidth() + view.getWidth());
714
+ view.setX((float) endTime / mDuration * trimmerContainerBg.getWidth() + view.getWidth());
486
715
  progressIndicator.setX(view.getX() - progressIndicator.getWidth());
487
716
  } else {
488
717
  didClamp = true;
489
718
  // If the duration is greater than the maximum, set endTime to the maximum possible to maintain the maximum duration
490
719
  endTime = startTime + mMaxDuration;
491
720
  // Adjust the handle position accordingly
492
- view.setX((float) endTime / mVideoView.getDuration() * trimmerContainerBg.getWidth() + view.getWidth());
721
+ view.setX((float) endTime / mDuration * trimmerContainerBg.getWidth() + view.getWidth());
493
722
  progressIndicator.setX(view.getX() - progressIndicator.getWidth());
494
723
  }
495
724
  }
@@ -544,7 +773,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
544
773
  }
545
774
 
546
775
  private void playHapticFeedback(boolean isLight) {
547
- if (vibrator != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
776
+ if (vibrator != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && enableHapticFeedback) {
548
777
  vibrator.vibrate(VibrationEffect.createOneShot(isLight ? 10 : 25, VibrationEffect.DEFAULT_AMPLITUDE)); // Light vibration
549
778
  }
550
779
  }
@@ -556,7 +785,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
556
785
  }
557
786
 
558
787
  zoomRunnable = () -> {
559
- Log.i("tag","A Kiss after 500ms");
788
+ Log.i("tag", "A Kiss after 500ms");
560
789
  stopZoomWaitTimer();
561
790
  zoomIfNeeded();
562
791
  };
@@ -578,7 +807,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
578
807
  return;
579
808
  }
580
809
 
581
- startShootVideoThumbs(mContext, mSourceUri, 10, 5000, 10000);
810
+ startShootVideoThumbs(mContext, 10, 5000, 10000);
582
811
 
583
812
  isZoomedIn = true;
584
813
  }
@@ -0,0 +1,19 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="165.48dp"
3
+ android:height="177.56dp"
4
+ android:viewportWidth="165.48"
5
+ android:viewportHeight="177.56">
6
+ <path
7
+ android:fillColor="#FF000000"
8
+ android:pathData="M0,0h165.48v177.56h-165.48z"
9
+ android:strokeAlpha="0"
10
+ android:fillAlpha="0"/>
11
+ <path
12
+ android:pathData="M5.3,102.25L11.93,102.25L11.93,85.75L5.3,85.75ZM42.93,177.56C52.3,177.56 56.8,168.88 51.61,159.69L20.93,105.25C18.55,101 14.86,99.06 10.36,99.06C2.68,99.06 -1.01,105.25 0.24,112.69C3.05,123.94 10.86,140.88 19.05,153.75C29.61,171.38 35.99,177.56 42.93,177.56ZM160.18,102.25L160.18,85.75L153.55,85.75L153.55,102.25ZM122.55,177.56C129.49,177.56 135.86,171.38 146.43,153.75C154.61,140.88 162.43,123.94 165.24,112.69C166.49,105.25 162.8,99.06 155.11,99.06C150.61,99.06 146.93,101 144.55,105.25L113.86,159.69C108.68,168.88 113.18,177.56 122.55,177.56ZM2.68,84.88C2.68,88.31 4.93,90.56 8.43,90.56C11.86,90.56 14.05,88.31 14.05,84.88C14.36,41.31 40.68,13.38 82.74,13.38C124.8,13.38 151.11,41.31 151.43,84.88C151.43,88.31 153.61,90.56 157.05,90.56C160.55,90.56 162.8,88.31 162.8,84.88C162.8,35.56 132.24,1.94 82.74,1.94C33.3,1.94 2.68,35.56 2.68,84.88Z"
13
+ android:fillColor="#ffffff"
14
+ android:fillAlpha="1"/>
15
+ <path
16
+ android:pathData="M65.3,161.75C70.49,158.81 71.86,152.38 68.61,146.63L41.99,99.56C38.68,93.75 32.55,91.75 27.36,94.69C24.3,96.44 23.61,99.19 25.24,102.06L57.86,159.81C59.49,162.75 62.3,163.5 65.3,161.75ZM100.18,161.75C103.18,163.5 105.99,162.75 107.61,159.81L140.24,102.06C141.86,99.19 141.18,96.44 138.11,94.69C132.93,91.75 126.8,93.75 123.49,99.56L96.86,146.63C93.61,152.38 94.99,158.81 100.18,161.75ZM35.74,40.69C48.74,33.56 64.43,29.81 82.74,29.81C101.05,29.81 116.74,33.56 129.74,40.69C119.3,27.81 104.05,20.81 82.74,20.81C61.43,20.81 46.18,27.81 35.74,40.69Z"
17
+ android:fillColor="#ffffff"
18
+ android:fillAlpha="0.5"/>
19
+ </vector>
@@ -0,0 +1,15 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
+ android:width="31.898dp"
3
+ android:height="28.887dp"
4
+ android:viewportWidth="31.898"
5
+ android:viewportHeight="28.887">
6
+ <path
7
+ android:fillColor="#FF000000"
8
+ android:pathData="M0,0h31.898v28.887h-31.898z"
9
+ android:strokeAlpha="0"
10
+ android:fillAlpha="0"/>
11
+ <path
12
+ android:pathData="M19.113,1.945L30.961,22.664C31.348,23.332 31.547,24.059 31.547,24.762C31.547,26.93 30.07,28.641 27.621,28.641L3.914,28.641C1.477,28.641 0,26.93 0,24.762C0,24.059 0.188,23.344 0.586,22.664L12.434,1.945C13.172,0.645 14.461,0 15.773,0C17.074,0 18.375,0.645 19.113,1.945ZM14.238,22.02C14.238,22.828 14.941,23.496 15.773,23.496C16.594,23.496 17.309,22.84 17.309,22.02C17.309,21.188 16.605,20.531 15.773,20.531C14.93,20.531 14.238,21.199 14.238,22.02ZM14.519,9.258L14.707,17.508C14.719,18.211 15.094,18.609 15.773,18.609C16.418,18.609 16.805,18.223 16.816,17.508L17.004,9.27C17.016,8.566 16.488,8.051 15.762,8.051C15.023,8.051 14.508,8.555 14.519,9.258Z"
13
+ android:fillColor="#ffffff"
14
+ android:fillAlpha="1"/>
15
+ </vector>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <shape xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:shape="rectangle">
4
+ <corners android:radius="6dp" />
5
+ <gradient
6
+ android:startColor="#FF5757"
7
+ android:endColor="#8C52FF" />
8
+ </shape>