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






