|  | @@ -0,0 +1,284 @@
 | 
	
		
			
				|  |  | +import { defineComponent, nextTick, onMounted, reactive, toRefs, watch } from 'vue'
 | 
	
		
			
				|  |  | +import 'plyr/dist/plyr.css'
 | 
	
		
			
				|  |  | +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'
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +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: {
 | 
	
		
			
				|  |  | +    item: {
 | 
	
		
			
				|  |  | +      type: Object,
 | 
	
		
			
				|  |  | +      default: () => {
 | 
	
		
			
				|  |  | +        return {}
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    isEmtry: {
 | 
	
		
			
				|  |  | +      type: Boolean,
 | 
	
		
			
				|  |  | +      default: false
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    isActive: {
 | 
	
		
			
				|  |  | +      type: Boolean,
 | 
	
		
			
				|  |  | +      default: false
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  },
 | 
	
		
			
				|  |  | +  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,
 | 
	
		
			
				|  |  | +      loop: false,
 | 
	
		
			
				|  |  | +      playState: 'pause' as 'play' | 'pause',
 | 
	
		
			
				|  |  | +      vudio: null as any,
 | 
	
		
			
				|  |  | +      showBar: true
 | 
	
		
			
				|  |  | +    })
 | 
	
		
			
				|  |  | +    const videoRef = ref()
 | 
	
		
			
				|  |  | +    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()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    // }
 | 
	
		
			
				|  |  | +    let playTimer = null as any
 | 
	
		
			
				|  |  | +    // 切换音频播放
 | 
	
		
			
				|  |  | +    const onToggleAudio = (state: 'play' | 'pause') => {
 | 
	
		
			
				|  |  | +      clearTimeout(playTimer)
 | 
	
		
			
				|  |  | +      if (state === 'play') {
 | 
	
		
			
				|  |  | +        playTimer = setTimeout(() => {
 | 
	
		
			
				|  |  | +          videoItem.value?.play()
 | 
	
		
			
				|  |  | +          data.playState = 'play'
 | 
	
		
			
				|  |  | +        }, 100)
 | 
	
		
			
				|  |  | +      } else {
 | 
	
		
			
				|  |  | +        videoItem.value?.pause()
 | 
	
		
			
				|  |  | +        data.playState = 'pause'
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      emit('togglePlay', data.playState)
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    // 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)
 | 
	
		
			
				|  |  | +    //   } else {
 | 
	
		
			
				|  |  | +    //     loopBtn.classList.add(styles.active)
 | 
	
		
			
				|  |  | +    //   }
 | 
	
		
			
				|  |  | +    //   videoItem.value.loop = !videoItem.value.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)
 | 
	
		
			
				|  |  | +      // } else {
 | 
	
		
			
				|  |  | +      //   playBtn.classList.remove(styles.btnPlay)
 | 
	
		
			
				|  |  | +      //   playBtn.classList.add(styles.btnPause)
 | 
	
		
			
				|  |  | +      // }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    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 } // 不适用全屏
 | 
	
		
			
				|  |  | +      // })
 | 
	
		
			
				|  |  | +      // if (videoItem.value) {
 | 
	
		
			
				|  |  | +      //   videoItem.value.on('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 = TCPlayer(videoID, {
 | 
	
		
			
				|  |  | +        appID: '',
 | 
	
		
			
				|  |  | +        controls: false
 | 
	
		
			
				|  |  | +      }) // 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) {
 | 
	
		
			
				|  |  | +            videoItem.value?.play()
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          emit('loadedmetadata', videoItem.value)
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 视频播放时加载
 | 
	
		
			
				|  |  | +        videoItem.value.on('timeupdate', () => {
 | 
	
		
			
				|  |  | +          if (data.timer) return
 | 
	
		
			
				|  |  | +          data.currentTime = videoItem.value.currentTime()
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 视频播放结束
 | 
	
		
			
				|  |  | +        videoItem.value.on('ended', () => {
 | 
	
		
			
				|  |  | +          data.playState = 'pause'
 | 
	
		
			
				|  |  | +          emit('ended')
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        //
 | 
	
		
			
				|  |  | +        videoItem.value.on('pause', () => {
 | 
	
		
			
				|  |  | +          data.playState = 'pause'
 | 
	
		
			
				|  |  | +          changePlayBtn('play')
 | 
	
		
			
				|  |  | +          emit('togglePlay', true)
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        videoItem.value.on('playing', () => {
 | 
	
		
			
				|  |  | +          data.playState = '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('canplay', (e: any) => {
 | 
	
		
			
				|  |  | +        //   console.log('canplay', e)
 | 
	
		
			
				|  |  | +        // })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // 视频播放异常
 | 
	
		
			
				|  |  | +        videoItem.value.on('error', (e: any) => {
 | 
	
		
			
				|  |  | +          emit('error')
 | 
	
		
			
				|  |  | +          console.log(e, 'error')
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    })
 | 
	
		
			
				|  |  | +    expose({
 | 
	
		
			
				|  |  | +      changePlayBtn,
 | 
	
		
			
				|  |  | +      toggleHideControl
 | 
	
		
			
				|  |  | +    })
 | 
	
		
			
				|  |  | +    watch(
 | 
	
		
			
				|  |  | +      () => props.isActive,
 | 
	
		
			
				|  |  | +      (val) => {
 | 
	
		
			
				|  |  | +        if (!val) {
 | 
	
		
			
				|  |  | +          videoItem.value?.pause()
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    )
 | 
	
		
			
				|  |  | +    return () => (
 | 
	
		
			
				|  |  | +      <div class={styles.videoWrap}>
 | 
	
		
			
				|  |  | +        <video
 | 
	
		
			
				|  |  | +          style={{ width: '100%', height: '100%' }}
 | 
	
		
			
				|  |  | +          src={isEmtry.value ? '' : item.value.content}
 | 
	
		
			
				|  |  | +          ref={videoRef}
 | 
	
		
			
				|  |  | +          id={videoID}
 | 
	
		
			
				|  |  | +          preload="auto"
 | 
	
		
			
				|  |  | +          playsinline
 | 
	
		
			
				|  |  | +          webkit-playsinline
 | 
	
		
			
				|  |  | +        ></video>
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        <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}
 | 
	
		
			
				|  |  | +              // value={data.currentTime}
 | 
	
		
			
				|  |  | +              // max={data.duration}
 | 
	
		
			
				|  |  | +              // onUpdate:value={(val) => handleChangeTime(val)}
 | 
	
		
			
				|  |  | +            />
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +          <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={() => (data.loop = !data.loop)}>
 | 
	
		
			
				|  |  | +              <img src={data.loop ? iconLoopActive : iconLoop} />
 | 
	
		
			
				|  |  | +            </div>
 | 
	
		
			
				|  |  | +          </div>
 | 
	
		
			
				|  |  | +        </div>
 | 
	
		
			
				|  |  | +      </div>
 | 
	
		
			
				|  |  | +    )
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +})
 |