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.
- package/README.md +33 -9
- package/android/src/main/AndroidManifest.xml +13 -0
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +185 -64
- package/android/src/main/java/com/videotrim/enums/ErrorCode.java +11 -0
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +2 -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 +15 -8
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +239 -70
- 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 +51 -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/VideoTrim.mm +4 -2
- package/ios/VideoTrim.swift +380 -167
- package/ios/VideoTrimmer.swift +16 -10
- package/ios/VideoTrimmerViewController.swift +78 -12
- package/lib/commonjs/index.js +20 -57
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +19 -57
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +47 -9
- package/lib/typescript/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +56 -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
|
@@ -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
|
|
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
|
-
|
|
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
|
|
157
|
+
public void initByURI(final Uri videoURI) {
|
|
127
158
|
mSourceUri = videoURI;
|
|
128
|
-
|
|
129
|
-
|
|
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,
|
|
210
|
+
private void startShootVideoThumbs(final Context context, int totalThumbsCount, long startPosition, long endPosition) {
|
|
133
211
|
mThumbnailContainer.removeAllViews();
|
|
134
|
-
VideoTrimmerUtil.shootVideoThumbInBackground(
|
|
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(
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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 /
|
|
183
|
-
float endPercent = (float) endTime /
|
|
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
|
|
315
|
+
private void mediaCompleted() {
|
|
204
316
|
setPlayPauseViewIcon(false);
|
|
205
317
|
mTimingHandler.removeCallbacks(mTimingRunnable);
|
|
206
318
|
}
|
|
207
319
|
|
|
208
320
|
private void playOrPause() {
|
|
209
|
-
if (
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
217
|
-
startTimingRunnable();
|
|
344
|
+
setPlayPauseViewIcon(audioPlayer.isPlaying());
|
|
218
345
|
}
|
|
219
|
-
setPlayPauseViewIcon(mVideoView.isPlaying());
|
|
220
346
|
}
|
|
221
347
|
|
|
222
|
-
public void
|
|
223
|
-
if (
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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());
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
307
|
-
tv.setText(config.getString("cancelButtonText"));
|
|
448
|
+
cancelBtn.setText(config.getString("cancelButtonText"));
|
|
308
449
|
}
|
|
309
450
|
if (config.hasKey("saveButtonText")) {
|
|
310
|
-
|
|
311
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
int
|
|
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
|
-
|
|
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 *
|
|
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
|
-
|
|
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()) *
|
|
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 /
|
|
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 /
|
|
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()) *
|
|
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 /
|
|
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 /
|
|
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,
|
|
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>
|