需求简介
需求:上传一个视频文件,截取视频的第一帧作为封面图 使用 vue+vant
文件上传
[注意项] 1、在safari中由于无点击事件无法触发video的loadeddata事件,所以需要上传视频文件后手动点击生成poster
<van-uploader
v-if="!form.videoUrl"
accept="video/*"
v-model="videoList"
max-count="1"
:preview-image="false"
:after-read="onVideoUpload"
/>
监听after-read钩子获取到文件实例
async onVideoUpload(file) {
try {
this.videoUploading = true;
const blob = new Blob([file.file]);
const localUrl = URL.createObjectURL(blob);
// 校验video合法并且返回video 此处会监听 loadeddata 事件在ios无效
const video = await validateVideo(localUrl);
if (!video) {
// safari下无法播放自动生成首图 切无法获取视频时长
// 这种情况下让用户手动触发获取视频播放状态
this.videoLoadError = true;
} else {
// 把video转化为file
const res = await videoCapture(video);
const picFile = await base64toFile(res);
const picUrl = await uploadFile(picFile);
this.form.videoCoverImg = picUrl;
}
const resultUrl = await uploadFile(file.file);
file.url = resultUrl;
this.form.videoUrl = resultUrl;
this.videoUploading = false;
} catch (err) {
this.videoUploading = false;
this.videoLoadError = false;
this.videoList = [];
this.$toast({
position: 'bottom',
message: err.message
});
}
},
校验视频loadeddata事件,并返回video本身
注意
1、如果视频需要跨域处理,即外部链接,则video标签需要添加crossOrigin="anonymous"属性
export const validateVideo = videourl => {
const video = document.createElement('video');
video.src = videourl;
video.currentTime = 0.1;
video.load();
return new Promise((resolve, reject) => {
video.addEventListener('loadeddata', function() {
if (video.duration > 16) {
reject({ message: '上传视频不符合规范' });
} else {
resolve(video);
}
});
// 超过三秒钟则直接退出
setTimeout(() => {
resolve(false);
}, 3000);
});
};
使用canvas画出第一帧并保存为base64格式
export default video => {
return new Promise((res, rej) => {
try {
const vw = video.videoWidth;
const vh = video.videoHeight;
const scale = 0.25;
const canvas = document.createElement('canvas');
canvas.width = vw * scale;
canvas.height = vh * scale;
const fill = canvas.getContext('2d');
fill.drawImage(video, 0, 0, canvas.width, canvas.height);
res(canvas.toDataURL('image/png'));
} catch (err) {
rej(err);
}
});
};
把base64图片转为file类型并上传给服务器
1、这里有个坑是如果正则表达式在safari浏览器上由于解码原因会报错
[Works in Chrome, but breaks in Safari: Invalid regular expression: invalid group specifier name /(?<=/)([^#]+)(?=#*)/ [duplicate]](stackoverflow.com/questions/5…)
export const base64toFile = base64 => {
return new Promise((resolve, reject) => {
try {
var arr = base64.split(',');
var mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]);
var n = bstr.length;
var u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
resolve(new File([u8arr], 'poster.png', { type: mime }));
// resolve(new Blob([u8arr], { type: mime }));
} catch (err) {
reject({ message: '转码失败' });
}
});
};
超过3秒未响应loadeddata事件,则使用点击事件触发生成预览图
<video
ref="video"
crossOrigin="anonymous"
:src="form.videoUrl"
:poster="form.videoCoverImg"
></video>
<van-button
v-if="videoError"
class="preview-butn"
native-type="button"
@click="genereteVideoPoster"
>
生成预览图
</van-button>
async genereteVideoPoster() {
try {
const video = this.$refs.video;
const videoDom = await videoLoadeddata(video);
const res = await videoCapture(videoDom);
const picFile = await base64toFile(res);
const picUrl = await uploadFile(picFile);
this.form.videoCoverImg = picUrl;
this.videoPaused = false;
} catch (err) {
this.$toast({
position: 'bottom',
message: err.message
});
}
}
结语
1、文章随手粘贴的代码,本人水平有限
- 在safari中由于无点击事件无法触发video的loadeddata事件,所以需要上传视频文件后手动点击生成poster
- 如果视频需要跨域处理,即外部链接,则
video标签需要添加crossOrigin="anonymous"属性 - 如果正则表达式在safari浏览器上由于解码原因会报错
Invalid regular expression: invalid group specifier name
2、关于参考的链接