import { defineComponent, onMounted, reactive, ref, watch } from 'vue' import styles from './video.module.less' // import poster from './images/video_bg.png' import { Button, Loading } from 'vant' import { browser } from '@/helpers/utils' import Plyr from 'plyr' import 'plyr/dist/plyr.css' import { useInterval, useIntervalFn } from '@vueuse/core' import { useRoute, useRouter } from 'vue-router' import request from '@/helpers/request' import qs from 'query-string' export default defineComponent({ name: 'pre-register', setup() { const route = useRoute() const router = useRouter() const openId = sessionStorage.getItem('active-open-id') // 页面定时 const pageTimer = useInterval(1000, { controls: true }) pageTimer.pause() const forms = reactive({ coverImg: '', introductionVideo: '', videoBrowsePoint: 0, saveId: route.query.saveId, orchestraId: route.query.id, openId: route.query.openId || openId, loading: false, player: null as any, intervalFnRef: null as any }) // 播放视频总时长 const videoIntervalRef = useInterval(1000, { controls: true }) videoIntervalRef.pause() /** * 格式化视屏播放有效时间 - 合并区间 * @param intervals [[], []] * @example [[4, 8],[0, 4],[10, 30]] * @returns [[0, 8], [10, 30]] */ const formatEffectiveTime = (intervals: any[]) => { const res: any = [] intervals.sort((a, b) => a[0] - b[0]) let prev = intervals[0] for (let i = 1; i < intervals.length; i++) { const cur = intervals[i] if (prev[1] >= cur[0]) { // 有重合 prev[1] = Math.max(cur[1], prev[1]) } else { // 不重合,prev推入res数组 res.push(prev) prev = cur // 更新 prev } } res.push(prev) return res } /** * 获取数据有效期 * @param intervals [[], []] * @returns 0s */ const formatTimer = (intervals: any[]) => { const afterIntervals = formatEffectiveTime(intervals) let time = 0 afterIntervals.forEach((t: any) => { time += t[1] - t[0] }) return time } /** * 视屏累计时长 * 1、视屏开始播放时-开始计时 * 2、视频暂停时暂停-停止计时 * 3、视频加载时-停止计时 * 4、视频倍数播放时,时间正常计时 * 5、点击视频进度或拖动进度时,时间暂停 */ const _init = () => { const controls = [ 'play-large', 'play', 'progress', 'captions', 'current-time', 'duration', 'settings', 'fullscreen' ] const params: any = { controls: controls, settings: ['speed'], speed: { selected: 1, options: [0.5, 1, 1.5, 2] }, i18n: { speed: '速度', normal: '默认' }, invertTime: false } if (browser().iPhone) { params.fullscreen = { enabled: true, fallback: 'force', iosNative: true } } forms.player = new Plyr('#register-video', params) forms.player.on('loadedmetadata', () => { console.log('loadedmetadata') forms.loading = false forms.player.currentTime = forms.videoBrowsePoint }) forms.player.on('seeking', (val: any) => { // console.log('seeking') videoIntervalRef.isActive.value && videoIntervalRef.pause() }) // // 拖动结束时 forms.player.on('seeked', (val: any) => { // console.log('seeked') videoIntervalRef.isActive.value && videoIntervalRef.pause() }) // 正在搜索中 forms.player.on('waiting', () => { // console.log('waiting pause') videoIntervalRef.isActive.value && videoIntervalRef.pause() }) // 如何视频在缓存不会触发 forms.player.on('timeupdate', () => { // console.log('timeupdate', forms.player.currentTime) // 判断视频计时器是否暂停,如果暂停则恢复 // 添加 「forms.player.playing」 是由会跳转到上次播放时间,会触发些方法 if ( !videoIntervalRef.isActive.value && forms.player.currentTime > 0 && forms.player.playing ) { console.log('timeupdate play') videoIntervalRef.resume() } }) // 开始播放 forms.player.on('play', () => { console.log('play') // 判断视频计时器是否暂停,如果暂停则恢复 if (!videoIntervalRef.isActive.value) { videoIntervalRef.resume() } }) // 暂停播放 forms.player.on('pause', () => { console.log('pause') videoIntervalRef.pause() }) forms.player.on('enterfullscreen', () => { console.log('fullscreen') const i = document.createElement('i') i.id = 'fullscreen-back' i.className = 'van-icon van-icon-arrow-left video-back' i.addEventListener('click', () => { forms.player.fullscreen.exit() }) console.log(document.getElementsByClassName('plyr')) document.getElementsByClassName('plyr')[0].appendChild(i) }) forms.player.on('exitfullscreen', () => { console.log('exitfullscreen') const i = document.getElementById('fullscreen-back') i && i.remove() }) } // 保存零时时间 const moreTime: any = ref([]) // 多个观看时间段 let tempTime: any = [] // 临时存储时间 const currentTimer = useInterval(1000, { controls: true }) // 监听播放状态, watch( () => videoIntervalRef.isActive.value, (newVal: boolean) => { // console.log(newVal, 'videoIntervalRef.isActive.value in') // console.log('watch', forms.player.currentTime) // console.log('保留两个小数:', forms.player.currentTime.toFixed(2)) // console.log('向下取整:', Math.floor(forms.player.currentTime)) // console.log('向上取整:', Math.ceil(forms.player.currentTime)) // console.log('四舍五入:', Math.round(forms.player.currentTime)) if (newVal) { tempTime[0] = Math.round(forms.player.currentTime) } else { tempTime[1] = Math.round(forms.player.currentTime) } if (tempTime.length >= 2) { // console.log(tempTime, 'tempTime', moreTime.value) // 处理在短时间内的时间差 【视屏拖动,点击可能会导致时间差太大】 const diffTime = tempTime[1] - tempTime[0] - currentTimer.counter.value > 2 // console.log(diffTime, 'diffTime', currentTimer.counter.value, 'value') // 结束时间,如果 大于开始时间则清除 if (tempTime[1] >= tempTime[0] && !diffTime) moreTime.value.push(tempTime) tempTime = [] currentTimer.counter.value = 0 } // console.log('观看的时间', moreTime) } ) // 更新时间 const updateStat = async (pageBrowseTime = 10) => { try { const videoBrowseData = moreTime.value.length > 0 ? formatEffectiveTime(moreTime.value) : [] let time = moreTime.value.length > 0 ? formatTimer(moreTime.value) : 0 const videoCountTime = videoIntervalRef?.counter.value const videoDuration = forms.player.duration // 判断如何视屏播放时间大于视屏播放有效时间则说明数据有问题,进行重置数据 if (time > videoCountTime && time < videoDuration) { time = videoCountTime } const rate = Math.floor((time / Math.floor(videoDuration)) * 100) await request.post('/api-student/open/studentBrowseRecord/updateStat', { data: { id: forms.saveId, pageBrowseTime, // 固定10秒 videoBrowseData: JSON.stringify(videoBrowseData), // 视屏播放数据 videoBrowseDataTime: time || 0, // 视屏观看百分比时长 videoBrowsePercentage: rate || 0, // 视频观看百分比 videoBrowseTime: videoIntervalRef?.counter.value, // 视频观看时长 videoBrowsePoint: Math.floor(forms.player.currentTime || 0) // 视频最后观看点 - 向下取整 } }) } catch { // } } // 提交 const onSubmit = async () => { try { // 暂停回调 forms.intervalFnRef?.pause() currentTimer.pause() // 页面计时暂停 pageTimer.pause() await updateStat() window.location.href = window.location.origin + window.location.pathname + '/project/preRegister.html?' + qs.stringify({ orchestraId: forms.orchestraId, openId: forms.openId }) // window.location.href = // window.location.origin + // '/project/preRegister.html?' + // qs.stringify({ // orchestraId: forms.orchestraId, // openId: forms.openId // }) } catch (e) { console.log(e, 'e') // 还原 forms.intervalFnRef?.resume() pageTimer.resume() currentTimer.resume() } } onMounted(async () => { try { const { data } = await request.get('/api-student//open/studentBrowseRecord/query', { params: { openId: forms.openId, orchestraId: forms.orchestraId } }) forms.videoBrowsePoint = data.videoBrowsePoint || 0 if (forms.player) { forms.player.currentTime = data.videoBrowsePoint || 0 } forms.introductionVideo = data.introductionVideo forms.coverImg = data.coverImg console.log(data) moreTime.value = data.videoBrowseData ? JSON.parse(data.videoBrowseData) : [] _init() // 间隔多少时间同步数据 forms.intervalFnRef = useIntervalFn(async () => { // 页面时间恢复 pageTimer.counter.value = 0 pageTimer.resume() await updateStat() videoIntervalRef.counter.value = 0 }, 10000) } catch { // } }) // 判断是否有openId if (!forms.openId) { router.replace({ path: '/pre-register-video', query: { id: forms.orchestraId } }) } return () => (