在获取到后端返回的全部相关人物视频片段后,前端需要提供给用户一个剪辑的窗口。用户可以自由对返回片段进行排序重组、并且对视频片段进行进一步的时长裁剪等调整。
在进入剪辑页面后,首先需要根据视频片段合成初始的完整视频
返回的视频片段,放入特定文件夹中,需要根据文件路径获取所有的视频片段路径,创建 txt 文件 videolist.txt
由于项目已经集成了 ffmpeg 框架,这里直接可以执行 FFmpegCmd 的方法执行命令
public void concat_video(){
String strPath = base_path + "work/" + fileName;
File dir = new File(strPath);//文件夹dir
File[] files = dir.listFiles();//文件夹下的所有文件或文件夹
if (files != null){
for (int i = 0; i < files.length; i++) {
PageLog.dTag(TAG,files[i].getAbsolutePath());
UrlList.add(files[i].getName());
}
}
try {
File videolist = new File(strPath + "/videolist.txt");
if(!videolist.exists())
videolist.createNewFile();
BufferedWriter out = new BufferedWriter(new FileWriter(videolist,true));
for(int i = 0 ;i < UrlList.size(); i++){
String content = "file '" + UrlList.get(i) + "'";
out.write(content);
out.newLine();
}
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
String cmd = "ffmpeg -f concat -i " + strPath + "/videolist.txt " + "-c copy " + video_url_work;
PageLog.dTag(TAG,cmd);
fFmpegCmd.ffmpeg_cmd(cmd);
}
剪辑页面主要是视频播放器,此外也需要创建一个 RecyclerView,来展示所有的视频片段
protected void initViews() {
mTitles = ResUtils.getStringArray(R.array.work_titles);
mVideoView = binding.player;
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);
// Glide.with(this).load(THUMB).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);
titleView.setTitle(fileName);
mVideoView.setVideoController(controller);
//播放状态监听
mVideoView.addOnStateChangeListener(mOnStateChangeListener);
mVideoView.setUrl(video_url_work);
mVideoView.start();
mRecyclerView = binding.recycleVideoView;
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(getContext());
mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(mLinearLayoutManager);
mAdapter = new RecyclerViewAdapter(base_path, fileName, UrlList);
mRecyclerView.setAdapter(mAdapter);
helper.attachToRecyclerView(mRecyclerView);
mRecyclerView.addOnItemTouchListener(new OnRecyclerItemClickListener(mRecyclerView) {
@Override
public void onItemClick(RecyclerView.ViewHolder vh) {
}
@Override
public void onItemLongClick(RecyclerView.ViewHolder vh) {
//如果item不是最后一个,则执行拖拽
if (vh.getLayoutPosition() != UrlList.size() - 1) {
helper.startDrag(vh);
} else if (!TextUtils.equals(UrlList.get(UrlList.size() - 1), "add")) {
helper.startDrag(vh);
}
}
});
}
RecyclerView 的数据与视图的匹配,需要 Adapter 适配器
这里主要是根据视频 url 获取视频的第一帧图片,并且显示在 ImageView 上
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.VideoHolder>{
private ArrayList<String> UrlList;
private String absolut_url;
public RecyclerViewAdapter(String base_path, String fileName, ArrayList<String> UrlList) {
this.UrlList = UrlList;
absolut_url = base_path + "work/" + fileName + "/";
}
@NonNull
@Override
public VideoHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_thumb, parent, false);
return new RecyclerViewAdapter.VideoHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull VideoHolder holder, int position) {
String url = absolut_url + UrlList.get(position);
PageLog.dTag("WorkFragment", url);
// 设置封面图片
Glide.with(holder.mThumb.getContext()).setDefaultRequestOptions(
new RequestOptions()
.frame(0)
.centerCrop()
).load(url).placeholder(android.R.color.darker_gray).into(holder.mThumb);
holder.mPosition = position;
}
@Override
public int getItemCount() {
return UrlList.size();
}
public class VideoHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public int mPosition;
public ImageView mThumb;
VideoHolder(View itemView) {
super(itemView);
mThumb = itemView.findViewById(R.id.vthumb);
itemView.setTag(this);
}
@Override
public void onClick(View v) {
}
}
}
这里实现了长按视频片段,可以拖动视频,改变视频片段排序的功能,需要一个 ItemTouchHelper,用于实现Recyclerview 拖拽效果的帮助
private ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
//设置监听拖拽的方向
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
int swipeFlags = 0;
return makeMovementFlags(dragFlags, swipeFlags);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//得到item原来的position
int toPosition = target.getAdapterPosition();//得到目标position
if ((toPosition == UrlList.size() - 1 || UrlList.size() - 1 == fromPosition) && TextUtils.equals(UrlList.get(UrlList.size() - 1), "add")) {
return true;
}
//滑动事件
Collections.swap(UrlList, viewHolder.getAdapterPosition(), target.getAdapterPosition());
mAdapter.notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
// 修改原视频顺序
reorder_video();
return false;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
@Override
public boolean isLongPressDragEnabled() {
//是否可拖拽
return false;
}
@Override
public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
viewHolder.itemView.setScaleX(1.1f);
viewHolder.itemView.setScaleY(1.1f);
}
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (!recyclerView.isComputingLayout()) {
//拖拽结束后恢复view的状态
viewHolder.itemView.setScaleX(1.0f);
viewHolder.itemView.setScaleY(1.0f);
}
}
});
需要为 RecyclerView 设置点击每一个子组件的监听,特别为手势点击、长按来进行监听。
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
private GestureDetectorCompat mGestureDetector;
private RecyclerView recyclerView;
public OnRecyclerItemClickListener(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener());
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapUp(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child != null) {
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
onItemClick(vh);
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child != null) {
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
onItemLongClick(vh);
}
}
}
public abstract void onItemClick(RecyclerView.ViewHolder vh);
public abstract void onItemLongClick(RecyclerView.ViewHolder vh);
}
在长按拖动视频片段排序后,需要相应重新调整合成视频的片段排序
public void reorder_video(){
String strPath = base_path + "work/" + fileName;
try {
File videolist = new File(strPath + "/videolist.txt");
BufferedWriter out = new BufferedWriter(new FileWriter(videolist,false));
for(int i = 0 ;i < UrlList.size(); i++){
String content = "file '" + UrlList.get(i) + "'";
out.write(content);
out.newLine();
}
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
String cmd = "ffmpeg -f concat -i " + strPath + "/videolist.txt " + "-c copy " + video_url_work;
PageLog.dTag(TAG,cmd);
fFmpegCmd.ffmpeg_cmd(cmd);
mVideoView.release();
mVideoView.setUrl(video_url_work);
mVideoView.start();
}