本文重点不是讲述富文本,所以富文本内容带过一下,为什么要获取首帧?只要是微信环境下不能自动获取视频首帧,所以在微信环境下视频就很丑
主要实现的功能: 1、对接云点播 2、获取视频首帧(云点播有首帧获取功能,我们在调试过程发现有点慢,所以改用前端canvas获取首帧)
对接流程: 1、调用后端接口获取密钥 2、使用web端sdk进行上传拿到url
大致思路: 上传视频后拿到视频url,通过canvas再获取到视频首帧(base64格式),然后将这个base64转为File对象,再调用我们的图片上传接口拿到图片再作为视频的封面插入到富文本中。
前端代码
import { Message, Loading } from 'element-ui'
import TcVod from 'vod-js-sdk-v6'
import { ref } from 'vue'
import API from '@/fetch/api.js'
import { base64toFile, getVideoConverUrl } from '@/util/tool'
const loadingOpts = {
spinner: 'el-icon-loading',
background: '#22222280',
lock: true,
}
// 获取上传密钥签名
async function getUploadSignature() {
return API.getUploadSignature({}, {noLoading: true})
}
// 得到视频的首帧作为封面图
function getVideoCoverImage(url) {
return new Promise(async(resolve, reject) => {
const base64 = await getVideoConverUrl(url)
const file = base64toFile(base64)
var formData = new FormData()
formData.append('file', file, 'image.png')
API.upload(formData).then((res) => {
if (res.code == '000000') {
const yunUrl = res.data.yunUrl
resolve(yunUrl)
} else {
Message.error(res.mesg)
}
})
.catch(err => {
console.log(err)
reject(err)
})
})
}
// 封装一个hook
export function useTcVod(onSuccess, onError) {
const loadingInstance = ref()
const percent = ref(0)
async function startUploadVideo(mediaFile, needCoverUrl = true) {
const fileName = mediaFile.name
try {
loadingInstance.value = Loading.service(loadingOpts)
const signatureInfo = await getUploadSignature()
const signatureData = signatureInfo.data
if (signatureInfo.code === '000000') {
const tcVod = new TcVod({
getSignature: async() => signatureData.signature,
appId: signatureData.appId,
})
const uploader = tcVod.upload({
mediaFile,
mediaName: signatureData.comNo + fileName,
dynamicAccelerate: true,
})
uploader.on('media_progress', function(info) {
percent.value = info.percent
})
uploader?.done?.()?.then(async function(doneResult) {
if (!needCoverUrl) {
onSuccess(doneResult)
return
}
const starTime = new Date().getTime()
const videoCoverResult = await getVideoCoverImage(doneResult.video?.url)
console.log('获取首帧时间:' + (new Date().getTime() - starTime))
onSuccess({
...doneResult,
coverUrl: videoCoverResult,
})
}).catch(function(err) {
onError(err)
}).finally(() => loadingInstance.value?.close())
} else {
Message.error(signatureInfo.mesg)
loadingInstance.value?.close()
}
} catch (err) {
console.log(err)
}
}
return {
startUploadVideo,
percent,
}
}
获取视频首帧方法,base64转为File方法
/**
* @param {string} base base64地址
* @param {string} filename 名称
* @returns
*/
export const base64toFile = (base, filename = 'image.png') => {
const arr = base.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
// 转换成file对象
return new File([u8arr], filename, { type: mime })
}
/**
* @param {string} url 视频链接
* @description 根据视频链接获取首帧base64
*/
export const getVideoConverUrl = (url) => {
return new Promise((resolve) => {
const video = document.createElement('video')
video.style.display = 'none'
video.src = url
video.autoplay = true
video.controls = true
video.muted = true
video.crossOrigin = 'Anonymous'
document.body.appendChild(video)
video.addEventListener('canplaythrough', () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = video.videoWidth
canvas.height = video.videoHeight
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
const firstFrameDataURL = canvas.toDataURL('image/jpeg')
video.remove()
resolve(firstFrameDataURL)
})
})
}
froala富文本中使用具体情况结合自身情况
/**
* @param {*} e
* @description 上传视频
*/
const handleUploadVideo = async(e, ctx) => {
const mediaFile = e[0]
const fileSize = mediaFile.size
if (fileSize > 500 * 1024 * 1024) {
app.$message({
message: '只能上传500M以内的视频',
type: 'error',
center: true,
showClose: true,
})
return false
}
try {
const {startUploadVideo} = useTcVod(async(doneResult) => {
console.log(doneResult)
const url = doneResult.video?.url
const coverUrl = doneResult.coverUrl
const videoEl = document.createElement('video')
videoEl.src = url
videoEl.setAttribute('poster', coverUrl)
videoEl.setAttribute('controls', '')
videoEl.setAttribute('class', 'fr-draggable')
videoEl.innerText = '您的浏览器不支持HTML5视频'
ctx.video.insert(videoEl.outerHTML)
ctx.video.remove()
ctx.events.focus()
}, (err) => {
app.$message({
type: 'error',
message: '上传失败,请检查参数',
})
})
startUploadVideo(mediaFile)
} catch (err) {
console.log(err)
}
}