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 { 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' 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({ 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: '' }) // 播放视频总时长 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 formatEffectiveTimeToAfter(res) } const formatEffectiveTimeToAfter = (res: any[]) => { // 格式化有效时间 const effective: any = [] const startNode = forms.pointVideo.startNode const endNode = forms.pointVideo.endNode res.forEach((item: any) => { // 开始时间大于 设置时间 if (item[0] >= startNode && item[1] <= endNode) { effective.push(item) } if (item[0] >= startNode && item[1] <= endNode && item[1] >= endNode) { effective.push([item[0], endNode]) } if (item[0] < startNode && item[1] > startNode && item[1] < endNode) { effective.push(startNode, item[1]) } }) 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) => { forms.videoDetails.forEach((item: any) => { if (item.startNode <= time && time <= item.endNode) { forms.videoSelectId = item.id } }) } /** * 视屏累计时长 * 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() }) checkVideoDetails(0) } // 保存零时时间 const moreTime: any = ref([]) // 多个观看时间段 let tempTime: any = [] // 临时存储时间 const currentTimer = useInterval(1000, { controls: true }) // 监听播放状态, watch( () => videoIntervalRef.isActive.value, (newVal: boolean) => { initVideoCount(newVal) } ) const initVideoCount = (newVal: any) => { // 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.floor(forms.player.currentTime) } else { tempTime[1] = Math.floor(forms.player.currentTime) } // 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, 'value') // 结束时间,如果 大于开始时间则清除 if (tempTime[1] >= tempTime[0] && !diffTime) moreTime.value.push(tempTime) 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 { 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 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() await updateStat() videoIntervalRef.counter.value = 0 }, 10000) } catch { // } }) onUnmounted(() => { forms.intervalFnRef?.pause() currentTimer.pause() // 页面计时暂停 pageTimer.pause() }) // 判断是否有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} ))}
{/*

家长您好!

2、报名截止时间为后天21:00前,因编制有限,以报名先后顺序为原则确定席位;

3、入团需准备自用乐器和乐团AI学练工具,家长自愿选择准备方式;

4、乐团报名需点击乐团报名或扫码,完成提交视为报名成功。

{/*

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

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

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

*/}
) } })