Explorar el Código

课件播放重构0.1

liushengqiang hace 2 años
padre
commit
fd65bd5056

+ 139 - 28
src/views/coursewarePlay/component/video-play.tsx

@@ -1,32 +1,143 @@
-import { defineComponent, nextTick, onMounted } from "vue";
+import { defineComponent, nextTick, onMounted, toRefs } from 'vue'
 import 'plyr/dist/plyr.css'
-import Plyr from "plyr";
-import { ref } from "vue";
+import Plyr from 'plyr'
+import { ref } from 'vue'
+import styles from './video.module.less'
+
+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'
 
 export default defineComponent({
-    name: 'video-play',
-    props:{
-        item: {
-            type: Object,
-            default: () => {return {}}
-        }
-    },
-    setup(props){
-        const videoRef = ref()
-        const item = props.item
-        onMounted(() => {
-            const v = new Plyr(videoRef.value, {
-                muted: true
-            })
-            v.play()
-            nextTick(() => {
-                v.muted = false
-            })
-        })
-        return () => (
-            <div style={{width: '100%', height: '100%'}}>
-                <video style={{width: '100%', height: '100%'}} src={item.content} ref={videoRef}></video>
-            </div>
-        )
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    }
+  },
+  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
+  setup(props, { emit, expose }) {
+    const { item } = toRefs(props)
+    const videoRef = ref()
+    const videoItem = ref<Plyr>()
+    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 1000)
+    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 1000)
+    const loopBtnId = 'loop' + Date.now() + Math.floor(Math.random() * 1000)
+    const playIcon = 'icon' + Date.now() + Math.floor(Math.random() * 1000)
+    const toggleHideControl = (isShow: false) => {
+      videoItem.value?.toggleControls(isShow)
+    }
+    const togglePlay = (e: Event) => {
+      e.stopPropagation()
+      videoItem.value?.togglePlay()
+      emit('togglePlay', videoItem.value?.paused)
     }
-})
+    const toggleLoop = (e: Event) => {
+      const loopBtn = document.getElementById(loopBtnId)
+      if (!loopBtn || !videoItem.value) return
+      const isLoop = videoItem.value.loop
+      if (isLoop) {
+        loopBtn.innerHTML = `<img src="${iconLoop}" />`
+      } else {
+        loopBtn.innerHTML = `<img src="${iconLoopActive}" />`
+      }
+      videoItem.value.loop = !videoItem.value.loop
+    }
+    const onDefault = () => {
+      document.getElementById(controlID)?.addEventListener('click', (e: Event) => {
+        e.stopPropagation()
+        emit('reset')
+      })
+      document.getElementById(playBtnId)?.addEventListener('click', togglePlay)
+      document.getElementById(loopBtnId)?.addEventListener('click', toggleLoop)
+    }
+
+    const changePlayBtn = (code: string) => {
+      const playBtn = document.getElementById(playBtnId)
+      if (!playBtn) return
+      if (code == 'play') {
+        playBtn.innerHTML = `<img class="${playIcon}" src="${iconplay}" />`
+      } else {
+        playBtn.innerHTML = `<img class="${playIcon}" src="${iconpause}" />`
+      }
+    }
+    const controls = `
+            <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
+                <div class="${styles.time}">
+                    <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
+                    <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
+                </div>
+                <div class="${styles.slider}">
+                    <div class="plyr__progress">
+                        <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
+                        <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
+                        <span role="tooltip" class="plyr__tooltip">00:00</span>
+                    </div>
+                </div>
+                <div class="${styles.actions}">
+                    <div class="${styles.actionWrap}">
+                        <button id="${playBtnId}" class="${styles.actionBtn}">
+                            <div class="van-loading van-loading--circular" aria-live="polite" aria-busy="true"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(255, 255, 255);"><svg class="van-loading__circular" viewBox="25 25 50 50"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>  
+                        </button>
+                        <button id="${loopBtnId}" class="${styles.actionBtn}">
+                            <img src="${iconLoop}" />
+                        </button>
+                    </div>
+                    <div>${item.value.name}</div>
+                </div>
+            </div>`
+
+    onMounted(() => {
+      videoItem.value = new Plyr(videoRef.value, {
+        muted: true,
+        controls: controls,
+        autopause: true, // 一次只允许
+        ratio: '16:9', // 强制所有视频的纵横比
+        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
+        clickToPlay: false // 单击(或点击)视频容器将切换播放/暂停
+      })
+      if (videoItem.value) {
+        videoItem.value.once('loadedmetadata', (e: Event) => {
+          changePlayBtn('play')
+          console.log('自动播放', item.value.autoPlay)
+          if (item.value.autoPlay && videoItem.value) {
+            videoItem.value.play()
+          }
+          emit('loadedmetadata', videoItem.value)
+        })
+        videoItem.value.on('play', () => {
+          if (videoItem.value) {
+            videoItem.value.muted = false
+          }
+          if (!item.value.isprepare) return
+          changePlayBtn('')
+        })
+        videoItem.value.on('pause', () => changePlayBtn('play'))
+        videoItem.value.on('ended', (e: Event) => {
+          emit('ended')
+          changePlayBtn('play')
+        })
+        nextTick(() => {
+          onDefault()
+        })
+      }
+    })
+    expose({
+      changePlayBtn,
+      toggleHideControl
+    })
+    return () => (
+      <div class={styles.videoWrap}>
+        <video
+          style={{ width: '100%', height: '100%' }}
+          src={item.value.content}
+          ref={videoRef}
+        ></video>
+      </div>
+    )
+  }
+})

