488 lines
14 KiB
Vue
488 lines
14 KiB
Vue
<template>
|
|
<view>
|
|
<div class="video">
|
|
<video
|
|
id="myVideo"
|
|
:src="path"
|
|
:initial-time="studyDuration"
|
|
:show-progress="true"
|
|
:enable-progress-gesture="false"
|
|
@error="videoErrorCallback"
|
|
@timeupdate="videoTimeUpdate"
|
|
controls
|
|
>
|
|
<cover-view v-if="showCoverView" style="position: absolute; top: 28px; right: 20px; color: #fff; z-index: 999">
|
|
<cover-view @tap="showSwitchRate">x {{ currentRate }}</cover-view>
|
|
</cover-view>
|
|
<cover-view v-if="rateShow" class="multi-list rate" :class="{ active: rateShow }">
|
|
<cover-view
|
|
v-for="(item, index) in ['0.5', '0.8', '1.0', '1.25', '1.5']"
|
|
:key="index"
|
|
class="multi-item rate"
|
|
:data-rate="item"
|
|
@tap="switchRate"
|
|
:class="{ active: item == currentRate }"
|
|
>
|
|
{{ item }}
|
|
</cover-view>
|
|
</cover-view>
|
|
</video>
|
|
</div>
|
|
|
|
<u-modal
|
|
:show="showModal"
|
|
title="提示"
|
|
:content="content"
|
|
showCancelButton
|
|
@cancel="showModal = false"
|
|
@confirm="handleEnd"
|
|
/>
|
|
|
|
<div class="btn">
|
|
<u-button type="primary" shape="circle" text="结束学习" size="small" @click="openModal" />
|
|
</div>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import config from '@/config'
|
|
import { pathToBase64, base64ToPath } from 'image-tools'
|
|
import { updStudyDuration } from '@/api/eduApp'
|
|
|
|
export default {
|
|
props: {
|
|
states: {
|
|
type: Object,
|
|
default: () => {}
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
isLoading: false,
|
|
studyId: '', // 学习id
|
|
stageId: '', // 阶段id
|
|
stageContentId: '', // 阶段内容id
|
|
stageType: '', // 阶段类型
|
|
studyCourseId: '',
|
|
sourceId: '', // 素材id
|
|
path: '', // 视频路径
|
|
studyDuration: 0, // 学习时长
|
|
allStudyDuration: 0, // 总时长
|
|
studyPercentage: 0, // 学习进度
|
|
// 当前播放时间
|
|
currentTime: 0,
|
|
showModal: false, // 是否显示弹窗
|
|
content: '是否确认结束学习?',
|
|
isEnd: false,
|
|
video: null,
|
|
isHide: false,
|
|
oldTime: 0,
|
|
random1: 0,
|
|
random2: 0,
|
|
// 失败次数
|
|
failCount: 3,
|
|
rateShow: false, // 倍速浮层
|
|
currentRate: 1.0, // 默认倍速
|
|
showCoverView: true
|
|
}
|
|
},
|
|
onLoad(opt) {
|
|
opt = JSON.parse(opt.params)
|
|
console.log('🚀 ~ onLoad ~ :', opt)
|
|
this.studyId = opt.studyId
|
|
this.stageId = opt.stageId
|
|
this.stageContentId = opt.stageContentId
|
|
this.stageType = opt.stageType
|
|
this.studyCourseId = opt.studyCourseId
|
|
this.sourceId = opt.sourceId
|
|
this.path = config.fileUrl + opt.path
|
|
console.log('🚀 ~ onLoad ~ this.path:', this.path)
|
|
this.studyDuration = this.currentTime = Number(opt.studyDuration)
|
|
this.allStudyDuration = Number(opt.allStudyDuration)
|
|
this.random1 = ((this.allStudyDuration / 3) * 2).toFixed(0)
|
|
this.random2 = (this.allStudyDuration / 3).toFixed(0)
|
|
this.isEnd = opt.isEnd
|
|
if (opt.isEnd) {
|
|
this.studyDuration = 0
|
|
}
|
|
// if (opt.isSpeed == '0') {
|
|
// this.showCoverView = false
|
|
// }
|
|
this.video = uni.createVideoContext('myVideo')
|
|
this.$verificationToken()
|
|
},
|
|
onHide() {
|
|
console.log('🚀 ~ onHide ~ 页面隐藏')
|
|
if (!this.isEnd && !this.isHide) {
|
|
// 关闭页面时,修改项目进度
|
|
this.handleEnd()
|
|
}
|
|
},
|
|
onUnload() {
|
|
console.log('🚀 ~ onUnload ~ 页面关闭')
|
|
if (!this.isEnd && !this.isHide) {
|
|
// 关闭页面时,修改项目进度
|
|
this.handleEnd()
|
|
}
|
|
},
|
|
methods: {
|
|
onFullScreenChange(e) {
|
|
console.log('🚀 ~ 全屏了 ~ e:', e)
|
|
},
|
|
// 显示倍速浮层
|
|
showSwitchRate(rate) {
|
|
let that = this
|
|
console.log('rateShow', rate)
|
|
this.$nextTick(() => {
|
|
that.rateShow = true
|
|
})
|
|
console.log('rateShow', that.rateShow)
|
|
},
|
|
// 切换倍速
|
|
switchRate(e) {
|
|
let that = this
|
|
let rate = Number(e.currentTarget.dataset.rate)
|
|
that.currentRate = rate
|
|
that.rateShow = false
|
|
this.video.playbackRate(rate)
|
|
},
|
|
videoErrorCallback(e) {
|
|
console.log('视频错误信息:', e)
|
|
},
|
|
// 视频播放时间更新
|
|
videoTimeUpdate(e) {
|
|
let newTime = Math.ceil(e.detail.currentTime)
|
|
if (Math.abs(newTime - this.currentTime) > 1.5) {
|
|
console.log('手动拖动了进度条,重置时间到上传播放时间')
|
|
this.video.seek(this.currentTime)
|
|
return
|
|
}
|
|
if (this.currentTime != newTime) {
|
|
this.currentTime = newTime
|
|
console.log('🚀 ~ 视频播放时间: ~ this.currentTime:', this.currentTime)
|
|
// 每隔 10 秒记录一次学习时长
|
|
if (this.currentTime % 10 == 0) {
|
|
console.log('学习时长:', this.currentTime)
|
|
this.updStudyDuration()
|
|
}
|
|
}
|
|
// console.log('🚀 ~ 视频总时长: ~ this.allStudyDuration:', this.allStudyDuration)
|
|
// console.log('🚀 ~ videoTimeUpdate ~ random1-2:', this.random1, this.random2)
|
|
|
|
if (this.currentTime != this.oldTime) {
|
|
if (this.currentTime == this.random2 || this.currentTime == this.random1) {
|
|
this.oldTime = this.currentTime // 记录当前时间, 防止重复执行, 如果下次时间等于当前时间, 则不执行
|
|
this.video.pause()
|
|
this.openPhotograph()
|
|
}
|
|
}
|
|
},
|
|
openModal() {
|
|
this.showModal = true
|
|
},
|
|
// 结束学习
|
|
handleEnd() {
|
|
if (this.isLoading) {
|
|
uni.showToast({
|
|
title: '正在提交中, 请稍后',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
this.isLoading = true
|
|
if (this.isEnd) {
|
|
uni.reLaunch({
|
|
url: '/pages/YNEduApp/learnProj/learnProjDetail?id=' + this.studyId
|
|
})
|
|
return
|
|
}
|
|
// 手动暂停视频
|
|
this.video.pause()
|
|
// 获取当前播放时间 - 用于记录学习时长
|
|
console.log('当前播放时间:', this.currentTime, this.allStudyDuration)
|
|
this.studyDuration = this.currentTime
|
|
// 计算学习进度
|
|
this.studyPercentage =
|
|
Math.ceil((this.studyDuration / this.allStudyDuration) * 100).toFixed(2) > 100
|
|
? 100
|
|
: Math.ceil((this.studyDuration / this.allStudyDuration) * 100).toFixed(2)
|
|
console.log('🚀 ~ handleEnd ~ this.studyPercentage:', this.studyPercentage)
|
|
|
|
const params = {
|
|
userId: uni.getStorageSync('userId'),
|
|
studyId: this.studyId,
|
|
stageId: this.stageId,
|
|
stageContentId: this.stageContentId,
|
|
stageType: this.stageType,
|
|
studyCourseId: this.studyCourseId,
|
|
sourceId: this.sourceId,
|
|
studyDuration: this.studyDuration,
|
|
studyPercentage: this.studyPercentage
|
|
}
|
|
console.log('🚀 ~ handleEnd ~ params:', params)
|
|
// updStudyDuration(params).then(res => {
|
|
// console.log('🚀 ~ handleEnd ~ res:', res)
|
|
// this.showModal = false
|
|
// uni.navigateBack()
|
|
// })
|
|
this.$verificationToken()
|
|
uni.request({
|
|
url: config.baseUrl + '/exam-student/student/updStudyDuration',
|
|
method: 'post',
|
|
data: params,
|
|
header: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
Authorization: uni.getStorageSync('access_token')
|
|
},
|
|
success: res => {
|
|
console.log('🚀 ~ handleEnd ~ res:', res)
|
|
uni.reLaunch({
|
|
url: '/pages/YNEduApp/learnProj/learnProjDetail?id=' + this.studyId
|
|
})
|
|
setTimeout(() => {
|
|
this.isLoading = false
|
|
this.showModal = false
|
|
}, 1000)
|
|
},
|
|
fail: err => {
|
|
console.log('🚀 ~ handleEnd ~ err:', err)
|
|
}
|
|
})
|
|
},
|
|
// 更新学习进度
|
|
updStudyDuration() {
|
|
// 获取当前播放时间 - 用于记录学习时长
|
|
console.log('当前播放时间:', this.currentTime, this.allStudyDuration)
|
|
this.studyDuration = this.currentTime
|
|
// 计算学习进度
|
|
this.studyPercentage =
|
|
Math.ceil((this.studyDuration / this.allStudyDuration) * 100).toFixed(2) > 100
|
|
? 100
|
|
: Math.ceil((this.studyDuration / this.allStudyDuration) * 100).toFixed(2)
|
|
console.log('🚀 ~ handleEnd ~ this.studyPercentage:', this.studyPercentage)
|
|
|
|
const params = {
|
|
userId: uni.getStorageSync('userId'),
|
|
studyId: this.studyId,
|
|
stageId: this.stageId,
|
|
stageContentId: this.stageContentId,
|
|
stageType: this.stageType,
|
|
studyCourseId: this.studyCourseId,
|
|
sourceId: this.sourceId,
|
|
studyDuration: this.studyDuration,
|
|
studyPercentage: this.studyPercentage
|
|
}
|
|
console.log('🚀 ~ handleEnd ~ params:', params)
|
|
|
|
this.$verificationToken()
|
|
uni.request({
|
|
url: config.baseUrl + '/exam-student/student/updStudyDuration',
|
|
method: 'post',
|
|
data: params,
|
|
header: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
Authorization: uni.getStorageSync('access_token')
|
|
},
|
|
success: res => {
|
|
console.log('🚀 ~ handleEnd ~ res:', res)
|
|
},
|
|
fail: err => {
|
|
console.log('🚀 ~ handleEnd ~ err:', err)
|
|
}
|
|
})
|
|
},
|
|
// 拍照录入
|
|
openPhotograph() {
|
|
this.isHide = true
|
|
const that = this
|
|
setTimeout(() => {
|
|
uni.chooseImage({
|
|
count: 1,
|
|
sizeType: ['compressed'],
|
|
sourceType: ['camera'],
|
|
success: res => {
|
|
let url = ''
|
|
console.log('🚀 ~ res-拍照:', res)
|
|
this.imgToBase64(res.tempFilePaths[0]).then(base64 => {
|
|
url = base64
|
|
console.log('🚀 ~ this.imgToBase64 ~ base64:', url)
|
|
this.getFaceRecognition({ userId: uni.getStorageSync('userId'), img: url })
|
|
})
|
|
},
|
|
fail(err) {
|
|
console.log('🚀 ~ openFaceScan ~ 人脸识别失败', err)
|
|
uni.showToast({
|
|
title: '人脸识别失败',
|
|
icon: ''
|
|
})
|
|
setTimeout(() => {
|
|
that.handleEnd()
|
|
}, 1000)
|
|
}
|
|
})
|
|
}, 100)
|
|
},
|
|
imgToBase64(data) {
|
|
return new Promise((resolve, reject) => {
|
|
pathToBase64(data)
|
|
.then(base64 => {
|
|
resolve(base64)
|
|
})
|
|
.catch(error => {
|
|
console.error(error)
|
|
reject(error)
|
|
})
|
|
})
|
|
},
|
|
// 发送请求
|
|
getFaceRecognition(params) {
|
|
console.log('🚀 ~ getFaceRecognition ~ params:', params)
|
|
const that = this
|
|
this.$verificationToken()
|
|
uni.request({
|
|
url: config.baseUrl + '/exam-student/personalCenter/getFaceRecognition',
|
|
method: 'POST',
|
|
header: {
|
|
'content-type': 'application/json',
|
|
Authorization: uni.getStorageSync('access_token')
|
|
},
|
|
data: params,
|
|
success: res => {
|
|
console.log('🚀 ~ openFaceScan ~ res-人脸识别:', res)
|
|
res = res.data
|
|
console.log('🚀 ~ openFaceScan ~ res-人脸识别:', res.code)
|
|
if (res.code == 200) {
|
|
// 提示
|
|
uni.showToast({
|
|
title: '人脸识别成功',
|
|
icon: '',
|
|
duration: 1500
|
|
})
|
|
this.failCount = 3 // 重置次数
|
|
this.isHide = false
|
|
this.video.play()
|
|
} else {
|
|
this.failCount--
|
|
if (this.failCount == 0) {
|
|
uni.showToast({
|
|
title: '人脸识别失败, 即将结束学习',
|
|
icon: 'none',
|
|
duration: 1500
|
|
})
|
|
setTimeout(() => {
|
|
this.handleEnd()
|
|
}, 1500)
|
|
} else {
|
|
uni.showToast({
|
|
title: '人脸识别失败, 请重新录入, 剩余次数: ' + this.failCount + '次',
|
|
icon: 'none',
|
|
duration: 1500
|
|
})
|
|
setTimeout(() => {
|
|
this.openPhotograph()
|
|
}, 1500)
|
|
}
|
|
}
|
|
},
|
|
fail(err) {
|
|
console.log('🚀 ~ openFaceScan ~ 人脸识别失败', err)
|
|
uni.showToast({
|
|
title: '人脸识别失败',
|
|
icon: ''
|
|
})
|
|
setTimeout(() => {
|
|
that.handleEnd()
|
|
}, 1000)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
// 隐藏 video 默认控制条
|
|
/* ::v-deep .uni-video-controls {
|
|
display: none !important;
|
|
} */
|
|
|
|
.video {
|
|
width: 100%;
|
|
height: 260px;
|
|
uni-video {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
.btn {
|
|
margin: 60px auto;
|
|
width: 100px;
|
|
}
|
|
|
|
/* ::v-deep .u-modal__content {
|
|
flex-direction: column;
|
|
align-items: center;
|
|
} */
|
|
|
|
/* 以下为视频CSS */
|
|
.multi-list.full-screen.vertical {
|
|
height: 100vh !important;
|
|
padding: 8vh 0;
|
|
}
|
|
.multi-list.full-screen.horizontal {
|
|
height: 100vw !important;
|
|
padding: 8vw 0;
|
|
}
|
|
.multi {
|
|
position: absolute;
|
|
right: 30rpx;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
z-index: 999998;
|
|
width: 100rpx;
|
|
color: #fff;
|
|
padding: 10rpx 0;
|
|
text-align: center;
|
|
transition: color ease 0.3s;
|
|
}
|
|
.multi.rate {
|
|
right: 30rpx;
|
|
}
|
|
.multi-list {
|
|
position: absolute;
|
|
height: 100%;
|
|
width: 0;
|
|
background-color: rgba(0, 0, 0, 0.65);
|
|
top: 0;
|
|
right: 0;
|
|
transition: width 0.3s ease;
|
|
z-index: 999999;
|
|
box-sizing: border-box;
|
|
padding: 50rpx 0;
|
|
}
|
|
.multi-list.rate {
|
|
padding: 25rpx 0;
|
|
}
|
|
.multi-list.active {
|
|
width: 300rpx;
|
|
}
|
|
.multi-item {
|
|
text-align: center;
|
|
color: #fff;
|
|
line-height: 80rpx;
|
|
transition: color ease 0.3s;
|
|
}
|
|
.multi-item.rate {
|
|
line-height: 70rpx;
|
|
}
|
|
.multi-item:hover,
|
|
.multi:hover {
|
|
color: #00d8ff;
|
|
}
|
|
.multi-item.active {
|
|
color: #00d8ff;
|
|
}
|
|
/* 视频CSS结束 */
|
|
</style>
|