项目实训—基于AI的智能视频剪辑器(十一)视频片段进度标识


前言

为了让用户更好地理解返回目标视频片段在原视频中的映射,使其理清关注部分的视频主线脉络,我们在视频剪辑页面前首先提供了一个原视频映射页面,具体呈现形式如下:

在这里插入图片描述


解析返回视频文件

后端首先返回的是一个文件,包含视频片段在服务器的路径、视频片段对应于原视频的起始帧序和结尾帧序,是否出现目标约束等信息。安卓端需要首先解析该文件,建立起映射关系,并请求下载视频片段

    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();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值