+ 86 - 0
src/views/coursewarePlay/component/video.module.less

@@ -0,0 +1,86 @@
+.videoWrap {
+    width: 100%;
+    height: 100%;
+
+    :global {
+        .plyr--video {
+            width: 100%;
+            height: 100%;
+        }
+        .plyr__time{
+            display: block !important;
+        }
+    }
+}
+
+:global(.bottomFixed).controls {
+    width: 100%;
+    background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+    padding: 0;
+    flex-direction: column;
+    transition: all 0.5s;
+    .time {
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+        color: #fff;
+        font-size: 10px;
+        padding: 4px 20px;
+
+        :global {
+            .plyr__time+.plyr__time:before {
+                content: '';
+            }
+        }
+    }
+
+    .slider {
+        width: 100%;
+        padding: 0 20px;
+
+        :global {
+            .van-slider__button {
+                background: var(--van-primary);
+            }
+
+            .van-loading {
+                width: 100%;
+                height: 100%;
+            }
+        }
+    }
+
+    .actions {
+        display: flex;
+        justify-content: space-between;
+        width: 100%;
+        color: #fff;
+        font-size: 12px;
+        padding: 0 20px;
+        align-items: center;
+        .actionWrap{
+            display: flex;
+        }
+
+        .actionBtn {
+            display: flex;
+            width: 38px;
+            height: 38px;
+            padding: 4px 0;
+            background: transparent;
+        }
+
+        .actionBtn>img {
+            width: 100%;
+            height: 100%;
+            display: block;
+            
+        }
+        :global{
+            .van-loading__circular{
+                width: 100%;
+                height: 100%;
+            }
+        }
+    }
+}

+ 282 - 0
src/views/coursewarePlay/play.module.less

