index.tsx 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. import { defineComponent, reactive, ref, nextTick } from 'vue';
  2. import styles from './index.module.less';
  3. import iconplay from '@views/attend-class/image/icon-pause.png';
  4. import iconpause from '@views/attend-class/image/icon-play.png';
  5. import iconReplay from '@views/attend-class/image/icon-replay.png';
  6. import iconPreviewDownload from '@views/attend-class/image/icon-preivew-download.png';
  7. import iconFullscreen from '@views/attend-class/image/icon-fullscreen.png';
  8. import iconFullscreenExit from '@views/attend-class/image/icon-fullscreen-exit.png';
  9. import { NSlider, useMessage } from 'naive-ui';
  10. import Vudio from 'vudio.js';
  11. import tickMp3 from '@views/attend-class/image/tick.mp3';
  12. import { saveAs } from 'file-saver';
  13. import { exitFullscreen } from '/src/utils';
  14. export default defineComponent({
  15. name: 'audio-play',
  16. props: {
  17. item: {
  18. type: Object,
  19. default: () => {
  20. return {};
  21. }
  22. },
  23. isEmtry: {
  24. type: Boolean,
  25. default: false
  26. },
  27. isDownload: {
  28. type: Boolean,
  29. default: false
  30. },
  31. fullscreen: {
  32. type: Boolean,
  33. default: false
  34. }
  35. },
  36. setup(props) {
  37. const videoId =
  38. 'vFullscreen' + Date.now() + Math.floor(Math.random() * 100);
  39. const message = useMessage();
  40. const audioForms = reactive({
  41. isFullScreen: false, // 是否全屏
  42. paused: true,
  43. currentTimeNum: 0,
  44. currentTime: '00:00',
  45. durationNum: 0,
  46. duration: '00:00',
  47. showBar: true,
  48. afterMa3: true,
  49. timer: null as any
  50. });
  51. const canvas: any = ref();
  52. const audio: any = ref();
  53. let vudio: any = null;
  54. // 切换音频播放
  55. const onToggleAudio = (e?: MouseEvent) => {
  56. e?.stopPropagation();
  57. if (audio.value.paused) {
  58. onInit(audio.value, canvas.value);
  59. audio.value.play();
  60. audioForms.afterMa3 = false;
  61. setModelOpen();
  62. } else {
  63. audio.value.pause();
  64. clearTimeout(audioForms.timer);
  65. audioForms.showBar = true;
  66. }
  67. audioForms.paused = audio.value.paused;
  68. };
  69. const onInit = (audio: undefined, canvas: undefined) => {
  70. if (!vudio) {
  71. vudio = new Vudio(audio, canvas, {
  72. effect: 'waveform',
  73. accuracy: 256,
  74. width: 1024,
  75. height: 600,
  76. waveform: {
  77. maxHeight: 200,
  78. color: [
  79. [0, '#44D1FF'],
  80. [0.5, '#44D1FF'],
  81. [0.5, '#198CFE'],
  82. [1, '#198CFE']
  83. ],
  84. prettify: false
  85. }
  86. });
  87. vudio.dance();
  88. }
  89. };
  90. // 对时间进行格式化
  91. const timeFormat = (num: number) => {
  92. if (num > 0) {
  93. const m = Math.floor(num / 60);
  94. const s = num % 60;
  95. return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
  96. } else {
  97. return '00:00';
  98. }
  99. };
  100. const onReplay = () => {
  101. if (!audio.value) return;
  102. audio.value.currentTime = 0;
  103. console.log(props.item);
  104. };
  105. // 下载资源
  106. const onDownload = () => {
  107. if (!props.item.content) {
  108. message.error('下载失败');
  109. return;
  110. }
  111. const fileUrl = props.item.content;
  112. const filename = props.item.title;
  113. // 发起Fetch请求
  114. fetch(fileUrl)
  115. .then(response => response.blob())
  116. .then(blob => {
  117. saveAs(blob, filename || new Date().getTime() + '.mp3');
  118. })
  119. .catch(() => {
  120. message.error('下载失败');
  121. });
  122. };
  123. let vudio1 = null;
  124. const canvas1: any = ref();
  125. const audio1: any = ref();
  126. nextTick(() => {
  127. vudio1 = new Vudio(audio1.value, canvas1.value, {
  128. effect: 'waveform',
  129. accuracy: 256,
  130. width: 1024,
  131. height: 600,
  132. waveform: {
  133. maxHeight: 200,
  134. color: [
  135. [0, '#44D1FF'],
  136. [0.5, '#44D1FF'],
  137. [0.5, '#198CFE'],
  138. [1, '#198CFE']
  139. ],
  140. prettify: false
  141. }
  142. });
  143. vudio1.dance();
  144. });
  145. /** 全屏 */
  146. const isElementFullscreen = (element: any) => {
  147. const dom: any = document;
  148. return (
  149. element ===
  150. (dom.fullscreenElement ||
  151. dom.webkitFullscreenElement ||
  152. dom.mozFullScreenElement ||
  153. dom.msFullscreenElement)
  154. );
  155. };
  156. const onFullScreen = () => {
  157. const el: any = document.querySelector('#' + videoId);
  158. // //进入全屏
  159. if (el) {
  160. if (isElementFullscreen(el)) {
  161. exitFullscreen();
  162. audioForms.isFullScreen = false;
  163. } else {
  164. (el.requestFullscreen && el.requestFullscreen()) ||
  165. (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
  166. (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
  167. (el.msRequestFullscreen && el.msRequestFullscreen());
  168. audioForms.isFullScreen = true;
  169. }
  170. // audioForms.isFullScreen = isElementFullscreen(el);
  171. }
  172. };
  173. /** 延迟收起模态框 */
  174. const setModelOpen2 = () => {
  175. clearTimeout(audioForms.timer);
  176. audioForms.showBar = !audioForms.showBar;
  177. audioForms.timer = setTimeout(() => {
  178. audioForms.showBar = false;
  179. }, 3000);
  180. };
  181. /** 延迟收起模态框 */
  182. const setModelOpen = () => {
  183. clearTimeout(audioForms.timer);
  184. audioForms.showBar = true;
  185. audioForms.timer = setTimeout(() => {
  186. audioForms.showBar = false;
  187. }, 3000);
  188. };
  189. return () => (
  190. <div class={styles.audioWrap} id={videoId} onClick={setModelOpen2}>
  191. <div class={styles.audioContainer}>
  192. <audio
  193. ref={audio}
  194. crossorigin="anonymous"
  195. src={props.item.content + '?time=1'}
  196. onEnded={() => {
  197. audioForms.paused = true;
  198. }}
  199. onPause={() => {
  200. clearTimeout(audioForms.timer);
  201. audioForms.showBar = true;
  202. }}
  203. onTimeupdate={() => {
  204. audioForms.currentTime = timeFormat(
  205. Math.floor(audio.value?.currentTime || 0)
  206. );
  207. audioForms.currentTimeNum = audio.value?.currentTime || 0;
  208. }}
  209. onLoadedmetadata={() => {
  210. audioForms.duration = timeFormat(
  211. Math.floor(audio.value?.duration)
  212. );
  213. audioForms.durationNum = audio.value?.duration;
  214. }}></audio>
  215. <canvas ref={canvas}></canvas>
  216. {audioForms.afterMa3 && (
  217. <div class={styles.tempVudio}>
  218. <audio ref={audio1} src={tickMp3} />
  219. <canvas ref={canvas1}></canvas>
  220. </div>
  221. )}
  222. </div>
  223. <div
  224. class={[
  225. styles.controls,
  226. audioForms.showBar ? '' : styles.sectionAnimate
  227. ]}
  228. onClick={(e: MouseEvent) => {
  229. e.stopPropagation();
  230. setModelOpen();
  231. }}>
  232. <div class={styles.actions}>
  233. <div class={styles.actionWrap}>
  234. <button class={styles.actionBtn} onClick={onToggleAudio}>
  235. {audioForms.paused ? (
  236. <img class={styles.playIcon} src={iconplay} />
  237. ) : (
  238. <img class={styles.playIcon} src={iconpause} />
  239. )}
  240. </button>
  241. <button class={styles.iconReplay} onClick={onReplay}>
  242. <img src={iconReplay} />
  243. </button>
  244. </div>
  245. </div>
  246. <div class={styles.slider}>
  247. <NSlider
  248. value={audioForms.currentTimeNum}
  249. step={0.01}
  250. max={audioForms.durationNum}
  251. tooltip={false}
  252. onUpdate:value={(val: number) => {
  253. audio.value.currentTime = val;
  254. audioForms.currentTimeNum = val;
  255. audioForms.currentTime = timeFormat(Math.round(val || 0));
  256. }}
  257. />
  258. </div>
  259. <div class={styles.actions}>
  260. <div class={styles.time}>
  261. <div
  262. class="plyr__time plyr__time--current"
  263. aria-label="Current time">
  264. {audioForms.currentTime}
  265. </div>
  266. <span class={styles.line}>/</span>
  267. <div
  268. class="plyr__time plyr__time--duration"
  269. aria-label="Duration">
  270. {audioForms.duration}
  271. </div>
  272. </div>
  273. <div class={styles.actionWrap}>
  274. {props.isDownload && (
  275. <button class={styles.iconDownload} onClick={onDownload}>
  276. <img src={iconPreviewDownload} />
  277. </button>
  278. )}
  279. {props.fullscreen && (
  280. <button class={styles.iconDownload} onClick={onFullScreen}>
  281. <img
  282. src={
  283. audioForms.isFullScreen
  284. ? iconFullscreenExit
  285. : iconFullscreen
  286. }
  287. />
  288. </button>
  289. )}
  290. </div>
  291. </div>
  292. </div>
  293. </div>
  294. );
  295. }
  296. });