import { defineComponent, onMounted, onUnmounted, reactive, ref, watch } from 'vue' import styles from './video.module.less' import { Button, Loading } from 'vant' import { browser } from '@/helpers/utils' // import Plyr from 'plyr' // import 'plyr/dist/plyr.css' import TCPlayer from 'tcplayer.js' import 'tcplayer.js/dist/tcplayer.css' import { useInterval, useIntervalFn } from '@vueuse/core' import { useRoute, useRouter } from 'vue-router' import request from '@/helpers/request' import qs from 'query-string' import { usePageVisibility } from '@vant/use' import deepClone from '@/helpers/deep-clone' export default defineComponent({ name: 'pre-register', setup() { const route = useRoute() const router = useRouter() const pageVisibility = usePageVisibility() const openId = sessionStorage.getItem('active-open-id') // 页面定时 const pageTimer = useInterval(1000, { controls: true }) pageTimer.pause() const forms = reactive({ videoID: 'video' + Date.now() + Math.floor(Math.random() * 100), coverImg: '', introductionVideo: '', introductionVideoTime: 0, // 视频总时长 videoBrowsePoint: 0, // 视频最后观看点 saveId: route.query.saveId, orchestraId: route.query.id, openId: route.query.openId || openId, loading: false, player: null as any, playerSpeed: 1, intervalFnRef: null as any, videoDetails: [] as any, // 节点列表 pointVideo: {} as any, // 需要处理有效的时间段 pointVideoTime: 0, // 有效时长 videoSelectId: null, // 选中的编号 isPageHide: false, // 处理页面返回没有刷新的问题 parentConferencesNotes: '', orchestraRegisterType: '', status: '', registerDisplay: true }) // 播放视频总时长 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) // console.log(res, 'formatEffectiveTime') return formatEffectiveTimeToAfter(res) } const formatEffectiveTimeToAfter = (res: any[]) => { // 格式化有效时间 const effective: any = [] const startNode = forms.pointVideo.startNode const endNode = forms.pointVideo.endNode // console.log(startNode, endNode, 'startNode') res.forEach((item: any) => { // 开始时间大于 设置时间 if (item[1] >= item[0]) { /** * 1、开始时间 */ if (item[0] >= startNode && item[0] <= endNode && item[1] <= endNode) { // console.log(1) effective.push(item) } if (item[0] >= startNode && item[0] <= endNode && item[1] > endNode) { // console.log(3) effective.push([item[0], endNode]) } if (item[0] < startNode && item[1] > startNode && item[1] <= endNode) { // console.log(4) effective.push([startNode, item[1]]) } if (item[0] < startNode && item[1] > startNode && item[1] > endNode) { // console.log(4) effective.push([startNode, endNode]) } } }) // console.log(effective, 'effective') return effective } /** * 获取数据有效期 * @param intervals [[], []] * @returns 0s */ const formatTimer = (intervals: any[]) => { const afterIntervals = formatEffectiveTime(intervals) // console.log(afterIntervals, 'afterIntervals') let time = 0 afterIntervals.forEach((t: any) => { time += t[1] - t[0] }) return time } const checkVideoDetails = (time: number) => { let status = false forms.videoDetails.forEach((item: any) => { if (item.startNode <= time && time <= item.endNode) { forms.videoSelectId = item.id status = true } }) if(!status) { forms.videoSelectId = null } } /** * 视屏累计时长 * 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: '默认' // }, // autoplay: false, // invertTime: false // } // if (browser().iPhone) { // params.fullscreen = { // enabled: true, // fallback: 'force', // iosNative: true // } // } // const times: any = [] // forms.videoDetails.forEach((item: any) => { // times.push({ // time: item.startNode, // label: item.desc // }) // }) // params.markers = { enabled: true, points: times } // forms.player = new Plyr('#register-video', params) // forms.player.on('ready', (item: any) => { // console.log('ready', item) // // forms.player.pause() // }) // forms.player.on('loadedmetadata', () => { // console.log('loadedmetadata') // forms.loading = false // forms.player.currentTime() = forms.videoBrowsePoint // checkVideoDetails(forms.player.currentTime()) // }) // // 速度变化时 // forms.player.on('ratechange', () => { // forms.playerSpeed = // forms.playerSpeed < forms.player.speed ? forms.player.speed : forms.playerSpeed // }) // forms.player.on('seeking', () => { // console.log('seeking') // videoIntervalRef.isActive.value && videoIntervalRef.pause() // }) // // // 拖动结束时 // forms.player.on('seeked', () => { // 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()) // // 时间变化时更新每一段的状态 // checkVideoDetails(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('ended', () => { // forms.player.pause() // }) // // 开始播放 // forms.player.on('play', () => { // console.log('play') // // 判断视频计时器是否暂停,如果暂停则恢复 // videoIntervalRef.resume() // }) // // 暂停播放 // forms.player.on('pause', () => { // console.log('pause', videoIntervalRef.isActive.value) // 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 Button = TCPlayer.getComponent('Button') const BigPlayButton = TCPlayer.getComponent('BigPlayButton') BigPlayButton.prototype.createEl = function () { const el = Button.prototype.createEl.call(this) const _html = '' el.appendChild( TCPlayer.dom.createEl('div', { className: 'vjs-button-icon', innerHTML: _html }) ) return el } forms.player = TCPlayer('register-video', { appID: '', controls: true, plugins: { // ProgressMarker: { // markers: [ // { // content: '1111', // timeOffset: 1000 // } // ] // } } }) // player-container-id 为播放器容器 ID,必须与 html 中一致 if (forms.player) { forms.player.src(forms.introductionVideo) // url 播放地址 forms.player.poster(forms.coverImg || '') // forms.player.on('loadstart', () => {}) forms.player.on('ready', (item: any) => { // console.log('ready', item) // forms.player.pause() }) forms.player.on('loadedmetadata', () => { // console.log('loadedmetadata') forms.loading = false forms.player.currentTime(forms.videoBrowsePoint) checkVideoDetails(forms.player.currentTime()) }) // 速度变化时 forms.player.on('ratechange', () => { forms.playerSpeed = forms.playerSpeed < forms.player.playbackRate() ? forms.player.playbackRate() : forms.playerSpeed }) forms.player.on('seeking', () => { // console.log('seeking') videoIntervalRef.isActive.value && videoIntervalRef.pause() }) // // 拖动结束时 forms.player.on('seeked', () => { // 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()) // 时间变化时更新每一段的状态 checkVideoDetails(forms.player.currentTime()) // 判断视频计时器是否暂停,如果暂停则恢复 // 添加 「forms.player.playing」 是由会跳转到上次播放时间,会触发些方法 if ( !videoIntervalRef.isActive.value && forms.player.currentTime() > 0 && !forms.player.paused() ) { // console.log('timeupdate play') videoIntervalRef.resume() } }) // 视屏播放时暂停 forms.player.on('ended', () => { forms.player.pause() }) // 开始播放 forms.player.on('play', () => { console.log('play') // 判断视频计时器是否暂停,如果暂停则恢复 videoIntervalRef.resume() }) // 暂停播放 forms.player.on('pause', () => { console.log('pause', videoIntervalRef.isActive.value) videoIntervalRef.pause() }) forms.player.on('fullscreenchange', () => { if (forms.player.isFullscreen()) { 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.exitFullscreen() }) document.getElementsByClassName('video-js')[0].appendChild(i) } else { console.log('exitfullscreen') const i = document.getElementById('fullscreen-back') i && i.remove() } }) } checkVideoDetails(0) } // 保存零时时间 const moreTime: any = ref([]) // 多个观看时间段 let tempTime: any = [] // 临时存储时间 const currentTimer = useInterval(1000, { controls: true }) // 监听播放状态, watch( () => videoIntervalRef.isActive.value, (newVal: boolean) => { // console.log(videoIntervalRef.isActive.value, 'videoIntervalRef') initVideoCount(newVal) } ) /** * 初始化视频时长 * @param newVal 播放状态 * @param repeat 是否为定时发送的 */ const initVideoCount = (newVal: any, repeat = false) => { // console.log('watch', forms.player.currentTime) const initTime = deepClone(tempTime) if (repeat) { if (tempTime.length > 0) { // console.log('join video', tempTime, 'initTime', initTime) tempTime[1] = Math.floor(forms.player.currentTime()) } } else { if (newVal) { tempTime[0] = Math.floor(forms.player.currentTime()) } else { tempTime[1] = Math.floor(forms.player.currentTime()) } } // console.log(newVal, repeat, tempTime, tempTime.length, 'videoIntervalRef.isActive.value in') // console.log(forms.player.speed, 'speed') if (tempTime.length >= 2) { // console.log(tempTime, 'tempTime', moreTime.value) // 处理在短时间内的时间差 【视屏拖动,点击可能会导致时间差太大】 const diffTime = tempTime[1] - tempTime[0] - currentTimer.counter.value * forms.playerSpeed > 2 // console.log(diffTime, 'diffTime', currentTimer.counter.value, forms.playerSpeed, 'value') // 结束时间,如果 大于开始时间则清除 if (tempTime[1] >= tempTime[0] && !diffTime) moreTime.value.push(tempTime) if (repeat) { tempTime = deepClone(initTime) } else { tempTime = [] currentTimer.counter.value = 0 } } // console.log('观看的时间', moreTime) } watch(pageVisibility, (value: any) => { console.log('watch', value) if (value == 'hidden') { forms.player.pause() } }) // 更新时间 const updateStat = async (pageBrowseTime = 10) => { try { const videoBrowseData = moreTime.value.length > 0 ? formatEffectiveTime(moreTime.value) : [] // console.log(moreTime.value, videoBrowseData, 'video') const time = videoBrowseData.length > 0 ? formatTimer(videoBrowseData) : 0 // const videoCountTime = videoIntervalRef?.counter.value // 判断如何视屏播放时间大于视屏播放有效时间则说明数据有问题,进行重置数据 const rate = Math.floor((time / Math.floor(forms.pointVideoTime)) * 100) // console.log('videoIntervalRef?.counter.value', videoIntervalRef?.counter.value) 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.player.pause() // 视屏 forms.intervalFnRef?.pause() // 页面订时器 currentTimer.pause() videoIntervalRef.pause() // 页面计时暂停 pageTimer.pause() initVideoCount(videoIntervalRef.isActive.value) await updateStat() console.log(forms.orchestraRegisterType) if (forms.orchestraRegisterType === 'PARENT_CONFERENCES') { window.location.href = window.location.origin + window.location.pathname + `/#/preApply?id=${forms.orchestraId}` } else if (forms.orchestraRegisterType === 'GROUP_BUY') { window.location.href = window.location.origin + window.location.pathname + `/#/preGoodsApply?id=${forms.orchestraId}` } else { window.location.href = window.location.origin + window.location.pathname + '/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.introductionVideoTime = data.introductionVideoTime forms.coverImg = data.coverImg moreTime.value = data.videoBrowseData ? JSON.parse(data.videoBrowseData) : [] forms.parentConferencesNotes = data.parentConferencesNotes forms.orchestraRegisterType = data.orchestraRegisterType forms.registerDisplay = data.registerDisplay const videoDetails = data.videoDetails || [] videoDetails.forEach((video: any) => { forms.videoDetails.push({ startNode: video.startNode, endNode: video.endNode, desc: video.desc, id: video.id }) if (video.pointFlag) { forms.pointVideo = video forms.pointVideoTime = video.endNode - video.startNode } }) _init() // 间隔多少时间同步数据 forms.intervalFnRef = useIntervalFn(async () => { // 页面时间恢复 pageTimer.counter.value = 0 pageTimer.resume() // 同步数据时先进行有效时间进行保存 initVideoCount(false, true) await updateStat() videoIntervalRef.counter.value = 0 }, 10000) // const arr = [ // [10, 10], // [53, 53], // [64, 64], // [74, 74], // [155, 155], // [173, 173], // [183, 183], // [191, 201] // ] // console.log(formatEffectiveTime(arr)) } catch { // } }) onUnmounted(() => { forms.player?.fullscreen?.exit() forms.intervalFnRef?.pause() currentTimer.pause() // 页面计时暂停 pageTimer.pause() forms.player?.pause(); forms.player?.src(''); forms.player?.dispose(); }) // 判断是否有openId if (!forms.openId) { router.replace({ path: '/pre-register-video', query: { id: forms.orchestraId } }) } const onPageShow = () => { // console.log(forms.isPageHide, 'showInfo') if (forms.isPageHide) { window.location.reload() } } // 处理监听页面返回不刷新的问题 window.addEventListener('pageshow', onPageShow) const onPageHide = () => { // console.log(forms.isPageHide, 'showInfo') forms.isPageHide = true } window.addEventListener('pagehide', onPageHide) onUnmounted(() => { window.removeEventListener('pageshow', onPageShow) window.removeEventListener('pagehide', onPageHide) }) return () => (
{/* 加载视频使用 */} {forms.loading && (
加载中...
)}
{forms.videoDetails.map((item: any) => ( { forms.player.currentTime(item.startNode) forms.player.play() forms.videoBrowsePoint = item.startNode checkVideoDetails(forms.player.currentTime()) }} > {item.desc} ))}
{/*

家长您好!

请家长们合理安排时间,认真观看家长会内容。在详细了解 所有要求后,有意向让孩子加入乐团的家长,请在明晚20:00前,为孩子完成 乐团报名

下周,专业老师将针对意向入团学员进行身体条件确认。谢谢各位的支持!

注:乐团于下学期正式开始训练,训练时间下学期开学前另行通知,训练时间会与学校其他社团错开,家长无需担心时间冲突问题。

*/}
{forms.registerDisplay && }
) } })