video-play.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { defineComponent, nextTick, onMounted, onUnmounted, toRefs, watch } from 'vue'
  2. import 'plyr/dist/plyr.css'
  3. import Plyr from 'plyr'
  4. import { ref } from 'vue'
  5. import styles from './video.module.less'
  6. import iconLoop from '../image/icon-loop.svg'
  7. import iconLoopActive from '../image/icon-loop-active.svg'
  8. import iconplay from '../image/icon-play.svg'
  9. import iconpause from '../image/icon-pause.svg'
  10. export default defineComponent({
  11. name: 'video-play',
  12. props: {
  13. item: {
  14. type: Object,
  15. default: () => {
  16. return {}
  17. }
  18. },
  19. isEmtry: {
  20. type: Boolean,
  21. default: false
  22. },
  23. isActive: {
  24. type: Boolean,
  25. default: false
  26. }
  27. },
  28. emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
  29. setup(props, { emit, expose }) {
  30. const { item, isEmtry } = toRefs(props)
  31. const videoRef = ref()
  32. const videoItem: any = ref()
  33. const controlID = 'v' + Date.now() + Math.floor(Math.random() * 100)
  34. const playBtnId = 'play' + Date.now() + Math.floor(Math.random() * 100)
  35. const loopBtnId = 'loop' + Date.now() + Math.floor(Math.random() * 100)
  36. const toggleHideControl = (isShow: false) => {
  37. videoItem.value?.toggleControls(isShow)
  38. }
  39. const togglePlay = (e: Event) => {
  40. e.stopPropagation()
  41. videoItem.value?.togglePlay()
  42. }
  43. const toggleLoop = (e: Event) => {
  44. const loopBtn = document.getElementById(loopBtnId)
  45. if (!loopBtn || !videoItem.value) return
  46. const isLoop = videoItem.value.loop
  47. if (isLoop) {
  48. loopBtn.classList.remove(styles.active)
  49. } else {
  50. loopBtn.classList.add(styles.active)
  51. }
  52. videoItem.value.loop = !videoItem.value.loop
  53. }
  54. const onDefault = () => {
  55. document.getElementById(controlID)?.addEventListener('click', (e: Event) => {
  56. e.stopPropagation()
  57. emit('reset')
  58. })
  59. document.getElementById(playBtnId)?.addEventListener('click', togglePlay)
  60. document.getElementById(loopBtnId)?.addEventListener('click', toggleLoop)
  61. }
  62. const changePlayBtn = (code: string) => {
  63. const playBtn = document.getElementById(playBtnId)
  64. if (!playBtn) return
  65. if (code == 'play') {
  66. playBtn.classList.remove(styles.btnPause)
  67. playBtn.classList.add(styles.btnPlay)
  68. } else {
  69. playBtn.classList.remove(styles.btnPlay)
  70. playBtn.classList.add(styles.btnPause)
  71. }
  72. }
  73. const controls = `
  74. <div id="${controlID}" class="plyr__controls bottomFixed ${styles.controls}">
  75. <div class="${styles.time}">
  76. <div class="plyr__time plyr__time--current" aria-label="Current time">00:00</div>
  77. <div class="plyr__time plyr__time--duration" aria-label="Duration">00:00</div>
  78. </div>
  79. <div class="${styles.slider}">
  80. <div class="plyr__progress">
  81. <input data-plyr="seek" type="range" min="0" max="100" step="0.01" value="0" aria-label="Seek">
  82. <progress class="plyr__progress__buffer" min="0" max="100" value="0">% buffered</progress>
  83. <span role="tooltip" class="plyr__tooltip">00:00</span>
  84. </div>
  85. </div>
  86. <div class="${styles.actions}">
  87. <div class="${styles.actionWrap}">
  88. <button id="${playBtnId}" class="${styles.actionBtn}">
  89. <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>
  90. <img class="${styles.playIcon}" src="${iconplay}" />
  91. <img class="${styles.playIcon}" src="${iconpause}" />
  92. </button>
  93. <button id="${loopBtnId}" class="${styles.actionBtn} ${styles.loopBtn}">
  94. <img class="loop" src="${iconLoop}" />
  95. <img class="loopActive" src="${iconLoopActive}" />
  96. </button>
  97. </div>
  98. <div>${item.value.name}</div>
  99. </div>
  100. </div>`
  101. onMounted(() => {
  102. videoItem.value = new Plyr(videoRef.value, {
  103. autoplay: true,
  104. controls: controls,
  105. autopause: true, // 一次只允许
  106. ratio: '16:9', // 强制所有视频的纵横比
  107. hideControls: false, // 在 2 秒没有鼠标或焦点移动、控制元素模糊(制表符退出)、播放开始或进入全屏时自动隐藏视频控件。只要移动鼠标、聚焦控制元素或暂停播放,控件就会立即重新出现。
  108. clickToPlay: false, // 单击(或点击)视频容器将切换播放/暂停
  109. fullscreen: { enabled: false, fallback: false, iosNative: false } // 不适用全屏
  110. })
  111. if (videoItem.value) {
  112. videoItem.value.on('play', () => {
  113. if (videoItem.value) {
  114. videoItem.value.muted = false
  115. videoItem.value.volume = 1
  116. }
  117. // console.log('开始播放', item.value)
  118. if (!item.value.autoPlay && !item.value.isprepare && videoItem.value) {
  119. // 加载完成后,取消静音播放
  120. videoItem.value.pause()
  121. console.log(videoItem.value?.paused, 'video status')
  122. }
  123. changePlayBtn('')
  124. emit('togglePlay', videoItem.value?.paused)
  125. })
  126. videoItem.value.on('pause', () => {
  127. changePlayBtn('play')
  128. emit('togglePlay', videoItem.value?.paused)
  129. })
  130. videoItem.value.on('ended', (e: Event) => {
  131. emit('ended')
  132. changePlayBtn('play')
  133. })
  134. videoItem.value.once('loadedmetadata', (e: Event) => {
  135. changePlayBtn('play')
  136. videoItem.value.currentTime = 0
  137. if (item.value.autoPlay && videoItem.value) {
  138. videoItem.value.play()
  139. }
  140. emit('loadedmetadata', videoItem.value)
  141. })
  142. videoItem.value.on('timeupdate', (e: Event) => {
  143. // console.log(videoItem.value?.currentTime, '111')
  144. })
  145. nextTick(() => {
  146. onDefault()
  147. })
  148. }
  149. })
  150. expose({
  151. changePlayBtn,
  152. toggleHideControl
  153. })
  154. watch(
  155. () => props.isActive,
  156. (val) => {
  157. if (!val) {
  158. console.log(props.isActive, 'isActive')
  159. videoItem.value?.pause()
  160. }
  161. }
  162. )
  163. // onUnmounted(() => {
  164. // if (videoItem.value) {
  165. // videoItem.value?.pause()
  166. // videoItem.value?.destroy()
  167. // }
  168. // })
  169. return () => (
  170. <div class={styles.videoWrap}>
  171. <video
  172. style={{ width: '100%', height: '100%' }}
  173. src={isEmtry.value ? '' : item.value.content}
  174. ref={videoRef}
  175. playsinline="false"
  176. ></video>
  177. </div>
  178. )
  179. }
  180. })