1、canvas动画说明
10种动画每种都可以拆分为前后两部分; 前部分为第一张做动作(缩放、左右上下移动), 后一部分为第二张图片进场,进场方式多种根据特效而定;
各动画的具体实现参考各动画的代码
用到的主要api : 切片 drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
img | 规定要使用的图像、画布或视频。 |
---|---|
sx | 可选。开始剪切的 x 坐标位置。 |
sy | 可选。开始剪切的 y 坐标位置。 |
swidth | 可选。被剪切图像的宽度。 |
sheight | 可选。被剪切图像的高度。 |
x | 在画布上放置图像的 x 坐标位置。 |
y | 在画布上放置图像的 y 坐标位置。 |
width | 可选。要使用的图像的宽度。(伸展或缩小图像) |
height | 可选。要使用的图像的高度。(伸展或缩小图像) |
主要关键点: 动画暂停、图片比例兼容和3:4制作兼容、时长控制
动画暂停只在用户操作页面上需要该功能;
实现思路:首先弄一个变量来判断是暂停还是播放状态,动画实现是通过定时任务一直重复调动画方法,直到达到某条件才结束;
因此实现暂停是在每个动画方法开头进行暂停判断,如果是暂停状态就 将当前动画的一些参数存入全局变量 然后return动画方法; 继续的话同样调动画方法,只不过需要将动画的特有参数从全局变量中取出来,从而实现动画的继续渲染;
图片比例兼容和3:4兼容,图片不能变形,确定好怎么截取图片,
设计要点:动画画布必须被铺满、图片只能等比缩放(不能变形)、如果图片取不完,则取图片中心区域(高度多了就取中间部分,宽度多了同理)
时长控制: 目前所有动画四张图片时长误差在100毫秒之内,即9秒视频的误差在100毫秒之内;
定时任务最终采用的setTimeout;用过requestAnimationFrame测试,达不到定时效果,它跑完之后直接继续跑,如果要改成这个 需要去微调动画 “递归” 退出条件的增长量或减少量;
2、动画转视频 视频转码 合成音频 以及上传
动画转视频
参考api : MediaStream = canvas.captureStream(frameRate);
能将canvas画布上的动画转为视频流,问题是需要动画播放完(即动画多长转多长),考虑到用户不会等或进行其他操作,所以单独在其他项目渲染动画再转为视频;
视频转码
前端转码参考 FFmpeg
他的api run方法里面能直接使用 ffmpeg 的 转码命令(语法稍微不一样,空格变成逗号)
代码 (template-renderer项目)
//转MP4、合成音频
async getMp4(blob) {
let startTime = new Date()
let file = new window.File(blob, 'webmVideo', {
type: 'video/webm'
})
const { createFFmpeg, fetchFile } = FFmpeg
const ffmpeg = createFFmpeg({
log: true,
// eslint-disable-next-line no-unused-vars
progress: ({ ratio }) => {
// console.log(`Complete: ${(ratio * 100.0).toFixed(2)}%`)
}
})
const { name } = file
await ffmpeg.load()
ffmpeg.FS('writeFile', name, await fetchFile(file))
let musicBlob = await this.getMusic()
//转码 webm => mp4
await ffmpeg.run('-i', name, 'outputMp4Video.mp4')
console.log('BBT 转码完成')
//构建音频
// let musicBlob = await this.getMusic()
let fileMusic = new window.File(musicBlob, 'mp3Music', {
type: 'audio/mpeg'
})
const nameMusic = 'mp3Music'
ffmpeg.FS('writeFile', nameMusic, await fetchFile(fileMusic))
console.log('BBT 加载音乐完毕')
//剪切音乐
let musicTime = Math.ceil(this.images.length * 2.25)
musicTime = musicTime < 10 ? `0${musicTime}` : musicTime
// # ffmpeg -i aa.mp3 -ss 00:01:12 -t 00:01:42 -acodec copy bb.mp3
await ffmpeg.run(
'-i',
nameMusic,
'-ss',
'00:00:00',
'-t',
`00:00:${musicTime}`,
'-acodec',
'copy',
'mp3MusicNew.mp3'
)
//加背景音乐
// ffmpeg -i 1.mp3 -i 1.mp4 outputname.mp4
await ffmpeg.run(
'-i',
'mp3MusicNew.mp3',
'-i',
'outputMp4Video.mp4',
'endMusicMp4Video.mp4'
)
console.log('BBT 剪切背景音乐 合成音乐 结束')
//最终数据
const data = ffmpeg.FS('readFile', 'endMusicMp4Video.mp4')
console.log('BBT 加载最终视频文件')
// let byte = new Blob([data.buffer], {
// type: 'video/mp4'
// })
let aTemp = document.createElement('a')
aTemp.href = URL.createObjectURL(
new Blob([data.buffer], {
type: 'video/mp4'
})
)
aTemp.download = '40Test.mp4'
aTemp.click()
const video = document.getElementById('video-mp4')
video.src = URL.createObjectURL(
new Blob([data.buffer], {
type: 'video/mp4'
})
)
let endTime = new Date()
let mm = endTime.getTime() - startTime.getTime()
console.log('视频时长:', this.videoDuration, ';转换时间:', mm / 1000)
},
//加载音乐资源
getMusic() {
let that = this
return new Promise(function(res, rej) {
let musicBlob = []
let xhr = new XMLHttpRequest()
xhr.open('get', that.musicUrl, true)
xhr.responseType = 'blob'
xhr.onload = function() {
if (xhr.status == 200) {
musicBlob.push(xhr.response)
console.log(musicBlob)
res(musicBlob)
} else {
rej()
}
}
xhr.send()
})
},
后端转码 使用的 javacv
上传
后台将最终视频文件上传到oss
3、项目功能简述
涉及项目:用户操作页面、后台、node服务(模拟浏览器项目)、renderer页面(渲染项目)
用户操作页面:让用户直观看到“视频”效果、实现暂停继续播放、音频同步播放暂停、视频制作请求后台
后台:接收请求、存/更新记录到数据库、转调服务(调node服务)、视频处理(视频转码音频合并)、上传视频到oss
node服务:模拟浏览器行为,通过url打开页面并调用其方法(打开renderer渲染页面,让其开始渲染动画)
renderer页面:渲染动画、将动画转为视频流然后发送给后台处理