react-native-video-trim 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +33 -9
  2. package/android/src/main/AndroidManifest.xml +13 -0
  3. package/android/src/main/java/com/videotrim/VideoTrimModule.java +185 -64
  4. package/android/src/main/java/com/videotrim/enums/ErrorCode.java +11 -0
  5. package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +2 -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 +15 -8
  9. package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +239 -70
  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 +51 -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/VideoTrim.mm +4 -2
  18. package/ios/VideoTrim.swift +380 -167
  19. package/ios/VideoTrimmer.swift +16 -10
  20. package/ios/VideoTrimmerViewController.swift +78 -12
  21. package/lib/commonjs/index.js +20 -57
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/module/index.js +19 -57
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/typescript/index.d.ts +47 -9
  26. package/lib/typescript/index.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/index.tsx +56 -66
  29. package/android/src/main/java/iknow/android/utils/BuildConfig.java +0 -18
  30. package/android/src/main/java/iknow/android/utils/DateUtil.java +0 -64
@@ -1,14 +1,13 @@
1
1
  package com.videotrim.utils;
2
2
 
3
- import android.content.Context;
4
3
  import android.graphics.Bitmap;
5
4
  import android.media.MediaMetadataRetriever;
6
- import android.net.Uri;
7
5
  import com.arthenica.ffmpegkit.FFmpegKit;
8
6
  import com.arthenica.ffmpegkit.ReturnCode;
9
7
  import com.arthenica.ffmpegkit.SessionState;
10
8
  import com.facebook.react.bridge.Arguments;
11
9
  import com.facebook.react.bridge.WritableMap;
10
+ import com.videotrim.enums.ErrorCode;
12
11
  import com.videotrim.interfaces.VideoTrimListener;
13
12
 
14
13
  import java.text.SimpleDateFormat;
