import { closeToast, Icon, Popup, showDialog, showToast } from 'vant' import { defineComponent, onMounted, reactive, nextTick, onUnmounted, ref, watch, Transition, computed, onBeforeUnmount, shallowRef } from 'vue' import iconBack from './image/back.png' import styles from './index.module.less' import 'plyr/dist/plyr.css' import request from '@/helpers/request' import { setLogin, state } from '@/state' import { useRoute, useRouter } from 'vue-router' import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message' import qs from 'query-string' import MusicScore from './component/musicScore' // import iconDian from './image/icon-dian.svg' // import iconPoint from './image/icon-point.svg' import { iconUp, iconDown, iconTouping, iconMenu, iconCourseType, iconSearch } from './image/icons.json' import Points from './component/points' import { browser } from '@/helpers/utils' import { Vue3Lottie } from 'vue3-lottie' import playLoadData from './datas/data.json' import { usePageVisibility } from '@vant/use' import { useInterval, useIntervalFn, useNetwork } from '@vueuse/core' import PlayRecordTime from './playRecordTime' import { handleCheckVip } from '../hook/useFee' import OGuide from '@/components/o-guide' import Tool, { ToolItem, ToolType } from './component/tool' // import Pen from './component/tools/pen' // import VideoItem from './component/video-item' import deepClone from '@/helpers/deep-clone' import VideoPlay from './component/video-play' import CoursewareType from './component/courseware-type' import CoursewareTips from './component/courseware-tips' import GlobalTools from '@/components/globalTools' import { isPlay, penShow, toolOpen, whitePenShow } from '@/components/globalTools/globalTools' import PointsSearch from './component/points-search' export default defineComponent({ name: 'CoursewarePlay', setup() { const pageVisibility = usePageVisibility() const browserInfo = browser() const { isOnline } = useNetwork() /** 页面显示和隐藏 */ watch( () => pageVisibility.value, (value) => { if (value == 'hidden') { handleStop() } if (value === "visible") { if(data.source === 'search') { getUserInfo() } } } ) const getUserInfo = async () => { try { // 重新初始化用户信息 const userCash = await request.get(state.platformApi + '/user/getUserInfo') setLogin(userCash.data) } catch { // } } /** 设置播放容器 16:9 */ const parentContainer = reactive({ width: '100vw' }) // const setContainer = () => { // const min = Math.min(screen.width, screen.height) // const max = Math.max(screen.width, screen.height) // const width = min * (16 / 9) // if (width > max) { // parentContainer.width = '100vw' // return // } else { // parentContainer.width = width + 'px' // } // } const handleInit = (type = 0) => { //设置容器16:9 // setContainer() // 横屏 postMessage( { api: 'setRequestedOrientation', content: { orientation: type } }, () => { console.log(234) } ) // 头,包括返回箭头 // postMessage({ // api: 'setTitleBarVisibility', // content: { // status: type // } // }) // 安卓的状态栏 postMessage({ api: 'setStatusBarVisibility', content: { isVisibility: type } }) // 进入页面设置常量 postMessage({ api: 'keepScreenLongLight', content: { isOpenLight: type ? true : false } }) } handleInit() onUnmounted(() => { handleInit(1) window.removeEventListener('message', iframeHandle) }) const route = useRoute() const router = useRouter() const headeRef = ref() const isCurrentCoursewareMenu = shallowRef(true) // 是否为当前选的课程类型 const detailTempSearchList = shallowRef() const detailList = shallowRef()// 搜索来的所有数据 const data = reactive({ source: route.query.source as any, // 来源 search 搜索 searchLoading: false, // 搜索加载状态 search: route.query.search as any, // 默认的搜索条件 - searchTemp: route.query.search as any, // 默认的搜索条件 - isSearch: route.query.source === "search" ? true : false, // 是否搜索 currentId: route.query.id as any, lessonId: null as any, detail: null as any, knowledgePointList: [] as any, itemList: [] as any, lookVideoDataList: [] as any, // 观看视频统计数据 showHead: true, isCourse: false, isRecordPlay: false, videoRefs: {}, refLevelList: [] as any, videoState: 'init' as 'init' | 'play', videoItemRef: null as any, animationState: 'start' as 'start' | 'end', disableScreenRecordingFlag: '0' // 是否禁止录屏 }) const activeData = reactive({ isAutoPlay: true, // 是否自动播放 nowTime: 0, model: true, // 遮罩 isAnimation: true, // 是否动画 videoBtns: true, // 视频 currentTime: 0, duration: 0, timer: null as any, item: null as any }) // 获取缓存路径 const getCacheFilePath = async (material: any) => { const res = await promisefiyPostMessage({ api: 'getCourseFilePath', content: { url: material.content, localPath: '', materialId: material.materialId, updateTime: material.updateTime, type: material.type // SONG VIDEO IMAGE } }) // console.log('缓存路径返回', res) return res } // 获取当前课程是否签退 const getCourseSchedule = async () => { if (!route.query.courseId) return try { const res = await request.get( `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`, { hideLoading: true } ) if (res?.data) { data.isCourse = res.data.status === 'ING' && state.platformType == 'TEACHER' ? true : false // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf() } } catch (e) { console.log(e) } } const getTempList = async (materialList: any, name: any) => { const list: any = [] // const browserInfo = browser() for (let j = 0; j < materialList.length; j++) { const material = materialList[j] //请求本地缓存 // if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.type)) { // const localData = await getCacheFilePath(material) // if (localData?.content?.localPath) { // material.url = material.content // material.content = localData.content.localPath // } // } const videoData = data.lookVideoDataList.find( (i: any) => i.materialId === material.materialId ) material.moreTime = videoData?.videoBrowseData ? JSON.parse(videoData.videoBrowseData) : [] material.videoTime = videoData?.videoTime || 0 // 视频时长 material.iframeRef = null material.videoEle = null material.tabName = name material.autoPlay = false //加载完成是否自动播放 material.isprepare = false // 视频是否加载完成 material.isRender = false // 是否渲染了 list.push(material) // list.push({ // ...material, // moreTime: videoData?.videoBrowseData ? JSON.parse(videoData.videoBrowseData) : [], // videoTime: videoData?.videoTime || 0, // 视频时长 // iframeRef: null, // videoEle: null, // tabName: name, // autoPlay: false, //加载完成是否自动播放 // isprepare: false, // 视频是否加载完成 // isRender: false // 是否渲染了 // }) } return list } const getItemList = async () => { const list: any = [] for (let i = 0; i < data.knowledgePointList.length; i++) { const item = data.knowledgePointList[i] if (item.materialList && item.materialList.length > 0) { const tempList = await getTempList(item.materialList, item.name) list.push(...tempList) } // 第二层级 if (item.children && item.children.length > 0) { const childrenList = item.children || [] for (let j = 0; j < childrenList.length; j++) { const childItem = childrenList[j] const tempList = await getTempList(childItem.materialList, childItem.name) list.push(...tempList) } } } // console.log(list, 'list') let _firstIndex = list.findIndex( (n: any) => n.knowledgePointMaterialRelationId == route.query.kId || n.materialId == route.query.kId ) _firstIndex = _firstIndex > -1 ? _firstIndex : 0 const item = list[_firstIndex] // console.log(_firstIndex, '_firstIndex', route.query.kId, 'route.query.kId', item) // 是否自动播放 if (activeData.isAutoPlay) { item.autoPlay = true } popupData.activeIndex = _firstIndex popupData.playIndex = _firstIndex popupData.tabName = item.tabName popupData.tabActive = item.knowledgePointId popupData.itemActive = item.id popupData.itemName = item.name nextTick(() => { data.itemList = list getCurrentItemCatch(popupData.activeIndex) // 获取当前元素的缓存 checkedAnimation(popupData.activeIndex) postMessage({ api: 'courseLoading', content: { show: false, type: 'fullscreen' } }) //检测是否录屏 if (data.disableScreenRecordingFlag === '1') { handleLimitScreenRecord() } setTimeout(() => { data.animationState = 'end' }, 500) }) } /** 获取当前元素的缓存 */ const getCurrentItemCatch = async (index: number) => { const item = data.itemList[index] if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(item.typeCode) && !item.isReadCatch) { const localData: any = await getCacheFilePath(item) item.isReadCatch = true if (localData?.content?.localPath) { item.url = item.content item.content = localData.content.localPath } console.log('加载了缓存') } } const getDetail = async (id?: any) => { try { const res: any = await request.get( state.platformApi + `/lessonCoursewareDetail/detail/${id || route.query.id}`, { hideLoading: true } ) const result = res.data || {} result.lessonTargetDesc = result.lessonTargetDesc ? result.lessonTargetDesc.replace(/\n/g, "
") : "" data.detail = result; data.lessonId = result.lessonCoursewareId if (res?.data?.lockFlag) { postMessage({ api: 'courseLoading', content: { show: false, type: 'fullscreen' } }) showDialog({ title: '温馨提示', message: '课件已锁定' }).then(() => { goback() }) return } if (Array.isArray(res?.data?.knowledgePointList)) { let index = 0 data.knowledgePointList = res.data.knowledgePointList.map((n: any) => { if (Array.isArray(n.materialList)) { n.materialList = n.materialList.map((item: any) => { index++ const materialRefs = item.materialRefs ? item.materialRefs : [] const materialMusicId = materialRefs.length > 0 ? materialRefs[0].resourceId : null return { ...item, materialMusicId, knowledgePointId: [item.knowledgePointId], materialId: item.id, id: index + '' } }) } if (Array.isArray(n.children)) { n.children = n.children.map((cn: any) => { cn.materialList = cn.materialList.map((item: any) => { index++ const materialRefs = item.materialRefs ? item.materialRefs : [] const materialMusicId = materialRefs.length > 0 ? materialRefs[0].resourceId : null return { ...item, materialMusicId, knowledgePointId: [n.id, item.knowledgePointId], materialId: item.id, id: index + '' } }) return cn }) } return n }) getItemList() } return true } catch (error) { console.log(error) } } const getSearchItemList = async (knowledgePointList: any[]) => { const list: any = []; for (let i = 0; i < knowledgePointList.length; i++) { const item = knowledgePointList[i]; if (item.materialList && item.materialList.length > 0) { const tempList = await getTempList(item.materialList, item.name); list.push(...tempList); } // 第二层级 if (item.children && item.children.length > 0) { const childrenList = item.children || []; for (let j = 0; j < childrenList.length; j++) { const childItem = childrenList[j]; const tempList = await getTempList( childItem.materialList, childItem.name ); list.push(...tempList); } } } return list }; /** 从搜索页面来的 */ const getSearchDetail = async (params: { type?: string, id?: any, search?: string }) => { try { const res = await request.post( state.platformApi + `/courseSchedule/myCoursewareDetail/${params.id || route.query.lessonId}`, { hideLoading: true, requestType: "form", data: { detailFlag: "1", searchFlag: true, search: params.search } } ); const result = res.data || [] const allList: any[] = [] for(let i = 0; i < result.length; i++) { const itemResult = result[i]; itemResult.name = itemResult.coursewareDetailName; itemResult.id = itemResult.lessonCoursewareDetailId; itemResult.lessonTargetDesc = itemResult.lessonTargetDesc ? itemResult.lessonTargetDesc.replace(/\n/g, "
") : "" if (Array.isArray(itemResult?.knowledgePointList)) { let index = 0; itemResult.children = itemResult.knowledgePointList.map( (n: any) => { if (Array.isArray(n.materialList)) { n.materialList = n.materialList.map((item: any) => { index++; const materialRefs = item.materialRefs ? item.materialRefs : []; const materialMusicId = materialRefs.length > 0 ? materialRefs[0].resourceId : null; const useStatus = materialRefs.length > 0 ? materialRefs[0]?.extend?.useStatus : null return { ...item, materialMusicId, content: item.content, coursewareDetailId: itemResult.lessonCoursewareDetailId, lessonCoursewareDetailId: itemResult.lessonCoursewareDetailId, knowledgePointId: [itemResult.lessonCoursewareDetailId, item.knowledgePointId], materialId: item.id, id: (i * 1000 + '') + index + '' }; }); } if (Array.isArray(n.children)) { n.children = n.children.map((cn: any) => { cn.materialList = cn.materialList.map((item: any) => { index++; const materialRefs = item.materialRefs ? item.materialRefs : []; const materialMusicId = materialRefs.length > 0 ? materialRefs[0].resourceId : null; const useStatus = materialRefs.length > 0 ? materialRefs[0]?.extend?.useStatus : null return { ...item, materialMusicId, coursewareDetailId: itemResult.lessonCoursewareDetailId, lessonCoursewareDetailId: itemResult.lessonCoursewareDetailId, content: item.content, knowledgePointId: [itemResult.lessonCoursewareDetailId, n.id, item.knowledgePointId], materialId: item.id, id: (i * 1000 + '') + index + '' }; }); return cn; }); } return n; } ); itemResult.knowledgePointList = null // 去掉不要的 itemResult.list = await getSearchItemList(itemResult.children); allList.push(...itemResult.list) } } if(data.source !== 'search') { if(params.type === "pointSearch") { detailTempSearchList.value = result popupData.tempTabActive = allList.length > 0 ? allList[0].knowledgePointId : [] popupData.tempItemActive = "-1" data.searchTemp = params.search return } detailList.value = result detailTempSearchList.value = result return } if(params.type === 'pointSearch') { detailTempSearchList.value = result // 初始化选中的数据 临时 popupData.tempTabActive = allList.length > 0 ? allList[0].knowledgePointId : [] popupData.tempItemActive = "-1" data.searchTemp = params.search return } detailList.value = result detailTempSearchList.value = result if(!params.type) { let _firstIndex = allList.findIndex( (n: any) => n.knowledgePointMaterialRelationId == route.query.kId || n.materialId == route.query.kId ); _firstIndex = _firstIndex > -1 ? _firstIndex : 0; const item = allList[_firstIndex]; // console.log(item, 'item') // console.log(_firstIndex, '_firstIndex', route.query.kId, 'route.query.kId', item) // 是否自动播放 if (activeData.isAutoPlay) { item.autoPlay = true; } popupData.activeIndex = _firstIndex; popupData.playIndex = _firstIndex; popupData.tabName = item.tabName; popupData.tabActive = item.knowledgePointId; popupData.itemActive = item.id; popupData.itemName = item.name; data.detail = detailList.value?.find((child: any) => child.lessonCoursewareDetailId === item.lessonCoursewareDetailId) } nextTick(() => { data.itemList = allList; getCurrentItemCatch(popupData.activeIndex) // 获取当前元素的缓存 checkedAnimation(popupData.activeIndex); postMessage({ api: 'courseLoading', content: { show: false, type: 'fullscreen' } }); if (data.disableScreenRecordingFlag === '1') { // 检测是否录屏 handleLimitScreenRecord(); } setTimeout(() => { data.animationState = 'end'; }, 500); }); return true } catch (error) { console.log(error); } } const onTitleTip = (type: "phaseGoals" | "checkItem", text: string) => { handleStop() popupData.pointOpen = true popupData.pointContent = text if(type === "checkItem") { popupData.pointTitle = '检查事项' } else if(type === "phaseGoals") { popupData.pointTitle = '阶段目标' } } // ifram事件处理 const iframeHandle = (ev: MessageEvent) => { if (ev.data?.api === 'headerTogge') { activeData.model = ev.data.show || (ev.data.playState == 'play' ? false : true) } } // 获取学生观看数据 const getLookVideoData = async () => { try { const res = await request.get( state.platformApi + `/studentCoursewareMaterialRelation/findByDetailId`, { hideLoading: true, params: { lessonCoursewareDetailId: route.query.id } } ) data.lookVideoDataList = res.data || [] // 视频播放数据 } catch { // } } // 切换播放 const togglePlay = (m: any, isPlay: boolean) => { if (isPlay) { m.videoEle?.play() } else { m.videoEle?.pause() } } let timers: any = null const checkVideoPlay = () => { const activeVideoRef = data.videoItemRef?.getPlyrRef() if (activeVideoRef) { timers = setInterval(() => { if (!activeVideoRef.paused()) { activeVideoRef.pause() clearInterval(timers) } activeVideoRef.pause() }, 100) } setTimeout(() => { clearInterval(timers) }, 3000) } //录屏时间触发 const handleLimitScreenRecord = async () => { const result = await promisefiyPostMessage({ api: 'getDeviceStatus', content: { type: 'video' } }) const { status } = result?.content || {} if (status == '1') { data.itemList.forEach((item: any) => (item.autoPlay = false)) handleStop() // 处理事件 - 事件事件后加载的 checkVideoPlay() showDialog({ title: '温馨提示', message: '课件内容请勿录屏', beforeClose: () => { return new Promise((resolve) => { promisefiyPostMessage({ api: 'getDeviceStatus', content: { type: 'video' } }).then((res: any) => { const content = res.content if (content?.status == '1') { const activeItem = data.itemList[popupData.activeIndex] togglePlay(activeItem, false) resolve(false) } else { const activeItem = data.itemList[popupData.activeIndex] togglePlay(activeItem, true) resolve(true) } }) }) } }) } } // 获取禁止录屏 const sysParamConfig = async () => { try { const res = await request.get(`${state.platformApi}/sysParamConfig/queryByParamName`, { params: { paramName: 'disable_screen_recording_flag' } }) data.disableScreenRecordingFlag = res.data.paramValue || '' } catch { // } } const getRefLevel = async (id?: any) => { try { const res = await request.post(state.platformApi + '/lessonCoursewareDetail/refLevel', { data: { lessonCoursewareDetailId: id || route.query.id, courseScheduleId: route.query.courseId as any } }) data.refLevelList = res.data || [] return true } catch { // } } onMounted(async () => { await sysParamConfig() if (state.platformType === 'STUDENT') { await getLookVideoData() } if(data.source === 'search') { await getSearchDetail({search: data.search}) } else { // 只有老师有 课程类型 切换 if(state.platformType === "TEACHER") { await getRefLevel() } await getDetail() if(state.platformType !== "SCHOOL") { data.lessonId && await getSearchDetail({search: data.search, id: data.lessonId}) } } // console.log(data.detail, "data.detail"); // const hasFree = String(data.detail?.accessScope) === '0' // if (!hasFree) { // const hasVip = handleCheckVip() // if (!hasVip) { // nextTick(() => { // postMessage({ // api: 'courseLoading', // content: { // show: false, // type: 'fullscreen' // } // }) // }) // return // } // } getCourseSchedule() window.addEventListener('message', iframeHandle) if (data.disableScreenRecordingFlag === '1') { //禁止录屏 ios listenerMessage('setVideoPlayer', (result) => { if (result?.content?.status == 'pause') { handleLimitScreenRecord() } }) // 安卓 postMessage({ api: 'limitScreenRecord', content: { type: 1 } }) } }) onBeforeUnmount(() => { postMessage({ api: 'limitScreenRecord', content: { type: 0 } }) }) const playRef = ref() // 返回 const goback = () => { try { playRef.value?.handleOut() } catch (error) { console.log(error) } postMessage({ api: 'goBack' }) } const popupData = reactive({ pointOpen: false, pointContent: "", pointTitle: "", coursewareOpen: false, open: false, activeIndex: 0, playIndex: 0, tempTabActive: '', // 临时选中 tempItemActive: "", // 临时编号 tabActive: '', tabName: '', itemActive: '', itemName: '', guideOpen: false, toolOpen: false // 工具弹窗控制 }) const stopVideo = (el: HTMLVideoElement) => { return new Promise((resolve) => { if (el.paused) return resolve(true) el.onpause = () => { console.log('暂停') resolve(true) } el.pause() }) } /**停止所有的播放 */ const handleStop = () => { for (let i = 0; i < data.itemList.length; i++) { const activeItem = data.itemList[i] if (activeItem.type === 'VIDEO') { // activeItem.videoEle?.currentTime(0) activeItem.videoEle?.pause() // activeItem.videoEle?.stop() } // 停止曲谱的播放 if (activeItem.type === 'SONG') { activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*') } } console.log('视频暂停完成') data.itemList.forEach((item: any) => { if (item.type === 'SONG') { item.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*') } }) } // 切换素材 const toggleMaterial = (itemActive: any) => { const index = data.itemList.findIndex((n: any) => n.id == itemActive) if (index > -1) { handleSwipeChange(index) } } /** 延迟收起模态框 */ const setModelOpen = () => { clearTimeout(activeData.timer) closeToast() activeData.timer = setTimeout(() => { activeData.model = false }, 4000) } /** 立即收起所有的模态框 */ const clearModel = () => { clearTimeout(activeData.timer) closeToast() activeData.model = false } const toggleModel = (type = true) => { activeData.model = type } // 去点名,签退 const gotoRollCall = (pageTag: string) => { postMessage({ api: 'open_app_page', content: { action: 'app', pageTag: pageTag, url: '', params: JSON.stringify({ courseId: route.query.courseId }) } }) } // 双击 const handleDbClick = () => { if (activeVideoItem.value.type === 'VIDEO') { const activeVideoRef = data.videoItemRef?.getPlyrRef() if (activeVideoRef) { if (activeVideoRef.paused()) { activeVideoRef.play() } else { activeVideoRef.pause() showToast('已暂停') } } } } const effectIndex = ref(0) const effects = [ { prev: { transform: 'translate3d(0, 0, -800px) rotateX(180deg)' }, next: { transform: 'translate3d(0, 0, -800px) rotateX(-180deg)' } }, { prev: { transform: 'translate3d(-100%, 0, -800px)' }, next: { transform: 'translate3d(100%, 0, -800px)' } }, { prev: { transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)' }, next: { transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)' } }, { prev: { transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)' }, next: { transform: 'translate3d(100%, 0, -800px) rotateY(120deg)' } }, // 风车4 { prev: { transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)', opacity: 0 }, next: { transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)', opacity: 0 } }, // 翻页5 { prev: { transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)', opacity: 0 }, next: { transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)', opacity: 0 }, current: { transitionDelay: '700ms' } } ] const acitveTimer = ref() // 轮播切换 const handleSwipeChange = async (index: number) => { if(data.source === 'search') { const item = data.itemList[index]; console.log(item, detailList.value, "value"); data.detail = detailList.value?.find((child: any) => child.lessonCoursewareDetailId === item.lessonCoursewareDetailId) popupData.tabActive = item.knowledgePointId; popupData.itemActive = item.id; popupData.itemName = item.name; popupData.tabName = item.tabName; if (item.typeCode == 'SONG') { activeData.model = true; } } // 如果是当前正在播放 或者是视频最后一个 if (popupData.activeIndex == index) return await handleStop() data.animationState = 'start' data.videoState = 'init' clearTimeout(acitveTimer.value) checkedAnimation(popupData.activeIndex, index) nextTick(() => { popupData.activeIndex = index getCurrentItemCatch(index) // 获取当前元素的缓存 acitveTimer.value = setTimeout( () => { popupData.playIndex = index const item = data.itemList[index] if (item) { popupData.tabActive = item.knowledgePointId popupData.itemActive = item.id popupData.itemName = item.name popupData.tabName = item.tabName if (item.type == 'SONG') { activeData.model = true } } if (item.type === 'VIDEO') { // 自动播放下一个视频 clearTimeout(activeData.timer) closeToast() item.autoPlay = true // console.log(item, 'item') // 当视屏异常时重置链接 if (item.error) { item.videoEle?.src(item.content) item.error = false } nextTick(() => { item.videoEle?.play() }) } requestAnimationFrame(() => { const _effectIndex = effectIndex.value + 1 effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex }) }, activeData.isAnimation ? 800 : 0 ) }) } /** 是否有转场动画 */ const checkedAnimation = (index: number, nextIndex?: number) => { nextIndex = nextIndex ? nextIndex : index + 1 const item = data.itemList[index] const nextItem = data.itemList[nextIndex] if (nextItem) { if (nextItem.knowledgePointId != item.knowledgePointId) { activeData.isAnimation = true return } const videoEle = item.videoEle const nextVideo = nextItem.videoEle if (videoEle && videoEle.duration < 8 && index < nextIndex) { activeData.isAnimation = false } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex) { activeData.isAnimation = false } else { activeData.isAnimation = true } } else { activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true } } // 上一个知识点, 下一个知识点 const handlePreAndNext = (type: string) => { if(data.source === 'search') { // 判断是否需要会员 const index = type === "up" ? popupData.activeIndex - 1 : popupData.activeIndex + 1 const item = data.itemList[index] const parentItem = detailList.value?.find((n: any) => n.lessonCoursewareDetailId == item?.lessonCoursewareDetailId) if(String(parentItem?.accessScope) === '1') { const hasVip = handleCheckVip(false) if (!hasVip) { handleStop() return } } } if (type === 'up') { handleSwipeChange(popupData.activeIndex - 1) } else { handleSwipeChange(popupData.activeIndex + 1) } } /** 弹窗关闭 */ const handleClosePopup = () => { const item = data.itemList[popupData.activeIndex] if (item?.type == 'VIDEO' && !item.videoEle?.paused) { setModelOpen() } } /** 教学数据 */ const studyData = reactive({ type: '' as ToolType, penShow: false }) /** 打开教学工具 */ const openStudyTool = (item: ToolItem) => { const activeItem = data.itemList[popupData.activeIndex] // 暂停视频和曲谱的播放 if (activeItem.type === 'VIDEO' && activeItem.videoEle) { activeItem.videoEle.pause() } if (activeItem.type === 'SONG') { activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*') } clearModel() popupData.toolOpen = false studyData.type = item.type switch (item.type) { case 'pen': studyData.penShow = true break } } /** 关闭教学工具 */ const closeStudyTool = () => { studyData.type = 'init' toggleModel() } const activeVideoItem = computed(() => { const item = data.itemList[popupData.activeIndex] if (item && item.type && item.type.toLocaleUpperCase() === 'VIDEO') { return item } return {} }) let closeModelTimer: any = null /** * 统计视频播放时间段 */ const intervalFnRef = ref() // 定时任务 // 播放视频总时长 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 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 } // 保存零时时间 // const moreTime: any = ref([]) // 多个观看时间段 已经放到列表里面了 let tempTime: any = [] // 临时存储时间 const currentTimer = useInterval(1000, { controls: true }) // 监听播放状态, watch( () => videoIntervalRef.isActive.value, (newVal: boolean) => { initVideoCount(newVal) } ) // 白板的批注打开时暂停播放 watch( () => [whitePenShow.value, penShow.value], () => { if (whitePenShow.value || penShow.value) { handleStop() } } ) // 是否收起 watch( () => activeData.model, () => { if (activeData.model) { isPlay.value = false } else { isPlay.value = true toolOpen.value = false } } ) /** * 初始化视频时长 * @param newVal 播放状态 * @param repeat 是否为定时发送的 */ const initVideoCount = (newVal: any, repeat = false) => { // console.log('watch', forms.player.currentTime) const activeVideoRef = data.videoItemRef?.getPlyrRef() const initTime = deepClone(tempTime) if (repeat) { if (tempTime.length > 0) { // console.log('join video', tempTime, 'initTime', initTime) tempTime[1] = Math.floor(activeVideoRef.currentTime()) } } else { if (newVal) { tempTime[0] = Math.floor(activeVideoRef.currentTime()) } else { tempTime[1] = Math.floor(activeVideoRef.currentTime()) } } if (tempTime.length >= 2) { // console.log(tempTime, 'tempTime', moreTime.value) // 处理在短时间内的时间差 【视屏拖动,点击可能会导致时间差太大】 const diffTime = tempTime[1] - tempTime[0] - currentTimer.counter.value > 2 // 结束时间,如果 大于开始时间则清除 if (tempTime[1] >= tempTime[0] && !diffTime) { data.itemList[popupData.activeIndex].moreTime.push(tempTime) // moreTime.value.push(tempTime) } if (repeat) { tempTime = deepClone(initTime) } else { tempTime = [] currentTimer.counter.value = 0 } } } // 更新时间 const updateStat = async () => { try { const itemList = data.itemList const params: any = [] itemList.forEach((item: any) => { if (item.moreTime.length > 0) { const videoBrowseData = formatEffectiveTime(item.moreTime) const time = videoBrowseData.length > 0 ? formatTimer(videoBrowseData) : 0 const temp = { lessonCoursewareDetailId: route.query.id || data.detail?.lessonCoursewareDetailId, browseTime: time, // 播放时长 videoBrowseData: JSON.stringify(videoBrowseData), // 播放的数据 videoTime: item.videoTime, // 视频时长 materialId: item.materialId } params.push(temp) } }) // 只有学生才统计数据 if (params.length > 0 && state.platformType === 'STUDENT') { await request.post(`${state.platformApi}/studentCoursewareMaterialRelation/save`, { data: params }) } } catch { // } } onMounted(() => { // 间隔多少时间同步数据 intervalFnRef.value = useIntervalFn(async () => { // 同步数据时先进行有效时间进行保存 initVideoCount(false, true) await updateStat() videoIntervalRef.counter.value = 0 }, 10000) }) /** 统计视频播放时间段 */ return () => (
{ clearTimeout(closeModelTimer) clearTimeout(activeData.timer) closeToast() if (Date.now() - activeData.nowTime < 300) { handleDbClick() return } activeData.nowTime = Date.now() closeModelTimer = setTimeout(() => { activeData.model = !activeData.model }, 300) }} >
(data.videoItemRef = el)} item={activeVideoItem.value} activeModel={activeData.model} // isEmtry={isEmtry} onPlay={() => { data.videoState = 'play' data.animationState = 'end' if(whitePenShow.value || penShow.value || popupData.coursewareOpen || popupData.open || popupData.guideOpen || popupData.pointOpen) { handleStop() } }} onLoadedmetadata={(videoItem: any) => { data.videoState = 'play' activeVideoItem.value.videoEle = videoItem if (!activeVideoItem.value.isprepare) { activeVideoItem.value.isprepare = true } }} onPause={() => { clearTimeout(activeData.timer) // activeData.model = true videoIntervalRef.pause() }} onSeeked={() => { videoIntervalRef.isActive.value && videoIntervalRef.pause() }} onSeeking={() => { videoIntervalRef.isActive.value && videoIntervalRef.pause() }} onWaiting={() => { videoIntervalRef.isActive.value && videoIntervalRef.pause() }} onTimeupdate={() => { const activeVideoRef = data.videoItemRef?.getPlyrRef() if ( !videoIntervalRef.isActive.value && activeVideoRef?.currentTime() > 0 && !activeVideoRef?.paused() ) { videoIntervalRef.resume() } }} onTogglePlay={(paused: boolean) => { // console.log('播放切换', paused) // 首次播放完成 if (!activeVideoItem.value.isprepare) { activeVideoItem.value.isprepare = true } activeVideoItem.value.autoPlay = false if (paused || popupData.open || popupData.guideOpen) { clearTimeout(activeData.timer) } else { setModelOpen() } }} onEnded={async () => { const _index = popupData.activeIndex + 1 if (_index < data.itemList.length) { handleSwipeChange(_index) } else { // 说明是最后一个 intervalFnRef.value.pause() // 同步数据时先进行有效时间进行保存 initVideoCount(false, true) await updateStat() } }} onReset={() => { if (!activeVideoItem.value.videoEle?.paused) { setModelOpen() } }} onError={() => { // 视屏异常 activeVideoItem.value.error = true }} />
{data.itemList.map((m: any, mIndex: number) => { const isRenderItem = Math.abs(popupData.activeIndex - mIndex) < 2 const isRender = Math.abs(popupData.playIndex - mIndex) < 2 // 判断是否是当前选中的元素 const activeEle = popupData.playIndex === mIndex ? true : false return isRenderItem ? (
popupData.activeIndex ? effects[effectIndex.value].next : {} } > {m.type === 'VIDEO' && data.animationState !== 'end' && data.videoState != 'play' && !m.isprepare && (
)}
{isRender && m.type === 'IMG' && ( <> {m.materialMusicId && state.platformType !== 'SCHOOL' && (
{ // 去云练习完整版 e.stopPropagation() const parmas = qs.stringify({ id: m.materialMusicId }) const src = `${location.origin}/orchestra-music-score/?` + parmas postMessage({ api: 'openAccompanyWebView', content: { url: src, orientation: 0, c_orientation: 0, isHideTitle: true, statusBarTextColor: false, isOpenLight: true } }) }} >
)} )} {isRender && m.type === 'SONG' && ( { m.iframeRef = el }} /> )}
) : ( '' ) })}
{activeData.model && (
e.stopPropagation()}>
{state.platformType === 'TEACHER' && data.source !== 'search' &&
{ popupData.coursewareOpen = true handleStop() }}>
} {state.platformType !== "SCHOOL" &&
{ handleStop() data.isSearch = true detailTempSearchList.value = detailList.value popupData.tempItemActive = "" popupData.tempTabActive = "" data.searchTemp = "" // data.searchTemp = JSON.parse(JSON.stringify(data.search)) popupData.open = true }}>
} {data.source !== 'search' &&
{ popupData.open = true data.isSearch = false handleStop() }}> {/* 知识点 */}
}
{ if(popupData.activeIndex != 0) handlePreAndNext('up') }} > {/* 上一个 */}
{ if(popupData.activeIndex != data.itemList.length - 1) handlePreAndNext('down') }} > {/* 下一个 */}
)}
goback()} />
{popupData.tabName}

