前言
对于后端返回的每一个视频片段,前端需要保证用户可以对其进行剪切微调,具体的实现效果如下: 这里可以将整个过程拆解以下几个步骤:
- 视频全部画面帧的获取与显示
- 视频滑动选取框 RangeSeekBarView 的实现
- 根据起始终止时间进行视频的截取
视频全部画面帧的获取与显示
首先整个页面的布局 xml 文件如下:
<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="完成"
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="取消"
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"/>
<!--为两端的空间增加蒙层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"/>
<!--为两端的空间增加蒙层end-->
</RelativeLayout>
</RelativeLayout>
</LinearLayout>
上部首先是一个视频播放器 VideoView,底部是 RecyclerView 展示全部视频画面帧,以及自定义的 RangeSeekBarView 来完成滑动时长截取
应用到 RecyclerView,则需要对应的适配器来显示数据,定义 FramesAdapter 完成适配画面帧到页面的适配,这里的子 View 是 ImageView,表示画面帧图片
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);
}
}
}
InitViews 方法中首先添加初始化视频播放器和 RecyclerView 的代码
mVideoView = binding.mVideoView;
StandardVideoController controller = new StandardVideoController(this.getContext());
controller.setEnableOrientation(true);
PrepareView prepareView = new PrepareView(this.getContext());
prepareView.setClickStart();
ImageView thumb = prepareView.findViewById(R.id.thumb);
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()));
controller.addControlComponent(new ErrorView(this.getContext()));
TitleView titleView = new TitleView(this.getContext());
controller.addControlComponent(titleView);
VodControlView vodControlView = new VodControlView(this.getContext());
controller.addControlComponent(vodControlView);
GestureView gestureControlView = new GestureView(this.getContext());
controller.addControlComponent(gestureControlView);
mVideoView.setVideoController(controller);
mVideoView.addOnStateChangeListener(mOnStateChangeListener);
mVideoView.setUrl(video_url_work);
mVideoView.start();
mRecyclerView = binding.mRecyclerView;
mAdapter = new FramesAdapter();
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(getContext());
mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mRecyclerView.setAdapter(mAdapter);
预计1s获取一张画面帧,因此需要首先获得视频的总时长,这里需要注意,只有 VideoView 完成预备状态后才能获得正确的时长,因此这里要在状态监听计算 mFrames
private VideoView.OnStateChangeListener mOnStateChangeListener = new VideoView.SimpleOnStateChangeListener() {
@Override
public void onPlayerStateChanged(int playerState) {
switch (playerState) {
case VideoView.PLAYER_NORMAL://小屏
break;
case VideoView.PLAYER_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) 方法就是获取视频中 time 对应时间的这一帧,需要通过 ffmpeg 命令来获取
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);
}
视频滑动选取框 RangeSeekBarView 的实现
RangeSeekBarView 实现的基本思想就是设置监听,获取当前视频选中的起始时间和终止时间,通过 onDraw 方法重绘 RangeSeekBarView 在 RecyclerView 的位置以及时长显示
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;
private double absoluteMinValuePrim, absoluteMaxValuePrim;
private double normalizedMinValue = 0d;
private double normalizedMaxValue = 1d;
private double normalizedMinValueTime = 0d;
private double normalizedMaxValueTime = 1d;
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;
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() {
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);
canvas.drawRect(rangeL + thumbHalfWidth, thumbPaddingTop + paddingTop, rangeR - thumbHalfWidth, thumbPaddingTop + Utils.dp2px(2) + paddingTop, rectPaint);
canvas.drawRect(rangeL + thumbHalfWidth, getHeight() - Utils.dp2px(2), rangeR - thumbHalfWidth, getHeight(), rectPaint);
drawThumb(normalizedToScreen(normalizedMinValue), false, canvas, true);
drawThumb(normalizedToScreen(normalizedMaxValue), false, canvas, false);
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;
final int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
pointerIndex = event.findPointerIndex(mActivePointerId);
mDownMotionX = event.getX(pointerIndex);
pressedThumb = evalPressedThumb(mDownMotionX);
if (pressedThumb == null) return super.onTouchEvent(event);
setPressed(true);
onStartTrackingTouch();
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 {
pointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(pointerIndex);
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;
break;
case MotionEvent.ACTION_POINTER_DOWN:
final int index = event.getPointerCount() - 1;
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();
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);
float x = 0;
try {
x = event.getX(pointerIndex);
} catch (Exception e) {
return;
}
if (Thumb.MIN.equals(pressedThumb)) {
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) {
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) {
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));
} 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);
}
private Thumb evalPressedThumb(float touchX) {
Thumb result = null;
boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue, 2);
boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue, 2);
if (minThumbPressed && maxThumbPressed) {
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) {
return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth * scale;
}
private boolean isInThumbRangeLeft(float touchX, double normalizedThumbValue, double scale) {
return Math.abs(touchX - normalizedToScreen(normalizedThumbValue) - thumbWidth) <= thumbHalfWidth * scale;
}
private void attemptClaimDrag() {
if (getParent() != null) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
这里需要注意到,除了拖动 RangeSeekBarView 需要进行重绘外,由于还有当前起止时间的显示,RecyclerView 的滑动也需要对 RangeSeekBarView 进行重绘。因此 initViews 的代码中需要增加两个组件的监听设置
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();
mVideoView.seekTo((int)mMinTime);
}
});
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);
mVideoView.seekTo((int)mMinTime);
}
});
根据起始终止时间进行视频的截取
当用户点击保存按钮后,需要正式对视频进行切剪,并删除中间产生的画面帧
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);
File dir = new File(frames_path);
File[] files = dir.listFiles();
if (files != null){
for (int i = 0; i < files.length; i++) {
files[i].delete();
}
}
}
|