近日项目有个新需求,需要对视频或音频进行多段裁剪然后拼接。例如,一段视频长30分钟,我需要将5-10分钟、17-22分钟、24-29分钟这三段拼接到一起成一整段视频。裁剪在前端,拼接在后端。
网上简单找了找,基本都是客户端内的工具,没有纯网页的裁剪。既然没有,那就动手写一个。
代码已上传到GitHub: https://github.com/fengma1992/media-cut-tool
废话不多,下面就来看看怎么设计的。
效果图
图中底部的功能块为裁剪工具组件,上方的视频为演示用,当然也能是音频。
功能特点:
支持鼠标拖拽输入与键盘数字输入两种模式;
支持预览播放指定裁剪片段;
左侧鼠标输入与右侧键盘输入联动;
鼠标移动时自动捕捉高亮拖拽条;
确认裁剪时自动去重;
*注:项目中的图标都替换成了文字
思路
整体来看,通过一个数据数组 cropItemList 来保存用户输入数据,不管是鼠标拖拽还是键盘输入,都来操作 cropItemList 实现两侧数据联动。最后通过处理 cropItemList 来输出用户想要的裁剪。
cropItemList 结构如下:
cropItemList: [ { startTime: 0, // 开始时间 endTime: 100, // 结束时间 startTimeArr: [hoursStr, minutesStr, secondsStr], // 时分秒字符串 endTimeArr: [hoursStr, minutesStr, secondsStr], // 时分秒字符串 startTimeIndicatorOffsetX: 0, // 开始时间在左侧拖动区X偏移量 endTimeIndicatorOffsetX: 100, // 结束时间在左侧拖动区X偏移量 } ]
第一步
既然是多段裁剪,那么用户得知道裁剪了哪些时间段,这通过右侧的裁剪列表来呈现。
列表
列表存在三个状态:
无数据状态
无数据的时候显示内容为空,当用户点击输入框时主动为他生成一条数据,默认为视频长度的1/4到3/4处。
有一条数据
此时界面显示很简单,将唯一一条数据呈现。
有多条数据
有多条数据时就得有额外处理了,因为第1条数据在最下方,而如果用 v-for 去循环 cropItemList,那么就会出现下图的状况:
而且,第1条最右侧是添加按钮,而剩下的最右侧都是删除按钮。所以,我们 将第1条单独提出来写,然后将 cropItemList 逆序生成一个 renderList 并循环 renderList 的 0 -> listLength - 2 条
即可。
<template v-for="(item, index) in renderList"> <div v-if="index < listLength -1" :key="index"> ... ... </div> </template>
下图为最终效果:
时分秒输入
这个其实就是写三个 input 框,设 type="text" (设成 type=number 输入框右侧会有上下箭头),然后通过监听input事件来保证输入的正确性并更新数据。监听focus事件来确定是否需要在 cropItemList 为空时主动添加一条数据。
<div> <input type="text" :value="renderList[listLength -1] && renderList[listLength -1].startTimeArr[0]" @input="startTimeChange($event, 0, 0)" @focus="inputFocus()"/> : <input type="text" :value="renderList[listLength -1] && renderList[listLength -1].startTimeArr[1]" @input="startTimeChange($event, 0, 1)" @focus="inputFocus()"/> : <input type="text" :value="renderList[listLength -1] && renderList[listLength -1].startTimeArr[2]" @input="startTimeChange($event, 0, 2)" @focus="inputFocus()"/> </div>
播放片段
点击播放按钮时会通过 playingItem 记录当前播放的片段,然后向上层发出 play 事件并带上播放起始时间。同样还有 pause 和 stop 事件,来控制媒体暂停与停止。
<CropTool :duration="duration" :playing="playing" :currentPlayingTime="currentTime" @play="playVideo" @pause="pauseVideo" @stop="stopVideo"/>
/** * 播放选中片段 * @param index */ playSelectedClip: function (index) { if (!this.listLength) { console.log('无裁剪片段') return } this.playingItem = this.cropItemList[index] this.playingIndex = index this.isCropping = false this.$emit('play', this.playingItem.startTime || 0) }
这里控制了开始播放,那么如何让媒体播到裁剪结束时间的时候自动停止呢?
监听媒体的 timeupdate 事件并实时对比媒体的 currentTime 与 playingItem 的 endTime ,达到的时候就发出 pause 事件通知媒体暂停。
if (currentTime >= playingItem.endTime) { this.pause() }
至此,键盘输入的裁剪列表基本完成,下面介绍鼠标拖拽输入。
第二步
下面介绍如何通过鼠标点击与拖拽输入。
1、确定鼠标交互逻辑
新增裁剪
鼠标在拖拽区点击后,新增一条裁剪数据,开始时间与结束时间均为 mouseup 时进度条的时间,并让结束时间戳跟随鼠标移动,进入编辑状态。
确认时间戳
编辑状态,鼠标移动时,时间戳根据鼠标在进度条的当前位置来随动,鼠标再次点击后确认当前时间,并终止时间戳跟随鼠标移动。
更改时间