index.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import { defineComponent, nextTick, onMounted, reactive, toRefs } from 'vue';
  2. // import 'plyr/dist/plyr.css';
  3. // import Plyr from 'plyr';
  4. import { ref } from 'vue';
  5. import TCPlayer from 'tcplayer.js';
  6. import 'tcplayer.js/dist/tcplayer.min.css';
  7. import styles from './index.module.less';
  8. import iconplay from '@views/attend-class/image/icon-pause.png';
  9. import iconpause from '@views/attend-class/image/icon-play.png';
  10. import iconReplay from '@views/attend-class/image/icon-replay.png';
  11. import iconPreviewDownload from '@views/attend-class/image/icon-preivew-download.png';
  12. import iconSpeed from '@views/attend-class/image/icon-speed.png';
  13. import { NSlider, useMessage } from 'naive-ui';
  14. import { saveAs } from 'file-saver';
  15. export default defineComponent({
  16. name: 'video-play',
  17. props: {
  18. src: {
  19. type: String,
  20. default: ''
  21. },
  22. title: {
  23. type: String,
  24. default: ''
  25. },
  26. poster: {
  27. type: String,
  28. default: ''
  29. },
  30. isEmtry: {
  31. type: Boolean,
  32. default: false
  33. },
  34. isDownload: {
  35. type: Boolean,
  36. default: false
  37. }
  38. },
  39. emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
  40. setup(props, { emit, expose }) {
  41. const message = useMessage();
  42. const { src, poster, isEmtry } = toRefs(props);
  43. const videoFroms = reactive({
  44. paused: true,
  45. currentTimeNum: 0,
  46. currentTime: '00:00',
  47. durationNum: 0,
  48. duration: '00:00',
  49. showBar: true,
  50. speedControl: false,
  51. speedStyle: {
  52. left: '1px'
  53. },
  54. defaultSpeed: 1 // 默认速度
  55. });
  56. const videoRef = ref();
  57. const videoItem = ref();
  58. const videoID = 'video' + Date.now() + Math.floor(Math.random() * 100);
  59. // 对时间进行格式化
  60. const timeFormat = (num: number) => {
  61. if (num > 0) {
  62. const m = Math.floor(num / 60);
  63. const s = num % 60;
  64. return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
  65. } else {
  66. return '00:00';
  67. }
  68. };
  69. //
  70. const toggleHideControl = (isShow: false) => {
  71. videoFroms.showBar = isShow;
  72. videoFroms.speedControl = false;
  73. };
  74. const onReplay = () => {
  75. videoFroms.speedControl = false;
  76. if (!videoItem.value) return;
  77. videoItem.value.currentTime(0);
  78. };
  79. // 切换音频播放
  80. const onToggleVideo = (e?: MouseEvent) => {
  81. videoFroms.speedControl = false;
  82. e?.stopPropagation();
  83. if (videoFroms.paused) {
  84. videoItem.value.play();
  85. videoFroms.paused = false;
  86. } else {
  87. videoItem.value.pause();
  88. videoFroms.paused = true;
  89. }
  90. emit('togglePlay', videoFroms.paused);
  91. };
  92. // 下载资源
  93. const onDownload = () => {
  94. if (!props.src) {
  95. message.error('下载失败');
  96. return;
  97. }
  98. const fileUrl = props.src;
  99. // 发起Fetch请求
  100. fetch(fileUrl)
  101. .then(response => response.blob())
  102. .then(blob => {
  103. saveAs(blob, props.title || new Date().getTime() + '');
  104. })
  105. .catch(() => {
  106. message.error('下载失败');
  107. });
  108. };
  109. const __init = () => {
  110. if (videoItem.value) {
  111. videoItem.value.poster(poster.value); // 封面
  112. videoItem.value.src(isEmtry.value ? '' : src.value); // url 播放地址
  113. // 初步加载时
  114. videoItem.value.one('loadedmetadata', () => {
  115. console.log(' Loading metadata');
  116. // 获取时长
  117. videoFroms.duration = timeFormat(
  118. Math.round(videoItem.value.duration())
  119. );
  120. videoFroms.durationNum = videoItem.value.duration();
  121. emit('loadedmetadata', videoItem.value);
  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. };
  137. onMounted(() => {
  138. videoItem.value = TCPlayer(videoID, {
  139. appID: '',
  140. controls: false
  141. }); // player-container-id 为播放器容器 ID,必须与 html 中一致
  142. __init();
  143. });
  144. expose({
  145. // changePlayBtn,
  146. toggleHideControl
  147. });
  148. return () => (
  149. <div class={styles.videoWrap}>
  150. <video
  151. style={{ width: '100%', height: '100%' }}
  152. src={isEmtry.value ? '' : src.value}
  153. poster={poster.value}
  154. ref={videoRef}
  155. id={videoID}
  156. preload="auto"
  157. playsinline
  158. webkit-playsinline></video>
  159. <div
  160. class={[
  161. styles.controls,
  162. videoFroms.showBar ? '' : styles.sectionAnimate
  163. ]}
  164. onClick={(e: MouseEvent) => {
  165. e.stopPropagation();
  166. emit('reset');
  167. }}>
  168. <div class={styles.actions}>
  169. <div class={styles.actionWrap}>
  170. <button class={styles.actionBtn} onClick={onToggleVideo}>
  171. {videoFroms.paused ? (
  172. <img class={styles.playIcon} src={iconplay} />
  173. ) : (
  174. <img class={styles.playIcon} src={iconpause} />
  175. )}
  176. </button>
  177. <button class={styles.iconReplay} onClick={onReplay}>
  178. <img src={iconReplay} />
  179. </button>
  180. <div
  181. class={styles.actionBtnSpeed}
  182. onClick={() => {
  183. videoFroms.speedControl = !videoFroms.speedControl;
  184. }}>
  185. <img src={iconSpeed} />
  186. </div>
  187. </div>
  188. </div>
  189. <div class={styles.slider}>
  190. <NSlider
  191. value={videoFroms.currentTimeNum}
  192. step={0.01}
  193. max={videoFroms.durationNum}
  194. tooltip={false}
  195. onUpdate:value={(val: number) => {
  196. videoFroms.speedControl = false;
  197. videoItem.value.currentTime(val);
  198. videoFroms.currentTimeNum = val;
  199. videoFroms.currentTime = timeFormat(Math.round(val || 0));
  200. }}
  201. />
  202. </div>
  203. <div class={styles.actions}>
  204. <div class={styles.time}>
  205. <div
  206. class="plyr__time plyr__time--current"
  207. aria-label="Current time">
  208. {videoFroms.currentTime}
  209. </div>
  210. <span class={styles.line}>/</span>
  211. <div
  212. class="plyr__time plyr__time--duration"
  213. aria-label="Duration">
  214. {videoFroms.duration}
  215. </div>
  216. </div>
  217. <div class={styles.actionWrap}>
  218. {props.isDownload && (
  219. <button class={styles.iconDownload} onClick={onDownload}>
  220. <img src={iconPreviewDownload} />
  221. </button>
  222. )}
  223. </div>
  224. </div>
  225. </div>
  226. <div
  227. style={{
  228. display: videoFroms.speedControl ? 'block' : 'none'
  229. }}>
  230. <div
  231. class={styles.sliderPopup}
  232. onClick={(e: Event) => {
  233. e.stopPropagation();
  234. }}>
  235. <i
  236. class={styles.iconAdd}
  237. onClick={() => {
  238. if (videoFroms.defaultSpeed >= 1.5) {
  239. return;
  240. }
  241. if (videoItem.value) {
  242. videoFroms.defaultSpeed =
  243. (videoFroms.defaultSpeed * 10 + 1) / 10;
  244. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  245. }
  246. }}></i>
  247. <NSlider
  248. value={videoFroms.defaultSpeed}
  249. step={0.1}
  250. max={1.5}
  251. min={0.5}
  252. vertical
  253. tooltip={false}
  254. onUpdate:value={(val: number) => {
  255. videoFroms.defaultSpeed = val;
  256. if (videoItem.value) {
  257. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  258. }
  259. }}>
  260. {{
  261. thumb: () => (
  262. <div class={styles.sliderPoint}>
  263. {videoFroms.defaultSpeed}
  264. <span>x</span>
  265. </div>
  266. )
  267. }}
  268. </NSlider>
  269. <i
  270. class={[styles.iconCut]}
  271. onClick={() => {
  272. if (videoFroms.defaultSpeed <= 0.5) {
  273. return;
  274. }
  275. if (videoItem.value) {
  276. videoFroms.defaultSpeed =
  277. (videoFroms.defaultSpeed * 10 - 1) / 10;
  278. videoItem.value.playbackRate(videoFroms.defaultSpeed);
  279. }
  280. }}></i>
  281. </div>
  282. </div>
  283. </div>
  284. );
  285. }
  286. });