【實(shí)戰(zhàn)技巧】前端利用 ffmpeg 播放本地視頻 | 您所在的位置:網(wǎng)站首頁(yè) › video播放本地文件 › 【實(shí)戰(zhàn)技巧】前端利用 ffmpeg 播放本地視頻 |
前言
使用到的第三方庫(kù):???? ffmpeg.wasm npm 上包名叫這個(gè) ???? @ffmpeg/ffmpeg ffmpeg.wasm先來(lái)學(xué)習(xí)一下這個(gè)庫(kù)的基本用法,全部的 API 都在下面了。 createFFmpeg() ffmpeg.load() ffmpeg.run() ffmpeg.FS() ffmpeg.exit() ffmpeg.setLogging() ffmpeg.setLogger() ffmpeg.setProgress() fetchFile()安裝 npm install @ffmpeg/ffmpeg @ffmpeg/core -S 復(fù)制代碼在模塊中導(dǎo)出這兩個(gè)方法 createFFmpeg, fetchFile。 import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'; 復(fù)制代碼 createFFmpegcreateFFmpeg 是一個(gè)創(chuàng)建 ffmpeg 實(shí)例的工廠函數(shù)。 import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg'; const ffmpeg = createFFmpeg({}) 復(fù)制代碼參數(shù)如下 corePath: 指定 ffmpeg-core.js 的加載路徑。 log: 是否打開所有日志,默認(rèn)為 false logger: 獲取日志消息的函數(shù),例: ({ message }) => console.log(message) progress: 跟蹤進(jìn)度的函數(shù),例: p => console.log(p) ffmpeg.loadffmpeg.load() 返回一個(gè) Promise,用來(lái)加載 ffmpeg-core.js 核心包,在瀏覽器環(huán)境中,ffmpeg.wasm-core 腳本默認(rèn)是從 CDN 中獲取的,可以在創(chuàng)建 ffmpeg 實(shí)例時(shí)通過 corePath 來(lái)指定到本地路徑。 (async () => { await ffmpeg.load(); })(); 復(fù)制代碼 ffmpeg.runffmpeg.run(...args) 返回一個(gè) Promise,官網(wǎng)說這個(gè)方法和原生的 ffmpeg 一樣,需要傳遞的參數(shù)也一樣,但我原生的也不會(huì)啊????,算了,知道用法就行了。 (async () => { /* 等價(jià)于執(zhí)行了 `$ ffmpeg -i flame.avi -s 1920x1080 output.mp4` */ await ffmpeg.run('-i', 'flame.avi', '-s', '1920x1080', 'output.mp4'); })(); 復(fù)制代碼ffmpeg 參數(shù)說明: 參數(shù)說明基本選項(xiàng):-formats輸出所有可用格式-f fmt指定格式(音頻或視頻格式)-i filename指定輸入文件名,在linux下當(dāng)然也能指定:0.0(屏幕錄制)或攝像頭-y覆蓋已有文件-t duration記錄時(shí)長(zhǎng)為t-fs limit_size設(shè)置文件大小上限-ss time_off從指定的時(shí)間(s)開始, [-]hh:mm:ss[.xxx]的格式也支持-itsoffset time_off設(shè)置時(shí)間偏移(s),該選項(xiàng)影響所有后面的輸入文件。該偏移被加到輸入文件的時(shí)戳,定義一個(gè)正偏移意味著相應(yīng)的流被延遲了 offset秒。 [-]hh:mm:ss[.xxx]的格式也支持-title string標(biāo)題-timestamp time時(shí)間戳-author string作者-copyright string版權(quán)信息-comment string評(píng)論-album stringalbum名-v verbose與log相關(guān)的-target type設(shè)置目標(biāo)文件類型("vcd", "svcd", "dvd", "dv", "dv50", "pal-vcd", "ntsc-svcd", ...)-dframes number設(shè)置要記錄的幀數(shù)視頻選項(xiàng):-b指定比特率(bits/s),似乎ffmpeg是自動(dòng)VBR的,指定了就大概是平均比特率-bitexact使用標(biāo)準(zhǔn)比特率-vb指定視頻比特率(bits/s)-vframes number設(shè)置轉(zhuǎn)換多少楨(frame)的視頻-r rate幀速率(fps) (可以改,確認(rèn)非標(biāo)準(zhǔn)楨率會(huì)導(dǎo)致音畫不同步,所以只能設(shè)定為15或者29.97)-s size指定分辨率 (320x240)-aspect aspect設(shè)置視頻長(zhǎng)寬比(4:3, 16:9 or 1.3333, 1.7777)-croptop size設(shè)置頂部切除尺寸(in pixels)-cropbottom size設(shè)置底部切除尺寸(in pixels)-cropleft size設(shè)置左切除尺寸 (in pixels)-cropright size設(shè)置右切除尺寸 (in pixels)-padtop size設(shè)置頂部補(bǔ)齊尺寸(in pixels)-padbottom size底補(bǔ)齊(in pixels)-padleft size左補(bǔ)齊(in pixels)-padright size右補(bǔ)齊(in pixels)-padcolor color補(bǔ)齊帶顏色(000000-FFFFFF)-vn取消視頻-vcodec codec強(qiáng)制使用codec編解碼方式('copy' to copy stream)-sameq使用同樣視頻質(zhì)量作為源(VBR)-pass n選擇處理遍數(shù)(1或者2)。兩遍編碼非常有用。第一遍生成統(tǒng)計(jì)信息,第二遍生成精確的請(qǐng)求的碼率-passlogfile file選擇兩遍的紀(jì)錄文件名為file-newvideo在現(xiàn)在的視頻流后面加入新的視頻流高級(jí)視頻選項(xiàng):-pix_fmt formatset pixel format, 'list' as argument shows all the pixel formats supported-intra僅適用幀內(nèi)編碼-qscale q以質(zhì)量為基礎(chǔ)的VBR,取值0.01-255,約小質(zhì)量越好-loop_input設(shè)置輸入流的循環(huán)數(shù)(目前只對(duì)圖像有效)-loop_output設(shè)置輸出視頻的循環(huán)數(shù),比如輸出gif時(shí)設(shè)為0表示無(wú)限循環(huán)-g int設(shè)置圖像組大小-cutoff int設(shè)置截止頻率-qmin int設(shè)定最小質(zhì)量,與-qmax(設(shè)定最大質(zhì)量)共用,比如-qmin 10 -qmax 31-qmax int設(shè)定最大質(zhì)量-qdiff int量化標(biāo)度間最大偏差 (VBR)-bf int使用frames B 幀,支持mpeg1,mpeg2,mpeg4音頻選項(xiàng):-ab設(shè)置比特率(單位:bit/s,也許老版是kb/s)前面-ac設(shè)為立體聲時(shí)要以一半比特率來(lái)設(shè)置,比如192kbps的就設(shè)成96,轉(zhuǎn)換 默認(rèn)比特率都較小,要聽到較高品質(zhì)聲音的話建議設(shè)到160kbps(80)以上。-aframes number設(shè)置轉(zhuǎn)換多少楨(frame)的音頻-aq quality設(shè)置音頻質(zhì)量 (指定編碼)-ar rate設(shè)置音頻采樣率 (單位:Hz),PSP只認(rèn)24000-ac channels設(shè)置聲道數(shù),1就是單聲道,2就是立體聲,轉(zhuǎn)換單聲道的TVrip可以用1(節(jié)省一半容量),高品質(zhì)的DVDrip就可以用2-an取消音頻-acodec codec指定音頻編碼('copy' to copy stream)-vol volume設(shè)置錄制音量大小(默認(rèn)為256) ,某些DVDrip的AC3軌音量極小,轉(zhuǎn)換時(shí)可以用這個(gè)提高音量,比如200就是原來(lái)的2倍-newaudio在現(xiàn)在的音頻流后面加入新的音頻流字幕選項(xiàng):-sn取消字幕-scodec codec設(shè)置字幕編碼('copy' to copy stream)-newsubtitle在當(dāng)前字幕后新增-slang code設(shè)置字幕所用的ISO 639編碼(3個(gè)字母)Audio/Video 抓取選項(xiàng):-vc channel設(shè)置視頻捕獲通道(只對(duì)DV1394)-tvstd standard設(shè)置電視標(biāo)準(zhǔn)?NTSC PAL(SECAM) ffmpeg.FSffmpeg.FS(method, ...args) 用來(lái)運(yùn)行 FS 作。 對(duì)于 ffmpeg.wasm 的輸入/輸出文件,需要先將它們保存到 MEMFS 以便 ffmpeg.wasm 能夠使用它們。這里我們依賴 Emscripten 提供的 FS 方法♂?。 參數(shù)如下 method:? 需要執(zhí)行的方法名。 args:?執(zhí)行方法對(duì)應(yīng)的參數(shù)。 /* Write data to MEMFS, need to use Uint8Array for binary data */ // 把文件存入內(nèi)存中 ffmpeg.FS('writeFile', 'video.avi', new Uint8Array(...)); /* Read data from MEMFS */ // 在內(nèi)存中讀取 ffmpeg.FS('readFile', 'video.mp4'); /* Delete file in MEMFS */ // 在內(nèi)存中刪除 ffmpeg.FS('unlink', 'video.mp4'); 復(fù)制代碼 ffmpeg.exitffmpeg.exit() 用來(lái)殺死程序的執(zhí)行,同時(shí)刪除 MEMFS 以釋放內(nèi)存。 ffmpeg.setLoggingffmpeg.setLogging(logging) 控制是否將日志信息輸出到控制臺(tái)。 參數(shù)如下 logging:? 在控制臺(tái)中打開/關(guān)閉日志消息。 ffmpeg.setLogging(true); 復(fù)制代碼 ffmpeg.setLoggerffmpeg.setLogger(logger) 設(shè)置和獲取 ffmpeg.wasm 的輸出消息。。 參數(shù)如下 logger:? 處理消息的函數(shù)。 ffmpeg.setLogger(({ type, message }) => { console.log(type, message); /* * type can be one of following: * * info: internal workflow debug messages * fferr: ffmpeg native stderr output * ffout: ffmpeg native stdout output */ }); 復(fù)制代碼 ffmpeg.setProgressffmpeg.setProgress(progress) 進(jìn)度處理程序,用于獲取 ffmpeg 命令的當(dāng)前進(jìn)度。 參數(shù)如下 progress:? 處理進(jìn)度信息的函數(shù)。 ffmpeg.setProgress(({ ratio }) => { console.log(ratio); /* * ratio is a float number between 0 to 1. 0 到 1之間的數(shù)字 */ }); 復(fù)制代碼 fetchFilefetchFile(media) 返回一個(gè) Promise, 用于從各種資源中獲取文件。要處理的視頻/音頻文件可能位于遠(yuǎn)程 URL 或本地文件系統(tǒng)中的某個(gè)位置。這個(gè)函數(shù)幫助你獲取文件并返回一個(gè) Uint8Array 變量供 ffmpeg.wasm 使用。 參數(shù)如下 media:?URL 字符串、base64 字符串或 File、Blob、Buffer 對(duì)象。 (async () => { const data = await fetchFile('https://github.com/ffmpegwasm/testdata/raw/master/video-3s.avi'); /* * data will be in Uint8Array format */ })(); 復(fù)制代碼 補(bǔ)充 設(shè)置 corePathcorePath 支持引入 cdn const ffmpeg = createFFmpeg({ corePath: 'https://unpkg.com/@ffmpeg/[email protected]/dist/ffmpeg-core.js', }) 復(fù)制代碼但是業(yè)務(wù)需要部署在內(nèi)網(wǎng),訪問不了 cdn,幸好 corePath 也支持加載本地文件。 這里應(yīng)該是支持絕對(duì)路徑,默認(rèn)會(huì)去訪問 public 下面的文件。 const ffmpeg = createFFmpeg({ corePath: 'ffmpeg-core.js', }) 復(fù)制代碼把 node_modules\@ffmpeg\core\dist 下面的三個(gè)文件拷貝到 public 中。 設(shè)置日志在創(chuàng)建實(shí)例的時(shí)候,通過傳入 log: true,開啟日志。 const ffmpeg = createFFmpeg({ log: true, }) 復(fù)制代碼也可以通過 ffmpeg.setLogger 自定義日志格式,比如 ffmpeg.setLogger(({ type, message }) => { console.log('???? ~ message', message); console.log('???? ~ type', type); }); 復(fù)制代碼還可以直接在創(chuàng)建實(shí)例的時(shí)候傳入 logger 屬性,效果是一樣的,建議把 log 屬性改為 false,不然日志會(huì)重復(fù)。 const ffmpeg = createFFmpeg({ corePath: 'ffmpeg-core.js', log: false, logger: ({ type, message }) => { console.log('???? ~ message', message); console.log('???? ~ type', type); } }) 復(fù)制代碼 獲取進(jìn)度如何獲取上傳文件的進(jìn)度呢,可以通過 ffmpeg.setProgress ffmpeg.setProgress(({ ratio }) => { console.log('???? ~ ratio', ratio); }); 復(fù)制代碼也可以直接在創(chuàng)建實(shí)例的時(shí)候傳入 progress 屬性,效果是一樣的。 const ffmpeg = createFFmpeg({ corePath: 'ffmpeg-core.js', log: false, progress: ({ ratio }) => { console.log('???? ~ ratio', ratio); } }) 復(fù)制代碼 解決錯(cuò)誤如果產(chǎn)生下面這個(gè)錯(cuò)誤 本地開發(fā)的時(shí)候需要在 vue.config.js 中添加 devServer: { headers: { "Cross-Origin-Opener-Policy": "same-origin", "Cross-Origin-Embedder-Policy": "require-corp", }, } 復(fù)制代碼如果是 vite 的項(xiàng)目這樣修改 vite.config.ts plugins: [ vue(), vueJsx(), { name: 'configure-response-headers', configureServer: server => { server.app.use('/node_modules/',(_req, res, next) => { res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp'); res.setHeader('Cross-Origin-Opener-Policy', 'same-origin'); next(); }); } } ], 復(fù)制代碼部署的時(shí)候需要配置 nginx 或者在后端配置。 add_header Cross-Origin-Opener-Policy same-origin; add_header Cross-Origin-Embedder-Policy require-corp; 復(fù)制代碼 本地上傳視頻上傳文件功能我們用 IView 組件庫(kù)的上傳組件,組件庫(kù)基本都有上傳組件,道理是一樣的。 頁(yè)面組件 main.vue import { createFFmpeg, fetchFile } from '@ffmpeg/ffmpeg' import videoRef from '../../components/video/Video.vue' import UploadBtn from '../../components/video/UploadBtn.vue' export default { data() { return { ffmpeg: {}, videoFileUrl: "", } }, components: { videoRef, UploadBtn }, computed: {}, methods: { // 頁(yè)面初始化邏輯 async init() { await this.initFfmpeg() }, // 初始化 ffmpeg 功能 async initFfmpeg() { this.ffmpeg = createFFmpeg({ corePath: 'ffmpeg-core.js', log: true, }) }, // 上傳文件前的鉤子 async beforeUpload(file) { console.log('???? beforeUpload ~ file', file); await this.ffmpeg.load(); await this.getVideoFileUrl(file) }, async getVideoFileUrl(file) { // 獲取資源文件 const result = await fetchFile(file) console.log('???? ~ result', result); // 對(duì)于 ffmpeg.wasm 的輸入/輸出文件,需要先將它們保存到 MEMFS 以便 ffmpeg.wasm 能夠使用它們 this.ffmpeg.FS('writeFile', `${file.name}`, result); await this.ffmpeg.run('-i', `${file.name}`, '-acodec', 'aac', '-vcodec', 'libx264', '-y', `${file.name.split('.')[0]}.mp4`); // 在內(nèi)存中讀取文件 const data = this.ffmpeg.FS('readFile', `${file.name.split('.')[0]}.mp4`); // 獲取內(nèi)存中的播放地址 this.videoFileUrl = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })) console.log('???? ~ this.videoFileUrl', this.videoFileUrl); } }, async mounted() { // 頁(yè)面初始化邏輯 await this.init() } } 復(fù)制代碼子組件上傳按鈕 UploadBtn.vue 點(diǎn)擊上傳視頻 export default { methods: { // 上傳文件前的鉤子 async beforeUpload(file) { this.$emit("before-upload", file) }, } } 復(fù)制代碼子組件視頻播放器 Video.vue // 播放視頻我們采用 `HTML5` 原生標(biāo)簽 `video`。 export default { props: { videoFileUrl: { default: () => (''), }, }, } 復(fù)制代碼 |
CopyRight 2018-2019 實(shí)驗(yàn)室設(shè)備網(wǎng) 版權(quán)所有 |