import { closeToast, Icon, Popup, showDialog, showToast } from "vant" import { defineComponent, onMounted, reactive, nextTick, onUnmounted, ref, watch, Transition, computed } from "vue" import iconBack from "./image/back.svg" import styles from "./index.module.scss" import "plyr/dist/plyr.css" import "vant/lib/index.css" // import request from "@/helpers/request" // import { state } from "@/state" const state = { platformApi: "/api", platformType: "TEACHER" } import { useRoute } from "vue-router" import { postMessage, promisefiyPostMessage } from "./helpers/native-message" import MusicScore from "./component/musicScore" import iconDian from "./image/icon-dian.svg" import iconPoint from "./image/icon-point.svg" import icons from "./image/icons.json" const { iconUp, iconDown, iconPen, iconTouping } = icons 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 PlayRecordTime from "./playRecordTime" //import { handleCheckVip } from "../hook/useFee" function handleCheckVip() { return true } //import OGuide from "./component/o-guide" import Tool, { ToolItem, ToolType } from "./component/tool" import Pen from "./component/tools/pen" import VideoItem from "./component/video-item" import { getLessonCourseDetail_gym, getLessonCoursewareDetail_gyt } from "@/api/cloudTextbooks.api" import { httpAjaxErrMsg } from "@/plugin/httpAjax" import userStore from "@/store/modules/user" export default defineComponent({ name: "CoursewarePlay", setup() { const pageVisibility = usePageVisibility() const userStoreHook = userStore() /** 页面显示和隐藏 */ watch( () => pageVisibility.value, value => { if (value == "hidden") { handleStop() } } ) /** 设置播放容器 16:9 */ const parentContainer = reactive({ width: "100vw" }) // eslint-disable-next-line @typescript-eslint/no-unused-vars 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) window.removeEventListener("keyup", handleEventKeyup) }) const route = useRoute() const headeRef = ref() const data = reactive({ detail: null as any, knowledgePointList: [] as any, itemList: [] as any, showHead: true, isCourse: false, isRecordPlay: false, videoRefs: {}, videoState: "init" as "init" | "play", videoItemRef: null as any, animationState: "start" as "start" | "end" }) 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.typeCode // 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.typeCode)) { const localData = await getCacheFilePath(material) if (localData?.content?.localPath) { material.url = material.content material.content = localData.content.localPath } } list.push({ ...material, 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 checkedAnimation(popupData.activeIndex) postMessage({ api: "courseLoading", content: { show: false, type: "fullscreen" } }) setTimeout(() => { data.animationState = "end" }, 500) }) } const getDetail = async () => { try { //const res: any = await request.get(state.platformApi + `/lessonCourseware/getLessonCourseDetail/${route.query.id}`) const res: any = await httpAjaxErrMsg( userStoreHook.roles === "GYM" ? getLessonCourseDetail_gym : getLessonCoursewareDetail_gyt, route.params.id as string ) data.detail = res.data 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++ return { ...item, knowledgePointId: [item.knowledgePointId], materialId: item.id, id: index + "", typeCode: item.type || item.typeCode // GYM和GYT type字段不一样 这里统一 } }) } if (Array.isArray(n.children)) { n.children = n.children.map((cn: any) => { cn.materialList = cn.materialList.map((item: any) => { index++ return { ...item, knowledgePointId: [n.id, item.knowledgePointId], materialId: item.id, id: index + "", typeCode: item.type || item.typeCode // GYM和GYT type字段不一样 这里统一 } }) return cn }) } return n }) getItemList() } } catch (error) { console.log(error) } } // ifram事件处理 const iframeHandle = (ev: MessageEvent) => { if (ev.data?.api === "headerTogge") { activeData.model = ev.data.show || (ev.data.playState == "play" ? false : true) } } onMounted(async () => { await getDetail() 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) window.addEventListener("keyup", handleEventKeyup) }) function handleEventKeyup(event: any) { const key = event.key if (key === "ArrowDown") { handlePreAndNext("down") } else if (key === "ArrowUp") { handlePreAndNext("up") } } const playRef = ref() // 返回 const goback = () => { try { playRef.value?.handleOut() } catch (error) { console.log(error) } postMessage({ api: "goBack" }) window.close() } const popupData = reactive({ open: false, activeIndex: 0, playIndex: 0, 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 = async () => { const videos = document.querySelectorAll("video") for (let i = 0; i < videos.length; i++) { const videoEle = videos[i] as HTMLVideoElement await stopVideo(videoEle) } console.log("视频暂停完成") data.itemList.forEach((item: any) => { if (item.typeCode === "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.typeCode === "VIDEO") { const activeVideoRef = data.videoItemRef?.getVideoRef() 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 (popupData.activeIndex == index) return await handleStop() data.animationState = "start" data.videoState = "init" clearTimeout(acitveTimer.value) checkedAnimation(popupData.activeIndex, index) nextTick(() => { popupData.activeIndex = 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.typeCode == "SONG") { activeData.model = true } } requestAnimationFrame(() => { const _effectIndex = effectIndex.value + 1 effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex if (item && item.typeCode === "VIDEO") { // 自动播放下一个视频 clearTimeout(activeData.timer) closeToast() item.autoPlay = true data.animationState = "end" } }) }, activeData.isAnimation ? 850 : 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 (type === "up") { if (!popupData.activeIndex) { return } handleSwipeChange(popupData.activeIndex - 1) } else { if (popupData.activeIndex === data.itemList.length - 1) { return } handleSwipeChange(popupData.activeIndex + 1) } } /** 弹窗关闭 */ const handleClosePopup = () => { const item = data.itemList[popupData.activeIndex] if (item?.typeCode == "VIDEO" && !item.videoEle?.paused) { setModelOpen() } } /** 教学数据 */ const studyData = reactive({ type: "" as ToolType, penShow: false }) const whiteShow = ref(false) /** 打开教学工具 */ const openStudyTool = (item: ToolItem) => { const activeItem = data.itemList[popupData.activeIndex] // 暂停视频和曲谱的播放 if (activeItem.typeCode === "VIDEO" && data.videoItemRef?.getVideoItem()) { data.videoItemRef?.getVideoItem().pause() } if (activeItem.typeCode === "SONG") { activeItem.iframeRef?.contentWindow?.postMessage({ api: "setPlayState" }, "*") } clearModel() popupData.toolOpen = false studyData.type = item.type switch (item.type) { case "pen": studyData.penShow = true break case "white": whiteShow.value = true break } } /** 关闭教学工具 */ const closeStudyTool = () => { studyData.type = "init" toggleModel() } const activeVideoItem = computed(() => { console.log(data.itemList, " data.itemList") const item = data.itemList[popupData.activeIndex] if (item && item.typeCode && item.typeCode.toLocaleUpperCase() === "VIDEO") { return item } return {} }) let closeModelTimer: any = null return () => (