为了让用户更好地理解返回目标视频片段在原视频中的映射,使其理清关注部分的视频主线脉络,我们在视频剪辑页面前首先提供了一个原视频映射页面,具体呈现形式如下:
后端首先返回的是一个文件,包含视频片段在服务器的路径、视频片段对应于原视频的起始帧序和结尾帧序,是否出现目标约束等信息。安卓端需要首先解析该文件,建立起映射关系,并请求下载视频片段
public void getShortVideos(int task_position){
TaskBean task = tasks.get(task_position);
String video_name = task.getFilename();
String json_path = work_path + "/" + video_name + "/short_video.csv";
PageLog.dTag(TAG, "I am here again");
PageLog.dTag(TAG, json_path);
List<ShortVideoBean> ShortVideos = new ArrayList<>();
File csv = new File(json_path);
csv.setReadable(true);
csv.setWritable(true);
InputStreamReader isr = null;
BufferedReader br = null;
try {
isr = new InputStreamReader(new FileInputStream(csv), "UTF-8");
br = new BufferedReader(isr);
} catch (Exception e) {
e.printStackTrace();
}
String line = "";
int iteration = 0;
try {
while ((line = br.readLine()) != null) {
if(iteration == 0) {
iteration++;
continue;
}
ShortVideoBean sv = new ShortVideoBean();
String[] split=line.split(",");
String file_path = split[0];
String sv_name = file_path.substring(file_path.lastIndexOf("/")+1);
String[] split2 = sv_name.split("_");
String[] split3 = split2[1].split("\\.");
sv.setFace1(Integer.parseInt(split[1]));
sv.setFace2(Integer.parseInt(split[2]));
sv.setScene(Integer.parseInt(split[3]));
sv.setFile_path(file_path);
sv.setStart_frame(Integer.parseInt(split2[0]));
sv.setFinish_frame(Integer.parseInt(split3[0]));
if(task.getCutMode() == CutMode.SINGLE_PERSON){
if(sv.getFace1() == 1)
ShortVideos.add(sv);
}
else if(task.getCutMode() == CutMode.DOUBLE_PERSON){
if(sv.getFace1()*sv.getFace2() == 1)
ShortVideos.add(sv);
}
else if(task.getCutMode() == CutMode.SINGLE_PERSON_SCENE){
if(sv.getFace1()*sv.getScene() == 1)
ShortVideos.add(sv);
}
else if(task.getCutMode() == CutMode.DOUBLE_PERSON_SCENE){
if(sv.getFace1()*sv.getFace2()*sv.getScene() == 1)
ShortVideos.add(sv);
}
}
} catch (IOException e) {
e.printStackTrace();
}
task.setShortVideos(ShortVideos);
}
但是为了将视频片段的起始帧序和结尾帧序转换为对应到原视频的时间信息,还需要对原视频进行帧率获取
```java
public void getFrameRate(){
MediaExtractor extractor = new MediaExtractor();
try {
extractor.setDataSource(video_url_work);
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; ++i) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
extractor.release();
}
}
在得到这部分信息后,需要重新绘制视频进度条
public class VodControlView extends FrameLayout implements IControlComponent, View.OnClickListener, SeekBar.OnSeekBarChangeListener {
private String TAG = "VodControlView";
protected ControlWrapper mControlWrapper;
private TextView mTotalTime, mCurrTime;
private ImageView mFullScreen;
private LinearLayout mBottomContainer; // 底部容器
private SeekBar mVideoProgress; // 视频拖动条
private ProgressBar mBottomProgress; // 底部进度条
private ImageView mPlayButton;
private List<Integer> nodeList;
private int radius;
private int foreColor = Color.parseColor("#79CDCD");
private Paint forePaint;
private int totaltime;
private int frameRate;
private boolean mIsDragging;
private boolean mIsShowBottomProgress = true;
public VodControlView(@NonNull Context context) {
super(context);
}
public VodControlView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public VodControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
{
setVisibility(GONE);
LayoutInflater.from(getContext()).inflate(getLayoutId(), this, true);
mFullScreen = findViewById(R.id.fullscreen);
mFullScreen.setOnClickListener(this);
mBottomContainer = findViewById(R.id.bottom_container);
mVideoProgress = findViewById(R.id.seekBar);
mVideoProgress.setOnSeekBarChangeListener(this);
mTotalTime = findViewById(R.id.total_time);
mCurrTime = findViewById(R.id.curr_time);
mPlayButton = findViewById(R.id.iv_play);
mPlayButton.setOnClickListener(this);
mBottomProgress = findViewById(R.id.bottom_progress);
}
@SuppressLint("DrawAllocation")
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
PageLog.dTag(TAG, "on draw");
forePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
forePaint.setColor(foreColor);
forePaint.setStyle(Paint.Style.FILL);
radius = 10;
int viewY = mVideoProgress.getTop() + mBottomContainer.getTop() + 55;
if(mBottomContainer.getVisibility() == VISIBLE){
for (int i=0; i<nodeList.size(); i++){
int time = nodeList.get(i) /frameRate * 1000;
int pos = (int) (time * 1.0 / totaltime * mVideoProgress.getMax());
int loc = mVideoProgress.getLeft() + 10 + pos * mVideoProgress.getMeasuredWidth() / mVideoProgress.getMax();
PageLog.dTag(TAG, "pos: "+pos);
PageLog.dTag(TAG, "max: "+mVideoProgress.getMax());
PageLog.dTag(TAG, mVideoProgress.getWidth() + "");
PageLog.dTag(TAG, pos * mVideoProgress.getMeasuredWidth() / mVideoProgress.getMax() + "");
canvas.drawCircle(loc, viewY + 5, radius , forePaint);
}
}
}
public void setNodes(List<Integer> nodes){
this.nodeList = nodes;
}
public void setFrameRate(int fr){this.frameRate = fr;}
protected int getLayoutId() {
return R.layout.player_layout_vod_control_view;
}
/**
* 是否显示底部进度条,默认显示
*/
public void showBottomProgress(boolean isShow) {
mIsShowBottomProgress = isShow;
}
@Override
public void attach(@NonNull ControlWrapper controlWrapper) {
mControlWrapper = controlWrapper;
}
@Override
public View getView() {
return this;
}
@Override
public void onVisibilityChanged(boolean isVisible, Animation anim) {
if (isVisible) {
mBottomContainer.setVisibility(VISIBLE);
if (anim != null) {
mBottomContainer.startAnimation(anim);
}
if (mIsShowBottomProgress) {
mBottomProgress.setVisibility(GONE);
}
} else {
mBottomContainer.setVisibility(GONE);
if (anim != null) {
mBottomContainer.startAnimation(anim);
}
if (mIsShowBottomProgress) {
mBottomProgress.setVisibility(VISIBLE);
AlphaAnimation animation = new AlphaAnimation(0f, 1f);
animation.setDuration(300);
mBottomProgress.startAnimation(animation);
}
}
}
@Override
public void onPlayStateChanged(int playState) {
switch (playState) {
case VideoView.STATE_IDLE:
case VideoView.STATE_PLAYBACK_COMPLETED:
setVisibility(GONE);
// 播放完成,季度条归零
mBottomProgress.setProgress(0);
mBottomProgress.setSecondaryProgress(0);
mVideoProgress.setProgress(0);
mVideoProgress.setSecondaryProgress(0);
break;
case VideoView.STATE_START_ABORT:
case VideoView.STATE_PREPARING:
case VideoView.STATE_PREPARED:
case VideoView.STATE_ERROR:
setVisibility(GONE);
break;
case VideoView.STATE_PLAYING:
mPlayButton.setSelected(true);
if (mIsShowBottomProgress) {
if (mControlWrapper.isShowing()) {
mBottomProgress.setVisibility(GONE);
mBottomContainer.setVisibility(VISIBLE);
} else {
mBottomContainer.setVisibility(GONE);
mBottomProgress.setVisibility(VISIBLE);
}
} else {
mBottomContainer.setVisibility(GONE);
}
setVisibility(VISIBLE);
//开始刷新进度
mControlWrapper.startProgress();
break;
case VideoView.STATE_PAUSED:
mPlayButton.setSelected(false);
break;
case VideoView.STATE_BUFFERING:
case VideoView.STATE_BUFFERED:
mPlayButton.setSelected(mControlWrapper.isPlaying());
break;
}
}
@Override
public void onPlayerStateChanged(int playerState) {
// 是否全屏
switch (playerState) {
case VideoView.PLAYER_NORMAL:
mFullScreen.setSelected(false);
break;
case VideoView.PLAYER_FULL_SCREEN:
mFullScreen.setSelected(true);
break;
}
// 根据转向调整底部栏位置
Activity activity = PlayerUtils.scanForActivity(getContext());
if (activity != null && mControlWrapper.hasCutout()) {
int orientation = activity.getRequestedOrientation();
int cutoutHeight = mControlWrapper.getCutoutHeight();
if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
mBottomContainer.setPadding(0, 0, 0, 0);
mBottomProgress.setPadding(0, 0, 0, 0);
} else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
mBottomContainer.setPadding(cutoutHeight, 0, 0, 0);
mBottomProgress.setPadding(cutoutHeight, 0, 0, 0);
} else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
mBottomContainer.setPadding(0, 0, cutoutHeight, 0);
mBottomProgress.setPadding(0, 0, cutoutHeight, 0);
}
}
}
@Override
public void setProgress(int duration, int position) {
// duration 总时长
// position 当前时长
if (mIsDragging) {
return;
}
// 设置进度,要保证两个进度条的一致性
if (mVideoProgress != null) {
if (duration > 0) {
mVideoProgress.setEnabled(true);
int pos = (int) (position * 1.0 / duration * mVideoProgress.getMax());
mVideoProgress.setProgress(pos);
mBottomProgress.setProgress(pos);
} else {
mVideoProgress.setEnabled(false);
}
int percent = mControlWrapper.getBufferedPercentage();
if (percent >= 95) {
mVideoProgress.setSecondaryProgress(mVideoProgress.getMax());
mBottomProgress.setSecondaryProgress(mBottomProgress.getMax());
} else {
mVideoProgress.setSecondaryProgress(percent * 10);
mBottomProgress.setSecondaryProgress(percent * 10);
}
}
// 及时变换时间
if (mTotalTime != null){
mTotalTime.setText(stringForTime(duration));
totaltime = duration;
}
if (mCurrTime != null)
mCurrTime.setText(stringForTime(position));
}
@Override
public void onLockStateChanged(boolean isLocked) {
onVisibilityChanged(!isLocked, null);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.fullscreen) {
toggleFullScreen();
} else if (id == R.id.iv_play) {
mControlWrapper.togglePlay();
}
}
/**
* 横竖屏切换
*/
private void toggleFullScreen() {
Activity activity = PlayerUtils.scanForActivity(getContext());
mControlWrapper.toggleFullScreen(activity);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
mIsDragging = true;
mControlWrapper.stopProgress();
mControlWrapper.stopFadeOut();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 计算出新位置,调整进度条
long duration = mControlWrapper.getDuration();
long newPosition = (duration * seekBar.getProgress()) / mVideoProgress.getMax();
mControlWrapper.seekTo((int) newPosition);
mIsDragging = false;
mControlWrapper.startProgress();
mControlWrapper.startFadeOut();
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (!fromUser) {
return;
}
// 计算出新位置,调整进度条
long duration = mControlWrapper.getDuration();
long newPosition = (duration * progress) / mVideoProgress.getMax();
if (mCurrTime != null)
mCurrTime.setText(stringForTime((int) newPosition));
}
}
展示页面的完整代码如下:
@Page(anim = CoreAnim.none)
public class DisplayFragment extends BaseFragment<FragmentDisplayBinding> implements View.OnClickListener {
String TAG = "DisplayFragment";
private String base_path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/DCIM/easycut/";
private String work_path = Environment.getExternalStorageDirectory().getAbsolutePath()+"/DCIM/easycut/Work/";
private VideoView mVideoView;
private ArrayList<String> UrlList;
private ArrayList<String> picture_urlist;
private int frameRate = 24;
private String video_url_work;
private String fileName;
private List<ShortVideoBean> ShortVideos = new ArrayList<>();
private List<Integer> nodes;
private int cutMode;
protected RecyclerViewAdapter mAdapter;
protected RecyclerView RecyclerView;
protected RecyclerView Picture_RecyclerView;
protected PictureRecyclerViewAdapter pAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
cutMode = bundle.getInt("CutMode");
fileName = bundle.getString("fileName");
ShortVideos = (ArrayList) bundle.getSerializable("ShortVideos");
video_url_work = work_path + "/" + fileName + "/" + fileName + ".mp4";
picture_urlist = new ArrayList<>();
picture_urlist.add( work_path + "/" + fileName + "/1.jpg");
if(cutMode == CutMode.DOUBLE_PERSON)
picture_urlist.add( work_path + "/" + fileName + "/2.jpg");
else if(cutMode == CutMode.SINGLE_PERSON_SCENE)
picture_urlist.add( work_path + "/" + fileName + "/3.jpg");
else if(cutMode == CutMode.DOUBLE_PERSON_SCENE){
picture_urlist.add( work_path + "/" + fileName + "/2.jpg");
picture_urlist.add( work_path + "/" + fileName + "/3.jpg");
}
UrlList = new ArrayList<>();
nodes = new ArrayList<>();
for(int i=0; i<ShortVideos.size(); i++){
UrlList.add(i + ".mp4");
nodes.add(ShortVideos.get(i).getStart_frame());
}
for(int i = 0 ; i<picture_urlist.size(); i++){
PageLog.dTag("Task", picture_urlist.get(i));
}
getFrameRate();
}
@NonNull
@Override
protected FragmentDisplayBinding viewBindingInflate(LayoutInflater inflater, ViewGroup container) {
return FragmentDisplayBinding.inflate(inflater, container, false);
}
@Override
protected TitleBar initTitle() {
return null;
}
@Override
protected void initViews() {
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());//点播控制条
vodControlView.setNodes(nodes);
vodControlView.setFrameRate(frameRate);
vodControlView.invalidate();
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();
RecyclerView = binding.recycleVideoView;
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(getContext());
mLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
RecyclerView.setLayoutManager(mLinearLayoutManager);
mAdapter = new RecyclerViewAdapter(base_path, fileName, UrlList);
RecyclerView.setAdapter(mAdapter);
RecyclerView.addOnItemTouchListener(new DisplayFragment.OnRecyclerItemClickListener(RecyclerView) {
@Override
public void onItemClick(RecyclerView.ViewHolder vh) {
PageLog.dTag(TAG, UrlList.get(vh.getLayoutPosition()));
int time = nodes.get(vh.getLayoutPosition());
mVideoView.seekTo(time/frameRate*1000);
}
@Override
public void onItemLongClick(RecyclerView.ViewHolder vh) {
}
});
Picture_RecyclerView = binding.pictureRecycleview;
LinearLayoutManager mLinearLayoutManager2 = new LinearLayoutManager(getContext());
mLinearLayoutManager2.setOrientation(LinearLayoutManager.HORIZONTAL);
Picture_RecyclerView.setLayoutManager(mLinearLayoutManager2);
pAdapter = new PictureRecyclerViewAdapter(picture_urlist);
Picture_RecyclerView.setAdapter(pAdapter);
}
@Override
protected void initListeners() {
binding.nextStep.setOnClickListener(this);
}
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:
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;
}
}
};
@Override
public void onClick(View view) {
int id = view.getId();
if(id == R.id.next_step){
Bundle params = new Bundle();
params.putString("fileName", fileName);
openNewPage(WorkFragment.class, params);
}
}
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 DisplayFragment.OnRecyclerItemClickListener.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 getFrameRate(){
MediaExtractor extractor = new MediaExtractor();
try {
extractor.setDataSource(video_url_work);
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; ++i) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
extractor.release();
}
}
@Override
public void onPause() {
super.onPause();
mVideoView.release();
}
}