audio-pay.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import {
  2. defineComponent,
  3. reactive,
  4. ref,
  5. nextTick,
  6. onMounted,
  7. watch
  8. } from 'vue';
  9. import styles from './audio.module.less';
  10. import iconplay from '../image/icon-pause.png';
  11. import iconpause from '../image/icon-play.png';
  12. import iconReplay from '../image/icon-replay.png';
  13. import { NSlider } from 'naive-ui';
  14. import Vudio from 'vudio.js';
  15. import tickMp3 from '../image/tick.mp3';
  16. export default defineComponent({
  17. name: 'audio-play',
  18. props: {
  19. item: {
  20. type: Object,
  21. default: () => {
  22. return {};
  23. }
  24. },
  25. activeStatus: {
  26. type: Boolean,
  27. default: false
  28. },
  29. isEmtry: {
  30. type: Boolean,
  31. default: false
  32. },
  33. imagePos: {
  34. type: String,
  35. default: 'left'
  36. }
  37. },
  38. emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset'],
  39. setup(props, { emit, expose }) {
  40. const audioForms = reactive({
  41. paused: true,
  42. currentTimeNum: 0,
  43. currentTime: '00:00',
  44. durationNum: 0,
  45. duration: '00:00',
  46. showBar: true,
  47. afterMa3: true,
  48. count: 0,
  49. previousBytesLoaded: 0,
  50. previousTime: Date.now()
  51. });
  52. const canvas: any = ref();
  53. const audio: any = ref();
  54. let vudio: any = null;
  55. // 切换音频播放
  56. const onToggleAudio = (e?: any) => {
  57. e?.stopPropagation();
  58. // console.log(audio.value.paused, 'audio.value.paused');
  59. if (audio.value.paused) {
  60. audio.value.play();
  61. audioForms.afterMa3 = false;
  62. } else {
  63. audio.value?.pause();
  64. }
  65. audioForms.paused = audio.value?.paused;
  66. e?.target?.focus();
  67. emit('togglePlay', audioForms.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. //
  101. const toggleHideControl = (isShow: false) => {
  102. audioForms.showBar = isShow;
  103. };
  104. const onReplay = () => {
  105. if (!audio.value) return;
  106. audio.value.currentTime = 0;
  107. };
  108. let vudio1 = null;
  109. const canvas1: any = ref();
  110. const audio1: any = ref();
  111. nextTick(() => {
  112. vudio1 = new Vudio(audio1.value, canvas1.value, {
  113. effect: 'waveform',
  114. accuracy: 256,
  115. width: 1024,
  116. height: 600,
  117. waveform: {
  118. maxHeight: 200,
  119. color: [
  120. [0, '#44D1FF'],
  121. [0.5, '#44D1FF'],
  122. [0.5, '#198CFE'],
  123. [1, '#198CFE']
  124. ],
  125. prettify: false
  126. }
  127. });
  128. vudio1.dance();
  129. });
  130. watch(
  131. () => props.activeStatus,
  132. (val: any) => {
  133. // console.log(val, 'val');
  134. audioForms.count = 0;
  135. if (val && props.item.autoPlay) {
  136. vudio = null;
  137. onToggleAudio();
  138. } else {
  139. audio.value.pause();
  140. }
  141. }
  142. );
  143. // onMounted(() => {
  144. // console.log(props.item, 'eeeee');
  145. // });
  146. expose({
  147. toggleHideControl
  148. });
  149. return () => (
  150. <div class={styles.audioWrap}>
  151. <div class={styles.audioContainer}>
  152. <audio
  153. ref={audio}
  154. crossorigin="anonymous"
  155. src={props.item.content + '?time=1'}
  156. onEnded={() => {
  157. audioForms.paused = true;
  158. emit('ended');
  159. }}
  160. onTimeupdate={() => {
  161. audioForms.currentTime = timeFormat(
  162. Math.round(audio.value?.currentTime || 0)
  163. );
  164. audioForms.currentTimeNum = audio.value?.currentTime || 0;
  165. if (audioForms.count <= 1) {
  166. audioForms.count += 1;
  167. onInit(audio.value, canvas.value);
  168. }
  169. }}
  170. onLoadedmetadata={() => {
  171. audioForms.duration = timeFormat(
  172. Math.round(audio.value?.duration)
  173. );
  174. audioForms.durationNum = audio.value?.duration;
  175. if (props.item.autoPlay && audio.value && props.activeStatus) {
  176. // audio.value.play();
  177. onToggleAudio();
  178. }
  179. if (audio.value) {
  180. audio.value.stop = () => {
  181. audio.value?.pause();
  182. audioForms.paused = true;
  183. emit('togglePlay', audioForms.paused);
  184. };
  185. audio.value.onPlay = () => {
  186. audio.value?.play();
  187. audioForms.paused = false;
  188. onInit(audio.value, canvas.value);
  189. emit('togglePlay', audioForms.paused);
  190. };
  191. }
  192. emit('loadedmetadata', audio.value);
  193. }}
  194. onProgress={(e: any) => {
  195. console.log(e, 'loadedmetadata onProgress');
  196. const currentTime = Date.now();
  197. const videoElement = e.target;
  198. console.log(videoElement, audio.value.buffered);
  199. const currentBytesLoaded = videoElement.buffered.length
  200. ? videoElement.buffered.end(0) * videoElement.duration
  201. : 0;
  202. if (currentBytesLoaded > audioForms.previousBytesLoaded) {
  203. const timeDiff = (currentTime - audioForms.previousTime) / 1000; // Time difference in seconds
  204. const bytesDiff =
  205. currentBytesLoaded - audioForms.previousBytesLoaded; // Bytes downloaded since last check
  206. const speed = bytesDiff / timeDiff; // Bytes per second
  207. const speedInKbps = (speed / 1024).toFixed(2); // Convert to KB/s
  208. // speedDisplay.textContent = `Download speed: ${speedInKbps} KB/s`;
  209. console.log(`Download speed: ${speedInKbps} KB/s`);
  210. audioForms.previousBytesLoaded = currentBytesLoaded;
  211. audioForms.previousTime = currentTime;
  212. }
  213. }}></audio>
  214. <canvas ref={canvas}></canvas>
  215. {audioForms.afterMa3 && (
  216. <div class={styles.tempVudio}>
  217. <audio ref={audio1} src={tickMp3} />
  218. <canvas ref={canvas1}></canvas>
  219. </div>
  220. )}
  221. </div>
  222. <div
  223. class={[
  224. styles.controls,
  225. audioForms.showBar ? '' : styles.sectionAnimate
  226. ]}
  227. onClick={(e: MouseEvent) => {
  228. e.stopPropagation();
  229. emit('reset');
  230. }}>
  231. <div class={styles.slider}>
  232. <NSlider
  233. value={audioForms.currentTimeNum}
  234. step={0.01}
  235. max={audioForms.durationNum}
  236. tooltip={false}
  237. onUpdate:value={(val: number) => {
  238. audio.value.currentTime = val;
  239. audioForms.currentTimeNum = val;
  240. audioForms.currentTime = timeFormat(Math.round(val || 0));
  241. }}
  242. />
  243. </div>
  244. <div class={styles.tools}>
  245. {props.imagePos === 'right' ? (
  246. <>
  247. <div class={styles.actions}>
  248. <div class={styles.time}>
  249. <div
  250. class="plyr__time plyr__time--current"
  251. aria-label="Current time">
  252. {audioForms.currentTime}
  253. </div>
  254. <span class={styles.line}>/</span>
  255. <div
  256. class="plyr__time plyr__time--duration"
  257. aria-label="Duration">
  258. {audioForms.duration}
  259. </div>
  260. </div>
  261. </div>
  262. <div class={styles.actions}>
  263. <div class={styles.actionWrap}>
  264. <button class={styles.iconReplay} onClick={onReplay}>
  265. <img src={iconReplay} />
  266. </button>
  267. <div class={styles.actionBtn} onClick={onToggleAudio}>
  268. {audioForms.paused ? (
  269. <img class={styles.playIcon} src={iconplay} />
  270. ) : (
  271. <img class={styles.playIcon} src={iconpause} />
  272. )}
  273. </div>
  274. <div class={styles.downloadSpeed}>37.8M/s</div>
  275. </div>
  276. </div>
  277. </>
  278. ) : (
  279. <>
  280. <div class={styles.actions}>
  281. <div class={styles.actionWrap}>
  282. <div class={styles.actionBtn} onClick={onToggleAudio}>
  283. {audioForms.paused ? (
  284. <img class={styles.playIcon} src={iconplay} />
  285. ) : (
  286. <img class={styles.playIcon} src={iconpause} />
  287. )}
  288. </div>
  289. <button class={styles.iconReplay} onClick={onReplay}>
  290. <img src={iconReplay} />
  291. </button>
  292. <div class={styles.downloadSpeed}>37.8M/s</div>
  293. </div>
  294. </div>
  295. <div class={styles.actions}>
  296. <div class={styles.time}>
  297. <div
  298. class="plyr__time plyr__time--current"
  299. aria-label="Current time">
  300. {audioForms.currentTime}
  301. </div>
  302. <span class={styles.line}>/</span>
  303. <div
  304. class="plyr__time plyr__time--duration"
  305. aria-label="Duration">
  306. {audioForms.duration}
  307. </div>
  308. </div>
  309. </div>
  310. </>
  311. )}
  312. </div>
  313. </div>
  314. </div>
  315. );
  316. }
  317. });