{data.itemList[popupData.activeIndex]?.name}

{data.detail?.lessonTargetDesc ? onTitleTip('phaseGoals', data.detail?.lessonTargetDesc)}>阶段目标: ""} {data.itemList[popupData.activeIndex]?.checkItem ? onTitleTip('checkItem', data.itemList[popupData.activeIndex]?.checkItem)}>检查事项 : ""}
{data.isCourse && isCurrentCoursewareMenu.value && } {/*
{ const _effectIndex = effectIndex.value + 1 effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex setModelOpen() }} > {popupData.tabName}
*/} {state.platformType === 'TEACHER' && (
{ e.stopPropagation() clearTimeout(activeData.timer) }} > {data.isCourse && ( <>
gotoRollCall('student_roll_call')}> {/* */} 点名
gotoRollCall('sign_out')}> {/* */} 签退
)}
(popupData.guideOpen = true)}>
{/*
{ openStudyTool({ type: 'pen', icon: iconPen, name: '批注' }) }} >
*/} {/*
(popupData.toolOpen = true)}>
*/}
)}
{/* 更多弹窗 */} {data.isSearch ? { if(data.source !== "search") { if (browser().isApp) { postMessage({ api: 'openWebView', content: { url: `${location.origin}${location.pathname}#/coursewarePlay?lessonId=${data.lessonId}&source=search&kId=${res.materialId}&search=${encodeURIComponent(data.searchTemp ? JSON.parse(JSON.stringify(data.searchTemp)) : '')}`, orientation: 0, c_orientation: 0, isHideTitle: true, statusBarTextColor: false, isOpenLight: true, showLoadingAnim: true } }); return } else { router.push({ path: '/coursewarePlay', query: { lessonId: data.lessonId, kId: res.materialId, search: data.searchTemp ? JSON.parse(JSON.stringify(data.searchTemp)) : '', source: 'search' } }).then(() => { window.location.reload() }); // Toast('请使用App打开') return } } if(res.isSearch) { detailList.value = detailTempSearchList.value const tempList: any[] = [] detailTempSearchList.value?.forEach((item: any) => { if(Array.isArray(item.list)) { tempList.push(...item.list) } }) data.itemList = tempList || [] data.search = data.searchTemp ? JSON.parse(JSON.stringify(data.searchTemp)) : '' } // 判断是否需要会员 const item = data.itemList.find((n: any) => n.id == res.itemActive) const parentItem = detailList.value?.find((n: any) => n.lessonCoursewareDetailId == item?.lessonCoursewareDetailId) if(String(parentItem?.accessScope) === '1') { const hasVip = handleCheckVip(false) if (!hasVip) return } toggleMaterial(res.itemActive); popupData.open = false; }} onHandleSearch={async (val: any) => { data.searchLoading = true detailTempSearchList.value = [] if(data.source === 'search') { await getSearchDetail({ type: 'pointSearch', search: val.search }) } else { await getSearchDetail({ type: 'pointSearch', search: val.search, id: data.lessonId }) } data.searchTemp = val.search; data.searchLoading = false }} /> : { // onChangeSwiper('change', res.itemActive) popupData.open = false toggleMaterial(res.itemActive) }} />} {/* 课件类型 */} { // 判断是否为当前课程类型 if(data.currentId === item.id) { return } const n = await getDetail(item.id); const s = await getRefLevel(item.id); data.isSearch = false if(n && s) { data.currentId = item.id; isCurrentCoursewareMenu.value = item.id === route.query.id ? true : false popupData.coursewareOpen = false; popupData.activeIndex = 0; getCurrentItemCatch(popupData.activeIndex) // 获取当前元素的缓存 nextTick(() => { popupData.open = true }) } else { if(!isOnline.value) { showToast('网络异常') } } if(state.platformType !== "SCHOOL") { data.lessonId && await getSearchDetail({search: data.search, id: data.lessonId}) } }} /> { popupData.pointOpen = false }} show={popupData.pointOpen} content={popupData.pointContent} titleName={popupData.pointTitle} />
) } })