uni-app利用renderjs实现截取视频第一帧画面作为封面图

前置知识

利用renderjs在app端加载for web库 - 掘金 (juejin.cn)

需求背景

如下图,使用 uni-app 做 app 时,要上传图片和视频,这里选择图片和视频分别使用的 uni.chooseImageuni.chooseVideo,上传使用的 uni.uploadFile,问题就是这些 API 还有上传成功后服务器返回内容中都没有提供视频封面图,于是只能使用一个固定的图片来充当视频封面,但是这样用户体验很不好

解决思路

在获取到视频链接后,如果我们可以让视频在后台自动播放,出现第一帧画面后再将它给停掉,在这个过程中利用 canvas 截取到视频播放的第一帧画面保存起来,那不就可以作为视频封面了吗?没那么容易,平时在 H5 环境中,到目前为止就行了,但问题是,现在我这里是 App,然后 uni-app 自带的 video 组件没法截取画面,而 App 环境又没法用 H5 环境的 video 标签,它甚至没有 document 对象, 技术框架上不兼容, 那怎么办?

这时候就需要用到 renderjs 了,毕竟它的核心作用之一就是 "在视图层操作dom,运行 for webjs库"。

那思路就有了,在 renderjs 模块中监听原始模块中的文件列表,当更改时(新增、删除),在 renderjs 中动态创建 video 元素,让它自动静音播放,使用 canvas 截取第一帧画面后销毁 video 元素并将图片传递给原始模块,原始模块将其设置为对应视频的封面

代码逻辑

html 复制代码
<template>
  <view :prop="canvasList" :change:prop="canvas.getVideoCanvas">
    <view v-for="(item,index) in fileList" :key="index">
      <image v-if="item.type===0" :src="item.url" @click="previewImage(item.url)"></image>
      <view v-else @click="previewVideoSrc = item.url">
        <!-- 封面图 -->
        <image mode="widthFix" :src="item.cover"></image>
        <!-- 播放图标 -->
        <u-icon class="play-icon" name="play-right-fill" size="30" color="#fff"></u-icon>
      </view>
    </view>
    <view class="preview-full" v-if="previewVideoSrc!=''">
      <video :autoplay="true" :src="previewVideoSrc" :show-fullscreen-btn="false">
        <cover-view class="preview-full-close" @click="previewVideoSrc=''"> ×
        </cover-view>
      </video>
    </view>
  </view>
</template>

<script>
import { deepClone } from '@/utils'
 // 原始模块
export default {
  data() {
    return {
      previewVideoSrc: '', // 预览视频url
      fileList: [
        { url: '', type: 0 },
        { url: '', type: 1 },
        { url: '', type: 1 },
      ] // 真正用来展示和传递的文件列表,type: 0代表图片,1代表视频
    }
  },
  computed: {
    // 用于 renderjs 模块监听,不用 fileList 是因为后续还有更改它(为其内部元素添加 cover )属性
    // 监听 fileList 然后又更改它会导致循环递归,这里使用 deepClone 也是为了让 canvasList 不与
    // fileList 产生关联
    canvasList() {
      return deepClone(this.fileList)
    }
  },
  methods: {
    // 预览图片
    previewImage(url) {
      uni.previewImage({
        urls: [url]
      });
    },
    // 生成视频封面
    getVideoPoster({ index, cover }) {
        this.$set(this.fileList[index], 'cover', cover)
    },
  }
}
</script>
<script module="canvas" lang="renderjs">
 // renderjs 模块
export default {
  methods: {
    getVideoCanvas(nV, oV, ownerInstance) {
      if(oV !== undefined && Array.isArray(nV) && nV.length > 0) {
        nV.forEach((item, index) => {
          // 如果是视频
          if(item.type == 1) {
            // 防止一次性执行过多逻辑导致卡顿
            setTimeout(() => {
              // 创建video标签
              let video = document.createElement("video")
              // 设置为自动播放和静音
              video.setAttribute('autoplay', 'autoplay')	
              video.setAttribute('muted', 'muted')
              // 设置播放源
              video.innerHTML = '<source src=' + item.url + ' type="audio/mp4">'
              // 创建 canvas 元素和 2d 画布
              let canvas = document.createElement('canvas')
              let ctx = canvas.getContext('2d')
              // 监听 video 的 canplay 事件
              video.addEventListener('canplay', function () {
                  // 设置宽高
                  let anw = document.createAttribute("width");
                  anw.nodeValue = 80;
                  let anh = document.createAttribute("height");
                  anh.nodeValue = 80;
                  canvas.setAttributeNode(anw);
                  canvas.setAttributeNode(anh);
                  // 画布渲染
                  ctx.drawImage(video, 0, 0, 80, 80);
                  // 生成 base64 图片
                  let base64 = canvas.toDataURL('image/png')
                  // 暂停并销毁 video 元素
                  video.pause()
                  video.remove();
                  // 传递数据给逻辑层
                  ownerInstance.callMethod('getVideoPoster', {
                      index,
                      cover: base64
                  })
              }, false)
            }, index * 120)
          }
        })
      }
    }
  }
}
</script>

成果展示

还有另一个地方,之前就是这样的,都是用的默认图片当作封面:

经过处理后就是这样啦:

相关推荐
Json_1817901448014 分钟前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
风尚云网37 分钟前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网
木子020439 分钟前
前端VUE项目启动方式
前端·javascript·vue.js
GISer_Jing41 分钟前
React核心功能详解(一)
前端·react.js·前端框架
捂月44 分钟前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
深度混淆1 小时前
实用功能,觊觎(Edge)浏览器的内置截(长)图功能
前端·edge
Smartdaili China1 小时前
如何在 Microsoft Edge 中设置代理: 快速而简单的方法
前端·爬虫·安全·microsoft·edge·社交·动态住宅代理
秦老师Q1 小时前
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
前端·chrome·edge
滴水可藏海1 小时前
Chrome离线安装包下载
前端·chrome
endingCode1 小时前
45.坑王驾到第九期:Mac安装typescript后tsc命令无效的问题
javascript·macos·typescript