H5 video标签列表渲染用canvas截取视频画面做封面

这是一个关于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截取视频画面做封面的一个不完美的方法,如果路过的亲有更好的解决方法,望分享,么么哒。

猜你喜欢

转载自blog.csdn.net/single15/article/details/80453279
今日推荐