Commit c810faa8 authored by 李维's avatar 李维

添加视频上传组件

parent 797e5629
...@@ -9,8 +9,11 @@ ...@@ -9,8 +9,11 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"axios": "^1.7.2", "axios": "^1.7.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-ui": "^2.15.14", "element-ui": "^2.15.14",
"spark-md5": "^3.0.2",
"uuid": "^10.0.0",
"vue": "^2.6.14" "vue": "^2.6.14"
}, },
"devDependencies": { "devDependencies": {
...@@ -2097,6 +2100,11 @@ ...@@ -2097,6 +2100,11 @@
"semver": "bin/semver" "semver": "bin/semver"
} }
}, },
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
},
"node_modules/css-declaration-sorter": { "node_modules/css-declaration-sorter": {
"version": "6.4.1", "version": "6.4.1",
"resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", "resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
...@@ -6590,6 +6598,15 @@ ...@@ -6590,6 +6598,15 @@
"websocket-driver": "^0.7.4" "websocket-driver": "^0.7.4"
} }
}, },
"node_modules/sockjs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz",
...@@ -6616,6 +6633,11 @@ ...@@ -6616,6 +6633,11 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"node_modules/spark-md5": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/spark-md5/-/spark-md5-3.0.2.tgz",
"integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="
},
"node_modules/spdx-correct": { "node_modules/spdx-correct": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz", "resolved": "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz",
...@@ -7135,10 +7157,13 @@ ...@@ -7135,10 +7157,13 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "8.3.2", "version": "10.0.0",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
"dev": true, "funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/bin/uuid"
} }
......
...@@ -8,8 +8,11 @@ ...@@ -8,8 +8,11 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.2", "axios": "^1.7.2",
"crypto-js": "^4.2.0",
"echarts": "^5.5.0", "echarts": "^5.5.0",
"element-ui": "^2.15.14", "element-ui": "^2.15.14",
"spark-md5": "^3.0.2",
"uuid": "^10.0.0",
"vue": "^2.6.14" "vue": "^2.6.14"
}, },
"devDependencies": { "devDependencies": {
......
<template> <template>
<div id="app"> <div id="app">
<div class="main-header"> <VideoUploader v-model="itemData.url" @change="save"/>
<div style="float: left;">
<span>当前编辑视频:</span>
<el-button type="text" @click="handleClickSelectSyllabus">在库中选择视频</el-button>
</div>
<div style="float: right;">
<el-button type="primary" @click="handleClickSave">保存</el-button>
</div>
</div>
<div class="main-body">
<div ref="refDubbingScrollList" class="dubbing-items">
<el-card v-for="item, index in dubbingData.dubbingItems" :key="index" class="box-card">
<div style="position: relative;">
<span style="position: absolute;" class="item-index">{{ index + 1 }} <span style="font-size: medium; line-height: normal;">{{ currentSelectDubbingIndex==index ? '正在编辑' : '' }}</span></span>
<div style="text-align: right;">
<el-button size="mini" type="danger" @click="handleClickDelete(index)">删除</el-button>
<el-button size="mini" type="primary" @click="handleClickSetCurrent(index)">编辑</el-button>
</div>
</div>
<el-divider></el-divider>
<el-form label-width="50px">
<el-form-item label="原文">
<el-input type="text" v-model="item.orgText" />
</el-form-item>
<!-- <el-form-item label="译文">
<el-input type="text" v-model="item.transText"/>
</el-form-item> -->
<el-form-item label="开始">
<el-input type="text" v-model="item.timelineIn">
<template slot="append">(秒)</template>
</el-input>
</el-form-item>
<el-form-item label="结束">
<el-input type="text" v-model="item.timelineOut">
<template slot="append">(秒)</template>
</el-input>
</el-form-item>
</el-form>
</el-card>
<el-card class="box-card">
<el-button type="primary" @click="handelClickAdd" style="width: 100%;">新建</el-button>
</el-card>
</div>
<div class="video-panel">
<div class="center-container">
<video v-if="!updateVideoSource" ref="refVideo" controls width="768px">
<source :src="videoUrl" type="video/mp4">
Your browser does not support the video tag.
</video>
<TimeRangeSelector v-model="timeRange" @change="handleRangeChange"></TimeRangeSelector>
</div>
</div>
<el-collapse-transition>
<div v-show="currentSelectDubbingIndex >= 0" class="button-group">
<el-button type="" size="medium" icon="el-icon-refresh" @click="handleClickPlayRange">播放区间</el-button>
<el-button v-show="!isPlaying" type="" size="medium" icon="el-icon-video-play" @click="handleClickPlay">播放</el-button>
<el-button v-show="isPlaying" type="" size="medium" icon="el-icon-video-pause" @click="handleClickPause">暂停</el-button>
<el-button type="" size="medium" @click="handelClickRre10s">向前1秒</el-button>
<el-button type="" size="medium" @click="handleClickSetStart">设置为起点</el-button>
<el-button type="" size="medium" @click="handleClickSetEnd">设置为结束点</el-button>
</div>
</el-collapse-transition>
</div>
<el-dialog title="选择内容" :visible.sync="dialogSelectShow" width="500px">
<div class="el-main" style="height: 300px;">
<el-tree :data="treeListData" :props="treeProps" @node-click="handleClickTreeNode" :lazy="true"
:load="handleLazyLoadTreeData"></el-tree>
</div>
<div slot="footer" class="dialog-footer">
<span class="current-selected-text">当前选择:{{ currentSelectTreeNode ? currentSelectTreeNode.name : "" }} -
{{ currentSelectTreeNode ? currentSelectTreeNode.id : "" }}</span>
<el-button @click="handleClickCancelSelect">取 消</el-button>
<el-button type="primary" @click="handleClickConfirmSelect">确 定</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script> <script>
import TimeRangeSelector from './components/TimeRangeSelector.vue' import TimeRangeSelector from './components/TimeRangeSelector.vue'
import { getSyllabusListById, getFirstCoursewareBySyllId, getPidPathName } from '@/api/syllabus' import { getSyllabusListById, getFirstCoursewareBySyllId, getPidPathName } from '@/api/syllabus'
import VideoUploader from './components/VideoUploader.vue'
export default { export default {
name: 'App', name: 'App',
components: { components: {
TimeRangeSelector TimeRangeSelector,
VideoUploader
}, },
data() { data() {
return { return {
// 是否正在播放 itemData: {
isPlaying: false, url: "",
}
// 视频地址
videoUrl: '',
// 时间区间
timeRange: {
start: 0,
end: 1
},
// 配音数据
dubbingData: {
backgroundAudio: {
url: "",
duration: 0,
size: 0
},
dubbingItems: []
},
// 选择内容对话框显示隐藏控制
dialogSelectShow: false,
// 树形列表数据
treeListData: [],
// 当前选择的节点信息
currentSelectTreeNode: null,
// 树形列表配置
treeProps: {
children: 'children',
label: 'name',
isLeaf: function (data, node) {
return data.has_courseware == "1";
}
},
// 是否更新视频资源
updateVideoSource: false,
// 当前选择的配音秀索引
currentSelectDubbingIndex: -1
}; };
}, },
async mounted() { async mounted() {
this.getData(); this.getData();
// this.$setData([
// {
// "orgText": "Apple.",
// "transText": "",
// "timelineIn": "0.352728",
// "timelineOut": "2.349307"
// },
// {
// "orgText": "Green apple.",
// "transText": "",
// "timelineIn": "4.938305",
// "timelineOut": "7.2651"
// },
// {
// "orgText": "Red apple.",
// "transText": "",
// "timelineIn": "10.687003",
// "timelineOut": "13.054597"
// },
// {
// "orgText": "Juicy apple.",
// "transText": "",
// "timelineIn": "16.128933",
// "timelineOut": "20.358868"
// },
// {
// "orgText": "Gulp,gulp,hic!",
// "transText": "",
// "timelineIn": "20.674126",
// "timelineOut": "23.316044"
// },
// {
// "orgText": "BOTH:apples!",
// "transText": "",
// "timelineIn": "26.360674",
// "timelineOut": "29.569075"
// },
// {
// "orgText": "Shiny apple.",
// "transText": "",
// "timelineIn": "34.97507",
// "timelineOut": "37.929512"
// },
// {
// "orgText": "Crunchy apple.",
// "transText": "",
// "timelineIn": "41.66807",
// "timelineOut": "44.267387"
// },
// {
// "orgText": "Bobbing apple.",
// "transText": "",
// "timelineIn": "48.00274",
// "timelineOut": "50.683136"
// },
// {
// "orgText": "Way-hay!Apples!",
// "transText": "",
// "timelineIn": "53.787037",
// "timelineOut": "57.842"
// }
// ])
}, },
methods: { methods: {
async getData() { async getData() {
const dpeData = await this.$getDPEData();
console.log("====dpeData====", dpeData)
const res = await this.$getData(); const res = await this.$getData();
console.log("====getData====", res)
const videoData = dpeData.find(item => item.name === '视频');
// console.log("videoData", videoData)
if(!videoData) {
this.videoUrl = '';
} else {
try {
const obj = JSON.parse(videoData.data);
this.videoUrl = obj.url;
// 重新加载视频组件
this.updateVideoSource = true;
this.$nextTick(() => {
this.updateVideoSource = false;
})
// console.log("videoUrl", this.videoUrl)
} catch (error) {
}
}
if(!res) { if(!res) {
// 默认数据 // 默认数据
this.dubbingData = { this.itemData = {
backgroundAudio: { url: "",
url: "",
duration: 0,
size: 0
},
dubbingItems: []
} }
} else { } else {
this.dubbingData = res this.itemData = res
}
},
handleRangeChange(event) {
this.$refs.refVideo.currentTime = event.start * this.$refs.refVideo.duration;
const sTime = this.timeRange.start * this.$refs.refVideo.duration;
const eTime = this.timeRange.end * this.$refs.refVideo.duration;
this.dubbingData.dubbingItems[this.currentSelectDubbingIndex].timelineIn = sTime;
this.dubbingData.dubbingItems[this.currentSelectDubbingIndex].timelineOut = eTime;
},
// 点击区间播放
handleClickPlayRange() {
const sTime = this.timeRange.start * this.$refs.refVideo.duration;
const eTime = this.timeRange.end * this.$refs.refVideo.duration;
this.$refs.refVideo.currentTime = sTime;
this.$refs.refVideo.play().then(() => {
this.isPlaying = true;
const checkTime = () => {
if (this.$refs.refVideo.currentTime >= eTime) {
this.$refs.refVideo.pause();
this.isPlaying = false;
this.$refs.refVideo.removeEventListener('timeupdate', checkTime);
}
}
this.$refs.refVideo.addEventListener('timeupdate', checkTime, false);
})
.catch(error => {
console.log(error);
});
},
handleClickPlay() {
this.$refs.refVideo.play();
this.isPlaying = true;
},
handleClickPause() {
this.$refs.refVideo.pause();
this.isPlaying = false;
},
handelClickRre10s() {
this.$refs.refVideo.currentTime -= 1;
},
handleClickSetStart() {
this.timeRange.start = this.$refs.refVideo.currentTime / this.$refs.refVideo.duration;
this.dubbingData.dubbingItems[this.currentSelectDubbingIndex].timelineIn = this.$refs.refVideo.currentTime;
},
handleClickSetEnd() {
this.timeRange.end = this.$refs.refVideo.currentTime / this.$refs.refVideo.duration;
this.dubbingData.dubbingItems[this.currentSelectDubbingIndex].timelineOut = this.$refs.refVideo.currentTime;
},
handleClickSetCurrent(index, play = false) {
const timelineIn = this.dubbingData.dubbingItems[index].timelineIn / this.$refs.refVideo.duration;
const timelineOut = this.dubbingData.dubbingItems[index].timelineOut / this.$refs.refVideo.duration;
this.currentSelectDubbingIndex = index;
this.timeRange = {
start: timelineIn,
end: timelineOut
} }
play && this.handleClickPlayRange()
},
// 删除配音秀
handleClickDelete(index) {
this.$confirm("确定要删除吗?").then(res=>{
this.dubbingData.dubbingItems.splice(index, 1)
}).catch(_=>"")
},
// 添加配音
handelClickAdd() {
this.dubbingData.dubbingItems.push({
orgText: '',
transText: '',
timelineIn: 0,
timelineOut: 1
})
this.$nextTick(() => {
this.$refs.refDubbingScrollList.scrollTop = this.$refs.refDubbingScrollList.scrollHeight;
})
},
// 点击切换配音资源
async handleClickSelectSyllabus() {
this.dialogSelectShow = true;
const resData = await getSyllabusListById();
this.treeListData = resData.rows;
},
// 点击树形列表节点
handleClickTreeNode(node) {
if (node.has_courseware == "1") {
this.currentSelectTreeNode = node;
}
},
// 懒加载树形列表数据
handleLazyLoadTreeData(node, resolve) {
getSyllabusListById(node.data.id).then(data => {
resolve(data.rows)
})
}, },
// 取消选择
handleClickCancelSelect() { // 保存数据
this.dialogSelectShow = false; save() {
this.currentSelectTreeNode = null; console.log(this.itemData)
this.treeListData = []; this.$setData(this.itemData)
}, },
// 确定选择节点 handleUploading(progress) {
async handleClickConfirmSelect() {
// 获取视频课件信息
const resData = await getFirstCoursewareBySyllId(this.currentSelectTreeNode.id);
let jsonData = null;
try {
jsonData = JSON.parse(resData.data);
} catch (error) {
}
// 获取视频地址
this.videoUrl = jsonData ? jsonData.url : '';
// 重新加载视频组件
this.updateVideoSource = true;
this.$nextTick(() => {
this.updateVideoSource = false;
})
// 关闭对话框
this.handleClickCancelSelect();
},
// 保存数据
async handleClickSave() {
this.$setData(this.dubbingData)
} }
} }
} }
...@@ -411,76 +63,4 @@ html { ...@@ -411,76 +63,4 @@ html {
} }
#app {} #app {}
.cascader-item {
width: 150px;
}
.button-item {
margin-left: 10px;
}
.current-selected-text {
float: left;
font-size: small;
}
.video-panel {
margin-left: 320px;
}
.center-container {
width: 768px;
margin-left: 320px;
padding: 20px 10px;
margin: 0 auto;
}
.button-group {
margin-left: 320px;
text-align: center;
}
.dubbing-items {
width: 300px;
height: calc(100% - 90px);
background-color: #f0f0f0;
position: absolute;
left: 0;
overflow: auto;
padding: 10px;
}
.box-card {
margin-bottom: 10px;
}
::v-deep .el-card__body {
padding: 0 !important;
}
.item-index {
font-size: 20px;
font-weight: bold;
margin-right: 10px;
}
.main-header {
height: 70px;
line-height: 70px;
position: fixed;
z-index: 99;
top: 0;
background-color: #FFFFFF;
width: calc(100% - 40px);
border-bottom: 1px solid #dcdfe6;
padding: 0 20px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
}
.main-body {
margin-top: 70px;
height: calc(100% - 70px);
overflow: auto;
}
</style> </style>
<!-- 视频上传组件 -->
<template>
<div style="position: relative; width: 360px; height: 180px;">
<el-upload :data="uploadData" :disabled="uploading || transcoding" :action="uploadUrl" drag :before-upload="customReq" accept=".mp4" :show-file-list="false">
<!-- 上传提示 -->
<i v-show="!transcoding && !videoDone && !uploading" class="el-icon-upload"></i>
<div v-show="!transcoding && !videoDone && !uploading" class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div v-if="!transcoding && !videoDone && !uploading" style="text-align: center;" class="el-upload__tip" slot="tip">只能上传mp4文件</div>
<div v-if="transcoding" style="text-align: center;" class="el-upload__tip" slot="tip">视频后台转码中,请稍后...</div>
<!-- 进度条 -->
<div v-show="!transcoding && !videoDone && uploading"
style="position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);">
<el-progress type="circle" :percentage="uploadPercentage" :status="uploadStatus"></el-progress>
<div>{{ uploadSpeed }}</div>
</div>
<!-- 预览视频 -->
<div v-show="!transcoding && videoDone">
<video ref="refVideo" width="360px" height="180px" controls crossOrigin="anonymous">
<source :src="`${videoUrl}`" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
<!-- 转换动画 -->
<div v-if="transcoding">
<div class="mesh-loader">
<div class="set-one">
<div class="circle"></div>
<div class="circle"></div>
</div>
<div class="set-two">
<div class="circle"></div>
<div class="circle"></div>
</div>
</div>
</div>
</el-upload>
<!-- 截图 下载 删除按钮 -->
<div v-if="videoDone" class="buttons">
<el-button type="primary" size="mini" icon="el-icon-camera" @click="captureVideo"></el-button>
<el-button type="primary" size="mini" icon="el-icon-download" @click="download"></el-button>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteVideo"></el-button>
</div>
</div>
</template>
<script>
import cryptoJs from 'crypto-js';
import axios from 'axios';
import SparkMD5 from 'spark-md5';
export default {
name: 'VideoUploader',
props: {
value: {
type: String,
default: ''
},
},
watch: {
value: function (val, oldVal) {
// 监测父组件的更新
if(val) {
// 查看是否资源已经存在
this.httpHeadCall(val, async (exist) => {
if (exist) {
// 存在,直接标记视频完成
this.videoDone = true
} else {
// 如果不存在则默认是正在后台转码状态
this.transcoding = true;
await this.checkResourceAvaliable();
this.$message({
type: 'success',
message: '转换完成'
});
this.videoDone = true;
this.transcoding = false;
// 刷新视频源
this.$refs.refVideo.load()
}
})
}
this.videoUrl = val
},
videoUrl: function() {
this.$refs.refVideo.load()
}
},
data() {
return {
// 上传地址
uploadUrl: "",
// 上传附带的DATA
uploadData: "",
// 上传中标记位
uploading: false,
// 转码中
transcoding: false,
// 视频是否上传完成
videoDone: false,
// 上传进度
uploadPercentage: 0,
// 上传进度条状态 null success
uploadStatus: null,
// 上传速度
uploadSpeed: "0KB/s",
// 上传开始时间
startTime: null,
// 上传结束时间
endTime: null,
// 已经上传的文件大小
loaded: 0,
// 视频地址
videoUrl: "",
// 定时器ID
setIntervalIds: [],
setTimeoutIds: [],
}
},
created() {
// 获取上传地址
this.uploadUrl = window.courseware.uploadVideoUrl();
this.uploadData = window.courseware.uploadData();
window['air'].getUploadVideoCallback = (url, data) => {
this.uploadUrl = url;
this.uploadData = data;
};
},
mounted() {
// 添加点击事件监听,防止点击视频画面自动播放。(点击视频画面可以重新上传视频)
this.$refs.refVideo.addEventListener('click', () => {
const stop = () => {
this.$refs.refVideo.pause();
this.$refs.refVideo.removeEventListener("play", stop)
}
this.$refs.refVideo.addEventListener("play", stop)
});
},
methods: {
// 解析文件名
parseFileName(fileName) {
const lastDotIndex = fileName.lastIndexOf('.');
const name = fileName.substring(0, lastDotIndex);
const ext = fileName.substring(lastDotIndex);
return { name, ext };
},
// 重置上传变量
resetUploadVars() {
this.uploadPercentage = 0;
this.uploadStatus = null;
this.uploadSpeed = "0KB/s";
this.startTime = null;
this.endTime = null;
this.loaded = 0;
},
// 发送HEAD请求 测试资源是否存在
httpHeadCall(requsetUrl, callback) {
let xhr = new XMLHttpRequest();
try {
xhr.onreadystatechange = () => {
try {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 400)) {
callback(true);
} else {
callback(false);
}
}
} catch (e) {
callback(false);
}
};
xhr.open("HEAD", requsetUrl, true);
xhr.send();
xhr.timeout = 15000;
xhr.onerror = (e) => {
callback(false);
};
xhr.ontimeout = (e) => {
callback(false);
};
} catch (e) {
callback(false);
}
},
// 上传文件
async customReq(item) {
const { name, ext } = this.parseFileName(item.name)
if (ext !== '.mp4') {
this.$message({
type: 'error',
message: '只能上传mp4文件'
});
return false
}
// 获取文件MD5
const fileName = await this.getFileMD5(item);
const protocolStr = document.location.protocol;
const baseCDN = `${protocolStr}//teach.cdn.ireadabc.com`;
const baseAPI = `${protocolStr}//openapi.iteachabc.com`;
// 如果当前视频和新上传的视频是同一个,则不变。
if(this.videoUrl == `${baseCDN}/${fileName}_h.mp4`) {
this.$refs.refVideo.pause();
this.$refs.refVideo.currentTime = 0;
this.$message({
type: 'success',
message: '极速上传完成'
})
return
}
// 重置上传变量,并设置状态为上传中
this.resetUploadVars();
this.videoUrl = "";
this.uploading = true;
this.transcoding = false;
this.videoDone = false;
// 测试文件是否在服务上存在
this.httpHeadCall(`${baseCDN}/${fileName}_h.mp4`, (exit) => {
if(exit) {
// 文件存在,极速上传完成。并标记为完成状态。
this.uploadStatus = "success";
this.videoUrl = `${baseCDN}/${fileName}_h.mp4`;
this.$emit("input", this.videoUrl);
this.$emit('change', { url: this.videoUrl, file: item });
this.$emit('success', { url: this.videoUrl, file: item });
this.uploading = false;
this.$message({
type: 'success',
message: '极速上传完成'
})
return
}
// 开始上传到OSS
axios.get(`${baseAPI}/oss/timestamp`, {}).then(tmps => {
const sign = cryptoJs.MD5(tmps + "iplayABC2019").toString();
axios.get(`${baseAPI}/oss/signature`, { sign, tmps }).then((res) => {
const serve = res.data.data;
const formData = new FormData();
formData.append('key', fileName + `.mp4`);
formData.append('bucket', `iplayabc-worker-queue-videolmh`);
formData.append('OSSAccessKeyId', serve.OSSAccessKeyId);
formData.append('policy', serve.policy);
formData.append('Signature', serve.signature);
formData.append('success_action_status', '200');
formData.append('file', item);
item.action = `${protocolStr}//iplayabc-worker-queue-videolmh.oss-cn-beijing.aliyuncs.com`;
// 标记开始时间
this.startTime = new Date().getTime();
this.uploadStatus = null
axios({
method: 'post',
url: item.action,
headers: { 'Access-Control-Allow-Origin': '*' },
onUploadProgress: (progressEvent) => {
// 更新上传进度
this.uploadPercentage = Math.ceil(progressEvent.progress * 100);
// 计算上传速率
this.endTime = new Date().getTime();
const timeDiff = this.endTime - this.startTime;
this.startTime = new Date().getTime();
if (timeDiff === 0) return;
const sent = progressEvent.loaded - this.loaded;
this.loaded = progressEvent.loaded;
const speed = (sent / timeDiff) * 1000; // Convert to KB/s
const spNum = Math.round(speed / 1024);
if (spNum > 1024) {
this.uploadSpeed = `${(spNum / 1024).toFixed(2)}MB/s`;
} else {
this.uploadSpeed = `${spNum}KB/s`;
}
this.$emit('onProgress', progressEvent);
},
data: formData
}).then(async event => {
// 上传完成,延迟隐藏状态
const tid = setTimeout(() => {
this.resetUploadVars();
this.uploading = false;
this.transcoding = true;
this.videoDone = false;
const idIndex = this.setTimeoutIds.indexOf(tid);
if (idIndex > -1) {
this.setTimeoutIds.splice(idIndex, 1);
}
}, 1000);
// 编辑为成功状态
this.uploadStatus = "success";
this.videoUrl = `${baseCDN}/${fileName}_h.mp4`;
this.$emit("input", this.videoUrl);
this.$emit('change', { url: this.videoUrl, file: item });
this.$emit('success', { url: this.videoUrl, file: item });
this.setTimeoutIds.push(tid);
this.uploading = false;
this.$message({
type: 'info',
message: '上传成功,开始转码...'
});
}).catch(err => {
// 报错
const tid = setTimeout(() => {
this.uploading = false;
this.transcoding = false;
this.videoDone = false;
const idIndex = this.setTimeoutIds.indexOf(tid);
if (idIndex > -1) {
this.setTimeoutIds.splice(idIndex, 1);
}
}, 1000);
this.uploadStatus = "error";
this.$emit('error', err);
this.setTimeoutIds.push(tid);
})
});
});
})
return false
},
// 检查资源是否可用
checkResourceAvaliable() {
return new Promise(resovle => {
let intervalId = null;
intervalId = setInterval(() => {
this.httpHeadCall(this.videoUrl, (isSuccess) => {
if (isSuccess) {
if (intervalId) {
const index = this.setIntervalIds.indexOf(intervalId)
clearInterval(intervalId)
this.setIntervalIds.splice(index, 1)
}
resovle(true)
}
});
}, 800)
this.setIntervalIds.push(intervalId)
})
},
async download() {
const url = this.videoUrl
const fileName = url.split('/').pop()
const response = await fetch(url) //发送GET请求
if (response.ok) {
const blob = await response.blob() //获取Blob对象
const url = window.URL.createObjectURL(blob) //创建Blob对象的URL
const link = document.createElement('a') //创建一个新的<a>元素
link.href = url //设置<a>元素的href属性为Blob对象的URL
link.download = fileName //设置下载文件的文件名
document.body.appendChild(link) //将<a>元素添加到文档中
link.click() //模拟点击<a>元素以触发文件下载
this.$message({
type: 'success',
message: '下载完成'
});
} else {
//处理API响应失败的情况,例如显示错误消息
this.$message({
type: 'error',
message: '下载失败'
});
}
},
captureVideo() {
const fullFileName = this.videoUrl.split('/').pop();
const { name, ext } = this.parseFileName(fullFileName)
const video = this.$refs.refVideo;
const cvs = document.createElement('canvas');
const ctx = cvs.getContext("2d");
cvs.width = video.videoWidth;
cvs.height = video.videoHeight;
ctx.drawImage(video, 0, 0, cvs.width, cvs.height);
const screenshotDataUrl = cvs.toDataURL('image/png');
const link = document.createElement('a') //创建一个新的<a>元素
link.href = screenshotDataUrl //设置<a>元素的href属性为Blob对象的URL
link.download = name + "_" + Math.floor(video.currentTime * 1000) + ".png"//设置下载文件的文件名
document.body.appendChild(link) //将<a>元素添加到文档中
link.click() //模拟点击<a>元素以触发文件下载
this.$message({
type: 'success',
message: '截图成功'
});
},
deleteVideo() {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.videoUrl = "";
this.videoDone = false;
this.$emit("input", "");
this.$emit('change', { url: this.videoUrl, file: null });
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
},
// 计算文件MD5
getFileMD5(file) {
return new Promise(resovle => {
let fileReader = new FileReader();
let Spark = new SparkMD5.ArrayBuffer();
fileReader.readAsArrayBuffer(file);
fileReader.onload = function (e) {
Spark.append(e.target.result);
let md5 = Spark.end();
resovle(md5);
}
})
},
},
// 组件销毁时清除定时器
beforeDestroy() {
this.setTimeoutIds.forEach(tid => {
clearTimeout(tid);
});
this.setIntervalIds.forEach(iid => {
clearInterval(iid)
})
}
}
</script>
<style scoped>
.buttons {
text-align: center
}
.buttons .el-button {
padding: 4px 12px;
font-size: 18px;
}
/* MESH LOADER */
.mesh-loader {
overflow: hidden;
height: inherit;
width: inherit;
}
.mesh-loader .circle {
width: 10px;
height: 10px;
position: absolute;
background: #f44336;
border-radius: 50%;
margin: -5px;
-webkit-animation: mesh 3s ease-in-out infinite -1.5s;
animation: mesh 3s ease-in-out infinite -1.5s;
}
.mesh-loader>div .circle:last-child {
-webkit-animation-delay: 0s;
animation-delay: 0s;
}
.mesh-loader>div {
position: absolute;
top: 50%;
left: 50%;
}
.mesh-loader>div:last-child {
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
@keyframes mesh {
0% {
-webkit-transform-origin: 50% -100%;
transform-origin: 50% -100%;
-webkit-transform: rotate(0);
transform: rotate(0);
}
50% {
-webkit-transform-origin: 50% -100%;
transform-origin: 50% -100%;
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
50.1% {
-webkit-transform-origin: 50% 200%;
transform-origin: 50% 200%;
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform-origin: 50% 200%;
transform-origin: 50% 200%;
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
</style>
\ No newline at end of file
...@@ -9,8 +9,10 @@ Vue.use(ElementUI); ...@@ -9,8 +9,10 @@ Vue.use(ElementUI);
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.prototype.$getData = () => { Vue.prototype.$getData = () => {
console.log("===== getData =====");
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
(window).courseware.getData((data) => { (window).courseware.getData((data) => {
console.log(data);
resolve(data) resolve(data)
}, "default_key") }, "default_key")
}) })
...@@ -19,6 +21,8 @@ Vue.prototype.$getData = () => { ...@@ -19,6 +21,8 @@ Vue.prototype.$getData = () => {
Vue.prototype.$getDPEData = getDPEData; Vue.prototype.$getDPEData = getDPEData;
Vue.prototype.$setData = (data) => { Vue.prototype.$setData = (data) => {
console.log("===== setData =====");
console.log(data);
(window).courseware.setData(data, null, "default_key") (window).courseware.setData(data, null, "default_key")
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment