手机版

Vue视频媒体多片段剪辑组件实现示例

时间:2021-08-30 来源:互联网 编辑:宝哥软件园 浏览:

最近项目中有了新的要求,需要对视频或音频进行多段剪切和拼接。比如一个视频30分钟长,我需要把5到10分钟,17到22分钟,24到29分钟拼接成一个完整的视频。前端切割,后端拼接。

简单的网上搜索基本都是客户端的工具,没有纯粹的网页裁剪。既然没有人,就写一个。

代码已上传到github:https://github.com/fengma1992/media-cut-tool

废话不多说,我们来看看怎么设计。

翻译

图下方的功能块是刀具组件,上方的视频是演示用的,但也可以是音频。

功能特性:

支持鼠标拖放输入和键盘数字输入模式;支持预览和播放指定的剪辑;鼠标左键输入与键盘右键输入联动;鼠标移动时自动捕捉高亮拖动条;切割时确认自动去重;*注意:项目中的所有图标都已替换为文字

思路

总的来说,是用一个数据数组cropItemList保存用户输入的数据,无论是鼠标拖动还是键盘输入,都是操作cropItemList实现两边的数据联动。最后,处理作物列表以输出用户想要的作物。

CropItemList结构如下:

裁剪项目列表:[{开始时间: 0,//开始时间结束时间3360 100,//结束时间开始时间arr :[小时字符串,分钟字符串,秒字符串],//小时/分钟字符串endtimearr 3360[小时字符串,分钟字符串,秒字符串],//小时/分钟字符串starttimeindicatoroffset : 0 0,//开始时间在左拖动区域x offset endtimeindicatoroffset : 100,//结束时间在左拖动区域

由于是多段剪辑,用户需要知道剪辑了哪些时间段,这由右边的剪辑列表来表示。

目录

列表中有三个州:

无数据状态

没有数据时,显示内容为空。当用户点击输入框时,会主动为他生成一条数据。默认值为视频长度的1/4到3/4。

有一条数据

此时界面显示非常简单,只呈现一条数据。

有多条数据

当有多条数据时,需要进行额外的处理,因为第一条数据位于底部,如果使用v-for循环cropItemList,将出现下图:

此外,第1条中最右边的按钮是添加,而其余最右边的按钮是删除。因此,我们将分别编写第一篇文章,然后反转cropItemList以生成renderList,并循环renderList的0-listLength-2篇文章

动手吧。

模板v-for='(项目,索引)在呈现列表' div v-if='索引列表长度-1' :键='索引'类='裁剪时间-项目'./div/模板下图显示了最终效果:

小时、分钟和秒输入

其实这是写三个输入框,设置type='text '(设置type=number,输入框右侧会有上下箭头),然后监控输入事件,保证输入的正确性并更新数据。倾听焦点事件,确定当cropItemList为空时是否需要主动添加一条数据。

div class=' time-input ' input type=' text ' : value=' render list[list length-1]render list[list length-1]。startTimeArr[0]' @ input=' startTimeChange($ event,0,0)' @ focus=' inputFocus()'/: input type=' text ' : value=' render list[list length-1]render list[list length-1]。startTimeArr[1]' @ input=' startTimeChange($ event,0,1)' @ focus=' inputFocus()'/: input type=' text ' : value=' render list[list length-1]render list[list length-1]。startTimeArr[2] @ input='开始时间更改($ event,0,2)' @ focus=' inputfocus ()'//div播放剪辑

当点击播放按钮时,当前播放的片段会通过playingItem进行录制,然后会以播放的开始时间向上层发送播放事件。还有暂停和停止事件来控制媒体的暂停和停止。

croptol : duration=' duration ' : playing=' playing ' : currentplaying time=' current time ' @ play=' play video ' @ pauseVideo ' @ stop=' stop video '/* * *播放所选剪辑* @ param index */playselectedclip 3360函数(index) {if(!This.listLength) {console.log('无剪辑')返回} this。玩游戏=这个。裁剪项目列表[索引]这。playingindex=indexthis。iscropping=false this。$ emit ('play ',这个。玩游戏。starttime | | 0)}这里是控制开始播放的,那么如何让剪辑结束时媒体自动停止播放呢?

监控媒体的时间更新事件,实时比较媒体的当前时间和播放结束时间,到达时发出暂停事件通知媒体暂停。

If(当前时间=播放时间。end time){这个。pause ()}至此,键盘输入的剪辑列表基本完成。鼠标拖动输入描述如下。

第二步

下面介绍如何用鼠标点击和拖动输入。

1.确定鼠标交互逻辑

添加剪辑

鼠标在拖放区点击后,新增一条剪切数据,开始时间和结束时间为使用mouseup时进度条的时间,结束时间戳随鼠标移动进入编辑状态。

确认时间戳

编辑状态。当鼠标移动时,时间戳会根据鼠标在进度条上的当前位置跟随。再次单击鼠标后,确认当前时间并停止跟随鼠标移动的时间戳。

更改时间

在非编辑状态下,当鼠标在进度条上移动时,聆听mousemove事件,并在接近任何剪切数据的开始或结束时间戳时高亮显示当前数据并显示时间戳。鼠标放下后,选择时间戳并开始拖动以更改时间数据。鼠标悬停后结束更改。

2.确定需要监控的鼠标事件

鼠标需要在进度条区域监听三个事件: mousedown、mousemove和mouseup。进度条区域有很多元素,可以分为三类:

当鼠标移动时,以下时间戳包括开始时间戳、结束时间戳和浅蓝色时间掩码。首先,对mousedown和mouseup的监控绑定到进度条本身。

this . timelinecontainer . addeventlistener(' mousedown ',e={ const current current cursoroffsetx=e . clientx-container left lastmousdowoffsetx=current current current cursoroffsetx//检查时间戳是否包含此。time indicatorcheck(currentcursorofsetx,' mousedown')})这个。TimelineContainer。AddEventListener ('mouseup ',e={//已经处于剪辑状态,鼠标被抬起。如果出现以下情况,剪辑状态将被取消。正在推荐){这个。stoppering()return } const current cursoroffsetx=this。getformatedoffsetx(e . clientx-container left)//如果mousedown和mouseup位置不一致,则不认为是点击。直接回到if(math . ABS(current cursorofsetx-lastmousdowoffsetx)3){ return }//这次更新当前鼠标指向。current cursor ortime=current cursoroffsetx * this。time topixelratio//单击鼠标添加剪辑if(!这个。正在推荐){这个。addnewcropitemslider()//新的操作位置是数组的最后一位数字,这个。开始裁剪(这个。cropitemlist。length-1)}}) mousemove。不处于编辑状态时,当然是监控进度条,实现时间戳跟踪鼠标。当需要选择开始或结束时间戳进入编辑状态时,我原本设想通过监听时间戳本身来达到选择时间戳的目的。实际情况是:当鼠标接近开始或结束时间戳时,前面总有一个时间戳跟在鼠标后面,而且因为理论上剪辑可以无限增加,所以我要监控2* clip mousemove。

基于此,只监控进度条本身的mousemove,通过实时比较鼠标位置和时间戳位置来判断是否到达对应位置。当然,必须增加一个油门。

这个。timelinecontainer。addevent侦听器(' mousemove ',e={ throttle(()={ const current cursor offset x=e . client x-container left//mouse move范思哲(签名)if(当前光标偏移量0 | |当前光标偏移量容器宽度){ this.isCursorIn=false //-你好-你好慕斯比菊治如果(这个。这是。停止裁剪()这个。时间指示器检查(当前光标偏移0)是否正确?0 3330容器宽度,“mouse up”)return } else { this。iscursorin=true }这一点。当前光标时间=当前光标偏移量x * this。时间到了。当前光标偏移量=当前光标偏移量/范成才这个。时间指示器检查(当前光标偏移,“鼠标移动”)/范思哲,范思哲此。时间指示器移动(当前光标偏移量)},10,true()})3是吴惠玲吗

你是说,你是说,你是说,你是说,你是说,你是说,你是说,你是说,我是说,你是说,你是说,你是说,你是说,我是说,我是说,我是说,我是说,我是说,我是说,我是说,我是说,我是说,我是说,我是说,我是说.

时间指示器检查(当前光标偏移,mouseEvent) { //你好,阿云阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海阿海如果(这个。iscsi ping){ return }//伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇伊万诺维奇,-什么霍弗!霍弗菊治这个。startimeindicatorhovenex=-1这个。endimeindicator hovenex=-1这个。startimeindicatorragginndex=-1这个。endimendicatordragginndex=-1这个。cropitemhovenex=-1这个。cropitemlist。foreach((item,index)={ if(当前光标偏移量=item。startimeindicatoroffsetx当前光标偏移量=item。endimedateindicatorofsetx){ this。cropitemhovenex=index }//黄头发的人是黄头发的人,黄头发的人是黄头发的人,黄头发的人是黄头发的人,黄头发的人是黄头发的人,黄头发的人是黄头发的人,黄头发的人是黄头发的人,黄头发的人,黄头发的人是黄头发的人,黄头发的人,黄头发的人,黄头发的人,黄头发的人,黄头发的人,黄头发的人if(iscursorclose(项。endimeindicatoroffsetx,当前光标offsetx)){ this。endimendicatorhovenex=index//何如,你好if(鼠标事件===mousedown){ this。结束时间指示器拖动索引=索引此。current editingindex=对此进行索引。is cropping=true } } else if(is cursor close(item。startimeindicatoroffsetx),当前光标offsetx)]{此。startimeindicator hover rinex=index//何如,你好if(鼠标事件===mousedown){ this。startimeindicator ragginindex=this。current editingindex=对此进行索引。iscroping=true } } } },time indicatormove(当前光标偏移量){//西文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文阿文,阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉阿吉如果(这个。is cropping){ const current editing index=this。current editing index const startimeindicator raging index=this。startimeindicatordragging index const end time indicatordragging index=this。end time indicator raging index const current cursor time=this。当前光标时间让当前项目=这个。cropitemlist[current editing index]//station nnnnnnnif(开始时间指示器dragginindex-1当前项目){//-你好-你好if(当前光标偏离当前项目。结束时间指示器ffsetx){ return }当前项目。startimeindicatoroffsetx=当前光标偏移当前项目。开始时间=当前光标时间}//if(结束时间指示器draginindex-1当前项目){//*本文件迟交,是因为本文件迟交if(当前光标偏离当前项目。startimeindicatoroffsetx){ return }当前项目。endimeindicatoroffsetx=当前光标偏移量。结束时间=当前光标时间}这。updatecropitem(当前项目,currentEditingIndex)} }

阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔,阿叔。

阿力坤3330红薯_ 3330(#人)你好#)

范仲淹?范仲淹,阿俊啊王振(电影)哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟哟,拜占庭阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格阿韦格,卓别林,-你好-你好。-你好-你好。

吕宋吕宋吕宋

/** * cropItemList排序并去重*/clean cropItemList(){让cropItemList=this。cropItemList//1 .依据开始时间由小到大排序cropItemList=cropItemList.sort(函数(第1项,第2项){退回第1项。开始时间-第2项。开始时间})让tempCropItemList=[]让startTime=cropItemList[0]。开始时间让endTime=cropItemList[0]。结束时间常数最后一个索引=cropitemlist。长度-1//遍历,删除重复片段cropItemList.forEach((项,索引)={ //遍历到最后一项,直接写入if(最后一个索引===index){ tempcropitemlist。推送({开始时间:开始时间,结束时间:结束时间,开始时间arr :格式时间。getformat time arr(开始时间),end time arr :格式时间。getformat time arr(结束时间),})返回} //currentItem片段包含项目if(项目。结束时间=结束时间项目。开始时间=开始时间){ return }//当前项目片段与项目有重叠if(项目。开始时间=结束时间项目。结束时间=结束时间){结束时间=项目。结束时间返回}//当前项目片段与项目无重叠,向列表添加一项,更新记录参数if(项目。开始时间结束时间){ tempcropitemlist。推送({开始时间:开始时间,结束时间:结束时间,开始时间arr :格式时间。getformat time arr(开始时间),end time arr :格式时间。getformat time arr(结束时间),}) //标志量移到当前项目开始时间=项目。开始时间结束时间=项目。结束时间} })返回tempCropItemList}第四步

使用裁剪工具:通过小道具及发射事件实现媒体与裁剪工具之间的通信。

模板div id=' app '视频ref=' video ' src=' http :3359 pan。prpr。我/?/dplayer/hikarunara.mp4 '控制宽度=' 600像素'/视频CropTool :持续时间=' duration ' :播放=' playing ' :当前播放时间=' current time ' @ play=' play video ' @ pauseVideo ' @ stop=' stop video '/div/模板脚本从导入裁剪工具./组件/裁剪工具。vue ' export default { name : ' app ',components: { CropTool,},data(){ return { duration : 0 0,playing: false,currentime : 0 0,} },mounted(){ const video element=this .参考文献。视频元素。ondurationchange=()={ this。持续时间=视频元素。持续时间}视频元素。在播放=()={这个。播放=真}视频元素。on pause=()={ this。播放=假}视频元素。ontimeupdate=()={ this。当前时间=视频元素。当前时间} },方法: { seekVideo(seekTime){ this .参考文献。视频。当前时间=seekTime },playVideo(time){ this。这一次.$refs.video.play() },pauseVideo () { this .$refs.video.pause() },stopVideo () { this .$refs.video.pause()这个$refs.video.currentTime=0 }、}、}/script总结

写博客比写代码难多了,感觉很混乱的写完了这个博客。

几个小细节列表增删时的高度动画

用户界面提了个需求,最多展示10条裁剪片段,超过了之后就滚动,还得有增删动画。本来以为直接设个最大高度完事,结果发现

半铸钢钢性铸铁(铸造半钢)的过渡动画只有针对绝对值的高度有效,这就有点小麻烦,因为裁剪条数是变化的,那么高度也是在变化的。设绝对值该怎么办呢。

这里通过超文本标记语言中标签的属性属性数据计数来告诉半铸钢钢性铸铁(铸造半钢)我现在有几条裁剪,然后让半铸钢钢性铸铁(铸造半钢)根据数据计数来设置列表高度。

!-超过10条数据也只传10,让列表滚动-div class='裁剪-时间-正文' : data-count='列表长度10?10 :列表长度-1 '/div。裁剪-时间-主体{ overflow-y : auto;溢出-x:隐藏;高度:5s;[数据计数=' 0 ']{ height : 0;}[数据计数=' 1 ']{高度: 40px}[数据计数=' 2 ']{高度: 80px} .[数据计数=' 10 ']{高度: 380 px}}mousemove时事件的当前目标问题

由于DOM事件的抓取和冒泡,进度条上可能还有其他元素,比如时间戳和剪辑片段,所以mousemove事件的currentTarget可能会发生变化,这可能会导致从鼠标取进度条最左侧的offsetX出现问题;但是,如果currentTarget是进度条,就会出现问题,因为当鼠标移动时,总会有一个时间戳跟随,导致进度条对应的mousemove事件在一段时间内无法偶尔触发。

解决方法是在页面加载后,获取进度条最左边部分和页面最左边部分之间的距离。mousemove事件不是offsetX,而是根据页面最左边的部分获取clientX,然后减去它们得到鼠标距离进度条最左边的像素值。上面的代码是在添加mousemove监听中编写的。

时间格式

因为剪辑工具的很多部分需要将秒转换成00:00:00格式的字符串,所以写了一个工具函数:输入秒,输出一个包含DD、HH、MM、SS四个键的Object,每个键都是一个长度为2的字符串。由ES8的String.prototype.padStart()方法实现。

导出默认函数(秒){ const date=new Date(秒* 1000);返回{ days : String(date . getutchdate()-1)。padStart(2,' 0 '),hours 3360 String(date . getutchhours())。padStart(2,' 0 '),分钟3360字符串(date.getUTCMinutes())。padStart(2,' 0 '),seconds 3360 String(date . getutcsters())。padStart(2,' 0 ')};}以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

版权声明:Vue视频媒体多片段剪辑组件实现示例是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。