preface
For each video clip returned from the back end, the front end needs to ensure that the user can cut and fine tune it. The specific implementation effects are as follows:
Here, the whole process can be disassembled into the following steps:
- Acquisition and display of all video frames
- Implementation of video sliding marquee RangeSeekBarView
- Intercept the video according to the start and end time
Acquisition and display of all video frames
First, the layout xml file of the whole page is as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@color/black"> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="4"> <com.dueeeke.videoplayer.player.VideoView android:id="@+id/mVideoView" android:layout_width="wrap_content" android:layout_height="300dp" app:layout_constraintDimensionRatio="16:10" android:layout_centerInParent="true"/> <TextView android:id="@+id/mTvOk" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="complete" android:textSize="15sp" android:padding="10px" android:layout_alignParentRight="true" android:layout_marginTop="20dp" android:layout_marginRight="15dp" android:textColor="@color/xui_btn_blue_normal_color"/> <TextView android:id="@+id/mTvCancel" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="cancel" android:textSize="15sp" android:padding="10px" android:layout_alignParentLeft="true" android:layout_marginTop="20dp" android:layout_marginLeft="15dp" android:textColor="@android:color/white"/> </RelativeLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="2" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/mRecyclerView" android:layout_width="match_parent" android:layout_height="50dp" android:paddingLeft="20dp" android:paddingRight="20dp" android:clipToPadding="false" android:layout_marginTop="10dp" /> <com.xuexiang.easycut.component.RangeSeekBarView android:id="@+id/mRangeSeekBarView" android:layout_width="match_parent" android:layout_height="60dp" android:layout_marginLeft="20dp" android:layout_marginRight="20dp"/> <!--Add a mask to the space at both ends start--> <View android:layout_width="20dp" android:layout_height="60dp" android:background="@color/shadow_color"/> <View android:layout_width="20dp" android:layout_height="60dp" android:layout_alignParentRight="true" android:background="@color/shadow_color"/> <!--Add a mask to the space at both ends end--> </RelativeLayout> </RelativeLayout> </LinearLayout>
At the top is a video player VideoView, and at the bottom is RecyclerView, which displays all video frames, as well as a custom RangeSeekBarView to complete the sliding duration interception
When it is applied to RecyclerView, the corresponding adapter is required to display data, and the frameadapter is defined to complete the adaptation of picture frame to page. The sub View here is ImageView, which represents picture frame picture
public class FramesAdapter extends RecyclerView.Adapter<FramesAdapter.ViewHolder> { private List<String> list = new ArrayList<>(); private int mWidth = Utils.dp2px(35f); public FramesAdapter(){ } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.frames_item_layout,parent,false)); } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { Glide.with(holder.mIv.getContext()).load(list.get(position)).into(holder.mIv); ViewGroup.LayoutParams layoutParams = holder.mIv.getLayoutParams(); layoutParams.width = mWidth; holder.mIv.setLayoutParams(layoutParams); } @Override public int getItemCount() { return list.size(); } public void updateList(@NotNull List<String> list) { this.list.clear(); this.list.addAll(list); notifyDataSetChanged(); } public void updateItem(int position, @NotNull String outfile) { this.list.set(position,outfile); notifyItemChanged(position); } public void setItemWidth(int mWidth) { this.mWidth = mWidth; } public class ViewHolder extends RecyclerView.ViewHolder{ private final ImageView mIv; public ViewHolder(@NonNull View itemView) { super(itemView); mIv = itemView.findViewById(R.id.mIv); } } }
In the InitViews method, first add the code to initialize the video player and RecyclerView
// VideoView mVideoView = binding.mVideoView; StandardVideoController controller = new StandardVideoController(this.getContext()); controller.setEnableOrientation(true); PrepareView prepareView = new PrepareView(this.getContext());//Ready to play interface prepareView.setClickStart(); ImageView thumb = prepareView.findViewById(R.id.thumb);//Cover picture Glide.with(this).setDefaultRequestOptions( new RequestOptions() .frame(0) .centerCrop() ).load(video_url_work).placeholder(android.R.color.darker_gray).into(thumb); controller.addControlComponent(prepareView); controller.addControlComponent(new CompleteView(this.getContext()));//Auto complete playback interface controller.addControlComponent(new ErrorView(this.getContext()));//Error interface TitleView titleView = new TitleView(this.getContext());//Title Block controller.addControlComponent(titleView); VodControlView vodControlView = new VodControlView(this.getContext());//On demand control bar controller.addControlComponent(vodControlView); GestureView gestureControlView = new GestureView(this.getContext());//Slide control view controller.addControlComponent(gestureControlView); mVideoView.setVideoController(controller); mVideoView.addOnStateChangeListener(mOnStateChangeListener); mVideoView.setUrl(video_url_work); mVideoView.start(); // RecyclerView mRecyclerView = binding.mRecyclerView; mAdapter = new FramesAdapter(); LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(getContext()); mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(mLinearLayoutManager); mRecyclerView.setAdapter(mAdapter);
It is estimated that a picture frame will be obtained in 1s, so the total duration of the video needs to be obtained first. It should be noted here that the correct duration can be obtained only after the VideoView completes the preparation state. Therefore, it is necessary to monitor and calculate mFrames in the state
private VideoView.OnStateChangeListener mOnStateChangeListener = new VideoView.SimpleOnStateChangeListener() { @Override public void onPlayerStateChanged(int playerState) { switch (playerState) { case VideoView.PLAYER_NORMAL://Small screen break; case VideoView.PLAYER_FULL_SCREEN://Full screen break; } } @Override public void onPlayStateChanged(int playState) { switch (playState) { case VideoView.STATE_IDLE: break; case VideoView.STATE_PREPARING: break; case VideoView.STATE_PREPARED: mFrames = mVideoView.getDuration() / 1000; gotoGetFrameAtTime(0); break; case VideoView.STATE_PLAYING: break; case VideoView.STATE_PAUSED: break; case VideoView.STATE_BUFFERING: break; case VideoView.STATE_BUFFERED: break; case VideoView.STATE_PLAYBACK_COMPLETED: break; case VideoView.STATE_ERROR: break; } } };
gotoGetFrameAtTime(int time) method is to obtain the frame corresponding to time in the video, which needs to be obtained through ffmpeg command
// Get picture frame private void gotoGetFrameAtTime(int time){ if(time >= mFrames) return; String outfile = frames_path + "/" + time + ".jpg"; String cmd = "ffmpeg -ss " + time + " -i " + video_url_work + " -preset " + "ultrafast" + " -frames:v 1 -f image2 -s " + mWidth + "x" + mHeight + " -y " + outfile; fFmpegCmd.ffmpeg_cmd(cmd); if(time == 0){ for (int i = 0; i<mFrames; i++) { list.add(outfile); } mAdapter.updateList(list); }else{ list.set(time, outfile); mAdapter.updateItem(time, outfile); } gotoGetFrameAtTime(time + 1); }
Implementation of video sliding marquee RangeSeekBarView
The basic idea of RangeSeekBarView implementation is to set monitoring, obtain the start time and end time of the current video selection, and redraw the position and duration display of RangeSeekBarView in RecyclerView through onDraw method
public class RangeSeekBarView extends View { private static final String TAG = RangeSeekBarView.class.getSimpleName(); public static final int INVALID_POINTER_ID = 255; public static final int ACTION_POINTER_INDEX_MASK = 0x0000ff00, ACTION_POINTER_INDEX_SHIFT = 8; private static final int TextPositionY = Utils.dp2px(7); private static final int paddingTop = Utils.dp2px(10); private int mActivePointerId = INVALID_POINTER_ID; private long mMinShootTime = 3*1000;//Minimum clip 3s, default private double absoluteMinValuePrim, absoluteMaxValuePrim; private double normalizedMinValue = 0d;//The proportional value of point coordinates in the total length, ranging from 0-1 private double normalizedMaxValue = 1d;//The proportional value of point coordinates in the total length, ranging from 0-1 private double normalizedMinValueTime = 0d; private double normalizedMaxValueTime = 1d;// Normalized: normalized -- the proportional value of point coordinates in the total length, ranging from 0-1 private int mScaledTouchSlop; private Bitmap thumbImageLeft; private Bitmap thumbImageRight; private Bitmap thumbPressedImage; private Paint paint; private Paint rectPaint; private final Paint mVideoTrimTimePaintL = new Paint(); private final Paint mVideoTrimTimePaintR = new Paint(); private final Paint mShadow = new Paint(); private int thumbWidth; private float thumbHalfWidth; private final float padding = 0; private long mStartPosition = 0; private long mEndPosition = 0; private float thumbPaddingTop = 0; private boolean isTouchDown; private float mDownMotionX; private boolean mIsDragging; private Thumb pressedThumb; private boolean isMin; private double min_width = 1;//Minimum clipping distance private boolean notifyWhileDragging = false; private OnRangeSeekBarChangeListener mRangeSeekBarChangeListener; private int whiteColorRes = getContext().getResources().getColor(R.color.white); public enum Thumb { MIN, MAX } public RangeSeekBarView(Context context) { this(context,null); } public RangeSeekBarView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public RangeSeekBarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.absoluteMinValuePrim = 0*1000; this.absoluteMaxValuePrim = 10*1000; setFocusable(true); setFocusableInTouchMode(true); init(); } private void init() { // mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); thumbImageLeft = BitmapFactory.decodeResource(getResources(), R.drawable.ic_video_thumb_handle); int width = thumbImageLeft.getWidth(); int height = thumbImageLeft.getHeight(); int newWidth = Utils.dp2px(12.5f); int newHeight = Utils.dp2px(50f); float scaleWidth = newWidth * 1.0f / width; float scaleHeight = newHeight * 1.0f / height; Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); thumbImageLeft = Bitmap.createBitmap(thumbImageLeft, 0, 0, width, height, matrix, true); thumbImageRight = thumbImageLeft; thumbPressedImage = thumbImageLeft; thumbWidth = newWidth; thumbHalfWidth = thumbWidth / 2f; int shadowColor = getContext().getResources().getColor(R.color.shadow_color); mShadow.setAntiAlias(true); mShadow.setColor(shadowColor); paint = new Paint(Paint.ANTI_ALIAS_FLAG); rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); rectPaint.setStyle(Paint.Style.FILL); rectPaint.setColor(whiteColorRes); mVideoTrimTimePaintL.setStrokeWidth(3); mVideoTrimTimePaintL.setARGB(255, 51, 51, 51); mVideoTrimTimePaintL.setTextSize(28); mVideoTrimTimePaintL.setAntiAlias(true); mVideoTrimTimePaintL.setColor(whiteColorRes); mVideoTrimTimePaintL.setTextAlign(Paint.Align.LEFT); mVideoTrimTimePaintR.setStrokeWidth(3); mVideoTrimTimePaintR.setARGB(255, 51, 51, 51); mVideoTrimTimePaintR.setTextSize(28); mVideoTrimTimePaintR.setAntiAlias(true); mVideoTrimTimePaintR.setColor(whiteColorRes); mVideoTrimTimePaintR.setTextAlign(Paint.Align.RIGHT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = 300; if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) { width = MeasureSpec.getSize(widthMeasureSpec); } int height = 120; if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) { height = MeasureSpec.getSize(heightMeasureSpec); } setMeasuredDimension(width, height); } @SuppressLint("DrawAllocation") @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); float bg_middle_left = 0; float bg_middle_right = getWidth() - getPaddingRight(); float rangeL = normalizedToScreen(normalizedMinValue); float rangeR = normalizedToScreen(normalizedMaxValue); Rect leftRect = new Rect((int) bg_middle_left, getHeight(), (int) rangeL, 0); Rect rightRect = new Rect((int) rangeR, getHeight(), (int) bg_middle_right, 0); canvas.drawRect(leftRect, mShadow); canvas.drawRect(rightRect, mShadow); //border-top canvas.drawRect(rangeL + thumbHalfWidth, thumbPaddingTop + paddingTop, rangeR - thumbHalfWidth, thumbPaddingTop + Utils.dp2px(2) + paddingTop, rectPaint); //bottom canvas.drawRect(rangeL + thumbHalfWidth, getHeight() - Utils.dp2px(2), rangeR - thumbHalfWidth, getHeight(), rectPaint); //Draw left thumb drawThumb(normalizedToScreen(normalizedMinValue), false, canvas, true); //Draw right thumb drawThumb(normalizedToScreen(normalizedMaxValue), false, canvas, false); //Draw text drawVideoTrimTimeText(canvas); } private void drawThumb(float screenCoord, boolean pressed, Canvas canvas, boolean isLeft) { canvas.drawBitmap(pressed ? thumbPressedImage : (isLeft ? thumbImageLeft : thumbImageRight), screenCoord - (isLeft ? 0 : thumbWidth), paddingTop, paint); } private void drawVideoTrimTimeText(Canvas canvas) { String leftThumbsTime = Utils.convertSecondsToTime(mStartPosition); String rightThumbsTime = Utils.convertSecondsToTime(mEndPosition); canvas.drawText(leftThumbsTime, normalizedToScreen(normalizedMinValue), TextPositionY, mVideoTrimTimePaintL); canvas.drawText(rightThumbsTime, normalizedToScreen(normalizedMaxValue), TextPositionY, mVideoTrimTimePaintR); } @Override public boolean onTouchEvent(MotionEvent event) { if (isTouchDown) { return super.onTouchEvent(event); } if (event.getPointerCount() > 1) { return super.onTouchEvent(event); } if (!isEnabled()) return false; if (absoluteMaxValuePrim <= mMinShootTime) { return super.onTouchEvent(event); } int pointerIndex;// Record the index of click points final int action = event.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: //Remember the coordinate x of the point where the last finger clicks on the screen, mDownMotionX mActivePointerId = event.getPointerId(event.getPointerCount() - 1); pointerIndex = event.findPointerIndex(mActivePointerId); mDownMotionX = event.getX(pointerIndex); // Judge whether the touch reaches the maximum value thumb or the minimum value thumb pressedThumb = evalPressedThumb(mDownMotionX); if (pressedThumb == null) return super.onTouchEvent(event); setPressed(true);// Set the control to be pressed onStartTrackingTouch();// Set mIsDragging to true and start tracking touch events trackTouchEvent(event); attemptClaimDrag(); if (mRangeSeekBarChangeListener != null) { mRangeSeekBarChangeListener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_DOWN, isMin, pressedThumb); } break; case MotionEvent.ACTION_MOVE: if (pressedThumb != null) { if (mIsDragging) { trackTouchEvent(event); } else { // Scroll to follow the motion event pointerIndex = event.findPointerIndex(mActivePointerId); final float x = event.getX(pointerIndex);// The X coordinate of the point of the finger on the control // The finger does not point on the maximum and minimum value, and there is a sliding event on the control if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) { setPressed(true); invalidate(); onStartTrackingTouch(); trackTouchEvent(event); attemptClaimDrag(); } } if (notifyWhileDragging && mRangeSeekBarChangeListener != null) { mRangeSeekBarChangeListener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_MOVE, isMin, pressedThumb); } } break; case MotionEvent.ACTION_UP: if (mIsDragging) { trackTouchEvent(event); onStopTrackingTouch(); setPressed(false); } else { onStartTrackingTouch(); trackTouchEvent(event); onStopTrackingTouch(); } invalidate(); if (mRangeSeekBarChangeListener != null) { mRangeSeekBarChangeListener.onRangeSeekBarValuesChanged(this, getSelectedMinValue(), getSelectedMaxValue(), MotionEvent.ACTION_UP, isMin, pressedThumb); } pressedThumb = null;// When the finger is raised, the thumb touched by the touch is set to be empty break; case MotionEvent.ACTION_POINTER_DOWN: final int index = event.getPointerCount() - 1; // final int index = ev.getActionIndex(); mDownMotionX = event.getX(index); mActivePointerId = event.getPointerId(index); invalidate(); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(event); invalidate(); break; case MotionEvent.ACTION_CANCEL: if (mIsDragging) { onStopTrackingTouch(); setPressed(false); } invalidate(); // see above explanation break; default: break; } return true; } private void onSecondaryPointerUp(MotionEvent ev) { final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT; final int pointerId = ev.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mDownMotionX = ev.getX(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); } } private void trackTouchEvent(MotionEvent event) { if (event.getPointerCount() > 1) return; final int pointerIndex = event.findPointerIndex(mActivePointerId);// Get the index of the pressed point float x = 0; try { x = event.getX(pointerIndex); } catch (Exception e) { return; } if (Thumb.MIN.equals(pressedThumb)) { // Screentonormalized (x) -- > get the normalized value of 0-1 setNormalizedMinValue(screenToNormalized(x, 0)); } else if (Thumb.MAX.equals(pressedThumb)) { setNormalizedMaxValue(screenToNormalized(x, 1)); } } private double screenToNormalized(float screenCoord, int position) { int width = getWidth(); if (width <= 2 * padding) { // prevent division by zero, simply return 0. return 0d; } else { isMin = false; double current_width = screenCoord; float rangeL = normalizedToScreen(normalizedMinValue); float rangeR = normalizedToScreen(normalizedMaxValue); double min = mMinShootTime / (absoluteMaxValuePrim - absoluteMinValuePrim) * (width - thumbWidth * 2); if (absoluteMaxValuePrim > 5 * 60 * 1000) {//Four exact decimal places greater than 5 minutes DecimalFormat df = new DecimalFormat("0.0000"); min_width = Double.parseDouble(df.format(min)); } else { min_width = Math.round(min + 0.5d); } if (position == 0) { if (isInThumbRangeLeft(screenCoord, normalizedMinValue, 0.5)) { return normalizedMinValue; } float rightPosition = (getWidth() - rangeR) >= 0 ? (getWidth() - rangeR) : 0; double left_length = getValueLength() - (rightPosition + min_width); if (current_width > rangeL) { current_width = rangeL + (current_width - rangeL); } else if (current_width <= rangeL) { current_width = rangeL - (rangeL - current_width); } if (current_width > left_length) { isMin = true; current_width = left_length; } if (current_width < thumbWidth * 2 / 3) { current_width = 0; } double resultTime = (current_width - padding) / (width - 2 * thumbWidth); normalizedMinValueTime = Math.min(1d, Math.max(0d, resultTime)); double result = (current_width - padding) / (width - 2 * padding); return Math.min(1d, Math.max(0d, result));// Ensure that the value is between 0-1, but when is this judgment useful? } else { if (isInThumbRange(screenCoord, normalizedMaxValue, 0.5)) { return normalizedMaxValue; } double right_length = getValueLength() - (rangeL + min_width); if (current_width > rangeR) { current_width = rangeR + (current_width - rangeR); } else if (current_width <= rangeR) { current_width = rangeR - (rangeR - current_width); } double paddingRight = getWidth() - current_width; if (paddingRight > right_length) { isMin = true; current_width = getWidth() - right_length; paddingRight = right_length; } if (paddingRight < thumbWidth * 2 / 3) { current_width = getWidth(); paddingRight = 0; } double resultTime = (paddingRight - padding) / (width - 2 * thumbWidth); resultTime = 1 - resultTime; normalizedMaxValueTime = Math.min(1d, Math.max(0d, resultTime)); double result = (current_width - padding) / (width - 2 * padding); return Math.min(1d, Math.max(0d, result)); } } } private int getValueLength() { return (getWidth() - 2 * thumbWidth); } /** * Calculate which Thumb is in * * @param touchX touchX * @return Is the minimum or maximum value of touch null */ private Thumb evalPressedThumb(float touchX) { Thumb result = null; boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue, 2);// Is the touch point within the minimum picture range boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue, 2); if (minThumbPressed && maxThumbPressed) { // If two thumbs are overlapped and it is impossible to determine which one to drag, do the following // If the touch point is on the right side of the screen, it is judged that the touch has reached the minimum value thumb; otherwise, it is judged that the touch has reached the maximum value thumb result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX; } else if (minThumbPressed) { result = Thumb.MIN; } else if (maxThumbPressed) { result = Thumb.MAX; } return result; } private boolean isInThumbRange(float touchX, double normalizedThumbValue, double scale) { // X coordinate of the current touch point - the difference between the X coordinate of the center point of the minimum picture on the screen < = the general width of the minimum picture // That is, judge whether the touch point is in a circle with the center of the minimum picture as the origin and half the width as the radius. return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth * scale; } private boolean isInThumbRangeLeft(float touchX, double normalizedThumbValue, double scale) { // X coordinate of the current touch point - the difference between the X coordinate of the center point of the minimum picture on the screen < = the general width of the minimum picture // That is, judge whether the touch point is in a circle with the center of the minimum picture as the origin and half the width as the radius. return Math.abs(touchX - normalizedToScreen(normalizedThumbValue) - thumbWidth) <= thumbHalfWidth * scale; } /** * Trying to tell the parent view not to intercept the child control's drag */ private void attemptClaimDrag() { if (getParent() != null) { getParent().requestDisallowInterceptTouchEvent(true); } }
It should be noted here that in addition to dragging RangeSeekBarView to redraw, the current start and end time is also displayed, and the sliding of RecyclerView also needs to redraw RangeSeekBarView. Therefore, the code of initViews needs to add the listening settings of two components
// Sliding listening of RecyclerView mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); LinearLayoutManager lm = (LinearLayoutManager)recyclerView.getLayoutManager(); mFirstPosition = lm.findFirstVisibleItemPosition(); mMinTime = mRangeSeekBarView.getSelectedMinValue() + (mFirstPosition * 1000); mMaxTime = mRangeSeekBarView.getSelectedMaxValue() + (mFirstPosition * 1000); mRangeSeekBarView.setStartEndTime(mMinTime, mMaxTime); mRangeSeekBarView.invalidate(); // The video player jumps to the clip position mVideoView.seekTo((int)mMinTime); } }); // Drag monitoring of RangeSeekBarView mRangeSeekBarView.setSelectedMinValue(mMinTime); mRangeSeekBarView.setSelectedMaxValue(mMaxTime); mRangeSeekBarView.setStartEndTime(mMinTime, mMaxTime); mRangeSeekBarView.setNotifyWhileDragging(true); mRangeSeekBarView.setOnRangeSeekBarChangeListener(new RangeSeekBarView.OnRangeSeekBarChangeListener(){ @Override public void onRangeSeekBarValuesChanged(RangeSeekBarView bar, long minValue, long maxValue, int action, boolean isMin, RangeSeekBarView.Thumb pressedThumb) { mMinTime = minValue + (mFirstPosition * 1000); mMaxTime = maxValue + (mFirstPosition * 1000); mRangeSeekBarView.setStartEndTime(mMinTime, mMaxTime); // The video player jumps to the clip position mVideoView.seekTo((int)mMinTime); } });
Intercept the video according to the start and end time
When the user clicks the Save button, it is necessary to officially cut the video and delete the picture frame generated in the middle
private void trimVideo(){ String outfile = work_path; long start = mMinTime/1000; long end = mMaxTime/1000; String cmd = "ffmpeg -ss " + start + " -to " + end + " -accurate_seek" + " -i " + video_url_work + " -to " + (end - start) + " -preset " + "superfast" + " -crf 23 -c:a copy -avoid_negative_ts 0 -y " + outfile; fFmpegCmd.ffmpeg_cmd(cmd); // Delete all frames File dir = new File(frames_path); File[] files = dir.listFiles();//All files or folders under the folder if (files != null){ for (int i = 0; i < files.length; i++) { files[i].delete(); } } }