Ver Fonte

更换腾讯视频

lex há 1 ano atrás
pai
commit
f60a31335d

+ 252 - 0
src/views/coursewarePlay/component/video-item/index copy.tsx

@@ -0,0 +1,252 @@
+import { defineComponent, nextTick, onMounted, reactive, toRefs, watch, ref } from 'vue'
+import 'plyr/dist/plyr.css'
+import Plyr from 'plyr'
+import styles from './index.module.less'
+import { iconVideoBg, iconLoop, iconLoopActive, iconPlay, iconPause } from '../../image/icons.json'
+
+export default defineComponent({
+  name: 'video-play',
+  props: {
+    item: {
+      type: Object,
+      default: () => {
+        return {}
+      }
+    },
+    activeModel: {
+      type: Boolean,
+      default: true
+    }
+  },
+  emits: ['play', 'pause', 'ended', 'close', 'seeked', 'seeking', 'waiting', 'timeupdate'],
+  setup(props, { emit, expose }) {
+    const { item } = toRefs(props)
+    const data = reactive({
+      videoContianerRef: null as unknown as HTMLAudioElement,
+      videoState: 'pause' as 'init' | 'play' | 'pause',
+      animationState: 'start' as 'start' | 'end',
+      videoItem: null as unknown as Plyr
+    })
+    const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100)
+    const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100)
+    const loopBtnId = 'loop' + Date.now() + Math.floor(Math.random() * 100)
+
+    const togglePlay = (e: Event) => {
+      e.stopPropagation()
+      if (!data.videoContianerRef.paused) {
+        data.videoItem?.pause()
+      } else {
+        data.videoContianerRef?.play()
+      }
+    }
+    const toggleLoop = () => {
+      const loopBtn = document.getElementById(loopBtnId)
+      if (!loopBtn || !data.videoItem) return
+      const isLoop = data.videoItem.loop
+      if (isLoop) {
+        loopBtn.classList.remove(styles.active)
+      } else {
+        loopBtn.classList.add(styles.active)
+      }
+      data.videoItem.loop = !data.videoItem.loop
+    }
+    const onDefault = () => {
+      document.getElementById(controlID)?.addEventListener('click', (e: Event) => {
+        e.stopPropagation()
+        if (data.videoContianerRef.paused) return
+        emit('close')
+      })
+      document.getElementById(controlID)?.addEventListener('touchmove', () => {
+        if (data.videoContianerRef.paused) return
+        emit('close')
+      })
+      document.getElementById(playBtnId)?.addEventListener('click', togglePlay)
+      document.getElementById(loopBtnId)?.addEventListener('click', toggleLoop)
+      setName()
+    }
+    const setName = () => {
+      const nameEl = document.getElementById('videoItemName')
+      if (nameEl) {
+        nameEl.innerHTML = item.value.name || ''
+      }
+    }
+
+    const changePlayBtn = (code: string) => {
+      const playBtn = document.getElementById(playBtnId)
+      if (!playBtn) return
+      if (code == 'play') {
+        playBtn.classList.remove(styles.btnPause)
+        playBtn.classList.add(styles.btnPlay)
+      } else {
+        playBtn.classList.remove(styles.btnPlay)
+        playBtn.classList.add(styles.btnPause)
+      }
+    }
+    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>
+                            <img class="${styles.playIcon}" src="${iconPlay}" />
+                            <img class="${styles.playIcon}" src="${iconPause}" />
+                        </button>
+                        <button id="${loopBtnId}" class="${styles.actionBtn} ${styles.loopBtn}">
+                            <img class="loop" src="${iconLoop}" />
+                            <img class="loopActive" src="${iconLoopActive}" />
+                        </button>
+                    </div>
+                    <div id="videoItemName"></div>
+                </div>
+            </div>`
+
+    onMounted(() => {
+      data.videoItem = new Plyr(data.videoContianerRef, {
+        autoplay: true,
+        controls: controls,
+        ratio: '16:9', // 强制所有视频的纵横比
+        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
+        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
+        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
+      })
+
+      nextTick(() => {
+        onDefault()
+      })
+    })
+
+    const toggleHideControl = (isShow: boolean) => {
+      data.videoItem?.toggleControls(isShow)
+    }
+    watch(
+      () => props.activeModel,
+      () => {
+        toggleHideControl(props.activeModel)
+      }
+    )
+
+    watch(
+      () => props.item,
+      () => {
+        setName()
+      }
+    )
+    let videoTimer = null as any
+    let videoTimerErrorCount = 0
+    const handlePlayVideo = () => {
+      // if (videoTimerErrorCount > 5) {
+      //   return
+      // }
+      clearTimeout(videoTimer)
+      nextTick(() => {
+        data.videoContianerRef.play().catch((err) => {
+          // console.log('🚀 ~ err:', err)
+          videoTimer = setTimeout(() => {
+            if (err?.message?.includes('play()')) {
+              emit('play')
+            }
+            handlePlayVideo()
+          }, 1000)
+        })
+      })
+      videoTimerErrorCount++
+    }
+
+    let videoErrorTimer = null as any
+    let videoErrorCount = 0
+    const handleErrorVideo = () => {
+      if (videoErrorCount > 5) {
+        return
+      }
+      clearTimeout(videoErrorTimer)
+      nextTick(() => {
+        videoErrorTimer = setTimeout(() => {
+          data.videoContianerRef.src = props.item?.content
+          emit('play')
+          data.videoContianerRef.load()
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          handleErrorVideo()
+        }, 1000)
+      })
+      videoErrorCount++
+    }
+    const getVideoRef = () => {
+      return data.videoContianerRef
+    }
+
+    const getPlyrRef = () => {
+      return data.videoItem
+    }
+    expose({
+      getVideoRef,
+      getPlyrRef
+    })
+
+    return () => (
+      <div class={styles.videoWrap}>
+        <video
+          ref={(el) => (data.videoContianerRef = el as unknown as HTMLAudioElement)}
+          class={styles.itemDiv}
+          src={props.item?.content}
+          poster={iconVideoBg}
+          webkit-playsinline
+          playsinline
+          x5-video-player-type="h5"
+          onLoadedmetadata={() => {
+            data.videoState = 'pause'
+            changePlayBtn('play')
+            nextTick(() => {
+              data.videoContianerRef.currentTime = 0
+              nextTick(handlePlayVideo)
+            })
+          }}
+          onPlay={() => {
+            videoErrorCount = 0
+            console.log('开始播放')
+            data.videoState = 'play'
+            changePlayBtn('pause')
+            emit('close')
+            emit('play')
+            clearTimeout(videoErrorTimer)
+          }}
+          onPause={() => {
+            console.log('暂停播放')
+            data.videoState = 'pause'
+            changePlayBtn('play')
+            emit('pause')
+          }}
+          onEnded={() => {
+            console.log('播放结束')
+            data.videoState = 'pause'
+            changePlayBtn('play')
+            emit('ended')
+          }}
+          onSeeked={() => {
+            emit('seeked')
+          }}
+          onSeeking={() => {
+            emit('seeking')
+          }}
+          onTimeupdate={() => {
+            emit('timeupdate')
+          }}
+          onWaiting={() => {
+            emit('waiting')
+          }}
+          onError={handleErrorVideo}
+        ></video>
+      </div>
+    )
+  }
+})

+ 124 - 26
src/views/coursewarePlay/component/video-play.tsx

@@ -1,6 +1,6 @@
 import { defineComponent, nextTick, onMounted, reactive, toRefs, watch } from 'vue'
-import 'plyr/dist/plyr.css'
-import Plyr from 'plyr'
+// import 'plyr/dist/plyr.css'
+// import Plyr from 'plyr'
 import { ref } from 'vue'
 import styles from './video.module.less'
 
@@ -45,9 +45,26 @@ export default defineComponent({
     isActive: {
       type: Boolean,
       default: false
+    },
+    activeModel: {
+      type: Boolean,
+      default: true
     }
   },
-  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset', 'error', 'close'],
+  emits: [
+    'loadedmetadata',
+    'togglePlay',
+    'ended',
+    'reset',
+    'error',
+    'close',
+    'play',
+    'pause',
+    'seeked',
+    'seeking',
+    'waiting',
+    'timeupdate'
+  ],
   setup(props, { emit, expose }) {
     const { item, isEmtry } = toRefs(props)
     const data = reactive({
@@ -72,7 +89,7 @@ export default defineComponent({
     let playTimer = null as any
     // 切换音频播放
     const onToggleAudio = (state: 'play' | 'pause') => {
-      console.log(state, 'state')
+      // console.log(state, 'state')
       clearTimeout(playTimer)
       if (state === 'play') {
         playTimer = setTimeout(() => {
@@ -112,41 +129,41 @@ export default defineComponent({
         data.timer = null
       }, 300)
     }
-    onMounted(() => {
-      videoItem.value = TCPlayer(videoID, {
-        appID: '',
-        controls: false
-        // autoplay: true
-      }) // player-container-id 为播放器容器 ID,必须与 html 中一致
-      if (videoItem.value) {
+
+    const __initVideo = () => {
+      if (videoItem.value && props.item.id) {
         videoItem.value.poster(props.item.coverImg) // 封面
-        videoItem.value.src(isEmtry.value ? '' : item.value.content) // url 播放地址
+        videoItem.value.src(item.value.content) // url 播放地址
 
         // 初步加载时
-        videoItem.value.one('loadedmetadata', (e: any) => {
-          // console.log(' Loading metadata')
+        videoItem.value.on('loadedmetadata', (e: any) => {
+          console.log(' Loading metadata')
 
           // 获取时长
           data.duration = videoItem.value.duration()
-
           // 必须在当前元素
 
           if (item.value.autoPlay && videoItem.value && props.isActive) {
-            videoItem.value?.play()
+            // videoItem.value?.play()
+            nextTick(() => {
+              videoItem.value.currentTime(0)
+              nextTick(handlePlayVideo)
+            })
           }
           emit('loadedmetadata', videoItem.value)
         })
-        videoItem.value.on('timeupdate', () => {
-          if (!props.isActive) {
-            console.log('不是激活的视频,如果在播放,就暂停')
-            videoRef.value.pause()
-          }
-        })
+        // videoItem.value.on('timeupdate', () => {
+        //   if (!props.isActive) {
+        //     console.log('不是激活的视频,如果在播放,就暂停')
+        //     videoRef.value.pause()
+        //   }
+        // })
 
         // 视频播放时加载
         videoItem.value.on('timeupdate', () => {
           if (data.timer) return
           data.currentTime = videoItem.value.currentTime()
+          emit('timeupdate')
         })
 
         // 视频播放结束
@@ -160,6 +177,18 @@ export default defineComponent({
           data.playState = 'pause'
           changePlayBtn('pause')
           emit('togglePlay', true)
+          emit('pause')
+        })
+
+        videoItem.value.on('seeked', () => {
+          emit('seeked')
+        })
+
+        videoItem.value.on('seeking', () => {
+          emit('seeking')
+        })
+        videoItem.value.on('waiting', () => {
+          emit('waiting')
         })
 
         videoItem.value.on('play', () => {
@@ -169,26 +198,94 @@ export default defineComponent({
             videoItem.value.muted = false
             videoItem.value.volume = 1
           }
-          // console.log('开始播放', item.value)
           if (!item.value.autoPlay && !item.value.isprepare && videoItem.value) {
             // 加载完成后,取消静音播放
-            console.log(videoItem.value)
+            // console.log(videoItem.value)
             videoItem.value.pause()
           }
           emit('togglePlay', videoItem.value?.paused)
+          emit('play')
         })
 
         // 视频播放异常
         videoItem.value.on('error', (e: any) => {
+          handleErrorVideo()
           emit('error')
           console.log(e, 'error')
         })
       }
+    }
+
+    let videoTimer = null as any
+    let videoTimerErrorCount = 0
+    const handlePlayVideo = () => {
+      if (videoTimerErrorCount > 5) {
+        return
+      }
+      clearTimeout(videoTimer)
+      nextTick(() => {
+        videoItem.value?.play().catch((err) => {
+          // console.log('🚀 ~ err:', err)
+          videoTimer = setTimeout(() => {
+            if (err?.message?.includes('play()')) {
+              emit('play')
+            }
+            handlePlayVideo()
+          }, 1000)
+        })
+      })
+      videoTimerErrorCount++
+    }
+
+    let videoErrorTimer = null as any
+    let videoErrorCount = 0
+    const handleErrorVideo = () => {
+      if (videoErrorCount > 5) {
+        return
+      }
+      clearTimeout(videoErrorTimer)
+      nextTick(() => {
+        videoErrorTimer = setTimeout(() => {
+          videoItem.value.src = props.item?.content
+          emit('play')
+          videoItem.value.load()
+          // eslint-disable-next-line @typescript-eslint/no-unused-vars
+          handleErrorVideo()
+        }, 1000)
+      })
+      videoErrorCount++
+    }
+
+    onMounted(() => {
+      videoItem.value = TCPlayer(videoID, {
+        appID: '',
+        controls: false
+        // autoplay: true
+      }) // player-container-id 为播放器容器 ID,必须与 html 中一致
+      __initVideo()
     })
+
+    watch(
+      () => props.item,
+      () => {
+        __initVideo()
+      }
+    )
+
+    const getVideoRef = () => {
+      return videoRef.value
+    }
+
+    const getPlyrRef = () => {
+      return videoItem.value
+    }
     expose({
       changePlayBtn,
-      toggleHideControl
+      toggleHideControl,
+      getVideoRef,
+      getPlyrRef
     })
+
     watch(
       () => props.isActive,
       (val) => {
@@ -197,11 +294,12 @@ export default defineComponent({
         }
       }
     )
+
     return () => (
       <div class={styles.videoWrap}>
         <video
           style={{ width: '100%', height: '100%' }}
-          src={isEmtry.value ? '' : item.value.content}
+          src={item.value.content}
           ref={videoRef}
           id={videoID}
           preload="auto"

+ 83 - 9
src/views/coursewarePlay/index.tsx

@@ -34,6 +34,7 @@ import Tool, { ToolItem, ToolType } from './component/tool'
 import Pen from './component/tools/pen'
 import VideoItem from './component/video-item'
 import deepClone from '@/helpers/deep-clone'
+import VideoPlay from './component/video-play'
 
 export default defineComponent({
   name: 'CoursewarePlay',
@@ -563,7 +564,7 @@ export default defineComponent({
               clearTimeout(activeData.timer)
               closeToast()
               item.autoPlay = true
-              console.log(item, 'item')
+              // console.log(item, 'item')
               // 当视屏异常时重置链接
               if (item.error) {
                 item.videoEle?.src(item.content)
@@ -738,19 +739,16 @@ export default defineComponent({
       if (repeat) {
         if (tempTime.length > 0) {
           // console.log('join video', tempTime, 'initTime', initTime)
-          tempTime[1] = Math.floor(activeVideoRef.currentTime)
+          tempTime[1] = Math.floor(activeVideoRef.currentTime())
         }
       } else {
         if (newVal) {
-          tempTime[0] = Math.floor(activeVideoRef.currentTime)
+          tempTime[0] = Math.floor(activeVideoRef.currentTime())
         } else {
-          tempTime[1] = Math.floor(activeVideoRef.currentTime)
+          tempTime[1] = Math.floor(activeVideoRef.currentTime())
         }
       }
 
-      // console.log(newVal, repeat, tempTime, tempTime.length, 'videoIntervalRef.isActive.value in')
-      // console.log(activeVideoRef.speed, 'speed')
-
       if (tempTime.length >= 2) {
         // console.log(tempTime, 'tempTime', moreTime.value)
         // 处理在短时间内的时间差 【视屏拖动,点击可能会导致时间差太大】
@@ -842,7 +840,7 @@ export default defineComponent({
               }
               class={styles.itemDiv}
             >
-              <VideoItem
+              {/* <VideoItem
                 ref={(el: any) => (data.videoItemRef = el)}
                 item={activeVideoItem.value}
                 activeModel={activeData.model}
@@ -890,6 +888,81 @@ export default defineComponent({
                     videoIntervalRef.resume()
                   }
                 }}
+              /> */}
+              <VideoPlay
+                ref={(el: any) => (data.videoItemRef = el)}
+                item={activeVideoItem.value}
+                activeModel={activeData.model}
+                // isEmtry={isEmtry}
+                onPlay={() => {
+                  data.videoState = 'play'
+                  data.animationState = 'end'
+                }}
+                onLoadedmetadata={(videoItem: any) => {
+                  data.videoState = 'play'
+                  activeVideoItem.value.videoEle = videoItem
+                  if (!activeVideoItem.value.isprepare) {
+                    activeVideoItem.value.isprepare = true
+                  }
+                }}
+                onPause={() => {
+                  clearTimeout(activeData.timer)
+                  activeData.model = true
+                  videoIntervalRef.pause()
+                }}
+                onSeeked={() => {
+                  videoIntervalRef.isActive.value && videoIntervalRef.pause()
+                }}
+                onSeeking={() => {
+                  videoIntervalRef.isActive.value && videoIntervalRef.pause()
+                }}
+                onWaiting={() => {
+                  videoIntervalRef.isActive.value && videoIntervalRef.pause()
+                }}
+                onTimeupdate={() => {
+                  const activeVideoRef = data.videoItemRef?.getPlyrRef()
+                  if (
+                    !videoIntervalRef.isActive.value &&
+                    activeVideoRef?.currentTime() > 0 &&
+                    !activeVideoRef?.paused()
+                  ) {
+                    videoIntervalRef.resume()
+                  }
+                }}
+                onTogglePlay={(paused: boolean) => {
+                  // console.log('播放切换', paused)
+                  // 首次播放完成
+                  if (!activeVideoItem.value.isprepare) {
+                    activeVideoItem.value.isprepare = true
+                  }
+                  activeVideoItem.value.autoPlay = false
+                  if (paused || popupData.open || popupData.guideOpen) {
+                    clearTimeout(activeData.timer)
+                  } else {
+                    setModelOpen()
+                  }
+                }}
+                onEnded={async () => {
+                  const _index = popupData.activeIndex + 1
+                  if (_index < data.itemList.length) {
+                    handleSwipeChange(_index)
+                  } else {
+                    // 说明是最后一个
+                    intervalFnRef.value.pause()
+                    // 同步数据时先进行有效时间进行保存
+                    initVideoCount(false, true)
+                    await updateStat()
+                  }
+                }}
+                onReset={() => {
+                  if (!activeVideoItem.value.videoEle?.paused) {
+                    setModelOpen()
+                  }
+                }}
+                onError={() => {
+                  // 视屏异常
+                  activeVideoItem.value.error = true
+                }}
               />
             </div>
             {data.itemList.map((m: any, mIndex: number) => {
@@ -972,7 +1045,8 @@ export default defineComponent({
                   <Transition name="van-fade">
                     {m.type === 'VIDEO' &&
                       data.animationState !== 'end' &&
-                      data.videoState != 'play' && (
+                      data.videoState != 'play' &&
+                      !m.isprepare && (
                         <div class={styles.loadWrap}>
                           <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
                         </div>

+ 0 - 6
vite.config.ts

@@ -11,14 +11,8 @@ function resolve(dir: string) {
 }
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
-<<<<<<< HEAD
 // const proxyUrl = 'https://online.lexiaoya.cn/';
 const proxyUrl = 'https://test.lexiaoya.cn/'
-=======
-// const proxyUrl = 'https://online.lexiaoya.cn/'
-// const proxyUrl = 'https://test.lexiaoya.cn/'
-const proxyUrl = 'https://dev.lexiaoya.cn/'
->>>>>>> online
 // const proxyUrl = 'http://47.98.131.38:8989/'
 // const proxyUrl = 'http://192.168.3.20:8989/' // 邹旋
 // const proxyUrl = 'http://192.168.3.143:8989/' // 尚科