|
@@ -9,8 +9,11 @@
|
|
|
<el-col :span="18" style="width:680px;">
|
|
|
|
|
|
<div style="height: 540px;">
|
|
|
- <video-player id="myVideo" class="video-player-box" ref="videoPlayer" :playsinline="true"
|
|
|
- @pause="onPlayerPause($event)" @play="onPlayerStart($event)" @ready="playerReadied"
|
|
|
+ <video-player id="myVideo" class="video-player-box" ref="videoPlayer"
|
|
|
+
|
|
|
+ @pause="onPlayerPause($event)"
|
|
|
+ @play="onPlayerStart($event)"
|
|
|
+ @ready="playerReadied"
|
|
|
@timeupdate="onPlayerTimeupdate($event)" @ended="onPlayerEnded($event)" :globalOptions="{controls:true}"
|
|
|
:options="options">
|
|
|
</video-player>
|
|
@@ -40,7 +43,7 @@
|
|
|
<div class="sub-list" v-if="item.name==activeChapter">
|
|
|
<a v-for="(subItem,index) in list" :key="subItem.id" v-if="subItem.chapterName == activeChapter"
|
|
|
@click="goSubState(subItem, index)" :class="{'current':subItem.name==activeName}">
|
|
|
- <span style="width: 8px;height: 8px;">
|
|
|
+ <span class="media-process">
|
|
|
<el-progress :percentage="subItem.percent" type="circle" :width="16" :height="16"
|
|
|
:format="()=>{return ''}" v-if="subItem.percent>=100" color="green"></el-progress>
|
|
|
<el-progress :percentage="subItem.percent" type="circle" :width="16" :height="16"
|
|
@@ -48,7 +51,7 @@
|
|
|
<el-progress :percentage="subItem.percent" type="circle" :width="16" :height="16"
|
|
|
:format="()=>{return ''}" v-else></el-progress>
|
|
|
</span>
|
|
|
- <span style="margin-left: 16px;"> {{subItem.name}} </span>
|
|
|
+ <span class="media-name"> {{subItem.name}} </span>
|
|
|
</a>
|
|
|
</div>
|
|
|
</li>
|
|
@@ -64,7 +67,7 @@
|
|
|
<div class="sub-list pt10" v-if="item.name==activeChapter">
|
|
|
<a v-for="(subItem,index) in list" :key="subItem.id" v-if="subItem.chapterName == activeChapter"
|
|
|
@click="goSubState(subItem, index)" :class="{'current':subItem.name==activeName}">
|
|
|
- <span style="width: 8px;height: 8px;">
|
|
|
+ <span class="media-process">
|
|
|
<el-progress :percentage="subItem.percent" type="circle" :width="16" :height="16"
|
|
|
:format="()=>{return ''}" v-if="subItem.percent>=100" color="green"></el-progress>
|
|
|
<el-progress :percentage="subItem.percent" type="circle" :width="16" :height="16"
|
|
@@ -72,7 +75,7 @@
|
|
|
<el-progress :percentage="subItem.percent" type="circle" :width="16" :height="16"
|
|
|
:format="()=>{return ''}" v-else></el-progress>
|
|
|
</span>
|
|
|
- <span style="margin-left: 16px;"> {{subItem.name}} </span>
|
|
|
+ <span class="media-name"> {{subItem.name}} </span>
|
|
|
</a>
|
|
|
</a>
|
|
|
</div>
|
|
@@ -88,11 +91,19 @@
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
- <div class="left-float" v-if="!closeFace" v-drag>
|
|
|
- <video ref="video" v-show="isnotbtn" width="240" height="180" autoplay></video>
|
|
|
+ <div class="left-float" v-if="!closeFace" v-drag v-show="identifyFacePass">
|
|
|
+ <video ref="video" width="240" height="180" autoplay></video>
|
|
|
<canvas ref="canvas" v-show="ontakebtn" width="240" height="180"></canvas>
|
|
|
</div>
|
|
|
|
|
|
+ <el-dialog title="人脸认证" center :visible.sync="identifyFace" width="500px" :close-on-click-modal="false">
|
|
|
+ <div style="width: 240px;margin: 100px auto;">
|
|
|
+ <video ref="video2" width="240" height="180" autoplay></video>
|
|
|
+ <p style="margin-top: 20px;">当前照片:</p>
|
|
|
+ <canvas ref="canvas" width="240" height="180"></canvas>
|
|
|
+ <p v-if="errMsg" style="font-size: 30px;color: red;"> {{errMsg}}</p>
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
@@ -121,8 +132,9 @@
|
|
|
tickNum: 0,
|
|
|
prevTime: 0,
|
|
|
isReady: false,
|
|
|
- isnotbtn: false,
|
|
|
ontakebtn: false,
|
|
|
+ identifyFace: false,
|
|
|
+ identifyFacePass: false,
|
|
|
activeChapter: '',
|
|
|
activeName: '',
|
|
|
curTimes: 0,
|
|
@@ -174,7 +186,7 @@
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
this.destroyTimer()
|
|
|
- this.closeCamera()
|
|
|
+ this.closeCamera( "video" )
|
|
|
},
|
|
|
created() {
|
|
|
this.startTick()
|
|
@@ -189,21 +201,24 @@
|
|
|
this.tickNum = 0
|
|
|
this.errMsg = ''
|
|
|
this.errCount = 0
|
|
|
+ this.startIdentify()
|
|
|
this.setposition( this.media.position )
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
- photograph() {
|
|
|
+ photograph( ref ) {
|
|
|
+ let identify = (ref=="video2");
|
|
|
let ctx = this.$refs["canvas"].getContext("2d");
|
|
|
// 把当前视频帧内容渲染到canvas上
|
|
|
this.ontakebtn = true
|
|
|
- ctx.drawImage(this.$refs["video"], 0, 0, 240, 180);
|
|
|
+ ctx.drawImage(this.$refs[ref], 0, 0, 240, 180);
|
|
|
// 转base64格式、图片格式转换、图片质量压缩
|
|
|
let imgBase64 = this.$refs["canvas"].toDataURL("image/jpeg", 1); // 由字节转换为KB 判断大小
|
|
|
this.ontakebtn = false
|
|
|
let str = imgBase64.replace("data:image/jpeg;base64,", "");
|
|
|
let param = {
|
|
|
id: this.media.id,
|
|
|
+ ref,
|
|
|
image: str
|
|
|
}
|
|
|
httpServer("course.collect", param).then(res => {
|
|
@@ -215,9 +230,12 @@
|
|
|
if (msg) {
|
|
|
this.errCount++
|
|
|
} else {
|
|
|
+ if( identify ){
|
|
|
+ this.identifyPassAndPlay()
|
|
|
+ }
|
|
|
this.errCount = 0;
|
|
|
}
|
|
|
- if (this.errCount > this.maxErrorCount) {
|
|
|
+ if (!identify && this.errCount > this.maxErrorCount) {
|
|
|
this.doPause();
|
|
|
}
|
|
|
})
|
|
@@ -235,10 +253,10 @@
|
|
|
goSubState(item, index) {
|
|
|
this.$emit('loadMedia', item, index)
|
|
|
},
|
|
|
- callCamera() {
|
|
|
+ callCamera( ref ) {
|
|
|
// H5调用电脑摄像头API
|
|
|
if (this.closeFace) {
|
|
|
- this.isnotbtn = true;
|
|
|
+ this.identifyFacePass = true;
|
|
|
return;
|
|
|
}
|
|
|
navigator.mediaDevices
|
|
@@ -246,31 +264,27 @@
|
|
|
video: true,
|
|
|
})
|
|
|
.then((success) => {
|
|
|
- this.isnotbtn = true
|
|
|
// 摄像头开启成功
|
|
|
- this.$refs["video"].srcObject = success;
|
|
|
+ this.$refs[ref].srcObject = success;
|
|
|
// 实时拍照效果
|
|
|
- this.$refs["video"].play();
|
|
|
+ this.$refs[ref].play();
|
|
|
})
|
|
|
.catch((error) => {
|
|
|
this.$message.error(
|
|
|
"摄像头开启失败,请检查摄像头是否可用!或者打开摄影头"
|
|
|
);
|
|
|
- this.isnotbtn = false
|
|
|
console.error("摄像头开启失败,请检查摄像头是否可用!");
|
|
|
});
|
|
|
},
|
|
|
- closeCamera() {
|
|
|
- console.log("closeCamera")
|
|
|
- if (!this.$refs["video"]) return;
|
|
|
- if (!this.$refs["video"].srcObject) return;
|
|
|
- let stream = this.$refs["video"].srcObject;
|
|
|
+ closeCamera( ref ) {
|
|
|
+ if (!this.$refs[ref]) return;
|
|
|
+ if (!this.$refs[ref].srcObject) return;
|
|
|
+ let stream = this.$refs[ref].srcObject;
|
|
|
let tracks = stream.getTracks();
|
|
|
tracks.forEach((track) => {
|
|
|
track.stop();
|
|
|
});
|
|
|
- this.$refs["video"].srcObject = null;
|
|
|
- this.isnotbtn = false
|
|
|
+ this.$refs[ref].srcObject = null;
|
|
|
},
|
|
|
startTick() {
|
|
|
let tick = this.tryTick;
|
|
@@ -302,7 +316,6 @@
|
|
|
this.setposition(position)
|
|
|
}, 2000)
|
|
|
}
|
|
|
- this.callCamera()
|
|
|
this.isReady = true
|
|
|
},
|
|
|
onPlayerTimeupdate(player) {
|
|
@@ -328,7 +341,6 @@
|
|
|
},
|
|
|
onPlayerPause(event) {
|
|
|
this.reportErr("play", 'pause');
|
|
|
- this.stopTick()
|
|
|
this.onPlay = false
|
|
|
},
|
|
|
onPlayerEnded(event) {
|
|
@@ -339,10 +351,9 @@
|
|
|
this.reportErr("play", 'close')
|
|
|
this.doPause()
|
|
|
this.$emit("close")
|
|
|
- this.closeCamera()
|
|
|
+ this.closeCamera( "video")
|
|
|
},
|
|
|
doPause() {
|
|
|
- this.stopTick()
|
|
|
this.onPlay = false
|
|
|
let myPlayer = this.$refs.videoPlayer.player;
|
|
|
myPlayer && myPlayer.pause()
|
|
@@ -356,12 +367,29 @@
|
|
|
myPlayer && myPlayer.play()
|
|
|
this.tickNum = 0
|
|
|
},
|
|
|
- onPlayerStart() {
|
|
|
+ onPlayerStart( player ) {
|
|
|
console.log("onPlayerStart")
|
|
|
+ this.onPlay = true
|
|
|
+ if( !this.identifyFacePass){
|
|
|
+ this.startIdentify()
|
|
|
+ }
|
|
|
this.reportErr("play", 'start');
|
|
|
this.startTick();
|
|
|
- this.onPlay = true
|
|
|
- this.tickNum = 0
|
|
|
+ },
|
|
|
+ startIdentify(){
|
|
|
+ this.identifyFace = true
|
|
|
+ this.identifyFacePass = false
|
|
|
+ this.closeCamera("video")
|
|
|
+ this.callCamera("video2")
|
|
|
+ this.startTick()
|
|
|
+ },
|
|
|
+ identifyPassAndPlay(){
|
|
|
+ this.identifyFacePass = true
|
|
|
+ this.identifyFace = false;
|
|
|
+ this.closeCamera("video2")
|
|
|
+ this.callCamera("video")
|
|
|
+ this.$message.successMsg("人脸认证通过", 2)
|
|
|
+ this.doPlay()
|
|
|
},
|
|
|
reportErr(action, msg) {
|
|
|
httpServer("course.report", {
|
|
@@ -385,6 +413,21 @@
|
|
|
tick(force = false) {
|
|
|
let media = this.media;
|
|
|
this.tickNum++
|
|
|
+ // 人脸认证期间
|
|
|
+ if( !this.identifyFacePass ) {
|
|
|
+ if( this.onPlay ){
|
|
|
+ this.doPause()
|
|
|
+ }
|
|
|
+ // 人脸认证
|
|
|
+ if (this.tickNum % 3 == 1) {
|
|
|
+ this.photograph( "video2" );
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ };
|
|
|
+ // 未开始
|
|
|
+ if(!force && !this.onPlay ){
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
// 已经完成
|
|
|
if (this.media.isFinish) {
|
|
@@ -395,12 +438,12 @@
|
|
|
if (this.tickNum % this.heartbeat != 0) {
|
|
|
return;
|
|
|
}
|
|
|
- if (!this.isnotbtn && !this.media.isFinish && this.onPlay && !this.closeFace) {
|
|
|
- console.log(this.isnotbtn , this.media.isFinish , this.onPlay, this.closeFace)
|
|
|
- this.$message.errorMsg("需要安装摄像头才能学习", 2);
|
|
|
- this.doPause()
|
|
|
- return;
|
|
|
- }
|
|
|
+ // if (!this.media.isFinish && this.onPlay && !this.closeFace) {
|
|
|
+ // console.log(this.identifyFacePass , this.media.isFinish , this.onPlay, this.closeFace)
|
|
|
+ // this.$message.errorMsg("需要安装摄像头才能学习", 2);
|
|
|
+ // this.doPause()
|
|
|
+ // return;
|
|
|
+ // }
|
|
|
if (this.errCount >= this.maxErrorCount) {
|
|
|
this.$message.errorMsg("人脸不在摄像头上", 5);
|
|
|
this.destroyTimer()
|
|
@@ -412,9 +455,9 @@
|
|
|
// 异常 10秒检查
|
|
|
if (!this.closeFace) {
|
|
|
if (this.errCount > 0) {
|
|
|
- this.photograph()
|
|
|
+ this.photograph("video")
|
|
|
} else if (heartBeat % this.collectBeat == 1) {
|
|
|
- this.photograph()
|
|
|
+ this.photograph("video")
|
|
|
}
|
|
|
}
|
|
|
// 主动暂停
|
|
@@ -545,4 +588,22 @@
|
|
|
width: 80px;
|
|
|
padding: -4px auto !important;
|
|
|
}
|
|
|
+ .media-name {
|
|
|
+ width: 300px;
|
|
|
+ margin-left: 16px;
|
|
|
+ line-height: 24px;
|
|
|
+ white-space: nowrap;
|
|
|
+ overflow: hidden;
|
|
|
+ align-items: center;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ }
|
|
|
+ .media-process{
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .vjs-tech {
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
</style>
|