介绍Canvas为前端提供了一个动画展示的平台。随着视频娱乐的普及,你有没有想过将Canvas动画导出到视频中?目前纯前端视频编码转换(如WebMEncoderWhammy)还有很多局限性。比较成熟的方案是将每一帧图片传给后端实现,后端调用FFmpeg进行视频转码。整体流程并不复杂,本文将带您走完整个流程。整体方案是前端记录Canvas动画的每一帧,以base64字符串的形式发送给后端。使用nodefluent-ffmpeg模块,调用FFmpeg将图片合并成视频,将视频存储在服务器端,并返回相应的下载url给前端通过请求前端部分的视频文件生成每一帧图片的生成,可以通过canvas原生接口生成图片到DataURL,最后以base64的形式返回图片数据。generatePng(){...varimgData=canvas.toDataURL("图像/png");returnimgData;}动画录制和图片流动画的录制和传输是一个异步过程,这里返回一个Promise,等待后端处理完成,当收到response时这个异步过程完成。下面的代码将画布每一帧的动画信息存储到一个图片数组imgs中,并将该数组转成字符串发送给后端。请注意,这里的contentType设置为“text/plain”。generateVideo(){varthat=this;returnnewPromise(function(resolve,reject){varimgs=[];...window.requestAnimationFrame(that.recordTick.bind(that,imgs,resolve,reject));})}recordTick(imgs,resolve,reject)){...//记录每一帧动画的信息,比如时间戳等if(...){//动画终止条件this.stopPlay();imgs.push(这个.generatePng());$.ajax({url:'/video/record',data:imgs.join(''),method:'POST',contentType:'text/plain',success:function(data,textStatus,jqXHR){resolve(数据);},错误:函数(jqXHR,textStatus,errorThrown){reject(errorThrown);}});}else{...//每一帧动画显示的代码imgs.push(this.generatePng());window.requestAnimationFrame(this.recordTick.bind(this,imgs,resolve,reject));}}视频下载在上一段代码中,当动画停止时,会发送所有的图片数据,经过后端处理后,返回的数据中包含一个url,是视频文件的下载地址。为了支持浏览器端用户点击下载,我们需要使用a标签的download属性,可以支持点击a标签后下载指定文件。editor.generateVideo().then(function(data){videoRecordingModal.setDownloadLink(data.url,data.filename);videoRecordingModal.changeStatus('recorded');});setDownloadLink:function(url,filename){this.config.$dom.find('.video-download').attr('href',url);this.config.$dom.find('.video-download').attr('download',filename);}后端部分图像序列生成接收到前端传来的图像数据后,我们首先需要解析图像并将其存储在服务器中。我们创建一个以当前时间戳命名的文件夹,并将图像序列以特定格式存储在其中。由于每张图片的写入是一个异步过程,所以我们需要使用Promise.all来保证所有的图片都处理完之后再进行视频转码过程。Promise.all(imgs.map(function(value,index){varimg=decodeBase64Image(value)vardata=img.datavartype=img.typereturnnewPromise(function(resolve,reject){fs.writeFile(path.resolve(__dirname,(folder+'/img'+index+'.'+type)),data,'base64',function(err){if(err){reject(err)}else{resolve()}})})})).then(function(){...//视频转码})这里引用了decodeBase64Image函数。视频生成视频生成利用FFmpeg转码工具。首先确保服务器端安装了FFmpegbrewinstallffmpeg。在项目中安装fluent-ffmpeg。这是node调用ffmpeg的接口模块。npminstallfluent-ffmpeg--save结合上一节存储图片序列的代码,整个界面代码如下:post('/video/record',function(req,res){varimgs=req.text.split('')vartimeStamp=Date.now()varfolder='images/'+timeStampif(!fs.existsSync(resolve(folder))){fs.mkdirSync(resolve(folder));}Promise.all(imgs.map(function(value,index){varimg=decodeBase64Image(value)vardata=img.datavar类型=img.typereturnnewPromise(function(resolve,reject){fs.writeFile(path.resolve(__dirname,(folder+'/img'+index+'.'+type)),data,'base64',函数(err){if(err){reject(err)}else{resolve()}})})})).then(function(){varproc=newffmpeg({source:resolve(folder+'/img%d.png'),nolog:true}).withFps(25).on('end',function(){res.status(200)res.send({url:'/video/mpeg/'+timeStamp,filename:'jianshi'+timeStamp+'.mpeg'})}).on('error',function(err){console.log('ERR:'+err.message)}).saveToFile(resolve('video/jianshi'+timeStamp+'.mpeg'))})})videodownload最后将视频文件传到前端的接口代码如下:app.get('/video/mpeg/:timeStamp',function(req,res){res.contentType('mpeg');varrstream=fs.createReadStream(resolve('video/jianshi'+req.params.timeStamp+'.mpeg'));rstream.pipe(res,{end:true});})效果预览注:该函数是个人项目“简氏”的一部分,完整代码可以查看https://github.com/moyuer1992...