@@ -0,0 +1,282 @@
+.playContent {
+  width: 100vw;
+  height: 100vh;
+  background-color: #000;
+  overflow: hidden;
+  --plyr-color-main: var(--van-primary);
+  --plyr-range-track-height: 3px;
+  // :global {
+  //   // 更改动画样式
+  //   .van-swipe__track {
+  //     height: 100% !important;
+  //     transform: translateY(0px) !important;
+  //     position: relative;
+  //   }
+  // }
+}
+.coursewarePlay {
+  position: relative;
+  height: 100vh;
+  margin: 0 auto;
+  overflow: hidden;
+}
+.playModel {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  box-shadow: inset 0px 0px 164px 0px rgba(0, 0, 0, 1);
+  pointer-events: none;
+}
+.headerContainer {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background: linear-gradient(180deg, rgba(0, 0, 0, 0.6), transparent);
+  transition: transform 0.5s;
+}
+.backBtn {
+  color: #fff;
+  height: 26px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 10;
+  padding: 4px 10px 4px 15px;
+  :global {
+    .van-icon {
+      margin-right: 8px;
+    }
+  }
+}
+.menu {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 12px;
+  color: #fff;
+}
+.tabsContent {
+  width: 100vw;
+  height: 100vh;
+  :global {
+    .van-tabs__wrap {
+      display: none !important;
+    }
+    .van-tabs__content {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+.swipeItem {
+  overflow: hidden;
+}
+.itemDiv {
+  position: absolute;
+  left: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  background-color: #000;
+  video {
+    width: 100%;
+    height: 100%;
+  }
+  img {
+    display: block;
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+  }
+}
+.fullBtn {
+  width: 38px;
+  height: 46px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  color: #fff;
+  justify-content: space-evenly;
+  overflow: hidden;
+  white-space: nowrap;
+  &:active {
+    background: rgba(255, 255, 255, 0.2);
+  }
+}
+.rightFixedBtns {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  right: 12px;
+  z-index: 10;
+  
+  .btnsBottom {
+    margin-top: 10px;
+  }
+}
+.leftFixedBtns {
+  position: absolute;
+  top: 50%;
+  transform: translateY(-50%);
+  left: 12px;
+  z-index: 10;
+  .prePoint {
+    margin-bottom: 8px;
+  }
+}
+.btnsWrap {
+  background: rgba(51, 51, 51, 0.4);
+  border-radius: 6px;
+  overflow: hidden;
+}
+
+.bottomFixedContainer {
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 10;
+  background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+  transition: transform 0.5s;
+  .time {
+    display: flex;
+    justify-content: space-between;
+    color: #fff;
+    font-size: 10px;
+    padding: 4px 20px;
+  }
+  .slider {
+    padding: 8px 20px;
+    :global {
+      .van-slider__button {
+        background: var(--van-primary);
+      }
+    }
+  }
+  .actions {
+    display: flex;
+    justify-content: space-between;
+    color: #fff;
+    font-size: 12px;
+    padding: 0 10px 4px 20px;
+    align-items: center;
+    .actionBtn {
+      display: flex;
+    }
+    .actionBtn > img {
+      width: 30px;
+      height: 30px;
+      display: block;
+      padding: 4px 10px 4px 4px;
+    }
+  }
+}
+.popup {
+  background: rgba(0, 0, 0, 0.5);
+}
+.overlayClass {
+  --van-overlay-background: transparent;
+}
+:global {
+  .top-enter-active,
+  .top-leave-active {
+    transition: transform 0.5s;
+  }
+  .top-enter-from,
+  .top-leave-to {
+    transform: translateY(-100%);
+  }
+
+  .left-enter-active,
+  .left-leave-active {
+    transition: all 0.5s;
+  }
+  .left-enter-from,
+  .left-leave-to {
+    left: -60px;
+    opacity: 0;
+  }
+
+  .right-enter-active,
+  .right-leave-active {
+    transition: all 0.5s;
+  }
+
+  .right-enter-from,
+  .right-leave-to {
+    right: -60px;
+    opacity: 0;
+  }
+
+  .bottom-enter-active,
+  .bottom-leave-active {
+    transition: transform 0.5s;
+  }
+
+  .bottom-enter-from,
+  .bottom-leave-to {
+    transform: translateY(100%);
+  }
+}
+
+.loadWrap {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(45deg, #21232a, #111218);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.playRecordTime {
+  width: 90px;
+  margin-right: 10px;
+  background: rgba(0, 0, 0, 0.4);
+  border-radius: 20px;
+  font-size: 12px;
+  padding: 6px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  .timeLoad {
+    width: 5px;
+    height: 5px;
+    background: #ff4e19;
+    border: 0.5px solid #ffffff;
+    border-radius: 50%;
+    margin-right: 3px;
+    animation: loadFade 1s ease-in-out infinite;
+  }
+}
+
+@keyframes loadFade {
+  0% {
+    opacity: 0;
+  }
+  50% {
+    opacity: 0.5;
+  }
+  100% {
+    opacity: 1;
+  }
+}
+.swiperContainer{
+  :global{
+    .swiper-wrapper{
+      height: 100vh;
+    }
+  }
+}

+ 247 - 418
src/views/coursewarePlay/play.tsx

@@ -21,7 +21,7 @@ import {
   TransitionGroup
 } from 'vue'
 import iconBack from './image/back.svg'
-import styles from './index.module.less'
+import styles from './play.module.less'
 import 'plyr/dist/plyr.css'
 import request from '@/helpers/request'
 import { state } from '@/state'
@@ -44,8 +44,7 @@ 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 VideoPlay from './component/video-play'
 import {
   Pagination,
   Navigation,
@@ -73,11 +72,11 @@ export default defineComponent({
       const activeItem = data.itemList[popupData.activeIndex]
       if (activeItem.type != 'VIDEO') return
       if (value == 'hidden') {
-        isPlay.value = !activeItem.paused
-        handlePaused(activeItem)
+        isPlay.value = !activeItem.videoEle.paused
+        togglePlay(activeItem, false)
       } else {
         // 页面显示,并且
-        if (isPlay.value) handlePlay(activeItem)
+        if (isPlay.value) togglePlay(activeItem, true)
       }
     })
     /** 设置播放容器 16:9 */
@@ -96,13 +95,6 @@ export default defineComponent({
       }
     }
     const handleInit = (type = 0) => {
-      // postMessage({
-      //   api: 'courseLoading',
-      //   content: {
-      //     show: true,
-      //     type: 'fullscreen'
-      //   }
-      // })
       //设置容器16:9
       setContainer()
       // 横屏
@@ -149,7 +141,8 @@ export default defineComponent({
       itemList: [] as any,
       showHead: true,
       isCourse: false,
-      isRecordPlay: false
+      isRecordPlay: false,
+      videoRefs: {}
     })
     const activeData = reactive({
       nowTime: 0,
@@ -208,57 +201,31 @@ export default defineComponent({
             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,
+            videoEle: null,
             tabName: item.name,
             isLast: j === itemLength, // 当前知识点
             autoPlay: false, //加载完成是否自动播放
-            display: false
+            isprepare: false, // 视频是否加载完成
+            isRender: 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
-      }
+      let _firstIndex = list.findIndex((n: any) => n.materialId == route.query.kId)
+      _firstIndex = _firstIndex > -1 ? _firstIndex : 0
+      const item = list[_firstIndex]
+      item.autoPlay = true
+      popupData.activeIndex = _firstIndex
+      popupData.tabName = item.tabName
+      popupData.tabActive = item.knowledgePointId
+      popupData.itemActive = item.id
+      popupData.itemName = item.name
       // console.log('🚀 ~ list', list)
       nextTick(() => {
         data.itemList = list
@@ -270,8 +237,6 @@ export default defineComponent({
           }
         })
       })
-      // setTimeout(() => {
-      // }, 300)
     }
     const getDetail = async () => {
       try {
@@ -311,7 +276,6 @@ export default defineComponent({
       }
     }
 
-    const swiperDom = ref()
     onMounted(() => {
       const hasVip = handleCheckVip()
       if (!hasVip) {
@@ -343,9 +307,7 @@ export default defineComponent({
       postMessage({ api: 'goBack' })
     }
 
-    const swipeRef = ref<SwipeInstance>()
     const popupData = reactive({
-      firstIndex: 0,
       open: false,
       activeIndex: 0,
       tabActive: '',
@@ -356,37 +318,22 @@ export default defineComponent({
     })
 
     /**停止所有的播放 */
-    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 handleStop = (preIndex: number) => {
+      const activeItem = data.itemList[preIndex]
+      if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
+        activeItem.videoEle.stop()
+      }
+      // console.log('🚀 ~ activeItem:', activeItem)
+      // 停止曲谱的播放
+      if (activeItem.type === 'SONG') {
+        activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
       }
     }
     // 切换素材
     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)
+        handleSwipeChange(index)
       }
     }
     /** 延迟收起模态框 */
@@ -395,37 +342,9 @@ export default defineComponent({
       closeToast()
       activeData.timer = setTimeout(() => {
         activeData.model = false
+        Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(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) => {
@@ -442,7 +361,6 @@ export default defineComponent({
 
     // 双击
     const handleDbClick = (item: any) => {
-      // console.log(item)
       if (item && item.type === 'VIDEO') {
         const videoEle: HTMLVideoElement = item.videoEle
         if (videoEle) {
@@ -457,31 +375,12 @@ export default defineComponent({
       }
     }
 
-    // 暂停播放
-    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 togglePlay = (m: any, isPlay: boolean) => {
+      if (isPlay) {
+        m.videoEle?.play()
+      } else {
+        m.videoEle?.pause()
       }
     }
 
@@ -562,309 +461,239 @@ export default defineComponent({
         }
       }
     ]
+
+    // 轮播切换
+    const handleSwipeChange = (index: number) => {
+      handleStop(popupData.activeIndex)
+      console.log('轮播切换')
+      popupData.activeIndex = 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
+        }
+        console.log('🚀 ~ VIDEO', item, item.type === 'VIDEO' && item.videoEle)
+        if (item.type === 'VIDEO') {
+          console.log("🚀 ~ item:",item, item.autoPlay, popupData.activeIndex)
+          // 自动播放下一个视频
+          clearTimeout(activeData.timer)
+          closeToast()
+          item.autoPlay = true
+          nextTick(() => {
+            item.videoEle?.play()
+          })
+        }
+      }
+    }
+
     const swiperType = ref(effects[3])
     // 上一个知识点, 下一个知识点
     const handlePreAndNext = (type: string) => {
-      setTimeout(() => {
-        if (type === 'up') {
-          swiperDom.value.slidePrev(800)
-        } else {
-          swiperDom.value.slideNext(800)
-        }
-      }, 400)
+      if (type === 'up') {
+        handleSwipeChange(popupData.activeIndex - 1)
+      } else {
+        handleSwipeChange(popupData.activeIndex + 1)
+      }
     }
 
     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
+          onClick={() => {
+            clearTimeout(activeData.timer)
+            activeData.model = !activeData.model
+            Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(activeData.model))
+          }}
+        >
+          <div class={styles.coursewarePlay} style={{ width: parentContainer.width }}>
+            {data.itemList.map((m: any, mIndex: number) => {
+              const isRender = m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2
+              if (isRender) {
+                m.isRender = true
+              }
+              return isRender ? (
+                <div
+                  key={'index' + mIndex}
+                  class={[styles.itemDiv]}
+                  style={{ zIndex: popupData.activeIndex == mIndex ? 10 : 1 }}
+                  onClick={(e: Event) => {
+                    e.stopPropagation()
+                    clearTimeout(activeData.timer)
+                    if (Date.now() - activeData.nowTime < 300) {
+                      handleDbClick(m)
+                      return
+                    }
+                    activeData.nowTime = Date.now()
+                    activeData.timer = setTimeout(() => {
+                      activeData.model = !activeData.model
+                      Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(activeData.model))
+                      if (activeData.model) {
+                        setModelOpen()
+                      }
+                    }, 300)
+                  }}
+                >
+                  {m.type === 'VIDEO' ? (
+                    <>
+                      <VideoPlay
+                        ref={(v: any) => (data.videoRefs[mIndex] = v)}
+                        item={m}
+                        onLoadedmetadata={(videoItem: any) => {
+                          m.videoEle = videoItem
+                          m.isprepare = true
+                        }}
+                        onTogglePlay={(paused: boolean) => {
+                          m.autoPlay = false
+                          if (paused) {
+                            clearTimeout(activeData.timer)
+                          } else {
+                            setModelOpen()
+                          }
+                        }}
+                        onEnded={() => handleSwipeChange(popupData.activeIndex + 1)}
+                        onReset={setModelOpen}
+                      />
+                      <Transition name="van-fade">
+                        {!m.isprepare && (
+                          <div class={styles.loadWrap}>
+                            <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
                           </div>
-                        </>
-                      ) : m.type === 'IMG' ? (
-                        <img src={m.content} />
-                      ) : (
-                        <MusicScore
-                          data-vid={m.id}
-                          music={m}
-                          onSetIframe={(el: any) => {
-                            m.iframeRef = el
-                          }}
-                        />
-                      )}
-                    </div>
-                  </SwiperSlide>
-                )
-              })}
-            </Swiper>
-          )}
+                        )}
+                      </Transition>
+                    </>
+                  ) : m.type === 'IMG' ? (
+                    <img src={m.content} />
+                  ) : (
+                    <MusicScore
+                      data-vid={m.id}
+                      music={m}
+                      onSetIframe={(el: any) => {
+                        m.iframeRef = el
+                      }}
+                    />
+                  )}
+                </div>
+              ) : null
+            })}
+          </div>
+        </div>
 
-          <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
+          style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
+          id="coursePlayHeader"
+          class={styles.headerContainer}
+          ref={headeRef}
+        >
+          <div class={styles.backBtn} onClick={() => goback()}>
+            <Icon name={iconBack} />
+            返回
           </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 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 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>
-            )}
-          </Transition>
-          <Transition name="left">
-            {activeData.model && (
-              <div class={styles.leftFixedBtns}>
-                {popupData.activeIndex != 0 && (
-                  <div
-                    class={[styles.fullBtn, styles.prePoint]}
-                    onClick={() => handlePreAndNext('up')}
-                  >
+            </div>
+          )}
+        </Transition>
+
+        <Transition name="left">
+          {activeData.model && (
+            <div class={styles.leftFixedBtns}>
+              {popupData.activeIndex != 0 && (
+                <div class={[styles.btnsWrap,  styles.prePoint]}>
+                  <div class={styles.fullBtn} 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')}>
+                </div>
+              )}
+              {popupData.activeIndex != data.itemList.length - 1 && (
+                <div class={styles.btnsWrap}>
+                  <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()
-              }
+                </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()
             }}
-          >
-            <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>
 
-          <Popup
-            class={styles.popup}
-            overlayClass={styles.overlayClass}
-            position="right"
-            round
-            v-model:show={popupData.guideOpen}
-          >
-            <OGuide />
-          </Popup>
-        </div>
+        <Popup
+          class={styles.popup}
+          overlayClass={styles.overlayClass}
+          position="right"
+          round
+          v-model:show={popupData.guideOpen}
+        >
+          <OGuide />
+        </Popup>
       </div>
     )
   }