uniapp截取视频画面帧

前言

最近开发中遇到这么一个需求,上传视频文件的时候需要截取视频的一部分画面来供选择,作为视频的封面,截取画面可以使用canvas来实现,将视频画面画进canvas里,再通过canvas来生成文件对象和一个预览的url

逻辑层和视图层

想要将视频画进canvas里就需要操作dom,但是在uniapp中我们是无法操作dom的,微信uniapp的app端逻辑层和视图层是分离的,renderjs运行的层叫【视图层】,uniapp原生script叫【逻辑层】,会产生一个副作用就是是在造成了两层之间通信阻塞

所以uniapp推出了renderjs,renderjs是一个运行在视图层的js,可以让我们在视图层操作dom,但是不能直接调用,需要在dom元素中绑定某个值,当值发生改变就会触发视图层的事件

js 复制代码
// 视图层
// module为renderjs模块的名称,通过 模块名.事件 来调用事件
<script module="capture" lang="renderjs"></script>

// 逻辑层
// prop为绑定的值,名字可以随便起,但是要和change后面一致
// 当prop绑定的值发生改变就会触发capture模块的captures事件
<view style="display: none;" :prop="tempFilePath" :change:prop="capture.captures"></view>
js 复制代码
<template>
    <view class="container">
        <view style="display: none;" :prop="tempFilePath" :change:prop="capture.captures"></view>
    </view>
</template>

<script>
    export default {
        data() {
            return {
                tempFilePath: ''
            }
        },
        mounted() {
           this.tempFilePath = 'aaaaaaaaaaaaaaaa'
        }
    }
</script>

<script module="capture" lang="renderjs">
    export default {
        methods: {
            captures(tempFilePath) {
                console.log(tempFilePath);
            },
        }
    }
</script>

截取画面帧

我们先获取到视频的信息,通过uni.chooseVideo(OBJECT)这个api我们可以拿到视频的临时文件路径,然后再将临时路径交给renderjs去进行截取操作

定义一个captureFrame 方法,方法接收两个参数:视频的临时文件路径 和截取的时间 。先创建video元素,设置video元素的currentTime属性(视频播放的当前位置)的值为截取的时间,由于video元素没有加到dom上,video播放到currentTime结束

并设置video元素的autoplay 自动播放属性为true,但是由于浏览器的限制video无法自动播放,但是静音状态下可以自动播放,所以还要将video元素的muted 属性设为true,最后再将src属性设置为视频的临时文件路径,当video元素可以播放的时候就可以将video元素画进canvas里了

js 复制代码
captureFrame(vdoSrc, time = 0) {
    return new Promise((resolve) => {
        const vdo = document.createElement('video')
        // video元素没有加到dom上,video播放到currentTime(当前帧)结束
        // 定格时间,截取帧
        vdo.currentTime = time
        // 设置自动播放,不播放是黑屏状态,截取不到帧画面
        // 静音状态下允许自动播放
        vdo.muted = true
        vdo.autoplay = true
        vdo.src = vdoSrc
        vdo.oncanplay = async () => {
            const frame = await this.drawVideo(vdo)
            resolve(frame)
        }
    })
},

然后再定义一个drawVideo方法用于绘制视频,接收一个video元素参数,在方法中创建一个canvas元素,将canvas元素的宽高设置为video元素的宽高,通过drawImage 方法将视频画进canvas里,再通过toBlob 方法创建Blob对象,然后通过URL.createObjectURL() 创建一个指向blob对象的临时url,blob对象可以用来上传,url可以用来预览

js 复制代码
drawVideo(vdo) {
    return new Promise((resolve) => {
        const cvs = document.createElement('canvas')
        const ctx = cvs.getContext('2d')
        cvs.width = vdo.videoWidth
        cvs.height = vdo.videoHeight
        ctx.drawImage(vdo, 0, 0, cvs.width, cvs.height)

        // 创建blob对象
        cvs.toBlob((blob) => {
            resolve({
                blob,
                url: URL.createObjectURL(blob),
            })
        })
    })
}

最后我们就可以在触发视图层的事件里去使用这两个方法来截取视频画面帧了,最后将数据传递返回给逻辑层,通过this.$ownerInstance.callMethod() 向逻辑层发送消息并将数据传递过去

js 复制代码
// 视图层
async captures(tempFilePath) {
    let duration = await this.getDuration(tempFilePath)
    let list = []
    for (let i = 0; i < duration; i++) {
        const frame = await this.captureFrame(tempFilePath, duration / 10 * i)
        list.push(frame)
    }
    this.$ownerInstance.callMethod('captureList', {
        list,
        duration
    })
},
getDuration(tempFilePath) {
    return new Promise(resolve => {
        const vdo = document.createElement('video')
        vdo.src = tempFilePath
        vdo.addEventListener('loadedmetadata', () => {
            const duration = Math.floor(vdo.duration);
            vdo.remove();
            resolve(duration)
        });
    })
},

// 逻辑层
captureList({
    list,
    duration
}) {
    // 操作......
}

运行

最后运行起来,发现报了一个错误:Failed to execute 'toBlob' on 'HTMLCanvasElement': Tainted canvases may not be exported ,这是因为由于浏览器的安全考虑,如果在使用canvas绘图的过程中,使用到了外域的资源,那么在toBlob()时会抛出异常,设置video元素的crossOrigin属性值为anonymous就行了

app端 h5
相关推荐
卸任7 小时前
Vibe coding工具使用心得
前端·ai编程
wuxianda10307 小时前
2026 苹果审核 4.3 (a) 被拒全解析|Guideline 4.3a spam 马甲包拒审原因 + 避坑方案
flutter·uni-app·苹果上架
brien97 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance8 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M8 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写8 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李8 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易8 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马8 小时前
前沿视界
前端
kyriewen8 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员