import { closeToast, Icon, Loading, Popup, showToast, Slider, Swipe, SwipeInstance, SwipeItem } from 'vant' import { defineComponent, onMounted, reactive, nextTick, onUnmounted, ref, watch, Transition, TransitionGroup } from 'vue' import iconBack from './image/back.svg' import styles from './index.module.less' import 'plyr/dist/plyr.css' import request from '@/helpers/request' import { state } from '@/state' import { useRoute, useRouter } from 'vue-router' import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message' import MusicScore from './component/musicScore' import iconMenu from './image/icon-menu.svg' import iconDian from './image/icon-dian.svg' import iconPoint from './image/icon-point.svg' import iconLoop from './image/icon-loop.svg' import iconLoopActive from './image/icon-loop-active.svg' import iconplay from './image/icon-play.svg' import iconpause from './image/icon-pause.svg' import iconUp from './image/icon-up.svg' import iconDown from './image/icon-down.svg' import Points from './component/points' import { browser, getSecondRPM } from '@/helpers/utils' import { Vue3Lottie } from 'vue3-lottie' import playLoadData from './datas/data.json' import { usePageVisibility, useRect } from '@vant/use' import PlayRecordTime from './playRecordTime' import dayjs from 'dayjs' import { Pagination, Navigation, EffectFade, EffectFlip, EffectCreative, Lazy } from 'swiper' import { Swiper, SwiperSlide } from 'swiper/vue' import 'swiper/less' import 'swiper/less/effect-fade' import 'swiper/less/effect-flip' import 'swiper/less/effect-creative' export default defineComponent({ name: 'CoursewarePlay', setup() { const pageVisibility = usePageVisibility() const isPlay = ref(false) /** 页面显示和隐藏 */ watch(pageVisibility, (value) => { const activeItem = data.itemList[popupData.activeIndex] if (activeItem.type != 'VIDEO') return if (value == 'hidden') { isPlay.value = !activeItem.paused handlePaused(activeItem) } else { // 页面显示,并且 if (isPlay.value) handlePlay(activeItem) } }) /** 设置播放容器 16:9 */ const parentContainer = reactive({ width: '100vw' }) const setContainer = () => { let min = Math.min(screen.width, screen.height) let max = Math.max(screen.width, screen.height) let width = min * (16 / 9) if (width > max) { parentContainer.width = '100vw' return } else { parentContainer.width = width + 'px' } } const handleInit = (type = 0) => { // postMessage({ // api: 'courseLoading', // content: { // show: true, // type: 'fullscreen' // } // }) //设置容器16:9 setContainer() // 横屏 postMessage({ api: 'setRequestedOrientation', content: { orientation: type } }) // 头,包括返回箭头 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 data = reactive({ detail: null, knowledgePointList: [] as any, itemList: [] as any, showHead: true, isCourse: false, isRecordPlay: false }) const activeData = reactive({ nowTime: 0, model: 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 getItemList = async () => { const list: any = [] const browserInfo = browser() for (let i = 0; i < data.knowledgePointList.length; i++) { const item = data.knowledgePointList[i] const itemLength = item.materialList.length - 1 for (let j = 0; j < item.materialList.length; j++) { const material = item.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 // console.log("🚀 ~ material", material) } } let videoItem = {} if (material.type === 'VIDEO') { videoItem = { currentTime: 0, duration: 0, progress: 0, paused: true, loop: false, videoEle: null, timer: null, playModel: false, isprepare: false, isDrage: false, muted: true // 是否静音 } } list.push({ ...material, ...videoItem, iframeRef: null, tabName: item.name, isLast: j === itemLength, // 当前知识点 autoPlay: false, //加载完成是否自动播放 display: false }) } } let item: any = null if (route.query.kId) { item = list.find((n: any) => n.materialId == route.query.kId) const _firstIndex = list.findIndex((n: any) => n.materialId == route.query.kId) popupData.firstIndex = _firstIndex > -1 ? _firstIndex : 0 } item = item ? item : list[0] || {} if (item) { popupData.tabName = item.tabName popupData.tabActive = item.knowledgePointId popupData.itemActive = item.id popupData.itemName = item.name popupData.activeIndex = popupData.firstIndex item.autoPlay = true item.muted = true item.display = true } // console.log('🚀 ~ list', list) data.itemList = list nextTick(() => { postMessage({ api: 'courseLoading', content: { show: false, type: 'fullscreen' } }) }) // setTimeout(() => { // }, 300) } const getDetail = async () => { try { const res: any = await request.get( state.platformApi + `/lessonCoursewareDetail/detail/${route.query.id}`, { hideLoading: true } ) if (Array.isArray(res?.data)) { data.detail = res.data } 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, materialId: item.id, id: index + '' } }) } return n }) getItemList() } } catch (error) {} } // ifram事件处理 const iframeHandle = (ev: MessageEvent) => { if (ev.data?.api === 'headerTogge') { // console.log("🚀 ~ ev.data", ev.data) activeData.model = ev.data.show || (ev.data.playState == 'play' ? true : false) } } const swiperDom = ref() onMounted(() => { getDetail() getCourseSchedule() window.addEventListener('message', iframeHandle) }) const playRef = ref() // 返回 const goback = () => { try { playRef.value?.handleOut() } catch (error) {} if (route.query.source == 'my-course') { router.back() return } postMessage({ api: 'goBack' }) } const swipeRef = ref() const popupData = reactive({ firstIndex: 0, open: false, activeIndex: 0, tabActive: '', tabName: '', itemActive: '', itemName: '' }) /**停止所有的播放 */ const handleStop = () => { const activeItem = data.itemList[popupData.activeIndex] for (let i = 0; i < data.itemList.length; i++) { const item = data.itemList[i] // 停止视频播放 if (item.type === 'VIDEO') { // console.log("🚀 ~ item", item) if (item?.id != activeItem.id) { item.currentTime = 0 item.progress = 0 if (item.videoEle) { item.videoEle.currentTime = 0 item.videoEle.pause() } } } // 停止曲谱的播放 if (item.type === 'SONG') { item.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*') item.display = false } } } // 切换素材 const toggleMaterial = () => { const index = data.itemList.findIndex((n: any) => n.id == popupData.itemActive) if (index > -1) { // swipeRef.value?.swipeTo(index, { // immediate: true // }) swiperDom.value?.slideTo(index, 1000) } } /** 延迟收起模态框 */ const setModelOpen = () => { clearTimeout(activeData.timer) closeToast() activeData.timer = setTimeout(() => { activeData.model = false }, 4000) } // 轮播切换 const handleSwipeChange = (val: any) => { console.log('轮播切换') popupData.activeIndex = val const item = data.itemList[val] handleStop() 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 item.display = true } if (item.type === 'VIDEO') { // console.log("🚀 ~ item", item) // 自动播放下一个视频 clearTimeout(activeData.timer) closeToast() item.currentTime = 0 item.videoEle && (item.videoEle.currentTime = 0) nextTick(() => { item.autoPlay = true item.videoEle?.play() }) } } } // 去点名,签退 const gotoRollCall = (pageTag: string) => { postMessage({ api: 'open_app_page', content: { action: 'app', pageTag: pageTag, url: '', params: JSON.stringify({ courseId: route.query.courseId }) } }) } // 双击 const handleDbClick = (item: any) => { // console.log(item) if (item && item.type === 'VIDEO') { const videoEle: HTMLVideoElement = item.videoEle if (videoEle) { if (videoEle.paused) { closeToast() videoEle.play() } else { showToast('已暂停') videoEle.pause() } } } } // 暂停播放 const handlePaused = (m: any) => { m.videoEle?.pause() m.paused = true } // 开始播放 const handlePlay = (m: any) => { closeToast() m.videoEle?.play() } // 调整播放进度 const handleChangeSlider = (m: any) => { if (m?.videoEle) { // console.log('进度条', m.progress) m.currentTime = m.duration * (m.progress / 100) m.videoEle.currentTime = m.currentTime } } //当前视频播放完 const handleEnded = (m: any) => { // console.log(m) if (popupData.activeIndex != data.itemList.length - 1) { swiperDom.value.slideNext(800) // setTimeout(() => { // swipeRef.value?.next() // ;(document.querySelector('.swiper-button-next') as any)?.click() // swiperDom.value.navigation.nextEl.click() // swiperDom.value.slideNext(800) // }, 300) } } const effects = [ { prev: { shadow: true, translate: [0, 0, -400] }, next: { translate: ['100%', 0, 0] } }, { prev: { shadow: true, translate: ['-120%', 0, -500] }, next: { shadow: true, translate: ['120%', 0, -500] } }, { prev: { shadow: true, translate: ['-20%', 0, -1] }, next: { translate: ['100%', 0, 0] } }, { prev: { shadow: true, translate: [0, 0, -800], rotate: [180, 0, 0] }, next: { shadow: true, translate: [0, 0, -800], rotate: [-180, 0, 0] } }, { prev: { shadow: true, translate: ['-125%', 0, -800], rotate: [0, 0, -90] }, next: { shadow: true, translate: ['125%', 0, -800], rotate: [0, 0, 90] } }, { prev: { shadow: true, origin: 'right center', translate: ['5%', 0, -200], rotate: [0, -100, 0] }, next: { origin: 'left center', translate: ['-5%', 0, -200], rotate: [0, 100, 0] } }, { prev: { scale: 0.3, opacity: 0.4 }, next: { opacity: 0.4, scale: 0.3 } } ] const swiperType = ref(effects[3]) // 上一个知识点, 下一个知识点 const handlePreAndNext = (type: string) => { setTimeout(() => { if (type === 'up') { // swiperRef.value?.allowSlidePrev() // ;(document.querySelector('.swiper-button-prev') as any)?.click() // swiperDom.value.navigation.prevEl.click() swiperDom.value.slidePrev(800) } else { // swiperRef.value?.allowSlideNext() // ;(document.querySelector('.swiper-button-next') as any)?.click() // swiperDom.value.navigation.nextEl.click() // const nextKonwledgeId = data.itemList[popupData.activeIndex + 1].knowledgePointId // console.log(nextKonwledgeId === popupData.tabActive) // if (nextKonwledgeId === popupData.tabActive) { // swiperType.value = effects[2] // } else { // swiperType.value = effects[3] // } swiperDom.value.slideNext(800) } }, 400) } return () => (
{/* Slide 1 Slide 2 Slide 3 */} {/* 知识点 翻 素材滑 */} { // console.log(swiper, 'index') swiperDom.value = swiper }} onSlideChange={(swiper: any) => { // console.log(swiper, 'index') handleSwipeChange(swiper.activeIndex) }} > {data.itemList.map((m: any, mIndex: number) => { return (
{ clearTimeout(activeData.timer) if (Date.now() - activeData.nowTime < 300) { handleDbClick(m) return } activeData.nowTime = Date.now() activeData.timer = setTimeout(() => { activeData.model = !activeData.model setModelOpen() }, 300) }} > {m.type === 'VIDEO' ? ( <> {m.muted && (
)}
{ e.stopPropagation() setModelOpen() }} >
{getSecondRPM(m.currentTime)} {getSecondRPM(m.duration)}
setModelOpen()} buttonSize={16} step={1} modelValue={m.progress} onUpdate:modelValue={(val: any) => { console.log('val', val) m.progress = val handleChangeSlider(m) }} onDragStart={(e: Event) => { // 开始拖动,暂停播放 console.log('开始拖动') // 如果拖动之前,视频是播放状态,拖动完毕后继续播放 if (!m.paused) { m.isDrage = true } handlePaused(m) }} onDragEnd={(e: Event) => { console.log('结束拖动') if (m.isDrage) { m.isDrage = false handlePlay(m) } }} min={0} max={100} />
{m.isprepare ? ( <> {m.paused ? ( handlePlay(m)} /> ) : ( handlePaused(m)} /> )} ) : ( )} {m.loop ? ( (m.loop = false)} /> ) : ( (m.loop = true)} /> )}
{m.name}
) : m.type === 'IMG' ? ( ) : ( { m.iframeRef = el }} /> )}
) })}
{/* */}
goback()}> 返回
{popupData.tabName}
{data.isCourse && }
{activeData.model && (
{ clearTimeout(activeData.timer) popupData.open = true }} > 知识点
{data.isCourse && ( <>
gotoRollCall('student_roll_call')} > 点名
gotoRollCall('sign_out')}> 签退
)}
)}
{activeData.model && (
{popupData.activeIndex != 0 && (
handlePreAndNext('up')} > 上一个
)} {popupData.activeIndex != data.itemList.length - 1 && (
handlePreAndNext('down')}> 下一个
)}
)}
{ const item = data.itemList[popupData.activeIndex] if (item?.type == 'VIDEO') { setModelOpen() } }} > { // console.log(res) popupData.tabActive = res.tabActive popupData.itemActive = res.itemActive popupData.tabName = res.tabName popupData.open = false toggleMaterial() }} />
) } })