<template>
<div>
<el-row>
<el-col :span="10" style="">
<div>
<el-card style="margin: 0; padding:0; overflow-y: auto">
<div style="width:100%; min-height:600px;position: relative;">
<!--(防止页面方法缩小弹幕位置偏差) 思路:每次页面浏览器狂发生变化时获得video的offsetWidth, 每次发生变化时重新设置弹幕的margin-left/left -->
<barrage ref="barrage" :options="paperData.randList" :videoDisabled="videoDisabled" class="barrage"></barrage>
<video id="live" style="min-height:600px!important;box-sizing: border-box;" > </video>
</div>
</el-card>
<el-card style=" overflow-y: auto">
<el-row style="text-align: center;" :gutter="10">
<el-col :span="24" v-if="!videoDisabled">
剩余时间: <exam-timer v-model="paperData.leftSecondsVideo" type="video" @timeout="doHandler(1)" />
</el-col>
<el-col :span="24" v-else>
剩余考试时间:
<span style="color: #ff0000; font-weight: 700">{{ min }}分钟{{ sec }}秒</span>
<!-- <exam-timer v-model="paperData.leftSecondsVideo" type="video" timerTime="slow" :time-map="paperData.timeMap" @timeout="doHandler(1)" /> -->
</el-col>
<el-col :span="24">
<el-divider />
</el-col>
<el-col :span="24">
<button id="start" :disabled="videoDisabled" :class="!videoDisabled?'el-button el-button--primary el-button--small':'el-button el-button--primary el-button--medium'">{{!videoDisabled?'点击开始录制':'正在录制'}}</button>
<button id="stop" class="el-button el-button--success el-button--small">点击保存并上传</button>
</el-col>
<el-col :span="24">
<el-divider />
</el-col>
</el-row>
</el-card>
</div>
<!-- <button @click="updata">点击保存并上传上传</button> -->
</el-col>
<el-col :span="14">
<el-card style="height: 99vh; overflow-y: auto">
<el-form ref="practiceForm" :model="practiceForm" :rules="practiceRules">
<el-table
:data="paperData.dtlList"
:span-method="objectSpanMethod"
border
style="width: 100%; margin-top: 20px">
<el-table-column type="index" label="序号" align="center"/>
<el-table-column
prop="category"
label="提示1"
min-width="50" align="center">
</el-table-column>
<el-table-column
prop="stepNum"
label="提示2" min-width="70" align="center">
<template #default="{row}">
{{handleStepSelect(row)}}
</template>
</el-table-column>
<el-table-column
prop="speech"
label="参考话术3" min-width="100" align="center" >
<template #default="{row}">
{{row.speech}}
</template>
</el-table-column>
<el-table-column
prop="archives"
label="文字信息" min-width="40" align="center">
<template #default="{row}">
<div v-html="handleArchives(row.archives)"></div>
</template>
</el-table-column>
<el-table-column
prop="remark"
label="备注" align="center">
<template #default="{row}">
{{row.remark}}
</template>
</el-table-column>
</el-table>
</el-form>
</el-card>
</el-col>
</el-row>
<div v-show="progressShow" style="width:100%;height:100%; background:rgb(255,255,255,.8);position:fixed;top:0;left:0">
<el-progress type="circle" :percentage="progress>100 ? 100:progress" ></el-progress>
</div>
</div>
</template>
<script>
import ExamTimer from './components/ExamTimer' // 组件
import screenfull from 'screenfull' // 组件
import Barrage from './components/Barrage'
import {paperDetail,getarchivesbykey, uploadVideo,captureImage, upsmileScore, uponetime,videouploadAili } from '@/api/web/practiceExam'
export default {
name:'ExamVideo',
components:{
ExamTimer,
screenfull,
Barrage
},
data() {
return {
mediaRecorderData: "",
n:0,
blobData:"",
paperId:null,
paperData:{},
practiceForm:{ dtlList:[]},
practiceRules:{ },
spanArr:[],
position:null,
buzhouOptions: [],
video:null,
canvas: null,
canvasContext:null,
timer:'',
numTimer: null,
num:0,
videoDisabled: false,
handleStop: null,
streamorigin: null,
progress:0, //进度
progressShow:false,
min: '00',
sec:'00',
leftTimer: null,
timemap: null,
timeMap: null,
};
},
destroyed(){
clearInterval(this.leftTimer)
clearInterval(this.timer)
clearInterval(this.numTimer)
},
mounted() {
// 防止页面后退
history.pushState(null, null, document.URL)
window.addEventListener('popstate', function() {
history.pushState(null, null, document.URL)
})
// 防止页面刷新
window.onbeforeunload = function(event){
return false
},
document.addEventListener('fullscreenchange', v=>{
if(!screenfull.isFullscreen){
this.doHandler(1)
}
})
this.stopF5Refresh()
this.getNavigator()
this.video = document.querySelectorAll('#video')
this.canvas = document.createElement('canvas')
this.canvas.width = 400
this.canvas.height = 300
this.canvasContext = this.canvas.getContext('2d')
},
async created() {
const id = this.$route.params.id
if (typeof id !== 'undefined') {
this.paperId = id
await this.fetchData(id)
this.handleConfirm()
this.getDicts("sys_dict_video_buzhou").then(response => {
this.buzhouOptions = response.data;
});
}
this.video = document.querySelectorAll('#video')
this.canvas = document.createElement('canvas')
this.canvas.width = 400
this.canvas.height = 300
this.canvasContext = this.canvas.getContext('2d')
},
methods: {
stopF5Refresh() {
document.onkeydown = function(e) {
var evt = window.event || e;
var code = evt.keyCode || evt.which;
//屏蔽F1---F12
if ((code > 111 && code < 124) || (code == 17 || code == 18 || code==82)) {
if (evt.preventDefault) {
evt.preventDefault();
} else {
evt.keyCode = 0;
evt.returnValue = false;
}
}
};
//禁止鼠标右键菜单
document.oncontextmenu = function(e) {
return false;
};
},
handleTimeMap(){
let newValue = this.timeMap
const keys = Object.keys(newValue)
keys.sort(function(a, b){
return newValue[b] - newValue[a]
})
this.timemap = keys
},
handleConfirm(){
this.$confirm('开场互动环节', '直播考試环节', {
confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning',
}).then(async (valid)=>{
await uponetime({id: this.paperData.id})
const res = await paperDetail({ id: this.paperId })
this.paperData.leftSecondsVideo = res.data.leftSecondsVideo
// this.paperData.leftSecondsVideo = 20
this.leftTimer = setInterval(() =>this.countdown(), 1000)
this.toggleFull()
if(document.all) {
document.getElementById("start").click();
}else {
var e = document.createEvent("MouseEvents");e.initEvent("click", true, true);
document.getElementById("start").dispatchEvent(e);
}
}).catch(() => {
this.handleConfirm()
})
},
countdown(){
if (this.paperData.leftSecondsVideo <= 0) {
this.doHandler(1)
return
}
const min = parseInt(this.paperData.leftSecondsVideo / 60)
const sec = parseInt(this.paperData.leftSecondsVideo % 60)
this.min = min > 9 ? min : '0' + min
this.sec = sec > 9 ? sec : '0' + sec
this.paperData.leftSecondsVideo -= 1
if(this.paperData.leftSecondsVideo == parseInt(this.timeMap[this.timemap[3]])){
this.handlerAlert(this.timemap[3])
}else if(this.paperData.leftSecondsVideo == parseInt(this.timeMap[this.timemap[2]])){
this.handlerAlert(this.timemap[2])
}else if(this.paperData.leftSecondsVideo == parseInt(this.timeMap[this.timemap[1]])){
this.handlerAlert(this.timemap[1])
}
},
handlerAlert(msg){
this.$message({
message: '当前环节进入'+ msg,
type: 'success',
showClose: true,
duration:10000
});
},
toggleFull() {
if (!screenfull.isFullscreen) {
screenfull.toggle()
this.$message({
message: ' 本场考试已开启切屏监控,请保持全屏,离开将会强制交卷,请诚信考试!',
type: 'error',
showClose: true,
duration:20000
});
}
},
fetchData(){
paperDetail({ id: this.paperId }).then(response => {
// 试卷内容
if(response.code!==200){
this.$message.error(res.msg)
return false;
}
this.paperData = Object.assign({},response.data)
this.timeMap = response.data.timeMap
this.handleTimeMap()
// this.paperData.leftSecondsVideo = 10
response.data.dtlList.forEach(list=>{
getarchivesbykey({key:list.step}).then((res)=>{
if(res.code == 200){
list.archives = res.msg
}
})
})
if(this.paperData.dtlList.length>0){
this.paperData.dtlList = response.data.dtlList.sort(this.compare('stepNum'))
}
this.getSpanArr(this.paperData.dtlList)
})
},
getNavigator(){
let stopButton = document.getElementById("stop");
let startButton = document.getElementById("start");
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true,
})
.then((stream) => {
// console.log(stream, "stream");
let liveVideo = document.getElementById("live");
// liveVideo.src = URL.createObjectURL(stream); // 你会看到一些警告
liveVideo.srcObject = stream;
this.streamorigin = stream
liveVideo.play();
stopButton.addEventListener("click", this.stopLive);
startButton.addEventListener("click", (e) => {
this.startLive(stream);
});
});
},
// 暂停后下载视频
downLoadVideo(chunks) {
let downloadLink = document.createElement("a");
downloadLink.href = URL.createObjectURL(
new Blob(chunks, {
type: "application/video",
})
);
// downloadLink.download = 'live.webm';
// downloadLink.download = "live.ogg";
downloadLink.download = "live.mp4";
downloadLink.click();
},
// 结束录制
stopLive() {
this.n = 0;
if (this.mediaRecorderData&&this.mediaRecorderData!=null&&this.mediaRecorderData!='') {
this.mediaRecorderData.stop();
} else {
this.$message.error("还没有开始。");
}
},
// 开始
startLive(stream) {
let recordedChunks = [];
this.mediaRecorderData = new MediaRecorder(stream);
this.mediaRecorderData.start();
this.mediaRecorderData.addEventListener("dataavailable", (e)=> {
if (e.data.size > 0) {
recordedChunks.push(e.data);
this.blobData = recordedChunks;
};
});
this.mediaRecorderData.addEventListener("stop", async ()=>{
// console.log("暂停 自动下载");
// this.downLoadVideo(recordedChunks);
await this.updata()
await upsmileScore({id: this.paperId})
clearInterval(this.timer)
clearInterval(this.numTimer)
clearInterval(this.leftTimer)
this.num = 0
});
this.mediaRecorderData.addEventListener("start", (e) => {
// console.log("开始 录制");
this.videoDisabled = true
this.timer = setInterval(() =>this.captureImage(), 30000)
this.numTimer = setInterval(() =>this.handleTiming(), 1000)
});
},
compare(property){
return (a,b)=>{
const value1 = a[property]
const value2 = b[property]
return value1-value2
}
},
// 上传视频
async doHandler(num){
clearInterval(this.timer)
clearInterval(this.numTimer)
clearInterval(this.leftTimer)
if(!this.videoDisabled){
this.updata()
}else{
let stopButton = document.getElementById("stop");
if(stopButton) stopButton.click()
}
},
updata(num){
if(this.blobData){
var file = new File(this.blobData, 'video-' + (new Date).toISOString().replace(/:|\./g, '-') + '.mp4', {
type: 'video/mp4'
})
}else{
var file = ''
}
const data = new FormData();
data.append('file', file);
data.append('id', this.paperId);
data.append('videoTime', this.num)
var onUploadProgress= progressEvent => {
this.$message.closeAll()
this.progressShow = true
var complete = (progressEvent.loaded / progressEvent.total * 100 | 0)
this.progress = complete
}
uploadVideo(data, onUploadProgress).then(response=>{
if(response.code!==200){
this.$message.error(response.msg)
}else {
this.num = 0
if (screenfull.isFullscreen) {
screenfull.toggle()
}
this.progressShow = false
this.$router.push({ name: 'ShowExam', params: { id: this.paperId, type:2,mode:'0' }})
}
})
},
getSpanArr(data){
data.forEach((item, i) => {
if(i===0){
this.spanArr.push(1)
this.position = 0
}else{
if(data[i].category == data[i-1].category){
this.spanArr[this.position] += 1
this.spanArr.push(0);
}else{
this.spanArr.push(1)
this.position = i
}
}
})
},
objectSpanMethod({ row, column, rowIndex, columnIndex }) {
if (columnIndex === 1 ||(columnIndex==4 &&(rowIndex==8||rowIndex==9||rowIndex==10||rowIndex==11||rowIndex==12))) {
const _row = this.spanArr[rowIndex]
const _col = this.spanArr[columnIndex]
return {
rowspan: _row,
colspan: 1
};
}
},
handleArchives(s){
let html = ''
if(s){
const flag = s.indexOf(",") !=-1
const sName = s.split(",")
let kk = ''
for(let index = 0; index < sName.length; index++) {
kk+=` <div>${sName[index]}</div>`
}
html = flag ? kk : s
}
return html
},
handleStepSelect(row){
const obj = this.buzhouOptions.find(option=>row.step == option.dictValue)
return obj && obj.dictLabel ? obj.dictLabel:''
},
handleTiming(){
this.num+=1
// console.log(this.num)
},
captureImage() {//上传截图
const video = document.getElementById('live')
const canvas = document.createElement('canvas') //创建一个canvas
if(video&& canvas){
canvas.width = video.offsetWidth
canvas.height = video.offsetHeight
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height)//绘制图像
const img = new Image() //创建img
img.src = canvas.toDataURL('image/png')
const blobFile = this.dataURLtoFile(img.src, Date.parse(new Date())+'.jpg')
const data = new FormData();
data.append('file', blobFile);
data.append('id', this.paperId);
captureImage(data).then(response=>{
if(response.code!==200){
this.$message.error(response.msg)
}
})
}
},
//将base64转换为blob
dataURLtoFile(dataurl, filename) {
//将base64转换为文件
var arr = dataurl.split(","),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {
type: mime,
});
}
},
};
</script>
<style scoped>
#live{
position: absolute;
top: 50%;
left: 50%;
background-color: #000;
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
::v-deep .el-divider--horizontal{
margin: 1vh 0;
}
::v-deep .el-card.is-always-shadow {
box-shadow:none;
}
.barrage{
position: absolute;
width: 500px;
bottom: 6%;
left: 50%;
margin-left:-380px;
z-index: 99999999999999999;
}
::v-deep .el-progress.el-progress--circle{
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
</style>