改了需求,视频由原来上传到服务器直接上传到阿里云的视频点播,使用官方推荐的获取地址和视频凭证的方式。结合框架使用的elementUI 库,记录一下。
上传到视频点播大致流程如下:使用elementUI 的上传组件添加视频,通过自定义验证后,调用后端的接口,返回videoI等必要参数,然后调用视频点播的方法。刷新视频凭证也是如此,调用后端接口,返回必要数据。
功能主要有,获取视频时长、获取视频的本地url地址、截取视频帧,上传视频、获取上传进度
因为代码过多不好维护,这部分的代码封装了一个组件,然后引入到表单页面中(父组件)
AddVideo.vue 是子组件
releasevideo.vue 是父组件
AddVideo.vue的源码
<template>
<el-upload class="videoboxUpload" :action="uploadApi" :show-file-list="false" :auto-upload="false" accept=".mp4,.flv,.mov"
:on-change="videoChange">
<div class="upload_div">
<div style="float: left;">
<video v-if="videoDataUrl !='' && videoFlag == true" :src="videoDataUrl" class="avatar" controls="controls">您的浏览器不支持视频播放</video>
<i v-else-if="videoDataUrl =='' && videoFlag == false" class="el-icon-plus avatar-uploader-icon"></i>
<el-progress v-else-if="videoDataUrl != '' && videoFlag == false" type="circle" :width="110" :percentage="authProgress"></el-progress>
</div>
<div class="upload_txt">
<el-button type="primary" size="mini" class="el-icon-upload el-icon--right">
<span v-if="videoFlag == true"> 重新上传</span>
<span v-else-if="videoFlag == false"> 上传视频</span>
</el-button>
<br>
<span>支持视频的类型 mp4</span>
<br>
<span v-if="videoFlag == true"> <i class="el-icon-success" style="color:#52C41A;margin-right:10px;">上传成功</i> </span>
<span v-if="videoFlag == true">时长:{{ totalTime }}</span>
</div>
<video style="display: none;" :src="videoDataUrl" class="avatar" controls="controls" autoplay name="media" ref="divVideo"
crossOrigin='*' @loadeddata="loadedListener">
<source>
</video>
<!-- <span v-for="(item, index) in fileImgs">
<img :src="item" :id="index" @click="getId($event)" width="200px" height="150px" />
</span> -->
</div>
</el-upload>
</template>
<script>
import axios from 'axios'
export default {
props:["parentThis","setVideoImg"], //传入父组件的方法、属性
data() {
return {
videoFlag: false,
authProgress: 0,
uploadApi: '',
videoDataUrl:'',
vUrl:'',
fileImgs:[],
totalTime: '',
cover: '',
uploader: '',
videoDataForm: {
userId: '1811802489331712',
region: '',
partSize: 1048576, //分片大小
parallel: 5, //并行上传片数
retryCount: 3, //网络失败后重新上传次数
retryDuration: 2, //网络失败后重新上传时间
},
videoId: ''
}
},
methods: {
// 视频文件钩子改变
videoChange(file, fileList) {
this.authProgress = 0
this.videoFlag = false
console.log(file, '视频改变的钩子')
// 视频文件上传前的验证
// console.log(file.size / 1024 / 1024)
const isLt1G = file.raw.size / 1024 / 1024 < 1024;
// if (['video/mp4', 'video/ogg', 'video/flv', 'video/avi', 'video/wmv', 'video/rmvb'].indexOf(file.raw.type) == -
// 1) {
// this.$message.error('请上传正确的视频格式');
// return false;
// }
if (!isLt1G) {
this.$message.error('上传视频大小不能超过1G哦!');
return false;
}
this.fileImgs = [],
this.setVideoImg()
this.videoDataUrl = "",
this.videoFlag = false
// 获取上传视频的本地localUrl
var localUrl = null;
if (window.createObjectURL != undefined) {
// basic
localUrl = window.createObjectURL(file.raw);
} else if (window.URL != undefined) {
// mozilla(firefox)
localUrl = window.URL.createObjectURL(file.raw);
} else if (window.webkitURL != undefined) {
// webkit or chrome
localUrl = window.webkitURL.createObjectURL(file.raw);
}
// 转换后的地址为 blob:http://xxx/7bf54338-74bb-47b9-9a7f-7a7093c716b5
console.log(localUrl)
this.videoDataUrl = localUrl
// this.localUrl = localUrl
//获取视频时长
let url = URL.createObjectURL(file.raw);
let audioElement = new Audio(url);
let duration;
audioElement.addEventListener("loadedmetadata", function(_event) {
duration = audioElement.duration; //时长为秒,小数,182.36
// console.log(duration / 60, (duration / 60).toFixed(2));
console.log(duration)
});
//异步更新data视图
setTimeout(() => {
console.log(duration)
this.totalTime = this.secondsFormat(duration)
// this.videoDataUrl = videoDataUrl
}, 100)
let that = this
let userData = '{"Vod":{}}'
if (this.uploader) {
this.uploader.stopUpload()
}
this.uploader = this.createUplader(this)
this.uploader.addFile(file.raw, null, null, null, userData)
this.toSaveVideo ()
},
// 上传视频按钮
toSaveVideo() {
console.log("开始上传")
this.uploader.startUpload()
},
// createUploader
createUplader(that) {
console.log("that", that)
let uploader = new AliyunUpload.Vod({
userId: that.videoDataForm.userId,
partSize: that.videoDataForm.partSize,
parallel: that.videoDataForm.parallel,
retryCount: that.videoDataForm.retryCount,
retryDuration: that.videoDataForm.retryDuration,
//是否上报上传日志到点播,默认为true
enableUploadProgress: true,
// 开始上传
'onUploadstarted': function(uploadInfo) {
console.log(uploadInfo)
if (uploadInfo.videoId) {
// 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
//这里主要是前端请求后台刷新上传凭证
let refreshUrl = 'http://47.94.228.6:8060/video/refreshVoucher?videoId=' + uploadInfo.videoId
axios.post(refreshUrl).then(({
data
}) => {
if (data.result.code !== 1) {
return that.$message.error("上传失败")
}
let uploadAuth = data.result.uploadAuth
let uploadAddress = data.result.uploadAddress
let videoId = data.result.videoId
that.videoId = data.result.videoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
})
} else {
// 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
//这里主要是前端请求后台获取上传凭证
let title = uploadInfo.file.name.substr(0, uploadInfo.file.name.lastIndexOf('.'))
let createUrl = 'http://47.94.228.6:8060/video/getVoucher?title=' + title + '&FileName=' +
uploadInfo.file.name
console.log(createUrl)
axios.post(createUrl).then(({
data
}) => {
console.log(data)
if (data.code !== 1) {
return that.$message.error("uploadInfo.videoId 不存在")
}
let uploadAuth = data.result.uploadAuth
let uploadAddress = data.result.uploadAddress
let videoId = data.result.videoId
that.videoId = data.result.videoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
})
}
},
// 文件上传成功
'onUploadSucceed': function(uploadInfo) {
console.log("点播上传成功------",uploadInfo)
that.$message.success("上传成功")
let vUrl = uploadInfo.object
that.vUrl = "http://hou.k-playchina.com/" + vUrl
// console.log(that.vUrl)
that.videoFlag = true
},
// 文件上传失败
'onUploadFailed': function(uploadInfo, code, message) {
console.log(uploadInfo)
that.$message.error('上传失败')
// that.videoLoading.close()
},
// 文件上传进度,单位:字节
'onUploadProgress': function(uploadInfo, totalSize, loadedPercent) {
// console.log(uploadInfo)
// console.log(totalSize)
// console.log(loadedPercent)
that.authProgress = Math.ceil(loadedPercent * 100)
},
// 上传凭证超时
'onUploadTokenExpired': function(uploadInfo) {
console.log("OnUploadToken已过期");
//实现时,根据uploadInfo.videoId调用刷新视频上传凭证接口重新获取UploadAuth
//https://help.aliyun.com/document_detail/55408.html
//从点播服务刷新的uploadAuth,设置到SDK里
that.$message.success('上传文件超时,正在重新上传')
uploader.resumeUploadWithAuth(uploadAuth);
},
//全部文件上传结束
'onUploadEnd': function(uploadInfo) {
// console.log("onUploadEnd: uploaded all the files");
// that.$message.success('上传结束')
console.log("上传结束")
}
});
return uploader
},
// 转换视频时长的函数
secondsFormat( s ) {
var hour = Math.floor( s / 3600);
var minute = Math.floor( (s - hour*3600) /60 );
var second = Math.floor(s - hour*3600 - minute*60);
return hour + ":" + minute + ":" + second;
},
//视频截图
loadedListener() {
if (this.videoDataUrl && this.$refs.divVideo && this.$refs.divVideo.readyState === 4) {
// 获取到的视频时长(s)
console.log(parseInt(this.$refs.divVideo.duration));
// console.log(this.$refs.divVideo)
let _this = this;
// 截取视频帧,video标签内使用了autoplay让视频自动播放,这里需要截4张,设置了定时器
var canvas = document.createElement("canvas");
console.log(canvas)
canvas.width = _this.$refs.divVideo.videoWidth * 0.8;
canvas.height = _this.$refs.divVideo.videoHeight * 0.8;
canvas.getContext('2d').drawImage(_this.$refs.divVideo, 0, 0, canvas.width, canvas
.height);
// 生成图片(base64)
let aaa =setInterval(()=> {
const dataUrl = canvas.toDataURL('image/png')
_this.fileImgs.push(dataUrl)
// _this.$emit('setVideoImg');
_this.setVideoImg()
}, 2500)
setTimeout(()=>{
clearInterval(aaa)
// 截图完毕,停止视频
_this.$refs.divVideo.pause()
},10000)
}
},
editMethod(){
this.totalTime = this.parentThis.totalTime
this.videoFlag = this.parentThis.videoFlag
this.videoDataUrl = this.parentThis.vUrl
console.log(this.editVideo)
}
}
}
</script>
<style scoped="scoped">
/deep/ .el-upload {
cursor: auto;
margin-top: 5px;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 167px;
height: 93px;
line-height: 93px;
text-align: center;
background: #ddd;
cursor: pointer;
}
.avatar {
display: inline-block;
width: 167px;
height: 93px;
}
.img-box img {
width: 167px;
height: 93px;
margin-right: 15px;
}
.imgstyle {
box-shadow: 0 0 7px 2px #1890FF;
}
.upload_txt {
float: left;
text-align: left;
margin-left: 20px;
height: 93px;
line-height: 30px;
color: rgba(0, 0, 0, 0.6);
}
.upload_txt button {
margin-bottom: 10px;
}
</style>
releasevideo.vue 的源码(只有视频的干净版)
<template>
<div class="video-container">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-position="top" label-width="100px" class="demo-ruleForm">
<el-form-item label="上传视频:" prop="videoIntroduce" style="">
<AddVideo ref="setVideoImg" :setVideoImg="setVideoImg" :parentThis="this" />
</el-form-item>
<el-form-item label="选取视频帧" required>
<div class="img-box" v-model="ruleForm.fileImg">
<span v-for="(item, index) in fileImgs">
<img :src="item" :id="index" @click="getId($event)" :class="{imgstyle:index == current}" />
</span>
</div>
</el-form-item>
<el-form-item class="footer-btn">
<el-button type="primary" size="small" @click="submitForm('ruleForm')">
<span v-if="btnFlag">立即创建</span>
<span v-else-if="!btnFlag">立即修改</span>
</el-button>
<el-button size="small" @click="$router.push({name:'VideoList'})">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
import { AddVideo } from './components'
export default {
components: {
AddVideo, //导入添加视频组件
},
data() {
return {
btnFlag: true,
vUrl: '',
totalTime: '',
fileImgs:[],
formData: '',
ruleForm: {
fileImg: '',
}
}
},
methods: {
//获取子组件的视频帧
setVideoImg(){
this.fileImgs = this.$refs.setVideoImg.fileImgs
this.ruleForm.fileImg = this.fileImgs[0] //默认选择第一个
}
}
}
</script>
<style scoped="scoped">
.video-container {
padding: 20px;
padding-top: 5px;
}
.el-input {
width: 400px;
}
/deep/ .el-input__inner {
height: 32px;
line-height: 32px;
}
/deep/ .el-upload {
cursor: auto;
}
.img-box img {
width: 167px;
height: 93px;
margin-right: 15px;
}
.imgstyle {
box-shadow: 0 0 7px 2px #1890FF;
}
/deep/ .el-form-item {
margin-bottom: 5px;
}
/deep/ .el-form--label-top .el-form-item__label {
padding: 0;
}
/deep/ .el-form-item__error {
position: absolute;
top: 8px;
left: 410px;
}
.footer-btn {
margin-top: 50px;
}
.footer-btn button {
width: 100px;
margin-right: 20px;
}
.footer-btn button:nth-child(2) {
width: auto;
}
</style>
releasevideo.vue 的源码完整版
<template>
<div class="video-container">
<el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-position="top" label-width="100px" class="demo-ruleForm">
<el-form-item label="视频标题" prop="videoTitle">
<el-input v-model="ruleForm.videoTitle" placeholder="请输入视频标题"></el-input>
</el-form-item>
<el-form-item label="视频分类" required >
<el-select v-model="ruleForm.bigName" value-key="bigId" @focus="getBigName" @change="getKeyId(ruleForm.bigName)"
placeholder="请选择一级分类">
<el-option v-for="item in bigNames" :keyId="item.bigId" :label="item.bigName" :value="item.bigName">
</el-option>
</el-select>
<el-select v-model="ruleForm.classifyName" placeholder="请选择二级分类" @change="getCid(ruleForm.classifyName)">
<el-option v-for="item in classifyNames" :label="item.classifyName" :value="item.classifyName"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="集数">
<el-input v-model="ruleForm.videoDiver" style="width: 70px;margin-right: 10px;"></el-input>集
<div class="video-title" style="line-height: 40px; color: rgba(0, 0, 0, 0.45);">不选集数默认为单视频</div>
</el-form-item>
<el-form-item label="上传视频:" prop="videoIntroduce" style="">
<AddVideo ref="setVideoImg" :setVideoImg="setVideoImg" :parentThis="this" />
</el-form-item>
<el-form-item label="选取视频帧" required>
<div class="img-box" v-model="ruleForm.fileImg">
<span v-for="(item, index) in fileImgs">
<img :src="item" :id="index" @click="getId($event)" :class="{imgstyle:index == current}" />
</span>
</div>
</el-form-item>
<el-form-item class="footer-btn">
<el-button type="primary" size="small" @click="submitForm('ruleForm')">
<span v-if="btnFlag">立即创建</span>
<span v-else-if="!btnFlag">立即修改</span>
</el-button>
<el-button size="small" @click="$router.push({name:'VideoList'})">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
import { AddVideo } from './components'
import {
getBigAndClassify,
getBigId,
uploadVideo,
findId,
putVideoUrl,
addVideoInput
} from '@/api/video'
export default {
components: {
AddVideo, //导入添加视频组件
},
data() {
return {
actionURL: '',
videoFlag: true,
btnFlag: true,
bigNames: [],
keyId: '',
classifyNames: [],
fileImgs: [],
bigId: '',
vUrl: '',
localUrl:'',
totalTime: '',
current: 0,
formData: '',
ruleForm: {
videoTitle: '',
bigName: '',
classifyName: '',
videoDiver: '',
cid: '',
file: '',
fileImg: '',
id: '' //原视频视频的id -修改页面
},
dialogImageUrl: '',
dialogVisible: false,
rules: {
videoTitle: [{
required: true,
message: '请输入视频名称',
trigger: 'blur'
},
{
min: 1,
max: 30,
message: '长度最好控制在20个字符左右',
trigger: 'blur'
}
],
bigName: [{
required: true,
message: '请选择视频分类',
trigger: 'change'
}],
classifyName: [{
required: true,
message: '请选择视频分类',
trigger: 'change'
}]
},
imageUrl: '',
progressPercent: 0
}
},
methods: {
//获取一级分类
getBigName() {
getBigAndClassify().then(response => {
// console.log(response)
this.bigNames = response.result
}).catch(error => {
reject(error)
})
},
getKeyId(val) {
// console.log(val)
let obj = {};
obj = this.bigNames.find((item) => { //遍历bigNames的数据
return item.bigName === val; //筛选出匹配数据
});
this.keyId = obj.bigId;
this.bigId = "bigId" + "=" + this.keyId;
// 获取二级分类
getBigId(this.bigId).then(response => {
// console.log(response)
this.classifyNames = response.result
this.ruleForm.classifyName = ''
}).catch(error => {
reject(error)
})
},
//获取二级分类的cid
getCid(val) {
// console.log(val)
let obj = {};
obj = this.classifyNames.find((item) => { //遍历bigNames的数据
return item.classifyName === val; //筛选出匹配数据
});
this.ruleForm.cid = obj.cid
// console.log(this.ruleForm.cid)
// console.log(this.ruleForm.classifyName)
},
//获取子组件的视频帧
setVideoImg(){
this.fileImgs = this.$refs.setVideoImg.fileImgs
this.ruleForm.fileImg = this.fileImgs[0] //默认选择第一个
},
//清除数据
clearFrom() {
this.fileImgs = [];
this.ruleForm.videoDiver = ""
this.ruleForm.bigName = ""
this.ruleForm.classifyName = ""
this.imageUrl = ""
this.videoFlag == false
this.vUrl = ''
},
//提交
submitForm(formName) {
this.totalTime = this.$refs.setVideoImg.totalTime
this.vUrl = this.$refs.setVideoImg.vUrl
this.$refs[formName].validate((valid) => {
if (valid) {
var formData = new FormData();
formData.append('videoImg', this.ruleForm.fileImg.substring (22));
formData.append('vUrl', this.vUrl);
formData.append('videoTitle', this.ruleForm.videoTitle);
formData.append('bigName', this.ruleForm.bigName);
formData.append('classifyName', this.ruleForm.classifyName);
formData.append('videoDiver', this.ruleForm.videoDiver);
formData.append('totalTime', this.totalTime);
formData.append('vId', this.ruleForm.cid);
// formData1.append('aId', this.aId);
formData.append('aId', 1);
// console.log(formData1.totalTime)
//判断是添加还是编辑
if (this.$route.query.id) {
formData.append('id', this.ruleForm.id);
putVideoUrl(formData).then(response => {
console.log(response)
if (response.code == "1") {
this.$message.success("修改成功");
this.$router.push({
name: 'VideoList'
})
// this.$refs[formName].resetFields(); //重置表单
// this.clearFrom()
}
}).catch(error => {
return reject(error)
})
} else {
addVideoInput(formData).then(response => {
if (response.code == "1") {
this.$message.success("添加成功");
this.$refs[formName].resetFields(); //重置表单
this.$router.push({name:'VideoList'})
// this.clearFrom()
}
}).catch(error => {
reject(error)
})
}
} else {
console.log('error submit!!');
return false;
}
});
},
getId(e) {
this.current = "";
console.log(e.currentTarget); // 当前元素
console.log(e); // 获取 v-bind:id=值 中绑定的 id 的值,此 id 为其标签中的属性,且不会与非绑定的 id 冲突
console.log(e.currentTarget.src); // 获取 v-bind:id=值 中绑定的 id 的值,此 id 为其标签中的属性,且不会与非绑定的 id 冲突
this.current = e.currentTarget.id
this.ruleForm.fileImg = e.currentTarget.src
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
mounted() {
console.log(this.$route.query.id)
this.btnFlag = this.$route.query.id === undefined
//判断是添加还是编辑页面
if (this.$route.query.id) {
findId(this.$route.query.id).then(response => {
console.log(response)
this.ruleForm.videoTitle = response.result.findId.videoTitle;
this.ruleForm.videoDiver = response.result.findId.videoDiver;
this.ruleForm.cid = response.result.findId.vid
this.ruleForm.bigName = this.$route.query.bigName
this.ruleForm.classifyName = this.$route.query.classifyName
this.fileImgs.push(response.result.findId.videoImg);
this.ruleForm.fileImg = response.result.findId.videoImg;
this.totalTime = response.result.findId.totalTime
this.ruleForm.id = this.$route.query.id
this.totalTime = response.result.findId.totalTime
this.vUrl = response.result.findId.vurl;
// 调用子组件的赋值
this.$refs.setVideoImg.editMethod()
})
}
}
}
</script>
<style scoped="scoped">
.video-container {
padding: 20px;
padding-top: 5px;
}
.el-input {
width: 400px;
}
/deep/ .el-input__inner {
height: 32px;
line-height: 32px;
}
/deep/ .el-upload {
cursor: auto;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 167px;
height: 93px;
line-height: 93px;
text-align: center;
background: #ddd;
cursor: pointer;
}
.avatar {
display: inline-block;
width: 167px;
height: 93px;
}
.img-box img {
width: 167px;
height: 93px;
margin-right: 15px;
}
.imgstyle {
box-shadow: 0 0 7px 2px #1890FF;
}
/deep/ .el-form-item {
margin-bottom: 5px;
}
/deep/ .el-form--label-top .el-form-item__label {
padding: 0;
}
/deep/ .el-form-item__error {
position: absolute;
top: 8px;
left: 410px;
}
.footer-btn {
margin-top: 50px;
}
.footer-btn button {
width: 100px;
margin-right: 20px;
}
.footer-btn button:nth-child(2) {
width: auto;
}
</style>