index.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { defineComponent, nextTick, reactive, toRefs, watch } from 'vue';
  2. import { ref } from 'vue';
  3. import styles from './index.module.less';
  4. import iconLoop from '../../image/icon-loop.png';
  5. import iconLoopActive from '../../image/icon-loop-active.png';
  6. import iconplay from '../../image/icon-play.png';
  7. import iconpause from '../../image/icon-pause.png';
  8. import { NSlider } from 'naive-ui';
  9. import Vudio from 'vudio.js';
  10. import { getSecondRPM } from '@/helpers/utils';
  11. import { Slider, showToast } from 'vant';
  12. import tickMp3 from '../../image/tick.mp3';
  13. export default defineComponent({
  14. name: 'video-play',
  15. props: {
  16. item: {
  17. type: Object,
  18. default: () => {
  19. return {};
  20. }
  21. },
  22. show: {
  23. type: Boolean,
  24. default: false
  25. },
  26. pageVisibility: {
  27. type: String,
  28. default: ''
  29. },
  30. showModel: {
  31. type: Boolean,
  32. default: false
  33. },
  34. isEmtry: {
  35. type: Boolean,
  36. default: false
  37. }
  38. },
  39. emits: ['loadedmetadata', 'togglePlay', 'ended', 'reset', 'close'],
  40. setup(props, { emit }) {
  41. const { item, isEmtry } = toRefs(props);
  42. const data = reactive({
  43. timer: null as any,
  44. currentTime: 0,
  45. duration: 0.1,
  46. loop: false,
  47. dragStatus: false, // 是否开始拖动
  48. playState: 'pause' as 'play' | 'pause',
  49. vudio: null as any,
  50. afterMa3: true,
  51. count: 0
  52. });
  53. const canvasRef: any = ref();
  54. const audioRef: any = ref();
  55. const contetRef = ref();
  56. watch(
  57. () => props.show,
  58. val => {
  59. data.count = 0;
  60. if (val && props.item.autoPlay) {
  61. // data.count = 0;
  62. // audioRef.value.src = item.value.content;
  63. onToggleAudio();
  64. } else {
  65. audioRef.value.pause();
  66. // audioRef.value.src = '';
  67. data.playState = 'pause';
  68. // data.vudio = null;
  69. }
  70. }
  71. );
  72. // watch(
  73. // () => props.item,
  74. // () => {
  75. // // data.count = 0;
  76. // // audioRef.value.src = item.value.content;
  77. // onToggleAudio();
  78. // }
  79. // );
  80. watch(
  81. () => props.pageVisibility,
  82. val => {
  83. if (val === 'hidden' && props.show) {
  84. audioRef.value.pause();
  85. data.playState = 'pause';
  86. }
  87. }
  88. );
  89. // 切换音频播放
  90. const onToggleAudio = () => {
  91. if (audioRef.value.paused) {
  92. audioRef.value.play();
  93. data.playState = 'play';
  94. //
  95. nextTick(() => {
  96. // onInit(audioRef.value, canvasRef.value);
  97. // setTimeout(() => {
  98. // onInit(audioRef.value, canvasRef.value);
  99. // }, 100);
  100. // console.log(data.vudio, 'data.vudio');
  101. // if (data.vudio) {
  102. // data.vudio.dance();
  103. // }
  104. });
  105. } else {
  106. audioRef.value.pause();
  107. data.playState = 'pause';
  108. }
  109. };
  110. const onInit = (audio: undefined, canvas: undefined) => {
  111. if (!data.vudio && data.count > 1) {
  112. console.log(data.vudio, 'data.vudio');
  113. try {
  114. const rect = contetRef.value.getBoundingClientRect() || {
  115. width: 200,
  116. height: 200
  117. };
  118. data.vudio = new Vudio(audio, canvas, {
  119. effect: 'waveform',
  120. accuracy: 128,
  121. width: rect.width,
  122. height: rect.height,
  123. waveform: {
  124. maxHeight: 160,
  125. color: [
  126. [0, '#44D1FF'],
  127. [0.5, '#44D1FF'],
  128. [0.5, '#198CFE'],
  129. [1, '#198CFE']
  130. ],
  131. prettify: false
  132. }
  133. });
  134. data.vudio.dance();
  135. } catch (e) {
  136. console.log(e, 'e');
  137. }
  138. } else {
  139. }
  140. };
  141. /** 加载成功 */
  142. const onLoadedmetadata = () => {
  143. data.duration = audioRef.value?.duration;
  144. // onInit(audioRef.value, canvasRef.value);
  145. if (props.item.autoPlay && audioRef.value && props.show) {
  146. // data.vudio = null;
  147. // data.count = 0;
  148. onToggleAudio();
  149. }
  150. // if (audioRef.value) {
  151. // audioRef.value.stop = () => {
  152. // audioRef.value?.pause();
  153. // };
  154. // audioRef.value.onPlay = () => {
  155. // audioRef.value?.play();
  156. // onInit(audioRef.value, canvasRef.value);
  157. // };
  158. // }
  159. emit('loadedmetadata', audioRef.value)
  160. };
  161. /** 改变播放时间 */
  162. const handleChangeTime = (val: number) => {
  163. data.currentTime = val;
  164. clearTimeout(data.timer);
  165. data.timer = setTimeout(() => {
  166. audioRef.value.currentTime = val;
  167. data.timer = null;
  168. }, 60);
  169. };
  170. /** 播放结束 */
  171. const onEnded = () => {
  172. data.playState = 'pause';
  173. // emit('ended');
  174. };
  175. let vudio1 = null;
  176. const canvas1: any = ref();
  177. const audio1: any = ref();
  178. nextTick(() => {
  179. const rect = contetRef.value.getBoundingClientRect() || {
  180. width: 200,
  181. height: 200
  182. };
  183. vudio1 = new Vudio(audio1.value, canvas1.value, {
  184. effect: 'waveform',
  185. accuracy: 128,
  186. width: rect.width,
  187. height: rect.height,
  188. waveform: {
  189. maxHeight: 160,
  190. color: [
  191. [0, '#44D1FF'],
  192. [0.5, '#44D1FF'],
  193. [0.5, '#198CFE'],
  194. [1, '#198CFE']
  195. ],
  196. prettify: false
  197. }
  198. });
  199. vudio1.dance();
  200. });
  201. return () => (
  202. <div class={styles.videoWrap}>
  203. <div class={styles.content}>
  204. <div ref={contetRef} class={styles.contentWrap}>
  205. <canvas ref={canvasRef}></canvas>
  206. <audio
  207. src={isEmtry.value ? '' : item.value.content}
  208. ref={audioRef}
  209. loop={data.loop}
  210. onLoadedmetadata={onLoadedmetadata}
  211. onTimeupdate={() => {
  212. if (data.timer && data.playState === 'pause') return;
  213. if (data.count <= 1) {
  214. data.count += 1;
  215. onInit(audioRef.value, canvasRef.value);
  216. }
  217. // 开始拖动时也不能更新
  218. if (data.dragStatus) return;
  219. data.currentTime = audioRef.value?.currentTime;
  220. }}
  221. onError={() => {
  222. console.log('error');
  223. audioRef.value.pause();
  224. data.playState = 'pause';
  225. }}
  226. onPause={() => {
  227. console.log('pause');
  228. data.playState = 'pause';
  229. }}
  230. onEnded={onEnded}
  231. crossorigin="anonymous"
  232. playsinline="false"></audio>
  233. {data.afterMa3 && (
  234. <div class={styles.tempVudio}>
  235. <audio ref={audio1} src={tickMp3} />
  236. <canvas ref={canvas1}></canvas>
  237. </div>
  238. )}
  239. </div>
  240. </div>
  241. <div
  242. class={[styles.controls, props.showModel ? '' : styles.hide]}
  243. onClick={(e: Event) => {
  244. e.stopPropagation();
  245. }}
  246. onTouchmove={(e: TouchEvent) => {
  247. emit('close');
  248. }}>
  249. <div class={styles.time}>
  250. <div>{getSecondRPM(data.currentTime)}</div>
  251. <div>{getSecondRPM(data.duration)}</div>
  252. </div>
  253. <div class={[styles.slider]}>
  254. <Slider
  255. step={0.01}
  256. class={styles.timeProgress}
  257. v-model={data.currentTime}
  258. max={data.duration}
  259. onUpdate:modelValue={val => {
  260. handleChangeTime(val);
  261. }}
  262. onDragStart={() => {
  263. data.dragStatus = true;
  264. console.log('onDragStart');
  265. }}
  266. onDragEnd={() => {
  267. data.dragStatus = false;
  268. console.log('onDragEnd');
  269. }}
  270. />
  271. </div>
  272. <div class={styles.actions} onClick={() => emit('close')}>
  273. <div class={styles.actionBtn} onClick={() => onToggleAudio()}>
  274. <img src={data.playState === 'pause' ? iconplay : iconpause} />
  275. </div>
  276. <div
  277. class={styles.actionBtn}
  278. onClick={() => {
  279. data.loop = !data.loop;
  280. if (data.loop) {
  281. setTimeout(() => {
  282. showToast('已打开循环播放');
  283. }, 60);
  284. } else {
  285. setTimeout(() => {
  286. showToast('已关闭循环播放');
  287. }, 60);
  288. }
  289. }}>
  290. <img src={data.loop ? iconLoopActive : iconLoop} />
  291. </div>
  292. </div>
  293. </div>
  294. </div>
  295. );
  296. }
  297. });