|  | @@ -1,4 +1,4 @@
 | 
	
		
			
				|  |  | -import { defineComponent, nextTick, onMounted, toRefs, watch } from 'vue'
 | 
	
		
			
				|  |  | +import { defineComponent, nextTick, onMounted, reactive, toRefs, watch } from 'vue'
 | 
	
		
			
				|  |  |  import 'plyr/dist/plyr.css'
 | 
	
		
			
				|  |  |  import Plyr from 'plyr'
 | 
	
		
			
				|  |  |  import { ref } from 'vue'
 | 
	
	
		
			
				|  | @@ -9,6 +9,26 @@ import iconLoopActive from '../image/icon-loop-active.svg'
 | 
	
		
			
				|  |  |  import iconplay from '../image/icon-play.svg'
 | 
	
		
			
				|  |  |  import iconpause from '../image/icon-pause.svg'
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import TCPlayer from 'tcplayer.js'
 | 
	
		
			
				|  |  | +import 'tcplayer.js/dist/tcplayer.min.css'
 | 
	
		
			
				|  |  | +import { Slider } from 'vant'
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// 秒转分
 | 
	
		
			
				|  |  | +export const getSecondRPM = (second: number, type?: string) => {
 | 
	
		
			
				|  |  | +  if (isNaN(second)) return '00:00'
 | 
	
		
			
				|  |  | +  const mm = Math.floor(second / 60)
 | 
	
		
			
				|  |  | +    .toString()
 | 
	
		
			
				|  |  | +    .padStart(2, '0')
 | 
	
		
			
				|  |  | +  const dd = Math.floor(second % 60)
 | 
	
		
			
				|  |  | +    .toString()
 | 
	
		
			
				|  |  | +    .padStart(2, '0')
 | 
	
		
			
				|  |  | +  if (type === 'cn') {
 | 
	
		
			
				|  |  | +    return mm + '分' + dd + '秒'
 | 
	
		
			
				|  |  | +  } else {
 | 
	
		
			
				|  |  | +    return mm + ':' + dd
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  export default defineComponent({
 | 
	
		
			
				|  |  |    name: 'video-play',
 | 
	
		
			
				|  |  |    props: {
 | 
	
	
		
			
				|  | @@ -27,126 +47,135 @@ export default defineComponent({
 | 
	
		
			
				|  |  |        default: false
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    },
 | 
	
		
			
				|  |  | -  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
 | 
	
		
			
				|  |  | +  emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset', 'error', 'close'],
 | 
	
		
			
				|  |  |    setup(props, { emit, expose }) {
 | 
	
		
			
				|  |  |      const { item, isEmtry } = toRefs(props)
 | 
	
		
			
				|  |  | +    const data = reactive({
 | 
	
		
			
				|  |  | +      timer: null as any,
 | 
	
		
			
				|  |  | +      currentTime: 0,
 | 
	
		
			
				|  |  | +      duration: 0.1,
 | 
	
		
			
				|  |  | +      loop: false,
 | 
	
		
			
				|  |  | +      playState: 'pause' as 'play' | 'pause',
 | 
	
		
			
				|  |  | +      vudio: null as any,
 | 
	
		
			
				|  |  | +      showBar: true
 | 
	
		
			
				|  |  | +    })
 | 
	
		
			
				|  |  |      const videoRef = ref()
 | 
	
		
			
				|  |  | -    const videoItem = ref<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 videoItem = ref()
 | 
	
		
			
				|  |  | +    const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100)
 | 
	
		
			
				|  |  |      const toggleHideControl = (isShow: false) => {
 | 
	
		
			
				|  |  | -      videoItem.value?.toggleControls(isShow)
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    const togglePlay = (e: Event) => {
 | 
	
		
			
				|  |  | -      e.stopPropagation()
 | 
	
		
			
				|  |  | -      videoItem.value?.togglePlay()
 | 
	
		
			
				|  |  | +      data.showBar = isShow
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    const toggleLoop = (e: Event) => {
 | 
	
		
			
				|  |  | -      const loopBtn = document.getElementById(loopBtnId)
 | 
	
		
			
				|  |  | -      if (!loopBtn || !videoItem.value) return
 | 
	
		
			
				|  |  | -      const isLoop = videoItem.value.loop
 | 
	
		
			
				|  |  | -      if (isLoop) {
 | 
	
		
			
				|  |  | -        loopBtn.classList.remove(styles.active)
 | 
	
		
			
				|  |  | +    // const togglePlay = (e: Event) => {
 | 
	
		
			
				|  |  | +    //   e.stopPropagation()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // }
 | 
	
		
			
				|  |  | +    let playTimer = null as any
 | 
	
		
			
				|  |  | +    // 切换音频播放
 | 
	
		
			
				|  |  | +    const onToggleAudio = (state: 'play' | 'pause') => {
 | 
	
		
			
				|  |  | +      console.log(state, 'state')
 | 
	
		
			
				|  |  | +      clearTimeout(playTimer)
 | 
	
		
			
				|  |  | +      if (state === 'play') {
 | 
	
		
			
				|  |  | +        playTimer = setTimeout(() => {
 | 
	
		
			
				|  |  | +          videoItem.value?.play()
 | 
	
		
			
				|  |  | +          data.playState = 'play'
 | 
	
		
			
				|  |  | +        }, 100)
 | 
	
		
			
				|  |  |        } else {
 | 
	
		
			
				|  |  | -        loopBtn.classList.add(styles.active)
 | 
	
		
			
				|  |  | +        videoItem.value?.pause()
 | 
	
		
			
				|  |  | +        data.playState = 'pause'
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  | -      videoItem.value.loop = !videoItem.value.loop
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      emit('togglePlay', data.playState)
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    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 toggleLoop = () => {
 | 
	
		
			
				|  |  | +      if (!videoItem.value) return
 | 
	
		
			
				|  |  | +      if (data.loop) {
 | 
	
		
			
				|  |  | +        videoItem.value.loop(false)
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        videoItem.value.loop(true)
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      data.loop = !data.loop
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      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)
 | 
	
		
			
				|  |  | +        data.playState = 'play'
 | 
	
		
			
				|  |  |        } else {
 | 
	
		
			
				|  |  | -        playBtn.classList.remove(styles.btnPlay)
 | 
	
		
			
				|  |  | -        playBtn.classList.add(styles.btnPause)
 | 
	
		
			
				|  |  | +        data.playState = 'pause'
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    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>${item.value.name}</div>
 | 
	
		
			
				|  |  | -                </div>
 | 
	
		
			
				|  |  | -            </div>`
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /** 改变播放时间 */
 | 
	
		
			
				|  |  | +    const handleChangeTime = (val: number) => {
 | 
	
		
			
				|  |  | +      data.currentTime = val
 | 
	
		
			
				|  |  | +      clearTimeout(data.timer)
 | 
	
		
			
				|  |  | +      data.timer = setTimeout(() => {
 | 
	
		
			
				|  |  | +        videoItem.value.currentTime(val)
 | 
	
		
			
				|  |  | +        data.timer = null
 | 
	
		
			
				|  |  | +      }, 300)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |      onMounted(() => {
 | 
	
		
			
				|  |  | -      videoItem.value = new Plyr(videoRef.value, {
 | 
	
		
			
				|  |  | -        autoplay: true,
 | 
	
		
			
				|  |  | -        controls: controls,
 | 
	
		
			
				|  |  | -        autopause: true, // 一次只允许
 | 
	
		
			
				|  |  | -        ratio: '16:9', // 强制所有视频的纵横比
 | 
	
		
			
				|  |  | -        hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
 | 
	
		
			
				|  |  | -        clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
 | 
	
		
			
				|  |  | -        fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
 | 
	
		
			
				|  |  | -      })
 | 
	
		
			
				|  |  | +      videoItem.value = TCPlayer(videoID, {
 | 
	
		
			
				|  |  | +        appID: '',
 | 
	
		
			
				|  |  | +        controls: false
 | 
	
		
			
				|  |  | +        // autoplay: true
 | 
	
		
			
				|  |  | +      }) // player-container-id 为播放器容器 ID,必须与 html 中一致
 | 
	
		
			
				|  |  |        if (videoItem.value) {
 | 
	
		
			
				|  |  | +        videoItem.value.poster(props.item.coverImg) // 封面
 | 
	
		
			
				|  |  | +        videoItem.value.src(isEmtry.value ? '' : item.value.content) // url 播放地址
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 初步加载时
 | 
	
		
			
				|  |  | +        videoItem.value.one('loadedmetadata', (e: any) => {
 | 
	
		
			
				|  |  | +          // console.log(' Loading metadata')
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // 获取时长
 | 
	
		
			
				|  |  | +          data.duration = videoItem.value.duration()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          // 必须在当前元素
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +          if (item.value.autoPlay && videoItem.value && props.isActive) {
 | 
	
		
			
				|  |  | +            videoItem.value?.play()
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          emit('loadedmetadata', videoItem.value)
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 视频播放时加载
 | 
	
		
			
				|  |  | +        videoItem.value.on('timeupdate', () => {
 | 
	
		
			
				|  |  | +          if (data.timer) return
 | 
	
		
			
				|  |  | +          data.currentTime = videoItem.value.currentTime()
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 视频播放结束
 | 
	
		
			
				|  |  | +        videoItem.value.on('ended', () => {
 | 
	
		
			
				|  |  | +          changePlayBtn('play')
 | 
	
		
			
				|  |  | +          emit('ended')
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        //
 | 
	
		
			
				|  |  | +        videoItem.value.on('pause', () => {
 | 
	
		
			
				|  |  | +          data.playState = 'pause'
 | 
	
		
			
				|  |  | +          changePlayBtn('pause')
 | 
	
		
			
				|  |  | +          emit('togglePlay', true)
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          videoItem.value.on('play', () => {
 | 
	
		
			
				|  |  | +          // console.log(play, 'playing')
 | 
	
		
			
				|  |  | +          changePlayBtn('play')
 | 
	
		
			
				|  |  |            if (videoItem.value) {
 | 
	
		
			
				|  |  |              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)
 | 
	
		
			
				|  |  |              videoItem.value.pause()
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  | -          changePlayBtn('')
 | 
	
		
			
				|  |  |            emit('togglePlay', videoItem.value?.paused)
 | 
	
		
			
				|  |  |          })
 | 
	
		
			
				|  |  | -        videoItem.value.on('pause', () => {
 | 
	
		
			
				|  |  | -          changePlayBtn('play')
 | 
	
		
			
				|  |  | -          emit('togglePlay', videoItem.value?.paused)
 | 
	
		
			
				|  |  | -        })
 | 
	
		
			
				|  |  | -        videoItem.value.on('ended', (e: Event) => {
 | 
	
		
			
				|  |  | -          emit('ended')
 | 
	
		
			
				|  |  | -          changePlayBtn('play')
 | 
	
		
			
				|  |  | -        })
 | 
	
		
			
				|  |  | -        videoItem.value.once('loadedmetadata', (e: Event) => {
 | 
	
		
			
				|  |  | -          changePlayBtn('play')
 | 
	
		
			
				|  |  | -          if (item.value.autoPlay && videoItem.value) {
 | 
	
		
			
				|  |  | -            videoItem.value.play()
 | 
	
		
			
				|  |  | -          }
 | 
	
		
			
				|  |  | -          emit('loadedmetadata', videoItem.value)
 | 
	
		
			
				|  |  | -        })
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        nextTick(() => {
 | 
	
		
			
				|  |  | -          onDefault()
 | 
	
		
			
				|  |  | +        // 视频播放异常
 | 
	
		
			
				|  |  | +        videoItem.value.on('error', (e: any) => {
 | 
	
		
			
				|  |  | +          emit('error')
 | 
	
		
			
				|  |  | +          console.log(e, 'error')
 | 
	
		
			
				|  |  |          })
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  |      })
 | 
	
	
		
			
				|  | @@ -168,8 +197,55 @@ export default defineComponent({
 | 
	
		
			
				|  |  |            style={{ width: '100%', height: '100%' }}
 | 
	
		
			
				|  |  |            src={isEmtry.value ? '' : item.value.content}
 | 
	
		
			
				|  |  |            ref={videoRef}
 | 
	
		
			
				|  |  | -          playsinline="false"
 | 
	
		
			
				|  |  | +          id={videoID}
 | 
	
		
			
				|  |  | +          preload="auto"
 | 
	
		
			
				|  |  | +          playsinline
 | 
	
		
			
				|  |  | +          webkit-playsinline
 | 
	
		
			
				|  |  |          ></video>
 | 
	
		
			
				|  |  | +        <div class={styles.videoSection}></div>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        <div
 | 
	
		
			
				|  |  | +          class={[styles.controls, data.showBar ? '' : styles.hide]}
 | 
	
		
			
				|  |  | +          onClick={(e: Event) => {
 | 
	
		
			
				|  |  | +            e.stopPropagation()
 | 
	
		
			
				|  |  | +          }}
 | 
	
		
			
				|  |  | +          // onTouchmove={(e: TouchEvent) => {
 | 
	
		
			
				|  |  | +          //   emit('close')
 | 
	
		
			
				|  |  | +          // }}
 | 
	
		
			
				|  |  | +        >
 | 
	
		
			
				|  |  | +          <div class={styles.time}>
 | 
	
		
			
				|  |  | +            <div>{getSecondRPM(data.currentTime)}</div>
 | 
	
		
			
				|  |  | +            <div>{getSecondRPM(data.duration)}</div>
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +          <div class={styles.slider}>
 | 
	
		
			
				|  |  | +            <Slider
 | 
	
		
			
				|  |  | +              step={0.01}
 | 
	
		
			
				|  |  | +              class={styles.timeProgress}
 | 
	
		
			
				|  |  | +              v-model={data.currentTime}
 | 
	
		
			
				|  |  | +              max={data.duration}
 | 
	
		
			
				|  |  | +              onUpdate:modelValue={(val) => {
 | 
	
		
			
				|  |  | +                handleChangeTime(val)
 | 
	
		
			
				|  |  | +              }}
 | 
	
		
			
				|  |  | +            />
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +          <div class={styles.actionSection}>
 | 
	
		
			
				|  |  | +            <div class={styles.actions} onClick={() => emit('close')}>
 | 
	
		
			
				|  |  | +              <div
 | 
	
		
			
				|  |  | +                class={styles.actionBtn}
 | 
	
		
			
				|  |  | +                onClick={(e: any) => {
 | 
	
		
			
				|  |  | +                  e.stopPropagation()
 | 
	
		
			
				|  |  | +                  onToggleAudio(data.playState === 'pause' ? 'play' : 'pause')
 | 
	
		
			
				|  |  | +                }}
 | 
	
		
			
				|  |  | +              >
 | 
	
		
			
				|  |  | +                <img src={data.playState === 'pause' ? iconplay : iconpause} />
 | 
	
		
			
				|  |  | +              </div>
 | 
	
		
			
				|  |  | +              <div class={styles.actionBtn} onClick={toggleLoop}>
 | 
	
		
			
				|  |  | +                <img src={data.loop ? iconLoopActive : iconLoop} />
 | 
	
		
			
				|  |  | +              </div>
 | 
	
		
			
				|  |  | +            </div>
 | 
	
		
			
				|  |  | +            <div class={styles.name}>{item.value.name}</div>
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  |        </div>
 | 
	
		
			
				|  |  |      )
 | 
	
		
			
				|  |  |    }
 |