video-play.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import {
  2. defineComponent,
  3. nextTick,
  4. onMounted,
  5. onUnmounted,
  6. reactive,
  7. toRefs,
  8. watch
  9. } from 'vue';
  10. import TCPlayer from 'tcplayer.js';
  11. import 'tcplayer.js/dist/tcplayer.min.css';
  12. // import 'plyr/dist/plyr.css';
  13. // import Plyr from 'plyr';
  14. import { ref } from 'vue';
  15. import styles from './video.module.less';
  16. import iconplay from '../image/icon-pause.png';
  17. import iconpause from '../image/icon-play.png';
  18. import iconReplay from '../image/icon-replay.png';
  19. import { NSlider } from 'naive-ui';
  20. export default defineComponent({
  21. name: 'video-play',
  22. props: {
  23. item: {
  24. type: Object,
  25. default: () => {
  26. return {};
  27. }
  28. },
  29. showModel: {
  30. type: Boolean,
  31. default: false
  32. },
  33. isEmtry: {
  34. type: Boolean,
  35. default: false
  36. }
  37. },
  38. emits: [
  39. 'canplay',
  40. 'pause',
  41. 'togglePlay',
  42. 'ended',
  43. 'reset',
  44. 'error',
  45. 'close',
  46. 'loadedmetadata'
  47. ],
  48. setup(props, { emit, expose }) {
  49. const { item, isEmtry } = toRefs(props);
  50. const videoFroms = reactive({
  51. paused: true,
  52. currentTimeNum: 0,
  53. currentTime: '00:00',
  54. durationNum: 0,
  55. duration: '00:00',
  56. showBar: true,
  57. showAction: true
  58. });
  59. const videoRef = ref();
  60. const videoItem = ref();
  61. const videoID = ref('video' + Date.now() + Math.floor(Math.random() * 100));
  62. // 对时间进行格式化
  63. const timeFormat = (num: number) => {
  64. if (num > 0) {
  65. const m = Math.floor(num / 60);
  66. const s = num % 60;
  67. return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
  68. } else {
  69. return '00:00';
  70. }
  71. };
  72. // 如果视屏异常后,需要重新播放视屏
  73. const onPlay = () => {
  74. if (videoItem.value) {
  75. videoItem.value.src(item.value.content);
  76. emit('reset');
  77. }
  78. };
  79. //
  80. const toggleHideControl = (isShow: false) => {
  81. videoFroms.showBar = isShow;
  82. };
  83. const onReplay = () => {
  84. if (!videoItem.value) return;
  85. videoItem.value.currentTime(0);
  86. };
  87. // 切换音频播放
  88. const onToggleVideo = (e?: MouseEvent) => {
  89. e?.stopPropagation();
  90. if (videoFroms.paused) {
  91. videoItem.value.play();
  92. videoFroms.paused = false;
  93. } else {
  94. videoItem.value.pause();
  95. videoFroms.paused = true;
  96. }
  97. emit('togglePlay', videoFroms.paused);
  98. };
  99. onMounted(() => {
  100. videoItem.value = TCPlayer(videoID.value, {
  101. appID: '',
  102. controls: false
  103. }); // player-container-id 为播放器容器 ID,必须与 html 中一致
  104. if (videoItem.value) {
  105. videoItem.value.poster(props.item.coverImg); // 封面
  106. videoItem.value.src(item.value.content); // url 播放地址
  107. // 初步加载时
  108. videoItem.value.one('loadedmetadata', () => {
  109. console.log(' Loading metadata');
  110. // 获取时长
  111. videoFroms.duration = timeFormat(
  112. Math.round(videoItem.value.duration())
  113. );
  114. videoFroms.durationNum = videoItem.value.duration();
  115. emit('canplay');
  116. emit('loadedmetadata', videoItem.value);
  117. });
  118. // 视频开始播放
  119. videoItem.value.on('play', () => {
  120. emit('close');
  121. emit('canplay');
  122. });
  123. // 视频播放时加载
  124. videoItem.value.on('timeupdate', () => {
  125. videoFroms.currentTime = timeFormat(
  126. Math.round(videoItem.value?.currentTime() || 0)
  127. );
  128. videoFroms.currentTimeNum = videoItem.value.currentTime();
  129. });
  130. // 视频播放结束
  131. videoItem.value.on('ended', () => {
  132. videoFroms.paused = true;
  133. emit('ended');
  134. });
  135. //
  136. videoItem.value.on('pause', () => {
  137. videoFroms.paused = true;
  138. emit('pause');
  139. });
  140. videoItem.value.on('playing', () => {
  141. videoFroms.paused = false;
  142. });
  143. videoItem.value.on('canplay', (e: any) => {
  144. // 获取时长
  145. videoFroms.duration = timeFormat(
  146. Math.round(videoItem.value.duration())
  147. );
  148. videoFroms.durationNum = videoItem.value.duration();
  149. emit('canplay');
  150. });
  151. // 视频播放异常
  152. videoItem.value.on('error', (e: any) => {
  153. emit('error');
  154. console.log(e, 'error');
  155. });
  156. }
  157. });
  158. const stop = () => {
  159. videoItem.value.currentTime(0);
  160. videoItem.value.pause();
  161. };
  162. onUnmounted(() => {
  163. if (videoItem.value) {
  164. videoItem.value.pause();
  165. videoItem.value.src('');
  166. videoItem.value.dispose();
  167. }
  168. });
  169. watch(
  170. () => props.item,
  171. (val, oldVal) => {
  172. videoItem.value.pause();
  173. videoItem.value.currentTime(0);
  174. videoItem.value.poster(props.item.coverImg); // 封面
  175. videoItem.value.src(item.value.content); // url 播放地址
  176. videoFroms.paused = true;
  177. }
  178. );
  179. watch(
  180. () => props.showModel,
  181. () => {
  182. // console.log(props.showModel, 'props.showModel')
  183. videoFroms.showAction = props.showModel;
  184. }
  185. );
  186. expose({
  187. onPlay,
  188. stop,
  189. // changePlayBtn,
  190. toggleHideControl
  191. });
  192. return () => (
  193. <div class={styles.videoWrap}>
  194. <video
  195. style={{ width: '100%', height: '100%' }}
  196. ref={videoRef}
  197. id={videoID.value}
  198. preload="auto"
  199. playsinline
  200. webkit-playsinline
  201. x5-video-player-type="h5"></video>
  202. <div
  203. class={[
  204. styles.controls,
  205. videoFroms.showAction ? '' : styles.sectionAnimate
  206. ]}
  207. onClick={(e: MouseEvent) => {
  208. e.stopPropagation();
  209. if (videoItem.value.paused()) return;
  210. emit('close');
  211. emit('reset');
  212. }}>
  213. <div class={styles.actions}>
  214. <div class={styles.actionWrap}>
  215. <button class={styles.actionBtn} onClick={onToggleVideo}>
  216. {videoFroms.paused ? (
  217. <img class={styles.playIcon} src={iconplay} />
  218. ) : (
  219. <img class={styles.playIcon} src={iconpause} />
  220. )}
  221. </button>
  222. </div>
  223. <div class={styles.time}>
  224. <div
  225. class="plyr__time plyr__time--current"
  226. aria-label="Current time">
  227. {videoFroms.currentTime}
  228. </div>
  229. <span class={styles.line}>/</span>
  230. <div
  231. class="plyr__time plyr__time--duration"
  232. aria-label="Duration">
  233. {videoFroms.duration}
  234. </div>
  235. </div>
  236. </div>
  237. <div class={styles.slider}>
  238. <NSlider
  239. value={videoFroms.currentTimeNum}
  240. step={0.01}
  241. max={videoFroms.durationNum}
  242. tooltip={false}
  243. onUpdate:value={(val: number) => {
  244. videoItem.value.currentTime(val);
  245. videoFroms.currentTimeNum = val;
  246. videoFroms.currentTime = timeFormat(Math.round(val || 0));
  247. }}
  248. />
  249. </div>
  250. <div class={styles.actions}>
  251. <div class={styles.actionWrap}>
  252. <button class={styles.iconReplay} onClick={onReplay}>
  253. <img src={iconReplay} />
  254. </button>
  255. </div>
  256. </div>
  257. </div>
  258. </div>
  259. );
  260. }
  261. });