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.
- package/README.md +168 -33
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +282 -75
- package/android/src/main/java/com/videotrim/enums/ErrorCode.java +10 -0
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +4 -1
- package/android/src/main/java/com/videotrim/utils/MediaMetadataUtil.java +75 -0
- package/android/src/main/java/com/videotrim/utils/StorageUtil.java +2 -2
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +26 -16
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +310 -81
- package/android/src/main/res/drawable/airpodsmax.xml +19 -0
- package/android/src/main/res/drawable/exclamationmark_triangle_fill.xml +15 -0
- package/android/src/main/res/drawable/thumb_container_bg.xml +8 -0
- package/android/src/main/res/layout/video_trimmer_view.xml +71 -4
- package/android/src/main/res/xml/file_paths.xml +5 -0
- package/ios/AssetLoader.swift +99 -0
- package/ios/ErrorCode.swift +16 -0
- package/ios/ProgressAlertController.swift +100 -0
- package/ios/VideoTrim.mm +4 -2
- package/ios/VideoTrim.swift +472 -177
- package/ios/VideoTrimmer.swift +16 -10
- package/ios/VideoTrimmerViewController.swift +191 -22
- package/lib/commonjs/index.js +25 -55
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +24 -55
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +215 -9
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +229 -66
- package/android/src/main/java/iknow/android/utils/BuildConfig.java +0 -18
- 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
|
|
165
|
+
public void initByURI(final Uri videoURI) {
|
|
127
166
|
mSourceUri = videoURI;
|
|
128
|
-
|
|
129
|
-
|
|
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,
|
|
223
|
+
private void startShootVideoThumbs(final Context context, int totalThumbsCount, long startPosition, long endPosition) {
|
|
133
224
|
mThumbnailContainer.removeAllViews();
|
|
134
|
-
VideoTrimmerUtil.shootVideoThumbInBackground(
|
|
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
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
252
|
+
// take first frame
|
|
253
|
+
Bitmap bitmap = mediaMetadataRetriever.getFrameAtTime(0, MediaMetadataRetriever.OPTION_CLOSEST_SYNC);
|
|
163
254
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
255
|
+
if (bitmap != null) {
|
|
256
|
+
VideoTrimmerUtil.mThumbWidth = VideoTrimmerUtil.THUMB_HEIGHT * bitmap.getWidth() / bitmap.getHeight();
|
|
257
|
+
}
|
|
167
258
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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 /
|
|
183
|
-
float endPercent = (float) endTime /
|
|
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
|
|
324
|
+
private void mediaCompleted() {
|
|
204
325
|
setPlayPauseViewIcon(false);
|
|
205
326
|
mTimingHandler.removeCallbacks(mTimingRunnable);
|
|
206
327
|
}
|
|
207
328
|
|
|
208
329
|
private void playOrPause() {
|
|
209
|
-
if (
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
startTimingRunnable();
|
|
353
|
+
setPlayPauseViewIcon(audioPlayer.isPlaying());
|
|
218
354
|
}
|
|
219
|
-
setPlayPauseViewIcon(mVideoView.isPlaying());
|
|
220
355
|
}
|
|
221
356
|
|
|
222
|
-
public void
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
236
|
-
|
|
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
|
-
|
|
254
|
-
VideoTrimmerUtil.trim(
|
|
255
|
-
mSourceUri.
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
tv.setText(config.getString("cancelButtonText"));
|
|
465
|
+
cancelBtn.setText(config.getString("cancelButtonText"));
|
|
308
466
|
}
|
|
309
467
|
if (config.hasKey("saveButtonText")) {
|
|
310
|
-
|
|
311
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
int
|
|
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
|
-
|
|
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 *
|
|
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
|
-
|
|
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()) *
|
|
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 /
|
|
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 /
|
|
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()) *
|
|
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 /
|
|
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 /
|
|
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,
|
|
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>
|