|
@@ -0,0 +1,871 @@
|
|
|
+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 iconTouping from './image/icon-touping.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,
|
|
|
+ Virtual,
|
|
|
+ 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'
|
|
|
+import { handleCheckVip } from '../hook/useFee'
|
|
|
+import OGuide from '@/components/o-guide'
|
|
|
+
|
|
|
+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)
|
|
|
+ console.log('🚀 ~ item:', _firstIndex, list)
|
|
|
+ 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
|
|
|
+ console.log("🚀 ~ popupData.activeIndex:", popupData.activeIndex)
|
|
|
+ item.autoPlay = true
|
|
|
+ item.muted = true
|
|
|
+ item.display = true
|
|
|
+ }
|
|
|
+ // console.log('🚀 ~ list', list)
|
|
|
+ nextTick(() => {
|
|
|
+ data.itemList = list
|
|
|
+ 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(() => {
|
|
|
+ const hasVip = handleCheckVip()
|
|
|
+ if (!hasVip) {
|
|
|
+ nextTick(() => {
|
|
|
+ postMessage({
|
|
|
+ api: 'courseLoading',
|
|
|
+ content: {
|
|
|
+ show: false,
|
|
|
+ type: 'fullscreen'
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ 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<SwipeInstance>()
|
|
|
+ const popupData = reactive({
|
|
|
+ firstIndex: 0,
|
|
|
+ open: false,
|
|
|
+ activeIndex: 0,
|
|
|
+ tabActive: '',
|
|
|
+ tabName: '',
|
|
|
+ itemActive: '',
|
|
|
+ itemName: '',
|
|
|
+ guideOpen: false
|
|
|
+ })
|
|
|
+
|
|
|
+ /**停止所有的播放 */
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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') {
|
|
|
+ swiperDom.value.slidePrev(800)
|
|
|
+ } else {
|
|
|
+ swiperDom.value.slideNext(800)
|
|
|
+ }
|
|
|
+ }, 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ return () => (
|
|
|
+ <div class={styles.playContent}>
|
|
|
+ <div class={styles.coursewarePlay} style={{ width: parentContainer.width }}>
|
|
|
+ {data.itemList.length && (
|
|
|
+ <Swiper
|
|
|
+ style={{ height: '100%' }}
|
|
|
+ class={styles.swiperContainer}
|
|
|
+ effect="creative"
|
|
|
+ modules={[Pagination, Navigation, EffectCreative]}
|
|
|
+ creativeEffect={swiperType.value}
|
|
|
+ direction="vertical"
|
|
|
+ navigation
|
|
|
+ allowTouchMove={false}
|
|
|
+ onSwiper={(swiper: any) => {
|
|
|
+ swiperDom.value = swiper
|
|
|
+ }}
|
|
|
+ onSlideChange={(swiper: any) => {
|
|
|
+ handleSwipeChange(swiper.activeIndex)
|
|
|
+ }}
|
|
|
+ initialSlide={popupData.activeIndex}
|
|
|
+ >
|
|
|
+ {data.itemList.map((m: any, mIndex: number) => {
|
|
|
+ return (
|
|
|
+ <SwiperSlide class={styles.swipeItem} key={'index' + mIndex}>
|
|
|
+ <div
|
|
|
+ class={[styles.itemDiv]}
|
|
|
+ onClick={() => {
|
|
|
+ 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' ? (
|
|
|
+ <>
|
|
|
+ <video
|
|
|
+ class={['player']}
|
|
|
+ playsinline="false"
|
|
|
+ muted={m.muted}
|
|
|
+ preload="auto"
|
|
|
+ data-vid={m.id}
|
|
|
+ src={m.content}
|
|
|
+ loop={m.loop}
|
|
|
+ autoplay={m.autoPlay}
|
|
|
+ onLoadedmetadata={(e: Event) => {
|
|
|
+ const videoEle = e.target as unknown as HTMLVideoElement
|
|
|
+ m.currentTime = videoEle.currentTime
|
|
|
+ m.duration = videoEle.duration
|
|
|
+ m.videoEle = videoEle
|
|
|
+ m.isprepare = true
|
|
|
+ }}
|
|
|
+ onTimeupdate={(e: Event) => {
|
|
|
+ if (!m.isprepare) return
|
|
|
+ const videoEle = e.target as unknown as HTMLVideoElement
|
|
|
+ m.currentTime = videoEle.currentTime
|
|
|
+ m.progress = Number((videoEle.currentTime / m.duration) * 100)
|
|
|
+ }}
|
|
|
+ onPlay={() => {
|
|
|
+ // 播放
|
|
|
+ m.paused = false
|
|
|
+ console.log('播放')
|
|
|
+ setModelOpen()
|
|
|
+ // 第一次播放
|
|
|
+ if (m.muted) {
|
|
|
+ m.muted = false
|
|
|
+ m.autoPlay = false
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ onPause={() => {
|
|
|
+ //暂停
|
|
|
+ clearTimeout(activeData.timer)
|
|
|
+ m.paused = true
|
|
|
+ }}
|
|
|
+ onEnded={() => handleEnded(m)}
|
|
|
+ >
|
|
|
+ <source src={m.content} type="video/mp4" />
|
|
|
+ </video>
|
|
|
+ {m.muted && (
|
|
|
+ <div class={styles.loadWrap}>
|
|
|
+ <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ <div
|
|
|
+ style={{ transform: activeData.model ? '' : 'translateY(100%)' }}
|
|
|
+ class={styles.bottomFixedContainer}
|
|
|
+ onClick={(e: Event) => {
|
|
|
+ e.stopPropagation()
|
|
|
+ setModelOpen()
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div style={{ opacity: m.isprepare ? '1' : '0' }}>
|
|
|
+ <div class={styles.time}>
|
|
|
+ <span>{getSecondRPM(m.currentTime)}</span>
|
|
|
+ <span>{getSecondRPM(m.duration)}</span>
|
|
|
+ </div>
|
|
|
+ <div class={styles.slider}>
|
|
|
+ <Slider
|
|
|
+ onClick={() => 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}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={styles.actions}>
|
|
|
+ <div class={styles.actionBtn}>
|
|
|
+ {m.isprepare ? (
|
|
|
+ <>
|
|
|
+ {m.paused ? (
|
|
|
+ <img src={iconplay} onClick={(e: Event) => handlePlay(m)} />
|
|
|
+ ) : (
|
|
|
+ <img
|
|
|
+ src={iconpause}
|
|
|
+ onClick={(e: Event) => handlePaused(m)}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <Loading color="#fff" />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {m.loop ? (
|
|
|
+ <img
|
|
|
+ src={iconLoopActive}
|
|
|
+ onClick={(e: Event) => (m.loop = false)}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <img src={iconLoop} onClick={(e: Event) => (m.loop = true)} />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ <div>{m.name}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ ) : m.type === 'IMG' ? (
|
|
|
+ <img src={m.content} />
|
|
|
+ ) : (
|
|
|
+ <MusicScore
|
|
|
+ data-vid={m.id}
|
|
|
+ music={m}
|
|
|
+ onSetIframe={(el: any) => {
|
|
|
+ m.iframeRef = el
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </SwiperSlide>
|
|
|
+ )
|
|
|
+ })}
|
|
|
+ </Swiper>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div
|
|
|
+ style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
|
|
|
+ id="coursePlayHeader"
|
|
|
+ class={styles.headerContainer}
|
|
|
+ ref={headeRef}
|
|
|
+ >
|
|
|
+ <div class={styles.backBtn} onClick={() => goback()}>
|
|
|
+ <Icon name={iconBack} />
|
|
|
+ 返回
|
|
|
+ </div>
|
|
|
+ <div class={styles.menu}>{popupData.tabName}</div>
|
|
|
+ {data.isCourse && <PlayRecordTime ref={playRef} list={data.itemList} />}
|
|
|
+ </div>
|
|
|
+ <Transition name="right">
|
|
|
+ {activeData.model && (
|
|
|
+ <div class={styles.rightFixedBtns}>
|
|
|
+ <div class={styles.btnsWrap}>
|
|
|
+ <div
|
|
|
+ class={[styles.fullBtn, styles.point]}
|
|
|
+ onClick={() => {
|
|
|
+ clearTimeout(activeData.timer)
|
|
|
+ popupData.open = true
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <img src={iconMenu} />
|
|
|
+ <span>知识点</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={[styles.btnsWrap, styles.btnsBottom]}>
|
|
|
+ <div class={styles.fullBtn} onClick={() => (popupData.guideOpen = true)}>
|
|
|
+ <img src={iconTouping} />
|
|
|
+ <span>投屏</span>
|
|
|
+ </div>
|
|
|
+ {data.isCourse && (
|
|
|
+ <>
|
|
|
+ <div class={styles.fullBtn} onClick={() => gotoRollCall('student_roll_call')}>
|
|
|
+ <img src={iconDian} />
|
|
|
+ <span>点名</span>
|
|
|
+ </div>
|
|
|
+ <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
|
|
|
+ <img src={iconPoint} />
|
|
|
+ <span>签退</span>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </Transition>
|
|
|
+ <Transition name="left">
|
|
|
+ {activeData.model && (
|
|
|
+ <div class={styles.leftFixedBtns}>
|
|
|
+ {popupData.activeIndex != 0 && (
|
|
|
+ <div
|
|
|
+ class={[styles.fullBtn, styles.prePoint]}
|
|
|
+ onClick={() => handlePreAndNext('up')}
|
|
|
+ >
|
|
|
+ <img src={iconUp} />
|
|
|
+ <span style={{ textAlign: 'center' }}>上一个</span>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ {popupData.activeIndex != data.itemList.length - 1 && (
|
|
|
+ <div class={[styles.fullBtn]} onClick={() => handlePreAndNext('down')}>
|
|
|
+ <span style={{ textAlign: 'center' }}>下一个</span>
|
|
|
+ <img src={iconDown} />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </Transition>
|
|
|
+ <Popup
|
|
|
+ class={styles.popup}
|
|
|
+ overlayClass={styles.overlayClass}
|
|
|
+ position="right"
|
|
|
+ round
|
|
|
+ v-model:show={popupData.open}
|
|
|
+ onClose={() => {
|
|
|
+ const item = data.itemList[popupData.activeIndex]
|
|
|
+ if (item?.type == 'VIDEO') {
|
|
|
+ setModelOpen()
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <Points
|
|
|
+ data={data.knowledgePointList}
|
|
|
+ tabActive={popupData.tabActive}
|
|
|
+ itemActive={popupData.itemActive}
|
|
|
+ onHandleSelect={(res: any) => {
|
|
|
+ // console.log(res)
|
|
|
+ popupData.tabActive = res.tabActive
|
|
|
+ popupData.itemActive = res.itemActive
|
|
|
+ popupData.tabName = res.tabName
|
|
|
+ popupData.open = false
|
|
|
+ toggleMaterial()
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </Popup>
|
|
|
+
|
|
|
+ <Popup
|
|
|
+ class={styles.popup}
|
|
|
+ overlayClass={styles.overlayClass}
|
|
|
+ position="right"
|
|
|
+ round
|
|
|
+ v-model:show={popupData.guideOpen}
|
|
|
+ >
|
|
|
+ <OGuide />
|
|
|
+ </Popup>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+})
|