index.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. import {
  2. closeToast,
  3. Icon,
  4. Loading,
  5. Popup,
  6. showDialog,
  7. showToast,
  8. Slider,
  9. Swipe,
  10. SwipeInstance,
  11. SwipeItem
  12. } from 'vant'
  13. import {
  14. defineComponent,
  15. onMounted,
  16. reactive,
  17. nextTick,
  18. onUnmounted,
  19. ref,
  20. watch,
  21. Transition,
  22. TransitionGroup,
  23. onBeforeUnmount
  24. } from 'vue'
  25. import iconBack from './image/back.svg'
  26. import styles from './index.module.less'
  27. import 'plyr/dist/plyr.css'
  28. import request from '@/helpers/request'
  29. import { state } from '@/state'
  30. import { useRoute, useRouter } from 'vue-router'
  31. import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  32. import MusicScore from './component/musicScore'
  33. import iconMenu from './image/icon-menu.svg'
  34. import iconDian from './image/icon-dian.svg'
  35. import iconTouping from './image/icon-touping.svg'
  36. import iconPoint from './image/icon-point.svg'
  37. import iconUp from './image/icon-up.svg'
  38. import iconDown from './image/icon-down.svg'
  39. import iconMore from './image/icon-more.png'
  40. import Points from './component/points'
  41. import { browser, getSecondRPM } from '@/helpers/utils'
  42. import { Vue3Lottie } from 'vue3-lottie'
  43. import playLoadData from './datas/data.json'
  44. import { usePageVisibility, useRect } from '@vant/use'
  45. import PlayRecordTime from './playRecordTime'
  46. import VideoPlay from './component/video-play'
  47. import {
  48. Pagination,
  49. Navigation,
  50. Virtual,
  51. EffectFade,
  52. EffectFlip,
  53. EffectCreative,
  54. Lazy
  55. } from 'swiper'
  56. import { Swiper, SwiperSlide } from 'swiper/vue'
  57. import 'swiper/less'
  58. import 'swiper/less/effect-fade'
  59. import 'swiper/less/effect-flip'
  60. import 'swiper/less/effect-creative'
  61. import { handleCheckVip } from '../hook/useFee'
  62. import OGuide from '@/components/o-guide'
  63. import Tool, { ToolItem, ToolType } from './component/tool'
  64. import Tools from './component/tools/pen'
  65. import Pen from './component/tools/pen'
  66. import iconPen from './image/icon-pen.png'
  67. export default defineComponent({
  68. name: 'CoursewarePlay',
  69. setup() {
  70. const pageVisibility = usePageVisibility()
  71. const isPlay = ref(false)
  72. /** 页面显示和隐藏 */
  73. watch(pageVisibility, (value) => {
  74. const activeItem = data.itemList[popupData.activeIndex]
  75. if (activeItem.type != 'VIDEO') return
  76. if (value == 'hidden') {
  77. isPlay.value = !activeItem.videoEle?.paused
  78. togglePlay(activeItem, false)
  79. } else {
  80. // 页面显示,并且
  81. if (isPlay.value) togglePlay(activeItem, true)
  82. }
  83. })
  84. /** 设置播放容器 16:9 */
  85. const parentContainer = reactive({
  86. width: '100vw'
  87. })
  88. const setContainer = () => {
  89. let min = Math.min(screen.width, screen.height)
  90. let max = Math.max(screen.width, screen.height)
  91. let width = min * (16 / 9)
  92. if (width > max) {
  93. parentContainer.width = '100vw'
  94. return
  95. } else {
  96. parentContainer.width = width + 'px'
  97. }
  98. }
  99. const handleInit = (type = 0) => {
  100. //设置容器16:9
  101. setContainer()
  102. // 横屏
  103. postMessage(
  104. {
  105. api: 'setRequestedOrientation',
  106. content: {
  107. orientation: type
  108. }
  109. },
  110. () => {
  111. console.log(234)
  112. }
  113. )
  114. // 头,包括返回箭头
  115. // postMessage({
  116. // api: 'setTitleBarVisibility',
  117. // content: {
  118. // status: type
  119. // }
  120. // })
  121. // 安卓的状态栏
  122. postMessage({
  123. api: 'setStatusBarVisibility',
  124. content: {
  125. isVisibility: type
  126. }
  127. })
  128. // 进入页面设置常量
  129. postMessage({
  130. api: 'keepScreenLongLight',
  131. content: {
  132. isOpenLight: type ? true : false
  133. }
  134. })
  135. }
  136. handleInit()
  137. onUnmounted(() => {
  138. handleInit(1)
  139. window.removeEventListener('message', iframeHandle)
  140. })
  141. const route = useRoute()
  142. const router = useRouter()
  143. const headeRef = ref()
  144. const data = reactive({
  145. detail: null,
  146. knowledgePointList: [] as any,
  147. itemList: [] as any,
  148. showHead: true,
  149. isCourse: false,
  150. isRecordPlay: false,
  151. videoRefs: {}
  152. })
  153. const activeData = reactive({
  154. isAutoPlay: true, // 是否自动播放
  155. nowTime: 0,
  156. model: true, // 遮罩
  157. isAnimation: true, // 是否动画
  158. videoBtns: true, // 视频
  159. currentTime: 0,
  160. duration: 0,
  161. timer: null as any,
  162. item: null as any
  163. })
  164. // 获取缓存路径
  165. const getCacheFilePath = async (material: any) => {
  166. const res = await promisefiyPostMessage({
  167. api: 'getCourseFilePath',
  168. content: {
  169. url: material.content,
  170. localPath: '',
  171. materialId: material.materialId,
  172. updateTime: material.updateTime,
  173. type: material.type // SONG VIDEO IMAGE
  174. }
  175. })
  176. // console.log('缓存路径返回', res)
  177. return res
  178. }
  179. // 获取当前课程是否签退
  180. const getCourseSchedule = async () => {
  181. if (!route.query.courseId) return
  182. try {
  183. const res = await request.get(
  184. `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`,
  185. {
  186. hideLoading: true
  187. }
  188. )
  189. if (res?.data) {
  190. data.isCourse =
  191. res.data.status === 'ING' && state.platformType == 'TEACHER' ? true : false
  192. // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf()
  193. }
  194. } catch (e) {
  195. console.log(e)
  196. }
  197. }
  198. const getItemList = async () => {
  199. const list: any = []
  200. const browserInfo = browser()
  201. for (let i = 0; i < data.knowledgePointList.length; i++) {
  202. const item = data.knowledgePointList[i]
  203. for (let j = 0; j < item.materialList.length; j++) {
  204. const material = item.materialList[j]
  205. //请求本地缓存
  206. if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.type)) {
  207. const localData = await getCacheFilePath(material)
  208. if (localData?.content?.localPath) {
  209. material.url = material.content
  210. material.content = localData.content.localPath
  211. }
  212. }
  213. list.push({
  214. ...material,
  215. iframeRef: null,
  216. videoEle: null,
  217. tabName: item.name,
  218. autoPlay: false, //加载完成是否自动播放
  219. isprepare: false, // 视频是否加载完成
  220. isRender: false // 是否渲染了
  221. })
  222. }
  223. }
  224. let _firstIndex = list.findIndex((n: any) => n.materialId == route.query.kId)
  225. _firstIndex = _firstIndex > -1 ? _firstIndex : 0
  226. const item = list[_firstIndex]
  227. // 是否自动播放
  228. if (activeData.isAutoPlay) {
  229. item.autoPlay = true
  230. }
  231. popupData.activeIndex = _firstIndex
  232. popupData.tabName = item.tabName
  233. popupData.tabActive = item.knowledgePointId
  234. popupData.itemActive = item.id
  235. popupData.itemName = item.name
  236. nextTick(() => {
  237. data.itemList = list
  238. checkedAnimation(popupData.activeIndex)
  239. postMessage({
  240. api: 'courseLoading',
  241. content: {
  242. show: false,
  243. type: 'fullscreen'
  244. }
  245. })
  246. //检测是否录屏
  247. // handleLimitScreenRecord()
  248. })
  249. }
  250. const getDetail = async () => {
  251. try {
  252. const res: any = await request.get(
  253. state.platformApi + `/lessonCoursewareDetail/detail/${route.query.id}`,
  254. {
  255. hideLoading: true
  256. }
  257. )
  258. data.detail = res.data
  259. if (res?.data?.lockFlag) {
  260. postMessage({
  261. api: 'courseLoading',
  262. content: {
  263. show: false,
  264. type: 'fullscreen'
  265. }
  266. })
  267. showDialog({
  268. title: '温馨提示',
  269. message: '课件已锁定'
  270. }).then((value) => {
  271. goback()
  272. })
  273. return
  274. }
  275. if (Array.isArray(res?.data?.knowledgePointList)) {
  276. let index = 0
  277. data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
  278. if (Array.isArray(n.materialList)) {
  279. n.materialList = n.materialList.map((item: any) => {
  280. index++
  281. return {
  282. ...item,
  283. materialId: item.id,
  284. id: index + ''
  285. }
  286. })
  287. }
  288. return n
  289. })
  290. getItemList()
  291. }
  292. } catch (error) {}
  293. }
  294. // ifram事件处理
  295. const iframeHandle = (ev: MessageEvent) => {
  296. if (ev.data?.api === 'headerTogge') {
  297. activeData.model = ev.data.show || (ev.data.playState == 'play' ? false : true)
  298. }
  299. }
  300. //录屏时间触发
  301. const handleLimitScreenRecord = async () => {
  302. const result = await promisefiyPostMessage({
  303. api: 'getDeviceStatus',
  304. content: { type: 'video' }
  305. })
  306. const { status } = result?.content || {}
  307. if (status == '1') {
  308. data.itemList.forEach((item: any) => (item.autoPlay = false))
  309. handleStop()
  310. showDialog({
  311. title: '温馨提示',
  312. message: '课件内容请勿录屏',
  313. beforeClose: () => {
  314. return new Promise(async (resolve) => {
  315. const { content } =
  316. (await promisefiyPostMessage({
  317. api: 'getDeviceStatus',
  318. content: { type: 'video' }
  319. })) || {}
  320. if (content?.status == '1') {
  321. resolve(false)
  322. } else {
  323. const activeItem = data.itemList[popupData.activeIndex]
  324. togglePlay(activeItem, true)
  325. resolve(true)
  326. }
  327. })
  328. }
  329. })
  330. }
  331. }
  332. onMounted(() => {
  333. const hasVip = handleCheckVip()
  334. if (!hasVip) {
  335. nextTick(() => {
  336. postMessage({
  337. api: 'courseLoading',
  338. content: {
  339. show: false,
  340. type: 'fullscreen'
  341. }
  342. })
  343. })
  344. return
  345. }
  346. getDetail()
  347. getCourseSchedule()
  348. window.addEventListener('message', iframeHandle)
  349. //禁止录屏 ios
  350. // listenerMessage('setVideoPlayer', (result) => {
  351. // if (result?.content?.status == 'pause') {
  352. // handleLimitScreenRecord()
  353. // }
  354. // })
  355. // // 安卓
  356. // postMessage({
  357. // api: 'limitScreenRecord',
  358. // content: {
  359. // type: 1
  360. // }
  361. // })
  362. })
  363. // onBeforeUnmount(() => {
  364. // postMessage({
  365. // api: 'limitScreenRecord',
  366. // content: {
  367. // type: 0
  368. // }
  369. // })
  370. // })
  371. const playRef = ref()
  372. // 返回
  373. const goback = () => {
  374. try {
  375. playRef.value?.handleOut()
  376. } catch (error) {}
  377. postMessage({ api: 'goBack' })
  378. }
  379. const popupData = reactive({
  380. open: false,
  381. activeIndex: 0,
  382. tabActive: '',
  383. tabName: '',
  384. itemActive: '',
  385. itemName: '',
  386. guideOpen: false,
  387. toolOpen: false // 工具弹窗控制
  388. })
  389. /**停止所有的播放 */
  390. const handleStop = () => {
  391. for (let i = 0; i < data.itemList.length; i++) {
  392. const activeItem = data.itemList[i]
  393. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  394. activeItem.videoEle.stop()
  395. }
  396. // console.log('🚀 ~ activeItem:', activeItem)
  397. // 停止曲谱的播放
  398. if (activeItem.type === 'SONG') {
  399. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  400. }
  401. }
  402. }
  403. // 切换素材
  404. const toggleMaterial = (itemActive: any) => {
  405. const index = data.itemList.findIndex((n: any) => n.id == itemActive)
  406. if (index > -1) {
  407. handleSwipeChange(index)
  408. }
  409. }
  410. /** 延迟收起模态框 */
  411. const setModelOpen = () => {
  412. clearTimeout(activeData.timer)
  413. closeToast()
  414. activeData.timer = setTimeout(() => {
  415. activeData.model = false
  416. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(false))
  417. }, 4000)
  418. }
  419. /** 立即收起所有的模态框 */
  420. const clearModel = () => {
  421. clearTimeout(activeData.timer)
  422. closeToast()
  423. activeData.model = false
  424. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(false))
  425. }
  426. const toggleModel = (type: boolean = true) => {
  427. activeData.model = type
  428. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(type))
  429. }
  430. // 去点名,签退
  431. const gotoRollCall = (pageTag: string) => {
  432. postMessage({
  433. api: 'open_app_page',
  434. content: {
  435. action: 'app',
  436. pageTag: pageTag,
  437. url: '',
  438. params: JSON.stringify({ courseId: route.query.courseId })
  439. }
  440. })
  441. }
  442. // 双击
  443. const handleDbClick = (item: any) => {
  444. if (item && item.type === 'VIDEO') {
  445. const videoEle: HTMLVideoElement = item.videoEle
  446. if (videoEle) {
  447. if (videoEle.paused) {
  448. closeToast()
  449. videoEle.play()
  450. } else {
  451. showToast('已暂停')
  452. videoEle.pause()
  453. }
  454. }
  455. }
  456. }
  457. // 切换播放
  458. const togglePlay = (m: any, isPlay: boolean) => {
  459. if (isPlay) {
  460. m.videoEle?.play()
  461. } else {
  462. m.videoEle?.pause()
  463. }
  464. }
  465. const showIndex = ref(-4)
  466. const effectIndex = ref(0)
  467. const effects = [
  468. {
  469. prev: {
  470. transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
  471. },
  472. next: {
  473. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
  474. }
  475. },
  476. {
  477. prev: {
  478. transform: 'translate3d(-100%, 0, -800px)'
  479. },
  480. next: {
  481. transform: 'translate3d(100%, 0, -800px)'
  482. }
  483. },
  484. {
  485. prev: {
  486. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
  487. },
  488. next: {
  489. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
  490. }
  491. },
  492. {
  493. prev: {
  494. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
  495. },
  496. next: {
  497. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
  498. }
  499. },
  500. // 风车4
  501. {
  502. prev: {
  503. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  504. opacity: 0
  505. },
  506. next: {
  507. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  508. opacity: 0
  509. }
  510. },
  511. // 翻页5
  512. {
  513. prev: {
  514. transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  515. opacity: 0
  516. },
  517. next: {
  518. transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  519. opacity: 0
  520. },
  521. current: { transitionDelay: '700ms' }
  522. }
  523. ]
  524. const acitveTimer = ref()
  525. // 轮播切换
  526. const handleSwipeChange = (index: number) => {
  527. // 如果是当前正在播放 或者是视频最后一个
  528. if (popupData.activeIndex == index) return
  529. handleStop()
  530. clearTimeout(acitveTimer.value)
  531. const oldIndex = popupData.activeIndex
  532. checkedAnimation(popupData.activeIndex, index)
  533. popupData.activeIndex = index
  534. acitveTimer.value = setTimeout(
  535. () => {
  536. const item = data.itemList[index]
  537. if (item) {
  538. popupData.tabActive = item.knowledgePointId
  539. popupData.itemActive = item.id
  540. popupData.itemName = item.name
  541. popupData.tabName = item.tabName
  542. if (item.type == 'SONG') {
  543. activeData.model = true
  544. }
  545. if (item.type === 'VIDEO') {
  546. // 自动播放下一个视频
  547. clearTimeout(activeData.timer)
  548. closeToast()
  549. item.autoPlay = true
  550. nextTick(() => {
  551. item.videoEle?.play()
  552. })
  553. }
  554. }
  555. requestAnimationFrame(() => {
  556. const _effectIndex = effectIndex.value + 1
  557. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  558. })
  559. },
  560. activeData.isAnimation ? 800 : 0
  561. )
  562. }
  563. /** 是否有转场动画 */
  564. const checkedAnimation = (index: number, nextIndex?: number) => {
  565. const item = data.itemList[index]
  566. const nextItem = data.itemList[nextIndex!]
  567. if (nextItem) {
  568. if (nextItem.knowledgePointId != item.knowledgePointId) {
  569. activeData.isAnimation = true
  570. return
  571. }
  572. const videoEle = item.videoEle
  573. const nextVideo = nextItem.videoEle
  574. if (videoEle && videoEle.duration < 8 && index < nextIndex!) {
  575. activeData.isAnimation = false
  576. } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex!) {
  577. activeData.isAnimation = false
  578. } else {
  579. activeData.isAnimation = true
  580. }
  581. } else {
  582. activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true
  583. }
  584. }
  585. // 上一个知识点, 下一个知识点
  586. const handlePreAndNext = (type: string) => {
  587. if (type === 'up') {
  588. handleSwipeChange(popupData.activeIndex - 1)
  589. } else {
  590. handleSwipeChange(popupData.activeIndex + 1)
  591. }
  592. }
  593. /** 弹窗关闭 */
  594. const handleClosePopup = () => {
  595. const item = data.itemList[popupData.activeIndex]
  596. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  597. setModelOpen()
  598. }
  599. }
  600. /** 教学数据 */
  601. const studyData = reactive({
  602. type: '' as ToolType,
  603. penShow: false
  604. })
  605. /** 打开教学工具 */
  606. const openStudyTool = (item: ToolItem) => {
  607. const activeItem = data.itemList[popupData.activeIndex]
  608. // 暂停视频和曲谱的播放
  609. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  610. activeItem.videoEle.pause()
  611. }
  612. if (activeItem.type === 'SONG') {
  613. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  614. }
  615. clearModel()
  616. popupData.toolOpen = false
  617. studyData.type = item.type
  618. switch (item.type) {
  619. case 'pen':
  620. studyData.penShow = true
  621. break
  622. }
  623. }
  624. /** 关闭教学工具 */
  625. const closeStudyTool = () => {
  626. studyData.type = 'init'
  627. toggleModel()
  628. }
  629. return () => (
  630. <div id="playContent" class={styles.playContent}>
  631. <div
  632. onClick={() => {
  633. clearTimeout(activeData.timer)
  634. activeData.model = !activeData.model
  635. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(activeData.model))
  636. }}
  637. >
  638. <div
  639. class={styles.coursewarePlay}
  640. style={{ width: parentContainer.width }}
  641. onClick={(e: Event) => {
  642. e.stopPropagation()
  643. setModelOpen()
  644. }}
  645. >
  646. <div class={styles.wraps}>
  647. {data.itemList.map((m: any, mIndex: number) => {
  648. const isRender = m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2
  649. const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4
  650. if (isRender) {
  651. m.isRender = true
  652. }
  653. return isRender ? (
  654. <div
  655. key={'index' + mIndex}
  656. class={[
  657. styles.itemDiv,
  658. popupData.activeIndex === mIndex && styles.itemActive,
  659. activeData.isAnimation && styles.acitveAnimation,
  660. Math.abs(popupData.activeIndex - mIndex) < 2 ? styles.show : styles.hide
  661. ]}
  662. style={
  663. mIndex < popupData.activeIndex
  664. ? effects[effectIndex.value].prev
  665. : mIndex > popupData.activeIndex
  666. ? effects[effectIndex.value].next
  667. : {}
  668. }
  669. onClick={(e: Event) => {
  670. e.stopPropagation()
  671. clearTimeout(activeData.timer)
  672. if (Date.now() - activeData.nowTime < 300) {
  673. handleDbClick(m)
  674. return
  675. }
  676. activeData.nowTime = Date.now()
  677. activeData.timer = setTimeout(() => {
  678. activeData.model = !activeData.model
  679. Object.values(data.videoRefs).map((n: any) =>
  680. n.toggleHideControl(activeData.model)
  681. )
  682. if (activeData.model) {
  683. setModelOpen()
  684. }
  685. }, 300)
  686. }}
  687. >
  688. {m.type === 'VIDEO' ? (
  689. <>
  690. <VideoPlay
  691. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  692. item={m}
  693. isEmtry={isEmtry}
  694. onLoadedmetadata={(videoItem: any) => {
  695. m.videoEle = videoItem
  696. }}
  697. onTogglePlay={(paused: boolean) => {
  698. // console.log('播放切换', paused)
  699. // 首次播放完成
  700. if (!m.isprepare) {
  701. m.isprepare = true
  702. }
  703. m.autoPlay = false
  704. if (paused || popupData.open || popupData.guideOpen) {
  705. clearTimeout(activeData.timer)
  706. } else {
  707. setModelOpen()
  708. }
  709. }}
  710. onEnded={() => {
  711. const _index = popupData.activeIndex + 1
  712. if (_index < data.itemList.length) {
  713. handleSwipeChange(_index)
  714. }
  715. }}
  716. onReset={() => {
  717. if (!m.videoEle?.paused) {
  718. setModelOpen()
  719. }
  720. }}
  721. />
  722. <Transition name="van-fade">
  723. {!m.isprepare && (
  724. <div class={styles.loadWrap}>
  725. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  726. </div>
  727. )}
  728. </Transition>
  729. </>
  730. ) : m.type === 'IMG' ? (
  731. <img src={m.content} />
  732. ) : (
  733. <MusicScore
  734. activeModel={activeData.model}
  735. data-vid={m.id}
  736. music={m}
  737. onSetIframe={(el: any) => {
  738. m.iframeRef = el
  739. }}
  740. />
  741. )}
  742. </div>
  743. ) : null
  744. })}
  745. </div>
  746. <Transition name="right">
  747. {activeData.model && (
  748. <div
  749. class={styles.rightFixedBtns}
  750. onClick={(e: Event) => {
  751. e.stopPropagation()
  752. clearTimeout(activeData.timer)
  753. }}
  754. >
  755. <div class={styles.btnsWrap}>
  756. <div
  757. class={[styles.fullBtn, styles.point]}
  758. onClick={() => (popupData.open = true)}
  759. >
  760. <img src={iconMenu} />
  761. <span>知识点</span>
  762. </div>
  763. </div>
  764. <div class={[styles.btnsWrap, styles.btnsBottom]}>
  765. {/* <div class={styles.fullBtn} onClick={() => (popupData.guideOpen = true)}>
  766. <img src={iconTouping} />
  767. <span>投屏</span>
  768. </div> */}
  769. {data.isCourse && (
  770. <>
  771. <div
  772. class={styles.fullBtn}
  773. onClick={() => gotoRollCall('student_roll_call')}
  774. >
  775. <img src={iconDian} />
  776. <span>点名</span>
  777. </div>
  778. <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
  779. <img src={iconPoint} />
  780. <span>签退</span>
  781. </div>
  782. </>
  783. )}
  784. </div>
  785. </div>
  786. )}
  787. </Transition>
  788. <Transition name="left">
  789. {activeData.model && (
  790. <div class={styles.leftFixedBtns} onClick={(e: Event) => e.stopPropagation()}>
  791. {popupData.activeIndex != 0 && (
  792. <div class={[styles.btnsWrap, styles.prePoint]}>
  793. <div class={styles.fullBtn} onClick={() => handlePreAndNext('up')}>
  794. <img src={iconUp} />
  795. <span style={{ textAlign: 'center' }}>上一个</span>
  796. </div>
  797. </div>
  798. )}
  799. {popupData.activeIndex != data.itemList.length - 1 && (
  800. <div class={styles.btnsWrap}>
  801. <div class={styles.fullBtn} onClick={() => handlePreAndNext('down')}>
  802. <span style={{ textAlign: 'center' }}>下一个</span>
  803. <img src={iconDown} />
  804. </div>
  805. </div>
  806. )}
  807. </div>
  808. )}
  809. </Transition>
  810. </div>
  811. </div>
  812. <div
  813. style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
  814. id="coursePlayHeader"
  815. class={styles.headerContainer}
  816. ref={headeRef}
  817. >
  818. <div class={styles.backBtn} onClick={() => goback()}>
  819. <Icon name={iconBack} />
  820. 返回
  821. </div>
  822. {data.isCourse && <PlayRecordTime ref={playRef} list={data.knowledgePointList} />}
  823. <div
  824. class={styles.menu}
  825. onClick={() => {
  826. const _effectIndex = effectIndex.value + 1
  827. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  828. setModelOpen()
  829. }}
  830. >
  831. {popupData.tabName}
  832. </div>
  833. {state.platformType == 'TEACHER' && (
  834. <div
  835. class={styles.headRight}
  836. onClick={(e: Event) => {
  837. e.stopPropagation()
  838. clearTimeout(activeData.timer)
  839. }}
  840. >
  841. <div class={styles.rightBtn} onClick={() => (popupData.guideOpen = true)}>
  842. <img src={iconTouping} />
  843. </div>
  844. <div
  845. class={styles.rightBtn}
  846. onClick={() => {
  847. openStudyTool({
  848. type: 'pen',
  849. icon: iconPen,
  850. name: '批注'
  851. })
  852. }}
  853. >
  854. <img src={iconPen} />
  855. </div>
  856. {/* <div class={styles.rightBtn} onClick={() => (popupData.toolOpen = true)}>
  857. <img src={iconMore} />
  858. </div> */}
  859. </div>
  860. )}
  861. </div>
  862. {/* 更多弹窗 */}
  863. <Popup
  864. class={styles.popupMore}
  865. overlayClass={styles.overlayClass}
  866. position="right"
  867. round
  868. v-model:show={popupData.toolOpen}
  869. onClose={handleClosePopup}
  870. >
  871. <Tool onHandleTool={openStudyTool} />
  872. </Popup>
  873. <Popup
  874. class={styles.popup}
  875. overlayClass={styles.overlayClass}
  876. position="right"
  877. round
  878. v-model:show={popupData.open}
  879. onClose={handleClosePopup}
  880. >
  881. <Points
  882. data={data.knowledgePointList}
  883. tabActive={popupData.tabActive}
  884. itemActive={popupData.itemActive}
  885. onHandleSelect={(res: any) => {
  886. popupData.open = false
  887. toggleMaterial(res.itemActive)
  888. }}
  889. />
  890. </Popup>
  891. <Popup
  892. class={styles.popup}
  893. overlayClass={styles.overlayClass}
  894. position="right"
  895. round
  896. v-model:show={popupData.guideOpen}
  897. onClose={handleClosePopup}
  898. >
  899. <OGuide />
  900. </Popup>
  901. {studyData.penShow && (
  902. <Pen show={studyData.type === 'pen'} close={() => closeStudyTool()} />
  903. )}
  904. </div>
  905. )
  906. }
  907. })