vue-element-admin 集成阿里云VOD点播
目前负责的一个系统里的点播要使用阿里云的VOD点播,经过大概研读阿里云的帮助文档,并且做了一些实质性的开发。现将开发思路记录下来,以免以后又掉进坑里。
第一步,下载阿里云VOD的JAVASCRIPT版本的SDK。
第二步,将下载下来的阿里云VOD的JAVASCRIPT版本的SDK放在VUE-ELEMENT-ADMIN项目目录/public/
第三步,在VUE-ELEMENT-ADMIN项目文件/public/index.html的head部分新增以下代码
<script src="<%= BASE_URL %>aliyun-upload-sdk-1.5.2/lib/es6-promise.min.js"></script>
<script src="<%= BASE_URL %>aliyun-upload-sdk-1.5.2/lib/aliyun-oss-sdk-6.13.0.min.js"></script>
<script src="<%= BASE_URL %>aliyun-upload-sdk-1.5.2/aliyun-upload-sdk-1.5.2.min.js"></script>
第四步,新增showVodUploadPanel状态,用于打开视频上传组件面板。修改/src/store/modules/app.js
const state = {
sidebar: {
opened: window.localStorage.getItem('sidebarStatus') ? !!+window.localStorage.getItem('sidebarStatus') : false,
withoutAnimation: false
},
device: 'desktop',
showVodUploadPanel: false
}
const mutations = {
TOGGLE_SIDEBAR: state => {
state.sidebar.opened = !state.sidebar.opened
state.sidebar.withoutAnimation = false
if (state.sidebar.opened) {
window.localStorage.setItem('sidebarStatus', 1)
} else {
window.localStorage.setItem('sidebarStatus', 0)
}
},
CLOSE_SIDEBAR: (state, withoutAnimation) => {
window.localStorage.setItem('sidebarStatus', 0)
state.sidebar.opened = false
state.sidebar.withoutAnimation = withoutAnimation
},
TOGGLE_DEVICE: (state, device) => {
state.device = device
},
OPEN_VODUPLOADPANEL: state => {
state.showVodUploadPanel = true
},
CLOSE_VODUPLOADPANEL: state => {
state.showVodUploadPanel = false
}
}
const actions = {
toggleSideBar({ commit }) {
commit('TOGGLE_SIDEBAR')
},
closeSideBar({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
toggleDevice({ commit }, device) {
commit('TOGGLE_DEVICE', device)
},
openVodUploadPanel({ commit }) {
commit('OPEN_VODUPLOADPANEL')
},
closeVodUploadPanel({ commit }) {
commit('CLOSE_VODUPLOADPANEL')
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
第五步,修改/src/store/getters.js
const getters = {
// ..................................多余部分省略
showVodUploadPanel: state => state.app.showVodUploadPanel
}
export default getters
第六步,在导航栏右侧新增打开视频上传面板组件的按钮,修改文件/src/layout/components/Navbar.vue
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu">
<template v-if="device!=='mobile'">
<div class="right-menu-item hover-effect">
<i class=" el-icon-film" @click="openVodUploadPanel" />
</div>
<search id="header-search" class="right-menu-item" />
openVodUploadPanel() {
this.$store.dispatch('app/openVodUploadPanel')
}
由于视频文件一般都比较大,为了在上传视频的时候不影响其他操作,我选择在布局文件上添加视频上传按钮。
第七步,新增视频上传组件。创建文件/src/layout/components/MediaUpload/index.vue
<template>
<div class="media-upload-container">
<div class="upload-info" @click="handlerVodFileSelect">
<div class="upload-info-icon">
<i class="el-icon-upload" />
<div class="upload-info-icon-help">
点击这里上传
</div>
</div>
<div class="upload-info-text">
支持3GP、ASF、AVI、DAT、DV、FLV、F4V、GIF、M2T、M4V、MJ2、MJPEG、MKV、MOV、MP4、MPE、MPG、MPEG、MTS、OGG、QT、RM、RMVB、SWF、TS、VOB、WMV、WEBM 等视频格式上传 , 音频支持aac, ac3, acm, amr, ape, caf, flac, m4a, mp3, ra, wav, wma
</div>
<input ref="vodFileInput" type="file" multiple class="upload-info-btn" @change="fileChange">
</div>
<div v-if="showVodFileInfo">
<el-table
:data="vodFileInfo"
:header-cell-style="{'background':'#f4f4f4'}"
style="width: 100%"
class="vod-file-info-table"
>
<el-table-column
prop="name"
label="音/视频名称"
>
<template slot-scope="scope">
<el-input v-model="scope.row.name" placeholder="请输入标题" />
</template>
</el-table-column>
<el-table-column
prop="type"
label="格式"
width="120"
/>
<el-table-column
prop="size"
label="大小"
width="120"
>
<template slot-scope="scope">
<span v-text="handleFormatFileSize(scope.row.size)" />
</template>
</el-table-column>
</el-table>
<el-row>
<el-col :span="20">
<el-progress :text-inside="true" :stroke-width="28" :percentage="authProgress" :format="uploadStatusFormat" :class="{'progress-ready': progressReady}" />
</el-col>
<el-col :span="4" style="background-color: #e6ebf5; text-align: right">
<el-button :disabled="uploadDisabled" type="primary" size="mini" @click="authUpload">开始上传</el-button>
<el-button :disabled="uploadDisabled" type="primary" size="mini" @click="resumeUpload">恢复上传</el-button>
</el-col>
</el-row>
</div>
<el-table
v-if="showVodUploadSuccessInfo"
:data="VodUploadSuccessInfo"
:header-cell-style="{'background':'#f4f4f4'}"
style="width: 100%"
class="vod-upload-success-info-table"
>
<el-table-column
prop="file.name"
label="音/视频名称"
/>
<el-table-column
prop="file.type"
label="格式"
/>
<el-table-column
prop="file.size"
label="大小"
>
<template slot-scope="scope">
<span v-text="handleFormatFileSize(scope.row.file.size)" />
</template>
</el-table-column>
<el-table-column
prop="state"
label="上传状态"
/>
<el-table-column
label="操作"
align="right"
>
<template slot-scope="scope">
<el-button type="text" @click="deleteVod(scope.row.videoId)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import axios from 'axios'
import { getOptionsByCurrentServiceId } from '@/api/options'
import { formatBytesSize } from '@/utils/index'
export default {
data() {
return {
loading: true,
aliyun_vod_accessKey_id: '',
aliyun_vod_accessKey_secret: '',
aliyun_vod_timeout: 60000,
aliyun_vod_partSize: 1048576,
aliyun_vod_parallel: 5,
aliyun_vod_retryCount: 3,
aliyun_vod_retryDuration: 2,
aliyun_vod_region: 'cn-shanghai',
aliyun_vod_userId: '1303984639806000',
saveLoading: false,
uploader: null,
statusText: '',
authProgress: 0,
uploadDisabled: true,
resumeDisabled: true,
pauseDisabled: true,
showVodFileInfo: false,
vodFileInfo: [],
showVodUploadSuccessInfo: false,
VodUploadSuccessInfo: [],
progressReady: false
}
},
created() {
this.getConfig()
},
methods: {
handleFormatFileSize(num) {
return formatBytesSize(num)
},
handlerVodFileSelect() {
this.$refs.vodFileInput.dispatchEvent(new MouseEvent('click'))
},
fileChange() {
const files = this.$refs.vodFileInput.files
for (let index = 0; index < files.length; index++) {
const file = files[index]
if (!file) {
this.$message({
message: '请选择需要上传的文件',
type: 'warning'
})
} else {
if (this.uploader) {
this.uploader.stopUpload()
this.authProgress = 0
this.statusText = ''
} else {
this.uploader = this.createUploader()
}
const userData = '{"Vod":{}}'
this.uploader.addFile(file, null, null, null, userData)
this.uploadDisabled = false
this.pauseDisabled = true
this.resumeDisabled = false
this.vodFileInfo.unshift(file)
}
}
this.progressReady = true
this.showVodFileInfo = true
},
// 开始上传
authUpload() {
// 然后调用 startUpload 方法, 开始上传
if (this.uploader !== null) {
this.uploader.startUpload()
this.uploadDisabled = true
this.pauseDisabled = false
}
},
// 暂停上传
pauseUpload() {
if (this.uploader !== null) {
this.uploader.stopUpload()
this.resumeDisabled = false
this.pauseDisabled = true
}
},
// 恢复上传
resumeUpload() {
if (this.uploader !== null) {
this.uploader.startUpload()
this.resumeDisabled = true
this.pauseDisabled = false
}
},
uploadStatusFormat(percentage) {
return percentage > 0 ? (this.statusText + percentage + '%') : this.statusText
},
createUploader() {
const self = this
const uploader = new AliyunUpload.Vod({
timeout: self.aliyun_vod_timeout || 60000,
partSize: self.aliyun_vod_partSize || 1048576,
parallel: self.aliyun_vod_parallel || 5,
retryCount: self.aliyun_vod_retryCount || 3,
retryDuration: self.aliyun_vod_retryDuration || 2,
region: self.aliyun_vod_region,
userId: self.aliyun_vod_userId,
// 添加文件成功
addFileSuccess: function(uploadInfo) {
self.uploadDisabled = false
self.resumeDisabled = false
self.statusText = '添加文件成功, 等待上传...'
console.log('addFileSuccess: ' + uploadInfo.file.name)
},
// 开始上传
onUploadstarted: function(uploadInfo) {
// 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
// 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress
// 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
// 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
// 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
// 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
if (!uploadInfo.videoId) {
const createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6'
axios.get(createUrl).then(({ data }) => {
const uploadAuth = data.UploadAuth
const uploadAddress = data.UploadAddress
const videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
})
self.statusText = '文件开始上传...'
console.log('onUploadStarted:' + uploadInfo.file.name + ', endpoint:' + uploadInfo.endpoint + ', bucket:' + uploadInfo.bucket + ', object:' + uploadInfo.object)
} else {
// 如果videoId有值,根据videoId刷新上传凭证
// https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY
const refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
axios.get(refreshUrl).then(({ data }) => {
const uploadAuth = data.UploadAuth
const uploadAddress = data.UploadAddress
const videoId = data.VideoId
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
})
}
},
// 文件上传成功
onUploadSucceed: function(uploadInfo) {
console.log('onUploadSucceed: ' + uploadInfo.file.name + ', endpoint:' + uploadInfo.endpoint + ', bucket:' + uploadInfo.bucket + ', object:' + uploadInfo.object)
self.statusText = '文件上传成功!'
self.VodUploadSuccessInfo.unshift(uploadInfo)
self.vodFileInfo.forEach((item, index) => {
if (item.name === uploadInfo.file.name) {
self.$delete(self.vodFileInfo, index)
}
})
self.showVodUploadSuccessInfo = true
self.createVod(uploadInfo)
},
// 文件上传失败
onUploadFailed: function(uploadInfo, code, message) {
console.log('onUploadFailed: file:' + uploadInfo.file.name + ',code:' + code + ', message:' + message)
self.statusText = '文件上传失败!'
},
// 取消文件上传
onUploadCanceled: function(uploadInfo, code, message) {
console.log('Canceled file: ' + uploadInfo.file.name + ', code: ' + code + ', message:' + message)
self.statusText = '文件已暂停上传'
},
// 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
onUploadProgress: function(uploadInfo, totalSize, progress) {
const progressPercent = Number((progress * 100).toFixed(2))
self.authProgress = progressPercent
self.statusText = uploadInfo.file.name + ' 上传中...'
if (self.progressReady === true && progress > 0) {
self.progressReady = false
}
console.log('onUploadProgress:file:' + uploadInfo.file.name + ', fileSize:' + totalSize + ', percent:' + progressPercent + '%')
},
// 上传凭证超时
onUploadTokenExpired: function(uploadInfo) {
// 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
// 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
// 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
const refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
axios.get(refreshUrl).then(({ data }) => {
const uploadAuth = data.UploadAuth
uploader.resumeUploadWithAuth(uploadAuth)
console.log('upload expired and resume upload with uploadauth ' + uploadAuth)
})
self.statusText = '文件超时...'
},
// 全部文件上传结束
onUploadEnd: function(uploadInfo) {
self.statusText = '文件上传完毕'
self.showVodFileInfo = false
}
})
return uploader
},
getConfig() {
this.loading = true
getOptionsByCurrentServiceId().then(response => {
if (response.data != null) {
this.aliyun_vod_accessKey_id = response.data.aliyun_vod_accessKey_id
this.aliyun_vod_accessKey_secret = response.data.aliyun_vod_accessKey_secret
this.aliyun_vod_timeout = response.data.aliyun_vod_timeout
this.aliyun_vod_partSize = response.data.aliyun_vod_partSize
this.aliyun_vod_parallel = response.data.aliyun_vod_parallel
this.aliyun_vod_retryCount = response.data.aliyun_vod_retryCount
this.aliyun_vod_retryDuration = response.data.aliyun_vod_retryDuration
this.aliyun_vod_region = response.data.aliyun_vod_region
this.aliyun_vod_userId = response.data.aliyun_vod_userId
}
this.loading = false
}).catch(() => {
this.loading = false
})
},
deleteVod(videoId) {
},
createVod(videoId) {
}
}
}
</script>
<style lang="scss" scoped>
.media-upload-container {
position: relative;
padding: 20px;
.upload-info {
padding: 15px 0;
color: #c1c1c1;
text-align: center;
border: 1px dashed #c1c1c1;
cursor: pointer;
.upload-info-icon {
color: #111;
margin-bottom: 10px;
.el-icon-upload {
font-size: 48px;
}
.upload-info-icon-help {
font-size: 16px;
}
}
.upload-info-text {
font-size: 14px;
}
.upload-info-btn {
display: none;
}
}
.vod-file-info-table {
margin: 20px 0;
}
.vod-upload-success-info-table {
margin: 20px 0 0 0;
}
.el-progress {
&.progress-ready {
position: relative;
/deep/.el-progress-bar__innerText {
color: #1682e6;
}
}
/deep/.el-progress-bar__outer {
border-radius: 0;
.el-progress-bar__inner {
border-radius: 0;
}
}
}
}
</style>
第八步,修改/src/layout/index.vue
<template>
<div :class="classObj" class="app-wrapper">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div :class="{hasTagsView:needTagsView}" class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar />
<tags-view v-if="needTagsView" />
</div>
<app-main :class="workbenchSidebarSatausObj" />
<workbench-sidebar />
<right-panel v-if="showSettings">
<settings />
</right-panel>
<el-drawer
title="视频上传"
size="1200px"
:visible="showPanel"
@open="openDrawer"
@close="closeDrawer"
>
<media-upload />
</el-drawer>
</div>
</div>
</template>
<script>
import RightPanel from '@/components/RightPanel'
import { AppMain, Navbar, Settings, Sidebar, WorkbenchSidebar, TagsView, MediaUpload } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
export default {
name: 'Layout',
components: {
AppMain,
Navbar,
RightPanel,
Settings,
Sidebar,
WorkbenchSidebar,
TagsView,
MediaUpload
},
mixins: [ResizeMixin],
data() {
return {
showPanel: false
}
},
computed: {
...mapState({
sidebar: state => state.app.sidebar,
workbenchSidebar: state => state.app.workbenchSidebar,
device: state => state.app.device,
showSettings: state => state.settings.showSettings,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader
}),
classObj() {
return {
hideSidebar: !this.sidebar.opened,
openSidebar: this.sidebar.opened,
withoutAnimation: this.sidebar.withoutAnimation,
mobile: this.device === 'mobile'
}
},
workbenchSidebarSatausObj() {
return {
hideWorkbenchSidebar: !this.workbenchSidebar.opened,
openWorkbenchSidebar: this.workbenchSidebar.opened
}
}
},
watch: {
'$store.state.app.showVodUploadPanel'(val) {
this.showPanel = val
}
},
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
},
openDrawer() {
this.$store.dispatch('app/openVodUploadPanel')
},
closeDrawer() {
this.$store.dispatch('app/closeVodUploadPanel')
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
@import "~@/styles/variables.scss";
.app-wrapper {
@include clearfix;
position: relative;
height: 100%;
width: 100%;
&.mobile.openSidebar {
position: fixed;
top: 0;
}
}
.drawer-bg {
background: #000;
opacity: 0.3;
width: 100%;
top: 0;
height: 100%;
position: absolute;
z-index: 999;
}
.fixed-header {
position: fixed;
top: 0;
right: 0;
z-index: 9;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
&.driver-fix-stacking {
position: absolute;
}
}
.hideSidebar .fixed-header {
width: 100%
}
.mobile .fixed-header {
width: 100%;
}
</style>
最后来一张最终效果图
回复
要发表评论,您必须先登录。