摘要:動畫錄制與圖片流傳輸動畫的記錄與傳送是個異步過程,這里返回一個,等待后端處理完畢,收到回應(yīng)后,即完成此異步過程。
導(dǎo)言
Canvas為前端提供了動畫展示的平臺,隨著現(xiàn)在視頻娛樂的流行,你是否想過把Canvas動畫導(dǎo)出視頻?目前純前端的視頻編碼轉(zhuǎn)換(例如WebM Encoder Whammy)還存在許多限制,較為成熟的方案是將每幀圖片傳給后端實現(xiàn),由后端調(diào)用FFmpeg進(jìn)行視頻轉(zhuǎn)碼。整體流程并不復(fù)雜,這篇文章將帶大家實現(xiàn)這個過程。
整體方案由前端記錄Canvas動畫的每幀圖像,以base64字符串形式傳給后端
利用node fluent-ffmpeg模塊,調(diào)用FFmpeg將圖片合并成視頻,并將視頻存儲在server端,并返回相應(yīng)下載url
前端通過請求得到視頻文件
前端部分 每幀圖片生成圖片生成可以通過canvas原生接口toDataURL實現(xiàn),最終返回base64形式的圖像數(shù)據(jù)。
generatePng () { ... var imgData = canvas.toDataURL("image/png"); return imgData; }動畫錄制與圖片流傳輸
動畫的記錄與傳送是個異步過程,這里返回一個Promise,等待后端處理完畢,收到回應(yīng)后,即完成此異步過程。
以下代碼將canvas每幀動畫信息存入一個圖片數(shù)組imgs中,將數(shù)組轉(zhuǎn)成字符串的形式傳給后端。注意這里contentType設(shè)置為“text/plain”。
generateVideo () { var that = this; return new Promise ( function (resolve, reject) { var imgs = []; ... window.requestAnimationFrame(that.recordTick.bind(that, imgs, resolve, reject)); } ) }
recordTick (imgs, resolve, reject) { ...//每幀動畫的記錄信息,如時間戳等 if (...) {//動畫終止條件 this.stopPlay(); imgs.push(this.generatePng()); $.ajax({ url: "/video/record", data: imgs.join(" "), method: "POST", contentType: "text/plain", success: function (data, textStatus, jqXHR) { resolve(data); }, error: function (jqXHR, textStatus, errorThrown) { reject(errorThrown); } }); } else { ...//每幀動畫展示的代碼 imgs.push(this.generatePng()); window.requestAnimationFrame(this.recordTick.bind(this, imgs, resolve, reject)); } }視頻下載
上一節(jié)代碼中,動畫停止時,會通過post請求給后端傳送所有圖片數(shù)據(jù),后端處理完畢后,返回數(shù)據(jù)中包含一個url,此url即為視頻文件的下載地址。
為了支持瀏覽器端用戶點擊下載,我們需要用到a標(biāo)簽的download屬性,此屬性可以支持點擊a標(biāo)簽后下載指定文件。
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); }后端部分 圖片序列生成
接收到前端傳送的圖片數(shù)據(jù)后,我們首先需要將圖片解析、存儲在服務(wù)器中,我們建立以當(dāng)前時間戳命名的文件夾,將圖片序列以一定格式存儲于其中。由于每張圖片寫入都是異步過程,為確保所有圖片都已處理完畢后,才執(zhí)行視頻轉(zhuǎn)碼過程,我們需要用到Promise.all。
Promise.all(imgs.map(function (value, index) { var img = decodeBase64Image(value) var data = img.data var type = img.type return new Promise(function (resolve, reject) { fs.writeFile(path.resolve(__dirname, (folder + "/img" + index + "." + type)), data, "base64", function(err) { if (err) { reject(err) } else { resolve() } }) }) })).then(function () { …//視頻轉(zhuǎn)碼 })
其中decodeBase64Image函數(shù)參考這里。
視頻生成視頻生成利用FFmpeg轉(zhuǎn)碼工具。
首先確保server端安裝了FFmpeg
brew install ffmpeg
在項目中安裝fluent-ffmpeg,這是node調(diào)用ffmpeg的接口模塊
npm install fluent-ffmpeg --save
結(jié)合上一節(jié)圖片序列存儲的代碼,整個接口代碼如下:
app.post("/video/record", function(req, res) { var imgs = req.text.split(" ") var timeStamp = Date.now() var folder = "images/" + timeStamp if (!fs.existsSync(resolve(folder))){ fs.mkdirSync(resolve(folder)); } Promise.all(imgs.map(function (value, index) { var img = decodeBase64Image(value) var data = img.data var type = img.type return new Promise(function (resolve, reject) { fs.writeFile(path.resolve(__dirname, (folder + "/img" + index + "." + type)), data, "base64", function(err) { if (err) { reject(err) } else { resolve() } }) }) })).then(function () { var proc = new ffmpeg({ 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")) }) })視頻下載
最終將視頻文件傳輸給前端的接口代碼如下:
app.get("/video/mpeg/:timeStamp", function(req, res) { res.contentType("mpeg"); var rstream = fs.createReadStream(resolve("video/jianshi" + req.params.timeStamp + ".mpeg")); rstream.pipe(res, {end: true}); })效果預(yù)覽
注:此功能是個人項目”簡詩”的一部分,完整代碼可以查看https://github.com/moyuer1992...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/82328.html
摘要:文章首發(fā)于個人博客在最近項目中需要實現(xiàn)一個精靈動畫,素材方只提供了一個短視頻素材,所以在實現(xiàn)精靈動畫之前先介紹兩個工具來幫助我們更好的實現(xiàn)需求。 文章首發(fā)于個人博客:http://heavenru.com 在最近項目中需要實現(xiàn)一個精靈動畫,素材方只提供了一個短視頻素材,所以在實現(xiàn)精靈動畫之前先介紹兩個工具來幫助我們更好的實現(xiàn)需求。在這篇文章中,主要是介紹兩個命令行工具來實現(xiàn)將一個短視頻...
摘要:文章首發(fā)于個人博客在最近項目中需要實現(xiàn)一個精靈動畫,素材方只提供了一個短視頻素材,所以在實現(xiàn)精靈動畫之前先介紹兩個工具來幫助我們更好的實現(xiàn)需求。 文章首發(fā)于個人博客:http://heavenru.com 在最近項目中需要實現(xiàn)一個精靈動畫,素材方只提供了一個短視頻素材,所以在實現(xiàn)精靈動畫之前先介紹兩個工具來幫助我們更好的實現(xiàn)需求。在這篇文章中,主要是介紹兩個命令行工具來實現(xiàn)將一個短視頻...
摘要:文章首發(fā)于個人博客在最近項目中需要實現(xiàn)一個精靈動畫,素材方只提供了一個短視頻素材,所以在實現(xiàn)精靈動畫之前先介紹兩個工具來幫助我們更好的實現(xiàn)需求。 文章首發(fā)于個人博客:http://heavenru.com 在最近項目中需要實現(xiàn)一個精靈動畫,素材方只提供了一個短視頻素材,所以在實現(xiàn)精靈動畫之前先介紹兩個工具來幫助我們更好的實現(xiàn)需求。在這篇文章中,主要是介紹兩個命令行工具來實現(xiàn)將一個短視頻...
摘要:關(guān)于節(jié)日圣誕節(jié),元旦,看大家情侶在朋友圈里發(fā)各種慶祝的或者祝福的話語,甚是感動,然后悄悄拉黑了。預(yù)覽效果本地下打開很卡,火狐正常圣誕樹早先的時候是圣誕節(jié)的時候,看到各種用字符組成圣誕樹的形式,于是自己就去試了下,還是比較簡單的。 關(guān)于節(jié)日 圣誕節(jié),元旦,看大家(情侶)在朋友圈里發(fā)各種慶祝的或者祝福的話語,甚是感動,然后悄悄拉黑了。作為單身狗,我們也有自己慶祝節(jié)日的方式,今天我們就來實現(xiàn)...
閱讀 3201·2021-11-25 09:43
閱讀 3206·2021-11-23 09:51
閱讀 3519·2019-08-30 13:08
閱讀 1569·2019-08-29 12:48
閱讀 3594·2019-08-29 12:26
閱讀 397·2019-08-28 18:16
閱讀 2562·2019-08-26 13:45
閱讀 2429·2019-08-26 12:15