| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- 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
- },
- activeModel: {
- type: Boolean,
- default: true
- }
- },
- 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({
- 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()
- const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100)
- const toggleHideControl = (isShow: false) => {
- data.showBar = isShow
- }
- // 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 {
- videoItem.value?.pause()
- data.playState = 'pause'
- }
- emit('togglePlay', data.playState)
- }
- 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) => {
- if (code == 'play') {
- data.playState = 'play'
- } else {
- data.playState = 'pause'
- }
- }
- /** 改变播放时间 */
- const handleChangeTime = (val: number) => {
- data.currentTime = val
- clearTimeout(data.timer)
- data.timer = setTimeout(() => {
- videoItem.value.currentTime(val)
- data.timer = null
- }, 300)
- }
- const __initVideo = () => {
- if (videoItem.value && props.item.id) {
- videoItem.value.poster(props.item.coverImg) // 封面
- videoItem.value.src(item.value.content) // url 播放地址
- // 初步加载时
- 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()
- 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 (data.timer) return
- data.currentTime = videoItem.value.currentTime()
- emit('timeupdate')
- })
- // 视频播放结束
- videoItem.value.on('ended', () => {
- changePlayBtn('play')
- emit('ended')
- })
- //
- videoItem.value.on('pause', () => {
- 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', () => {
- // console.log(play, 'playing')
- changePlayBtn('play')
- if (videoItem.value) {
- videoItem.value.muted = false
- videoItem.value.volume(1)
- }
- if (!item.value.autoPlay && !item.value.isprepare && 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,
- getVideoRef,
- getPlyrRef
- })
- watch(
- () => props.isActive,
- (val) => {
- if (!val) {
- videoItem.value?.pause()
- }
- }
- )
- return () => (
- <div class={styles.videoWrap}>
- <video
- style={{ width: '100%', height: '100%' }}
- src={item.value.content}
- ref={videoRef}
- 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>
- )
- }
- })
|