|
@@ -0,0 +1,517 @@
|
|
|
+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 // 处理页面返回没有刷新的问题
|
|
|
+ })
|
|
|
+
|
|
|
+ // 播放视频总时长
|
|
|
+ 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()
|
|
|
+ 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.introductionVideoTime = data.introductionVideoTime
|
|
|
+ forms.coverImg = data.coverImg
|
|
|
+ moreTime.value = data.videoBrowseData ? JSON.parse(data.videoBrowseData) : []
|
|
|
+
|
|
|
+ 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 () => (
|
|
|
+ <div class={styles['pre-register-video']}>
|
|
|
+ <div class={styles.videoContainer}>
|
|
|
+ <div class={styles['video-content']}>
|
|
|
+ <video
|
|
|
+ id="register-video"
|
|
|
+ class={styles['video']}
|
|
|
+ src={forms.introductionVideo}
|
|
|
+ // src={
|
|
|
+ // 'https://cloud-coach.ks3-cn-beijing.ksyuncs.com/1684981545808.mp4?time' + Date.now()
|
|
|
+ // }
|
|
|
+ playsinline={true}
|
|
|
+ poster={forms.coverImg}
|
|
|
+ preload="auto"
|
|
|
+ ></video>
|
|
|
+ {/* 加载视频使用 */}
|
|
|
+ {forms.loading && (
|
|
|
+ <div class={styles.loadingVideo}>
|
|
|
+ <Loading
|
|
|
+ size={36}
|
|
|
+ color="#FF8057"
|
|
|
+ vertical
|
|
|
+ style={{ height: '100%', justifyContent: 'center' }}
|
|
|
+ >
|
|
|
+ 加载中...
|
|
|
+ </Loading>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class={styles.videoCount}>
|
|
|
+ <div class={styles.videoCountContent}>
|
|
|
+ {forms.videoDetails.map((item: any) => (
|
|
|
+ <span
|
|
|
+ class={[item.id === forms.videoSelectId ? styles.active : '']}
|
|
|
+ onClick={() => {
|
|
|
+ forms.player.currentTime = item.startNode
|
|
|
+ forms.player.play()
|
|
|
+ forms.videoBrowsePoint = item.startNode
|
|
|
+ checkVideoDetails(forms.player.currentTime)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {item.desc}
|
|
|
+ </span>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class={styles.messageContainer}>
|
|
|
+ <div class={styles.messageContent}>
|
|
|
+ <p>家长您好!</p>
|
|
|
+ <p class={styles.c1}>
|
|
|
+ 请家长们合理安排时间,<span>认真观看</span>家长会内容。在<span>详细了解</span>
|
|
|
+ 所有要求后,有意向让孩子加入乐团的家长,请在<span>明晚20:00前</span>,为孩子完成
|
|
|
+ <span>乐团报名</span>。
|
|
|
+ </p>
|
|
|
+ <p class={styles.c1}>
|
|
|
+ 下周,专业老师将针对意向入团学员进行身体条件确认。谢谢各位的支持!
|
|
|
+ </p>
|
|
|
+ <p class={styles.bottom}>
|
|
|
+ 注:乐团于下学期正式开始训练,训练时间下学期开学前另行通知,训练时间会与学校其他社团错开,家长无需担心时间冲突问题。
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <Button class={styles.submitBtn} onClick={onSubmit}></Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+})
|