@@ -31,6 +30,7 @@ public class VideoTrimmerUtil {
31
30
  public static int MAX_COUNT_RANGE = 10; // how many images in the highlight range of seek bar
32
31
  public static int SCREEN_WIDTH_FULL = DeviceUtil.getDeviceWidth();
33
32
  public static final int RECYCLER_VIEW_PADDING = UnitConverter.dpToPx(35);
33
+ public static String DEFAULT_AUDIO_EXTENSION = ".wav";
34
34
  public static int VIDEO_FRAMES_WIDTH = SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
35
35
  // public static final int THUMB_WIDTH = (SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2) / VIDEO_MAX_TIME;
36
36
  public static int mThumbWidth = 0; // make it automatic
@@ -65,6 +65,7 @@ public class VideoTrimmerUtil {
65
65
  "creation_time=" + formattedDateTime,
66
66
  outputFile
67
67
  };
68
+ System.out.println("Commandddddd: " + String.join(",", cmds));
68
69
 
69
70
  FFmpegKit.executeWithArgumentsAsync(cmds, session -> {
70
71
  SessionState state = session.getState();
@@ -77,7 +78,7 @@ public class VideoTrimmerUtil {
77
78
  else {
78
79
  // CANCEL + FAILURE
79
80
  String errorMessage = String.format("Command failed with state %s and rc %s.%s", state, returnCode, session.getFailStackTrace());
80
- callback.onError(errorMessage);
81
+ callback.onError(errorMessage, ErrorCode.TRIMMING_FAILED);
81
82
  }
82
83
  }, log -> {
83
84
  System.out.println("FFmpeg process started with log " + log.getMessage());
@@ -110,18 +111,25 @@ public class VideoTrimmerUtil {
110
111
  });
111
112
  }
112
113
 
113
- public static void shootVideoThumbInBackground(final Context context, final Uri videoUri, final int totalThumbsCount, final long startPosition,
114
+ public static void shootVideoThumbInBackground(final MediaMetadataRetriever mediaMetadataRetriever, final int totalThumbsCount, final long startPosition,
114
115
  final long endPosition, final SingleCallback<Bitmap, Integer> callback) {
115
116
  BackgroundExecutor.execute(new BackgroundExecutor.Task("", 0L, "") {
116
117
  @Override public void execute() {
117
118
  try {
118
- MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
119
- mediaMetadataRetriever.setDataSource(context, videoUri);
120
119
  // Retrieve media data use microsecond
121
120
  long interval = (endPosition - startPosition) / (totalThumbsCount - 1);
122
121
  for (long i = 0; i < totalThumbsCount; ++i) {
123
122
  long frameTime = startPosition + interval * i;
124
- Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(frameTime * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
123
+
124
+ Bitmap bitmap;
125
+ try {
126
+ bitmap = mediaMetadataRetriever.getFrameAtTime(frameTime * 1000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
127
+ } catch (final Throwable t) {
128
+ // this can happen while thumbnails are being generated in background and we press Cancel
129
+ t.printStackTrace();
130
+ break;
131
+ }
132
+
125
133
  if(bitmap == null) continue;
126
134
  try {
127
135
  bitmap = Bitmap.createScaledBitmap(bitmap, mThumbWidth * THUMB_RESOLUTION_RES, THUMB_HEIGHT * THUMB_RESOLUTION_RES, false);
@@ -130,7 +138,6 @@ public class VideoTrimmerUtil {
130
138
  }
131
139
  callback.onSingleCallback(bitmap, (int) interval);
132
140
  }
133
- mediaMetadataRetriever.release();
134
141
  } catch (final Throwable e) {
135
142
  Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
136
143
  }
@@ -1,6 +1,7 @@
1
1
  package com.videotrim.widgets;
2
2
 
3
3
  import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
4
+ import static com.videotrim.utils.VideoTrimmerUtil.DEFAULT_AUDIO_EXTENSION;
4
5
  import static com.videotrim.utils.VideoTrimmerUtil.RECYCLER_VIEW_PADDING;
5
6
  import static com.videotrim.utils.VideoTrimmerUtil.VIDEO_FRAMES_WIDTH;
6
7
 
@@ -8,6 +9,9 @@ import android.content.Context;
8
9
  import android.content.pm.ActivityInfo;
9
10
  import android.content.res.Configuration;
10
11
  import android.graphics.Bitmap;
12
+ import android.graphics.Color;
13
+ import android.graphics.drawable.Drawable;
14
+ import android.graphics.drawable.GradientDrawable;
11
15
  import android.media.MediaMetadataRetriever;
12
16
  import android.media.MediaPlayer;
13
17
  import android.net.Uri;
@@ -20,21 +24,29 @@ import android.util.Log;
20
24
  import android.view.LayoutInflater;
21
25
  import android.view.MotionEvent;
22
26
  import android.view.View;
27
+ import android.view.ViewGroup;
23
28
  import android.widget.FrameLayout;
24
29
  import android.widget.ImageView;
25
30
  import android.widget.LinearLayout;
31
+ import android.widget.ProgressBar;
26
32
  import android.widget.RelativeLayout;
27
33
  import android.widget.TextView;
28
34
  import android.widget.VideoView;
29
35
 
36
+ import androidx.core.content.ContextCompat;
37
+ import androidx.core.content.res.ResourcesCompat;
38
+
30
39
  import com.facebook.react.bridge.ReactApplicationContext;
31
40
  import com.facebook.react.bridge.ReadableMap;
32
41
  import com.videotrim.R;
42
+ import com.videotrim.enums.ErrorCode;
33
43
  import com.videotrim.interfaces.IVideoTrimmerView;
34
44
  import com.videotrim.interfaces.VideoTrimListener;
45
+ import com.videotrim.utils.MediaMetadataUtil;
35
46
  import com.videotrim.utils.StorageUtil;
36
47
  import com.videotrim.utils.VideoTrimmerUtil;
37
48
 
49
+ import java.io.IOException;
38
50
  import java.util.Locale;
39
51
 
40
52
  import iknow.android.utils.DeviceUtil;
@@ -82,6 +94,18 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
82
94
  private final Handler zoomWaitTimer = new Handler();
83
95
  private Runnable zoomRunnable;
84
96
 
97
+ private MediaMetadataRetriever mediaMetadataRetriever;
98
+ private ProgressBar loadingIndicator;
99
+ private TextView saveBtn;
100
+ private TextView cancelBtn;
101
+ private FrameLayout audioBannerView;
102
+ private boolean isVideoType = true;
103
+ private MediaPlayer audioPlayer;
104
+ private ImageView failToLoadBtn;
105
+
106
+ private String mOutputExt = "mp4";
107
+ private boolean enableHapticFeedback = true;
108
+
85
109
  public VideoTrimmerView(ReactApplicationContext context, ReadableMap config, AttributeSet attrs) {
86
110
  this(context, attrs, 0, config);
87
111
  }
@@ -118,20 +142,74 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
118
142
  trailingHandle = findViewById(R.id.trailingHandle);
119
143
  leadingOverlay = findViewById(R.id.leadingOverlay);
120
144
  trailingOverlay = findViewById(R.id.trailingOverlay);
145
+
121
146
  trimmerContainerWrapper = findViewById(R.id.trimmerContainerWrapper);
122
147
  trimmerContainerWrapper.setVisibility(View.INVISIBLE);
123
148
  trimmerContainerWrapper.setAlpha(0f);
149
+
150
+ loadingIndicator = findViewById(R.id.loadingIndicator);
151
+ saveBtn = findViewById(R.id.saveBtn);
152
+ cancelBtn = findViewById(R.id.cancelBtn);
153
+ audioBannerView = findViewById(R.id.audioBannerView);
154
+ failToLoadBtn = findViewById(R.id.failToLoadBtn);
124
155
  }
125
156
 
126
- public void initVideoByURI(final Uri videoURI) {
157
+ public void initByURI(final Uri videoURI) {
127
158
  mSourceUri = videoURI;
128
- mVideoView.setVideoURI(videoURI);
129
- mVideoView.requestFocus();
159
+
160
+ if (isVideoType) {
161
+ mVideoView.setVideoURI(videoURI);
162
+ mVideoView.requestFocus();
163
+
164
+ mVideoView.setOnPreparedListener(mp -> {
165
+ if (!mIsPrepared) {
166
+ mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
167
+ videoPrepared();
168
+ mIsPrepared = true;
169
+ }
170
+ });
171
+
172
+ mVideoView.setOnErrorListener((mp, what, extra) -> {
173
+ mediaFailed();
174
+ mOnTrimVideoListener.onError("Error loading video file. Please try again.", ErrorCode.FAIL_TO_LOAD_VIDEO);
175
+ return true;
176
+ });
177
+
178
+ mVideoView.setOnCompletionListener(mp -> mediaCompleted());
179
+ } else {
180
+ mVideoView.setVisibility(View.GONE);
181
+ audioBannerView.setAlpha(0f);
182
+ audioBannerView.setVisibility(View.VISIBLE);
183
+ audioBannerView.animate().alpha(1f).setDuration(500).start();
184
+
185
+ audioPlayer = new MediaPlayer();
186
+ try {
187
+ audioPlayer.setDataSource(videoURI.toString());
188
+ audioPlayer.setOnPreparedListener(mp -> {
189
+ if (!mIsPrepared) {
190
+ audioPrepared();
191
+ mIsPrepared = true;
192
+ }
193
+ });
194
+ audioPlayer.setOnCompletionListener(mp -> mediaCompleted());
195
+ audioPlayer.setOnErrorListener((mp, what, extra) -> {
196
+ mediaFailed();
197
+ mOnTrimVideoListener.onError("Error loading audio file. Please try again.", ErrorCode.FAIL_TO_LOAD_AUDIO);
198
+ return true;
199
+ });
200
+
201
+ audioPlayer.prepareAsync(); // use prepareAsync to avoid blocking the main thread
202
+ } catch (IOException e) {
203
+ e.printStackTrace();
204
+ mediaFailed();
205
+ mOnTrimVideoListener.onError("Error initializing audio player. Please try again.", ErrorCode.FAIL_TO_INITIALIZE_AUDIO_PLAYER);
206
+ }
207
+ }
130
208
  }
131
209
 
132
- private void startShootVideoThumbs(final Context context, final Uri videoUri, int totalThumbsCount, long startPosition, long endPosition) {
210
+ private void startShootVideoThumbs(final Context context, int totalThumbsCount, long startPosition, long endPosition) {
133
211
  mThumbnailContainer.removeAllViews();
134
- VideoTrimmerUtil.shootVideoThumbInBackground(context, videoUri, totalThumbsCount, startPosition, endPosition,
212
+ VideoTrimmerUtil.shootVideoThumbInBackground(mediaMetadataRetriever, totalThumbsCount, startPosition, endPosition,
135
213
  (bitmap, interval) -> {
136
214
  if (bitmap != null) {
137
215
  runOnUiThread(() -> {
@@ -147,13 +225,16 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
147
225
  });
148
226
  }
149
227
 
150
- private void videoPrepared(MediaPlayer mp) {
228
+ private void videoPrepared() {
151
229
  mDuration = mVideoView.getDuration();
152
-
153
230
  mMaxDuration = Math.min(mMaxDuration, mDuration);
231
+ mediaMetadataRetriever = MediaMetadataUtil.getMediaMetadataRetriever(mSourceUri.toString());
232
+
233
+ if (mediaMetadataRetriever == null) {
234
+ mOnTrimVideoListener.onError("Error when retrieving video info. Please try again.", ErrorCode.FAIL_TO_GET_VIDEO_INFO);
235
+ return;
236
+ }
154
237
 
155
- MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
156
- mediaMetadataRetriever.setDataSource(mContext, mSourceUri);
157
238
  // take first frame
158
239
  Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
159
240
 
@@ -165,9 +246,24 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
165
246
  VideoTrimmerUtil.VIDEO_FRAMES_WIDTH = VideoTrimmerUtil.SCREEN_WIDTH_FULL - RECYCLER_VIEW_PADDING * 2;
166
247
  VideoTrimmerUtil.MAX_COUNT_RANGE = Math.max((VIDEO_FRAMES_WIDTH / VideoTrimmerUtil.mThumbWidth), VideoTrimmerUtil.MAX_COUNT_RANGE);
167
248
 
168
- int mThumbsTotalCount;
169
- mThumbsTotalCount = (int) (mDuration * 1.0f / (mMaxDuration * 1.0f) * VideoTrimmerUtil.MAX_COUNT_RANGE);
170
- startShootVideoThumbs(mContext, mSourceUri, mThumbsTotalCount, 0, mDuration);
249
+ startShootVideoThumbs(mContext, VideoTrimmerUtil.MAX_COUNT_RANGE, 0, mDuration);
250
+
251
+ // Set initial handle positions if mMaxDuration < video duration
252
+ if (mMaxDuration < mDuration) {
253
+ endTime = mMaxDuration;
254
+ } else {
255
+ endTime = mDuration;
256
+ }
257
+ updateHandlePositions();
258
+
259
+ loadingIndicator.setVisibility(View.GONE);
260
+ mPlayView.setVisibility(View.VISIBLE);
261
+ saveBtn.setVisibility(View.VISIBLE);
262
+ }
263
+
264
+ private void audioPrepared() {
265
+ mDuration = audioPlayer.getDuration();
266
+ mMaxDuration = Math.min(mMaxDuration, mDuration);
171
267
 
172
268
  // Set initial handle positions if mMaxDuration < video duration
173
269
  if (mMaxDuration < mDuration) {
@@ -175,12 +271,32 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
175
271
  } else {
176
272
  endTime = mDuration;
177
273
  }
274
+
178
275
  updateHandlePositions();
276
+ loadingIndicator.setVisibility(View.GONE);
277
+ mPlayView.setVisibility(View.VISIBLE);
278
+ saveBtn.setVisibility(View.VISIBLE);
279
+ // mThumbnailContainer.animate().alpha(1f).setDuration(250).start();
280
+ }
281
+
282
+ private void updateGradientColors(int startColor, int endColor) {
283
+ GradientDrawable gradientDrawable = new GradientDrawable();
284
+ gradientDrawable.setShape(GradientDrawable.RECTANGLE);
285
+ gradientDrawable.setCornerRadius(6f); // Adjust corner radius as needed
286
+ gradientDrawable.setColors(new int[]{startColor, endColor});
287
+ gradientDrawable.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
288
+
289
+ mThumbnailContainer.setBackground(gradientDrawable);
290
+ }
291
+
292
+ private void mediaFailed() {
293
+ loadingIndicator.setVisibility(View.GONE);
294
+ failToLoadBtn.setVisibility(View.VISIBLE);
179
295
  }
180
296
 
181
297
  private void updateHandlePositions() {
182
- float startPercent = (float) startTime / mVideoView.getDuration();
183
- float endPercent = (float) endTime / mVideoView.getDuration();
298
+ float startPercent = (float) startTime / mDuration;
299
+ float endPercent = (float) endTime / mDuration;
184
300
 
185
301
  float containerWidth = trimmerContainerBg.getWidth();
186
302
  float leadingHandleX = startPercent * containerWidth;
@@ -194,36 +310,54 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
194
310
 
195
311
  trimmerContainerWrapper.setVisibility(View.VISIBLE);
196
312
  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
313
  }
202
314
 
203
- private void videoCompleted() {
315
+ private void mediaCompleted() {
204
316
  setPlayPauseViewIcon(false);
205
317
  mTimingHandler.removeCallbacks(mTimingRunnable);
206
318
  }
207
319
 
208
320
  private void playOrPause() {
209
- if (mVideoView.isPlaying()) {
210
- onVideoPause();
321
+ if (isVideoType) {
322
+ if (mVideoView.isPlaying()) {
323
+ onMediaPause();
324
+ } else {
325
+ // if current video time >= end time, seek to start time
326
+ if (mVideoView.getCurrentPosition() >= endTime) {
327
+ seekTo(startTime, true);
328
+ }
329
+ mVideoView.start();
330
+ startTimingRunnable();
331
+ }
332
+ setPlayPauseViewIcon(mVideoView.isPlaying());
333
+
211
334
  } else {
212
- // if current video time >= end time, seek to start time
213
- if (mVideoView.getCurrentPosition() >= endTime) {
214
- seekTo(startTime, true);
335
+ if (audioPlayer.isPlaying()) {
336
+ onMediaPause();
337
+ } else {
338
+ if (audioPlayer.getCurrentPosition() >= endTime) {
339
+ seekTo(startTime, true);
340
+ }
341
+ audioPlayer.start();
342
+ startTimingRunnable();
215
343
  }
216
- mVideoView.start();
217
- startTimingRunnable();
344
+ setPlayPauseViewIcon(audioPlayer.isPlaying());
218
345
  }
219
- setPlayPauseViewIcon(mVideoView.isPlaying());
220
346
  }
221
347
 
222
- public void onVideoPause() {
223
- if (mVideoView.isPlaying()) {
224
- mTimingHandler.removeCallbacks(mTimingRunnable);
225
- mVideoView.pause();
226
- setPlayPauseViewIcon(false);
348
+ public void onMediaPause() {
349
+ if (isVideoType) {
350
+ if (mVideoView.isPlaying()) {
351
+ mTimingHandler.removeCallbacks(mTimingRunnable);
352
+ mVideoView.pause();
353
+ setPlayPauseViewIcon(false);
354
+ }
355
+ } else {
356
+ if (audioPlayer.isPlaying()) {
357
+ mTimingHandler.removeCallbacks(mTimingRunnable);
358
+ audioPlayer.pause();
359
+ setPlayPauseViewIcon(false);
360
+ }
227
361
  }
228
362
  }
229
363
 
@@ -232,28 +366,18 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
232
366
  }
233
367
 
234
368
  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());
369
+ cancelBtn.setOnClickListener(view -> mOnTrimVideoListener.onCancel());
370
+ saveBtn.setOnClickListener(view -> mOnTrimVideoListener.onSave());
247
371
  mPlayView.setOnClickListener(view -> playOrPause());
248
372
  setHandleTouchListener(leadingHandle, true);
249
373
  setHandleTouchListener(trailingHandle, false);
250
374
  }
251
375
 
252
376
  public void onSaveClicked() {
253
- onVideoPause();
377
+ onMediaPause();
254
378
  VideoTrimmerUtil.trim(
255
- mSourceUri.getPath(),
256
- StorageUtil.getOutputPath(mContext),
379
+ isVideoType ? mSourceUri.getPath() : mSourceUri.toString(),
380
+ StorageUtil.getOutputPath(mContext, mOutputExt),
257
381
  mDuration,
258
382
  startTime,
259
383
  endTime,
@@ -261,7 +385,12 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
261
385
  }
262
386
 
263
387
  private void seekTo(long msec, boolean needUpdateProgress) {
264
- mVideoView.seekTo((int) msec);
388
+ if (isVideoType) {
389
+ mVideoView.seekTo((int) msec);
390
+ } else {
391
+ audioPlayer.seekTo((int) msec);
392
+ }
393
+
265
394
  updateCurrentTime(needUpdateProgress);
266
395
  }
267
396
 
@@ -283,6 +412,19 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
283
412
  mContext.getCurrentActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
284
413
  mTimingHandler.removeCallbacks(mTimingRunnable);
285
414
  zoomWaitTimer.removeCallbacks(zoomRunnable);
415
+
416
+ try {
417
+ if (mediaMetadataRetriever != null) {
418
+ mediaMetadataRetriever.release();
419
+ }
420
+ } catch (Exception e) {
421
+ e.printStackTrace();
422
+ }
423
+
424
+ if (audioPlayer != null) {
425
+ audioPlayer.stop();
426
+ audioPlayer.release();
427
+ }
286
428
  }
287
429
 
288
430
  private int getScreenWidthInPortraitMode() {
@@ -303,12 +445,27 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
303
445
  mMinDuration = Math.max(1000L, config.getInt("minDuration") * 1000L);
304
446
  }
305
447
  if (config.hasKey("cancelButtonText")) {
306
- TextView tv = findViewById(R.id.cancelBtn);
307
- tv.setText(config.getString("cancelButtonText"));
448
+ cancelBtn.setText(config.getString("cancelButtonText"));
308
449
  }
309
450
  if (config.hasKey("saveButtonText")) {
310
- TextView tv = findViewById(R.id.saveBtn);
311
- tv.setText(config.getString("saveButtonText"));
451
+ saveBtn.setText(config.getString("saveButtonText"));
452
+ }
453
+
454
+ if (config.hasKey("type")) {
455
+ isVideoType = !config.getString("type").equals("audio");
456
+
457
+ // if (!isVideoType) {
458
+ // mThumbnailContainer.setAlpha(0f);
459
+ // mThumbnailContainer.setBackground(ContextCompat.getDrawable(mContext, R.drawable.thumb_container_bg));
460
+ // }
461
+ }
462
+
463
+ if (config.hasKey("outputExt")) {
464
+ mOutputExt = config.getString("outputExt");
465
+ }
466
+
467
+ if (config.hasKey("enableHapticFeedback")) {
468
+ enableHapticFeedback = config.getBoolean("enableHapticFeedback");
312
469
  }
313
470
  }
314
471
 
@@ -316,10 +473,15 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
316
473
  mTimingRunnable = new Runnable() {
317
474
  @Override
318
475
  public void run() {
319
- int currentPosition = mVideoView.getCurrentPosition();
476
+ int currentPosition;
477
+ if (isVideoType) {
478
+ currentPosition = mVideoView.getCurrentPosition();
479
+ } else {
480
+ currentPosition = audioPlayer.getCurrentPosition();
481
+ }
320
482
 
321
483
  if (currentPosition >= endTime) {
322
- onVideoPause();
484
+ onMediaPause();
323
485
  seekTo(endTime, true); // Ensure exact end time display
324
486
  } else {
325
487
  updateCurrentTime(true);
@@ -332,8 +494,15 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
332
494
 
333
495
  private void updateCurrentTime(boolean needUpdateProgress) {
334
496
  // 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();
497
+
498
+ int currentPosition;
499
+ if (isVideoType) {
500
+ currentPosition = mVideoView.getCurrentPosition();
501
+ } else {
502
+ currentPosition = audioPlayer.getCurrentPosition();
503
+ }
504
+
505
+ int duration = mDuration;
337
506
 
338
507
  if (currentPosition >= duration - 100) {
339
508
  currentPosition = duration;
@@ -373,7 +542,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
373
542
  switch (event.getAction()) {
374
543
  case MotionEvent.ACTION_DOWN:
375
544
  didClampWhilePanning = false;
376
- onVideoPause();
545
+ onMediaPause();
377
546
  onTrimmerContainerPanned(event);
378
547
  playHapticFeedback(true);
379
548
  break;
@@ -402,8 +571,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
402
571
  // check play haptic feedback
403
572
  if (newX <= leftBoundary) {
404
573
  didClamp = true;
405
- }
406
- else if (newX >= rightBoundary) {
574
+ } else if (newX >= rightBoundary) {
407
575
  didClamp = true;
408
576
  }
409
577
 
@@ -418,16 +586,17 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
418
586
 
419
587
  // TODO: check this
420
588
  float indicatorPositionPercent = indicatorPosition / (trimmerContainerBg.getWidth() - progressIndicator.getWidth());
421
- long newVideoPosition = (long) (indicatorPositionPercent * mVideoView.getDuration());
589
+ long newVideoPosition = (long) (indicatorPositionPercent * mDuration);
422
590
 
423
591
  seekTo(newVideoPosition, false);
424
592
  }
593
+
425
594
  private void setHandleTouchListener(View handle, boolean isLeading) {
426
595
  handle.setOnTouchListener((view, event) -> {
427
596
  switch (event.getAction()) {
428
597
  case MotionEvent.ACTION_DOWN:
429
598
  didClampWhilePanning = false;
430
- onVideoPause();
599
+ onMediaPause();
431
600
  fadeOutProgressIndicator();
432
601
  seekTo(isLeading ? startTime : endTime, true);
433
602
  playHapticFeedback(true);
@@ -446,7 +615,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
446
615
  // Calculate new startTime or endTime
447
616
  if (isLeading) {
448
617
  // Calculate the new startTime based on the handle's new position
449
- long newStartTime = (long) ((newX / trimmerContainerBg.getWidth()) * mVideoView.getDuration());
618
+ long newStartTime = (long) ((newX / trimmerContainerBg.getWidth()) * mDuration);
450
619
  // Calculate the duration between the new startTime and the current endTime
451
620
  long duration = endTime - newStartTime;
452
621
  if (duration >= mMinDuration && duration <= mMaxDuration) {
@@ -458,19 +627,19 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
458
627
  // If the duration is less than the minimum, set startTime to the maximum possible to maintain the minimum duration
459
628
  startTime = endTime - mMinDuration;
460
629
  // Adjust the handle position accordingly
461
- view.setX((float) startTime / mVideoView.getDuration() * trimmerContainerBg.getWidth());
630
+ view.setX((float) startTime / mDuration * trimmerContainerBg.getWidth());
462
631
  progressIndicator.setX(view.getX() + view.getWidth());
463
632
  } else {
464
633
  didClamp = true;
465
634
  // If the duration is greater than the maximum, set startTime to the minimum possible to maintain the maximum duration
466
635
  startTime = endTime - mMaxDuration;
467
636
  // Adjust the handle position accordingly
468
- view.setX((float) startTime / mVideoView.getDuration() * trimmerContainerBg.getWidth());
637
+ view.setX((float) startTime / mDuration * trimmerContainerBg.getWidth());
469
638
  progressIndicator.setX(view.getX() + view.getWidth());
470
639
  }
471
640
  } else {
472
641
  // Calculate the new endTime based on the handle's new position
473
- long newEndTime = (long) (((newX - view.getWidth()) / trimmerContainerBg.getWidth()) * mVideoView.getDuration());
642
+ long newEndTime = (long) (((newX - view.getWidth()) / trimmerContainerBg.getWidth()) * mDuration);
474
643
  // Calculate the duration between the new endTime and the current startTime
475
644
  long duration = newEndTime - startTime;
476
645
  if (duration >= mMinDuration && duration <= mMaxDuration) {
@@ -482,14 +651,14 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
482
651
  // If the duration is less than the minimum, set endTime to the minimum possible to maintain the minimum duration
483
652
  endTime = startTime + mMinDuration;
484
653
  // Adjust the handle position accordingly
485
- view.setX((float) endTime / mVideoView.getDuration() * trimmerContainerBg.getWidth() + view.getWidth());
654
+ view.setX((float) endTime / mDuration * trimmerContainerBg.getWidth() + view.getWidth());
486
655
  progressIndicator.setX(view.getX() - progressIndicator.getWidth());
487
656
  } else {
488
657
  didClamp = true;
489
658
  // If the duration is greater than the maximum, set endTime to the maximum possible to maintain the maximum duration
490
659
  endTime = startTime + mMaxDuration;
491
660
  // Adjust the handle position accordingly
492
- view.setX((float) endTime / mVideoView.getDuration() * trimmerContainerBg.getWidth() + view.getWidth());
661
+ view.setX((float) endTime / mDuration * trimmerContainerBg.getWidth() + view.getWidth());
493
662
  progressIndicator.setX(view.getX() - progressIndicator.getWidth());
494
663
  }
495
664
  }
@@ -544,7 +713,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
544
713
  }
545
714
 
546
715
  private void playHapticFeedback(boolean isLight) {
547
- if (vibrator != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
716
+ if (vibrator != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && enableHapticFeedback) {
548
717
  vibrator.vibrate(VibrationEffect.createOneShot(isLight ? 10 : 25, VibrationEffect.DEFAULT_AMPLITUDE)); // Light vibration
549
718
  }
550
719
  }
@@ -556,7 +725,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
556
725
  }
557
726
 
558
727
  zoomRunnable = () -> {
559
- Log.i("tag","A Kiss after 500ms");
728
+ Log.i("tag", "A Kiss after 500ms");
560
729
  stopZoomWaitTimer();
561
730
  zoomIfNeeded();
562
731
  };
@@ -578,7 +747,7 @@ public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
578
747
  return;
579
748
  }
580
749
 
581
- startShootVideoThumbs(mContext, mSourceUri, 10, 5000, 10000);
750
+ startShootVideoThumbs(mContext, 10, 5000, 10000);
582
751
 
583
752
  isZoomedIn = true;
584
753
  }
@@ -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>