video-play.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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 iconLoop from '../image/icon-loop.svg';
  20. import iconLoopActive from '../image/icon-loop-active.svg';
  21. import iconSpeed from '../image/icon-speed.png';
  22. import { NSlider } from 'naive-ui';
  23. export default defineComponent({
  24. name: 'video-play',
  25. props: {
  26. item: {
  27. type: Object,
  28. default: () => {
  29. return {};
  30. }
  31. },
  32. showModel: {
  33. type: Boolean,
  34. default: false
  35. },
  36. isEmtry: {
  37. type: Boolean,
  38. default: false
  39. }
  40. },
  41. emits: [
  42. 'canplay',
  43. 'pause',
  44. 'togglePlay',
  45. 'ended',
  46. 'reset',
  47. 'error',
  48. 'close',
  49. 'loadedmetadata'
  50. ],
  51. setup(props, { emit, expose }) {
  52. const { item, isEmtry } = toRefs(props);
  53. const videoFroms = reactive({
  54. paused: true,
  55. currentTimeNum: 0,
  56. currentTime: '00:00',
  57. durationNum: 0,
  58. duration: '00:00',
  59. showBar: true,
  60. showAction: true,
  61. loop: false,
  62. speedControl: false,
  63. speedStyle: {
  64. left: '1px'
  65. },
  66. defaultSpeed: 1 // 默认速度
  67. });
  68. const videoRef = ref();
  69. const videoItem = ref();
  70. const videoID = ref('video' + Date.now() + Math.floor(Math.random() * 100));
  71. const speedBtnId = 'speed' + Date.now() + Math.floor(Math.random() * 100);
  72. // 对时间进行格式化
  73. const timeFormat = (num: number) => {
  74. if (num > 0) {
  75. const m = Math.floor(num / 60);
  76. const s = num % 60;
  77. return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
  78. } else {
  79. return '00:00';
  80. }
  81. };
  82. // 如果视屏异常后,需要重新播放视屏
  83. const onPlay = () => {
  84. if (videoItem.value) {
  85. videoItem.value.src(item.value.content);
  86. emit('reset');
  87. }
  88. };
  89. //
  90. const toggleHideControl = (isShow: false) => {
  91. videoFroms.showBar = isShow;
  92. videoFroms.speedControl = false;
  93. };
  94. const onReplay = () => {
  95. videoFroms.speedControl = false;
  96. if (!videoItem.value) return;
  97. videoItem.value.currentTime(0);
  98. };
  99. // 切换音频播放
  100. const onToggleVideo = (e?: MouseEvent) => {
  101. e?.stopPropagation();
  102. if (videoFroms.paused) {
  103. videoItem.value.play();
  104. videoFroms.paused = false;
  105. } else {
  106. videoItem.value.pause();
  107. videoFroms.paused = true;
  108. }
  109. emit('togglePlay', videoFroms.paused);
  110. };
  111. const __init = () => {
  112. if (videoItem.value) {
  113. videoItem.value.poster(props.item.coverImg); // 封面
  114. videoItem.value.src(item.value.content); // url 播放地址
  115. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  116. // 初步加载时
  117. videoItem.value.one('loadedmetadata', () => {
  118. // console.log(' Loading metadata');
  119. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  120. // 获取时长
  121. videoFroms.duration = timeFormat(
  122. Math.round(videoItem.value.duration())
  123. );
  124. videoFroms.durationNum = videoItem.value.duration();
  125. emit('canplay');
  126. emit('loadedmetadata', videoItem.value);
  127. });
  128. // 视频开始播放
  129. videoItem.value.on('play', () => {
  130. emit('close');
  131. emit('canplay');
  132. });
  133. // 视频播放时加载
  134. videoItem.value.on('timeupdate', () => {
  135. videoFroms.currentTime = timeFormat(
  136. Math.round(videoItem.value?.currentTime() || 0)
  137. );
  138. videoFroms.currentTimeNum = videoItem.value.currentTime();
  139. });
  140. // 视频播放结束
  141. videoItem.value.on('ended', () => {
  142. videoFroms.paused = true;
  143. emit('ended');
  144. });
  145. //
  146. videoItem.value.on('pause', () => {
  147. videoFroms.paused = true;
  148. emit('pause');
  149. });
  150. videoItem.value.on('playing', () => {
  151. videoFroms.paused = false;
  152. });
  153. videoItem.value.on('canplay', (e: any) => {
  154. // 获取时长
  155. videoFroms.duration = timeFormat(
  156. Math.round(videoItem.value.duration())
  157. );
  158. videoFroms.durationNum = videoItem.value.duration();
  159. emit('canplay');
  160. });
  161. // 视频播放异常
  162. videoItem.value.on('error', (e: any) => {
  163. emit('error');
  164. console.log(e, 'error');
  165. });
  166. }
  167. };
  168. onMounted(() => {
  169. videoItem.value = TCPlayer(videoID.value, {
  170. appID: '',
  171. controls: false
  172. }); // player-container-id 为播放器容器 ID,必须与 html 中一致
  173. __init();
  174. document.getElementById(speedBtnId)?.addEventListener('click', e => {
  175. e.stopPropagation();
  176. videoFroms.speedControl = !videoFroms.speedControl;
  177. });
  178. });
  179. const stop = () => {
  180. videoItem.value.currentTime(0);
  181. videoItem.value.pause();
  182. };
  183. const pause = () => {
  184. videoItem.value.pause();
  185. };
  186. onUnmounted(() => {
  187. if (videoItem.value) {
  188. videoItem.value.pause();
  189. videoItem.value.src('');
  190. videoItem.value.dispose();
  191. }
  192. });
  193. watch(
  194. () => props.item,
  195. () => {
  196. videoItem.value.pause();
  197. videoItem.value.currentTime(0);
  198. // videoItem.value.poster(props.item.coverImg); // 封面
  199. // videoItem.value.src(item.value.content); // url 播放地址
  200. __init();
  201. videoFroms.paused = true;
  202. }
  203. );
  204. watch(
  205. () => props.showModel,
  206. () => {
  207. // console.log(props.showModel, 'props.showModel')
  208. videoFroms.showAction = props.showModel;
  209. videoFroms.speedControl = false;
  210. }
  211. );
  212. expose({
  213. onPlay,
  214. stop,
  215. pause,
  216. // changePlayBtn,
  217. toggleHideControl
  218. });
  219. return () => (
  220. <div class={styles.videoWrap}>
  221. <video
  222. style={{ width: '100%', height: '100%' }}
  223. ref={videoRef}
  224. id={videoID.value}
  225. preload="auto"
  226. playsinline
  227. webkit-playsinline
  228. x5-video-player-type="h5"></video>
  229. <div class={styles.videoPop}></div>
  230. <div
  231. class={[
  232. styles.controls,
  233. videoFroms.showAction ? '' : styles.sectionAnimate
  234. ]}
  235. onClick={(e: MouseEvent) => {
  236. e.stopPropagation();
  237. if (videoItem.value.paused()) return;
  238. emit('close');
  239. emit('reset');
  240. }}>
  241. <div class={styles.actions}>
  242. <div class={styles.actionWrap}>
  243. <div
  244. class={styles.actionBtn}
  245. onClick={() => {
  246. videoFroms.speedControl = false;
  247. onToggleVideo();
  248. }}>
  249. {videoFroms.paused ? (
  250. <img class={styles.playIcon} src={iconplay} />
  251. ) : (
  252. <img class={styles.playIcon} src={iconpause} />
  253. )}
  254. </div>
  255. <button class={styles.iconReplay} onClick={onReplay}>
  256. <img src={iconLoop} />
  257. </button>
  258. <div class={styles.actionBtnSpeed} id={speedBtnId}>
  259. <img src={iconSpeed} />
  260. </div>
  261. </div>
  262. </div>
  263. <div class={styles.slider}>
  264. <NSlider
  265. value={videoFroms.currentTimeNum}
  266. step={0.01}
  267. max={videoFroms.durationNum}
  268. tooltip={false}
  269. onUpdate:value={(val: number) => {
  270. videoFroms.speedControl = false;
  271. videoItem.value.currentTime(val);
  272. videoFroms.currentTimeNum = val;
  273. videoFroms.currentTime = timeFormat(Math.round(val || 0));
  274. }}
  275. />
  276. </div>
  277. <div class={styles.actions}>
  278. <div class={styles.actionWrap}>
  279. <div class={styles.time}>
  280. <div
  281. class="plyr__time plyr__time--current"
  282. aria-label="Current time">
  283. {videoFroms.currentTime}
  284. </div>
  285. <span class={styles.line}>/</span>
  286. <div
  287. class="plyr__time plyr__time--duration"
  288. aria-label="Duration">
  289. {videoFroms.duration}
  290. </div>
  291. </div>
  292. </div>
  293. </div>
  294. </div>
  295. <div
  296. style={{
  297. display: videoFroms.speedControl ? 'block' : 'none'
  298. }}>
  299. <div
  300. class={styles.sliderPopup}
  301. onClick={(e: Event) => {
  302. e.stopPropagation();
  303. }}>
  304. <i
  305. class={styles.iconAdd}
  306. onClick={() => {
  307. if (videoFroms.defaultSpeed >= 1.5) {
  308. return;
  309. }
  310. if (videoItem.value) {
  311. videoFroms.defaultSpeed =
  312. (videoFroms.defaultSpeed * 10 + 1) / 10;
  313. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  314. }
  315. }}></i>
  316. <NSlider
  317. value={videoFroms.defaultSpeed}
  318. step={0.1}
  319. max={1.5}
  320. min={0.6}
  321. vertical
  322. tooltip={false}
  323. onUpdate:value={(val: number) => {
  324. videoFroms.defaultSpeed = val;
  325. if (videoItem.value) {
  326. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  327. }
  328. }}>
  329. {{
  330. thumb: () => (
  331. <div class={styles.sliderPoint}>
  332. {videoFroms.defaultSpeed}
  333. <span>x</span>
  334. </div>
  335. )
  336. }}
  337. </NSlider>
  338. <i
  339. class={[styles.iconCut]}
  340. onClick={() => {
  341. if (videoFroms.defaultSpeed <= 0.6) {
  342. return;
  343. }
  344. if (videoItem.value) {
  345. videoFroms.defaultSpeed =
  346. (videoFroms.defaultSpeed * 10 - 1) / 10;
  347. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  348. }
  349. }}></i>
  350. </div>
  351. </div>
  352. </div>
  353. );
  354. }
  355. });