當視頻能夠預覽并上傳后,非要來一張視頻第一幀的截圖貼上,第一幀是黑的怎么辦,下一幀。
一、文件上傳
使用<input type="file">上傳,change事件作為預覽video的src的觸發條件 新鮮源碼:
<video controls width="700" height="300" src="" id="video"></video> <input type="file" id="input" hidden /> <button id="fileBtn">點擊上傳視頻</button>
二、canvas截取圖片
關于截取或者處理圖片/視頻/富文本編輯器,canvas是一個非常nice的選擇。
- 創建畫布canvas或在html中直接寫入。
var canvas = document.createElement('canvas');
- 創建基于canvas的繪圖環境
var ctx = canvas.getContext('2d');
附 Q&A:
- 什么是繪圖環境?
網絡上的常規理解是“在準備畫布后,需要一些‘染料、畫筆、繪圖工具’的準備工作。” 比較官方的說話是返回canvas的上下文環境, 說人話是'你能夠更好的操作你的canvas'。
- 關于getContext('2d')的參數
方法中的2d參數目前可以理解為是固定參數,表示想要一個二維繪制環境。雖然大家都認為有2d自然應該有3d,然而實際上本身設計時也是這么考慮的,不過大家有點等不起了,所以都去選擇webGL了。 webGL是啥?瀏覽器端借助系統顯卡進行 3D 繪圖。這是另一個故事了(IE別想了)。
- 關于canvas.getContext('2d')的返回值
返回一個CanvasRenderingContext2D對象,也就是上文所說的能夠支持絕大多數對畫布的操作。
- 在canvas上繪制圖片
// ctx.drawImage(file,sx,sy,swidth,sheight,x,y,width,height); ctx.drawImage(this, 0, 0, swidth, sheight);
在不需要剪裁的情況下,使用上述參數即截取操作file的全部,繪制到canvas上
關于參數(w3school) |參數 | 描述 | | :-------: |:-------------:| |file|規定要使用的圖像、畫布或視頻。 |sx|可選。開始剪切的 x 坐標位置。 |sy|可選。開始剪切的 y 坐標位置。 |swidth|可選。被剪切圖像的寬度。 |sheight|可選。被剪切圖像的高度。 |x|在畫布上放置圖像的 x 坐標位置。 |y|在畫布上放置圖像的 y 坐標位置。 |width|可選。要使用的圖像的寬度。(伸展或縮小圖像) |height|可選。要使用的圖像的高度。(伸展或縮小圖像)
- 將canvas導出成圖片放入src
var src = canvas.toDataURL('image/jpeg');
關于toDataURL()方法。將canvas的內容導出
canvas.toDataURL(type, encoderOptions);
type: 圖片格式,默認image/jpeg, encoderOptions:圖片質量,取值范圍為0到1,默認0.92。 返回值:包含 data URI 的DOMString,也就是base64格式。
三、截取視頻第一幀
上傳文件OK,用canvas截取OK,怎么找第一幀呢?(啥時候開始截取呢?)
當然是多媒體的事件來觸發。 關于video的事件非常多(全部事件),這里只討論能夠影響到截取到第一幀的各個事件。
video.addEventListener('loadeddata', consoleString.bind(video, 'loadeddata')) // 當前幀加載完畢 video.addEventListener('loadedmetadata', consoleString.bind(video, 'loadedmetadata')) // 視頻元數據加載完畢 video.addEventListener('canplay', consoleString.bind(video, 'canplay')) // 視頻緩沖能夠開始播放 video.addEventListener('timeupdate', consoleString.bind(video, 'timeupdate')) // 播放位置發生改變時 video.addEventListener('play', consoleString.bind(video, 'play')) // 開始播放時 video.addEventListener('waiting', consoleString.bind(video, 'waiting')) // 要播放下一幀而需要緩沖時 function consoleString(string) { console.log(string) } // 執行結果 // timeupdate // loadedmetadata // loadeddata // canplay // play(開始播放) // 沒有waiting, 因為視頻較小不需要緩沖
- 根據順序,第一個被觸發的竟然是timeupdate事件,按設想來說,最先執行的應該是loadedmetadata,元數據加載完畢。 關于這一點,在MDN上沒有明確的說明,但是可以推理一下:
當currentTime更新時會觸發timeupdate事件
來源:MDN
loadedmetadata的元數據恰好是指時長、尺寸(僅視頻)以及文本軌道,也就是說在video未定義的時候currentTime是NaN或NULL,當元數據中時長加載完畢后,currentTime更新至0,因此觸發。
結論:雖然最先觸發,但是此時視頻文件尚未加載,截取的是canvas的無內容本身。 注:timeupdate事件根據使用的系統不同,每秒觸發4-66次,且由于觸發頻率高,單位過小(毫秒級別),事件響應需要延遲等原因,無法完全精準的控制。
- loadedmetadata 上文提到,元數據加載完畢之后即觸發,但數據中并不包括視頻文件本身。 結論:如果視頻文件較大,加載時間較長,仍然無法截取到已加載的第一幀。 補充:通過URL.createObjectURL()方法能夠基本做到無察覺,但并不保險。
- loadeddata 當前幀數(第一幀)加載完畢觸發,沒毛病。 結論:可用。 補充:萬一第一幀是黑屏想用下一幀怎么辦,對不起,余下幀數加沒加載完不在它的考慮范圍之類,這個事件不管。
- canplay 視頻能夠開始播放時觸發,也就是根據上傳的視頻幀數決定加載多少幀(24/25/30/60等等)后滿足播放畫面后觸發。 總結:因為加載相對于loadeddata的事件來說更多(多一丟丟),總體可行。 補充:通過控制currentTime可以滿足(但不可能是第二幀那么準確),可以看做“當前播放幀”。
- play 開始播放時才會觸發,和上傳快速截取的需求不是很符合。
- waiting 已播放但下一畫面沒緩沖好時觸發,適合插播小廣告。
文件、方法、事件都OK了。截就完事兒了。
video.addEventListener('loadeddata', function (e) { canvas.width = this.videoWidth canvas.height = this.videoHeight width = this.videoWidth height = this.videoHeight ctx.drawImage(this, 0, 0, width, height); var src = canvas.toDataURL('image/jpeg'); img.src = src; // var currentTime = this.currentTime // duration = this.duration // var fps = duration / 30 })