react-native-video-trim 0.0.1 → 1.0.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/LICENSE +20 -0
- package/README.md +206 -0
- package/android/build.gradle +105 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/AndroidManifestDeprecated.xml +3 -0
- package/android/src/main/java/com/videotrim/VideoTrimModule.java +223 -0
- package/android/src/main/java/com/videotrim/VideoTrimPackage.java +28 -0
- package/android/src/main/java/com/videotrim/adapters/VideoTrimmerAdapter.java +60 -0
- package/android/src/main/java/com/videotrim/interfaces/IVideoTrimmerView.java +5 -0
- package/android/src/main/java/com/videotrim/interfaces/VideoTrimListener.java +7 -0
- package/android/src/main/java/com/videotrim/utils/StorageUtil.java +285 -0
- package/android/src/main/java/com/videotrim/utils/VideoTrimmerUtil.java +93 -0
- package/android/src/main/java/com/videotrim/widgets/RangeSeekBarView.java +534 -0
- package/android/src/main/java/com/videotrim/widgets/SpacesItemDecoration2.java +33 -0
- package/android/src/main/java/com/videotrim/widgets/VideoTrimmerView.java +443 -0
- package/android/src/main/java/com/videotrim/widgets/ZVideoView.java +48 -0
- package/android/src/main/res/drawable/ic_video_pause_black.png +0 -0
- package/android/src/main/res/drawable/ic_video_play_black.png +0 -0
- package/android/src/main/res/drawable/ic_video_thumb_handle.png +0 -0
- package/android/src/main/res/drawable/icon_seek_bar.png +0 -0
- package/android/src/main/res/layout/video_thumb_item_layout.xml +16 -0
- package/android/src/main/res/layout/video_trimmer_view.xml +148 -0
- package/android/src/main/res/values/colors.xml +17 -0
- package/android/src/main/res/values/strings.xml +14 -0
- package/ios/VideoTrim-Bridging-Header.h +2 -0
- package/ios/VideoTrim.mm +10 -0
- package/ios/VideoTrim.swift +170 -0
- package/ios/VideoTrim.xcodeproj/project.pbxproj +283 -0
- package/ios/VideoTrim.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
- package/lib/commonjs/index.js +32 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/index.js +25 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/index.d.ts +7 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +158 -7
- package/react-native-video-trim.podspec +41 -0
- package/src/index.tsx +35 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
package com.videotrim.widgets;
|
|
2
|
+
|
|
3
|
+
import static com.videotrim.utils.VideoTrimmerUtil.MAX_COUNT_RANGE;
|
|
4
|
+
import static com.videotrim.utils.VideoTrimmerUtil.RECYCLER_VIEW_PADDING;
|
|
5
|
+
import static com.videotrim.utils.VideoTrimmerUtil.VIDEO_FRAMES_WIDTH;
|
|
6
|
+
|
|
7
|
+
import android.animation.ValueAnimator;
|
|
8
|
+
import android.content.Context;
|
|
9
|
+
import android.media.MediaPlayer;
|
|
10
|
+
import android.net.Uri;
|
|
11
|
+
import android.os.Handler;
|
|
12
|
+
import android.util.AttributeSet;
|
|
13
|
+
import android.view.LayoutInflater;
|
|
14
|
+
import android.view.MotionEvent;
|
|
15
|
+
import android.view.View;
|
|
16
|
+
import android.view.ViewGroup;
|
|
17
|
+
import android.view.animation.LinearInterpolator;
|
|
18
|
+
import android.widget.FrameLayout;
|
|
19
|
+
import android.widget.ImageView;
|
|
20
|
+
import android.widget.LinearLayout;
|
|
21
|
+
import android.widget.RelativeLayout;
|
|
22
|
+
import android.widget.Toast;
|
|
23
|
+
|
|
24
|
+
import androidx.appcompat.app.AlertDialog;
|
|
25
|
+
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
26
|
+
import androidx.recyclerview.widget.RecyclerView;
|
|
27
|
+
|
|
28
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
29
|
+
import com.videotrim.R;
|
|
30
|
+
import com.videotrim.adapters.VideoTrimmerAdapter;
|
|
31
|
+
import com.videotrim.interfaces.IVideoTrimmerView;
|
|
32
|
+
import com.videotrim.interfaces.VideoTrimListener;
|
|
33
|
+
import com.videotrim.utils.StorageUtil;
|
|
34
|
+
import com.videotrim.utils.VideoTrimmerUtil;
|
|
35
|
+
|
|
36
|
+
import iknow.android.utils.thread.BackgroundExecutor;
|
|
37
|
+
import iknow.android.utils.thread.UiThreadExecutor;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Author:J.Chou
|
|
41
|
+
* Date: 2016.08.01 2:23 PM
|
|
42
|
+
* Email: who_know_me@163.com
|
|
43
|
+
* Describe:
|
|
44
|
+
*/
|
|
45
|
+
public class VideoTrimmerView extends FrameLayout implements IVideoTrimmerView {
|
|
46
|
+
|
|
47
|
+
private static final String TAG = VideoTrimmerView.class.getSimpleName();
|
|
48
|
+
|
|
49
|
+
private int mMaxWidth = VIDEO_FRAMES_WIDTH;
|
|
50
|
+
private ReactApplicationContext mContext;
|
|
51
|
+
private RelativeLayout mLinearVideo;
|
|
52
|
+
private ZVideoView mVideoView;
|
|
53
|
+
private ImageView mPlayView;
|
|
54
|
+
private RecyclerView mVideoThumbRecyclerView;
|
|
55
|
+
private RangeSeekBarView mRangeSeekBarView;
|
|
56
|
+
private LinearLayout mSeekBarLayout;
|
|
57
|
+
private ImageView mRedProgressIcon;
|
|
58
|
+
private float mAverageMsPx;//每毫秒所占的px
|
|
59
|
+
private float averagePxMs;//每px所占用的ms毫秒
|
|
60
|
+
private Uri mSourceUri;
|
|
61
|
+
private VideoTrimListener mOnTrimVideoListener;
|
|
62
|
+
private int mDuration = 0;
|
|
63
|
+
private VideoTrimmerAdapter mVideoThumbAdapter;
|
|
64
|
+
private boolean isFromRestore = false;
|
|
65
|
+
//new
|
|
66
|
+
private long mLeftProgressPos, mRightProgressPos;
|
|
67
|
+
private long mRedProgressBarPos = 0;
|
|
68
|
+
private long scrollPos = 0;
|
|
69
|
+
private int mScaledTouchSlop;
|
|
70
|
+
private int lastScrollX;
|
|
71
|
+
private boolean isSeeking;
|
|
72
|
+
private boolean isOverScaledTouchSlop;
|
|
73
|
+
private int mThumbsTotalCount;
|
|
74
|
+
private ValueAnimator mRedProgressAnimator;
|
|
75
|
+
private Handler mAnimationHandler = new Handler();
|
|
76
|
+
private Boolean mIsPrepared = false;
|
|
77
|
+
private int mMaxDuration = 0;
|
|
78
|
+
|
|
79
|
+
public VideoTrimmerView(ReactApplicationContext context, AttributeSet attrs) {
|
|
80
|
+
this(context, attrs, 0);
|
|
81
|
+
}
|
|
82
|
+
public VideoTrimmerView(ReactApplicationContext context, int maxDuration, AttributeSet attrs) {
|
|
83
|
+
this(context, attrs, 0);
|
|
84
|
+
this.mMaxDuration = maxDuration;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public VideoTrimmerView(ReactApplicationContext context, AttributeSet attrs, int defStyleAttr) {
|
|
88
|
+
super(context, attrs, defStyleAttr);
|
|
89
|
+
init(context);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private void init(ReactApplicationContext context) {
|
|
93
|
+
this.mContext = context;
|
|
94
|
+
LayoutInflater.from(context).inflate(R.layout.video_trimmer_view, this, true);
|
|
95
|
+
|
|
96
|
+
mLinearVideo = findViewById(R.id.layout_surface_view);
|
|
97
|
+
mVideoView = findViewById(R.id.video_loader);
|
|
98
|
+
mPlayView = findViewById(R.id.icon_video_play);
|
|
99
|
+
mSeekBarLayout = findViewById(R.id.seekBarLayout);
|
|
100
|
+
mRedProgressIcon = findViewById(R.id.positionIcon);
|
|
101
|
+
mVideoThumbRecyclerView = findViewById(R.id.video_frames_recyclerView);
|
|
102
|
+
mVideoThumbRecyclerView.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false));
|
|
103
|
+
mVideoThumbAdapter = new VideoTrimmerAdapter(mContext);
|
|
104
|
+
mVideoThumbRecyclerView.setAdapter(mVideoThumbAdapter);
|
|
105
|
+
mVideoThumbRecyclerView.addOnScrollListener(mOnScrollListener);
|
|
106
|
+
|
|
107
|
+
setUpListeners();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private void initRangeSeekBarView() {
|
|
111
|
+
if(mRangeSeekBarView != null) return;
|
|
112
|
+
mLeftProgressPos = 0;
|
|
113
|
+
if (mDuration <= VideoTrimmerUtil.maxShootDuration) {
|
|
114
|
+
mThumbsTotalCount = MAX_COUNT_RANGE;
|
|
115
|
+
mRightProgressPos = mDuration;
|
|
116
|
+
} else {
|
|
117
|
+
mThumbsTotalCount = (int) (mDuration * 1.0f / (VideoTrimmerUtil.maxShootDuration * 1.0f) * MAX_COUNT_RANGE);
|
|
118
|
+
mRightProgressPos = VideoTrimmerUtil.maxShootDuration;
|
|
119
|
+
}
|
|
120
|
+
mVideoThumbRecyclerView.addItemDecoration(new SpacesItemDecoration2(RECYCLER_VIEW_PADDING, mThumbsTotalCount));
|
|
121
|
+
mRangeSeekBarView = new RangeSeekBarView(mContext, mLeftProgressPos, mRightProgressPos);
|
|
122
|
+
mRangeSeekBarView.setSelectedMinValue(mLeftProgressPos);
|
|
123
|
+
mRangeSeekBarView.setSelectedMaxValue(mRightProgressPos);
|
|
124
|
+
mRangeSeekBarView.setStartEndTime(mLeftProgressPos, mRightProgressPos);
|
|
125
|
+
mRangeSeekBarView.setMinShootTime(VideoTrimmerUtil.MIN_SHOOT_DURATION);
|
|
126
|
+
mRangeSeekBarView.setNotifyWhileDragging(true);
|
|
127
|
+
mRangeSeekBarView.setOnRangeSeekBarChangeListener(mOnRangeSeekBarChangeListener);
|
|
128
|
+
mSeekBarLayout.addView(mRangeSeekBarView);
|
|
129
|
+
if(mThumbsTotalCount - MAX_COUNT_RANGE > 0) {
|
|
130
|
+
mAverageMsPx = (mDuration - VideoTrimmerUtil.maxShootDuration) / (float) (mThumbsTotalCount - MAX_COUNT_RANGE);
|
|
131
|
+
} else {
|
|
132
|
+
mAverageMsPx = 0f;
|
|
133
|
+
}
|
|
134
|
+
averagePxMs = (mMaxWidth * 1.0f / (mRightProgressPos - mLeftProgressPos));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public void initVideoByURI(final Uri videoURI) {
|
|
138
|
+
mSourceUri = videoURI;
|
|
139
|
+
mVideoView.setVideoURI(videoURI);
|
|
140
|
+
mVideoView.requestFocus();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private void startShootVideoThumbs(final Context context, final Uri videoUri, int totalThumbsCount, long startPosition, long endPosition) {
|
|
144
|
+
VideoTrimmerUtil.shootVideoThumbInBackground(context, videoUri, totalThumbsCount, startPosition, endPosition,
|
|
145
|
+
(bitmap, interval) -> {
|
|
146
|
+
if (bitmap != null) {
|
|
147
|
+
UiThreadExecutor.runTask("", () -> mVideoThumbAdapter.addBitmaps(bitmap), 0L);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private void onCancelClicked() {
|
|
153
|
+
mOnTrimVideoListener.onCancel();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private void videoPrepared(MediaPlayer mp) {
|
|
157
|
+
ViewGroup.LayoutParams lp = mVideoView.getLayoutParams();
|
|
158
|
+
int videoWidth = mp.getVideoWidth();
|
|
159
|
+
int videoHeight = mp.getVideoHeight();
|
|
160
|
+
|
|
161
|
+
float videoProportion = (float) videoWidth / (float) videoHeight;
|
|
162
|
+
int screenWidth = mLinearVideo.getWidth();
|
|
163
|
+
int screenHeight = mLinearVideo.getHeight();
|
|
164
|
+
|
|
165
|
+
if (videoHeight > videoWidth) {
|
|
166
|
+
lp.width = screenWidth;
|
|
167
|
+
lp.height = screenHeight;
|
|
168
|
+
} else {
|
|
169
|
+
lp.width = screenWidth;
|
|
170
|
+
float r = videoHeight / (float) videoWidth;
|
|
171
|
+
lp.height = (int) (lp.width * r);
|
|
172
|
+
}
|
|
173
|
+
mVideoView.setLayoutParams(lp);
|
|
174
|
+
mDuration = mVideoView.getDuration();
|
|
175
|
+
|
|
176
|
+
VideoTrimmerUtil.maxShootDuration = mMaxDuration > 0 ? Math.min(mMaxDuration * 1000L, mDuration) : mDuration;
|
|
177
|
+
if (!getRestoreState()) {
|
|
178
|
+
seekTo((int) mRedProgressBarPos);
|
|
179
|
+
} else {
|
|
180
|
+
setRestoreState(false);
|
|
181
|
+
seekTo((int) mRedProgressBarPos);
|
|
182
|
+
}
|
|
183
|
+
initRangeSeekBarView();
|
|
184
|
+
startShootVideoThumbs(mContext, mSourceUri, mThumbsTotalCount, 0, mDuration);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private void videoCompleted() {
|
|
188
|
+
seekTo(mLeftProgressPos);
|
|
189
|
+
setPlayPauseViewIcon(false);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private void onVideoReset() {
|
|
193
|
+
mVideoView.pause();
|
|
194
|
+
setPlayPauseViewIcon(false);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private void playVideoOrPause() {
|
|
198
|
+
mRedProgressBarPos = mVideoView.getCurrentPosition();
|
|
199
|
+
if (mVideoView.isPlaying()) {
|
|
200
|
+
mVideoView.pause();
|
|
201
|
+
pauseRedProgressAnimation();
|
|
202
|
+
} else {
|
|
203
|
+
mVideoView.start();
|
|
204
|
+
playingRedProgressAnimation();
|
|
205
|
+
}
|
|
206
|
+
setPlayPauseViewIcon(mVideoView.isPlaying());
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public void onVideoPause() {
|
|
210
|
+
if (mVideoView.isPlaying()) {
|
|
211
|
+
seekTo(mLeftProgressPos);//复位
|
|
212
|
+
mVideoView.pause();
|
|
213
|
+
setPlayPauseViewIcon(false);
|
|
214
|
+
mRedProgressIcon.setVisibility(GONE);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public void setOnTrimVideoListener(VideoTrimListener onTrimVideoListener) {
|
|
219
|
+
mOnTrimVideoListener = onTrimVideoListener;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private void setUpListeners() {
|
|
223
|
+
findViewById(R.id.cancelBtn).setOnClickListener(view -> {
|
|
224
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(mContext.getCurrentActivity());
|
|
225
|
+
builder.setMessage("Are you sure want to cancel?");
|
|
226
|
+
builder.setTitle("Warning!");
|
|
227
|
+
builder.setCancelable(false);
|
|
228
|
+
builder.setPositiveButton("Proceed", (dialog, which) -> {
|
|
229
|
+
dialog.cancel();
|
|
230
|
+
onCancelClicked();
|
|
231
|
+
});
|
|
232
|
+
builder.setNegativeButton("Close", (dialog, which) -> {
|
|
233
|
+
dialog.cancel();
|
|
234
|
+
});
|
|
235
|
+
AlertDialog alertDialog = builder.create();
|
|
236
|
+
alertDialog.show();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
findViewById(R.id.saveBtn).setOnClickListener(view -> {
|
|
240
|
+
AlertDialog.Builder builder = new AlertDialog.Builder(mContext.getCurrentActivity());
|
|
241
|
+
builder.setMessage("Are you sure want to save?");
|
|
242
|
+
builder.setTitle("Confirmation!");
|
|
243
|
+
builder.setCancelable(false);
|
|
244
|
+
builder.setPositiveButton("Proceed", (dialog, which) -> {
|
|
245
|
+
dialog.cancel();
|
|
246
|
+
onSaveClicked();
|
|
247
|
+
});
|
|
248
|
+
builder.setNegativeButton("Close", (dialog, which) -> {
|
|
249
|
+
dialog.cancel();
|
|
250
|
+
});
|
|
251
|
+
AlertDialog alertDialog = builder.create();
|
|
252
|
+
alertDialog.show();
|
|
253
|
+
});
|
|
254
|
+
mVideoView.setOnPreparedListener(mp -> {
|
|
255
|
+
// this is called everytime activity goes active, and can fire multiple times
|
|
256
|
+
// so that we create a flag to not run below code more than once
|
|
257
|
+
if (mIsPrepared) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
mp.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT);
|
|
261
|
+
videoPrepared(mp);
|
|
262
|
+
mIsPrepared = true;
|
|
263
|
+
});
|
|
264
|
+
mVideoView.setOnCompletionListener(mp -> {
|
|
265
|
+
videoCompleted();
|
|
266
|
+
});
|
|
267
|
+
mPlayView.setOnClickListener(view -> {
|
|
268
|
+
playVideoOrPause();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private void onSaveClicked() {
|
|
273
|
+
if (mRightProgressPos - mLeftProgressPos < VideoTrimmerUtil.MIN_SHOOT_DURATION) {
|
|
274
|
+
Toast.makeText(mContext, "Video shorter than 3s, can't proceed", Toast.LENGTH_SHORT).show();
|
|
275
|
+
} else {
|
|
276
|
+
mVideoView.pause();
|
|
277
|
+
VideoTrimmerUtil.trim(mContext,
|
|
278
|
+
mSourceUri.getPath(),
|
|
279
|
+
StorageUtil.getCacheDir(),
|
|
280
|
+
mLeftProgressPos,
|
|
281
|
+
mRightProgressPos,
|
|
282
|
+
mOnTrimVideoListener);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private void seekTo(long msec) {
|
|
287
|
+
mVideoView.seekTo((int) msec);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private boolean getRestoreState() {
|
|
291
|
+
return isFromRestore;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
public void setRestoreState(boolean fromRestore) {
|
|
295
|
+
isFromRestore = fromRestore;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private void setPlayPauseViewIcon(boolean isPlaying) {
|
|
299
|
+
mPlayView.setImageResource(isPlaying ? R.drawable.ic_video_pause_black : R.drawable.ic_video_play_black);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private final RangeSeekBarView.OnRangeSeekBarChangeListener mOnRangeSeekBarChangeListener = new RangeSeekBarView.OnRangeSeekBarChangeListener() {
|
|
303
|
+
@Override public void onRangeSeekBarValuesChanged(RangeSeekBarView bar, long minValue, long maxValue, int action, boolean isMin,
|
|
304
|
+
RangeSeekBarView.Thumb pressedThumb) {
|
|
305
|
+
mLeftProgressPos = minValue + scrollPos;
|
|
306
|
+
mRedProgressBarPos = mLeftProgressPos;
|
|
307
|
+
|
|
308
|
+
// when dragging the highlighted section mRightProgressPos in some cases can bigger than mDuration
|
|
309
|
+
// Eg: mDuration=62006, then mRightProgressPos can be 63000
|
|
310
|
+
mRightProgressPos = Math.min(maxValue + scrollPos, mDuration);
|
|
311
|
+
switch (action) {
|
|
312
|
+
case MotionEvent.ACTION_DOWN:
|
|
313
|
+
isSeeking = false;
|
|
314
|
+
break;
|
|
315
|
+
case MotionEvent.ACTION_MOVE:
|
|
316
|
+
isSeeking = true;
|
|
317
|
+
seekTo((int) (pressedThumb == RangeSeekBarView.Thumb.MIN ? mLeftProgressPos : mRightProgressPos));
|
|
318
|
+
break;
|
|
319
|
+
case MotionEvent.ACTION_UP:
|
|
320
|
+
isSeeking = false;
|
|
321
|
+
seekTo((int) mLeftProgressPos);
|
|
322
|
+
break;
|
|
323
|
+
default:
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
mRangeSeekBarView.setStartEndTime(mLeftProgressPos, mRightProgressPos);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
|
|
333
|
+
@Override
|
|
334
|
+
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
|
335
|
+
super.onScrollStateChanged(recyclerView, newState);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
@Override
|
|
339
|
+
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
|
340
|
+
super.onScrolled(recyclerView, dx, dy);
|
|
341
|
+
isSeeking = false;
|
|
342
|
+
int scrollX = calcScrollXDistance();
|
|
343
|
+
//达不到滑动的距离
|
|
344
|
+
if (Math.abs(lastScrollX - scrollX) < mScaledTouchSlop) {
|
|
345
|
+
isOverScaledTouchSlop = false;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
isOverScaledTouchSlop = true;
|
|
349
|
+
//初始状态,why ? 因为默认的时候有35dp的空白!
|
|
350
|
+
if (scrollX == -RECYCLER_VIEW_PADDING) {
|
|
351
|
+
scrollPos = 0;
|
|
352
|
+
mLeftProgressPos = mRangeSeekBarView.getSelectedMinValue() + scrollPos;
|
|
353
|
+
|
|
354
|
+
// when scrolling the highlighted section mRightProgressPos in some cases can bigger than mDuration
|
|
355
|
+
// Eg: mDuration=62006, then mRightProgressPos can be 63000
|
|
356
|
+
mRightProgressPos = Math.min(mRangeSeekBarView.getSelectedMaxValue() + scrollPos, mDuration);
|
|
357
|
+
mRedProgressBarPos = mLeftProgressPos;
|
|
358
|
+
} else {
|
|
359
|
+
isSeeking = true;
|
|
360
|
+
scrollPos = (long) (mAverageMsPx * (RECYCLER_VIEW_PADDING + scrollX) / VideoTrimmerUtil.mThumbWidth);
|
|
361
|
+
mLeftProgressPos = mRangeSeekBarView.getSelectedMinValue() + scrollPos;
|
|
362
|
+
|
|
363
|
+
// when scrolling the highlighted section mRightProgressPos in some cases can bigger than mDuration
|
|
364
|
+
// Eg: mDuration=62006, then mRightProgressPos can be 63000
|
|
365
|
+
mRightProgressPos = Math.min(mRangeSeekBarView.getSelectedMaxValue() + scrollPos, mDuration);
|
|
366
|
+
mRedProgressBarPos = mLeftProgressPos;
|
|
367
|
+
if (mVideoView.isPlaying()) {
|
|
368
|
+
mVideoView.pause();
|
|
369
|
+
setPlayPauseViewIcon(false);
|
|
370
|
+
}
|
|
371
|
+
mRedProgressIcon.setVisibility(GONE);
|
|
372
|
+
seekTo(mLeftProgressPos);
|
|
373
|
+
|
|
374
|
+
mRangeSeekBarView.setStartEndTime(mLeftProgressPos, mRightProgressPos);
|
|
375
|
+
mRangeSeekBarView.invalidate();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
lastScrollX = scrollX;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* 水平滑动了多少px
|
|
384
|
+
*/
|
|
385
|
+
private int calcScrollXDistance() {
|
|
386
|
+
LinearLayoutManager layoutManager = (LinearLayoutManager) mVideoThumbRecyclerView.getLayoutManager();
|
|
387
|
+
int position = layoutManager.findFirstVisibleItemPosition();
|
|
388
|
+
View firstVisibleChildView = layoutManager.findViewByPosition(position);
|
|
389
|
+
int itemWidth = firstVisibleChildView.getWidth();
|
|
390
|
+
return (position) * itemWidth - firstVisibleChildView.getLeft();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private void playingRedProgressAnimation() {
|
|
394
|
+
pauseRedProgressAnimation();
|
|
395
|
+
playingAnimation();
|
|
396
|
+
mAnimationHandler.post(mAnimationRunnable);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private void playingAnimation() {
|
|
400
|
+
if (mRedProgressIcon.getVisibility() == View.GONE) {
|
|
401
|
+
mRedProgressIcon.setVisibility(View.VISIBLE);
|
|
402
|
+
}
|
|
403
|
+
final LayoutParams params = (LayoutParams) mRedProgressIcon.getLayoutParams();
|
|
404
|
+
int start = (int) (RECYCLER_VIEW_PADDING + (mRedProgressBarPos - scrollPos) * averagePxMs);
|
|
405
|
+
int end = (int) (RECYCLER_VIEW_PADDING + (mRightProgressPos - scrollPos) * averagePxMs);
|
|
406
|
+
mRedProgressAnimator = ValueAnimator.ofInt(start, end).setDuration((mRightProgressPos - scrollPos) - (mRedProgressBarPos - scrollPos));
|
|
407
|
+
mRedProgressAnimator.setInterpolator(new LinearInterpolator());
|
|
408
|
+
mRedProgressAnimator.addUpdateListener(animation -> {
|
|
409
|
+
params.leftMargin = (int) animation.getAnimatedValue();
|
|
410
|
+
mRedProgressIcon.setLayoutParams(params);
|
|
411
|
+
});
|
|
412
|
+
mRedProgressAnimator.start();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private void pauseRedProgressAnimation() {
|
|
416
|
+
mRedProgressIcon.clearAnimation();
|
|
417
|
+
if (mRedProgressAnimator != null && mRedProgressAnimator.isRunning()) {
|
|
418
|
+
mAnimationHandler.removeCallbacks(mAnimationRunnable);
|
|
419
|
+
mRedProgressAnimator.cancel();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private Runnable mAnimationRunnable = () -> updateVideoProgress();
|
|
424
|
+
|
|
425
|
+
private void updateVideoProgress() {
|
|
426
|
+
long currentPosition = mVideoView.getCurrentPosition();
|
|
427
|
+
if (currentPosition >= (mRightProgressPos)) {
|
|
428
|
+
mRedProgressBarPos = mLeftProgressPos;
|
|
429
|
+
pauseRedProgressAnimation();
|
|
430
|
+
onVideoPause();
|
|
431
|
+
} else {
|
|
432
|
+
mAnimationHandler.post(mAnimationRunnable);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Cancel trim thread execut action when finish
|
|
438
|
+
*/
|
|
439
|
+
@Override public void onDestroy() {
|
|
440
|
+
BackgroundExecutor.cancelAll("", true);
|
|
441
|
+
UiThreadExecutor.cancelAll("");
|
|
442
|
+
}
|
|
443
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.videotrim.widgets;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.media.MediaMetadataRetriever;
|
|
5
|
+
import android.net.Uri;
|
|
6
|
+
import android.util.AttributeSet;
|
|
7
|
+
import android.widget.VideoView;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
public class ZVideoView extends VideoView {
|
|
11
|
+
private int mVideoWidth = 480;
|
|
12
|
+
private int mVideoHeight = 480;
|
|
13
|
+
private int videoRealW = 1;
|
|
14
|
+
private int videoRealH = 1;
|
|
15
|
+
|
|
16
|
+
public ZVideoView(Context context) {
|
|
17
|
+
super(context);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public ZVideoView(Context context, AttributeSet attrs) {
|
|
21
|
+
super(context, attrs);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public ZVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
25
|
+
super(context, attrs, defStyleAttr);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Override
|
|
29
|
+
public void setVideoURI(Uri uri) {
|
|
30
|
+
super.setVideoURI(uri);
|
|
31
|
+
MediaMetadataRetriever retr = new MediaMetadataRetriever();
|
|
32
|
+
retr.setDataSource(uri.getPath());
|
|
33
|
+
String height = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
|
|
34
|
+
String width = retr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
|
|
35
|
+
try {
|
|
36
|
+
videoRealH = Integer.parseInt(height);
|
|
37
|
+
videoRealW = Integer.parseInt(width);
|
|
38
|
+
} catch (NumberFormatException e) {
|
|
39
|
+
e.printStackTrace();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Override
|
|
44
|
+
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
45
|
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<LinearLayout
|
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
5
|
+
android:layout_width="wrap_content"
|
|
6
|
+
android:layout_height="60dp"
|
|
7
|
+
android:orientation="vertical"
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
<ImageView
|
|
11
|
+
android:id="@+id/thumb"
|
|
12
|
+
android:layout_width="39dp"
|
|
13
|
+
android:layout_height="match_parent"
|
|
14
|
+
android:scaleType="centerCrop"
|
|
15
|
+
tools:src="@color/red"/>
|
|
16
|
+
</LinearLayout>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<RelativeLayout
|
|
3
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
xmlns:tools="http://schemas.android.com/tools"
|
|
5
|
+
android:layout_width="match_parent"
|
|
6
|
+
android:layout_height="match_parent"
|
|
7
|
+
android:orientation="vertical"
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
<RelativeLayout
|
|
11
|
+
android:id="@+id/layout_surface_view"
|
|
12
|
+
android:layout_width="match_parent"
|
|
13
|
+
android:layout_height="match_parent"
|
|
14
|
+
android:layout_above="@+id/layout"
|
|
15
|
+
android:background="@android:color/black"
|
|
16
|
+
android:gravity="center"
|
|
17
|
+
>
|
|
18
|
+
|
|
19
|
+
<com.videotrim.widgets.ZVideoView
|
|
20
|
+
android:id="@+id/video_loader"
|
|
21
|
+
android:layout_width="match_parent"
|
|
22
|
+
android:layout_height="match_parent"
|
|
23
|
+
android:layout_centerInParent="true" />
|
|
24
|
+
|
|
25
|
+
</RelativeLayout>
|
|
26
|
+
|
|
27
|
+
<RelativeLayout
|
|
28
|
+
android:id="@+id/layout"
|
|
29
|
+
android:layout_width="match_parent"
|
|
30
|
+
android:layout_height="wrap_content"
|
|
31
|
+
android:layout_alignParentBottom="true"
|
|
32
|
+
android:background="@android:color/black"
|
|
33
|
+
>
|
|
34
|
+
|
|
35
|
+
<!-- <TextView-->
|
|
36
|
+
<!-- android:id="@+id/video_shoot_tip"-->
|
|
37
|
+
<!-- android:layout_width="match_parent"-->
|
|
38
|
+
<!-- android:layout_height="30dp"-->
|
|
39
|
+
<!-- android:layout_alignParentLeft="true"-->
|
|
40
|
+
<!-- android:layout_alignParentStart="true"-->
|
|
41
|
+
<!-- android:layout_alignParentTop="true"-->
|
|
42
|
+
<!-- android:gravity="center"-->
|
|
43
|
+
<!-- android:textColor="@android:color/white"-->
|
|
44
|
+
<!-- android:textSize="12dp"-->
|
|
45
|
+
<!-- tools:text="拖动选择你要发表的10秒以内片段"-->
|
|
46
|
+
<!-- />-->
|
|
47
|
+
<FrameLayout
|
|
48
|
+
android:id="@+id/video_frames_layout"
|
|
49
|
+
android:layout_width="match_parent"
|
|
50
|
+
android:layout_height="60dp"
|
|
51
|
+
>
|
|
52
|
+
|
|
53
|
+
<androidx.recyclerview.widget.RecyclerView
|
|
54
|
+
android:id="@+id/video_frames_recyclerView"
|
|
55
|
+
android:layout_width="match_parent"
|
|
56
|
+
android:layout_height="50dp"
|
|
57
|
+
android:layout_gravity="bottom"
|
|
58
|
+
tools:background="@android:color/holo_green_light"
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<ImageView
|
|
62
|
+
android:id="@+id/positionIcon"
|
|
63
|
+
android:layout_width="wrap_content"
|
|
64
|
+
android:layout_height="50dp"
|
|
65
|
+
android:layout_gravity="bottom"
|
|
66
|
+
android:layout_marginLeft="35dp"
|
|
67
|
+
android:contentDescription="@null"
|
|
68
|
+
android:src="@drawable/icon_seek_bar"
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<LinearLayout
|
|
72
|
+
android:layout_width="match_parent"
|
|
73
|
+
android:layout_height="wrap_content"
|
|
74
|
+
android:layout_gravity="bottom"
|
|
75
|
+
android:orientation="horizontal"
|
|
76
|
+
>
|
|
77
|
+
|
|
78
|
+
<View
|
|
79
|
+
android:layout_width="35dp"
|
|
80
|
+
android:layout_height="50dp"
|
|
81
|
+
android:layout_gravity="bottom"
|
|
82
|
+
android:background="@color/shadow_color"
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
<LinearLayout
|
|
86
|
+
android:id="@+id/seekBarLayout"
|
|
87
|
+
android:layout_width="0dp"
|
|
88
|
+
android:layout_height="60dp"
|
|
89
|
+
android:layout_gravity="bottom"
|
|
90
|
+
android:layout_weight="1"
|
|
91
|
+
android:gravity="bottom"
|
|
92
|
+
android:orientation="vertical"
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
<View
|
|
96
|
+
android:layout_width="35dp"
|
|
97
|
+
android:layout_height="match_parent"
|
|
98
|
+
android:layout_gravity="bottom"
|
|
99
|
+
android:background="@color/shadow_color"
|
|
100
|
+
/>
|
|
101
|
+
</LinearLayout>
|
|
102
|
+
</FrameLayout>
|
|
103
|
+
|
|
104
|
+
<FrameLayout
|
|
105
|
+
android:layout_width="match_parent"
|
|
106
|
+
android:layout_height="50dp"
|
|
107
|
+
android:layout_below="@+id/video_frames_layout"
|
|
108
|
+
android:layout_marginLeft="10dp"
|
|
109
|
+
android:layout_marginRight="10dp"
|
|
110
|
+
android:orientation="horizontal"
|
|
111
|
+
android:visibility="visible"
|
|
112
|
+
>
|
|
113
|
+
|
|
114
|
+
<TextView
|
|
115
|
+
android:id="@+id/cancelBtn"
|
|
116
|
+
android:layout_width="wrap_content"
|
|
117
|
+
android:layout_height="match_parent"
|
|
118
|
+
android:layout_gravity="left"
|
|
119
|
+
android:gravity="center"
|
|
120
|
+
android:padding="10dp"
|
|
121
|
+
android:text="@string/cancel"
|
|
122
|
+
android:textColor="@android:color/white"
|
|
123
|
+
android:textSize="16dp" />
|
|
124
|
+
|
|
125
|
+
<ImageView
|
|
126
|
+
android:id="@+id/icon_video_play"
|
|
127
|
+
android:layout_width="wrap_content"
|
|
128
|
+
android:layout_height="match_parent"
|
|
129
|
+
android:layout_gravity="center"
|
|
130
|
+
android:padding="12dp"
|
|
131
|
+
android:src="@drawable/ic_video_play_black"
|
|
132
|
+
/>
|
|
133
|
+
|
|
134
|
+
<TextView
|
|
135
|
+
android:id="@+id/saveBtn"
|
|
136
|
+
android:layout_width="wrap_content"
|
|
137
|
+
android:layout_height="match_parent"
|
|
138
|
+
android:layout_gravity="right"
|
|
139
|
+
android:gravity="center"
|
|
140
|
+
android:padding="10dp"
|
|
141
|
+
android:text="@string/save"
|
|
142
|
+
android:textColor="#2196F3"
|
|
143
|
+
android:textSize="16dp" />
|
|
144
|
+
|
|
145
|
+
</FrameLayout>
|
|
146
|
+
|
|
147
|
+
</RelativeLayout>
|
|
148
|
+
</RelativeLayout>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<color name="colorPrimary">#3F51B5</color>
|
|
4
|
+
<color name="colorPrimaryDark">#303F9F</color>
|
|
5
|
+
<color name="colorAccent">#FF4081</color>
|
|
6
|
+
|
|
7
|
+
<color name="white">#ffffffff</color>
|
|
8
|
+
<color name="red">#aacc0000</color>
|
|
9
|
+
<color name="background_video_color">#2f3031</color>
|
|
10
|
+
<color name="background_progress_color">#6C6D6D</color>
|
|
11
|
+
<color name="progress_color">#aacc0000</color>
|
|
12
|
+
<color name="line_color">#FF15FF00</color>
|
|
13
|
+
<color name="shadow_color">#7F000000</color>
|
|
14
|
+
<color name="line_button">#565758</color>
|
|
15
|
+
<color name="black_translucent">#50000000</color>
|
|
16
|
+
<color name="top_bottom">#42BAF8</color>
|
|
17
|
+
</resources>
|