<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>
标签:canvas,vue,const,null,pc,截屏,video,document,data From: https://www.cnblogs.com/suka/p/17371007.html