H5 video標籤列表渲染用canvas擷取視訊畫面做封面

single15發表於2018-05-25

這是一個關於h5的video視訊播放標籤來做視訊播放擷取視訊畫面的問題。

需求是這樣的:要渲染一個視訊資源列表,在列表中獲取視訊的畫面來做列表的封面。看到這個需求就想,為什麼要在列表裡擷取視訊畫面做封面,為什麼不是後端返回圖片url呢?沒有那麼多為什麼,做出來就是了。

首先是找百度,怎麼擷取h5視訊的畫面,搜了一遍,大都是通過canvas來畫出來的。基本實現過程是:通過video標籤把視訊載入進來,設定display:none; 把video標籤隱藏起來,然後通過canvas的drawImage方法把視訊畫面畫出來,得到一個base6位的圖片編碼,然後將這個base64位的編碼賦給img標籤的src即可。

先來一串程式碼:

html:
<video id="video" :src="videoSrc" x-webkit-airplay="allow" preload="auto" style="display: none"></video>
<div id="output"></div>
js:
captureImage() {
    const output = document.getElementById('output')
    const video = document.getElementById('video')
    const canvas = document.createElement('canvas')
    canvas.width = video.videoWidth * 0.3
    canvas.height = video.videoHeight * 0.3
    const img = new Image()
    canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height)
    const dataUrl = canvas.toDataURL('image/png')
    img.src = dataUrl
    output.appendChild(img)
},

這是擷取一個視訊畫面的實現程式碼,可以監聽視訊載入好了之後執行captureImage() 方法(我這裡是基於vue的寫法,應該比較好理解)。那接下來進入主題,渲染視訊列表,如何去畫每個視訊的畫面呢?這裡我寫了個公共的方法:

html:
<li v-for="(item, index) in items" :key="index">
    <div class="video-cover">
        <video :id="item.id" :src="item.src" x-webkit-airplay="allow" preload="auto" style="display: none"></video>
        {{captureImage(item.id, index, 'items')}}
        <img :src="item.cover">
    </div>
</li>
js:
// videoId: 視訊標籤的id; index: 列表資料的索引;key: this.$data讀取列表資料的key
captureImage(videoId, index, key) {
    const self = this
    setTimeout(function () {
        const videoEle = document.getElementById(videoId)
        const canvas = document.createElement('canvas')
        canvas.width = 265
        canvas.height = 180
        canvas.getContext('2d').drawImage(videoEle, 0, 0, canvas.width, canvas.height)
        const dataUrl = canvas.toDataURL('image/png')
        self.$set(self.$data[key][index], 'cover', dataUrl)
    }, 100)
}

這是基於vue的寫法,首先寫一個擷取視訊畫面的公共方法,在進行列表渲染時,每渲染一個item,呼叫一次該方法(也可以在updated鉤子裡迴圈呼叫),傳入對應的視訊標籤的id以便獲取dom節點。這裡加了延時器,一開始沒加的時候,發現獲取到的videoEle是null,可能是該方法執行的時候,video標籤還沒渲染好,給個小小的延時就可以了(不知有沒有更好的解決方法,就先這樣做著了,如果路過的大神有更好的解決方案,還望賜教)。

以上的做法可以在列表裡渲染出視訊畫面做封面了,高興了一小會。後來發現,有bug!!!由於瀏覽器載入視訊速度的問題,導致列表中有些封面畫出來是一張透明的圖片,甚至有時候全部都是透明的圖片,可能是畫的時候,視訊畫面還沒載入。後來試了好多方法,最終做了小小的優化:

html:
<li v-for="(item, index) in items" :key="index">
    <div class="video-cover">
        <video :id="item.id" :src="item.src" x-webkit-airplay="allow" autoplay preload="auto" style="display: none"></video>
        {{captureImage(item.id, index, 'items')}}
        <img :src="item.cover">
    </div>
</li>
js:
// videoId: 視訊標籤的id; index: 列表資料的索引;key: this.$data讀取列表資料的key
captureImage(videoId, index, key) {
    const self = this
    setTimeout(function () {
        const videoEle = document.getElementById(videoId)
        const canvas = document.createElement('canvas')
        canvas.width = 265
        canvas.height = 180
        videoEle.addEventListener('timeupdate', function () {
            canvas.getContext('2d').drawImage(videoEle, 0, 0, canvas.width, canvas.height)
            const dataUrl = canvas.toDataURL('image/png')
            self.$set(self.$data[key][index], 'cover', dataUrl)
            videoEle.pause()
        }, false)
    }, 100)
}

這裡做了小小的改變,就是在渲染視訊列表是讓ta自動播放(autoplay),然後在畫封面的方法裡通過video的timeupdate事件來監聽視訊播放位置的改變,然後在進行canvas的繪製,繪製完呼叫video的pause()方法,暫停播放,這樣就能保障每個item都能畫出不是透明的圖片啦。

The end:可算是把視訊列表的封面都畫出來了,美中不足的是,視訊載入的速度還是沒法控制,列表中有些視訊載入的慢的,會延時好幾秒才畫出來。
以上就是H5 video標籤列表渲染用canvas擷取視訊畫面做封面的一個不完美的方法,如果路過的親有更好的解決方法,望分享,麼麼噠。

相關文章